mirror of
https://github.com/hashicorp/packer.git
synced 2026-09-22 16:01:43 -04:00
This commit irons out one of the pain points of the HCP rework by introducing a HCPPublisher interface, implemented both by the JSON Core, and the HCL2 PackerConfig, which keeps a map of the build names used by Packer to the build names pushed on HCP. This in turn lets us go back to the old implementation of the GetBuilds function, which returns a list of (filtered) builds, and eventually an error if something went wrong while processing.
375 lines
9.9 KiB
Go
375 lines
9.9 KiB
Go
package hcl2template
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/hashicorp/go-version"
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclparse"
|
|
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
|
|
"github.com/hashicorp/packer-plugin-sdk/template/config"
|
|
"github.com/hashicorp/packer/builder/null"
|
|
dnull "github.com/hashicorp/packer/datasource/null"
|
|
. "github.com/hashicorp/packer/hcl2template/internal"
|
|
hcl2template "github.com/hashicorp/packer/hcl2template/internal"
|
|
"github.com/hashicorp/packer/packer"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
const lockedVersion = "v1.5.0"
|
|
|
|
func getBasicParser(opts ...getParserOption) *Parser {
|
|
parser := &Parser{
|
|
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 },
|
|
"null": func() (packersdk.Datasource, error) { return &dnull.Datasource{}, nil },
|
|
},
|
|
},
|
|
}
|
|
for _, configure := range opts {
|
|
configure(parser)
|
|
}
|
|
return parser
|
|
}
|
|
|
|
type getParserOption func(*Parser)
|
|
|
|
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 []packersdk.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(packer.InitializeOptions{})
|
|
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...); 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
|
|
}
|
|
|
|
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...); diff != "" {
|
|
t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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{},
|
|
}
|
|
|
|
// 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",
|
|
}
|
|
|
|
basicMockBuilder = &MockBuilder{
|
|
Config: MockConfig{
|
|
NestedMockConfig: builderBasicNestedMockConfig,
|
|
Nested: builderBasicNestedMockConfig,
|
|
NestedSlice: []NestedMockConfig{
|
|
builderBasicNestedMockConfig,
|
|
builderBasicNestedMockConfig,
|
|
},
|
|
},
|
|
}
|
|
|
|
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{},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
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{},
|
|
},
|
|
}
|
|
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",
|
|
},
|
|
}
|
|
)
|
|
|
|
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)
|
|
})
|
|
|
|
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()
|
|
})
|
|
|
|
var cmpOpts = []cmp.Option{
|
|
ctyValueComparer,
|
|
ctyTypeComparer,
|
|
versionComparer,
|
|
versionConstraintComparer,
|
|
cmpopts.IgnoreUnexported(
|
|
PackerConfig{},
|
|
Variable{},
|
|
SourceBlock{},
|
|
DatasourceBlock{},
|
|
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
|
|
"HCPVars", // HCPVars will not be filled-in during parsing
|
|
),
|
|
cmpopts.IgnoreFields(VariableAssignment{},
|
|
"Expr", // its an interface
|
|
),
|
|
cmpopts.IgnoreTypes(hcl2template.MockBuilder{}),
|
|
cmpopts.IgnoreTypes(HCL2Ref{}),
|
|
cmpopts.IgnoreTypes([]*LocalBlock{}),
|
|
cmpopts.IgnoreTypes([]hcl.Range{}),
|
|
cmpopts.IgnoreTypes(hcl.Range{}),
|
|
cmpopts.IgnoreInterfaces(struct{ hcl.Expression }{}),
|
|
cmpopts.IgnoreInterfaces(struct{ hcl.Body }{}),
|
|
}
|