move ExtractLegacyConfigFromState calls out of genconfig

This commit is contained in:
James Bardin 2025-08-27 18:02:14 -04:00
parent 4c610ac5e6
commit 13faebed35
3 changed files with 79 additions and 40 deletions

View file

@ -103,7 +103,7 @@ func (i ImportGroup) ImportsString() string {
func GenerateResourceContents(addr addrs.AbsResourceInstance,
schema *configschema.Block,
pc addrs.LocalProviderConfig,
stateVal cty.Value,
configVal cty.Value,
forceProviderAddr bool,
) (Resource, tfdiags.Diagnostics) {
var buf strings.Builder
@ -117,19 +117,12 @@ func GenerateResourceContents(addr addrs.AbsResourceInstance,
buf.WriteString(fmt.Sprintf("provider = %s\n", pc.StringCompact()))
}
// This is generating configuration, so the only marks should be coming from
// the schema itself.
stateVal, _ = stateVal.UnmarkDeep()
// filter the state down to a suitable config value
stateVal = extractConfigFromState(schema, stateVal)
if stateVal.RawEquals(cty.NilVal) {
if configVal.RawEquals(cty.NilVal) {
diags = diags.Append(writeConfigAttributes(addr, &buf, schema.Attributes, 2))
diags = diags.Append(writeConfigBlocks(addr, &buf, schema.BlockTypes, 2))
} else {
diags = diags.Append(writeConfigAttributesFromExisting(addr, &buf, stateVal, schema.Attributes, 2))
diags = diags.Append(writeConfigBlocksFromExisting(addr, &buf, stateVal, schema.BlockTypes, 2))
diags = diags.Append(writeConfigAttributesFromExisting(addr, &buf, configVal, schema.Attributes, 2))
diags = diags.Append(writeConfigBlocksFromExisting(addr, &buf, configVal, schema.BlockTypes, 2))
}
// The output better be valid HCL which can be parsed and formatted.
@ -137,27 +130,27 @@ func GenerateResourceContents(addr addrs.AbsResourceInstance,
return Resource{Addr: addr, Body: formatted}, diags
}
// ResourceListElement is a single Resource state and identity pair derived from
// a list resource response.
type ResourceListElement struct {
// Config is the cty value extracted from the resource state which is
// intended to be written into the HCL resource block.
Config cty.Value
Identity cty.Value
}
func GenerateListResourceContents(addr addrs.AbsResourceInstance,
schema *configschema.Block,
idSchema *configschema.Object,
pc addrs.LocalProviderConfig,
stateVal cty.Value,
resources []ResourceListElement,
) (ImportGroup, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
ret := ImportGroup{}
if !stateVal.CanIterateElements() {
diags = diags.Append(
hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid resource instance value",
Detail: fmt.Sprintf("Resource instance %s has nil or non-iterable value", addr),
})
return ret, diags
}
iter := stateVal.ElementIterator()
for idx := 0; iter.Next(); idx++ {
for idx, res := range resources {
// Generate a unique resource name for each instance in the list.
resAddr := addrs.AbsResourceInstance{
Module: addr.Module,
@ -171,14 +164,7 @@ func GenerateListResourceContents(addr addrs.AbsResourceInstance,
},
}
_, val := iter.Element()
// we still need to generate the resource block even if the state is not given,
// so that the import block can reference it.
stateVal := cty.NilVal
if val.Type().HasAttribute("state") {
stateVal = val.GetAttr("state")
}
content, gDiags := GenerateResourceContents(resAddr, schema, pc, stateVal, true)
content, gDiags := GenerateResourceContents(resAddr, schema, pc, res.Config, true)
if gDiags.HasErrors() {
diags = diags.Append(gDiags)
continue
@ -191,8 +177,7 @@ func GenerateListResourceContents(addr addrs.AbsResourceInstance,
},
}
idVal := val.GetAttr("identity")
importContent, gDiags := GenerateImportBlock(resAddr, idSchema, pc, idVal)
importContent, gDiags := GenerateImportBlock(resAddr, idSchema, pc, res.Identity)
if gDiags.HasErrors() {
diags = diags.Append(gDiags)
continue
@ -667,11 +652,11 @@ func hclEscapeString(str string) string {
return str
}
// extractConfigFromState takes the state value of a resource, and filters the
// ExtractLegacyConfigFromState takes the state value of a resource, and filters the
// value down to what would be acceptable as a resource configuration value.
// This is used when the provider does not implement GenerateResourceConfig to
// create a suitable value.
func extractConfigFromState(schema *configschema.Block, state cty.Value) cty.Value {
func ExtractLegacyConfigFromState(schema *configschema.Block, state cty.Value) cty.Value {
config, _ := cty.Transform(state, func(path cty.Path, v cty.Value) (cty.Value, error) {
if v.IsNull() {
return v, nil

View file

@ -829,7 +829,11 @@ resource "tfcoremock_sensitive_values" "values" {
if err != nil {
t.Fatalf("schema failed InternalValidate: %s", err)
}
contents, diags := GenerateResourceContents(tc.addr, tc.schema, tc.provider, tc.value, false)
val, _ := tc.value.UnmarkDeep()
config := ExtractLegacyConfigFromState(tc.schema, val)
contents, diags := GenerateResourceContents(tc.addr, tc.schema, tc.provider, config, false)
if len(diags) > 0 {
t.Errorf("expected no diagnostics but found %s", diags)
}
@ -957,8 +961,29 @@ func TestGenerateResourceAndIDContents(t *testing.T) {
LocalName: "aws",
}
// the handling of the list value was moved to the caller, so break it back down in the same way here
var listElements []ResourceListElement
iter := value.ElementIterator()
for iter.Next() {
_, val := iter.Element()
// we still need to generate the resource block even if the state is not given,
// so that the import block can reference it.
stateVal := cty.NilVal
if val.Type().HasAttribute("state") {
stateVal = val.GetAttr("state")
}
stateVal, _ = stateVal.UnmarkDeep()
config := ExtractLegacyConfigFromState(schema, stateVal)
idVal := val.GetAttr("identity")
listElements = append(listElements, ResourceListElement{Config: config, Identity: idVal})
}
// Generate content
content, diags := GenerateListResourceContents(instAddr1, schema, idSchema, pc, value)
content, diags := GenerateListResourceContents(instAddr1, schema, idSchema, pc, listElements)
// Check for diagnostics
if diags.HasErrors() {
t.Fatalf("unexpected diagnostics: %s", diags.Err())

View file

@ -890,7 +890,13 @@ func (n *NodePlannableResourceInstance) generateHCLResourceDef(addr addrs.AbsRes
Alias: n.ResolvedProvider.Alias,
}
return genconfig.GenerateResourceContents(addr, schema.Body, providerAddr, state, false)
// This is generating configuration, so the only marks should be coming from
// the schema itself.
state, _ = state.UnmarkDeep()
// filter the state down to a suitable config value
config := genconfig.ExtractLegacyConfigFromState(schema.Body, state)
return genconfig.GenerateResourceContents(addr, schema.Body, providerAddr, config, false)
}
func (n *NodePlannableResourceInstance) generateHCLListResourceDef(addr addrs.AbsResourceInstance, state cty.Value, schema providers.Schema) (genconfig.ImportGroup, tfdiags.Diagnostics) {
@ -899,7 +905,30 @@ func (n *NodePlannableResourceInstance) generateHCLListResourceDef(addr addrs.Ab
Alias: n.ResolvedProvider.Alias,
}
return genconfig.GenerateListResourceContents(addr, schema.Body, schema.Identity, providerAddr, state)
if !state.CanIterateElements() {
panic(fmt.Sprintf("invalid list resource data: %#v\n", state))
}
var listElements []genconfig.ResourceListElement
iter := state.ElementIterator()
for iter.Next() {
_, val := iter.Element()
// we still need to generate the resource block even if the state is not given,
// so that the import block can reference it.
stateVal := cty.NilVal
if val.Type().HasAttribute("state") {
stateVal = val.GetAttr("state")
}
stateVal, _ = stateVal.UnmarkDeep()
config := genconfig.ExtractLegacyConfigFromState(schema.Body, stateVal)
idVal := val.GetAttr("identity")
listElements = append(listElements, genconfig.ResourceListElement{Config: config, Identity: idVal})
}
return genconfig.GenerateListResourceContents(addr, schema.Body, schema.Identity, providerAddr, listElements)
}
// mergeDeps returns the union of 2 sets of dependencies