command: various adjustments to the diff presentation
The previous diff presentation was rather "wordy", and not very friendly to those who can't see color either because they have color-blindness or because they don't have a color-supporting terminal. This new presentation uses the actual symbols used in the plan output and tries to be more concise. It also uses some framing characters to try to separate the different stages of "terraform plan" to make it easier to visually navigate. The apply command also adopts this new plan presentation, in preparation for "terraform apply" (with interactive plan confirmation) becoming the primary, safe workflow in the next major release. Finally, we standardize on the terminology "perform" and "actions" rather than "execute" and "changes" to reflect the fact that reading is now an action and that isn't actually a _change_.
This commit is contained in:
parent
892f60efe0
commit
83414beb8f
|
@ -108,19 +108,15 @@ func (b *Local) opApply(
|
|||
"There is no undo. Only 'yes' will be accepted to confirm."
|
||||
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(dispPlan.Format(b.Colorize()))
|
||||
b.renderPlan(dispPlan)
|
||||
b.CLI.Output("")
|
||||
}
|
||||
|
||||
v, err := op.UIIn.Input(&terraform.InputOpts{
|
||||
|
@ -337,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.
|
||||
`
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package local
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
@ -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
|
||||
|
@ -135,29 +139,62 @@ func (b *Local) opPlan(
|
|||
if b.CLI != nil {
|
||||
dispPlan := format.NewPlan(plan)
|
||||
if dispPlan.Empty() {
|
||||
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(planNoChanges)))
|
||||
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,
|
||||
))
|
||||
}
|
||||
|
||||
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,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Local) renderPlan(dispPlan *format.Plan) {
|
||||
|
||||
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 = `
|
||||
No configuration files found!
|
||||
|
||||
|
@ -168,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 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.
|
||||
const planHeaderIntro = `
|
||||
An execution plan has been generated and is shown below.
|
||||
Resource actions are indicated with the following symbols:
|
||||
`
|
||||
|
||||
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.
|
||||
const planHeaderNoOutput = `
|
||||
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.
|
||||
This plan was saved to: %s
|
||||
|
||||
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.
|
||||
|
||||
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 = `
|
||||
|
|
|
@ -221,11 +221,39 @@ func (p *Plan) Stats() PlanStats {
|
|||
return ret
|
||||
}
|
||||
|
||||
// ActionCounts returns the number of diffs for each action type
|
||||
func (p *Plan) ActionCounts() map[terraform.DiffChangeType]int {
|
||||
ret := map[terraform.DiffChangeType]int{}
|
||||
for _, r := range p.Resources {
|
||||
ret[r.Action]++
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Empty returns true if there is at least one resource diff in the receiving plan.
|
||||
func (p *Plan) Empty() bool {
|
||||
return len(p.Resources) == 0
|
||||
}
|
||||
|
||||
// DiffActionSymbol returns a string that, once passed through a
|
||||
// colorstring.Colorize, will produce a result that can be written
|
||||
// to a terminal to produce a symbol made of three printable
|
||||
// characters, possibly interspersed with VT100 color codes.
|
||||
func DiffActionSymbol(action terraform.DiffChangeType) string {
|
||||
switch action {
|
||||
case terraform.DiffDestroyCreate:
|
||||
return "[red]-[reset]/[green]+[reset]"
|
||||
case terraform.DiffCreate:
|
||||
return " [green]+[reset]"
|
||||
case terraform.DiffDestroy:
|
||||
return " [red]-[reset]"
|
||||
case terraform.DiffRefresh:
|
||||
return " [cyan]<=[reset]"
|
||||
default:
|
||||
return " [yellow]~[reset]"
|
||||
}
|
||||
}
|
||||
|
||||
// formatPlanInstanceDiff writes the text representation of the given instance diff
|
||||
// to the given buffer, using the given colorizer.
|
||||
func formatPlanInstanceDiff(buf *bytes.Buffer, r *InstanceDiff, keyLen int, colorizer *colorstring.Colorize) {
|
||||
|
@ -235,31 +263,27 @@ func formatPlanInstanceDiff(buf *bytes.Buffer, r *InstanceDiff, keyLen int, colo
|
|||
// for change, red for delete), and symbol, and output the
|
||||
// resource header.
|
||||
color := "yellow"
|
||||
symbol := " ~"
|
||||
symbol := DiffActionSymbol(r.Action)
|
||||
oldValues := true
|
||||
switch r.Action {
|
||||
case terraform.DiffDestroyCreate:
|
||||
color = "yellow"
|
||||
symbol = "[red]-[reset]/[green]+[reset][yellow]"
|
||||
case terraform.DiffCreate:
|
||||
color = "green"
|
||||
symbol = " +"
|
||||
oldValues = false
|
||||
case terraform.DiffDestroy:
|
||||
color = "red"
|
||||
symbol = " -"
|
||||
case terraform.DiffRefresh:
|
||||
symbol = " <="
|
||||
color = "cyan"
|
||||
oldValues = false
|
||||
}
|
||||
|
||||
var extraStr string
|
||||
if r.Tainted {
|
||||
extraStr = extraStr + colorizer.Color(" (tainted)")
|
||||
extraStr = extraStr + " (tainted)"
|
||||
}
|
||||
if r.Deposed {
|
||||
extraStr = extraStr + colorizer.Color(" (deposed)")
|
||||
extraStr = extraStr + " (deposed)"
|
||||
}
|
||||
if r.Action == terraform.DiffDestroyCreate {
|
||||
extraStr = extraStr + colorizer.Color(" [red][bold](new resource required)")
|
||||
|
@ -267,8 +291,8 @@ func formatPlanInstanceDiff(buf *bytes.Buffer, r *InstanceDiff, keyLen int, colo
|
|||
|
||||
buf.WriteString(
|
||||
colorizer.Color(fmt.Sprintf(
|
||||
"[%s]%s %s%s\n",
|
||||
color, symbol, addrStr, extraStr,
|
||||
"[%s]%s [%s]%s%s\n",
|
||||
color, symbol, color, addrStr, extraStr,
|
||||
)),
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue