Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure consistency of details and tallies in plan and apply #15884

Merged
merged 7 commits into from
Sep 2, 2017
52 changes: 9 additions & 43 deletions backend/local/backend_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,47 +96,27 @@ func (b *Local) opApply(
return
}

trivialPlan := plan.Diff == nil || plan.Diff.Empty()
dispPlan := format.NewPlan(plan)
trivialPlan := dispPlan.Empty()
hasUI := op.UIOut != nil && op.UIIn != nil
if hasUI && ((op.Destroy && !op.DestroyForce) ||
(!op.Destroy && !op.AutoApprove && !trivialPlan)) {
mustConfirm := hasUI && ((op.Destroy && !op.DestroyForce) || (!op.Destroy && !op.AutoApprove && !trivialPlan))
if mustConfirm {
var desc, query string
if op.Destroy {
// Default destroy message
desc = "Terraform will delete all your managed infrastructure, as shown above.\n" +
desc = "Terraform will destroy all your managed infrastructure, as shown above.\n" +
"There is no undo. Only 'yes' will be accepted to confirm."

// If targets are specified, list those to user
if op.Targets != nil {
var descBuffer bytes.Buffer
descBuffer.WriteString("Terraform will delete the following infrastructure:\n")
for _, target := range op.Targets {
descBuffer.WriteString("\t")
descBuffer.WriteString(target)
descBuffer.WriteString("\n")
}
descBuffer.WriteString("There is no undo. Only 'yes' will be accepted to confirm")
desc = descBuffer.String()
}
query = "Do you really want to destroy?"
} else {
desc = "Terraform will apply the changes described above.\n" +
desc = "Terraform will perform the actions described above.\n" +
"Only 'yes' will be accepted to approve."
query = "Do you want to apply these changes?"
query = "Do you want to perform these actions?"
}

if !trivialPlan {
// Display the plan of what we are going to apply/destroy.
if op.Destroy {
op.UIOut.Output("\n" + strings.TrimSpace(approveDestroyPlanHeader) + "\n")
} else {
op.UIOut.Output("\n" + strings.TrimSpace(approvePlanHeader) + "\n")
}
op.UIOut.Output(format.Plan(&format.PlanOpts{
Plan: plan,
Color: b.Colorize(),
ModuleDepth: -1,
}))
b.renderPlan(dispPlan)
b.CLI.Output("")
}

v, err := op.UIIn.Input(&terraform.InputOpts{
Expand Down Expand Up @@ -353,17 +333,3 @@ Terraform encountered an error attempting to save the state before canceling
the current operation. Once the operation is complete another attempt will be
made to save the final state.
`

const approvePlanHeader = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.
`

const approveDestroyPlanHeader = `
The Terraform destroy plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning.
Resources shown in red will be destroyed.
`
101 changes: 64 additions & 37 deletions backend/local/backend_plan.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package local

import (
"bytes"
"context"
"fmt"
"log"
Expand Down Expand Up @@ -95,6 +96,9 @@ func (b *Local) opPlan(
runningOp.Err = errwrap.Wrapf("Error refreshing state: {{err}}", err)
return
}
if b.CLI != nil {
b.CLI.Output("\n------------------------------------------------------------------------")
}
}

// Perform the plan
Expand Down Expand Up @@ -133,32 +137,62 @@ func (b *Local) opPlan(

// Perform some output tasks if we have a CLI to output to.
if b.CLI != nil {
if plan.Diff.Empty() {
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges)))
dispPlan := format.NewPlan(plan)
if dispPlan.Empty() {
b.CLI.Output("\n" + b.Colorize().Color(strings.TrimSpace(planNoChanges)))
return
}

b.renderPlan(dispPlan)

b.CLI.Output("\n------------------------------------------------------------------------")

if path := op.PlanOutPath; path == "" {
b.CLI.Output(strings.TrimSpace(planHeaderNoOutput) + "\n")
b.CLI.Output(fmt.Sprintf(
"\n" + strings.TrimSpace(planHeaderNoOutput) + "\n",
))
} else {
b.CLI.Output(fmt.Sprintf(
strings.TrimSpace(planHeaderYesOutput)+"\n",
path))
"\n"+strings.TrimSpace(planHeaderYesOutput)+"\n",
path, path,
))
}
}
}

func (b *Local) renderPlan(dispPlan *format.Plan) {

b.CLI.Output(format.Plan(&format.PlanOpts{
Plan: plan,
Color: b.Colorize(),
ModuleDepth: -1,
}))

b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
"[reset][bold]Plan:[reset] "+
"%d to add, %d to change, %d to destroy.",
countHook.ToAdd+countHook.ToRemoveAndAdd,
countHook.ToChange,
countHook.ToRemove+countHook.ToRemoveAndAdd)))
headerBuf := &bytes.Buffer{}
fmt.Fprintf(headerBuf, "\n%s\n", strings.TrimSpace(planHeaderIntro))
counts := dispPlan.ActionCounts()
if counts[terraform.DiffCreate] > 0 {
fmt.Fprintf(headerBuf, "%s create\n", format.DiffActionSymbol(terraform.DiffCreate))
}
if counts[terraform.DiffUpdate] > 0 {
fmt.Fprintf(headerBuf, "%s update in-place\n", format.DiffActionSymbol(terraform.DiffUpdate))
}
if counts[terraform.DiffDestroy] > 0 {
fmt.Fprintf(headerBuf, "%s destroy\n", format.DiffActionSymbol(terraform.DiffDestroy))
}
if counts[terraform.DiffDestroyCreate] > 0 {
fmt.Fprintf(headerBuf, "%s destroy and then create replacement\n", format.DiffActionSymbol(terraform.DiffDestroyCreate))
}
if counts[terraform.DiffRefresh] > 0 {
fmt.Fprintf(headerBuf, "%s read (data resources)\n", format.DiffActionSymbol(terraform.DiffRefresh))
}

b.CLI.Output(b.Colorize().Color(headerBuf.String()))

b.CLI.Output("Terraform will perform the following actions:\n")

b.CLI.Output(dispPlan.Format(b.Colorize()))

stats := dispPlan.Stats()
b.CLI.Output(b.Colorize().Color(fmt.Sprintf(
"[reset][bold]Plan:[reset] "+
"%d to add, %d to change, %d to destroy.",
stats.ToAdd, stats.ToChange, stats.ToDestroy,
)))
}

const planErrNoConfig = `
Expand All @@ -171,37 +205,30 @@ flag or create a single empty configuration file. Otherwise, please create
a Terraform configuration file in the path being executed and try again.
`

const planHeaderIntro = `
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
`

const planHeaderNoOutput = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
`

const planHeaderYesOutput = `
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Your plan was also saved to the path below. Call the "apply" subcommand
with this plan file and Terraform will exactly execute this execution
plan.
This plan was saved to: %s

Path: %s
To perform exactly these actions, run the following command to apply:
terraform apply %q
`

const planNoChanges = `
[reset][bold][green]No changes. Infrastructure is up-to-date.[reset][green]

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, Terraform
doesn't need to do anything.
configuration and real physical resources that exist. As a result, no
actions need to be performed.
`

const planRefreshing = `
Expand Down
Loading