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