Merge branch 'master' into update_go-cty_regex

This commit is contained in:
Megan Marsh 2020-03-10 10:52:42 -07:00 committed by GitHub
commit e518a0a8b7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 337 additions and 67 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclparse" "github.com/hashicorp/hcl/v2/hclparse"
"github.com/hashicorp/packer/builder/null"
. "github.com/hashicorp/packer/hcl2template/internal" . "github.com/hashicorp/packer/hcl2template/internal"
"github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -21,6 +22,7 @@ func getBasicParser() *Parser {
BuilderSchemas: packer.MapOfBuilder{ BuilderSchemas: packer.MapOfBuilder{
"amazon-ebs": func() (packer.Builder, error) { return &MockBuilder{}, nil }, "amazon-ebs": func() (packer.Builder, error) { return &MockBuilder{}, nil },
"virtualbox-iso": 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{ ProvisionersSchemas: packer.MapOfProvisioner{
"shell": func() (packer.Provisioner, error) { return &MockProvisioner{}, nil }, "shell": func() (packer.Provisioner, error) { return &MockProvisioner{}, nil },
@ -58,7 +60,7 @@ func testParse(t *testing.T, tests []parseTest) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
gotCfg, gotDiags := tt.parser.parse(tt.args.filename, tt.args.vars) gotCfg, gotDiags := tt.parser.parse(tt.args.filename, tt.args.vars)
if tt.parseWantDiags == (gotDiags == nil) { if tt.parseWantDiags == (gotDiags == nil) {
t.Fatalf("Parser.parse() unexpected diagnostics. %s", gotDiags) t.Fatalf("Parser.parse() unexpected %q diagnostics.", gotDiags)
} }
if tt.parseWantDiagHasErrors != gotDiags.HasErrors() { if tt.parseWantDiagHasErrors != gotDiags.HasErrors() {
t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags) t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags)
@ -120,6 +122,7 @@ func testParse(t *testing.T, tests []parseTest) {
packer.CoreBuild{}, packer.CoreBuild{},
packer.CoreBuildProvisioner{}, packer.CoreBuildProvisioner{},
packer.CoreBuildPostProcessor{}, packer.CoreBuildPostProcessor{},
null.Builder{},
), ),
); diff != "" { ); diff != "" {
t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff) t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff)

View File

@ -9,6 +9,8 @@ import (
"github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/helper/config"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/json"
) )
type NestedMockConfig struct { type NestedMockConfig struct {
@ -38,6 +40,27 @@ type MockConfig struct {
NestedSlice []NestedMockConfig `mapstructure:"nested_slice"` NestedSlice []NestedMockConfig `mapstructure:"nested_slice"`
} }
func (b *MockConfig) Prepare(raws ...interface{}) error {
for i, raw := range raws {
cval, ok := raw.(cty.Value)
if !ok {
continue
}
b, err := json.Marshal(cval, cty.DynamicPseudoType)
if err != nil {
return err
}
ccval, err := json.Unmarshal(b, cty.DynamicPseudoType)
if err != nil {
return err
}
raws[i] = ccval
}
return config.Decode(b, &config.DecodeOpts{
Interpolate: true,
}, raws...)
}
////// //////
// MockBuilder // MockBuilder
////// //////
@ -51,9 +74,7 @@ var _ packer.Builder = new(MockBuilder)
func (b *MockBuilder) ConfigSpec() hcldec.ObjectSpec { return b.Config.FlatMapstructure().HCL2Spec() } func (b *MockBuilder) ConfigSpec() hcldec.ObjectSpec { return b.Config.FlatMapstructure().HCL2Spec() }
func (b *MockBuilder) Prepare(raws ...interface{}) ([]string, []string, error) { func (b *MockBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
return nil, nil, config.Decode(&b.Config, &config.DecodeOpts{ return nil, nil, b.Config.Prepare(raws...)
Interpolate: true,
}, raws...)
} }
func (b *MockBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) { func (b *MockBuilder) Run(ctx context.Context, ui packer.Ui, hook packer.Hook) (packer.Artifact, error) {
@ -75,9 +96,7 @@ func (b *MockProvisioner) ConfigSpec() hcldec.ObjectSpec {
} }
func (b *MockProvisioner) Prepare(raws ...interface{}) error { func (b *MockProvisioner) Prepare(raws ...interface{}) error {
return config.Decode(&b.Config, &config.DecodeOpts{ return b.Config.Prepare(raws...)
Interpolate: true,
}, raws...)
} }
func (b *MockProvisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error { func (b *MockProvisioner) Provision(ctx context.Context, ui packer.Ui, comm packer.Communicator, _ map[string]interface{}) error {
@ -99,9 +118,7 @@ func (b *MockPostProcessor) ConfigSpec() hcldec.ObjectSpec {
} }
func (b *MockPostProcessor) Configure(raws ...interface{}) error { func (b *MockPostProcessor) Configure(raws ...interface{}) error {
return config.Decode(&b.Config, &config.DecodeOpts{ return b.Config.Prepare(raws...)
Interpolate: true,
}, raws...)
} }
func (b *MockPostProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, bool, error) { func (b *MockPostProcessor) PostProcess(ctx context.Context, ui packer.Ui, a packer.Artifact) (packer.Artifact, bool, bool, error) {
@ -124,9 +141,7 @@ func (b *MockCommunicator) ConfigSpec() hcldec.ObjectSpec {
} }
func (b *MockCommunicator) Configure(raws ...interface{}) ([]string, error) { func (b *MockCommunicator) Configure(raws ...interface{}) ([]string, error) {
return nil, config.Decode(&b.Config, &config.DecodeOpts{ return nil, b.Config.Prepare(raws...)
Interpolate: true,
}, raws...)
} }
////// //////

View File

@ -123,9 +123,14 @@ func (p *Parser) parse(filename string, vars map[string]string) (*PackerConfig,
varFiles = append(varFiles, f) varFiles = append(varFiles, f)
} }
diags = append(diags, cfg.InputVariables.collectVariableValues(os.Environ(), varFiles, vars)...) diags = append(diags, cfg.collectInputVariableValues(os.Environ(), varFiles, vars)...)
} }
_, moreDiags := cfg.InputVariables.Values()
diags = append(diags, moreDiags...)
_, moreDiags = cfg.LocalVariables.Values()
diags = append(diags, moreDiags...)
// decode the actual content // decode the actual content
for _, file := range files { for _, file := range files {
diags = append(diags, p.decodeConfig(file, cfg)...) diags = append(diags, p.decodeConfig(file, cfg)...)

View File

@ -29,6 +29,7 @@ variable "super_secret_password" {
description = <<IMSENSIBLE description = <<IMSENSIBLE
Handle with care plz Handle with care plz
IMSENSIBLE IMSENSIBLE
default = null
} }
locals { locals {

View File

View File

@ -1,4 +1,5 @@
variable "broken_type" { variable "broken_variable" {
invalid = true invalid = true
default = true
} }

View File

@ -0,0 +1,15 @@
variable "foo" {
type = string
}
build {
sources = [
"source.null.null-builder",
]
}
source "null" "null-builder" {
communicator = "none"
}

View File

@ -0,0 +1,10 @@
variable "foo" {
type = string
}
build {
sources = [
"source.null.null-builder${var.foo}",
]
}

View File

@ -60,6 +60,9 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti
Config hcl.Body `hcl:",remain"` Config hcl.Body `hcl:",remain"`
} }
diags := gohcl.DecodeBody(block.Body, nil, &b) diags := gohcl.DecodeBody(block.Body, nil, &b)
if diags.HasErrors() {
return nil, diags
}
for _, buildFrom := range b.FromSources { for _, buildFrom := range b.FromSources {
ref := sourceRefFromString(buildFrom) ref := sourceRefFromString(buildFrom)
@ -84,6 +87,9 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti
content, moreDiags := b.Config.Content(buildSchema) content, moreDiags := b.Config.Content(buildSchema)
diags = append(diags, moreDiags...) diags = append(diags, moreDiags...)
if diags.HasErrors() {
return nil, diags
}
for _, block := range content.Blocks { for _, block := range content.Blocks {
switch block.Type { switch block.Type {
case buildProvisionerLabel: case buildProvisionerLabel:

View File

@ -2,6 +2,7 @@ package hcl2template
import ( import (
"fmt" "fmt"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/helper/common" "github.com/hashicorp/packer/helper/common"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
@ -24,19 +25,27 @@ type PackerConfig struct {
InputVariables Variables InputVariables Variables
LocalVariables Variables LocalVariables Variables
ValidationOptions
// Builds is the list of Build blocks defined in the config files. // Builds is the list of Build blocks defined in the config files.
Builds Builds Builds Builds
} }
type ValidationOptions struct {
Strict bool
}
// EvalContext returns the *hcl.EvalContext that will be passed to an hcl // EvalContext returns the *hcl.EvalContext that will be passed to an hcl
// decoder in order to tell what is the actual value of a var or a local and // decoder in order to tell what is the actual value of a var or a local and
// the list of defined functions. // the list of defined functions.
func (cfg *PackerConfig) EvalContext() *hcl.EvalContext { func (cfg *PackerConfig) EvalContext() *hcl.EvalContext {
inputVariables, _ := cfg.InputVariables.Values()
localVariables, _ := cfg.LocalVariables.Values()
ectx := &hcl.EvalContext{ ectx := &hcl.EvalContext{
Functions: Functions(cfg.Basedir), Functions: Functions(cfg.Basedir),
Variables: map[string]cty.Value{ Variables: map[string]cty.Value{
"var": cty.ObjectVal(cfg.InputVariables.Values()), "var": cty.ObjectVal(inputVariables),
"local": cty.ObjectVal(cfg.LocalVariables.Values()), "local": cty.ObjectVal(localVariables),
}, },
} }
return ectx return ectx
@ -76,20 +85,19 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*Local, hcl.Diagnosti
content, moreDiags := f.Body.Content(configSchema) content, moreDiags := f.Body.Content(configSchema)
diags = append(diags, moreDiags...) diags = append(diags, moreDiags...)
var allLocals []*Local var locals []*Local
for _, block := range content.Blocks { for _, block := range content.Blocks {
switch block.Type { switch block.Type {
case localsLabel: case localsLabel:
attrs, moreDiags := block.Body.JustAttributes() attrs, moreDiags := block.Body.JustAttributes()
diags = append(diags, moreDiags...) diags = append(diags, moreDiags...)
locals := make([]*Local, 0, len(attrs))
for name, attr := range attrs { for name, attr := range attrs {
if _, found := c.LocalVariables[name]; found { if _, found := c.LocalVariables[name]; found {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Duplicate variable", Summary: "Duplicate value in " + localsLabel,
Detail: "Duplicate " + name + " variable definition found.", Detail: "Duplicate " + name + " definition found.",
Subject: attr.NameRange.Ptr(), Subject: attr.NameRange.Ptr(),
Context: block.DefRange.Ptr(), Context: block.DefRange.Ptr(),
}) })
@ -100,11 +108,10 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*Local, hcl.Diagnosti
Expr: attr.Expr, Expr: attr.Expr,
}) })
} }
allLocals = append(allLocals, locals...)
} }
} }
return allLocals, diags return locals, diags
} }
func (c *PackerConfig) evaluateLocalVariables(locals []*Local) hcl.Diagnostics { func (c *PackerConfig) evaluateLocalVariables(locals []*Local) hcl.Diagnostics {
@ -156,6 +163,7 @@ func (c *PackerConfig) evaluateLocalVariable(local *Local) hcl.Diagnostics {
return diags return diags
} }
c.LocalVariables[local.Name] = &Variable{ c.LocalVariables[local.Name] = &Variable{
Name: local.Name,
DefaultValue: value, DefaultValue: value,
Type: value.Type(), Type: value.Type(),
} }

View File

@ -23,15 +23,19 @@ func TestParser_complete(t *testing.T) {
Basedir: "testdata/complete", Basedir: "testdata/complete",
InputVariables: Variables{ InputVariables: Variables{
"foo": &Variable{ "foo": &Variable{
Name: "foo",
DefaultValue: cty.StringVal("value"), DefaultValue: cty.StringVal("value"),
}, },
"image_id": &Variable{ "image_id": &Variable{
Name: "image_id",
DefaultValue: cty.StringVal("image-id-default"), DefaultValue: cty.StringVal("image-id-default"),
}, },
"port": &Variable{ "port": &Variable{
Name: "port",
DefaultValue: cty.NumberIntVal(42), DefaultValue: cty.NumberIntVal(42),
}, },
"availability_zone_names": &Variable{ "availability_zone_names": &Variable{
Name: "availability_zone_names",
DefaultValue: cty.ListVal([]cty.Value{ DefaultValue: cty.ListVal([]cty.Value{
cty.StringVal("A"), cty.StringVal("A"),
cty.StringVal("B"), cty.StringVal("B"),
@ -41,15 +45,18 @@ func TestParser_complete(t *testing.T) {
}, },
LocalVariables: Variables{ LocalVariables: Variables{
"feefoo": &Variable{ "feefoo": &Variable{
Name: "feefoo",
DefaultValue: cty.StringVal("value_image-id-default"), DefaultValue: cty.StringVal("value_image-id-default"),
}, },
"standard_tags": &Variable{ "standard_tags": &Variable{
Name: "standard_tags",
DefaultValue: cty.ObjectVal(map[string]cty.Value{ DefaultValue: cty.ObjectVal(map[string]cty.Value{
"Component": cty.StringVal("user-service"), "Component": cty.StringVal("user-service"),
"Environment": cty.StringVal("production"), "Environment": cty.StringVal("production"),
}), }),
}, },
"abc_map": &Variable{ "abc_map": &Variable{
Name: "abc_map",
DefaultValue: cty.TupleVal([]cty.Value{ DefaultValue: cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{ cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("a"), "id": cty.StringVal("a"),

View File

@ -39,13 +39,15 @@ type Variable struct {
// declaration, the type of the default variable will be used. This will // declaration, the type of the default variable will be used. This will
// allow to ensure that users set this variable correctly. // allow to ensure that users set this variable correctly.
Type cty.Type Type cty.Type
// Common name of the variable
Name string
// Description of the variable // Description of the variable
Description string Description string
// When Sensitive is set to true Packer will try it best to hide/obfuscate // When Sensitive is set to true Packer will try it best to hide/obfuscate
// the variable from the output stream. By replacing the text. // the variable from the output stream. By replacing the text.
Sensitive bool Sensitive bool
block *hcl.Block Range hcl.Range
} }
func (v *Variable) GoString() string { func (v *Variable) GoString() string {
@ -60,27 +62,37 @@ func (v *Variable) Value() (cty.Value, *hcl.Diagnostic) {
v.EnvValue, v.EnvValue,
v.DefaultValue, v.DefaultValue,
} { } {
if !value.IsNull() { if value != cty.NilVal {
return value, nil return value, nil
} }
} }
return cty.NilVal, &hcl.Diagnostic{
value := cty.NullVal(cty.DynamicPseudoType)
return value, &hcl.Diagnostic{
Severity: hcl.DiagError, Severity: hcl.DiagError,
Summary: "Unset variable", Summary: fmt.Sprintf("Unset variable %q", v.Name),
Detail: "A used variable must be set; see " + Detail: "A used variable must be set or have a default value; see " +
"https://packer.io/docs/configuration/from-1.5/syntax.html for details.", "https://packer.io/docs/configuration/from-1.5/syntax.html for " +
Context: v.block.DefRange.Ptr(), "details.",
Context: v.Range.Ptr(),
} }
} }
type Variables map[string]*Variable type Variables map[string]*Variable
func (variables Variables) Values() map[string]cty.Value { func (variables Variables) Values() (map[string]cty.Value, hcl.Diagnostics) {
res := map[string]cty.Value{} res := map[string]cty.Value{}
var diags hcl.Diagnostics
for k, v := range variables { for k, v := range variables {
res[k], _ = v.Value() value, diag := v.Value()
if diag != nil {
diags = append(diags, diag)
continue
}
res[k] = value
} }
return res return res, diags
} }
// decodeVariable decodes a variable key and value into Variables // decodeVariable decodes a variable key and value into Variables
@ -108,8 +120,10 @@ func (variables *Variables) decodeVariable(key string, attr *hcl.Attribute, ectx
} }
(*variables)[key] = &Variable{ (*variables)[key] = &Variable{
Name: key,
DefaultValue: value, DefaultValue: value,
Type: value.Type(), Type: value.Type(),
Range: attr.Range,
} }
return diags return diags
@ -142,10 +156,13 @@ func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.Eval
return diags return diags
} }
name := block.Labels[0]
res := &Variable{ res := &Variable{
Name: name,
Description: b.Description, Description: b.Description,
Sensitive: b.Sensitive, Sensitive: b.Sensitive,
block: block, Range: block.DefRange,
} }
attrs, moreDiags := b.Rest.JustAttributes() attrs, moreDiags := b.Rest.JustAttributes()
@ -206,7 +223,7 @@ func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.Eval
}) })
} }
(*variables)[block.Labels[0]] = res (*variables)[name] = res
return diags return diags
} }
@ -215,8 +232,9 @@ func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.Eval
// them. // them.
const VarEnvPrefix = "PKR_VAR_" const VarEnvPrefix = "PKR_VAR_"
func (variables Variables) collectVariableValues(env []string, files []*hcl.File, argv map[string]string) hcl.Diagnostics { func (cfg *PackerConfig) collectInputVariableValues(env []string, files []*hcl.File, argv map[string]string) hcl.Diagnostics {
var diags hcl.Diagnostics var diags hcl.Diagnostics
variables := cfg.InputVariables
for _, raw := range env { for _, raw := range env {
if !strings.HasPrefix(raw, VarEnvPrefix) { if !strings.HasPrefix(raw, VarEnvPrefix) {
@ -245,6 +263,7 @@ func (variables Variables) collectVariableValues(env []string, files []*hcl.File
if moreDiags.HasErrors() { if moreDiags.HasErrors() {
continue continue
} }
val, valDiags := expr.Value(nil) val, valDiags := expr.Value(nil)
diags = append(diags, valDiags...) diags = append(diags, valDiags...)
if variable.Type != cty.NilType { if variable.Type != cty.NilType {
@ -310,7 +329,20 @@ func (variables Variables) collectVariableValues(env []string, files []*hcl.File
for name, attr := range attrs { for name, attr := range attrs {
variable, found := variables[name] variable, found := variables[name]
if !found { if !found {
// No file defines this variable; let's skip it sev := hcl.DiagWarning
if cfg.ValidationOptions.Strict {
sev = hcl.DiagError
}
diags = append(diags, &hcl.Diagnostic{
Severity: sev,
Summary: "Undefined variable",
Detail: fmt.Sprintf("A %q variable was set but was "+
"not found in known variables. To declare "+
"variable %q, place this block in one of your"+
".pkr files, such as variables.pkr.hcl",
name, name),
Context: attr.Range.Ptr(),
})
continue continue
} }
@ -340,8 +372,8 @@ func (variables Variables) collectVariableValues(env []string, files []*hcl.File
variable, found := variables[name] variable, found := variables[name]
if !found { if !found {
diags = append(diags, &hcl.Diagnostic{ diags = append(diags, &hcl.Diagnostic{
Severity: hcl.DiagWarning, Severity: hcl.DiagError,
Summary: "Unknown -var variable", Summary: "Undefined -var variable",
Detail: fmt.Sprintf("A %q variable was passed in the command "+ Detail: fmt.Sprintf("A %q variable was passed in the command "+
"line but was not found in known variables."+ "line but was not found in known variables."+
"To declare variable %q, place this block in one of your"+ "To declare variable %q, place this block in one of your"+

View File

@ -10,6 +10,7 @@ import (
"github.com/zclconf/go-cty/cty/convert" "github.com/zclconf/go-cty/cty/convert"
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/packer/builder/null"
"github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/packer"
) )
@ -24,36 +25,46 @@ func TestParse_variables(t *testing.T) {
Basedir: filepath.Join("testdata", "variables"), Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{ InputVariables: Variables{
"image_name": &Variable{ "image_name": &Variable{
Name: "image_name",
DefaultValue: cty.StringVal("foo-image-{{user `my_secret`}}"), DefaultValue: cty.StringVal("foo-image-{{user `my_secret`}}"),
}, },
"key": &Variable{ "key": &Variable{
Name: "key",
DefaultValue: cty.StringVal("value"), DefaultValue: cty.StringVal("value"),
}, },
"my_secret": &Variable{ "my_secret": &Variable{
Name: "my_secret",
DefaultValue: cty.StringVal("foo"), DefaultValue: cty.StringVal("foo"),
}, },
"image_id": &Variable{ "image_id": &Variable{
Name: "image_id",
DefaultValue: cty.StringVal("image-id-default"), DefaultValue: cty.StringVal("image-id-default"),
}, },
"port": &Variable{ "port": &Variable{
Name: "port",
DefaultValue: cty.NumberIntVal(42), DefaultValue: cty.NumberIntVal(42),
}, },
"availability_zone_names": &Variable{ "availability_zone_names": &Variable{
Name: "availability_zone_names",
DefaultValue: cty.ListVal([]cty.Value{ DefaultValue: cty.ListVal([]cty.Value{
cty.StringVal("us-west-1a"), cty.StringVal("us-west-1a"),
}), }),
Description: fmt.Sprintln("Describing is awesome ;D"), Description: fmt.Sprintln("Describing is awesome ;D"),
}, },
"super_secret_password": &Variable{ "super_secret_password": &Variable{
Sensitive: true, Name: "super_secret_password",
Description: fmt.Sprintln("Handle with care plz"), Sensitive: true,
DefaultValue: cty.NullVal(cty.String),
Description: fmt.Sprintln("Handle with care plz"),
}, },
}, },
LocalVariables: Variables{ LocalVariables: Variables{
"owner": &Variable{ "owner": &Variable{
Name: "owner",
DefaultValue: cty.StringVal("Community Team"), DefaultValue: cty.StringVal("Community Team"),
}, },
"service_name": &Variable{ "service_name": &Variable{
Name: "service_name",
DefaultValue: cty.StringVal("forum"), DefaultValue: cty.StringVal("forum"),
}, },
}, },
@ -68,7 +79,9 @@ func TestParse_variables(t *testing.T) {
&PackerConfig{ &PackerConfig{
Basedir: filepath.Join("testdata", "variables"), Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{ InputVariables: Variables{
"boolean_value": &Variable{}, "boolean_value": &Variable{
Name: "boolean_value",
},
}, },
}, },
true, true, true, true,
@ -81,7 +94,9 @@ func TestParse_variables(t *testing.T) {
&PackerConfig{ &PackerConfig{
Basedir: filepath.Join("testdata", "variables"), Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{ InputVariables: Variables{
"boolean_value": &Variable{}, "boolean_value": &Variable{
Name: "boolean_value",
},
}, },
}, },
true, true, true, true,
@ -94,26 +109,84 @@ func TestParse_variables(t *testing.T) {
&PackerConfig{ &PackerConfig{
Basedir: filepath.Join("testdata", "variables"), Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{ InputVariables: Variables{
"broken_type": &Variable{}, "broken_type": &Variable{
Name: "broken_type",
},
}, },
}, },
true, true, true, true,
[]packer.Build{}, []packer.Build{},
false, false,
}, },
{"invalid default type",
{"unknown key",
defaultParser, defaultParser,
parseTestArgs{"testdata/variables/unknown_key.pkr.hcl", nil}, parseTestArgs{"testdata/variables/unknown_key.pkr.hcl", nil},
&PackerConfig{ &PackerConfig{
Basedir: filepath.Join("testdata", "variables"), Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{ InputVariables: Variables{
"broken_type": &Variable{}, "broken_variable": &Variable{
Name: "broken_variable",
DefaultValue: cty.BoolVal(true),
},
}, },
}, },
true, false, true, false,
[]packer.Build{}, []packer.Build{},
false, false,
}, },
{"unset used variable",
defaultParser,
parseTestArgs{"testdata/variables/unset_used_string_variable.pkr.hcl", nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{
"foo": &Variable{
Name: "foo",
},
},
},
true, true,
[]packer.Build{},
true,
},
{"unset unused variable",
defaultParser,
parseTestArgs{"testdata/variables/unset_unused_string_variable.pkr.hcl", nil},
&PackerConfig{
Basedir: filepath.Join("testdata", "variables"),
InputVariables: Variables{
"foo": &Variable{
Name: "foo",
},
},
Sources: map[SourceRef]*SourceBlock{
SourceRef{"null", "null-builder"}: &SourceBlock{
Name: "null-builder",
Type: "null",
},
},
Builds: Builds{
&BuildBlock{
Sources: []SourceRef{SourceRef{"null", "null-builder"}},
},
},
},
true, true,
[]packer.Build{
&packer.CoreBuild{
Type: "null",
Builder: &null.Builder{},
Provisioners: []packer.CoreBuildProvisioner{},
PostProcessors: [][]packer.CoreBuildPostProcessor{},
Prepared: true,
},
},
false,
},
{"locals within another locals usage in different files", {"locals within another locals usage in different files",
defaultParser, defaultParser,
parseTestArgs{"testdata/variables/complicated", nil}, parseTestArgs{"testdata/variables/complicated", nil},
@ -121,23 +194,29 @@ func TestParse_variables(t *testing.T) {
Basedir: "testdata/variables/complicated", Basedir: "testdata/variables/complicated",
InputVariables: Variables{ InputVariables: Variables{
"name_prefix": &Variable{ "name_prefix": &Variable{
Name: "name_prefix",
DefaultValue: cty.StringVal("foo"), DefaultValue: cty.StringVal("foo"),
}, },
}, },
LocalVariables: Variables{ LocalVariables: Variables{
"name_prefix": &Variable{ "name_prefix": &Variable{
Name: "name_prefix",
DefaultValue: cty.StringVal("foo"), DefaultValue: cty.StringVal("foo"),
}, },
"foo": &Variable{ "foo": &Variable{
Name: "foo",
DefaultValue: cty.StringVal("foo"), DefaultValue: cty.StringVal("foo"),
}, },
"bar": &Variable{ "bar": &Variable{
Name: "bar",
DefaultValue: cty.StringVal("foo"), DefaultValue: cty.StringVal("foo"),
}, },
"for_var": &Variable{ "for_var": &Variable{
Name: "for_var",
DefaultValue: cty.StringVal("foo"), DefaultValue: cty.StringVal("foo"),
}, },
"bar_var": &Variable{ "bar_var": &Variable{
Name: "bar_var",
DefaultValue: cty.TupleVal([]cty.Value{ DefaultValue: cty.TupleVal([]cty.Value{
cty.StringVal("foo"), cty.StringVal("foo"),
cty.StringVal("foo"), cty.StringVal("foo"),
@ -172,12 +251,14 @@ func TestVariables_collectVariableValues(t *testing.T) {
argv map[string]string argv map[string]string
} }
tests := []struct { tests := []struct {
name string name string
variables Variables variables Variables
args args validationOptions ValidationOptions
wantDiags bool args args
wantVariables Variables wantDiags bool
wantValues map[string]cty.Value wantDiagsHasError bool
wantVariables Variables
wantValues map[string]cty.Value
}{ }{
{name: "string", {name: "string",
@ -329,11 +410,39 @@ func TestVariables_collectVariableValues(t *testing.T) {
}, },
}, },
{name: "undefined but set value", {name: "undefined but set value - pkrvar file - normal mode",
variables: Variables{}, variables: Variables{},
args: args{ args: args{
env: []string{`PKR_VAR_unused_string=value`}, hclFiles: []string{`undefined_string="value"`},
hclFiles: []string{`unused_string="value"`}, },
// output
wantDiags: true,
wantDiagsHasError: false,
wantVariables: Variables{},
wantValues: map[string]cty.Value{},
},
{name: "undefined but set value - pkrvar file - strict mode",
variables: Variables{},
validationOptions: ValidationOptions{
Strict: true,
},
args: args{
hclFiles: []string{`undefined_string="value"`},
},
// output
wantDiags: true,
wantDiagsHasError: true,
wantVariables: Variables{},
wantValues: map[string]cty.Value{},
},
{name: "undefined but set value - env",
variables: Variables{},
args: args{
env: []string{`PKR_VAR_undefined_string=value`},
}, },
// output // output
@ -342,18 +451,19 @@ func TestVariables_collectVariableValues(t *testing.T) {
wantValues: map[string]cty.Value{}, wantValues: map[string]cty.Value{},
}, },
{name: "undefined but set value - args", {name: "undefined but set value - argv",
variables: Variables{}, variables: Variables{},
args: args{ args: args{
argv: map[string]string{ argv: map[string]string{
"unused_string": "value", "undefined_string": "value",
}, },
}, },
// output // output
wantDiags: true, wantDiags: true,
wantVariables: Variables{}, wantDiagsHasError: true,
wantValues: map[string]cty.Value{}, wantVariables: Variables{},
wantValues: map[string]cty.Value{},
}, },
{name: "value not corresponding to type - env", {name: "value not corresponding to type - env",
@ -367,7 +477,8 @@ func TestVariables_collectVariableValues(t *testing.T) {
}, },
// output // output
wantDiags: true, wantDiags: true,
wantDiagsHasError: true,
wantVariables: Variables{ wantVariables: Variables{
"used_string": &Variable{ "used_string": &Variable{
Type: cty.List(cty.String), Type: cty.List(cty.String),
@ -390,7 +501,8 @@ func TestVariables_collectVariableValues(t *testing.T) {
}, },
// output // output
wantDiags: true, wantDiags: true,
wantDiagsHasError: true,
wantVariables: Variables{ wantVariables: Variables{
"used_string": &Variable{ "used_string": &Variable{
Type: cty.Bool, Type: cty.Bool,
@ -415,7 +527,8 @@ func TestVariables_collectVariableValues(t *testing.T) {
}, },
// output // output
wantDiags: true, wantDiags: true,
wantDiagsHasError: true,
wantVariables: Variables{ wantVariables: Variables{
"used_string": &Variable{ "used_string": &Variable{
Type: cty.Bool, Type: cty.Bool,
@ -434,9 +547,10 @@ func TestVariables_collectVariableValues(t *testing.T) {
}, },
// output // output
wantDiags: true, wantDiags: true,
wantVariables: Variables{}, wantDiagsHasError: true,
wantValues: map[string]cty.Value{}, wantVariables: Variables{},
wantValues: map[string]cty.Value{},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
@ -450,9 +564,17 @@ func TestVariables_collectVariableValues(t *testing.T) {
} }
files = append(files, file) files = append(files, file)
} }
if gotDiags := tt.variables.collectVariableValues(tt.args.env, files, tt.args.argv); (gotDiags == nil) == tt.wantDiags { cfg := &PackerConfig{
InputVariables: tt.variables,
ValidationOptions: tt.validationOptions,
}
gotDiags := cfg.collectInputVariableValues(tt.args.env, files, tt.args.argv)
if (gotDiags == nil) == tt.wantDiags {
t.Fatalf("Variables.collectVariableValues() = %v, want %v", gotDiags, tt.wantDiags) t.Fatalf("Variables.collectVariableValues() = %v, want %v", gotDiags, tt.wantDiags)
} }
if tt.wantDiagsHasError != gotDiags.HasErrors() {
t.Fatalf("Variables.collectVariableValues() unexpected diagnostics HasErrors. %s", gotDiags)
}
if diff := cmp.Diff(fmt.Sprintf("%#v", tt.wantVariables), fmt.Sprintf("%#v", tt.variables)); diff != "" { if diff := cmp.Diff(fmt.Sprintf("%#v", tt.wantVariables), fmt.Sprintf("%#v", tt.variables)); diff != "" {
t.Fatalf("didn't get expected variables: %s", diff) t.Fatalf("didn't get expected variables: %s", diff)
} }

View File

@ -278,3 +278,48 @@ precedence over earlier ones:
~> **Important:** Variables with map and object values behave the same way as ~> **Important:** Variables with map and object values behave the same way as
other variables: the last value found overrides the previous values. other variables: the last value found overrides the previous values.
### A variable value must be known :
Take the following variable for example:
``` hcl
variable "foo" {
type = string
```
Here `foo` must have a known value but you can default it to `null` to make
this behavior optional :
| | no default | `default = null` | `default = "xy"` |
|:---------------------------:|:----------------------------:|:----------------:|:----------------:|
| foo unused | error, "foo needs to be set" | - | - |
| var.foo | error, "foo needs to be set" | null¹ | xy |
| `PKR_VAR_foo=yz`<br>var.foo | yz | yz | yz |
| `-var foo=yz`<br>var.foo | yz | yz | yz |
1: Null is a valid value. Packer will only error when the receiving field needs
a value, example:
``` hcl
variable "example" {
type = string
default = null
}
source "example" "foo" {
arg = var.example
}
```
In the above case, as long as "arg" is optional for an "example" source, there is no error and arg wont be set.
### Setting an unknown variable will not always fail :
| Usage | packer validate | any other packer command |
|:------------------------------:|:-----------------------:|:-------------------------:|
| `bar=yz` in .pkrvars.hcl file. | error, "bar undeclared" | warning, "bar undeclared" |
| `var.bar` in .pkr.hcl file | error, "bar undeclared" | error, "bar undeclared" |
| `-var bar=yz` argument | error, "bar undeclared" | error, "bar undeclared" |
| `export PKR_VAR_bar=yz` | - | - |