github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/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 }