2019-12-17 05:25:56 -05:00
|
|
|
package hcl2template
|
|
|
|
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
"github.com/hashicorp/go-version"
|
2019-12-17 05:25:56 -05:00
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
2020-12-17 16:29:25 -05:00
|
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
|
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
2020-02-17 10:36:19 -05:00
|
|
|
"github.com/hashicorp/packer/builder/null"
|
Hcl2 input variables, local variables and functions (#8588)
Mainly redefine or reused what Terraform did.
* allow to used `variables`, `variable` and `local` blocks
* import the following functions and their docs from Terraform: abs, abspath, basename, base64decode, base64encode, bcrypt, can, ceil, chomp, chunklist, cidrhost, cidrnetmask, cidrsubnet, cidrsubnets, coalesce, coalescelist, compact, concat, contains, convert, csvdecode, dirname, distinct, element, file, fileexists, fileset, flatten, floor, format, formatdate, formatlist, indent, index, join, jsondecode, jsonencode, keys, length, log, lookup, lower, max, md5, merge, min, parseint, pathexpand, pow, range, reverse, rsadecrypt, setintersection, setproduct, setunion, sha1, sha256, sha512, signum, slice, sort, split, strrev, substr, timestamp, timeadd, title, trim, trimprefix, trimspace, trimsuffix, try, upper, urlencode, uuidv4, uuidv5, values, yamldecode, yamlencode, zipmap.
2020-02-06 05:49:21 -05:00
|
|
|
. "github.com/hashicorp/packer/hcl2template/internal"
|
2019-12-17 05:25:56 -05:00
|
|
|
"github.com/hashicorp/packer/packer"
|
|
|
|
"github.com/zclconf/go-cty/cty"
|
|
|
|
)
|
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
const lockedVersion = "v1.5.0"
|
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
func getBasicParser() *Parser {
|
|
|
|
return &Parser{
|
2021-02-02 12:05:04 -05:00
|
|
|
CorePackerVersion: version.Must(version.NewSemver(lockedVersion)),
|
|
|
|
CorePackerVersionString: lockedVersion,
|
|
|
|
Parser: hclparse.NewParser(),
|
|
|
|
PluginConfig: &packer.PluginConfig{
|
|
|
|
Builders: packer.MapOfBuilder{
|
|
|
|
"amazon-ebs": func() (packersdk.Builder, error) { return &MockBuilder{}, nil },
|
|
|
|
"virtualbox-iso": func() (packersdk.Builder, error) { return &MockBuilder{}, nil },
|
|
|
|
"null": func() (packersdk.Builder, error) { return &null.Builder{}, nil },
|
|
|
|
},
|
|
|
|
Provisioners: packer.MapOfProvisioner{
|
|
|
|
"shell": func() (packersdk.Provisioner, error) { return &MockProvisioner{}, nil },
|
|
|
|
"file": func() (packersdk.Provisioner, error) { return &MockProvisioner{}, nil },
|
|
|
|
},
|
|
|
|
PostProcessors: packer.MapOfPostProcessor{
|
|
|
|
"amazon-import": func() (packersdk.PostProcessor, error) { return &MockPostProcessor{}, nil },
|
|
|
|
"manifest": func() (packersdk.PostProcessor, error) { return &MockPostProcessor{}, nil },
|
|
|
|
},
|
|
|
|
DataSources: packer.MapOfDatasource{
|
|
|
|
"amazon-ami": func() (packersdk.Datasource, error) { return &MockDatasource{}, nil },
|
|
|
|
},
|
2021-01-20 04:37:16 -05:00
|
|
|
},
|
2019-12-17 05:25:56 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type parseTestArgs struct {
|
|
|
|
filename string
|
Hcl2 input variables, local variables and functions (#8588)
Mainly redefine or reused what Terraform did.
* allow to used `variables`, `variable` and `local` blocks
* import the following functions and their docs from Terraform: abs, abspath, basename, base64decode, base64encode, bcrypt, can, ceil, chomp, chunklist, cidrhost, cidrnetmask, cidrsubnet, cidrsubnets, coalesce, coalescelist, compact, concat, contains, convert, csvdecode, dirname, distinct, element, file, fileexists, fileset, flatten, floor, format, formatdate, formatlist, indent, index, join, jsondecode, jsonencode, keys, length, log, lookup, lower, max, md5, merge, min, parseint, pathexpand, pow, range, reverse, rsadecrypt, setintersection, setproduct, setunion, sha1, sha256, sha512, signum, slice, sort, split, strrev, substr, timestamp, timeadd, title, trim, trimprefix, trimspace, trimsuffix, try, upper, urlencode, uuidv4, uuidv5, values, yamldecode, yamlencode, zipmap.
2020-02-06 05:49:21 -05:00
|
|
|
vars map[string]string
|
2020-03-12 09:27:56 -04:00
|
|
|
varFiles []string
|
2019-12-17 05:25:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
type parseTest struct {
|
|
|
|
name string
|
|
|
|
parser *Parser
|
|
|
|
args parseTestArgs
|
|
|
|
|
|
|
|
parseWantCfg *PackerConfig
|
|
|
|
parseWantDiags bool
|
|
|
|
parseWantDiagHasErrors bool
|
|
|
|
|
2020-12-09 06:39:54 -05:00
|
|
|
getBuildsWantBuilds []packersdk.Build
|
2019-12-17 05:25:56 -05:00
|
|
|
getBuildsWantDiags bool
|
|
|
|
// getBuildsWantDiagHasErrors bool
|
|
|
|
}
|
|
|
|
|
|
|
|
func testParse(t *testing.T, tests []parseTest) {
|
2019-12-17 10:46:36 -05:00
|
|
|
t.Helper()
|
2019-12-17 05:25:56 -05:00
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2020-04-29 10:36:40 -04:00
|
|
|
gotCfg, gotDiags := tt.parser.Parse(tt.args.filename, tt.args.varFiles, tt.args.vars)
|
2021-01-20 04:37:16 -05:00
|
|
|
moreDiags := gotCfg.Initialize(packer.InitializeOptions{})
|
2020-07-24 04:58:03 -04:00
|
|
|
gotDiags = append(gotDiags, moreDiags...)
|
2019-12-17 05:25:56 -05:00
|
|
|
if tt.parseWantDiags == (gotDiags == nil) {
|
2020-03-03 05:15:56 -05:00
|
|
|
t.Fatalf("Parser.parse() unexpected %q diagnostics.", gotDiags)
|
2019-12-17 05:25:56 -05:00
|
|
|
}
|
|
|
|
if tt.parseWantDiagHasErrors != gotDiags.HasErrors() {
|
|
|
|
t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags)
|
|
|
|
}
|
2020-11-02 11:43:21 -05:00
|
|
|
if diff := cmp.Diff(tt.parseWantCfg, gotCfg, cmpOpts...); diff != "" {
|
2019-12-17 05:25:56 -05:00
|
|
|
t.Fatalf("Parser.parse() wrong packer config. %s", diff)
|
|
|
|
}
|
2020-02-21 06:12:30 -05:00
|
|
|
|
|
|
|
if gotCfg != nil && !tt.parseWantDiagHasErrors {
|
2020-11-02 11:52:19 -05:00
|
|
|
if diff := cmp.Diff(tt.parseWantCfg.InputVariables, gotCfg.InputVariables, cmpOpts...); diff != "" {
|
|
|
|
t.Fatalf("Parser.parse() unexpected input vars. %s", diff)
|
2020-02-21 06:12:30 -05:00
|
|
|
}
|
|
|
|
|
2020-11-02 11:52:19 -05:00
|
|
|
if diff := cmp.Diff(tt.parseWantCfg.LocalVariables, gotCfg.LocalVariables, cmpOpts...); diff != "" {
|
|
|
|
t.Fatalf("Parser.parse() unexpected local vars. %s", diff)
|
2020-02-21 06:12:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
if gotDiags.HasErrors() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-08 11:01:42 -04:00
|
|
|
gotBuilds, gotDiags := gotCfg.GetBuilds(packer.GetBuildsOptions{})
|
2019-12-17 05:25:56 -05:00
|
|
|
if tt.getBuildsWantDiags == (gotDiags == nil) {
|
|
|
|
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
|
|
|
|
}
|
2020-11-02 05:49:40 -05:00
|
|
|
if diff := cmp.Diff(tt.getBuildsWantBuilds, gotBuilds, cmpOpts...); diff != "" {
|
2019-12-17 05:25:56 -05:00
|
|
|
t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
func testParse_only_Parse(t *testing.T, tests []parseTest) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
gotCfg, gotDiags := tt.parser.Parse(tt.args.filename, tt.args.varFiles, tt.args.vars)
|
|
|
|
if tt.parseWantDiags == (gotDiags == nil) {
|
|
|
|
t.Fatalf("Parser.parse() unexpected %q diagnostics.", gotDiags)
|
|
|
|
}
|
|
|
|
if tt.parseWantDiagHasErrors != gotDiags.HasErrors() {
|
|
|
|
t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags)
|
|
|
|
}
|
|
|
|
if diff := cmp.Diff(tt.parseWantCfg, gotCfg, cmpOpts...); diff != "" {
|
|
|
|
t.Fatalf("Parser.parse() wrong packer config. %s", diff)
|
|
|
|
}
|
|
|
|
|
|
|
|
if gotCfg != nil && !tt.parseWantDiagHasErrors {
|
|
|
|
if diff := cmp.Diff(tt.parseWantCfg.InputVariables, gotCfg.InputVariables, cmpOpts...); diff != "" {
|
|
|
|
t.Fatalf("Parser.parse() unexpected input vars. %s", diff)
|
|
|
|
}
|
|
|
|
|
|
|
|
if diff := cmp.Diff(tt.parseWantCfg.LocalVariables, gotCfg.LocalVariables, cmpOpts...); diff != "" {
|
|
|
|
t.Fatalf("Parser.parse() unexpected local vars. %s", diff)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if gotDiags.HasErrors() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
var (
|
|
|
|
// everything in the tests is a basicNestedMockConfig this allow to test
|
|
|
|
// each known type to packer ( and embedding ) in one go.
|
|
|
|
basicNestedMockConfig = NestedMockConfig{
|
|
|
|
String: "string",
|
|
|
|
Int: 42,
|
|
|
|
Int64: 43,
|
|
|
|
Bool: true,
|
|
|
|
Trilean: config.TriTrue,
|
|
|
|
Duration: 10 * time.Second,
|
|
|
|
MapStringString: map[string]string{
|
|
|
|
"a": "b",
|
|
|
|
"c": "d",
|
|
|
|
},
|
|
|
|
SliceString: []string{
|
|
|
|
"a",
|
|
|
|
"b",
|
|
|
|
"c",
|
|
|
|
},
|
2020-02-03 11:03:28 -05:00
|
|
|
SliceSliceString: [][]string{
|
|
|
|
{"a", "b"},
|
|
|
|
{"c", "d"},
|
|
|
|
},
|
2020-02-20 04:51:34 -05:00
|
|
|
Tags: []MockTag{},
|
2019-12-17 05:25:56 -05:00
|
|
|
}
|
|
|
|
|
2021-01-20 04:37:16 -05:00
|
|
|
// everything in the tests is a basicNestedMockConfig this allow to test
|
|
|
|
// each known type to packer ( and embedding ) in one go.
|
|
|
|
builderBasicNestedMockConfig = NestedMockConfig{
|
|
|
|
String: "string",
|
|
|
|
Int: 42,
|
|
|
|
Int64: 43,
|
|
|
|
Bool: true,
|
|
|
|
Trilean: config.TriTrue,
|
|
|
|
Duration: 10 * time.Second,
|
|
|
|
MapStringString: map[string]string{
|
|
|
|
"a": "b",
|
|
|
|
"c": "d",
|
|
|
|
},
|
|
|
|
SliceString: []string{
|
|
|
|
"a",
|
|
|
|
"b",
|
|
|
|
"c",
|
|
|
|
},
|
|
|
|
SliceSliceString: [][]string{
|
|
|
|
{"a", "b"},
|
|
|
|
{"c", "d"},
|
|
|
|
},
|
|
|
|
Tags: []MockTag{},
|
|
|
|
Datasource: "string",
|
|
|
|
}
|
|
|
|
|
2019-12-17 05:25:56 -05:00
|
|
|
basicMockBuilder = &MockBuilder{
|
|
|
|
Config: MockConfig{
|
2021-01-20 04:37:16 -05:00
|
|
|
NestedMockConfig: builderBasicNestedMockConfig,
|
|
|
|
Nested: builderBasicNestedMockConfig,
|
2019-12-17 05:25:56 -05:00
|
|
|
NestedSlice: []NestedMockConfig{
|
2021-01-20 04:37:16 -05:00
|
|
|
builderBasicNestedMockConfig,
|
|
|
|
builderBasicNestedMockConfig,
|
2019-12-17 05:25:56 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
basicMockProvisioner = &MockProvisioner{
|
|
|
|
Config: MockConfig{
|
2020-07-02 12:02:19 -04:00
|
|
|
NotSquashed: "value <UNKNOWN>",
|
2019-12-17 05:25:56 -05:00
|
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
|
|
Nested: basicNestedMockConfig,
|
|
|
|
NestedSlice: []NestedMockConfig{
|
2020-02-20 04:51:34 -05:00
|
|
|
{
|
|
|
|
Tags: dynamicTagList,
|
|
|
|
},
|
2019-12-17 05:25:56 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
basicMockPostProcessor = &MockPostProcessor{
|
|
|
|
Config: MockConfig{
|
2020-07-06 05:48:39 -04:00
|
|
|
NotSquashed: "value <UNKNOWN>",
|
2019-12-17 05:25:56 -05:00
|
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
|
|
Nested: basicNestedMockConfig,
|
|
|
|
NestedSlice: []NestedMockConfig{
|
2020-02-20 04:51:34 -05:00
|
|
|
{
|
|
|
|
Tags: []MockTag{},
|
|
|
|
},
|
2019-12-17 05:25:56 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2021-02-02 12:05:04 -05:00
|
|
|
basicMockPostProcessorDynamicTags = &MockPostProcessor{
|
|
|
|
Config: MockConfig{
|
|
|
|
NotSquashed: "value <UNKNOWN>",
|
|
|
|
NestedMockConfig: NestedMockConfig{
|
|
|
|
String: "string",
|
|
|
|
Int: 42,
|
|
|
|
Int64: 43,
|
|
|
|
Bool: true,
|
|
|
|
Trilean: config.TriTrue,
|
|
|
|
Duration: 10 * time.Second,
|
|
|
|
MapStringString: map[string]string{
|
|
|
|
"a": "b",
|
|
|
|
"c": "d",
|
|
|
|
},
|
|
|
|
SliceString: []string{
|
|
|
|
"a",
|
|
|
|
"b",
|
|
|
|
"c",
|
|
|
|
},
|
|
|
|
SliceSliceString: [][]string{
|
|
|
|
{"a", "b"},
|
|
|
|
{"c", "d"},
|
|
|
|
},
|
|
|
|
Tags: []MockTag{
|
|
|
|
{Key: "first_tag_key", Value: "first_tag_value"},
|
|
|
|
{Key: "Component", Value: "user-service"},
|
|
|
|
{Key: "Environment", Value: "production"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Nested: basicNestedMockConfig,
|
|
|
|
NestedSlice: []NestedMockConfig{},
|
|
|
|
},
|
|
|
|
}
|
2019-12-17 05:25:56 -05:00
|
|
|
basicMockCommunicator = &MockCommunicator{
|
|
|
|
Config: MockConfig{
|
|
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
|
|
Nested: basicNestedMockConfig,
|
|
|
|
NestedSlice: []NestedMockConfig{
|
2020-02-20 04:51:34 -05:00
|
|
|
{
|
|
|
|
Tags: []MockTag{},
|
|
|
|
},
|
2019-12-17 05:25:56 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
2020-02-20 04:51:34 -05:00
|
|
|
|
2020-04-16 05:58:54 -04:00
|
|
|
emptyMockBuilder = &MockBuilder{
|
|
|
|
Config: MockConfig{
|
|
|
|
NestedMockConfig: NestedMockConfig{
|
|
|
|
Tags: []MockTag{},
|
|
|
|
},
|
|
|
|
Nested: NestedMockConfig{},
|
|
|
|
NestedSlice: []NestedMockConfig{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
emptyMockProvisioner = &MockProvisioner{
|
|
|
|
Config: MockConfig{
|
|
|
|
NestedMockConfig: NestedMockConfig{Tags: []MockTag{}},
|
|
|
|
NestedSlice: []NestedMockConfig{},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2020-02-20 04:51:34 -05:00
|
|
|
dynamicTagList = []MockTag{
|
|
|
|
{
|
|
|
|
Key: "first_tag_key",
|
|
|
|
Value: "first_tag_value",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: "Component",
|
|
|
|
Value: "user-service",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Key: "Environment",
|
|
|
|
Value: "production",
|
|
|
|
},
|
|
|
|
}
|
2019-12-17 05:25:56 -05:00
|
|
|
)
|
2020-11-02 05:49:40 -05:00
|
|
|
|
2020-11-02 10:52:21 -05:00
|
|
|
var ctyValueComparer = cmp.Comparer(func(x, y cty.Value) bool {
|
|
|
|
return x.RawEquals(y)
|
|
|
|
})
|
|
|
|
|
|
|
|
var ctyTypeComparer = cmp.Comparer(func(x, y cty.Type) bool {
|
|
|
|
if x == cty.NilType && y == cty.NilType {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if x == cty.NilType || y == cty.NilType {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return x.Equals(y)
|
|
|
|
})
|
|
|
|
|
2021-02-02 12:05:04 -05:00
|
|
|
var versionComparer = cmp.Comparer(func(x, y *version.Version) bool {
|
|
|
|
return x.Equal(y)
|
|
|
|
})
|
|
|
|
|
|
|
|
var versionConstraintComparer = cmp.Comparer(func(x, y *version.Constraint) bool {
|
|
|
|
return x.String() == y.String()
|
|
|
|
})
|
|
|
|
|
2020-11-02 05:49:40 -05:00
|
|
|
var cmpOpts = []cmp.Option{
|
2020-11-02 10:52:21 -05:00
|
|
|
ctyValueComparer,
|
|
|
|
ctyTypeComparer,
|
2021-02-02 12:05:04 -05:00
|
|
|
versionComparer,
|
|
|
|
versionConstraintComparer,
|
2020-11-02 05:49:40 -05:00
|
|
|
cmpopts.IgnoreUnexported(
|
|
|
|
PackerConfig{},
|
|
|
|
Variable{},
|
|
|
|
SourceBlock{},
|
2021-01-20 04:37:16 -05:00
|
|
|
Datasource{},
|
2020-11-02 05:49:40 -05:00
|
|
|
ProvisionerBlock{},
|
|
|
|
PostProcessorBlock{},
|
|
|
|
packer.CoreBuild{},
|
|
|
|
HCL2Provisioner{},
|
|
|
|
HCL2PostProcessor{},
|
|
|
|
packer.CoreBuildPostProcessor{},
|
|
|
|
packer.CoreBuildProvisioner{},
|
|
|
|
packer.CoreBuildPostProcessor{},
|
|
|
|
null.Builder{},
|
|
|
|
),
|
|
|
|
cmpopts.IgnoreFields(PackerConfig{},
|
|
|
|
"Cwd", // Cwd will change for every os type
|
|
|
|
),
|
|
|
|
cmpopts.IgnoreFields(VariableAssignment{},
|
|
|
|
"Expr", // its an interface
|
|
|
|
),
|
|
|
|
cmpopts.IgnoreTypes(HCL2Ref{}),
|
|
|
|
cmpopts.IgnoreTypes([]*LocalBlock{}),
|
|
|
|
cmpopts.IgnoreTypes([]hcl.Range{}),
|
|
|
|
cmpopts.IgnoreTypes(hcl.Range{}),
|
|
|
|
cmpopts.IgnoreInterfaces(struct{ hcl.Expression }{}),
|
|
|
|
cmpopts.IgnoreInterfaces(struct{ hcl.Body }{}),
|
|
|
|
}
|