Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Account for NestedType in static traversal validation #31532

Merged
merged 2 commits into from Aug 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
5 changes: 1 addition & 4 deletions internal/configs/configschema/empty_value.go
Expand Up @@ -26,10 +26,7 @@ func (b *Block) EmptyValue() cty.Value {
// the value that would be returned if there were no definition of the attribute
// at all, ignoring any required constraint.
func (a *Attribute) EmptyValue() cty.Value {
if a.NestedType != nil {
return cty.NullVal(a.NestedType.ImpliedType())
}
return cty.NullVal(a.Type)
return cty.NullVal(a.ImpliedType())
}

// EmptyValue returns the "empty value" for when there are zero nested blocks
Expand Down
14 changes: 14 additions & 0 deletions internal/configs/configschema/implied_type.go
Expand Up @@ -56,6 +56,20 @@ func (b *Block) ContainsSensitive() bool {
return false
}

// ImpliedType returns the cty.Type that would result from decoding a Block's
// ImpliedType and getting the resulting AttributeType.
//
// 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 (a *Attribute) ImpliedType() cty.Type {
if a.NestedType != nil {
return a.NestedType.specType().WithoutOptionalAttributesDeep()
}
return a.Type
}

// ImpliedType returns the cty.Type that would result from decoding a
// NestedType Attribute using the receiving block schema.
//
Expand Down
2 changes: 1 addition & 1 deletion internal/configs/configschema/internal_validate.go
Expand Up @@ -135,7 +135,7 @@ func (a *Attribute) internalValidate(name, prefix string) error {
// no validations to perform
case NestingList, NestingSet:
if a.NestedType.Nesting == NestingSet {
ety := a.NestedType.ImpliedType()
ety := a.ImpliedType()
if ety.HasDynamicTypes() {
// This is not permitted because the HCL (cty) set implementation
// needs to know the exact type of set elements in order to
Expand Down
8 changes: 4 additions & 4 deletions internal/configs/configschema/validate_traversal.go
Expand Up @@ -75,10 +75,10 @@ func (b *Block) StaticValidateTraversal(traversal hcl.Traversal) tfdiags.Diagnos

if attrS, exists := b.Attributes[name]; exists {
// For attribute validation we will just apply the rest of the
// traversal to an unknown value of the attribute type and pass
// through HCL's own errors, since we don't want to replicate all of
// HCL's type checking rules here.
val := cty.UnknownVal(attrS.Type)
// traversal to an unknown value of the attribute type and pass through
// HCL's own errors, since we don't want to replicate all of HCL's type
// checking rules here.
val := cty.UnknownVal(attrS.ImpliedType())
_, hclDiags := after.TraverseRel(val)
diags = diags.Append(hclDiags)
return diags
Expand Down
52 changes: 52 additions & 0 deletions internal/configs/configschema/validate_traversal_test.go
Expand Up @@ -13,6 +13,42 @@ func TestStaticValidateTraversal(t *testing.T) {
"str": {Type: cty.String, Optional: true},
"list": {Type: cty.List(cty.String), Optional: true},
"dyn": {Type: cty.DynamicPseudoType, Optional: true},
"nested_single": {
Optional: true,
NestedType: &Object{
Nesting: NestingSingle,
Attributes: map[string]*Attribute{
"optional": {Type: cty.String, Optional: true},
},
},
},
"nested_list": {
Optional: true,
NestedType: &Object{
Nesting: NestingList,
Attributes: map[string]*Attribute{
"optional": {Type: cty.String, Optional: true},
},
},
},
"nested_set": {
Optional: true,
NestedType: &Object{
Nesting: NestingSet,
Attributes: map[string]*Attribute{
"optional": {Type: cty.String, Optional: true},
},
},
},
"nested_map": {
Optional: true,
NestedType: &Object{
Nesting: NestingMap,
Attributes: map[string]*Attribute{
"optional": {Type: cty.String, Optional: true},
},
},
},
}
schema := &Block{
Attributes: attrs,
Expand Down Expand Up @@ -168,6 +204,22 @@ func TestStaticValidateTraversal(t *testing.T) {
`obj.map_block.anything.nonexist`,
`Unsupported attribute: This object has no argument, nested block, or exported attribute named "nonexist".`,
},
{
`obj.nested_single.optional`,
``,
},
{
`obj.nested_list[0].optional`,
``,
},
{
`obj.nested_set[0].optional`,
`Invalid index: Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of the set.`,
},
{
`obj.nested_map["key"].optional`,
``,
},
}

for _, test := range tests {
Expand Down
4 changes: 2 additions & 2 deletions internal/plans/objchange/objchange.go
Expand Up @@ -499,10 +499,10 @@ func setElementCompareValue(schema *configschema.Block, v cty.Value, isConfig bo
if isConfig {
attrs[name] = v.GetAttr(name)
} else {
attrs[name] = cty.NullVal(attr.Type)
attrs[name] = cty.NullVal(attr.ImpliedType())
}
case attr.Computed:
attrs[name] = cty.NullVal(attr.Type)
attrs[name] = cty.NullVal(attr.ImpliedType())
default:
attrs[name] = v.GetAttr(name)
}
Expand Down