From 07aa7ee1d52697c0b26e10faea5fdc3e0b3ce7fb Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Wed, 24 May 2023 13:58:26 +0200 Subject: [PATCH] Propagate generated config filename into the Terraform graph (#33255) --- internal/backend/local/backend_local.go | 12 +++++----- internal/terraform/context_plan.go | 10 ++++---- internal/terraform/context_plan2_test.go | 16 ++++++------- internal/terraform/graph_builder_plan.go | 10 ++++---- internal/terraform/node_resource_abstract.go | 5 ++-- internal/terraform/node_resource_plan.go | 2 +- .../terraform/node_resource_plan_instance.go | 9 +++---- internal/terraform/transform_config.go | 24 +++++++++---------- 8 files changed, 47 insertions(+), 41 deletions(-) diff --git a/internal/backend/local/backend_local.go b/internal/backend/local/backend_local.go index d290743b53..9bfad4e097 100644 --- a/internal/backend/local/backend_local.go +++ b/internal/backend/local/backend_local.go @@ -190,12 +190,12 @@ func (b *Local) localRunDirect(op *backend.Operation, run *backend.LocalRun, cor } planOpts := &terraform.PlanOpts{ - Mode: op.PlanMode, - Targets: op.Targets, - ForceReplace: op.ForceReplace, - SetVariables: variables, - SkipRefresh: op.Type != backend.OperationTypeRefresh && !op.PlanRefresh, - GenerateConfig: len(op.GenerateConfigOut) > 0, + Mode: op.PlanMode, + Targets: op.Targets, + ForceReplace: op.ForceReplace, + SetVariables: variables, + SkipRefresh: op.Type != backend.OperationTypeRefresh && !op.PlanRefresh, + GenerateConfigPath: op.GenerateConfigOut, } run.PlanOpts = planOpts diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index b8fcf22c33..f921eabe89 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -78,9 +78,11 @@ type PlanOpts struct { // will be added to the plan graph. ImportTargets []*ImportTarget - // GenerateConfig tells Terraform to generate configuration for any - // ImportTargets that do not have configuration already. - GenerateConfig bool + // GenerateConfig tells Terraform where to write any generated configuration + // for any ImportTargets that do not have configuration already. + // + // If empty, then no config will be generated. + GenerateConfigPath string } // Plan generates an execution plan by comparing the given configuration @@ -669,7 +671,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, preDestroyRefresh: opts.PreDestroyRefresh, Operation: walkPlan, ImportTargets: opts.ImportTargets, - GenerateConfig: opts.GenerateConfig, + GenerateConfigPath: opts.GenerateConfigPath, }).Build(addrs.RootModuleInstance) return graph, walkPlan, diags case plans.RefreshOnlyMode: diff --git a/internal/terraform/context_plan2_test.go b/internal/terraform/context_plan2_test.go index cb3fff1545..27de456a8e 100644 --- a/internal/terraform/context_plan2_test.go +++ b/internal/terraform/context_plan2_test.go @@ -4598,8 +4598,8 @@ resource "test_object" "a" { } plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ - Mode: plans.NormalMode, - GenerateConfig: true, + 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()) @@ -4658,8 +4658,8 @@ import { } plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ - Mode: plans.NormalMode, - GenerateConfig: true, + 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()) @@ -4740,8 +4740,8 @@ import { } plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ - Mode: plans.NormalMode, - GenerateConfig: true, + 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()) @@ -4823,8 +4823,8 @@ import { } plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{ - Mode: plans.NormalMode, - GenerateConfig: true, + Mode: plans.NormalMode, + GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty. }) if !diags.HasErrors() { t.Fatal("expected error") diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index a398ebd2c2..9d37781c94 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -76,9 +76,11 @@ type PlanGraphBuilder struct { // ImportTargets are the list of resources to import. ImportTargets []*ImportTarget - // GenerateConfig tells Terraform to generate config for any import targets - // that do not already have configuration. - GenerateConfig bool + // GenerateConfig tells Terraform where to write and generated config for + // any import targets that do not already have configuration. + // + // If empty, then config will not be generated. + GenerateConfigPath string } // See GraphBuilder @@ -117,7 +119,7 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { importTargets: b.ImportTargets, // We only want to generate config during a plan operation. - generateConfigForImportTargets: b.GenerateConfig, + generateConfigPathForImportTargets: b.GenerateConfigPath, }, // Add dynamic values diff --git a/internal/terraform/node_resource_abstract.go b/internal/terraform/node_resource_abstract.go index 64b73279cb..1190f3b710 100644 --- a/internal/terraform/node_resource_abstract.go +++ b/internal/terraform/node_resource_abstract.go @@ -79,8 +79,9 @@ type NodeAbstractResource struct { // This resource may expand into instances which need to be imported. importTargets []*ImportTarget - // generateConfig tells this node that it's okay for it to generate config. - generateConfig bool + // generateConfigPath tells this node which file to write generated config + // into. If empty, then config should not be generated. + generateConfigPath string } var ( diff --git a/internal/terraform/node_resource_plan.go b/internal/terraform/node_resource_plan.go index f6d99606d0..a2f2354095 100644 --- a/internal/terraform/node_resource_plan.go +++ b/internal/terraform/node_resource_plan.go @@ -336,7 +336,7 @@ func (n *nodeExpandPlannableResource) resourceInstanceSubgraph(ctx EvalContext, a.dependsOn = n.dependsOn a.Dependencies = n.dependencies a.preDestroyRefresh = n.preDestroyRefresh - a.generateConfig = n.generateConfig + a.generateConfigPath = n.generateConfigPath m = &NodePlannableResourceInstance{ NodeAbstractResourceInstance: a, diff --git a/internal/terraform/node_resource_plan_instance.go b/internal/terraform/node_resource_plan_instance.go index af9611a55b..4d211e8c7d 100644 --- a/internal/terraform/node_resource_plan_instance.go +++ b/internal/terraform/node_resource_plan_instance.go @@ -6,6 +6,7 @@ package terraform import ( "fmt" "log" + "path/filepath" "sort" "github.com/hashicorp/hcl/v2" @@ -160,7 +161,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) importing := n.importTarget.ID != "" - if importing && n.Config == nil && !n.generateConfig { + if importing && n.Config == nil && len(n.generateConfigPath) == 0 { // Then the user wrote an import target to a target that didn't exist. if n.Addr.Module.IsRoot() { diags = diags.Append(&hcl.Diagnostic{ @@ -275,7 +276,7 @@ func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext) // If we are importing and generating a configuration, we need to // ensure the change is written out so the configuration can be // captured. - if n.generateConfig { + if len(n.generateConfigPath) > 0 { // Update our return plan change := &plans.ResourceInstanceChange{ Addr: n.Addr, @@ -543,7 +544,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. } // If we're importing and generating config, generate it now. - if n.generateConfig { + if len(n.generateConfigPath) > 0 { if n.Config != nil { return instanceRefreshState, diags.Append(fmt.Errorf("tried to generate config for %s, but it already exists", n.Addr)) } @@ -565,7 +566,7 @@ func (n *NodePlannableResourceInstance) importState(ctx EvalContext, addr addrs. n.generatedConfigHCL = genconfig.WrapResourceContents(n.Addr, generatedHCLAttributes) // parse the "file" as HCL to get the hcl.Body - synthHCLFile, hclDiags := hclsyntax.ParseConfig([]byte(generatedHCLAttributes), "generated_resources.tf", hcl.Pos{Byte: 0, Line: 1, Column: 1}) + synthHCLFile, hclDiags := hclsyntax.ParseConfig([]byte(generatedHCLAttributes), filepath.Base(n.generateConfigPath), hcl.Pos{Byte: 0, Line: 1, Column: 1}) diags = diags.Append(hclDiags) if hclDiags.HasErrors() { return instanceRefreshState, diags diff --git a/internal/terraform/transform_config.go b/internal/terraform/transform_config.go index 61ab629313..3275073be8 100644 --- a/internal/terraform/transform_config.go +++ b/internal/terraform/transform_config.go @@ -39,14 +39,14 @@ type ConfigTransformer struct { // imported for them. importTargets []*ImportTarget - // generateConfigForImportTargets tells the graph to generate config for any - // import targets that are not contained within config. + // generateConfigPathForImportTargets tells the graph where to write any + // generated config for import targets that are not contained within config. // - // If this is false and an import target has no config, the graph will + // If this is empty and an import target has no config, the graph will // simply import the state for the target and any follow-up operations will // try to delete the imported resource unless the config is updated // manually. - generateConfigForImportTargets bool + generateConfigPathForImportTargets string } func (t *ConfigTransformer) Transform(g *Graph) error { @@ -60,23 +60,23 @@ func (t *ConfigTransformer) Transform(g *Graph) error { } // Start the transformation process - return t.transform(g, t.Config, t.generateConfigForImportTargets) + return t.transform(g, t.Config, t.generateConfigPathForImportTargets) } -func (t *ConfigTransformer) transform(g *Graph, config *configs.Config, generateConfig bool) error { +func (t *ConfigTransformer) transform(g *Graph, config *configs.Config, generateConfigPath string) error { // If no config, do nothing if config == nil { return nil } // Add our resources - if err := t.transformSingle(g, config, generateConfig); err != nil { + if err := t.transformSingle(g, config, generateConfigPath); err != nil { return err } // Transform all the children without generating config. for _, c := range config.Children { - if err := t.transform(g, c, false); err != nil { + if err := t.transform(g, c, ""); err != nil { return err } } @@ -84,7 +84,7 @@ func (t *ConfigTransformer) transform(g *Graph, config *configs.Config, generate return nil } -func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, generateConfig bool) error { +func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, generateConfigPath string) error { path := config.Path module := config.Module log.Printf("[TRACE] ConfigTransformer: Starting for path: %v", path) @@ -171,9 +171,9 @@ func (t *ConfigTransformer) transformSingle(g *Graph, config *configs.Config, ge // this is something that could be done during the Validate process. for _, i := range importTargets { abstract := &NodeAbstractResource{ - Addr: i.Addr.ConfigResource(), - importTargets: []*ImportTarget{i}, - generateConfig: generateConfig, + Addr: i.Addr.ConfigResource(), + importTargets: []*ImportTarget{i}, + generateConfigPath: generateConfigPath, } var node dag.Vertex = abstract