command/plan: nice plan formatting

This commit is contained in:
Mitchell Hashimoto 2014-07-12 16:32:48 -07:00
parent e476bca29c
commit 832211c17a
3 changed files with 132 additions and 5 deletions

97
command/format_plan.go Normal file
View File

@ -0,0 +1,97 @@
package command
import (
"bytes"
"fmt"
"sort"
"strings"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/colorstring"
)
// FormatPlan takes a plan and returns a
func FormatPlan(p *terraform.Plan, c *colorstring.Colorize) string {
if c == nil {
c = &colorstring.Colorize{
Colors: colorstring.DefaultColors,
Reset: false,
}
}
buf := new(bytes.Buffer)
// We want to output the resources in sorted order to make things
// easier to scan through, so get all the resource names and sort them.
names := make([]string, 0, len(p.Diff.Resources))
for name, _ := range p.Diff.Resources {
names = append(names, name)
}
sort.Strings(names)
// Go through each sorted name and start building the output
for _, name := range names {
rdiff := p.Diff.Resources[name]
// Determine the color for the text (green for adding, yellow
// for change, red for delete), and symbol, and output the
// resource header.
color := "yellow"
symbol := "~"
if rdiff.RequiresNew() {
color = "green"
symbol = "+"
} else if rdiff.Destroy {
color = "red"
symbol = "-"
}
buf.WriteString(c.Color(fmt.Sprintf(
"[%s]%s %s\n",
color, symbol, name)))
// Get all the attributes that are changing, and sort them. Also
// determine the longest key so that we can align them all.
keyLen := 0
keys := make([]string, 0, len(rdiff.Attributes))
for key, _ := range rdiff.Attributes {
// Skip the ID since we do that specially
if key == "id" {
continue
}
keys = append(keys, key)
if len(key) > keyLen {
keyLen = len(key)
}
}
sort.Strings(keys)
// Go through and output each attribute
for _, attrK := range keys {
attrDiff := rdiff.Attributes[attrK]
v := attrDiff.New
if attrDiff.NewComputed {
v = "<computed>"
}
newResource := ""
if attrDiff.RequiresNew && rdiff.Destroy {
newResource = " (forces new resource)"
}
buf.WriteString(fmt.Sprintf(
" %s:%s %#v => %#v%s\n",
attrK,
strings.Repeat(" ", keyLen-len(attrK)),
attrDiff.Old,
v,
newResource))
}
// Write the reset color so we don't overload the user's terminal
buf.WriteString(c.Color("[reset]\n"))
}
return strings.TrimSpace(buf.String())
}

View File

@ -29,9 +29,6 @@ func (h *UiHook) PreApply(
func (h *UiHook) PreDiff(
id string, s *terraform.ResourceState) (terraform.HookAction, error) {
h.once.Do(h.init)
h.ui.Output(fmt.Sprintf("%s: Calculating diff", id))
return terraform.HookActionContinue, nil
}

View File

@ -109,8 +109,6 @@ func (c *PlanCommand) Run(args []string) int {
return 0
}
c.Ui.Output(strings.TrimSpace(plan.String()))
if outPath != "" {
log.Printf("[INFO] Writing plan output to: %s", outPath)
f, err := os.Create(outPath)
@ -124,6 +122,16 @@ func (c *PlanCommand) Run(args []string) int {
}
}
if outPath == "" {
c.Ui.Output(strings.TrimSpace(planHeaderNoOutput)+"\n")
} else {
c.Ui.Output(fmt.Sprintf(
strings.TrimSpace(planHeaderYesOutput)+"\n",
outPath))
}
c.Ui.Output(FormatPlan(plan, nil))
return 0
}
@ -159,3 +167,28 @@ Options:
func (c *PlanCommand) Synopsis() string {
return "Show changes between Terraform config and infrastructure"
}
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.
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 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.
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
`