Merge pull request #8837 from hashicorp/fix_8730
Fix crash when an unset variable is used
This commit is contained in:
commit
8a1caaa804
5
go.mod
5
go.mod
@ -20,7 +20,6 @@ require (
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f
|
||||
github.com/antchfx/htmlquery v1.0.0 // indirect
|
||||
github.com/antchfx/xmlquery v1.0.0 // indirect
|
||||
|
||||
github.com/antchfx/xpath v0.0.0-20170728053731-b5c552e1acbd // indirect
|
||||
github.com/antchfx/xquery v0.0.0-20170730121040-eb8c3c172607 // indirect
|
||||
github.com/approvals/go-approval-tests v0.0.0-20160714161514-ad96e53bea43
|
||||
@ -153,7 +152,7 @@ require (
|
||||
github.com/xanzy/go-cloudstack v0.0.0-20190526095453-42f262b63ed0
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829
|
||||
github.com/zclconf/go-cty v1.2.1
|
||||
github.com/zclconf/go-cty v1.3.1
|
||||
github.com/zclconf/go-cty-yaml v1.0.1
|
||||
go.opencensus.io v0.22.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad
|
||||
@ -180,6 +179,4 @@ replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110
|
||||
|
||||
replace github.com/gofrs/flock => github.com/azr/flock v0.0.0-20190823144736-958d66434653
|
||||
|
||||
replace github.com/zclconf/go-cty => github.com/azr/go-cty v1.1.1-0.20200203143058-28fcda2fe0cc
|
||||
|
||||
go 1.13
|
||||
|
6
go.sum
6
go.sum
@ -488,6 +488,11 @@ github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e h1:hzwq5G
|
||||
github.com/yandex-cloud/go-genproto v0.0.0-20190916101622-7617782d381e/go.mod h1:HEUYX/p8966tMUHHT+TsS0hF/Ca/NYwqprC5WXSDMfE=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829 h1:2FGwbx03GpP1Ulzg/L46tSoKh9t4yg8BhMKQl/Ff1x8=
|
||||
github.com/yandex-cloud/go-sdk v0.0.0-20190916101744-c781afa45829/go.mod h1:Eml0jFLU4VVHgIN8zPHMuNwZXVzUMILyO6lQZSfz854=
|
||||
github.com/zclconf/go-cty v1.0.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
|
||||
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||
github.com/zclconf/go-cty v1.3.1 h1:QIOZl+CKKdkv4l2w3lG23nNzXgLoxsWLSEdg1MlX4p0=
|
||||
github.com/zclconf/go-cty v1.3.1/go.mod h1:YO23e2L18AG+ZYQfSobnY4G65nvwvprPCxBHkufUH1k=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8=
|
||||
github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0=
|
||||
go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg=
|
||||
@ -541,6 +546,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zH
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"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"
|
||||
@ -21,6 +22,7 @@ func getBasicParser() *Parser {
|
||||
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 },
|
||||
@ -58,7 +60,7 @@ func testParse(t *testing.T, tests []parseTest) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCfg, gotDiags := tt.parser.parse(tt.args.filename, tt.args.vars)
|
||||
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() {
|
||||
t.Fatalf("Parser.parse() unexpected diagnostics HasErrors. %s", gotDiags)
|
||||
@ -120,6 +122,7 @@ func testParse(t *testing.T, tests []parseTest) {
|
||||
packer.CoreBuild{},
|
||||
packer.CoreBuildProvisioner{},
|
||||
packer.CoreBuildPostProcessor{},
|
||||
null.Builder{},
|
||||
),
|
||||
); diff != "" {
|
||||
t.Fatalf("Parser.getBuilds() wrong packer builds. %s", diff)
|
||||
|
@ -9,6 +9,8 @@ import (
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/packer/helper/config"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/json"
|
||||
)
|
||||
|
||||
type NestedMockConfig struct {
|
||||
@ -38,6 +40,27 @@ type MockConfig struct {
|
||||
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
|
||||
//////
|
||||
@ -51,9 +74,7 @@ var _ packer.Builder = new(MockBuilder)
|
||||
func (b *MockBuilder) ConfigSpec() hcldec.ObjectSpec { return b.Config.FlatMapstructure().HCL2Spec() }
|
||||
|
||||
func (b *MockBuilder) Prepare(raws ...interface{}) ([]string, []string, error) {
|
||||
return nil, nil, config.Decode(&b.Config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
}, raws...)
|
||||
return nil, nil, b.Config.Prepare(raws...)
|
||||
}
|
||||
|
||||
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 {
|
||||
return config.Decode(&b.Config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
}, raws...)
|
||||
return b.Config.Prepare(raws...)
|
||||
}
|
||||
|
||||
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 {
|
||||
return config.Decode(&b.Config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
}, raws...)
|
||||
return b.Config.Prepare(raws...)
|
||||
}
|
||||
|
||||
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) {
|
||||
return nil, config.Decode(&b.Config, &config.DecodeOpts{
|
||||
Interpolate: true,
|
||||
}, raws...)
|
||||
return nil, b.Config.Prepare(raws...)
|
||||
}
|
||||
|
||||
//////
|
||||
|
@ -123,9 +123,14 @@ func (p *Parser) parse(filename string, vars map[string]string) (*PackerConfig,
|
||||
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
|
||||
for _, file := range files {
|
||||
diags = append(diags, p.decodeConfig(file, cfg)...)
|
||||
|
@ -29,6 +29,7 @@ variable "super_secret_password" {
|
||||
description = <<IMSENSIBLE
|
||||
Handle with care plz
|
||||
IMSENSIBLE
|
||||
default = null
|
||||
}
|
||||
|
||||
locals {
|
||||
|
0
hcl2template/testdata/variables/empty.pkr.hcl
vendored
Normal file
0
hcl2template/testdata/variables/empty.pkr.hcl
vendored
Normal file
@ -1,4 +1,5 @@
|
||||
|
||||
variable "broken_type" {
|
||||
variable "broken_variable" {
|
||||
invalid = true
|
||||
default = true
|
||||
}
|
||||
|
15
hcl2template/testdata/variables/unset_unused_string_variable.pkr.hcl
vendored
Normal file
15
hcl2template/testdata/variables/unset_unused_string_variable.pkr.hcl
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
|
||||
variable "foo" {
|
||||
type = string
|
||||
}
|
||||
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"source.null.null-builder",
|
||||
]
|
||||
}
|
||||
|
||||
source "null" "null-builder" {
|
||||
communicator = "none"
|
||||
}
|
10
hcl2template/testdata/variables/unset_used_string_variable.pkr.hcl
vendored
Normal file
10
hcl2template/testdata/variables/unset_used_string_variable.pkr.hcl
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
|
||||
variable "foo" {
|
||||
type = string
|
||||
}
|
||||
|
||||
build {
|
||||
sources = [
|
||||
"source.null.null-builder${var.foo}",
|
||||
]
|
||||
}
|
@ -60,6 +60,9 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti
|
||||
Config hcl.Body `hcl:",remain"`
|
||||
}
|
||||
diags := gohcl.DecodeBody(block.Body, nil, &b)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
|
||||
for _, buildFrom := range b.FromSources {
|
||||
ref := sourceRefFromString(buildFrom)
|
||||
@ -84,6 +87,9 @@ func (p *Parser) decodeBuildConfig(block *hcl.Block) (*BuildBlock, hcl.Diagnosti
|
||||
|
||||
content, moreDiags := b.Config.Content(buildSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
if diags.HasErrors() {
|
||||
return nil, diags
|
||||
}
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case buildProvisionerLabel:
|
||||
|
@ -2,6 +2,7 @@ package hcl2template
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/packer/helper/common"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
@ -24,19 +25,27 @@ type PackerConfig struct {
|
||||
InputVariables Variables
|
||||
LocalVariables Variables
|
||||
|
||||
ValidationOptions
|
||||
|
||||
// Builds is the list of Build blocks defined in the config files.
|
||||
Builds Builds
|
||||
}
|
||||
|
||||
type ValidationOptions struct {
|
||||
Strict bool
|
||||
}
|
||||
|
||||
// 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
|
||||
// the list of defined functions.
|
||||
func (cfg *PackerConfig) EvalContext() *hcl.EvalContext {
|
||||
inputVariables, _ := cfg.InputVariables.Values()
|
||||
localVariables, _ := cfg.LocalVariables.Values()
|
||||
ectx := &hcl.EvalContext{
|
||||
Functions: Functions(cfg.Basedir),
|
||||
Variables: map[string]cty.Value{
|
||||
"var": cty.ObjectVal(cfg.InputVariables.Values()),
|
||||
"local": cty.ObjectVal(cfg.LocalVariables.Values()),
|
||||
"var": cty.ObjectVal(inputVariables),
|
||||
"local": cty.ObjectVal(localVariables),
|
||||
},
|
||||
}
|
||||
return ectx
|
||||
@ -76,20 +85,19 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*Local, hcl.Diagnosti
|
||||
|
||||
content, moreDiags := f.Body.Content(configSchema)
|
||||
diags = append(diags, moreDiags...)
|
||||
var allLocals []*Local
|
||||
var locals []*Local
|
||||
|
||||
for _, block := range content.Blocks {
|
||||
switch block.Type {
|
||||
case localsLabel:
|
||||
attrs, moreDiags := block.Body.JustAttributes()
|
||||
diags = append(diags, moreDiags...)
|
||||
locals := make([]*Local, 0, len(attrs))
|
||||
for name, attr := range attrs {
|
||||
if _, found := c.LocalVariables[name]; found {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate variable",
|
||||
Detail: "Duplicate " + name + " variable definition found.",
|
||||
Summary: "Duplicate value in " + localsLabel,
|
||||
Detail: "Duplicate " + name + " definition found.",
|
||||
Subject: attr.NameRange.Ptr(),
|
||||
Context: block.DefRange.Ptr(),
|
||||
})
|
||||
@ -100,11 +108,10 @@ func (c *PackerConfig) parseLocalVariables(f *hcl.File) ([]*Local, hcl.Diagnosti
|
||||
Expr: attr.Expr,
|
||||
})
|
||||
}
|
||||
allLocals = append(allLocals, locals...)
|
||||
}
|
||||
}
|
||||
|
||||
return allLocals, diags
|
||||
return locals, diags
|
||||
}
|
||||
|
||||
func (c *PackerConfig) evaluateLocalVariables(locals []*Local) hcl.Diagnostics {
|
||||
@ -156,6 +163,7 @@ func (c *PackerConfig) evaluateLocalVariable(local *Local) hcl.Diagnostics {
|
||||
return diags
|
||||
}
|
||||
c.LocalVariables[local.Name] = &Variable{
|
||||
Name: local.Name,
|
||||
DefaultValue: value,
|
||||
Type: value.Type(),
|
||||
}
|
||||
|
@ -23,15 +23,19 @@ func TestParser_complete(t *testing.T) {
|
||||
Basedir: "testdata/complete",
|
||||
InputVariables: Variables{
|
||||
"foo": &Variable{
|
||||
Name: "foo",
|
||||
DefaultValue: cty.StringVal("value"),
|
||||
},
|
||||
"image_id": &Variable{
|
||||
Name: "image_id",
|
||||
DefaultValue: cty.StringVal("image-id-default"),
|
||||
},
|
||||
"port": &Variable{
|
||||
Name: "port",
|
||||
DefaultValue: cty.NumberIntVal(42),
|
||||
},
|
||||
"availability_zone_names": &Variable{
|
||||
Name: "availability_zone_names",
|
||||
DefaultValue: cty.ListVal([]cty.Value{
|
||||
cty.StringVal("A"),
|
||||
cty.StringVal("B"),
|
||||
@ -41,15 +45,18 @@ func TestParser_complete(t *testing.T) {
|
||||
},
|
||||
LocalVariables: Variables{
|
||||
"feefoo": &Variable{
|
||||
Name: "feefoo",
|
||||
DefaultValue: cty.StringVal("value_image-id-default"),
|
||||
},
|
||||
"standard_tags": &Variable{
|
||||
Name: "standard_tags",
|
||||
DefaultValue: cty.ObjectVal(map[string]cty.Value{
|
||||
"Component": cty.StringVal("user-service"),
|
||||
"Environment": cty.StringVal("production"),
|
||||
}),
|
||||
},
|
||||
"abc_map": &Variable{
|
||||
Name: "abc_map",
|
||||
DefaultValue: cty.TupleVal([]cty.Value{
|
||||
cty.ObjectVal(map[string]cty.Value{
|
||||
"id": cty.StringVal("a"),
|
||||
|
@ -39,13 +39,15 @@ type Variable struct {
|
||||
// declaration, the type of the default variable will be used. This will
|
||||
// allow to ensure that users set this variable correctly.
|
||||
Type cty.Type
|
||||
// Common name of the variable
|
||||
Name string
|
||||
// Description of the variable
|
||||
Description string
|
||||
// When Sensitive is set to true Packer will try it best to hide/obfuscate
|
||||
// the variable from the output stream. By replacing the text.
|
||||
Sensitive bool
|
||||
|
||||
block *hcl.Block
|
||||
Range hcl.Range
|
||||
}
|
||||
|
||||
func (v *Variable) GoString() string {
|
||||
@ -60,27 +62,37 @@ func (v *Variable) Value() (cty.Value, *hcl.Diagnostic) {
|
||||
v.EnvValue,
|
||||
v.DefaultValue,
|
||||
} {
|
||||
if !value.IsNull() {
|
||||
if value != cty.NilVal {
|
||||
return value, nil
|
||||
}
|
||||
}
|
||||
return cty.NilVal, &hcl.Diagnostic{
|
||||
|
||||
value := cty.NullVal(cty.DynamicPseudoType)
|
||||
|
||||
return value, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Unset variable",
|
||||
Detail: "A used variable must be set; see " +
|
||||
"https://packer.io/docs/configuration/from-1.5/syntax.html for details.",
|
||||
Context: v.block.DefRange.Ptr(),
|
||||
Summary: fmt.Sprintf("Unset variable %q", v.Name),
|
||||
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.",
|
||||
Context: v.Range.Ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
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{}
|
||||
var diags hcl.Diagnostics
|
||||
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
|
||||
@ -108,8 +120,10 @@ func (variables *Variables) decodeVariable(key string, attr *hcl.Attribute, ectx
|
||||
}
|
||||
|
||||
(*variables)[key] = &Variable{
|
||||
Name: key,
|
||||
DefaultValue: value,
|
||||
Type: value.Type(),
|
||||
Range: attr.Range,
|
||||
}
|
||||
|
||||
return diags
|
||||
@ -142,10 +156,13 @@ func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.Eval
|
||||
return diags
|
||||
}
|
||||
|
||||
name := block.Labels[0]
|
||||
|
||||
res := &Variable{
|
||||
Name: name,
|
||||
Description: b.Description,
|
||||
Sensitive: b.Sensitive,
|
||||
block: block,
|
||||
Range: block.DefRange,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -215,8 +232,9 @@ func (variables *Variables) decodeVariableBlock(block *hcl.Block, ectx *hcl.Eval
|
||||
// them.
|
||||
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
|
||||
variables := cfg.InputVariables
|
||||
|
||||
for _, raw := range env {
|
||||
if !strings.HasPrefix(raw, VarEnvPrefix) {
|
||||
@ -245,6 +263,7 @@ func (variables Variables) collectVariableValues(env []string, files []*hcl.File
|
||||
if moreDiags.HasErrors() {
|
||||
continue
|
||||
}
|
||||
|
||||
val, valDiags := expr.Value(nil)
|
||||
diags = append(diags, valDiags...)
|
||||
if variable.Type != cty.NilType {
|
||||
@ -310,7 +329,20 @@ func (variables Variables) collectVariableValues(env []string, files []*hcl.File
|
||||
for name, attr := range attrs {
|
||||
variable, found := variables[name]
|
||||
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
|
||||
}
|
||||
|
||||
@ -340,8 +372,8 @@ func (variables Variables) collectVariableValues(env []string, files []*hcl.File
|
||||
variable, found := variables[name]
|
||||
if !found {
|
||||
diags = append(diags, &hcl.Diagnostic{
|
||||
Severity: hcl.DiagWarning,
|
||||
Summary: "Unknown -var variable",
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Undefined -var variable",
|
||||
Detail: fmt.Sprintf("A %q variable was passed in the command "+
|
||||
"line but was not found in known variables."+
|
||||
"To declare variable %q, place this block in one of your"+
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/zclconf/go-cty/cty/convert"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/packer/builder/null"
|
||||
"github.com/hashicorp/packer/packer"
|
||||
)
|
||||
|
||||
@ -24,36 +25,46 @@ func TestParse_variables(t *testing.T) {
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"image_name": &Variable{
|
||||
Name: "image_name",
|
||||
DefaultValue: cty.StringVal("foo-image-{{user `my_secret`}}"),
|
||||
},
|
||||
"key": &Variable{
|
||||
Name: "key",
|
||||
DefaultValue: cty.StringVal("value"),
|
||||
},
|
||||
"my_secret": &Variable{
|
||||
Name: "my_secret",
|
||||
DefaultValue: cty.StringVal("foo"),
|
||||
},
|
||||
"image_id": &Variable{
|
||||
Name: "image_id",
|
||||
DefaultValue: cty.StringVal("image-id-default"),
|
||||
},
|
||||
"port": &Variable{
|
||||
Name: "port",
|
||||
DefaultValue: cty.NumberIntVal(42),
|
||||
},
|
||||
"availability_zone_names": &Variable{
|
||||
Name: "availability_zone_names",
|
||||
DefaultValue: cty.ListVal([]cty.Value{
|
||||
cty.StringVal("us-west-1a"),
|
||||
}),
|
||||
Description: fmt.Sprintln("Describing is awesome ;D"),
|
||||
},
|
||||
"super_secret_password": &Variable{
|
||||
Sensitive: true,
|
||||
Description: fmt.Sprintln("Handle with care plz"),
|
||||
Name: "super_secret_password",
|
||||
Sensitive: true,
|
||||
DefaultValue: cty.NullVal(cty.String),
|
||||
Description: fmt.Sprintln("Handle with care plz"),
|
||||
},
|
||||
},
|
||||
LocalVariables: Variables{
|
||||
"owner": &Variable{
|
||||
Name: "owner",
|
||||
DefaultValue: cty.StringVal("Community Team"),
|
||||
},
|
||||
"service_name": &Variable{
|
||||
Name: "service_name",
|
||||
DefaultValue: cty.StringVal("forum"),
|
||||
},
|
||||
},
|
||||
@ -68,7 +79,9 @@ func TestParse_variables(t *testing.T) {
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"boolean_value": &Variable{},
|
||||
"boolean_value": &Variable{
|
||||
Name: "boolean_value",
|
||||
},
|
||||
},
|
||||
},
|
||||
true, true,
|
||||
@ -81,7 +94,9 @@ func TestParse_variables(t *testing.T) {
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"boolean_value": &Variable{},
|
||||
"boolean_value": &Variable{
|
||||
Name: "boolean_value",
|
||||
},
|
||||
},
|
||||
},
|
||||
true, true,
|
||||
@ -94,26 +109,84 @@ func TestParse_variables(t *testing.T) {
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"broken_type": &Variable{},
|
||||
"broken_type": &Variable{
|
||||
Name: "broken_type",
|
||||
},
|
||||
},
|
||||
},
|
||||
true, true,
|
||||
[]packer.Build{},
|
||||
false,
|
||||
},
|
||||
{"invalid default type",
|
||||
|
||||
{"unknown key",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/unknown_key.pkr.hcl", nil},
|
||||
&PackerConfig{
|
||||
Basedir: filepath.Join("testdata", "variables"),
|
||||
InputVariables: Variables{
|
||||
"broken_type": &Variable{},
|
||||
"broken_variable": &Variable{
|
||||
Name: "broken_variable",
|
||||
DefaultValue: cty.BoolVal(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
true, false,
|
||||
[]packer.Build{},
|
||||
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",
|
||||
defaultParser,
|
||||
parseTestArgs{"testdata/variables/complicated", nil},
|
||||
@ -121,23 +194,29 @@ func TestParse_variables(t *testing.T) {
|
||||
Basedir: "testdata/variables/complicated",
|
||||
InputVariables: Variables{
|
||||
"name_prefix": &Variable{
|
||||
Name: "name_prefix",
|
||||
DefaultValue: cty.StringVal("foo"),
|
||||
},
|
||||
},
|
||||
LocalVariables: Variables{
|
||||
"name_prefix": &Variable{
|
||||
Name: "name_prefix",
|
||||
DefaultValue: cty.StringVal("foo"),
|
||||
},
|
||||
"foo": &Variable{
|
||||
Name: "foo",
|
||||
DefaultValue: cty.StringVal("foo"),
|
||||
},
|
||||
"bar": &Variable{
|
||||
Name: "bar",
|
||||
DefaultValue: cty.StringVal("foo"),
|
||||
},
|
||||
"for_var": &Variable{
|
||||
Name: "for_var",
|
||||
DefaultValue: cty.StringVal("foo"),
|
||||
},
|
||||
"bar_var": &Variable{
|
||||
Name: "bar_var",
|
||||
DefaultValue: cty.TupleVal([]cty.Value{
|
||||
cty.StringVal("foo"),
|
||||
cty.StringVal("foo"),
|
||||
@ -172,12 +251,14 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
argv map[string]string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
variables Variables
|
||||
args args
|
||||
wantDiags bool
|
||||
wantVariables Variables
|
||||
wantValues map[string]cty.Value
|
||||
name string
|
||||
variables Variables
|
||||
validationOptions ValidationOptions
|
||||
args args
|
||||
wantDiags bool
|
||||
wantDiagsHasError bool
|
||||
wantVariables Variables
|
||||
wantValues map[string]cty.Value
|
||||
}{
|
||||
|
||||
{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{},
|
||||
args: args{
|
||||
env: []string{`PKR_VAR_unused_string=value`},
|
||||
hclFiles: []string{`unused_string="value"`},
|
||||
hclFiles: []string{`undefined_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
|
||||
@ -342,18 +451,19 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
wantValues: map[string]cty.Value{},
|
||||
},
|
||||
|
||||
{name: "undefined but set value - args",
|
||||
{name: "undefined but set value - argv",
|
||||
variables: Variables{},
|
||||
args: args{
|
||||
argv: map[string]string{
|
||||
"unused_string": "value",
|
||||
"undefined_string": "value",
|
||||
},
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
wantDiags: true,
|
||||
wantDiagsHasError: true,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
},
|
||||
|
||||
{name: "value not corresponding to type - env",
|
||||
@ -367,7 +477,8 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantDiags: true,
|
||||
wantDiagsHasError: true,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.List(cty.String),
|
||||
@ -390,7 +501,8 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantDiags: true,
|
||||
wantDiagsHasError: true,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.Bool,
|
||||
@ -415,7 +527,8 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantDiags: true,
|
||||
wantDiagsHasError: true,
|
||||
wantVariables: Variables{
|
||||
"used_string": &Variable{
|
||||
Type: cty.Bool,
|
||||
@ -434,9 +547,10 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
},
|
||||
|
||||
// output
|
||||
wantDiags: true,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
wantDiags: true,
|
||||
wantDiagsHasError: true,
|
||||
wantVariables: Variables{},
|
||||
wantValues: map[string]cty.Value{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@ -450,9 +564,17 @@ func TestVariables_collectVariableValues(t *testing.T) {
|
||||
}
|
||||
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)
|
||||
}
|
||||
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 != "" {
|
||||
t.Fatalf("didn't get expected variables: %s", diff)
|
||||
}
|
||||
|
9
vendor/github.com/zclconf/go-cty/cty/convert/conversion.go
generated
vendored
9
vendor/github.com/zclconf/go-cty/cty/convert/conversion.go
generated
vendored
@ -138,6 +138,15 @@ func getConversionKnown(in cty.Type, out cty.Type, unsafe bool) conversion {
|
||||
outEty := out.ElementType()
|
||||
return conversionObjectToMap(in, outEty, unsafe)
|
||||
|
||||
case out.IsObjectType() && in.IsMapType():
|
||||
if !unsafe {
|
||||
// Converting a map to an object is an "unsafe" conversion,
|
||||
// because we don't know if all the map keys will correspond to
|
||||
// object attributes.
|
||||
return nil
|
||||
}
|
||||
return conversionMapToObject(in, out, unsafe)
|
||||
|
||||
case in.IsCapsuleType() || out.IsCapsuleType():
|
||||
if !unsafe {
|
||||
// Capsule types can only participate in "unsafe" conversions,
|
||||
|
186
vendor/github.com/zclconf/go-cty/cty/convert/conversion_collection.go
generated
vendored
186
vendor/github.com/zclconf/go-cty/cty/convert/conversion_collection.go
generated
vendored
@ -15,18 +15,18 @@ func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make([]cty.Value, 0, val.LengthInt())
|
||||
i := int64(0)
|
||||
path = append(path, nil)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
_, val := it.Element()
|
||||
var err error
|
||||
|
||||
path[len(path)-1] = cty.IndexStep{
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: cty.NumberIntVal(i),
|
||||
}
|
||||
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
@ -37,6 +37,9 @@ func conversionCollectionToList(ety cty.Type, conv conversion) conversion {
|
||||
}
|
||||
|
||||
if len(elems) == 0 {
|
||||
if ety == cty.DynamicPseudoType {
|
||||
ety = val.Type().ElementType()
|
||||
}
|
||||
return cty.ListValEmpty(ety), nil
|
||||
}
|
||||
|
||||
@ -55,18 +58,18 @@ func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make([]cty.Value, 0, val.LengthInt())
|
||||
i := int64(0)
|
||||
path = append(path, nil)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
_, val := it.Element()
|
||||
var err error
|
||||
|
||||
path[len(path)-1] = cty.IndexStep{
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: cty.NumberIntVal(i),
|
||||
}
|
||||
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
@ -77,6 +80,11 @@ func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
|
||||
}
|
||||
|
||||
if len(elems) == 0 {
|
||||
// Prefer a concrete type over a dynamic type when returning an
|
||||
// empty set
|
||||
if ety == cty.DynamicPseudoType {
|
||||
ety = val.Type().ElementType()
|
||||
}
|
||||
return cty.SetValEmpty(ety), nil
|
||||
}
|
||||
|
||||
@ -93,13 +101,13 @@ func conversionCollectionToSet(ety cty.Type, conv conversion) conversion {
|
||||
func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make(map[string]cty.Value, 0)
|
||||
path = append(path, nil)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
key, val := it.Element()
|
||||
var err error
|
||||
|
||||
path[len(path)-1] = cty.IndexStep{
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
@ -107,11 +115,11 @@ func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
|
||||
if err != nil {
|
||||
// Should never happen, because keys can only be numbers or
|
||||
// strings and both can convert to string.
|
||||
return cty.DynamicVal, path.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName())
|
||||
return cty.DynamicVal, elemPath.NewErrorf("cannot convert key type %s to string for map", key.Type().FriendlyName())
|
||||
}
|
||||
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
@ -121,9 +129,25 @@ func conversionCollectionToMap(ety cty.Type, conv conversion) conversion {
|
||||
}
|
||||
|
||||
if len(elems) == 0 {
|
||||
// Prefer a concrete type over a dynamic type when returning an
|
||||
// empty map
|
||||
if ety == cty.DynamicPseudoType {
|
||||
ety = val.Type().ElementType()
|
||||
}
|
||||
return cty.MapValEmpty(ety), nil
|
||||
}
|
||||
|
||||
if ety.IsCollectionType() || ety.IsObjectType() {
|
||||
var err error
|
||||
if elems, err = conversionUnifyCollectionElements(elems, path, false); err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := conversionCheckMapElementTypes(elems, path); err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
|
||||
return cty.MapVal(elems), nil
|
||||
}
|
||||
}
|
||||
@ -171,20 +195,20 @@ func conversionTupleToSet(tupleType cty.Type, listEty cty.Type, unsafe bool) con
|
||||
// element conversions in elemConvs
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make([]cty.Value, 0, len(elemConvs))
|
||||
path = append(path, nil)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
i := int64(0)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
_, val := it.Element()
|
||||
var err error
|
||||
|
||||
path[len(path)-1] = cty.IndexStep{
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: cty.NumberIntVal(i),
|
||||
}
|
||||
|
||||
conv := elemConvs[i]
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
@ -241,20 +265,20 @@ func conversionTupleToList(tupleType cty.Type, listEty cty.Type, unsafe bool) co
|
||||
// element conversions in elemConvs
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make([]cty.Value, 0, len(elemConvs))
|
||||
path = append(path, nil)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
i := int64(0)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
_, val := it.Element()
|
||||
var err error
|
||||
|
||||
path[len(path)-1] = cty.IndexStep{
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: cty.NumberIntVal(i),
|
||||
}
|
||||
|
||||
conv := elemConvs[i]
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
@ -315,19 +339,19 @@ func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) co
|
||||
// element conversions in elemConvs
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make(map[string]cty.Value, len(elemConvs))
|
||||
path = append(path, nil)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
name, val := it.Element()
|
||||
var err error
|
||||
|
||||
path[len(path)-1] = cty.IndexStep{
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: name,
|
||||
}
|
||||
|
||||
conv := elemConvs[name.AsString()]
|
||||
if conv != nil {
|
||||
val, err = conv(val, path)
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
@ -335,6 +359,130 @@ func conversionObjectToMap(objectType cty.Type, mapEty cty.Type, unsafe bool) co
|
||||
elems[name.AsString()] = val
|
||||
}
|
||||
|
||||
if mapEty.IsCollectionType() || mapEty.IsObjectType() {
|
||||
var err error
|
||||
if elems, err = conversionUnifyCollectionElements(elems, path, unsafe); err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := conversionCheckMapElementTypes(elems, path); err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
|
||||
return cty.MapVal(elems), nil
|
||||
}
|
||||
}
|
||||
|
||||
// conversionMapToObject returns a conversion that will take a value of the
|
||||
// given map type and return an object of the given type. The object attribute
|
||||
// types must all be compatible with the map element type.
|
||||
//
|
||||
// Will panic if the given mapType and objType are not maps and objects
|
||||
// respectively.
|
||||
func conversionMapToObject(mapType cty.Type, objType cty.Type, unsafe bool) conversion {
|
||||
objectAtys := objType.AttributeTypes()
|
||||
mapEty := mapType.ElementType()
|
||||
|
||||
elemConvs := make(map[string]conversion, len(objectAtys))
|
||||
for name, objectAty := range objectAtys {
|
||||
if objectAty.Equals(mapEty) {
|
||||
// no conversion required
|
||||
continue
|
||||
}
|
||||
|
||||
elemConvs[name] = getConversion(mapEty, objectAty, unsafe)
|
||||
if elemConvs[name] == nil {
|
||||
// If any of our element conversions are impossible, then the our
|
||||
// whole conversion is impossible.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// If we fall out here then a conversion is possible, using the
|
||||
// element conversions in elemConvs
|
||||
return func(val cty.Value, path cty.Path) (cty.Value, error) {
|
||||
elems := make(map[string]cty.Value, len(elemConvs))
|
||||
elemPath := append(path.Copy(), nil)
|
||||
it := val.ElementIterator()
|
||||
for it.Next() {
|
||||
name, val := it.Element()
|
||||
|
||||
// if there is no corresponding attribute, we skip this key
|
||||
if _, ok := objectAtys[name.AsString()]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: name,
|
||||
}
|
||||
|
||||
conv := elemConvs[name.AsString()]
|
||||
if conv != nil {
|
||||
val, err = conv(val, elemPath)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
}
|
||||
|
||||
elems[name.AsString()] = val
|
||||
}
|
||||
|
||||
return cty.ObjectVal(elems), nil
|
||||
}
|
||||
}
|
||||
|
||||
func conversionUnifyCollectionElements(elems map[string]cty.Value, path cty.Path, unsafe bool) (map[string]cty.Value, error) {
|
||||
elemTypes := make([]cty.Type, 0, len(elems))
|
||||
for _, elem := range elems {
|
||||
elemTypes = append(elemTypes, elem.Type())
|
||||
}
|
||||
unifiedType, _ := unify(elemTypes, unsafe)
|
||||
if unifiedType == cty.NilType {
|
||||
}
|
||||
|
||||
unifiedElems := make(map[string]cty.Value)
|
||||
elemPath := append(path.Copy(), nil)
|
||||
|
||||
for name, elem := range elems {
|
||||
if elem.Type().Equals(unifiedType) {
|
||||
unifiedElems[name] = elem
|
||||
continue
|
||||
}
|
||||
conv := getConversion(elem.Type(), unifiedType, unsafe)
|
||||
if conv == nil {
|
||||
}
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: cty.StringVal(name),
|
||||
}
|
||||
val, err := conv(elem, elemPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unifiedElems[name] = val
|
||||
}
|
||||
|
||||
return unifiedElems, nil
|
||||
}
|
||||
|
||||
func conversionCheckMapElementTypes(elems map[string]cty.Value, path cty.Path) error {
|
||||
elementType := cty.NilType
|
||||
elemPath := append(path.Copy(), nil)
|
||||
|
||||
for name, elem := range elems {
|
||||
if elementType == cty.NilType {
|
||||
elementType = elem.Type()
|
||||
continue
|
||||
}
|
||||
if !elementType.Equals(elem.Type()) {
|
||||
elemPath[len(elemPath)-1] = cty.IndexStep{
|
||||
Key: cty.StringVal(name),
|
||||
}
|
||||
return elemPath.NewErrorf("%s is required", elementType.FriendlyName())
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
43
vendor/github.com/zclconf/go-cty/cty/convert/unify.go
generated
vendored
43
vendor/github.com/zclconf/go-cty/cty/convert/unify.go
generated
vendored
@ -28,11 +28,14 @@ func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
|
||||
// a subset of that type, which would be a much less useful conversion for
|
||||
// unification purposes.
|
||||
{
|
||||
mapCt := 0
|
||||
objectCt := 0
|
||||
tupleCt := 0
|
||||
dynamicCt := 0
|
||||
for _, ty := range types {
|
||||
switch {
|
||||
case ty.IsMapType():
|
||||
mapCt++
|
||||
case ty.IsObjectType():
|
||||
objectCt++
|
||||
case ty.IsTupleType():
|
||||
@ -44,6 +47,8 @@ func unify(types []cty.Type, unsafe bool) (cty.Type, []Conversion) {
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case mapCt > 0 && (mapCt+dynamicCt) == len(types):
|
||||
return unifyMapTypes(types, unsafe, dynamicCt > 0)
|
||||
case objectCt > 0 && (objectCt+dynamicCt) == len(types):
|
||||
return unifyObjectTypes(types, unsafe, dynamicCt > 0)
|
||||
case tupleCt > 0 && (tupleCt+dynamicCt) == len(types):
|
||||
@ -95,6 +100,44 @@ Preferences:
|
||||
return cty.NilType, nil
|
||||
}
|
||||
|
||||
func unifyMapTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
|
||||
// If we had any dynamic types in the input here then we can't predict
|
||||
// what path we'll take through here once these become known types, so
|
||||
// we'll conservatively produce DynamicVal for these.
|
||||
if hasDynamic {
|
||||
return unifyAllAsDynamic(types)
|
||||
}
|
||||
|
||||
elemTypes := make([]cty.Type, 0, len(types))
|
||||
for _, ty := range types {
|
||||
elemTypes = append(elemTypes, ty.ElementType())
|
||||
}
|
||||
retElemType, _ := unify(elemTypes, unsafe)
|
||||
if retElemType == cty.NilType {
|
||||
return cty.NilType, nil
|
||||
}
|
||||
|
||||
retTy := cty.Map(retElemType)
|
||||
|
||||
conversions := make([]Conversion, len(types))
|
||||
for i, ty := range types {
|
||||
if ty.Equals(retTy) {
|
||||
continue
|
||||
}
|
||||
if unsafe {
|
||||
conversions[i] = GetConversionUnsafe(ty, retTy)
|
||||
} else {
|
||||
conversions[i] = GetConversion(ty, retTy)
|
||||
}
|
||||
if conversions[i] == nil {
|
||||
// Shouldn't be reachable, since we were able to unify
|
||||
return cty.NilType, nil
|
||||
}
|
||||
}
|
||||
|
||||
return retTy, conversions
|
||||
}
|
||||
|
||||
func unifyObjectTypes(types []cty.Type, unsafe bool, hasDynamic bool) (cty.Type, []Conversion) {
|
||||
// If we had any dynamic types in the input here then we can't predict
|
||||
// what path we'll take through here once these become known types, so
|
||||
|
182
vendor/github.com/zclconf/go-cty/cty/function/stdlib/collection.go
generated
vendored
182
vendor/github.com/zclconf/go-cty/cty/function/stdlib/collection.go
generated
vendored
@ -299,7 +299,7 @@ var ContainsFunc = function.New(&function.Spec{
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Bool),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
arg := args[0]
|
||||
ty := arg.Type()
|
||||
|
||||
@ -307,12 +307,39 @@ var ContainsFunc = function.New(&function.Spec{
|
||||
return cty.NilVal, errors.New("argument must be list, tuple, or set")
|
||||
}
|
||||
|
||||
_, err = Index(cty.TupleVal(arg.AsValueSlice()), args[1])
|
||||
if err != nil {
|
||||
if args[0].IsNull() {
|
||||
return cty.NilVal, errors.New("cannot search a nil list or set")
|
||||
}
|
||||
|
||||
if args[0].LengthInt() == 0 {
|
||||
return cty.False, nil
|
||||
}
|
||||
|
||||
return cty.True, nil
|
||||
if !args[0].IsKnown() || !args[1].IsKnown() {
|
||||
return cty.UnknownVal(cty.Bool), nil
|
||||
}
|
||||
|
||||
containsUnknown := false
|
||||
for it := args[0].ElementIterator(); it.Next(); {
|
||||
_, v := it.Element()
|
||||
eq := args[1].Equals(v)
|
||||
if !eq.IsKnown() {
|
||||
// We may have an unknown value which could match later, but we
|
||||
// first need to continue checking all values for an exact
|
||||
// match.
|
||||
containsUnknown = true
|
||||
continue
|
||||
}
|
||||
if eq.True() {
|
||||
return cty.True, nil
|
||||
}
|
||||
}
|
||||
|
||||
if containsUnknown {
|
||||
return cty.UnknownVal(cty.Bool), nil
|
||||
}
|
||||
|
||||
return cty.False, nil
|
||||
},
|
||||
})
|
||||
|
||||
@ -566,19 +593,12 @@ var LookupFunc = function.New(&function.Spec{
|
||||
Name: "key",
|
||||
Type: cty.String,
|
||||
},
|
||||
},
|
||||
VarParam: &function.Parameter{
|
||||
Name: "default",
|
||||
Type: cty.DynamicPseudoType,
|
||||
AllowUnknown: true,
|
||||
AllowDynamicType: true,
|
||||
AllowNull: true,
|
||||
{
|
||||
Name: "default",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
Type: func(args []cty.Value) (ret cty.Type, err error) {
|
||||
if len(args) < 1 || len(args) > 3 {
|
||||
return cty.NilType, fmt.Errorf("lookup() takes two or three arguments, got %d", len(args))
|
||||
}
|
||||
|
||||
ty := args[0].Type()
|
||||
|
||||
switch {
|
||||
@ -609,13 +629,7 @@ var LookupFunc = function.New(&function.Spec{
|
||||
}
|
||||
},
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
var defaultVal cty.Value
|
||||
defaultValueSet := false
|
||||
|
||||
if len(args) == 3 {
|
||||
defaultVal = args[2]
|
||||
defaultValueSet = true
|
||||
}
|
||||
defaultVal := args[2]
|
||||
|
||||
mapVar := args[0]
|
||||
lookupKey := args[1].AsString()
|
||||
@ -632,48 +646,128 @@ var LookupFunc = function.New(&function.Spec{
|
||||
return mapVar.Index(cty.StringVal(lookupKey)), nil
|
||||
}
|
||||
|
||||
if defaultValueSet {
|
||||
defaultVal, err = convert.Convert(defaultVal, retType)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
return defaultVal, nil
|
||||
defaultVal, err = convert.Convert(defaultVal, retType)
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
|
||||
return cty.UnknownVal(cty.DynamicPseudoType), fmt.Errorf(
|
||||
"lookup failed to find '%s'", lookupKey)
|
||||
return defaultVal, nil
|
||||
},
|
||||
})
|
||||
|
||||
// MergeFunc is a function that takes an arbitrary number of maps and
|
||||
// returns a single map that contains a merged set of elements from all of the maps.
|
||||
// MergeFunc constructs a function that takes an arbitrary number of maps or
|
||||
// objects, and returns a single value that contains a merged set of keys and
|
||||
// values from all of the inputs.
|
||||
//
|
||||
// If more than one given map defines the same key then the one that is later in
|
||||
// the argument sequence takes precedence.
|
||||
// If more than one given map or object defines the same key then the one that
|
||||
// is later in the argument sequence takes precedence.
|
||||
var MergeFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{},
|
||||
VarParam: &function.Parameter{
|
||||
Name: "maps",
|
||||
Type: cty.DynamicPseudoType,
|
||||
AllowDynamicType: true,
|
||||
AllowNull: true,
|
||||
},
|
||||
Type: func(args []cty.Value) (cty.Type, error) {
|
||||
// empty args is accepted, so assume an empty object since we have no
|
||||
// key-value types.
|
||||
if len(args) == 0 {
|
||||
return cty.EmptyObject, nil
|
||||
}
|
||||
|
||||
// collect the possible object attrs
|
||||
attrs := map[string]cty.Type{}
|
||||
|
||||
first := cty.NilType
|
||||
matching := true
|
||||
attrsKnown := true
|
||||
for i, arg := range args {
|
||||
ty := arg.Type()
|
||||
// any dynamic args mean we can't compute a type
|
||||
if ty.Equals(cty.DynamicPseudoType) {
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
|
||||
// check for invalid arguments
|
||||
if !ty.IsMapType() && !ty.IsObjectType() {
|
||||
return cty.NilType, fmt.Errorf("arguments must be maps or objects, got %#v", ty.FriendlyName())
|
||||
}
|
||||
|
||||
switch {
|
||||
case ty.IsObjectType() && !arg.IsNull():
|
||||
for attr, aty := range ty.AttributeTypes() {
|
||||
attrs[attr] = aty
|
||||
}
|
||||
case ty.IsMapType():
|
||||
switch {
|
||||
case arg.IsNull():
|
||||
// pass, nothing to add
|
||||
case arg.IsKnown():
|
||||
ety := arg.Type().ElementType()
|
||||
for it := arg.ElementIterator(); it.Next(); {
|
||||
attr, _ := it.Element()
|
||||
attrs[attr.AsString()] = ety
|
||||
}
|
||||
default:
|
||||
// any unknown maps means we don't know all possible attrs
|
||||
// for the return type
|
||||
attrsKnown = false
|
||||
}
|
||||
}
|
||||
|
||||
// record the first argument type for comparison
|
||||
if i == 0 {
|
||||
first = arg.Type()
|
||||
continue
|
||||
}
|
||||
|
||||
if !ty.Equals(first) && matching {
|
||||
matching = false
|
||||
}
|
||||
}
|
||||
|
||||
// the types all match, so use the first argument type
|
||||
if matching {
|
||||
return first, nil
|
||||
}
|
||||
|
||||
// We had a mix of unknown maps and objects, so we can't predict the
|
||||
// attributes
|
||||
if !attrsKnown {
|
||||
return cty.DynamicPseudoType, nil
|
||||
}
|
||||
|
||||
return cty.Object(attrs), nil
|
||||
},
|
||||
Type: function.StaticReturnType(cty.DynamicPseudoType),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
outputMap := make(map[string]cty.Value)
|
||||
|
||||
// if all inputs are null, return a null value rather than an object
|
||||
// with null attributes
|
||||
allNull := true
|
||||
for _, arg := range args {
|
||||
if !arg.IsWhollyKnown() {
|
||||
return cty.UnknownVal(retType), nil
|
||||
}
|
||||
if !arg.Type().IsObjectType() && !arg.Type().IsMapType() {
|
||||
return cty.NilVal, fmt.Errorf("arguments must be maps or objects, got %#v", arg.Type().FriendlyName())
|
||||
if arg.IsNull() {
|
||||
continue
|
||||
} else {
|
||||
allNull = false
|
||||
}
|
||||
|
||||
for it := arg.ElementIterator(); it.Next(); {
|
||||
k, v := it.Element()
|
||||
outputMap[k.AsString()] = v
|
||||
}
|
||||
}
|
||||
return cty.ObjectVal(outputMap), nil
|
||||
|
||||
switch {
|
||||
case allNull:
|
||||
return cty.NullVal(retType), nil
|
||||
case retType.IsMapType():
|
||||
return cty.MapVal(outputMap), nil
|
||||
case retType.IsObjectType(), retType.Equals(cty.DynamicPseudoType):
|
||||
return cty.ObjectVal(outputMap), nil
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected return type: %#v", retType))
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@ -1184,8 +1278,8 @@ func Keys(inputMap cty.Value) (cty.Value, error) {
|
||||
// Lookup performs a dynamic lookup into a map.
|
||||
// There are two required arguments, map and key, plus an optional default,
|
||||
// which is a value to return if no key is found in map.
|
||||
func Lookup(args ...cty.Value) (cty.Value, error) {
|
||||
return LookupFunc.Call(args)
|
||||
func Lookup(inputMap, key, defaultValue cty.Value) (cty.Value, error) {
|
||||
return LookupFunc.Call([]cty.Value{inputMap, key, defaultValue})
|
||||
}
|
||||
|
||||
// Merge takes an arbitrary number of maps and returns a single map that contains
|
||||
|
2
vendor/github.com/zclconf/go-cty/cty/function/stdlib/datetime.go
generated
vendored
2
vendor/github.com/zclconf/go-cty/cty/function/stdlib/datetime.go
generated
vendored
@ -217,7 +217,7 @@ var TimeAddFunc = function.New(&function.Spec{
|
||||
},
|
||||
Type: function.StaticReturnType(cty.String),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||
ts, err := time.Parse(time.RFC3339, args[0].AsString())
|
||||
ts, err := parseTimestamp(args[0].AsString())
|
||||
if err != nil {
|
||||
return cty.UnknownVal(cty.String), err
|
||||
}
|
||||
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -638,7 +638,7 @@ github.com/yandex-cloud/go-sdk/pkg/retry
|
||||
github.com/yandex-cloud/go-sdk/pkg/sdkerrors
|
||||
github.com/yandex-cloud/go-sdk/pkg/singleflight
|
||||
github.com/yandex-cloud/go-sdk/sdkresolvers
|
||||
# github.com/zclconf/go-cty v1.2.1 => github.com/azr/go-cty v1.1.1-0.20200203143058-28fcda2fe0cc
|
||||
# github.com/zclconf/go-cty v1.3.1
|
||||
github.com/zclconf/go-cty/cty
|
||||
github.com/zclconf/go-cty/cty/convert
|
||||
github.com/zclconf/go-cty/cty/function
|
||||
|
@ -278,3 +278,48 @@ precedence over earlier ones:
|
||||
|
||||
~> **Important:** Variables with map and object values behave the same way as
|
||||
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 won’t 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` | - | - |
|
||||
|
Loading…
x
Reference in New Issue
Block a user