configschema: do not expose optional attributes
Objects with optional attributes are only used for the decoding of HCL, and those types should never be exposed elsewhere within terraform. Separate the external ImpliedType method from the cty.Type generated internally for the decoder spec. This unfortunately causes our ImpliedType method to return a different type than the hcldec.ImpliedType function, but the former is only used within terraform for concrete values, while the latter is used to decode HCL. Renaming the ImpliedType methods could be done to further differentiate them, but that does cause fairly large diff in the codebase that does not seem worth the effort at this time.
This commit is contained in:
parent
cb5b159228
commit
af4f4540a9
|
@ -121,7 +121,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
|||
// implied type more complete, but if there are any
|
||||
// dynamically-typed attributes inside we must use a tuple
|
||||
// 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{
|
||||
TypeName: name,
|
||||
Nested: childSpec,
|
||||
|
@ -155,7 +155,7 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
|||
// implied type more complete, but if there are any
|
||||
// dynamically-typed attributes inside we must use a tuple
|
||||
// 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{
|
||||
TypeName: name,
|
||||
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.")
|
||||
}
|
||||
|
||||
ty := a.NestedType.ImpliedType()
|
||||
ty := a.NestedType.specType()
|
||||
ret.Type = ty
|
||||
ret.Required = a.Required || a.NestedType.MinItems > 0
|
||||
return ret
|
||||
|
|
|
@ -8,11 +8,23 @@ import (
|
|||
// ImpliedType returns the cty.Type that would result from decoding a
|
||||
// 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
|
||||
// inconsistent. Code that creates configschema.Block objects should be
|
||||
// tested using the InternalValidate method to detect any inconsistencies
|
||||
// that would cause this method to fall back on defaults and assumptions.
|
||||
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 {
|
||||
return cty.EmptyObject
|
||||
}
|
||||
|
@ -41,14 +53,20 @@ func (b *Block) ContainsSensitive() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// ImpliedType returns the cty.Type that would result from decoding a NestedType
|
||||
// Attribute using the receiving block schema.
|
||||
// ImpliedType returns the cty.Type that would result from decoding a
|
||||
// NestedType Attribute using the receiving block schema.
|
||||
//
|
||||
// ImpliedType always returns a result, even if the given schema is
|
||||
// inconsistent. Code that creates configschema.Object objects should be tested
|
||||
// using the InternalValidate method to detect any inconsistencies that would
|
||||
// cause this method to fall back on defaults and assumptions.
|
||||
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 {
|
||||
return cty.EmptyObject
|
||||
}
|
||||
|
@ -56,7 +74,7 @@ func (o *Object) ImpliedType() cty.Type {
|
|||
attrTys := make(map[string]cty.Type, len(o.Attributes))
|
||||
for name, attrS := range o.Attributes {
|
||||
if attrS.NestedType != nil {
|
||||
attrTys[name] = attrS.NestedType.ImpliedType()
|
||||
attrTys[name] = attrS.NestedType.specType()
|
||||
} else {
|
||||
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 {
|
||||
|
@ -137,6 +167,211 @@ func TestObjectImpliedType(t *testing.T) {
|
|||
&Object{},
|
||||
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": {
|
||||
&Object{
|
||||
Nesting: NestingSingle,
|
||||
|
@ -265,74 +500,10 @@ func TestObjectImpliedType(t *testing.T) {
|
|||
|
||||
for name, test := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := test.Schema.ImpliedType()
|
||||
got := test.Schema.specType()
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue