diff --git a/internal/configs/configschema/filter.go b/internal/configs/configschema/filter.go index 35aeba714c42..e8abd17c03cd 100644 --- a/internal/configs/configschema/filter.go +++ b/internal/configs/configschema/filter.go @@ -3,33 +3,35 @@ package configschema -type FilterT[T any] func(string, T) bool +import "github.com/zclconf/go-cty/cty" + +type FilterT[T any] func(cty.Path, T) bool var ( - FilterReadOnlyAttribute = func(name string, attribute *Attribute) bool { + FilterReadOnlyAttribute = func(path cty.Path, attribute *Attribute) bool { return attribute.Computed && !attribute.Optional } - FilterHelperSchemaIdAttribute = func(name string, attribute *Attribute) bool { - if name == "id" && attribute.Computed && attribute.Optional { + FilterHelperSchemaIdAttribute = func(path cty.Path, attribute *Attribute) bool { + if path.Equals(cty.GetAttrPath("id")) && attribute.Computed && attribute.Optional { return true } return false } - FilterDeprecatedAttribute = func(name string, attribute *Attribute) bool { + FilterDeprecatedAttribute = func(path cty.Path, attribute *Attribute) bool { return attribute.Deprecated } - FilterDeprecatedBlock = func(name string, block *NestedBlock) bool { + FilterDeprecatedBlock = func(path cty.Path, block *NestedBlock) bool { return block.Deprecated } ) func FilterOr[T any](filters ...FilterT[T]) FilterT[T] { - return func(name string, value T) bool { + return func(path cty.Path, value T) bool { for _, f := range filters { - if f(name, value) { + if f(path, value) { return true } } @@ -38,6 +40,10 @@ func FilterOr[T any](filters ...FilterT[T]) FilterT[T] { } func (b *Block) Filter(filterAttribute FilterT[*Attribute], filterBlock FilterT[*NestedBlock]) *Block { + return b.filter(nil, filterAttribute, filterBlock) +} + +func (b *Block) filter(path cty.Path, filterAttribute FilterT[*Attribute], filterBlock FilterT[*NestedBlock]) *Block { ret := &Block{ Description: b.Description, DescriptionKind: b.DescriptionKind, @@ -48,10 +54,11 @@ 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) { + path := path.GetAttr(name) + if filterAttribute == nil || !filterAttribute(path, attrS) { ret.Attributes[name] = attrS if attrS.NestedType != nil { - ret.Attributes[name].NestedType = filterNestedType(attrS.NestedType, filterAttribute) + ret.Attributes[name].NestedType = filterNestedType(attrS.NestedType, path, filterAttribute) } } } @@ -60,8 +67,9 @@ func (b *Block) Filter(filterAttribute FilterT[*Attribute], filterBlock FilterT[ ret.BlockTypes = make(map[string]*NestedBlock, len(b.BlockTypes)) } for name, blockS := range b.BlockTypes { - if filterBlock == nil || !filterBlock(name, blockS) { - block := blockS.Filter(filterAttribute, filterBlock) + path := path.GetAttr(name) + if filterBlock == nil || !filterBlock(path, blockS) { + block := blockS.filter(path, filterAttribute, filterBlock) ret.BlockTypes[name] = &NestedBlock{ Block: *block, Nesting: blockS.Nesting, @@ -74,7 +82,7 @@ func (b *Block) Filter(filterAttribute FilterT[*Attribute], filterBlock FilterT[ return ret } -func filterNestedType(obj *Object, filterAttribute FilterT[*Attribute]) *Object { +func filterNestedType(obj *Object, path cty.Path, filterAttribute FilterT[*Attribute]) *Object { if obj == nil { return nil } @@ -85,10 +93,11 @@ func filterNestedType(obj *Object, filterAttribute FilterT[*Attribute]) *Object } for name, attrS := range obj.Attributes { - if filterAttribute == nil || !filterAttribute(name, attrS) { + path := path.GetAttr(name) + if filterAttribute == nil || !filterAttribute(path, attrS) { ret.Attributes[name] = attrS if attrS.NestedType != nil { - ret.Attributes[name].NestedType = filterNestedType(attrS.NestedType, filterAttribute) + ret.Attributes[name].NestedType = filterNestedType(attrS.NestedType, path, filterAttribute) } } } diff --git a/internal/terraform/context_plan_import_test.go b/internal/terraform/context_plan_import_test.go index 91248baca45c..611e4cf0250a 100644 --- a/internal/terraform/context_plan_import_test.go +++ b/internal/terraform/context_plan_import_test.go @@ -671,6 +671,80 @@ func TestContext2Plan_importIdInvalidUnknown(t *testing.T) { } } +func TestContext2Plan_generateConfigWithNestedId(t *testing.T) { + m := testModuleInline(t, map[string]string{ + "main.tf": ` +import { + to = test_object.a + id = "foo" +} +`, + }) + + p := simpleMockProvider() + + p.GetProviderSchemaResponse.ResourceTypes = map[string]providers.Schema{ + "test_object": { + Block: &configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "test_id": { + Type: cty.String, + Required: true, + }, + "list_val": { + Optional: true, + NestedType: &configschema.Object{ + Nesting: configschema.NestingList, + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + } + + ctx := testContext2(t, &ContextOpts{ + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + p.ReadResourceResponse = &providers.ReadResourceResponse{ + NewState: cty.ObjectVal(map[string]cty.Value{ + "test_id": cty.StringVal("foo"), + "list_val": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("list_id"), + }), + }), + }), + } + p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ + ImportedResources: []providers.ImportedResource{ + { + TypeName: "test_object", + State: cty.ObjectVal(map[string]cty.Value{ + "test_id": cty.StringVal("foo"), + }), + }, + }, + } + + // Actual plan doesn't matter, just want to make sure there are no errors. + _, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ + Mode: plans.NormalMode, + GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty. + }) + if diags.HasErrors() { + t.Fatalf("unexpected errors\n%s", diags.Err().Error()) + } +} + func TestContext2Plan_importIntoModuleWithGeneratedConfig(t *testing.T) { m := testModuleInline(t, map[string]string{ "main.tf": ` @@ -717,17 +791,6 @@ resource "test_object" "a" { }, } - p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ - ImportedResources: []providers.ImportedResource{ - { - TypeName: "test_object", - State: cty.ObjectVal(map[string]cty.Value{ - "test_string": cty.StringVal("foo"), - }), - }, - }, - } - plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ Mode: plans.NormalMode, GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty.