plannable import: improve gen config human plan output (#33194)

* renderer: remove hard-coded config gen path

* mention config gen file in plan next steps
This commit is contained in:
kmoe 2023-05-15 15:21:41 +01:00 committed by GitHub
parent 789e30dfc5
commit b4d1146f58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 31 additions and 18 deletions

View File

@ -55,7 +55,6 @@ func (b *Local) opPlan(
} }
if len(op.GenerateConfigOut) > 0 { if len(op.GenerateConfigOut) > 0 {
if op.PlanMode != plans.NormalMode { if op.PlanMode != plans.NormalMode {
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
@ -192,7 +191,7 @@ func (b *Local) opPlan(
} }
// Write out any generated config, before we render the plan. // 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) diags = diags.Append(moreDiags)
if moreDiags.HasErrors() { if moreDiags.HasErrors() {
op.ReportResult(runningOp, diags) op.ReportResult(runningOp, diags)
@ -209,6 +208,10 @@ func (b *Local) opPlan(
op.ReportResult(runningOp, diags) op.ReportResult(runningOp, diags)
if !runningOp.PlanEmpty { if !runningOp.PlanEmpty {
op.View.PlanNextStep(op.PlanOutPath) if wroteConfig {
op.View.PlanNextStep(op.PlanOutPath, op.GenerateConfigOut)
} else {
op.View.PlanNextStep(op.PlanOutPath, "")
}
} }
} }

View File

@ -469,7 +469,7 @@ func resourceChangeComment(resource jsonplan.ResourceChange, action plans.Action
if resource.Change.Importing != nil { if resource.Change.Importing != nil {
buf.WriteString(fmt.Sprintf("[bold] # %s[reset] will be imported", dispAddr)) buf.WriteString(fmt.Sprintf("[bold] # %s[reset] will be imported", dispAddr))
if len(resource.Change.GeneratedConfig) > 0 { 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 printedImported = true
break break

View File

@ -178,7 +178,7 @@ Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
Terraform will perform the following actions: Terraform will perform the following actions:
# test_resource.resource will be imported # test_resource.resource will be imported
# (config will be written to generated_config.tf) # (config will be generated)
resource "test_resource" "resource" { resource "test_resource" "resource" {
id = "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E" id = "1D5F5E9E-F2E5-401B-9ED5-692A215AC67E"
value = "Hello, World!" value = "Hello, World!"

View File

@ -31,7 +31,7 @@ type Operation interface {
PlannedChange(change *plans.ResourceInstanceChangeSrc) PlannedChange(change *plans.ResourceInstanceChangeSrc)
Plan(plan *plans.Plan, schemas *terraform.Schemas) Plan(plan *plans.Plan, schemas *terraform.Schemas)
PlanNextStep(planPath string) PlanNextStep(planPath string, genConfigPath string)
Diagnostics(diags tfdiags.Diagnostics) 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 // 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. // 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 { if v.inAutomation {
return return
} }
v.view.outputHorizRule() v.view.outputHorizRule()
if genConfigPath != "" {
v.view.streams.Printf(
"\n"+strings.TrimSpace(format.WordWrap(planHeaderGenConfig, v.view.outputColumns()))+"\n", genConfigPath,
)
}
if planPath == "" { if planPath == "" {
v.view.streams.Print( v.view.streams.Print(
"\n" + strings.TrimSpace(format.WordWrap(planHeaderNoOutput, v.view.outputColumns())) + "\n", "\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 // PlanNextStep does nothing for the JSON view as it is a hook for user-facing
// output only applicable to human-readable UI. // 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) { 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: To perform exactly these actions, run the following command to apply:
terraform apply %q 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.
`

View File

@ -450,7 +450,7 @@ func TestOperation_planNextStep(t *testing.T) {
streams, done := terminal.StreamsForTesting(t) streams, done := terminal.StreamsForTesting(t)
v := NewOperation(arguments.ViewHuman, false, NewView(streams)) 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) { if got := done(t).Stdout(); !strings.Contains(got, tc.want) {
t.Errorf("wrong result\ngot: %q\nwant: %q", 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) streams, done := terminal.StreamsForTesting(t)
v := NewOperation(arguments.ViewHuman, true, NewView(streams)) v := NewOperation(arguments.ViewHuman, true, NewView(streams))
v.PlanNextStep("") v.PlanNextStep("", "")
if got := done(t).Stdout(); got != "" { if got := done(t).Stdout(); got != "" {
t.Errorf("unexpected output\ngot: %q", got) t.Errorf("unexpected output\ngot: %q", got)

View File

@ -21,22 +21,21 @@ func ValidateTargetFile(out string) tfdiags.Diagnostics {
return diags 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 { if len(out) == 0 {
// No specified out file, so don't write anything. // No specified out file, so don't write anything.
return nil return false, nil
} }
diags := ValidateTargetFile(out) diags = ValidateTargetFile(out)
if diags.HasErrors() { if diags.HasErrors() {
return diags return false, diags
} }
var writer io.Writer var writer io.Writer
for _, change := range plan.Changes.Resources { for _, change := range plan.Changes.Resources {
if len(change.GeneratedConfig) > 0 { if len(change.GeneratedConfig) > 0 {
if writer == nil { if writer == nil {
// Lazily create the generated file, in case we have no // Lazily create the generated file, in case we have no
// generated config to create. // generated config to create.
@ -47,14 +46,14 @@ func MaybeWriteGeneratedConfig(plan *plans.Plan, out string) tfdiags.Diagnostics
tfdiags.Error, tfdiags.Error,
"Failed to create target generated file", "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))) 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( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
"Failed to create target generated file", "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))) 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" 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", "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()))) 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
} }