github.com/jdextraze/terraform@v0.6.17-0.20160511153921-e33847c8a8af/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  		switch rdiff.ChangeType() {
    91  		case terraform.DiffDestroyCreate:
    92  			color = "green"
    93  			symbol = "-/+"
    94  		case terraform.DiffCreate:
    95  			color = "green"
    96  			symbol = "+"
    97  		case terraform.DiffDestroy:
    98  			color = "red"
    99  			symbol = "-"
   100  		}
   101  
   102  		buf.WriteString(opts.Color.Color(fmt.Sprintf(
   103  			"[%s]%s %s\n",
   104  			color, symbol, name)))
   105  
   106  		// Get all the attributes that are changing, and sort them. Also
   107  		// determine the longest key so that we can align them all.
   108  		keyLen := 0
   109  		keys := make([]string, 0, len(rdiff.Attributes))
   110  		for key, _ := range rdiff.Attributes {
   111  			// Skip the ID since we do that specially
   112  			if key == "id" {
   113  				continue
   114  			}
   115  
   116  			keys = append(keys, key)
   117  			if len(key) > keyLen {
   118  				keyLen = len(key)
   119  			}
   120  		}
   121  		sort.Strings(keys)
   122  
   123  		// Go through and output each attribute
   124  		for _, attrK := range keys {
   125  			attrDiff := rdiff.Attributes[attrK]
   126  
   127  			v := attrDiff.New
   128  			if attrDiff.NewComputed {
   129  				v = "<computed>"
   130  			}
   131  
   132  			newResource := ""
   133  			if attrDiff.RequiresNew && rdiff.Destroy {
   134  				newResource = opts.Color.Color(" [red](forces new resource)")
   135  			}
   136  
   137  			buf.WriteString(fmt.Sprintf(
   138  				"    %s:%s %#v => %#v%s\n",
   139  				attrK,
   140  				strings.Repeat(" ", keyLen-len(attrK)),
   141  				attrDiff.Old,
   142  				v,
   143  				newResource))
   144  		}
   145  
   146  		// Write the reset color so we don't overload the user's terminal
   147  		buf.WriteString(opts.Color.Color("[reset]\n"))
   148  	}
   149  }
   150  
   151  // formatPlanModuleSingle will output the given module and all of its
   152  // resources.
   153  func formatPlanModuleSingle(
   154  	buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) {
   155  	// Ignore empty diffs
   156  	if m.Empty() {
   157  		return
   158  	}
   159  
   160  	moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], "."))
   161  
   162  	// Determine the color for the text (green for adding, yellow
   163  	// for change, red for delete), and symbol, and output the
   164  	// resource header.
   165  	color := "yellow"
   166  	symbol := "~"
   167  	switch m.ChangeType() {
   168  	case terraform.DiffCreate:
   169  		color = "green"
   170  		symbol = "+"
   171  	case terraform.DiffDestroy:
   172  		color = "red"
   173  		symbol = "-"
   174  	}
   175  
   176  	buf.WriteString(opts.Color.Color(fmt.Sprintf(
   177  		"[%s]%s %s\n",
   178  		color, symbol, moduleName)))
   179  	buf.WriteString(fmt.Sprintf(
   180  		"    %d resource(s)",
   181  		len(m.Resources)))
   182  	buf.WriteString(opts.Color.Color("[reset]\n"))
   183  }