]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/command/format/plan.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / command / format / plan.go
1 package format
2
3 import (
4 "bytes"
5 "fmt"
6 "log"
7 "sort"
8 "strings"
9
10 "github.com/mitchellh/colorstring"
11
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/plans"
14 "github.com/hashicorp/terraform/states"
15 "github.com/hashicorp/terraform/terraform"
16 )
17
18 // Plan is a representation of a plan optimized for display to
19 // an end-user, as opposed to terraform.Plan which is for internal use.
20 //
21 // DisplayPlan excludes implementation details that may otherwise appear
22 // in the main plan, such as destroy actions on data sources (which are
23 // there only to clean up the state).
24 type Plan struct {
25 Resources []*InstanceDiff
26 }
27
28 // InstanceDiff is a representation of an instance diff optimized
29 // for display, in conjunction with DisplayPlan.
30 type InstanceDiff struct {
31 Addr *terraform.ResourceAddress
32 Action plans.Action
33
34 // Attributes describes changes to the attributes of the instance.
35 //
36 // For destroy diffs this is always nil.
37 Attributes []*AttributeDiff
38
39 Tainted bool
40 Deposed bool
41 }
42
43 // AttributeDiff is a representation of an attribute diff optimized
44 // for display, in conjunction with DisplayInstanceDiff.
45 type AttributeDiff struct {
46 // Path is a dot-delimited traversal through possibly many levels of list and map structure,
47 // intended for display purposes only.
48 Path string
49
50 Action plans.Action
51
52 OldValue string
53 NewValue string
54
55 NewComputed bool
56 Sensitive bool
57 ForcesNew bool
58 }
59
60 // PlanStats gives summary counts for a Plan.
61 type PlanStats struct {
62 ToAdd, ToChange, ToDestroy int
63 }
64
65 // NewPlan produces a display-oriented Plan from a terraform.Plan.
66 func NewPlan(changes *plans.Changes) *Plan {
67 log.Printf("[TRACE] NewPlan for %#v", changes)
68 ret := &Plan{}
69 if changes == nil {
70 // Nothing to do!
71 return ret
72 }
73
74 for _, rc := range changes.Resources {
75 addr := rc.Addr
76 log.Printf("[TRACE] NewPlan found %s (%s)", addr, rc.Action)
77 dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
78
79 // We create "delete" actions for data resources so we can clean
80 // up their entries in state, but this is an implementation detail
81 // that users shouldn't see.
82 if dataSource && rc.Action == plans.Delete {
83 continue
84 }
85
86 // For now we'll shim this to work with our old types.
87 // TODO: Update for the new plan types, ideally also switching over to
88 // a structural diff renderer instead of a flat renderer.
89 did := &InstanceDiff{
90 Addr: terraform.NewLegacyResourceInstanceAddress(addr),
91 Action: rc.Action,
92 }
93
94 if rc.DeposedKey != states.NotDeposed {
95 did.Deposed = true
96 }
97
98 // Since this is just a temporary stub implementation on the way
99 // to us replacing this with the structural diff renderer, we currently
100 // don't include any attributes here.
101 // FIXME: Implement the structural diff renderer to replace this
102 // codepath altogether.
103
104 ret.Resources = append(ret.Resources, did)
105 }
106
107 // Sort the instance diffs by their addresses for display.
108 sort.Slice(ret.Resources, func(i, j int) bool {
109 iAddr := ret.Resources[i].Addr
110 jAddr := ret.Resources[j].Addr
111 return iAddr.Less(jAddr)
112 })
113
114 return ret
115 }
116
117 // Format produces and returns a text representation of the receiving plan
118 // intended for display in a terminal.
119 //
120 // If color is not nil, it is used to colorize the output.
121 func (p *Plan) Format(color *colorstring.Colorize) string {
122 if p.Empty() {
123 return "This plan does nothing."
124 }
125
126 if color == nil {
127 color = &colorstring.Colorize{
128 Colors: colorstring.DefaultColors,
129 Reset: false,
130 }
131 }
132
133 // Find the longest path length of all the paths that are changing,
134 // so we can align them all.
135 keyLen := 0
136 for _, r := range p.Resources {
137 for _, attr := range r.Attributes {
138 key := attr.Path
139
140 if len(key) > keyLen {
141 keyLen = len(key)
142 }
143 }
144 }
145
146 buf := new(bytes.Buffer)
147 for _, r := range p.Resources {
148 formatPlanInstanceDiff(buf, r, keyLen, color)
149 }
150
151 return strings.TrimSpace(buf.String())
152 }
153
154 // Stats returns statistics about the plan
155 func (p *Plan) Stats() PlanStats {
156 var ret PlanStats
157 for _, r := range p.Resources {
158 switch r.Action {
159 case plans.Create:
160 ret.ToAdd++
161 case plans.Update:
162 ret.ToChange++
163 case plans.DeleteThenCreate, plans.CreateThenDelete:
164 ret.ToAdd++
165 ret.ToDestroy++
166 case plans.Delete:
167 ret.ToDestroy++
168 }
169 }
170 return ret
171 }
172
173 // ActionCounts returns the number of diffs for each action type
174 func (p *Plan) ActionCounts() map[plans.Action]int {
175 ret := map[plans.Action]int{}
176 for _, r := range p.Resources {
177 ret[r.Action]++
178 }
179 return ret
180 }
181
182 // Empty returns true if there is at least one resource diff in the receiving plan.
183 func (p *Plan) Empty() bool {
184 return len(p.Resources) == 0
185 }
186
187 // DiffActionSymbol returns a string that, once passed through a
188 // colorstring.Colorize, will produce a result that can be written
189 // to a terminal to produce a symbol made of three printable
190 // characters, possibly interspersed with VT100 color codes.
191 func DiffActionSymbol(action plans.Action) string {
192 switch action {
193 case plans.DeleteThenCreate:
194 return "[red]-[reset]/[green]+[reset]"
195 case plans.CreateThenDelete:
196 return "[green]+[reset]/[red]-[reset]"
197 case plans.Create:
198 return " [green]+[reset]"
199 case plans.Delete:
200 return " [red]-[reset]"
201 case plans.Read:
202 return " [cyan]<=[reset]"
203 case plans.Update:
204 return " [yellow]~[reset]"
205 default:
206 return " ?"
207 }
208 }
209
210 // formatPlanInstanceDiff writes the text representation of the given instance diff
211 // to the given buffer, using the given colorizer.
212 func formatPlanInstanceDiff(buf *bytes.Buffer, r *InstanceDiff, keyLen int, colorizer *colorstring.Colorize) {
213 addrStr := r.Addr.String()
214
215 // Determine the color for the text (green for adding, yellow
216 // for change, red for delete), and symbol, and output the
217 // resource header.
218 color := "yellow"
219 symbol := DiffActionSymbol(r.Action)
220 oldValues := true
221 switch r.Action {
222 case plans.DeleteThenCreate, plans.CreateThenDelete:
223 color = "yellow"
224 case plans.Create:
225 color = "green"
226 oldValues = false
227 case plans.Delete:
228 color = "red"
229 case plans.Read:
230 color = "cyan"
231 oldValues = false
232 }
233
234 var extraStr string
235 if r.Tainted {
236 extraStr = extraStr + " (tainted)"
237 }
238 if r.Deposed {
239 extraStr = extraStr + " (deposed)"
240 }
241 if r.Action.IsReplace() {
242 extraStr = extraStr + colorizer.Color(" [red][bold](new resource required)")
243 }
244
245 buf.WriteString(
246 colorizer.Color(fmt.Sprintf(
247 "[%s]%s [%s]%s%s\n",
248 color, symbol, color, addrStr, extraStr,
249 )),
250 )
251
252 for _, attr := range r.Attributes {
253
254 v := attr.NewValue
255 var dispV string
256 switch {
257 case v == "" && attr.NewComputed:
258 dispV = "<computed>"
259 case attr.Sensitive:
260 dispV = "<sensitive>"
261 default:
262 dispV = fmt.Sprintf("%q", v)
263 }
264
265 updateMsg := ""
266 switch {
267 case attr.ForcesNew && r.Action.IsReplace():
268 updateMsg = colorizer.Color(" [red](forces new resource)")
269 case attr.Sensitive && oldValues:
270 updateMsg = colorizer.Color(" [yellow](attribute changed)")
271 }
272
273 if oldValues {
274 u := attr.OldValue
275 var dispU string
276 switch {
277 case attr.Sensitive:
278 dispU = "<sensitive>"
279 default:
280 dispU = fmt.Sprintf("%q", u)
281 }
282 buf.WriteString(fmt.Sprintf(
283 " %s:%s %s => %s%s\n",
284 attr.Path,
285 strings.Repeat(" ", keyLen-len(attr.Path)),
286 dispU, dispV,
287 updateMsg,
288 ))
289 } else {
290 buf.WriteString(fmt.Sprintf(
291 " %s:%s %s%s\n",
292 attr.Path,
293 strings.Repeat(" ", keyLen-len(attr.Path)),
294 dispV,
295 updateMsg,
296 ))
297 }
298 }
299
300 // Write the reset color so we don't bleed color into later text
301 buf.WriteString(colorizer.Color("[reset]\n"))
302 }