github.com/candidpartners/terraform@v0.9.5-0.20171005231213-29f5f88820f6/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/terraform" 10 "github.com/mitchellh/colorstring" 11 ) 12 13 // PlanOpts are the options for formatting a plan. 14 type PlanOpts 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 // Plan takes a plan and returns a 27 func Plan(opts *PlanOpts) 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 *PlanOpts) { 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 var extraAttr []string 118 if rdiff.DestroyTainted { 119 extraAttr = append(extraAttr, "tainted") 120 } 121 if rdiff.DestroyDeposed { 122 extraAttr = append(extraAttr, "deposed") 123 } 124 var extraStr string 125 if len(extraAttr) > 0 { 126 extraStr = fmt.Sprintf(" (%s)", strings.Join(extraAttr, ", ")) 127 } 128 129 buf.WriteString(opts.Color.Color(fmt.Sprintf( 130 "[%s]%s %s%s\n", 131 color, symbol, name, extraStr))) 132 133 // Get all the attributes that are changing, and sort them. Also 134 // determine the longest key so that we can align them all. 135 keyLen := 0 136 keys := make([]string, 0, len(rdiff.Attributes)) 137 for key, _ := range rdiff.Attributes { 138 // Skip the ID since we do that specially 139 if key == "id" { 140 continue 141 } 142 143 keys = append(keys, key) 144 if len(key) > keyLen { 145 keyLen = len(key) 146 } 147 } 148 sort.Strings(keys) 149 150 // Go through and output each attribute 151 for _, attrK := range keys { 152 attrDiff := rdiff.Attributes[attrK] 153 154 v := attrDiff.New 155 if v == "" && attrDiff.NewComputed { 156 v = "<computed>" 157 } 158 159 if attrDiff.Sensitive { 160 v = "<sensitive>" 161 } 162 163 updateMsg := "" 164 if attrDiff.RequiresNew && rdiff.Destroy { 165 updateMsg = opts.Color.Color(" [red](forces new resource)") 166 } else if attrDiff.Sensitive && oldValues { 167 updateMsg = opts.Color.Color(" [yellow](attribute changed)") 168 } 169 170 if oldValues { 171 var u string 172 if attrDiff.Sensitive { 173 u = "<sensitive>" 174 } else { 175 u = attrDiff.Old 176 } 177 buf.WriteString(fmt.Sprintf( 178 " %s:%s %#v => %#v%s\n", 179 attrK, 180 strings.Repeat(" ", keyLen-len(attrK)), 181 u, 182 v, 183 updateMsg)) 184 } else { 185 buf.WriteString(fmt.Sprintf( 186 " %s:%s %#v%s\n", 187 attrK, 188 strings.Repeat(" ", keyLen-len(attrK)), 189 v, 190 updateMsg)) 191 } 192 } 193 194 // Write the reset color so we don't overload the user's terminal 195 buf.WriteString(opts.Color.Color("[reset]\n")) 196 } 197 } 198 199 // formatPlanModuleSingle will output the given module and all of its 200 // resources. 201 func formatPlanModuleSingle( 202 buf *bytes.Buffer, m *terraform.ModuleDiff, opts *PlanOpts) { 203 // Ignore empty diffs 204 if m.Empty() { 205 return 206 } 207 208 moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], ".")) 209 210 // Determine the color for the text (green for adding, yellow 211 // for change, red for delete), and symbol, and output the 212 // resource header. 213 color := "yellow" 214 symbol := "~" 215 switch m.ChangeType() { 216 case terraform.DiffCreate: 217 color = "green" 218 symbol = "+" 219 case terraform.DiffDestroy: 220 color = "red" 221 symbol = "-" 222 } 223 224 buf.WriteString(opts.Color.Color(fmt.Sprintf( 225 "[%s]%s %s\n", 226 color, symbol, moduleName))) 227 buf.WriteString(fmt.Sprintf( 228 " %d resource(s)", 229 len(m.Resources))) 230 buf.WriteString(opts.Color.Color("[reset]\n")) 231 }