github.com/opsidian/terraform@v0.7.8-0.20161104123224-27c39cdfba5b/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  		dataSource := strings.HasPrefix(name, "data.")
    82  
    83  		if moduleName != "" {
    84  			name = moduleName + "." + name
    85  		}
    86  
    87  		// Determine the color for the text (green for adding, yellow
    88  		// for change, red for delete), and symbol, and output the
    89  		// resource header.
    90  		color := "yellow"
    91  		symbol := "~"
    92  		oldValues := true
    93  		switch rdiff.ChangeType() {
    94  		case terraform.DiffDestroyCreate:
    95  			color = "green"
    96  			symbol = "-/+"
    97  		case terraform.DiffCreate:
    98  			color = "green"
    99  			symbol = "+"
   100  			oldValues = false
   101  
   102  			// If we're "creating" a data resource then we'll present it
   103  			// to the user as a "read" operation, so it's clear that this
   104  			// operation won't change anything outside of the Terraform state.
   105  			// Unfortunately by the time we get here we only have the name
   106  			// to work with, so we need to cheat and exploit knowledge of the
   107  			// naming scheme for data resources.
   108  			if dataSource {
   109  				symbol = "<="
   110  				color = "cyan"
   111  			}
   112  		case terraform.DiffDestroy:
   113  			color = "red"
   114  			symbol = "-"
   115  		}
   116  
   117  		taintStr := ""
   118  		if rdiff.DestroyTainted {
   119  			taintStr = " (tainted)"
   120  		}
   121  
   122  		buf.WriteString(opts.Color.Color(fmt.Sprintf(
   123  			"[%s]%s %s%s\n",
   124  			color, symbol, name, taintStr)))
   125  
   126  		// Get all the attributes that are changing, and sort them. Also
   127  		// determine the longest key so that we can align them all.
   128  		keyLen := 0
   129  		keys := make([]string, 0, len(rdiff.Attributes))
   130  		for key, _ := range rdiff.Attributes {
   131  			// Skip the ID since we do that specially
   132  			if key == "id" {
   133  				continue
   134  			}
   135  
   136  			keys = append(keys, key)
   137  			if len(key) > keyLen {
   138  				keyLen = len(key)
   139  			}
   140  		}
   141  		sort.Strings(keys)
   142  
   143  		// Go through and output each attribute
   144  		for _, attrK := range keys {
   145  			attrDiff := rdiff.Attributes[attrK]
   146  
   147  			v := attrDiff.New
   148  			if attrDiff.NewComputed {
   149  				v = "<computed>"
   150  			}
   151  
   152  			if attrDiff.Sensitive {
   153  				v = "<sensitive>"
   154  			}
   155  
   156  			updateMsg := ""
   157  			if attrDiff.RequiresNew && rdiff.Destroy {
   158  				updateMsg = opts.Color.Color(" [red](forces new resource)")
   159  			} else if attrDiff.Sensitive && oldValues {
   160  				updateMsg = opts.Color.Color(" [yellow](attribute changed)")
   161  			}
   162  
   163  			if oldValues {
   164  				var u string
   165  				if attrDiff.Sensitive {
   166  					u = "<sensitive>"
   167  				} else {
   168  					u = attrDiff.Old
   169  				}
   170  				buf.WriteString(fmt.Sprintf(
   171  					"    %s:%s %#v => %#v%s\n",
   172  					attrK,
   173  					strings.Repeat(" ", keyLen-len(attrK)),
   174  					u,
   175  					v,
   176  					updateMsg))
   177  			} else {
   178  				buf.WriteString(fmt.Sprintf(
   179  					"    %s:%s %#v%s\n",
   180  					attrK,
   181  					strings.Repeat(" ", keyLen-len(attrK)),
   182  					v,
   183  					updateMsg))
   184  			}
   185  		}
   186  
   187  		// Write the reset color so we don't overload the user's terminal
   188  		buf.WriteString(opts.Color.Color("[reset]\n"))
   189  	}
   190  }
   191  
   192  // formatPlanModuleSingle will output the given module and all of its
   193  // resources.
   194  func formatPlanModuleSingle(
   195  	buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) {
   196  	// Ignore empty diffs
   197  	if m.Empty() {
   198  		return
   199  	}
   200  
   201  	moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
   202  
   203  	// Determine the color for the text (green for adding, yellow
   204  	// for change, red for delete), and symbol, and output the
   205  	// resource header.
   206  	color := "yellow"
   207  	symbol := "~"
   208  	switch m.ChangeType() {
   209  	case terraform.DiffCreate:
   210  		color = "green"
   211  		symbol = "+"
   212  	case terraform.DiffDestroy:
   213  		color = "red"
   214  		symbol = "-"
   215  	}
   216  
   217  	buf.WriteString(opts.Color.Color(fmt.Sprintf(
   218  		"[%s]%s %s\n",
   219  		color, symbol, moduleName)))
   220  	buf.WriteString(fmt.Sprintf(
   221  		"    %d resource(s)",
   222  		len(m.Resources)))
   223  	buf.WriteString(opts.Color.Color("[reset]\n"))
   224  }