44616d3bff
The initialization of packer core in JSON also validates that `null` variables were set, except in the case of `packer validate --syntax-only` , but after the refactor to allow to have all commands work with HCL2 and JSON this subtlety was lost. This refactors the initialisation of the core in order to allow to have `packer validate --syntax-only` not error in case a variable is not set. Since these calls are refactored this works for HCL2 too. fix #9478
253 lines
6.7 KiB
Go
253 lines
6.7 KiB
Go
package hcl2template
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
|
"github.com/hashicorp/packer/builder/null"
|
|
. "github.com/hashicorp/packer/hcl2template/internal"
|
|
"github.com/hashicorp/packer/helper/config"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
func getBasicParser() *Parser {
|
|
return &Parser{
|
|
Parser: hclparse.NewParser(),
|
|
BuilderSchemas: packer.MapOfBuilder{
|
|
"amazon-ebs": func() (packer.Builder, error) { return &MockBuilder{}, nil },
|
|
"virtualbox-iso": func() (packer.Builder, error) { return &MockBuilder{}, nil },
|
|
"null": func() (packer.Builder, error) { return &null.Builder{}, nil },
|
|
},
|
|
ProvisionersSchemas: packer.MapOfProvisioner{
|
|
"shell": func() (packer.Provisioner, error) { return &MockProvisioner{}, nil },
|
|
"file": func() (packer.Provisioner, error) { return &MockProvisioner{}, nil },
|
|
},
|
|
PostProcessorsSchemas: packer.MapOfPostProcessor{
|
|
"amazon-import": func() (packer.PostProcessor, error) { return &MockPostProcessor{}, nil },
|
|
"manifest": func() (packer.PostProcessor, error) { return &MockPostProcessor{}, nil },
|
|
},
|
|
}
|
|
}
|
|
|
|
type parseTestArgs struct {
|
|
filename string
|
|
vars map[string]string
|
|
varFiles []string
|
|
}
|
|
|
|
type parseTest struct {
|
|
name string
|
|
parser *Parser
|
|
args parseTestArgs
|
|
|
|
parseWantCfg *PackerConfig
|
|
parseWantDiags bool
|
|
parseWantDiagHasErrors bool
|
|
|
|
getBuildsWantBuilds []packer.Build
|
|
getBuildsWantDiags bool
|
|
// getBuildsWantDiagHasErrors bool
|
|
}
|
|
|
|
func testParse(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)
|
|
moreDiags := gotCfg.Initialize()
|
|
gotDiags = append(gotDiags, moreDiags...)
|
|
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.IgnoreUnexported(
|
|
PackerConfig{},
|
|
cty.Value{},
|
|
cty.Type{},
|
|
Variable{},
|
|
SourceBlock{},
|
|
ProvisionerBlock{},
|
|
PostProcessorBlock{},
|
|
),
|
|
cmpopts.IgnoreFields(PackerConfig{},
|
|
"Cwd", // Cwd will change for every computer
|
|
),
|
|
cmpopts.IgnoreTypes(HCL2Ref{}),
|
|
cmpopts.IgnoreTypes([]*LocalBlock{}),
|
|
cmpopts.IgnoreTypes([]hcl.Range{}),
|
|
cmpopts.IgnoreTypes(hcl.Range{}),
|
|
cmpopts.IgnoreInterfaces(struct{ hcl.Expression }{}),
|
|
cmpopts.IgnoreInterfaces(struct{ hcl.Body }{}),
|
|
); diff != "" {
|
|
t.Fatalf("Parser.parse() wrong packer config. %s", diff)
|
|
}
|
|
|
|
if gotCfg != nil && !tt.parseWantDiagHasErrors {
|
|
gotInputVar := gotCfg.InputVariables
|
|
for name, value := range tt.parseWantCfg.InputVariables {
|
|
if variable, ok := gotInputVar[name]; ok {
|
|
if diff := cmp.Diff(variable.DefaultValue.GoString(), value.DefaultValue.GoString()); diff != "" {
|
|
t.Fatalf("Parser.parse(): unexpected default value for %s: %s", name, diff)
|
|
}
|
|
if diff := cmp.Diff(variable.VarfileValue.GoString(), value.VarfileValue.GoString()); diff != "" {
|
|
t.Fatalf("Parser.parse(): varfile value differs for %s: %s", name, diff)
|
|
}
|
|
} else {
|
|
t.Fatalf("Parser.parse() missing input variable. %s", name)
|
|
}
|
|
}
|
|
|
|
gotLocalVar := gotCfg.LocalVariables
|
|
for name, value := range tt.parseWantCfg.LocalVariables {
|
|
if variable, ok := gotLocalVar[name]; ok {
|
|
if variable.DefaultValue.GoString() != value.DefaultValue.GoString() {
|
|
t.Fatalf("Parser.parse() local variable %s expected '%s' but was '%s'", name, value.DefaultValue.GoString(), variable.DefaultValue.GoString())
|
|
}
|
|
} else {
|
|
t.Fatalf("Parser.parse() missing local variable. %s", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
if gotDiags.HasErrors() {
|
|
return
|
|
}
|
|
|
|
gotBuilds, gotDiags := gotCfg.GetBuilds(packer.GetBuildsOptions{})
|
|
if tt.getBuildsWantDiags == (gotDiags == nil) {
|
|
t.Fatalf("Parser.getBuilds() unexpected diagnostics. %s", gotDiags)
|
|
}
|
|
if diff := cmp.Diff(tt.getBuildsWantBuilds, gotBuilds,
|
|
cmpopts.IgnoreUnexported(
|
|
cty.Value{},
|
|
cty.Type{},
|
|
packer.CoreBuild{},
|
|
packer.CoreBuildProvisioner{},
|
|
packer.CoreBuildPostProcessor{},
|
|
null.Builder{},
|
|
HCL2Provisioner{},
|
|
HCL2PostProcessor{},
|
|
),
|
|
); diff != "" {
|
|
t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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",
|
|
},
|
|
SliceSliceString: [][]string{
|
|
{"a", "b"},
|
|
{"c", "d"},
|
|
},
|
|
Tags: []MockTag{},
|
|
}
|
|
|
|
basicMockBuilder = &MockBuilder{
|
|
Config: MockConfig{
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
Nested: basicNestedMockConfig,
|
|
NestedSlice: []NestedMockConfig{
|
|
basicNestedMockConfig,
|
|
basicNestedMockConfig,
|
|
},
|
|
},
|
|
}
|
|
|
|
basicMockProvisioner = &MockProvisioner{
|
|
Config: MockConfig{
|
|
NotSquashed: "value <UNKNOWN>",
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
Nested: basicNestedMockConfig,
|
|
NestedSlice: []NestedMockConfig{
|
|
{
|
|
Tags: dynamicTagList,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
basicMockPostProcessor = &MockPostProcessor{
|
|
Config: MockConfig{
|
|
NotSquashed: "value <UNKNOWN>",
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
Nested: basicNestedMockConfig,
|
|
NestedSlice: []NestedMockConfig{
|
|
{
|
|
Tags: []MockTag{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
basicMockCommunicator = &MockCommunicator{
|
|
Config: MockConfig{
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
Nested: basicNestedMockConfig,
|
|
NestedSlice: []NestedMockConfig{
|
|
{
|
|
Tags: []MockTag{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
emptyMockBuilder = &MockBuilder{
|
|
Config: MockConfig{
|
|
NestedMockConfig: NestedMockConfig{
|
|
Tags: []MockTag{},
|
|
},
|
|
Nested: NestedMockConfig{},
|
|
NestedSlice: []NestedMockConfig{},
|
|
},
|
|
}
|
|
|
|
emptyMockProvisioner = &MockProvisioner{
|
|
Config: MockConfig{
|
|
NestedMockConfig: NestedMockConfig{Tags: []MockTag{}},
|
|
NestedSlice: []NestedMockConfig{},
|
|
},
|
|
}
|
|
|
|
dynamicTagList = []MockTag{
|
|
{
|
|
Key: "first_tag_key",
|
|
Value: "first_tag_value",
|
|
},
|
|
{
|
|
Key: "Component",
|
|
Value: "user-service",
|
|
},
|
|
{
|
|
Key: "Environment",
|
|
Value: "production",
|
|
},
|
|
}
|
|
)
|