2017-01-19 05:49:42 +01:00
|
|
|
package format
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"sort"
|
|
|
|
"strings"
|
|
|
|
|
2017-06-22 03:38:33 +02:00
|
|
|
"github.com/hashicorp/terraform/config"
|
2017-01-19 05:49:42 +01:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
"github.com/mitchellh/colorstring"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PlanOpts are the options for formatting a plan.
|
|
|
|
type PlanOpts struct {
|
|
|
|
// Plan is the plan to format. This is required.
|
|
|
|
Plan *terraform.Plan
|
|
|
|
|
|
|
|
// Color is the colorizer. This is optional.
|
|
|
|
Color *colorstring.Colorize
|
|
|
|
|
|
|
|
// ModuleDepth is the depth of the modules to expand. By default this
|
|
|
|
// is zero which will not expand modules at all.
|
|
|
|
ModuleDepth int
|
|
|
|
}
|
|
|
|
|
|
|
|
// Plan takes a plan and returns a
|
|
|
|
func Plan(opts *PlanOpts) string {
|
|
|
|
p := opts.Plan
|
|
|
|
if p.Diff == nil || p.Diff.Empty() {
|
|
|
|
return "This plan does nothing."
|
|
|
|
}
|
|
|
|
|
|
|
|
if opts.Color == nil {
|
|
|
|
opts.Color = &colorstring.Colorize{
|
|
|
|
Colors: colorstring.DefaultColors,
|
|
|
|
Reset: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
for _, m := range p.Diff.Modules {
|
|
|
|
if len(m.Path)-1 <= opts.ModuleDepth || opts.ModuleDepth == -1 {
|
|
|
|
formatPlanModuleExpand(buf, m, opts)
|
|
|
|
} else {
|
|
|
|
formatPlanModuleSingle(buf, m, opts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.TrimSpace(buf.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// formatPlanModuleExpand will output the given module and all of its
|
|
|
|
// resources.
|
|
|
|
func formatPlanModuleExpand(
|
|
|
|
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *PlanOpts) {
|
|
|
|
// Ignore empty diffs
|
|
|
|
if m.Empty() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-22 03:38:33 +02:00
|
|
|
var modulePath []string
|
2017-01-19 05:49:42 +01:00
|
|
|
if !m.IsRoot() {
|
2017-06-22 03:38:33 +02:00
|
|
|
modulePath = m.Path[1:]
|
2017-01-19 05:49:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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(m.Resources))
|
2017-06-22 03:38:33 +02:00
|
|
|
addrs := map[string]*terraform.ResourceAddress{}
|
|
|
|
for name := range m.Resources {
|
2017-01-19 05:49:42 +01:00
|
|
|
names = append(names, name)
|
2017-06-22 03:38:33 +02:00
|
|
|
var err error
|
|
|
|
addrs[name], err = terraform.ParseResourceAddressForInstanceDiff(modulePath, name)
|
|
|
|
if err != nil {
|
|
|
|
// should never happen; indicates invalid diff
|
|
|
|
panic("invalid resource address in diff")
|
|
|
|
}
|
2017-01-19 05:49:42 +01:00
|
|
|
}
|
2017-06-22 03:38:33 +02:00
|
|
|
sort.Slice(names, func(i, j int) bool {
|
|
|
|
return addrs[names[i]].Less(addrs[names[j]])
|
|
|
|
})
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
// Go through each sorted name and start building the output
|
|
|
|
for _, name := range names {
|
|
|
|
rdiff := m.Resources[name]
|
|
|
|
if rdiff.Empty() {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-06-22 03:38:33 +02:00
|
|
|
addr := addrs[name]
|
|
|
|
addrStr := addr.String()
|
|
|
|
dataSource := addr.Mode == config.DataResourceMode
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
// Determine the color for the text (green for adding, yellow
|
|
|
|
// for change, red for delete), and symbol, and output the
|
|
|
|
// resource header.
|
|
|
|
color := "yellow"
|
2017-06-22 03:38:33 +02:00
|
|
|
symbol := " ~"
|
2017-01-19 05:49:42 +01:00
|
|
|
oldValues := true
|
|
|
|
switch rdiff.ChangeType() {
|
|
|
|
case terraform.DiffDestroyCreate:
|
2017-06-22 03:38:33 +02:00
|
|
|
color = "yellow"
|
|
|
|
symbol = "[red]-[reset]/[green]+[reset][yellow]"
|
2017-01-19 05:49:42 +01:00
|
|
|
case terraform.DiffCreate:
|
|
|
|
color = "green"
|
2017-06-22 03:38:33 +02:00
|
|
|
symbol = " +"
|
2017-01-19 05:49:42 +01:00
|
|
|
oldValues = false
|
|
|
|
|
|
|
|
// If we're "creating" a data resource then we'll present it
|
|
|
|
// to the user as a "read" operation, so it's clear that this
|
|
|
|
// operation won't change anything outside of the Terraform state.
|
|
|
|
// Unfortunately by the time we get here we only have the name
|
|
|
|
// to work with, so we need to cheat and exploit knowledge of the
|
|
|
|
// naming scheme for data resources.
|
|
|
|
if dataSource {
|
2017-06-22 03:38:33 +02:00
|
|
|
symbol = " <="
|
2017-01-19 05:49:42 +01:00
|
|
|
color = "cyan"
|
|
|
|
}
|
|
|
|
case terraform.DiffDestroy:
|
|
|
|
color = "red"
|
2017-06-22 03:38:33 +02:00
|
|
|
symbol = " -"
|
2017-01-19 05:49:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var extraAttr []string
|
|
|
|
if rdiff.DestroyTainted {
|
|
|
|
extraAttr = append(extraAttr, "tainted")
|
|
|
|
}
|
|
|
|
if rdiff.DestroyDeposed {
|
|
|
|
extraAttr = append(extraAttr, "deposed")
|
|
|
|
}
|
|
|
|
var extraStr string
|
|
|
|
if len(extraAttr) > 0 {
|
|
|
|
extraStr = fmt.Sprintf(" (%s)", strings.Join(extraAttr, ", "))
|
|
|
|
}
|
2017-06-22 03:38:33 +02:00
|
|
|
if rdiff.ChangeType() == terraform.DiffDestroyCreate {
|
|
|
|
extraStr = extraStr + opts.Color.Color(" [red][bold](new resource required)")
|
|
|
|
}
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
buf.WriteString(opts.Color.Color(fmt.Sprintf(
|
|
|
|
"[%s]%s %s%s\n",
|
2017-06-22 03:38:33 +02:00
|
|
|
color, symbol, addrStr, extraStr)))
|
2017-01-19 05:49:42 +01:00
|
|
|
|
|
|
|
// 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 v == "" && attrDiff.NewComputed {
|
|
|
|
v = "<computed>"
|
|
|
|
}
|
|
|
|
|
|
|
|
if attrDiff.Sensitive {
|
|
|
|
v = "<sensitive>"
|
|
|
|
}
|
|
|
|
|
|
|
|
updateMsg := ""
|
|
|
|
if attrDiff.RequiresNew && rdiff.Destroy {
|
|
|
|
updateMsg = opts.Color.Color(" [red](forces new resource)")
|
|
|
|
} else if attrDiff.Sensitive && oldValues {
|
|
|
|
updateMsg = opts.Color.Color(" [yellow](attribute changed)")
|
|
|
|
}
|
|
|
|
|
|
|
|
if oldValues {
|
|
|
|
var u string
|
|
|
|
if attrDiff.Sensitive {
|
|
|
|
u = "<sensitive>"
|
|
|
|
} else {
|
|
|
|
u = attrDiff.Old
|
|
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf(
|
2017-06-22 03:38:33 +02:00
|
|
|
" %s:%s %#v => %#v%s\n",
|
2017-01-19 05:49:42 +01:00
|
|
|
attrK,
|
|
|
|
strings.Repeat(" ", keyLen-len(attrK)),
|
|
|
|
u,
|
|
|
|
v,
|
|
|
|
updateMsg))
|
|
|
|
} else {
|
|
|
|
buf.WriteString(fmt.Sprintf(
|
2017-06-22 03:38:33 +02:00
|
|
|
" %s:%s %#v%s\n",
|
2017-01-19 05:49:42 +01:00
|
|
|
attrK,
|
|
|
|
strings.Repeat(" ", keyLen-len(attrK)),
|
|
|
|
v,
|
|
|
|
updateMsg))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the reset color so we don't overload the user's terminal
|
|
|
|
buf.WriteString(opts.Color.Color("[reset]\n"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// formatPlanModuleSingle will output the given module and all of its
|
|
|
|
// resources.
|
|
|
|
func formatPlanModuleSingle(
|
|
|
|
buf *bytes.Buffer, m *terraform.ModuleDiff, opts *PlanOpts) {
|
|
|
|
// Ignore empty diffs
|
|
|
|
if m.Empty() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
|
|
|
|
|
|
|
|
// 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 := "~"
|
|
|
|
switch m.ChangeType() {
|
|
|
|
case terraform.DiffCreate:
|
|
|
|
color = "green"
|
|
|
|
symbol = "+"
|
|
|
|
case terraform.DiffDestroy:
|
|
|
|
color = "red"
|
|
|
|
symbol = "-"
|
|
|
|
}
|
|
|
|
|
|
|
|
buf.WriteString(opts.Color.Color(fmt.Sprintf(
|
|
|
|
"[%s]%s %s\n",
|
|
|
|
color, symbol, moduleName)))
|
|
|
|
buf.WriteString(fmt.Sprintf(
|
|
|
|
" %d resource(s)",
|
|
|
|
len(m.Resources)))
|
|
|
|
buf.WriteString(opts.Color.Color("[reset]\n"))
|
|
|
|
}
|