Dereference attributes during filtering to avoid schema cache altering (#2468)

Signed-off-by: yottta <andrei.ciobanu@opentofu.org>
This commit is contained in:
Andrei Ciobanu 2025-02-04 17:05:08 +02:00 committed by GitHub
parent 60fdd359d5
commit 22a42d87ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 110 additions and 10 deletions

View File

@ -29,6 +29,7 @@ BUG FIXES:
- Fix the error message when default value of a complex variable is containing a wrong type ([2394](https://github.com/opentofu/opentofu/issues/2394))
- Fix the way OpenTofu downloads a module that is sourced from a GitHub branch containing slashes in the name. ([2396](https://github.com/opentofu/opentofu/issues/2396))
- `pg` backend doesn't fail on workspace creation for paralel runs, when the database is shared across multiple projects. ([#2411](https://github.com/opentofu/opentofu/pull/2411))
- Generating an OpenTofu configuration from an `import` block that is referencing a resource with nested attributes now works correctly, instead of giving an error that the nested computed attribute is required. ([2372](https://github.com/opentofu/opentofu/issues/2372))
## Previous Releases

View File

@ -50,12 +50,15 @@ func (b *Block) Filter(filterAttribute FilterT[*Attribute], filterBlock FilterT[
ret.Attributes = make(map[string]*Attribute, len(b.Attributes))
}
for name, attrS := range b.Attributes {
if filterAttribute == nil || !filterAttribute(name, attrS) {
ret.Attributes[name] = attrS
// Copy the attributes of the block. Otherwise, if the filterNestedType is filtering out some attributes,
// the underlying schema is getting altered too, rendering the providers.SchemaCache invalid.
attr := *attrS
if filterAttribute == nil || !filterAttribute(name, &attr) {
ret.Attributes[name] = &attr
}
if attrS.NestedType != nil {
ret.Attributes[name].NestedType = filterNestedType(attrS.NestedType, filterAttribute)
if attr.NestedType != nil {
ret.Attributes[name].NestedType = filterNestedType((&attr).NestedType, filterAttribute)
}
}
@ -88,10 +91,13 @@ func filterNestedType(obj *Object, filterAttribute FilterT[*Attribute]) *Object
}
for name, attrS := range obj.Attributes {
if filterAttribute == nil || !filterAttribute(name, attrS) {
ret.Attributes[name] = attrS
if attrS.NestedType != nil {
ret.Attributes[name].NestedType = filterNestedType(attrS.NestedType, filterAttribute)
// Copy the attributes of the block. Otherwise, if the filterNestedType is filtering out some attributes,
// the underlying schema is getting altered too, rendering the providers.SchemaCache invalid.
attr := *attrS
if filterAttribute == nil || !filterAttribute(name, &attr) {
ret.Attributes[name] = &attr
if attr.NestedType != nil {
ret.Attributes[name].NestedType = filterNestedType(attr.NestedType, filterAttribute)
}
}
}

View File

@ -8,10 +8,9 @@ package configschema
import (
"testing"
"github.com/zclconf/go-cty/cty"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/zclconf/go-cty/cty"
)
func TestFilter(t *testing.T) {
@ -270,14 +269,108 @@ func TestFilter(t *testing.T) {
},
},
},
"filter_computed_from_optional_block": {
schema: &Block{
Attributes: map[string]*Attribute{
"id": {
Type: cty.String,
Computed: true,
},
"nested_val": {
Type: cty.String,
Optional: true,
NestedType: &Object{
Attributes: map[string]*Attribute{
"child_computed": {
Type: cty.String,
Computed: true,
},
},
},
},
},
},
filterAttribute: FilterReadOnlyAttribute,
filterBlock: FilterDeprecatedBlock,
want: &Block{
Attributes: map[string]*Attribute{
"nested_val": {
Type: cty.String,
Optional: true,
NestedType: &Object{
Attributes: map[string]*Attribute{},
},
},
},
},
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
schemaBeforeFilter := cloneBlock(tc.schema)
got := tc.schema.Filter(tc.filterAttribute, tc.filterBlock)
if !cmp.Equal(got, tc.want, cmp.Comparer(cty.Type.Equals), cmpopts.EquateEmpty()) {
t.Fatal(cmp.Diff(got, tc.want, cmp.Comparer(cty.Type.Equals), cmpopts.EquateEmpty()))
}
if !cmp.Equal(schemaBeforeFilter, tc.schema, cmp.Comparer(cty.Type.Equals), cmpopts.EquateEmpty()) {
t.Fatal("before and after schema differ. the filtering function alters the actual schema", cmp.Diff(schemaBeforeFilter, tc.schema, cmp.Comparer(cty.Type.Equals), cmpopts.EquateEmpty()))
}
})
}
}
func cloneBlock(in *Block) *Block {
if in == nil {
return nil
}
out := Block{
Attributes: make(map[string]*Attribute, len(in.Attributes)),
BlockTypes: make(map[string]*NestedBlock, len(in.BlockTypes)),
Description: in.Description,
DescriptionKind: in.DescriptionKind,
Deprecated: in.Deprecated,
}
for k, v := range in.Attributes {
out.Attributes[k] = cloneAttribute(v)
}
for k, v := range in.BlockTypes {
out.BlockTypes[k] = cloneNestedBlock(v)
}
return &out
}
func cloneNestedBlock(in *NestedBlock) *NestedBlock {
bl := cloneBlock(&in.Block)
out := &NestedBlock{
Block: *bl,
Nesting: in.Nesting,
MinItems: in.MinItems,
MaxItems: in.MaxItems,
}
return out
}
func cloneAttribute(in *Attribute) *Attribute {
out := &Attribute{
Type: in.Type,
NestedType: nil, // handled later
Description: in.Description,
DescriptionKind: in.DescriptionKind,
Required: in.Required,
Optional: in.Optional,
Computed: in.Computed,
Sensitive: in.Sensitive,
Deprecated: in.Deprecated,
}
if in.NestedType != nil {
out.NestedType = &Object{
Attributes: make(map[string]*Attribute, len(in.NestedType.Attributes)),
Nesting: in.NestedType.Nesting,
}
for k, v := range in.NestedType.Attributes {
out.NestedType.Attributes[k] = cloneAttribute(v)
}
}
return out
}