to allow just reading the config and to not start anything. This will allow to later on run `validate --syntax-only`. Note that none of the builder/provisioner/post-processor config will be read but simply ignored. HCL2 still needs the body to be properly formatted and it should detect most syntax errors.
243 lines
6.3 KiB
Go
243 lines
6.3 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 },
|
|
},
|
|
}
|
|
}
|
|
|
|
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)
|
|
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.IgnoreTypes(HCL2Ref{}),
|
|
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(nil, nil)
|
|
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{},
|
|
),
|
|
); 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",
|
|
NestedMockConfig: basicNestedMockConfig,
|
|
Nested: basicNestedMockConfig,
|
|
NestedSlice: []NestedMockConfig{
|
|
{
|
|
Tags: dynamicTagList,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
basicMockPostProcessor = &MockPostProcessor{
|
|
Config: MockConfig{
|
|
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",
|
|
},
|
|
}
|
|
)
|