diff --git a/internal/backend/local/backend_plan.go b/internal/backend/local/backend_plan.go index d1c3122068..1699b508e8 100644 --- a/internal/backend/local/backend_plan.go +++ b/internal/backend/local/backend_plan.go @@ -55,7 +55,6 @@ func (b *Local) opPlan( } if len(op.GenerateConfigOut) > 0 { - if op.PlanMode != plans.NormalMode { diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, @@ -192,7 +191,7 @@ func (b *Local) opPlan( } // Write out any generated config, before we render the plan. - moreDiags = genconfig.MaybeWriteGeneratedConfig(plan, op.GenerateConfigOut) + wroteConfig, moreDiags := genconfig.MaybeWriteGeneratedConfig(plan, op.GenerateConfigOut) diags = diags.Append(moreDiags) if moreDiags.HasErrors() { op.ReportResult(runningOp, diags) @@ -209,6 +208,10 @@ func (b *Local) opPlan( op.ReportResult(runningOp, diags) if !runningOp.PlanEmpty { - op.View.PlanNextStep(op.PlanOutPath) + if wroteConfig { + op.View.PlanNextStep(op.PlanOutPath, op.GenerateConfigOut) + } else { + op.View.PlanNextStep(op.PlanOutPath, "") + } } } diff --git a/internal/command/jsonformat/plan.go b/internal/command/jsonformat/plan.go index afbc17d803..cd92e1a44f 100644 --- a/internal/command/jsonformat/plan.go +++ b/internal/command/jsonformat/plan.go @@ -469,7 +469,7 @@ func resourceChangeComment(resource jsonplan.ResourceChange, action plans.Action if resource.Change.Importing != nil { buf.WriteString(fmt.Sprintf("[bold] # %s[reset] will be imported", dispAddr)) if len(resource.Change.GeneratedConfig) > 0 { - buf.WriteString("\n #[reset] (config will be written to generated_config.tf)") + buf.WriteString("\n #[reset] (config will be generated)") } printedImported = true break diff --git a/internal/command/jsonformat/plan_test.go b/internal/command/jsonformat/plan_test.go index 6dc0edff0a..061fad67ab 100644 --- a/internal/command/jsonformat/plan_test.go +++ b/internal/command/jsonformat/plan_test.go @@ -178,7 +178,7 @@ Plan: 1 to import, 0 to add, 0 to change, 0 to destroy. Terraform will perform the following actions: # test_resource.resource will be imported - # (config will be written to generated_config.tf) + # (config will be generated) resource "test_resource" "resource" { id = "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E" value = "Hello, World!" diff --git a/internal/command/views/operation.go b/internal/command/views/operation.go index bd9cd199ff..3e14156c8e 100644 --- a/internal/command/views/operation.go +++ b/internal/command/views/operation.go @@ -31,7 +31,7 @@ type Operation interface { PlannedChange(change *plans.ResourceInstanceChangeSrc) Plan(plan *plans.Plan, schemas *terraform.Schemas) - PlanNextStep(planPath string) + PlanNextStep(planPath string, genConfigPath string) Diagnostics(diags tfdiags.Diagnostics) } @@ -135,12 +135,18 @@ func (v *OperationHuman) PlannedChange(change *plans.ResourceInstanceChangeSrc) // PlanNextStep gives the user some next-steps, unless we're running in an // automation tool which is presumed to provide its own UI for further actions. -func (v *OperationHuman) PlanNextStep(planPath string) { +func (v *OperationHuman) PlanNextStep(planPath string, genConfigPath string) { if v.inAutomation { return } v.view.outputHorizRule() + if genConfigPath != "" { + v.view.streams.Printf( + "\n"+strings.TrimSpace(format.WordWrap(planHeaderGenConfig, v.view.outputColumns()))+"\n", genConfigPath, + ) + } + if planPath == "" { v.view.streams.Print( "\n" + strings.TrimSpace(format.WordWrap(planHeaderNoOutput, v.view.outputColumns())) + "\n", @@ -261,7 +267,7 @@ func (v *OperationJSON) PlannedChange(change *plans.ResourceInstanceChangeSrc) { // PlanNextStep does nothing for the JSON view as it is a hook for user-facing // output only applicable to human-readable UI. -func (v *OperationJSON) PlanNextStep(planPath string) { +func (v *OperationJSON) PlanNextStep(planPath string, genConfigPath string) { } func (v *OperationJSON) Diagnostics(diags tfdiags.Diagnostics) { @@ -288,3 +294,7 @@ Saved the plan to: %s To perform exactly these actions, run the following command to apply: terraform apply %q ` + +const planHeaderGenConfig = ` +Terraform has generated configuration and written it to %s. Please review the configuration and edit it as necessary before adding it to version control. +` diff --git a/internal/command/views/operation_test.go b/internal/command/views/operation_test.go index 7baae0ed6e..0fe23f5fdc 100644 --- a/internal/command/views/operation_test.go +++ b/internal/command/views/operation_test.go @@ -450,7 +450,7 @@ func TestOperation_planNextStep(t *testing.T) { streams, done := terminal.StreamsForTesting(t) v := NewOperation(arguments.ViewHuman, false, NewView(streams)) - v.PlanNextStep(tc.path) + v.PlanNextStep(tc.path, "") if got := done(t).Stdout(); !strings.Contains(got, tc.want) { t.Errorf("wrong result\ngot: %q\nwant: %q", got, tc.want) @@ -465,7 +465,7 @@ func TestOperation_planNextStepInAutomation(t *testing.T) { streams, done := terminal.StreamsForTesting(t) v := NewOperation(arguments.ViewHuman, true, NewView(streams)) - v.PlanNextStep("") + v.PlanNextStep("", "") if got := done(t).Stdout(); got != "" { t.Errorf("unexpected output\ngot: %q", got) diff --git a/internal/genconfig/generate_config_write.go b/internal/genconfig/generate_config_write.go index 5a9e99e3e7..c82fa3a294 100644 --- a/internal/genconfig/generate_config_write.go +++ b/internal/genconfig/generate_config_write.go @@ -21,22 +21,21 @@ func ValidateTargetFile(out string) tfdiags.Diagnostics { return diags } -func MaybeWriteGeneratedConfig(plan *plans.Plan, out string) tfdiags.Diagnostics { +func MaybeWriteGeneratedConfig(plan *plans.Plan, out string) (wroteConfig bool, diags tfdiags.Diagnostics) { if len(out) == 0 { // No specified out file, so don't write anything. - return nil + return false, nil } - diags := ValidateTargetFile(out) + diags = ValidateTargetFile(out) if diags.HasErrors() { - return diags + return false, diags } var writer io.Writer for _, change := range plan.Changes.Resources { if len(change.GeneratedConfig) > 0 { - if writer == nil { // Lazily create the generated file, in case we have no // generated config to create. @@ -47,14 +46,14 @@ func MaybeWriteGeneratedConfig(plan *plans.Plan, out string) tfdiags.Diagnostics tfdiags.Error, "Failed to create target generated file", fmt.Sprintf("Terraform did not have permission to create the generated file (%s) in the target directory. Please modify permissions over the target directory, and try again.", out))) - return diags + return false, diags } diags = diags.Append(tfdiags.Sourceless( tfdiags.Error, "Failed to create target generated file", fmt.Sprintf("Terraform could not create the generated file (%s) in the target directory: %v. Depending on the error message, this may be a bug in Terraform itself. If so, please report it!", out, err))) - return diags + return false, diags } header := "# __generated__ by Terraform\n# Please review these resources and move them into your main configuration files.\n" @@ -74,8 +73,9 @@ func MaybeWriteGeneratedConfig(plan *plans.Plan, out string) tfdiags.Diagnostics "Failed to save generated config", fmt.Sprintf("Terraform encountered an error while writing generated config: %v. The config for %s must be created manually before applying. Depending on the error message, this may be a bug in Terraform itself. If so, please report it!", err, change.Addr.String()))) } + wroteConfig = true } } - return diags + return wroteConfig, diags }