github.com/jrasell/terraform@v0.6.17-0.20160523115548-2652f5232949/command/format_plan.go (about)

     1  package command
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform/terraform"
    10  	"github.com/mitchellh/colorstring"
    11  )
    12  
    13  // FormatPlanOpts are the options for formatting a plan.
    14  type FormatPlanOpts struct {
    15  	// Plan is the plan to format. This is required.
    16  	Plan *terraform.Plan
    17  
    18  	// Color is the colorizer. This is optional.
    19  	Color *colorstring.Colorize
    20  
    21  	// ModuleDepth is the depth of the modules to expand. By default this
    22  	// is zero which will not expand modules at all.
    23  	ModuleDepth int
    24  }
    25  
    26  // FormatPlan takes a plan and returns a
    27  func FormatPlan(opts *FormatPlanOpts) string {
    28  	p := opts.Plan
    29  	if p.Diff == nil || p.Diff.Empty() {
    30  		return "This plan does nothing."
    31  	}
    32  
    33  	if opts.Color == nil {
    34  		opts.Color = &colorstring.Colorize{
    35  			Colors: colorstring.DefaultColors,
    36  			Reset:  false,
    37  		}
    38  	}
    39  
    40  	buf := new(bytes.Buffer)
    41  	for _, m := range p.Diff.Modules {
    42  		if len(m.Path)-1 <= opts.ModuleDepth || opts.ModuleDepth == -1 {
    43  			formatPlanModuleExpand(buf, m, opts)
    44  		} else {
    45  			formatPlanModuleSingle(buf, m, opts)
    46  		}
    47  	}
    48  
    49  	return strings.TrimSpace(buf.String())
    50  }
    51  
    52  // formatPlanModuleExpand will output the given module and all of its
    53  // resources.
    54  func formatPlanModuleExpand(
    55  	buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) {
    56  	// Ignore empty diffs
    57  	if m.Empty() {
    58  		return
    59  	}
    60  
    61  	var moduleName string
    62  	if !m.IsRoot() {
    63  		moduleName = fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
    64  	}
    65  
    66  	// We want to output the resources in sorted order to make things
    67  	// easier to scan through, so get all the resource names and sort them.
    68  	names := make([]string, 0, len(m.Resources))
    69  	for name, _ := range m.Resources {
    70  		names = append(names, name)
    71  	}
    72  	sort.Strings(names)
    73  
    74  	// Go through each sorted name and start building the output
    75  	for _, name := range names {
    76  		rdiff := m.Resources[name]
    77  		if rdiff.Empty() {
    78  			continue
    79  		}
    80  
    81  		if moduleName != "" {
    82  			name = moduleName + "." + name
    83  		}
    84  
    85  		// Determine the color for the text (green for adding, yellow
    86  		// for change, red for delete), and symbol, and output the
    87  		// resource header.
    88  		color := "yellow"
    89  		symbol := "~"
    90  		oldValues := true
    91  		switch rdiff.ChangeType() {
    92  		case terraform.DiffDestroyCreate:
    93  			color = "green"
    94  			symbol = "-/+"
    95  		case terraform.DiffCreate:
    96  			color = "green"
    97  			symbol = "+"
    98  			oldValues = false
    99  
   100  			// If we're "creating" a data resource then we'll present it
   101  			// to the user as a "read" operation, so it's clear that this
   102  			// operation won't change anything outside of the Terraform state.
   103  			// Unfortunately by the time we get here we only have the name
   104  			// to work with, so we need to cheat and exploit knowledge of the
   105  			// naming scheme for data resources.
   106  			if strings.HasPrefix(name, "data.") {
   107  				symbol = "<="
   108  				color = "cyan"
   109  			}
   110  		case terraform.DiffDestroy:
   111  			color = "red"
   112  			symbol = "-"
   113  		}
   114  
   115  		buf.WriteString(opts.Color.Color(fmt.Sprintf(
   116  			"[%s]%s %s\n",
   117  			color, symbol, name)))
   118  
   119  		// Get all the attributes that are changing, and sort them. Also
   120  		// determine the longest key so that we can align them all.
   121  		keyLen := 0
   122  		keys := make([]string, 0, len(rdiff.Attributes))
   123  		for key, _ := range rdiff.Attributes {
   124  			// Skip the ID since we do that specially
   125  			if key == "id" {
   126  				continue
   127  			}
   128  
   129  			keys = append(keys, key)
   130  			if len(key) > keyLen {
   131  				keyLen = len(key)
   132  			}
   133  		}
   134  		sort.Strings(keys)
   135  
   136  		// Go through and output each attribute
   137  		for _, attrK := range keys {
   138  			attrDiff := rdiff.Attributes[attrK]
   139  
   140  			v := attrDiff.New
   141  			if attrDiff.NewComputed {
   142  				v = "<computed>"
   143  			}
   144  
   145  			newResource := ""
   146  			if attrDiff.RequiresNew && rdiff.Destroy {
   147  				newResource = opts.Color.Color(" [red](forces new resource)")
   148  			}
   149  
   150  			if oldValues {
   151  				buf.WriteString(fmt.Sprintf(
   152  					"    %s:%s %#v => %#v%s\n",
   153  					attrK,
   154  					strings.Repeat(" ", keyLen-len(attrK)),
   155  					attrDiff.Old,
   156  					v,
   157  					newResource))
   158  			} else {
   159  				buf.WriteString(fmt.Sprintf(
   160  					"    %s:%s %#v%s\n",
   161  					attrK,
   162  					strings.Repeat(" ", keyLen-len(attrK)),
   163  					v,
   164  					newResource))
   165  			}
   166  		}
   167  
   168  		// Write the reset color so we don't overload the user's terminal
   169  		buf.WriteString(opts.Color.Color("[reset]\n"))
   170  	}
   171  }
   172  
   173  // formatPlanModuleSingle will output the given module and all of its
   174  // resources.
   175  func formatPlanModuleSingle(
   176  	buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) {
   177  	// Ignore empty diffs
   178  	if m.Empty() {
   179  		return
   180  	}
   181  
   182  	moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
   183  
   184  	// Determine the color for the text (green for adding, yellow
   185  	// for change, red for delete), and symbol, and output the
   186  	// resource header.
   187  	color := "yellow"
   188  	symbol := "~"
   189  	switch m.ChangeType() {
   190  	case terraform.DiffCreate:
   191  		color = "green"
   192  		symbol = "+"
   193  	case terraform.DiffDestroy:
   194  		color = "red"
   195  		symbol = "-"
   196  	}
   197  
   198  	buf.WriteString(opts.Color.Color(fmt.Sprintf(
   199  		"[%s]%s %s\n",
   200  		color, symbol, moduleName)))
   201  	buf.WriteString(fmt.Sprintf(
   202  		"    %d resource(s)",
   203  		len(m.Resources)))
   204  	buf.WriteString(opts.Color.Color("[reset]\n"))
   205  }