github.com/miquella/terraform@v0.6.17-0.20160517195040-40db82f25ec0/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 oldValues := true 91 switch rdiff.ChangeType() { 92 case terraform.DiffDestroyCreate: 93 color = "green" 94 symbol = "-/+" 95 case terraform.DiffCreate: 96 color = "green" 97 symbol = "+" 98 oldValues = false 99 100 // If we're "creating" a data resource then we'll present it 101 // to the user as a "read" operation, so it's clear that this 102 // operation won't change anything outside of the Terraform state. 103 // Unfortunately by the time we get here we only have the name 104 // to work with, so we need to cheat and exploit knowledge of the 105 // naming scheme for data resources. 106 if strings.HasPrefix(name, "data.") { 107 symbol = "<=" 108 color = "cyan" 109 } 110 case terraform.DiffDestroy: 111 color = "red" 112 symbol = "-" 113 } 114 115 buf.WriteString(opts.Color.Color(fmt.Sprintf( 116 "[%s]%s %s\n", 117 color, symbol, name))) 118 119 // Get all the attributes that are changing, and sort them. Also 120 // determine the longest key so that we can align them all. 121 keyLen := 0 122 keys := make([]string, 0, len(rdiff.Attributes)) 123 for key, _ := range rdiff.Attributes { 124 // Skip the ID since we do that specially 125 if key == "id" { 126 continue 127 } 128 129 keys = append(keys, key) 130 if len(key) > keyLen { 131 keyLen = len(key) 132 } 133 } 134 sort.Strings(keys) 135 136 // Go through and output each attribute 137 for _, attrK := range keys { 138 attrDiff := rdiff.Attributes[attrK] 139 140 v := attrDiff.New 141 if attrDiff.NewComputed { 142 v = "<computed>" 143 } 144 145 newResource := "" 146 if attrDiff.RequiresNew && rdiff.Destroy { 147 newResource = opts.Color.Color(" [red](forces new resource)") 148 } 149 150 if oldValues { 151 buf.WriteString(fmt.Sprintf( 152 " %s:%s %#v => %#v%s\n", 153 attrK, 154 strings.Repeat(" ", keyLen-len(attrK)), 155 attrDiff.Old, 156 v, 157 newResource)) 158 } else { 159 buf.WriteString(fmt.Sprintf( 160 " %s:%s %#v%s\n", 161 attrK, 162 strings.Repeat(" ", keyLen-len(attrK)), 163 v, 164 newResource)) 165 } 166 } 167 168 // Write the reset color so we don't overload the user's terminal 169 buf.WriteString(opts.Color.Color("[reset]\n")) 170 } 171 } 172 173 // formatPlanModuleSingle will output the given module and all of its 174 // resources. 175 func formatPlanModuleSingle( 176 buf *bytes.Buffer, m *terraform.ModuleDiff, opts *FormatPlanOpts) { 177 // Ignore empty diffs 178 if m.Empty() { 179 return 180 } 181 182 moduleName := fmt.Sprintf("module.%s", strings.Join(m.Path[1:], ".")) 183 184 // Determine the color for the text (green for adding, yellow 185 // for change, red for delete), and symbol, and output the 186 // resource header. 187 color := "yellow" 188 symbol := "~" 189 switch m.ChangeType() { 190 case terraform.DiffCreate: 191 color = "green" 192 symbol = "+" 193 case terraform.DiffDestroy: 194 color = "red" 195 symbol = "-" 196 } 197 198 buf.WriteString(opts.Color.Color(fmt.Sprintf( 199 "[%s]%s %s\n", 200 color, symbol, moduleName))) 201 buf.WriteString(fmt.Sprintf( 202 " %d resource(s)", 203 len(m.Resources))) 204 buf.WriteString(opts.Color.Color("[reset]\n")) 205 }