github.com/aznashwan/terraform@v0.4.3-0.20151118032030-21f93ca4558d/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 }