10 "github.com/mitchellh/colorstring"
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/plans"
14 "github.com/hashicorp/terraform/states"
15 "github.com/hashicorp/terraform/terraform"
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.
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).
25 Resources []*InstanceDiff
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
34 // Attributes describes changes to the attributes of the instance.
36 // For destroy diffs this is always nil.
37 Attributes []*AttributeDiff
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.
60 // PlanStats gives summary counts for a Plan.
61 type PlanStats struct {
62 ToAdd, ToChange, ToDestroy int
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)
74 for _, rc := range changes.Resources {
76 log.Printf("[TRACE] NewPlan found %s (%s)", addr, rc.Action)
77 dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode
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 {
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.
90 Addr: terraform.NewLegacyResourceInstanceAddress(addr),
94 if rc.DeposedKey != states.NotDeposed {
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.
104 ret.Resources = append(ret.Resources, did)
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)
117 // Format produces and returns a text representation of the receiving plan
118 // intended for display in a terminal.
120 // If color is not nil, it is used to colorize the output.
121 func (p *Plan) Format(color *colorstring.Colorize) string {
123 return "This plan does nothing."
127 color = &colorstring.Colorize{
128 Colors: colorstring.DefaultColors,
133 // Find the longest path length of all the paths that are changing,
134 // so we can align them all.
136 for _, r := range p.Resources {
137 for _, attr := range r.Attributes {
140 if len(key) > keyLen {
146 buf := new(bytes.Buffer)
147 for _, r := range p.Resources {
148 formatPlanInstanceDiff(buf, r, keyLen, color)
151 return strings.TrimSpace(buf.String())
154 // Stats returns statistics about the plan
155 func (p *Plan) Stats() PlanStats {
157 for _, r := range p.Resources {
163 case plans.DeleteThenCreate, plans.CreateThenDelete:
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 {
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
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 {
193 case plans.DeleteThenCreate:
194 return "[red]-[reset]/[green]+[reset]"
195 case plans.CreateThenDelete:
196 return "[green]+[reset]/[red]-[reset]"
198 return " [green]+[reset]"
200 return " [red]-[reset]"
202 return " [cyan]<=[reset]"
204 return " [yellow]~[reset]"
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()
215 // Determine the color for the text (green for adding, yellow
216 // for change, red for delete), and symbol, and output the
219 symbol := DiffActionSymbol(r.Action)
222 case plans.DeleteThenCreate, plans.CreateThenDelete:
236 extraStr = extraStr + " (tainted)"
239 extraStr = extraStr + " (deposed)"
241 if r.Action.IsReplace() {
242 extraStr = extraStr + colorizer.Color(" [red][bold](new resource required)")
246 colorizer.Color(fmt.Sprintf(
248 color, symbol, color, addrStr, extraStr,
252 for _, attr := range r.Attributes {
257 case v == "" && attr.NewComputed:
260 dispV = "<sensitive>"
262 dispV = fmt.Sprintf("%q", v)
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)")
278 dispU = "<sensitive>"
280 dispU = fmt.Sprintf("%q", u)
282 buf.WriteString(fmt.Sprintf(
283 " %s:%s %s => %s%s\n",
285 strings.Repeat(" ", keyLen-len(attr.Path)),
290 buf.WriteString(fmt.Sprintf(
293 strings.Repeat(" ", keyLen-len(attr.Path)),
300 // Write the reset color so we don't bleed color into later text
301 buf.WriteString(colorizer.Color("[reset]\n"))