Merge pull request #29559 from hashicorp/jbardin/optional-attrs
Prevent `ObjectWithOptionalAttrs` from escaping
This commit is contained in:
commit
8ed9a270e5
|
@ -24,9 +24,10 @@ func TestParseVariableValuesUndeclared(t *testing.T) {
|
||||||
}
|
}
|
||||||
decls := map[string]*configs.Variable{
|
decls := map[string]*configs.Variable{
|
||||||
"declared1": {
|
"declared1": {
|
||||||
Name: "declared1",
|
Name: "declared1",
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
ParsingMode: configs.VariableParseLiteral,
|
ConstraintType: cty.String,
|
||||||
|
ParsingMode: configs.VariableParseLiteral,
|
||||||
DeclRange: hcl.Range{
|
DeclRange: hcl.Range{
|
||||||
Filename: "fake.tf",
|
Filename: "fake.tf",
|
||||||
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
Start: hcl.Pos{Line: 2, Column: 1, Byte: 0},
|
||||||
|
@ -34,9 +35,10 @@ func TestParseVariableValuesUndeclared(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"missing1": {
|
"missing1": {
|
||||||
Name: "missing1",
|
Name: "missing1",
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
ParsingMode: configs.VariableParseLiteral,
|
ConstraintType: cty.String,
|
||||||
|
ParsingMode: configs.VariableParseLiteral,
|
||||||
DeclRange: hcl.Range{
|
DeclRange: hcl.Range{
|
||||||
Filename: "fake.tf",
|
Filename: "fake.tf",
|
||||||
Start: hcl.Pos{Line: 3, Column: 1, Byte: 0},
|
Start: hcl.Pos{Line: 3, Column: 1, Byte: 0},
|
||||||
|
@ -44,10 +46,11 @@ func TestParseVariableValuesUndeclared(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"missing2": {
|
"missing2": {
|
||||||
Name: "missing1",
|
Name: "missing1",
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
ParsingMode: configs.VariableParseLiteral,
|
ConstraintType: cty.String,
|
||||||
Default: cty.StringVal("default for missing2"),
|
ParsingMode: configs.VariableParseLiteral,
|
||||||
|
Default: cty.StringVal("default for missing2"),
|
||||||
DeclRange: hcl.Range{
|
DeclRange: hcl.Range{
|
||||||
Filename: "fake.tf",
|
Filename: "fake.tf",
|
||||||
Start: hcl.Pos{Line: 4, Column: 1, Byte: 0},
|
Start: hcl.Pos{Line: 4, Column: 1, Byte: 0},
|
||||||
|
|
|
@ -121,7 +121,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
||||||
// implied type more complete, but if there are any
|
// implied type more complete, but if there are any
|
||||||
// dynamically-typed attributes inside we must use a tuple
|
// dynamically-typed attributes inside we must use a tuple
|
||||||
// instead, at the expense of our type then not being predictable.
|
// instead, at the expense of our type then not being predictable.
|
||||||
if blockS.Block.ImpliedType().HasDynamicTypes() {
|
if blockS.Block.specType().HasDynamicTypes() {
|
||||||
ret[name] = &hcldec.BlockTupleSpec{
|
ret[name] = &hcldec.BlockTupleSpec{
|
||||||
TypeName: name,
|
TypeName: name,
|
||||||
Nested: childSpec,
|
Nested: childSpec,
|
||||||
|
@ -155,7 +155,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
||||||
// implied type more complete, but if there are any
|
// implied type more complete, but if there are any
|
||||||
// dynamically-typed attributes inside we must use a tuple
|
// dynamically-typed attributes inside we must use a tuple
|
||||||
// instead, at the expense of our type then not being predictable.
|
// instead, at the expense of our type then not being predictable.
|
||||||
if blockS.Block.ImpliedType().HasDynamicTypes() {
|
if blockS.Block.specType().HasDynamicTypes() {
|
||||||
ret[name] = &hcldec.BlockObjectSpec{
|
ret[name] = &hcldec.BlockObjectSpec{
|
||||||
TypeName: name,
|
TypeName: name,
|
||||||
Nested: childSpec,
|
Nested: childSpec,
|
||||||
|
@ -195,7 +195,7 @@ func (a *Attribute) decoderSpec(name string) hcldec.Spec {
|
||||||
panic("Invalid attribute schema: NestedType and Type cannot both be set. This is a bug in the provider.")
|
panic("Invalid attribute schema: NestedType and Type cannot both be set. This is a bug in the provider.")
|
||||||
}
|
}
|
||||||
|
|
||||||
ty := a.NestedType.ImpliedType()
|
ty := a.NestedType.specType()
|
||||||
ret.Type = ty
|
ret.Type = ty
|
||||||
ret.Required = a.Required || a.NestedType.MinItems > 0
|
ret.Required = a.Required || a.NestedType.MinItems > 0
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -8,11 +8,23 @@ import (
|
||||||
// ImpliedType returns the cty.Type that would result from decoding a
|
// ImpliedType returns the cty.Type that would result from decoding a
|
||||||
// configuration block using the receiving block schema.
|
// configuration block using the receiving block schema.
|
||||||
//
|
//
|
||||||
|
// The type returned from Block.ImpliedType differs from the type returned by
|
||||||
|
// hcldec.ImpliedType in that there will be no objects with optional
|
||||||
|
// attributes, since this value is not to be used for the decoding of
|
||||||
|
// configuration.
|
||||||
|
//
|
||||||
// ImpliedType always returns a result, even if the given schema is
|
// ImpliedType always returns a result, even if the given schema is
|
||||||
// inconsistent. Code that creates configschema.Block objects should be
|
// inconsistent. Code that creates configschema.Block objects should be
|
||||||
// tested using the InternalValidate method to detect any inconsistencies
|
// tested using the InternalValidate method to detect any inconsistencies
|
||||||
// that would cause this method to fall back on defaults and assumptions.
|
// that would cause this method to fall back on defaults and assumptions.
|
||||||
func (b *Block) ImpliedType() cty.Type {
|
func (b *Block) ImpliedType() cty.Type {
|
||||||
|
return b.specType().WithoutOptionalAttributesDeep()
|
||||||
|
}
|
||||||
|
|
||||||
|
// specType returns the cty.Type used for decoding a configuration
|
||||||
|
// block using the receiving block schema. This is the type used internally by
|
||||||
|
// hcldec to decode configuration.
|
||||||
|
func (b *Block) specType() cty.Type {
|
||||||
if b == nil {
|
if b == nil {
|
||||||
return cty.EmptyObject
|
return cty.EmptyObject
|
||||||
}
|
}
|
||||||
|
@ -41,14 +53,20 @@ func (b *Block) ContainsSensitive() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImpliedType returns the cty.Type that would result from decoding a NestedType
|
// ImpliedType returns the cty.Type that would result from decoding a
|
||||||
// Attribute using the receiving block schema.
|
// NestedType Attribute using the receiving block schema.
|
||||||
//
|
//
|
||||||
// ImpliedType always returns a result, even if the given schema is
|
// ImpliedType always returns a result, even if the given schema is
|
||||||
// inconsistent. Code that creates configschema.Object objects should be tested
|
// inconsistent. Code that creates configschema.Object objects should be tested
|
||||||
// using the InternalValidate method to detect any inconsistencies that would
|
// using the InternalValidate method to detect any inconsistencies that would
|
||||||
// cause this method to fall back on defaults and assumptions.
|
// cause this method to fall back on defaults and assumptions.
|
||||||
func (o *Object) ImpliedType() cty.Type {
|
func (o *Object) ImpliedType() cty.Type {
|
||||||
|
return o.specType().WithoutOptionalAttributesDeep()
|
||||||
|
}
|
||||||
|
|
||||||
|
// specType returns the cty.Type used for decoding a NestedType Attribute using
|
||||||
|
// the receiving block schema.
|
||||||
|
func (o *Object) specType() cty.Type {
|
||||||
if o == nil {
|
if o == nil {
|
||||||
return cty.EmptyObject
|
return cty.EmptyObject
|
||||||
}
|
}
|
||||||
|
@ -56,7 +74,7 @@ func (o *Object) ImpliedType() cty.Type {
|
||||||
attrTys := make(map[string]cty.Type, len(o.Attributes))
|
attrTys := make(map[string]cty.Type, len(o.Attributes))
|
||||||
for name, attrS := range o.Attributes {
|
for name, attrS := range o.Attributes {
|
||||||
if attrS.NestedType != nil {
|
if attrS.NestedType != nil {
|
||||||
attrTys[name] = attrS.NestedType.ImpliedType()
|
attrTys[name] = attrS.NestedType.specType()
|
||||||
} else {
|
} else {
|
||||||
attrTys[name] = attrS.Type
|
attrTys[name] = attrS.Type
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,36 @@ func TestBlockImpliedType(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
"nested objects with optional attrs": {
|
||||||
|
&Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"map": {
|
||||||
|
Optional: true,
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingMap,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"optional": {Type: cty.String, Optional: true},
|
||||||
|
"required": {Type: cty.Number, Required: true},
|
||||||
|
"computed": {Type: cty.List(cty.Bool), Computed: true},
|
||||||
|
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// The ImpliedType from the type-level block should not contain any
|
||||||
|
// optional attributes.
|
||||||
|
cty.Object(map[string]cty.Type{
|
||||||
|
"map": cty.Map(cty.Object(
|
||||||
|
map[string]cty.Type{
|
||||||
|
"optional": cty.String,
|
||||||
|
"required": cty.Number,
|
||||||
|
"computed": cty.List(cty.Bool),
|
||||||
|
"optional_computed": cty.Map(cty.Bool),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
|
@ -137,6 +167,211 @@ func TestObjectImpliedType(t *testing.T) {
|
||||||
&Object{},
|
&Object{},
|
||||||
cty.EmptyObject,
|
cty.EmptyObject,
|
||||||
},
|
},
|
||||||
|
"attributes": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"optional": {Type: cty.String, Optional: true},
|
||||||
|
"required": {Type: cty.Number, Required: true},
|
||||||
|
"computed": {Type: cty.List(cty.Bool), Computed: true},
|
||||||
|
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.Object(
|
||||||
|
map[string]cty.Type{
|
||||||
|
"optional": cty.String,
|
||||||
|
"required": cty.Number,
|
||||||
|
"computed": cty.List(cty.Bool),
|
||||||
|
"optional_computed": cty.Map(cty.Bool),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
"nested attributes": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nested_type": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"optional": {Type: cty.String, Optional: true},
|
||||||
|
"required": {Type: cty.Number, Required: true},
|
||||||
|
"computed": {Type: cty.List(cty.Bool), Computed: true},
|
||||||
|
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.Object(map[string]cty.Type{
|
||||||
|
"nested_type": cty.Object(map[string]cty.Type{
|
||||||
|
"optional": cty.String,
|
||||||
|
"required": cty.Number,
|
||||||
|
"computed": cty.List(cty.Bool),
|
||||||
|
"optional_computed": cty.Map(cty.Bool),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"nested object-type attributes": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nested_type": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"optional": {Type: cty.String, Optional: true},
|
||||||
|
"required": {Type: cty.Number, Required: true},
|
||||||
|
"computed": {Type: cty.List(cty.Bool), Computed: true},
|
||||||
|
"optional_computed": {Type: cty.Map(cty.Bool), Optional: true, Computed: true},
|
||||||
|
"object": {
|
||||||
|
Type: cty.ObjectWithOptionalAttrs(map[string]cty.Type{
|
||||||
|
"optional": cty.String,
|
||||||
|
"required": cty.Number,
|
||||||
|
}, []string{"optional"}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.Object(map[string]cty.Type{
|
||||||
|
"nested_type": cty.Object(map[string]cty.Type{
|
||||||
|
"optional": cty.String,
|
||||||
|
"required": cty.Number,
|
||||||
|
"computed": cty.List(cty.Bool),
|
||||||
|
"optional_computed": cty.Map(cty.Bool),
|
||||||
|
"object": cty.Object(map[string]cty.Type{"optional": cty.String, "required": cty.Number}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"NestingList": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingList,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"foo": {Type: cty.String, Optional: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.List(cty.Object(map[string]cty.Type{"foo": cty.String})),
|
||||||
|
},
|
||||||
|
"NestingMap": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingMap,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"foo": {Type: cty.String},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.Map(cty.Object(map[string]cty.Type{"foo": cty.String})),
|
||||||
|
},
|
||||||
|
"NestingSet": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingSet,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"foo": {Type: cty.String},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.Set(cty.Object(map[string]cty.Type{"foo": cty.String})),
|
||||||
|
},
|
||||||
|
"deeply nested NestingList": {
|
||||||
|
&Object{
|
||||||
|
Nesting: NestingList,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"foo": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingList,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"bar": {Type: cty.String},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.List(cty.Object(map[string]cty.Type{"foo": cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))})),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := test.Schema.ImpliedType()
|
||||||
|
if !got.Equals(test.Want) {
|
||||||
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectContainsSensitive(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
Schema *Object
|
||||||
|
Want bool
|
||||||
|
}{
|
||||||
|
"object contains sensitive": {
|
||||||
|
&Object{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"sensitive": {Sensitive: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"no sensitive attrs": {
|
||||||
|
&Object{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"insensitive": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
"nested object contains sensitive": {
|
||||||
|
&Object{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nested": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"sensitive": {Sensitive: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
"nested obj, no sensitive attrs": {
|
||||||
|
&Object{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"nested": {
|
||||||
|
NestedType: &Object{
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"public": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, test := range tests {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
got := test.Schema.ContainsSensitive()
|
||||||
|
if got != test.Want {
|
||||||
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nested attribute should return optional object attributes for decoding.
|
||||||
|
func TestObjectSpecType(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
Schema *Object
|
||||||
|
Want cty.Type
|
||||||
|
}{
|
||||||
"attributes": {
|
"attributes": {
|
||||||
&Object{
|
&Object{
|
||||||
Nesting: NestingSingle,
|
Nesting: NestingSingle,
|
||||||
|
@ -265,74 +500,10 @@ func TestObjectImpliedType(t *testing.T) {
|
||||||
|
|
||||||
for name, test := range tests {
|
for name, test := range tests {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
got := test.Schema.ImpliedType()
|
got := test.Schema.specType()
|
||||||
if !got.Equals(test.Want) {
|
if !got.Equals(test.Want) {
|
||||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestObjectContainsSensitive(t *testing.T) {
|
|
||||||
tests := map[string]struct {
|
|
||||||
Schema *Object
|
|
||||||
Want bool
|
|
||||||
}{
|
|
||||||
"object contains sensitive": {
|
|
||||||
&Object{
|
|
||||||
Attributes: map[string]*Attribute{
|
|
||||||
"sensitive": {Sensitive: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
"no sensitive attrs": {
|
|
||||||
&Object{
|
|
||||||
Attributes: map[string]*Attribute{
|
|
||||||
"insensitive": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
"nested object contains sensitive": {
|
|
||||||
&Object{
|
|
||||||
Attributes: map[string]*Attribute{
|
|
||||||
"nested": {
|
|
||||||
NestedType: &Object{
|
|
||||||
Nesting: NestingSingle,
|
|
||||||
Attributes: map[string]*Attribute{
|
|
||||||
"sensitive": {Sensitive: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
},
|
|
||||||
"nested obj, no sensitive attrs": {
|
|
||||||
&Object{
|
|
||||||
Attributes: map[string]*Attribute{
|
|
||||||
"nested": {
|
|
||||||
NestedType: &Object{
|
|
||||||
Nesting: NestingSingle,
|
|
||||||
Attributes: map[string]*Attribute{
|
|
||||||
"public": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, test := range tests {
|
|
||||||
t.Run(name, func(t *testing.T) {
|
|
||||||
got := test.Schema.ContainsSensitive()
|
|
||||||
if got != test.Want {
|
|
||||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -198,7 +198,7 @@ func checkModuleExperiments(m *Module) hcl.Diagnostics {
|
||||||
|
|
||||||
if !m.ActiveExperiments.Has(experiments.ModuleVariableOptionalAttrs) {
|
if !m.ActiveExperiments.Has(experiments.ModuleVariableOptionalAttrs) {
|
||||||
for _, v := range m.Variables {
|
for _, v := range m.Variables {
|
||||||
if typeConstraintHasOptionalAttrs(v.Type) {
|
if typeConstraintHasOptionalAttrs(v.ConstraintType) {
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
Summary: "Optional object type attributes are experimental",
|
Summary: "Optional object type attributes are experimental",
|
||||||
|
|
|
@ -51,6 +51,7 @@ func (v *Variable) merge(ov *Variable) hcl.Diagnostics {
|
||||||
}
|
}
|
||||||
if ov.Type != cty.NilType {
|
if ov.Type != cty.NilType {
|
||||||
v.Type = ov.Type
|
v.Type = ov.Type
|
||||||
|
v.ConstraintType = ov.ConstraintType
|
||||||
}
|
}
|
||||||
if ov.ParsingMode != 0 {
|
if ov.ParsingMode != 0 {
|
||||||
v.ParsingMode = ov.ParsingMode
|
v.ParsingMode = ov.ParsingMode
|
||||||
|
@ -67,7 +68,7 @@ func (v *Variable) merge(ov *Variable) hcl.Diagnostics {
|
||||||
// constraint but the converted value cannot. In practice, this situation
|
// constraint but the converted value cannot. In practice, this situation
|
||||||
// should be rare since most of our conversions are interchangable.
|
// should be rare since most of our conversions are interchangable.
|
||||||
if v.Default != cty.NilVal {
|
if v.Default != cty.NilVal {
|
||||||
val, err := convert.Convert(v.Default, v.Type)
|
val, err := convert.Convert(v.Default, v.ConstraintType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// What exactly we'll say in the error message here depends on whether
|
// What exactly we'll say in the error message here depends on whether
|
||||||
// it was Default or Type that was overridden here.
|
// it was Default or Type that was overridden here.
|
||||||
|
|
|
@ -25,6 +25,7 @@ func TestModuleOverrideVariable(t *testing.T) {
|
||||||
DescriptionSet: true,
|
DescriptionSet: true,
|
||||||
Default: cty.StringVal("b_override"),
|
Default: cty.StringVal("b_override"),
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
ParsingMode: VariableParseLiteral,
|
ParsingMode: VariableParseLiteral,
|
||||||
DeclRange: hcl.Range{
|
DeclRange: hcl.Range{
|
||||||
Filename: "testdata/valid-modules/override-variable/primary.tf",
|
Filename: "testdata/valid-modules/override-variable/primary.tf",
|
||||||
|
@ -46,6 +47,7 @@ func TestModuleOverrideVariable(t *testing.T) {
|
||||||
DescriptionSet: true,
|
DescriptionSet: true,
|
||||||
Default: cty.StringVal("b_override partial"),
|
Default: cty.StringVal("b_override partial"),
|
||||||
Type: cty.String,
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
ParsingMode: VariableParseLiteral,
|
ParsingMode: VariableParseLiteral,
|
||||||
DeclRange: hcl.Range{
|
DeclRange: hcl.Range{
|
||||||
Filename: "testdata/valid-modules/override-variable/primary.tf",
|
Filename: "testdata/valid-modules/override-variable/primary.tf",
|
||||||
|
|
|
@ -22,7 +22,13 @@ type Variable struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Default cty.Value
|
Default cty.Value
|
||||||
Type cty.Type
|
|
||||||
|
// Type is the concrete type of the variable value.
|
||||||
|
Type cty.Type
|
||||||
|
// ConstraintType is used for decoding and type conversions, and may
|
||||||
|
// contain nested ObjectWithOptionalAttr types.
|
||||||
|
ConstraintType cty.Type
|
||||||
|
|
||||||
ParsingMode VariableParsingMode
|
ParsingMode VariableParsingMode
|
||||||
Validations []*VariableValidation
|
Validations []*VariableValidation
|
||||||
Sensitive bool
|
Sensitive bool
|
||||||
|
@ -45,6 +51,7 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno
|
||||||
// or not they are set when we merge.
|
// or not they are set when we merge.
|
||||||
if !override {
|
if !override {
|
||||||
v.Type = cty.DynamicPseudoType
|
v.Type = cty.DynamicPseudoType
|
||||||
|
v.ConstraintType = cty.DynamicPseudoType
|
||||||
v.ParsingMode = VariableParseLiteral
|
v.ParsingMode = VariableParseLiteral
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +99,8 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno
|
||||||
if attr, exists := content.Attributes["type"]; exists {
|
if attr, exists := content.Attributes["type"]; exists {
|
||||||
ty, parseMode, tyDiags := decodeVariableType(attr.Expr)
|
ty, parseMode, tyDiags := decodeVariableType(attr.Expr)
|
||||||
diags = append(diags, tyDiags...)
|
diags = append(diags, tyDiags...)
|
||||||
v.Type = ty
|
v.ConstraintType = ty
|
||||||
|
v.Type = ty.WithoutOptionalAttributesDeep()
|
||||||
v.ParsingMode = parseMode
|
v.ParsingMode = parseMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,9 +120,9 @@ func decodeVariableBlock(block *hcl.Block, override bool) (*Variable, hcl.Diagno
|
||||||
// attribute above.
|
// attribute above.
|
||||||
// However, we can't do this if we're in an override file where
|
// However, we can't do this if we're in an override file where
|
||||||
// the type might not be set; we'll catch that during merge.
|
// the type might not be set; we'll catch that during merge.
|
||||||
if v.Type != cty.NilType {
|
if v.ConstraintType != cty.NilType {
|
||||||
var err error
|
var err error
|
||||||
val, err = convert.Convert(val, v.Type)
|
val, err = convert.Convert(val, v.ConstraintType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = append(diags, &hcl.Diagnostic{
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
|
|
|
@ -1452,12 +1452,12 @@ func TestProposedNew(t *testing.T) {
|
||||||
"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))),
|
"map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))),
|
||||||
"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))),
|
"set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))),
|
||||||
"nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
|
"nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{
|
||||||
"inner": cty.ObjectWithOptionalAttrs(map[string]cty.Type{
|
"inner": cty.Object(map[string]cty.Type{
|
||||||
"optional": cty.String,
|
"optional": cty.String,
|
||||||
"computed": cty.String,
|
"computed": cty.String,
|
||||||
"optional_computed": cty.String,
|
"optional_computed": cty.String,
|
||||||
"required": cty.String,
|
"required": cty.String,
|
||||||
}, []string{"computed", "optional", "optional_computed"}),
|
}),
|
||||||
}))),
|
}))),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
|
|
@ -237,12 +237,6 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd
|
||||||
})
|
})
|
||||||
return cty.DynamicVal, diags
|
return cty.DynamicVal, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
wantType := cty.DynamicPseudoType
|
|
||||||
if config.Type != cty.NilType {
|
|
||||||
wantType = config.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
d.Evaluator.VariableValuesLock.Lock()
|
d.Evaluator.VariableValuesLock.Lock()
|
||||||
defer d.Evaluator.VariableValuesLock.Unlock()
|
defer d.Evaluator.VariableValuesLock.Unlock()
|
||||||
|
|
||||||
|
@ -262,15 +256,15 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd
|
||||||
if d.Operation == walkValidate {
|
if d.Operation == walkValidate {
|
||||||
// Ensure variable sensitivity is captured in the validate walk
|
// Ensure variable sensitivity is captured in the validate walk
|
||||||
if config.Sensitive {
|
if config.Sensitive {
|
||||||
return cty.UnknownVal(wantType).Mark(marks.Sensitive), diags
|
return cty.UnknownVal(config.Type).Mark(marks.Sensitive), diags
|
||||||
}
|
}
|
||||||
return cty.UnknownVal(wantType), diags
|
return cty.UnknownVal(config.Type), diags
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleAddrStr := d.ModulePath.String()
|
moduleAddrStr := d.ModulePath.String()
|
||||||
vals := d.Evaluator.VariableValues[moduleAddrStr]
|
vals := d.Evaluator.VariableValues[moduleAddrStr]
|
||||||
if vals == nil {
|
if vals == nil {
|
||||||
return cty.UnknownVal(wantType), diags
|
return cty.UnknownVal(config.Type), diags
|
||||||
}
|
}
|
||||||
|
|
||||||
val, isSet := vals[addr.Name]
|
val, isSet := vals[addr.Name]
|
||||||
|
@ -278,11 +272,11 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd
|
||||||
if config.Default != cty.NilVal {
|
if config.Default != cty.NilVal {
|
||||||
return config.Default, diags
|
return config.Default, diags
|
||||||
}
|
}
|
||||||
return cty.UnknownVal(wantType), diags
|
return cty.UnknownVal(config.Type), diags
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
val, err = convert.Convert(val, wantType)
|
val, err = convert.Convert(val, config.ConstraintType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// We should never get here because this problem should've been caught
|
// We should never get here because this problem should've been caught
|
||||||
// during earlier validation, but we'll do something reasonable anyway.
|
// during earlier validation, but we'll do something reasonable anyway.
|
||||||
|
@ -294,7 +288,7 @@ func (d *evaluationStateData) GetInputVariable(addr addrs.InputVariable, rng tfd
|
||||||
})
|
})
|
||||||
// Stub out our return value so that the semantic checker doesn't
|
// Stub out our return value so that the semantic checker doesn't
|
||||||
// produce redundant downstream errors.
|
// produce redundant downstream errors.
|
||||||
val = cty.UnknownVal(wantType)
|
val = cty.UnknownVal(config.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark if sensitive
|
// Mark if sensitive
|
||||||
|
|
|
@ -95,15 +95,19 @@ func TestEvaluatorGetInputVariable(t *testing.T) {
|
||||||
Module: &configs.Module{
|
Module: &configs.Module{
|
||||||
Variables: map[string]*configs.Variable{
|
Variables: map[string]*configs.Variable{
|
||||||
"some_var": {
|
"some_var": {
|
||||||
Name: "some_var",
|
Name: "some_var",
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
Default: cty.StringVal("foo"),
|
Default: cty.StringVal("foo"),
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
// Avoid double marking a value
|
// Avoid double marking a value
|
||||||
"some_other_var": {
|
"some_other_var": {
|
||||||
Name: "some_other_var",
|
Name: "some_other_var",
|
||||||
Sensitive: true,
|
Sensitive: true,
|
||||||
Default: cty.StringVal("bar"),
|
Default: cty.StringVal("bar"),
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -200,7 +200,6 @@ func (n *nodeModuleVariable) DotNode(name string, opts *dag.DotOpts) *dag.DotNod
|
||||||
// validation, and we will not have any expansion module instance
|
// validation, and we will not have any expansion module instance
|
||||||
// repetition data.
|
// repetition data.
|
||||||
func (n *nodeModuleVariable) evalModuleCallArgument(ctx EvalContext, validateOnly bool) (map[string]cty.Value, error) {
|
func (n *nodeModuleVariable) evalModuleCallArgument(ctx EvalContext, validateOnly bool) (map[string]cty.Value, error) {
|
||||||
wantType := n.Config.Type
|
|
||||||
name := n.Addr.Variable.Name
|
name := n.Addr.Variable.Name
|
||||||
expr := n.Expr
|
expr := n.Expr
|
||||||
|
|
||||||
|
@ -238,7 +237,7 @@ func (n *nodeModuleVariable) evalModuleCallArgument(ctx EvalContext, validateOnl
|
||||||
// now we can do our own local type conversion and produce an error message
|
// now we can do our own local type conversion and produce an error message
|
||||||
// with better context if it fails.
|
// with better context if it fails.
|
||||||
var convErr error
|
var convErr error
|
||||||
val, convErr = convert.Convert(val, wantType)
|
val, convErr = convert.Convert(val, n.Config.ConstraintType)
|
||||||
if convErr != nil {
|
if convErr != nil {
|
||||||
diags = diags.Append(&hcl.Diagnostic{
|
diags = diags.Append(&hcl.Diagnostic{
|
||||||
Severity: hcl.DiagError,
|
Severity: hcl.DiagError,
|
||||||
|
@ -251,7 +250,7 @@ func (n *nodeModuleVariable) evalModuleCallArgument(ctx EvalContext, validateOnl
|
||||||
})
|
})
|
||||||
// We'll return a placeholder unknown value to avoid producing
|
// We'll return a placeholder unknown value to avoid producing
|
||||||
// redundant downstream errors.
|
// redundant downstream errors.
|
||||||
val = cty.UnknownVal(wantType)
|
val = cty.UnknownVal(n.Config.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
vals := make(map[string]cty.Value)
|
vals := make(map[string]cty.Value)
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
|
@ -16,7 +17,9 @@ func TestNodeModuleVariablePath(t *testing.T) {
|
||||||
n := &nodeModuleVariable{
|
n := &nodeModuleVariable{
|
||||||
Addr: addrs.RootModuleInstance.InputVariable("foo"),
|
Addr: addrs.RootModuleInstance.InputVariable("foo"),
|
||||||
Config: &configs.Variable{
|
Config: &configs.Variable{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,7 +34,9 @@ func TestNodeModuleVariableReferenceableName(t *testing.T) {
|
||||||
n := &nodeExpandModuleVariable{
|
n := &nodeExpandModuleVariable{
|
||||||
Addr: addrs.InputVariable{Name: "foo"},
|
Addr: addrs.InputVariable{Name: "foo"},
|
||||||
Config: &configs.Variable{
|
Config: &configs.Variable{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +69,9 @@ func TestNodeModuleVariableReference(t *testing.T) {
|
||||||
Addr: addrs.InputVariable{Name: "foo"},
|
Addr: addrs.InputVariable{Name: "foo"},
|
||||||
Module: addrs.RootModule.Child("bar"),
|
Module: addrs.RootModule.Child("bar"),
|
||||||
Config: &configs.Variable{
|
Config: &configs.Variable{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
Expr: &hclsyntax.ScopeTraversalExpr{
|
Expr: &hclsyntax.ScopeTraversalExpr{
|
||||||
Traversal: hcl.Traversal{
|
Traversal: hcl.Traversal{
|
||||||
|
@ -90,7 +97,9 @@ func TestNodeModuleVariableReference_grandchild(t *testing.T) {
|
||||||
Addr: addrs.InputVariable{Name: "foo"},
|
Addr: addrs.InputVariable{Name: "foo"},
|
||||||
Module: addrs.RootModule.Child("bar"),
|
Module: addrs.RootModule.Child("bar"),
|
||||||
Config: &configs.Variable{
|
Config: &configs.Variable{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
Expr: &hclsyntax.ScopeTraversalExpr{
|
Expr: &hclsyntax.ScopeTraversalExpr{
|
||||||
Traversal: hcl.Traversal{
|
Traversal: hcl.Traversal{
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/internal/addrs"
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/configs"
|
"github.com/hashicorp/terraform/internal/configs"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNodeRootVariableExecute(t *testing.T) {
|
func TestNodeRootVariableExecute(t *testing.T) {
|
||||||
|
@ -13,7 +14,9 @@ func TestNodeRootVariableExecute(t *testing.T) {
|
||||||
n := &NodeRootVariable{
|
n := &NodeRootVariable{
|
||||||
Addr: addrs.InputVariable{Name: "foo"},
|
Addr: addrs.InputVariable{Name: "foo"},
|
||||||
Config: &configs.Variable{
|
Config: &configs.Variable{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
|
Type: cty.String,
|
||||||
|
ConstraintType: cty.String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -262,10 +262,8 @@ func checkInputVariables(vcs map[string]*configs.Variable, vs InputValues) tfdia
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
wantType := vc.Type
|
|
||||||
|
|
||||||
// A given value is valid if it can convert to the desired type.
|
// A given value is valid if it can convert to the desired type.
|
||||||
_, err := convert.Convert(val.Value, wantType)
|
_, err := convert.Convert(val.Value, vc.ConstraintType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch val.SourceType {
|
switch val.SourceType {
|
||||||
case ValueFromConfig, ValueFromAutoFile, ValueFromNamedFile:
|
case ValueFromConfig, ValueFromAutoFile, ValueFromNamedFile:
|
||||||
|
|
Loading…
Reference in New Issue