aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/command
diff options
context:
space:
mode:
authorNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
committerNathan Dench <ndenc2@gmail.com>2019-05-24 15:16:44 +1000
commit107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch)
treeca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/command
parent844b5a68d8af4791755b8f0ad293cc99f5959183 (diff)
downloadterraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.gz
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.tar.zst
terraform-provider-statuscake-107c1cdb09c575aa2f61d97f48d8587eb6bada4c.zip
Upgrade to 0.12
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/command')
-rw-r--r--vendor/github.com/hashicorp/terraform/command/format/diagnostic.go295
-rw-r--r--vendor/github.com/hashicorp/terraform/command/format/diff.go1192
-rw-r--r--vendor/github.com/hashicorp/terraform/command/format/format.go8
-rw-r--r--vendor/github.com/hashicorp/terraform/command/format/object_id.go123
-rw-r--r--vendor/github.com/hashicorp/terraform/command/format/plan.go302
-rw-r--r--vendor/github.com/hashicorp/terraform/command/format/state.go286
6 files changed, 2206 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/command/format/diagnostic.go b/vendor/github.com/hashicorp/terraform/command/format/diagnostic.go
new file mode 100644
index 0000000..3dd9238
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/command/format/diagnostic.go
@@ -0,0 +1,295 @@
1package format
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "sort"
8 "strings"
9
10 "github.com/hashicorp/hcl2/hcl"
11 "github.com/hashicorp/hcl2/hcled"
12 "github.com/hashicorp/hcl2/hclparse"
13 "github.com/hashicorp/terraform/tfdiags"
14 "github.com/mitchellh/colorstring"
15 wordwrap "github.com/mitchellh/go-wordwrap"
16 "github.com/zclconf/go-cty/cty"
17)
18
19// Diagnostic formats a single diagnostic message.
20//
21// The width argument specifies at what column the diagnostic messages will
22// be wrapped. If set to zero, messages will not be wrapped by this function
23// at all. Although the long-form text parts of the message are wrapped,
24// not all aspects of the message are guaranteed to fit within the specified
25// terminal width.
26func Diagnostic(diag tfdiags.Diagnostic, sources map[string][]byte, color *colorstring.Colorize, width int) string {
27 if diag == nil {
28 // No good reason to pass a nil diagnostic in here...
29 return ""
30 }
31
32 var buf bytes.Buffer
33
34 switch diag.Severity() {
35 case tfdiags.Error:
36 buf.WriteString(color.Color("\n[bold][red]Error: [reset]"))
37 case tfdiags.Warning:
38 buf.WriteString(color.Color("\n[bold][yellow]Warning: [reset]"))
39 default:
40 // Clear out any coloring that might be applied by Terraform's UI helper,
41 // so our result is not context-sensitive.
42 buf.WriteString(color.Color("\n[reset]"))
43 }
44
45 desc := diag.Description()
46 sourceRefs := diag.Source()
47
48 // We don't wrap the summary, since we expect it to be terse, and since
49 // this is where we put the text of a native Go error it may not always
50 // be pure text that lends itself well to word-wrapping.
51 fmt.Fprintf(&buf, color.Color("[bold]%s[reset]\n\n"), desc.Summary)
52
53 if sourceRefs.Subject != nil {
54 // We'll borrow HCL's range implementation here, because it has some
55 // handy features to help us produce a nice source code snippet.
56 highlightRange := sourceRefs.Subject.ToHCL()
57 snippetRange := highlightRange
58 if sourceRefs.Context != nil {
59 snippetRange = sourceRefs.Context.ToHCL()
60 }
61
62 // Make sure the snippet includes the highlight. This should be true
63 // for any reasonable diagnostic, but we'll make sure.
64 snippetRange = hcl.RangeOver(snippetRange, highlightRange)
65 if snippetRange.Empty() {
66 snippetRange.End.Byte++
67 snippetRange.End.Column++
68 }
69 if highlightRange.Empty() {
70 highlightRange.End.Byte++
71 highlightRange.End.Column++
72 }
73
74 var src []byte
75 if sources != nil {
76 src = sources[snippetRange.Filename]
77 }
78 if src == nil {
79 // This should generally not happen, as long as sources are always
80 // loaded through the main loader. We may load things in other
81 // ways in weird cases, so we'll tolerate it at the expense of
82 // a not-so-helpful error message.
83 fmt.Fprintf(&buf, " on %s line %d:\n (source code not available)\n", highlightRange.Filename, highlightRange.Start.Line)
84 } else {
85 file, offset := parseRange(src, highlightRange)
86
87 headerRange := highlightRange
88
89 contextStr := hcled.ContextString(file, offset-1)
90 if contextStr != "" {
91 contextStr = ", in " + contextStr
92 }
93
94 fmt.Fprintf(&buf, " on %s line %d%s:\n", headerRange.Filename, headerRange.Start.Line, contextStr)
95
96 // Config snippet rendering
97 sc := hcl.NewRangeScanner(src, highlightRange.Filename, bufio.ScanLines)
98 for sc.Scan() {
99 lineRange := sc.Range()
100 if !lineRange.Overlaps(snippetRange) {
101 continue
102 }
103 beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(highlightRange)
104 before := beforeRange.SliceBytes(src)
105 highlighted := highlightedRange.SliceBytes(src)
106 after := afterRange.SliceBytes(src)
107 fmt.Fprintf(
108 &buf, color.Color("%4d: %s[underline]%s[reset]%s\n"),
109 lineRange.Start.Line,
110 before, highlighted, after,
111 )
112 }
113
114 }
115
116 if fromExpr := diag.FromExpr(); fromExpr != nil {
117 // We may also be able to generate information about the dynamic
118 // values of relevant variables at the point of evaluation, then.
119 // This is particularly useful for expressions that get evaluated
120 // multiple times with different values, such as blocks using
121 // "count" and "for_each", or within "for" expressions.
122 expr := fromExpr.Expression
123 ctx := fromExpr.EvalContext
124 vars := expr.Variables()
125 stmts := make([]string, 0, len(vars))
126 seen := make(map[string]struct{}, len(vars))
127 Traversals:
128 for _, traversal := range vars {
129 for len(traversal) > 1 {
130 val, diags := traversal.TraverseAbs(ctx)
131 if diags.HasErrors() {
132 // Skip anything that generates errors, since we probably
133 // already have the same error in our diagnostics set
134 // already.
135 traversal = traversal[:len(traversal)-1]
136 continue
137 }
138
139 traversalStr := traversalStr(traversal)
140 if _, exists := seen[traversalStr]; exists {
141 continue Traversals // don't show duplicates when the same variable is referenced multiple times
142 }
143 switch {
144 case !val.IsKnown():
145 // Can't say anything about this yet, then.
146 continue Traversals
147 case val.IsNull():
148 stmts = append(stmts, fmt.Sprintf(color.Color("[bold]%s[reset] is null"), traversalStr))
149 default:
150 stmts = append(stmts, fmt.Sprintf(color.Color("[bold]%s[reset] is %s"), traversalStr, compactValueStr(val)))
151 }
152 seen[traversalStr] = struct{}{}
153 }
154 }
155
156 sort.Strings(stmts) // FIXME: Should maybe use a traversal-aware sort that can sort numeric indexes properly?
157
158 if len(stmts) > 0 {
159 fmt.Fprint(&buf, color.Color(" [dark_gray]|----------------[reset]\n"))
160 }
161 for _, stmt := range stmts {
162 fmt.Fprintf(&buf, color.Color(" [dark_gray]|[reset] %s\n"), stmt)
163 }
164 }
165
166 buf.WriteByte('\n')
167 }
168
169 if desc.Detail != "" {
170 detail := desc.Detail
171 if width != 0 {
172 detail = wordwrap.WrapString(detail, uint(width))
173 }
174 fmt.Fprintf(&buf, "%s\n", detail)
175 }
176
177 return buf.String()
178}
179
180func parseRange(src []byte, rng hcl.Range) (*hcl.File, int) {
181 filename := rng.Filename
182 offset := rng.Start.Byte
183
184 // We need to re-parse here to get a *hcl.File we can interrogate. This
185 // is not awesome since we presumably already parsed the file earlier too,
186 // but this re-parsing is architecturally simpler than retaining all of
187 // the hcl.File objects and we only do this in the case of an error anyway
188 // so the overhead here is not a big problem.
189 parser := hclparse.NewParser()
190 var file *hcl.File
191 var diags hcl.Diagnostics
192 if strings.HasSuffix(filename, ".json") {
193 file, diags = parser.ParseJSON(src, filename)
194 } else {
195 file, diags = parser.ParseHCL(src, filename)
196 }
197 if diags.HasErrors() {
198 return file, offset
199 }
200
201 return file, offset
202}
203
204// traversalStr produces a representation of an HCL traversal that is compact,
205// resembles HCL native syntax, and is suitable for display in the UI.
206func traversalStr(traversal hcl.Traversal) string {
207 // This is a specialized subset of traversal rendering tailored to
208 // producing helpful contextual messages in diagnostics. It is not
209 // comprehensive nor intended to be used for other purposes.
210
211 var buf bytes.Buffer
212 for _, step := range traversal {
213 switch tStep := step.(type) {
214 case hcl.TraverseRoot:
215 buf.WriteString(tStep.Name)
216 case hcl.TraverseAttr:
217 buf.WriteByte('.')
218 buf.WriteString(tStep.Name)
219 case hcl.TraverseIndex:
220 buf.WriteByte('[')
221 if keyTy := tStep.Key.Type(); keyTy.IsPrimitiveType() {
222 buf.WriteString(compactValueStr(tStep.Key))
223 } else {
224 // We'll just use a placeholder for more complex values,
225 // since otherwise our result could grow ridiculously long.
226 buf.WriteString("...")
227 }
228 buf.WriteByte(']')
229 }
230 }
231 return buf.String()
232}
233
234// compactValueStr produces a compact, single-line summary of a given value
235// that is suitable for display in the UI.
236//
237// For primitives it returns a full representation, while for more complex
238// types it instead summarizes the type, size, etc to produce something
239// that is hopefully still somewhat useful but not as verbose as a rendering
240// of the entire data structure.
241func compactValueStr(val cty.Value) string {
242 // This is a specialized subset of value rendering tailored to producing
243 // helpful but concise messages in diagnostics. It is not comprehensive
244 // nor intended to be used for other purposes.
245
246 ty := val.Type()
247 switch {
248 case val.IsNull():
249 return "null"
250 case !val.IsKnown():
251 // Should never happen here because we should filter before we get
252 // in here, but we'll do something reasonable rather than panic.
253 return "(not yet known)"
254 case ty == cty.Bool:
255 if val.True() {
256 return "true"
257 }
258 return "false"
259 case ty == cty.Number:
260 bf := val.AsBigFloat()
261 return bf.Text('g', 10)
262 case ty == cty.String:
263 // Go string syntax is not exactly the same as HCL native string syntax,
264 // but we'll accept the minor edge-cases where this is different here
265 // for now, just to get something reasonable here.
266 return fmt.Sprintf("%q", val.AsString())
267 case ty.IsCollectionType() || ty.IsTupleType():
268 l := val.LengthInt()
269 switch l {
270 case 0:
271 return "empty " + ty.FriendlyName()
272 case 1:
273 return ty.FriendlyName() + " with 1 element"
274 default:
275 return fmt.Sprintf("%s with %d elements", ty.FriendlyName(), l)
276 }
277 case ty.IsObjectType():
278 atys := ty.AttributeTypes()
279 l := len(atys)
280 switch l {
281 case 0:
282 return "object with no attributes"
283 case 1:
284 var name string
285 for k := range atys {
286 name = k
287 }
288 return fmt.Sprintf("object with 1 attribute %q", name)
289 default:
290 return fmt.Sprintf("object with %d attributes", l)
291 }
292 default:
293 return ty.FriendlyName()
294 }
295}
diff --git a/vendor/github.com/hashicorp/terraform/command/format/diff.go b/vendor/github.com/hashicorp/terraform/command/format/diff.go
new file mode 100644
index 0000000..c726f0e
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/command/format/diff.go
@@ -0,0 +1,1192 @@
1package format
2
3import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "sort"
8 "strings"
9
10 "github.com/mitchellh/colorstring"
11 "github.com/zclconf/go-cty/cty"
12 ctyjson "github.com/zclconf/go-cty/cty/json"
13
14 "github.com/hashicorp/terraform/addrs"
15 "github.com/hashicorp/terraform/configs/configschema"
16 "github.com/hashicorp/terraform/plans"
17 "github.com/hashicorp/terraform/plans/objchange"
18 "github.com/hashicorp/terraform/states"
19)
20
21// ResourceChange returns a string representation of a change to a particular
22// resource, for inclusion in user-facing plan output.
23//
24// The resource schema must be provided along with the change so that the
25// formatted change can reflect the configuration structure for the associated
26// resource.
27//
28// If "color" is non-nil, it will be used to color the result. Otherwise,
29// no color codes will be included.
30func ResourceChange(
31 change *plans.ResourceInstanceChangeSrc,
32 tainted bool,
33 schema *configschema.Block,
34 color *colorstring.Colorize,
35) string {
36 addr := change.Addr
37 var buf bytes.Buffer
38
39 if color == nil {
40 color = &colorstring.Colorize{
41 Colors: colorstring.DefaultColors,
42 Disable: true,
43 Reset: false,
44 }
45 }
46
47 dispAddr := addr.String()
48 if change.DeposedKey != states.NotDeposed {
49 dispAddr = fmt.Sprintf("%s (deposed object %s)", dispAddr, change.DeposedKey)
50 }
51
52 switch change.Action {
53 case plans.Create:
54 buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be created", dispAddr)))
55 case plans.Read:
56 buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be read during apply\n # (config refers to values not yet known)", dispAddr)))
57 case plans.Update:
58 buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be updated in-place", dispAddr)))
59 case plans.CreateThenDelete, plans.DeleteThenCreate:
60 if tainted {
61 buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] is tainted, so must be [bold][red]replaced", dispAddr)))
62 } else {
63 buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] must be [bold][red]replaced", dispAddr)))
64 }
65 case plans.Delete:
66 buf.WriteString(color.Color(fmt.Sprintf("[bold] # %s[reset] will be [bold][red]destroyed", dispAddr)))
67 default:
68 // should never happen, since the above is exhaustive
69 buf.WriteString(fmt.Sprintf("%s has an action the plan renderer doesn't support (this is a bug)", dispAddr))
70 }
71 buf.WriteString(color.Color("[reset]\n"))
72
73 switch change.Action {
74 case plans.Create:
75 buf.WriteString(color.Color("[green] +[reset] "))
76 case plans.Read:
77 buf.WriteString(color.Color("[cyan] <=[reset] "))
78 case plans.Update:
79 buf.WriteString(color.Color("[yellow] ~[reset] "))
80 case plans.DeleteThenCreate:
81 buf.WriteString(color.Color("[red]-[reset]/[green]+[reset] "))
82 case plans.CreateThenDelete:
83 buf.WriteString(color.Color("[green]+[reset]/[red]-[reset] "))
84 case plans.Delete:
85 buf.WriteString(color.Color("[red] -[reset] "))
86 default:
87 buf.WriteString(color.Color("??? "))
88 }
89
90 switch addr.Resource.Resource.Mode {
91 case addrs.ManagedResourceMode:
92 buf.WriteString(fmt.Sprintf(
93 "resource %q %q",
94 addr.Resource.Resource.Type,
95 addr.Resource.Resource.Name,
96 ))
97 case addrs.DataResourceMode:
98 buf.WriteString(fmt.Sprintf(
99 "data %q %q ",
100 addr.Resource.Resource.Type,
101 addr.Resource.Resource.Name,
102 ))
103 default:
104 // should never happen, since the above is exhaustive
105 buf.WriteString(addr.String())
106 }
107
108 buf.WriteString(" {")
109
110 p := blockBodyDiffPrinter{
111 buf: &buf,
112 color: color,
113 action: change.Action,
114 requiredReplace: change.RequiredReplace,
115 }
116
117 // Most commonly-used resources have nested blocks that result in us
118 // going at least three traversals deep while we recurse here, so we'll
119 // start with that much capacity and then grow as needed for deeper
120 // structures.
121 path := make(cty.Path, 0, 3)
122
123 changeV, err := change.Decode(schema.ImpliedType())
124 if err != nil {
125 // Should never happen in here, since we've already been through
126 // loads of layers of encode/decode of the planned changes before now.
127 panic(fmt.Sprintf("failed to decode plan for %s while rendering diff: %s", addr, err))
128 }
129
130 // We currently have an opt-out that permits the legacy SDK to return values
131 // that defy our usual conventions around handling of nesting blocks. To
132 // avoid the rendering code from needing to handle all of these, we'll
133 // normalize first.
134 // (Ideally we'd do this as part of the SDK opt-out implementation in core,
135 // but we've added it here for now to reduce risk of unexpected impacts
136 // on other code in core.)
137 changeV.Change.Before = objchange.NormalizeObjectFromLegacySDK(changeV.Change.Before, schema)
138 changeV.Change.After = objchange.NormalizeObjectFromLegacySDK(changeV.Change.After, schema)
139
140 bodyWritten := p.writeBlockBodyDiff(schema, changeV.Before, changeV.After, 6, path)
141 if bodyWritten {
142 buf.WriteString("\n")
143 buf.WriteString(strings.Repeat(" ", 4))
144 }
145 buf.WriteString("}\n")
146
147 return buf.String()
148}
149
150type blockBodyDiffPrinter struct {
151 buf *bytes.Buffer
152 color *colorstring.Colorize
153 action plans.Action
154 requiredReplace cty.PathSet
155}
156
157const forcesNewResourceCaption = " [red]# forces replacement[reset]"
158
159// writeBlockBodyDiff writes attribute or block differences
160// and returns true if any differences were found and written
161func (p *blockBodyDiffPrinter) writeBlockBodyDiff(schema *configschema.Block, old, new cty.Value, indent int, path cty.Path) bool {
162 path = ctyEnsurePathCapacity(path, 1)
163
164 bodyWritten := false
165 blankBeforeBlocks := false
166 {
167 attrNames := make([]string, 0, len(schema.Attributes))
168 attrNameLen := 0
169 for name := range schema.Attributes {
170 oldVal := ctyGetAttrMaybeNull(old, name)
171 newVal := ctyGetAttrMaybeNull(new, name)
172 if oldVal.IsNull() && newVal.IsNull() {
173 // Skip attributes where both old and new values are null
174 // (we do this early here so that we'll do our value alignment
175 // based on the longest attribute name that has a change, rather
176 // than the longest attribute name in the full set.)
177 continue
178 }
179
180 attrNames = append(attrNames, name)
181 if len(name) > attrNameLen {
182 attrNameLen = len(name)
183 }
184 }
185 sort.Strings(attrNames)
186 if len(attrNames) > 0 {
187 blankBeforeBlocks = true
188 }
189
190 for _, name := range attrNames {
191 attrS := schema.Attributes[name]
192 oldVal := ctyGetAttrMaybeNull(old, name)
193 newVal := ctyGetAttrMaybeNull(new, name)
194
195 bodyWritten = true
196 p.writeAttrDiff(name, attrS, oldVal, newVal, attrNameLen, indent, path)
197 }
198 }
199
200 {
201 blockTypeNames := make([]string, 0, len(schema.BlockTypes))
202 for name := range schema.BlockTypes {
203 blockTypeNames = append(blockTypeNames, name)
204 }
205 sort.Strings(blockTypeNames)
206
207 for _, name := range blockTypeNames {
208 blockS := schema.BlockTypes[name]
209 oldVal := ctyGetAttrMaybeNull(old, name)
210 newVal := ctyGetAttrMaybeNull(new, name)
211
212 bodyWritten = true
213 p.writeNestedBlockDiffs(name, blockS, oldVal, newVal, blankBeforeBlocks, indent, path)
214
215 // Always include a blank for any subsequent block types.
216 blankBeforeBlocks = true
217 }
218 }
219
220 return bodyWritten
221}
222
223func (p *blockBodyDiffPrinter) writeAttrDiff(name string, attrS *configschema.Attribute, old, new cty.Value, nameLen, indent int, path cty.Path) {
224 path = append(path, cty.GetAttrStep{Name: name})
225 p.buf.WriteString("\n")
226 p.buf.WriteString(strings.Repeat(" ", indent))
227 showJustNew := false
228 var action plans.Action
229 switch {
230 case old.IsNull():
231 action = plans.Create
232 showJustNew = true
233 case new.IsNull():
234 action = plans.Delete
235 case ctyEqualWithUnknown(old, new):
236 action = plans.NoOp
237 showJustNew = true
238 default:
239 action = plans.Update
240 }
241
242 p.writeActionSymbol(action)
243
244 p.buf.WriteString(p.color.Color("[bold]"))
245 p.buf.WriteString(name)
246 p.buf.WriteString(p.color.Color("[reset]"))
247 p.buf.WriteString(strings.Repeat(" ", nameLen-len(name)))
248 p.buf.WriteString(" = ")
249
250 if attrS.Sensitive {
251 p.buf.WriteString("(sensitive value)")
252 } else {
253 switch {
254 case showJustNew:
255 p.writeValue(new, action, indent+2)
256 if p.pathForcesNewResource(path) {
257 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
258 }
259 default:
260 // We show new even if it is null to emphasize the fact
261 // that it is being unset, since otherwise it is easy to
262 // misunderstand that the value is still set to the old value.
263 p.writeValueDiff(old, new, indent+2, path)
264 }
265 }
266}
267
268func (p *blockBodyDiffPrinter) writeNestedBlockDiffs(name string, blockS *configschema.NestedBlock, old, new cty.Value, blankBefore bool, indent int, path cty.Path) {
269 path = append(path, cty.GetAttrStep{Name: name})
270 if old.IsNull() && new.IsNull() {
271 // Nothing to do if both old and new is null
272 return
273 }
274
275 // Where old/new are collections representing a nesting mode other than
276 // NestingSingle, we assume the collection value can never be unknown
277 // since we always produce the container for the nested objects, even if
278 // the objects within are computed.
279
280 switch blockS.Nesting {
281 case configschema.NestingSingle, configschema.NestingGroup:
282 var action plans.Action
283 eqV := new.Equals(old)
284 switch {
285 case old.IsNull():
286 action = plans.Create
287 case new.IsNull():
288 action = plans.Delete
289 case !new.IsWhollyKnown() || !old.IsWhollyKnown():
290 // "old" should actually always be known due to our contract
291 // that old values must never be unknown, but we'll allow it
292 // anyway to be robust.
293 action = plans.Update
294 case !eqV.IsKnown() || !eqV.True():
295 action = plans.Update
296 }
297
298 if blankBefore {
299 p.buf.WriteRune('\n')
300 }
301 p.writeNestedBlockDiff(name, nil, &blockS.Block, action, old, new, indent, path)
302 case configschema.NestingList:
303 // For the sake of handling nested blocks, we'll treat a null list
304 // the same as an empty list since the config language doesn't
305 // distinguish these anyway.
306 old = ctyNullBlockListAsEmpty(old)
307 new = ctyNullBlockListAsEmpty(new)
308
309 oldItems := ctyCollectionValues(old)
310 newItems := ctyCollectionValues(new)
311
312 // Here we intentionally preserve the index-based correspondance
313 // between old and new, rather than trying to detect insertions
314 // and removals in the list, because this more accurately reflects
315 // how Terraform Core and providers will understand the change,
316 // particularly when the nested block contains computed attributes
317 // that will themselves maintain correspondance by index.
318
319 // commonLen is number of elements that exist in both lists, which
320 // will be presented as updates (~). Any additional items in one
321 // of the lists will be presented as either creates (+) or deletes (-)
322 // depending on which list they belong to.
323 var commonLen int
324 switch {
325 case len(oldItems) < len(newItems):
326 commonLen = len(oldItems)
327 default:
328 commonLen = len(newItems)
329 }
330
331 if blankBefore && (len(oldItems) > 0 || len(newItems) > 0) {
332 p.buf.WriteRune('\n')
333 }
334
335 for i := 0; i < commonLen; i++ {
336 path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
337 oldItem := oldItems[i]
338 newItem := newItems[i]
339 action := plans.Update
340 if oldItem.RawEquals(newItem) {
341 action = plans.NoOp
342 }
343 p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldItem, newItem, indent, path)
344 }
345 for i := commonLen; i < len(oldItems); i++ {
346 path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
347 oldItem := oldItems[i]
348 newItem := cty.NullVal(oldItem.Type())
349 p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Delete, oldItem, newItem, indent, path)
350 }
351 for i := commonLen; i < len(newItems); i++ {
352 path := append(path, cty.IndexStep{Key: cty.NumberIntVal(int64(i))})
353 newItem := newItems[i]
354 oldItem := cty.NullVal(newItem.Type())
355 p.writeNestedBlockDiff(name, nil, &blockS.Block, plans.Create, oldItem, newItem, indent, path)
356 }
357 case configschema.NestingSet:
358 // For the sake of handling nested blocks, we'll treat a null set
359 // the same as an empty set since the config language doesn't
360 // distinguish these anyway.
361 old = ctyNullBlockSetAsEmpty(old)
362 new = ctyNullBlockSetAsEmpty(new)
363
364 oldItems := ctyCollectionValues(old)
365 newItems := ctyCollectionValues(new)
366
367 if (len(oldItems) + len(newItems)) == 0 {
368 // Nothing to do if both sets are empty
369 return
370 }
371
372 allItems := make([]cty.Value, 0, len(oldItems)+len(newItems))
373 allItems = append(allItems, oldItems...)
374 allItems = append(allItems, newItems...)
375 all := cty.SetVal(allItems)
376
377 if blankBefore {
378 p.buf.WriteRune('\n')
379 }
380
381 for it := all.ElementIterator(); it.Next(); {
382 _, val := it.Element()
383 var action plans.Action
384 var oldValue, newValue cty.Value
385 switch {
386 case !val.IsKnown():
387 action = plans.Update
388 newValue = val
389 case !old.HasElement(val).True():
390 action = plans.Create
391 oldValue = cty.NullVal(val.Type())
392 newValue = val
393 case !new.HasElement(val).True():
394 action = plans.Delete
395 oldValue = val
396 newValue = cty.NullVal(val.Type())
397 default:
398 action = plans.NoOp
399 oldValue = val
400 newValue = val
401 }
402 path := append(path, cty.IndexStep{Key: val})
403 p.writeNestedBlockDiff(name, nil, &blockS.Block, action, oldValue, newValue, indent, path)
404 }
405
406 case configschema.NestingMap:
407 // For the sake of handling nested blocks, we'll treat a null map
408 // the same as an empty map since the config language doesn't
409 // distinguish these anyway.
410 old = ctyNullBlockMapAsEmpty(old)
411 new = ctyNullBlockMapAsEmpty(new)
412
413 oldItems := old.AsValueMap()
414 newItems := new.AsValueMap()
415 if (len(oldItems) + len(newItems)) == 0 {
416 // Nothing to do if both maps are empty
417 return
418 }
419
420 allKeys := make(map[string]bool)
421 for k := range oldItems {
422 allKeys[k] = true
423 }
424 for k := range newItems {
425 allKeys[k] = true
426 }
427 allKeysOrder := make([]string, 0, len(allKeys))
428 for k := range allKeys {
429 allKeysOrder = append(allKeysOrder, k)
430 }
431 sort.Strings(allKeysOrder)
432
433 if blankBefore {
434 p.buf.WriteRune('\n')
435 }
436
437 for _, k := range allKeysOrder {
438 var action plans.Action
439 oldValue := oldItems[k]
440 newValue := newItems[k]
441 switch {
442 case oldValue == cty.NilVal:
443 oldValue = cty.NullVal(newValue.Type())
444 action = plans.Create
445 case newValue == cty.NilVal:
446 newValue = cty.NullVal(oldValue.Type())
447 action = plans.Delete
448 case !newValue.RawEquals(oldValue):
449 action = plans.Update
450 default:
451 action = plans.NoOp
452 }
453
454 path := append(path, cty.IndexStep{Key: cty.StringVal(k)})
455 p.writeNestedBlockDiff(name, &k, &blockS.Block, action, oldValue, newValue, indent, path)
456 }
457 }
458}
459
460func (p *blockBodyDiffPrinter) writeNestedBlockDiff(name string, label *string, blockS *configschema.Block, action plans.Action, old, new cty.Value, indent int, path cty.Path) {
461 p.buf.WriteString("\n")
462 p.buf.WriteString(strings.Repeat(" ", indent))
463 p.writeActionSymbol(action)
464
465 if label != nil {
466 fmt.Fprintf(p.buf, "%s %q {", name, *label)
467 } else {
468 fmt.Fprintf(p.buf, "%s {", name)
469 }
470
471 if action != plans.NoOp && (p.pathForcesNewResource(path) || p.pathForcesNewResource(path[:len(path)-1])) {
472 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
473 }
474
475 bodyWritten := p.writeBlockBodyDiff(blockS, old, new, indent+4, path)
476 if bodyWritten {
477 p.buf.WriteString("\n")
478 p.buf.WriteString(strings.Repeat(" ", indent+2))
479 }
480 p.buf.WriteString("}")
481}
482
483func (p *blockBodyDiffPrinter) writeValue(val cty.Value, action plans.Action, indent int) {
484 if !val.IsKnown() {
485 p.buf.WriteString("(known after apply)")
486 return
487 }
488 if val.IsNull() {
489 p.buf.WriteString(p.color.Color("[dark_gray]null[reset]"))
490 return
491 }
492
493 ty := val.Type()
494
495 switch {
496 case ty.IsPrimitiveType():
497 switch ty {
498 case cty.String:
499 {
500 // Special behavior for JSON strings containing array or object
501 src := []byte(val.AsString())
502 ty, err := ctyjson.ImpliedType(src)
503 // check for the special case of "null", which decodes to nil,
504 // and just allow it to be printed out directly
505 if err == nil && !ty.IsPrimitiveType() && val.AsString() != "null" {
506 jv, err := ctyjson.Unmarshal(src, ty)
507 if err == nil {
508 p.buf.WriteString("jsonencode(")
509 if jv.LengthInt() == 0 {
510 p.writeValue(jv, action, 0)
511 } else {
512 p.buf.WriteByte('\n')
513 p.buf.WriteString(strings.Repeat(" ", indent+4))
514 p.writeValue(jv, action, indent+4)
515 p.buf.WriteByte('\n')
516 p.buf.WriteString(strings.Repeat(" ", indent))
517 }
518 p.buf.WriteByte(')')
519 break // don't *also* do the normal behavior below
520 }
521 }
522 }
523 fmt.Fprintf(p.buf, "%q", val.AsString())
524 case cty.Bool:
525 if val.True() {
526 p.buf.WriteString("true")
527 } else {
528 p.buf.WriteString("false")
529 }
530 case cty.Number:
531 bf := val.AsBigFloat()
532 p.buf.WriteString(bf.Text('f', -1))
533 default:
534 // should never happen, since the above is exhaustive
535 fmt.Fprintf(p.buf, "%#v", val)
536 }
537 case ty.IsListType() || ty.IsSetType() || ty.IsTupleType():
538 p.buf.WriteString("[")
539
540 it := val.ElementIterator()
541 for it.Next() {
542 _, val := it.Element()
543
544 p.buf.WriteString("\n")
545 p.buf.WriteString(strings.Repeat(" ", indent+2))
546 p.writeActionSymbol(action)
547 p.writeValue(val, action, indent+4)
548 p.buf.WriteString(",")
549 }
550
551 if val.LengthInt() > 0 {
552 p.buf.WriteString("\n")
553 p.buf.WriteString(strings.Repeat(" ", indent))
554 }
555 p.buf.WriteString("]")
556 case ty.IsMapType():
557 p.buf.WriteString("{")
558
559 keyLen := 0
560 for it := val.ElementIterator(); it.Next(); {
561 key, _ := it.Element()
562 if keyStr := key.AsString(); len(keyStr) > keyLen {
563 keyLen = len(keyStr)
564 }
565 }
566
567 for it := val.ElementIterator(); it.Next(); {
568 key, val := it.Element()
569
570 p.buf.WriteString("\n")
571 p.buf.WriteString(strings.Repeat(" ", indent+2))
572 p.writeActionSymbol(action)
573 p.writeValue(key, action, indent+4)
574 p.buf.WriteString(strings.Repeat(" ", keyLen-len(key.AsString())))
575 p.buf.WriteString(" = ")
576 p.writeValue(val, action, indent+4)
577 }
578
579 if val.LengthInt() > 0 {
580 p.buf.WriteString("\n")
581 p.buf.WriteString(strings.Repeat(" ", indent))
582 }
583 p.buf.WriteString("}")
584 case ty.IsObjectType():
585 p.buf.WriteString("{")
586
587 atys := ty.AttributeTypes()
588 attrNames := make([]string, 0, len(atys))
589 nameLen := 0
590 for attrName := range atys {
591 attrNames = append(attrNames, attrName)
592 if len(attrName) > nameLen {
593 nameLen = len(attrName)
594 }
595 }
596 sort.Strings(attrNames)
597
598 for _, attrName := range attrNames {
599 val := val.GetAttr(attrName)
600
601 p.buf.WriteString("\n")
602 p.buf.WriteString(strings.Repeat(" ", indent+2))
603 p.writeActionSymbol(action)
604 p.buf.WriteString(attrName)
605 p.buf.WriteString(strings.Repeat(" ", nameLen-len(attrName)))
606 p.buf.WriteString(" = ")
607 p.writeValue(val, action, indent+4)
608 }
609
610 if len(attrNames) > 0 {
611 p.buf.WriteString("\n")
612 p.buf.WriteString(strings.Repeat(" ", indent))
613 }
614 p.buf.WriteString("}")
615 }
616}
617
618func (p *blockBodyDiffPrinter) writeValueDiff(old, new cty.Value, indent int, path cty.Path) {
619 ty := old.Type()
620 typesEqual := ctyTypesEqual(ty, new.Type())
621
622 // We have some specialized diff implementations for certain complex
623 // values where it's useful to see a visualization of the diff of
624 // the nested elements rather than just showing the entire old and
625 // new values verbatim.
626 // However, these specialized implementations can apply only if both
627 // values are known and non-null.
628 if old.IsKnown() && new.IsKnown() && !old.IsNull() && !new.IsNull() && typesEqual {
629 switch {
630 case ty == cty.String:
631 // We have special behavior for both multi-line strings in general
632 // and for strings that can parse as JSON. For the JSON handling
633 // to apply, both old and new must be valid JSON.
634 // For single-line strings that don't parse as JSON we just fall
635 // out of this switch block and do the default old -> new rendering.
636 oldS := old.AsString()
637 newS := new.AsString()
638
639 {
640 // Special behavior for JSON strings containing object or
641 // list values.
642 oldBytes := []byte(oldS)
643 newBytes := []byte(newS)
644 oldType, oldErr := ctyjson.ImpliedType(oldBytes)
645 newType, newErr := ctyjson.ImpliedType(newBytes)
646 if oldErr == nil && newErr == nil && !(oldType.IsPrimitiveType() && newType.IsPrimitiveType()) {
647 oldJV, oldErr := ctyjson.Unmarshal(oldBytes, oldType)
648 newJV, newErr := ctyjson.Unmarshal(newBytes, newType)
649 if oldErr == nil && newErr == nil {
650 if !oldJV.RawEquals(newJV) { // two JSON values may differ only in insignificant whitespace
651 p.buf.WriteString("jsonencode(")
652 p.buf.WriteByte('\n')
653 p.buf.WriteString(strings.Repeat(" ", indent+2))
654 p.writeActionSymbol(plans.Update)
655 p.writeValueDiff(oldJV, newJV, indent+4, path)
656 p.buf.WriteByte('\n')
657 p.buf.WriteString(strings.Repeat(" ", indent))
658 p.buf.WriteByte(')')
659 } else {
660 // if they differ only in insigificant whitespace
661 // then we'll note that but still expand out the
662 // effective value.
663 if p.pathForcesNewResource(path) {
664 p.buf.WriteString(p.color.Color("jsonencode( [red]# whitespace changes force replacement[reset]"))
665 } else {
666 p.buf.WriteString(p.color.Color("jsonencode( [dim]# whitespace changes[reset]"))
667 }
668 p.buf.WriteByte('\n')
669 p.buf.WriteString(strings.Repeat(" ", indent+4))
670 p.writeValue(oldJV, plans.NoOp, indent+4)
671 p.buf.WriteByte('\n')
672 p.buf.WriteString(strings.Repeat(" ", indent))
673 p.buf.WriteByte(')')
674 }
675 return
676 }
677 }
678 }
679
680 if strings.Index(oldS, "\n") < 0 && strings.Index(newS, "\n") < 0 {
681 break
682 }
683
684 p.buf.WriteString("<<~EOT")
685 if p.pathForcesNewResource(path) {
686 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
687 }
688 p.buf.WriteString("\n")
689
690 var oldLines, newLines []cty.Value
691 {
692 r := strings.NewReader(oldS)
693 sc := bufio.NewScanner(r)
694 for sc.Scan() {
695 oldLines = append(oldLines, cty.StringVal(sc.Text()))
696 }
697 }
698 {
699 r := strings.NewReader(newS)
700 sc := bufio.NewScanner(r)
701 for sc.Scan() {
702 newLines = append(newLines, cty.StringVal(sc.Text()))
703 }
704 }
705
706 diffLines := ctySequenceDiff(oldLines, newLines)
707 for _, diffLine := range diffLines {
708 p.buf.WriteString(strings.Repeat(" ", indent+2))
709 p.writeActionSymbol(diffLine.Action)
710
711 switch diffLine.Action {
712 case plans.NoOp, plans.Delete:
713 p.buf.WriteString(diffLine.Before.AsString())
714 case plans.Create:
715 p.buf.WriteString(diffLine.After.AsString())
716 default:
717 // Should never happen since the above covers all
718 // actions that ctySequenceDiff can return for strings
719 p.buf.WriteString(diffLine.After.AsString())
720
721 }
722 p.buf.WriteString("\n")
723 }
724
725 p.buf.WriteString(strings.Repeat(" ", indent)) // +4 here because there's no symbol
726 p.buf.WriteString("EOT")
727
728 return
729
730 case ty.IsSetType():
731 p.buf.WriteString("[")
732 if p.pathForcesNewResource(path) {
733 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
734 }
735 p.buf.WriteString("\n")
736
737 var addedVals, removedVals, allVals []cty.Value
738 for it := old.ElementIterator(); it.Next(); {
739 _, val := it.Element()
740 allVals = append(allVals, val)
741 if new.HasElement(val).False() {
742 removedVals = append(removedVals, val)
743 }
744 }
745 for it := new.ElementIterator(); it.Next(); {
746 _, val := it.Element()
747 allVals = append(allVals, val)
748 if val.IsKnown() && old.HasElement(val).False() {
749 addedVals = append(addedVals, val)
750 }
751 }
752
753 var all, added, removed cty.Value
754 if len(allVals) > 0 {
755 all = cty.SetVal(allVals)
756 } else {
757 all = cty.SetValEmpty(ty.ElementType())
758 }
759 if len(addedVals) > 0 {
760 added = cty.SetVal(addedVals)
761 } else {
762 added = cty.SetValEmpty(ty.ElementType())
763 }
764 if len(removedVals) > 0 {
765 removed = cty.SetVal(removedVals)
766 } else {
767 removed = cty.SetValEmpty(ty.ElementType())
768 }
769
770 for it := all.ElementIterator(); it.Next(); {
771 _, val := it.Element()
772
773 p.buf.WriteString(strings.Repeat(" ", indent+2))
774
775 var action plans.Action
776 switch {
777 case !val.IsKnown():
778 action = plans.Update
779 case added.HasElement(val).True():
780 action = plans.Create
781 case removed.HasElement(val).True():
782 action = plans.Delete
783 default:
784 action = plans.NoOp
785 }
786
787 p.writeActionSymbol(action)
788 p.writeValue(val, action, indent+4)
789 p.buf.WriteString(",\n")
790 }
791
792 p.buf.WriteString(strings.Repeat(" ", indent))
793 p.buf.WriteString("]")
794 return
795 case ty.IsListType() || ty.IsTupleType():
796 p.buf.WriteString("[")
797 if p.pathForcesNewResource(path) {
798 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
799 }
800 p.buf.WriteString("\n")
801
802 elemDiffs := ctySequenceDiff(old.AsValueSlice(), new.AsValueSlice())
803 for _, elemDiff := range elemDiffs {
804 p.buf.WriteString(strings.Repeat(" ", indent+2))
805 p.writeActionSymbol(elemDiff.Action)
806 switch elemDiff.Action {
807 case plans.NoOp, plans.Delete:
808 p.writeValue(elemDiff.Before, elemDiff.Action, indent+4)
809 case plans.Update:
810 p.writeValueDiff(elemDiff.Before, elemDiff.After, indent+4, path)
811 case plans.Create:
812 p.writeValue(elemDiff.After, elemDiff.Action, indent+4)
813 default:
814 // Should never happen since the above covers all
815 // actions that ctySequenceDiff can return.
816 p.writeValue(elemDiff.After, elemDiff.Action, indent+4)
817 }
818
819 p.buf.WriteString(",\n")
820 }
821
822 p.buf.WriteString(strings.Repeat(" ", indent))
823 p.buf.WriteString("]")
824 return
825
826 case ty.IsMapType():
827 p.buf.WriteString("{")
828 if p.pathForcesNewResource(path) {
829 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
830 }
831 p.buf.WriteString("\n")
832
833 var allKeys []string
834 keyLen := 0
835 for it := old.ElementIterator(); it.Next(); {
836 k, _ := it.Element()
837 keyStr := k.AsString()
838 allKeys = append(allKeys, keyStr)
839 if len(keyStr) > keyLen {
840 keyLen = len(keyStr)
841 }
842 }
843 for it := new.ElementIterator(); it.Next(); {
844 k, _ := it.Element()
845 keyStr := k.AsString()
846 allKeys = append(allKeys, keyStr)
847 if len(keyStr) > keyLen {
848 keyLen = len(keyStr)
849 }
850 }
851
852 sort.Strings(allKeys)
853
854 lastK := ""
855 for i, k := range allKeys {
856 if i > 0 && lastK == k {
857 continue // skip duplicates (list is sorted)
858 }
859 lastK = k
860
861 p.buf.WriteString(strings.Repeat(" ", indent+2))
862 kV := cty.StringVal(k)
863 var action plans.Action
864 if old.HasIndex(kV).False() {
865 action = plans.Create
866 } else if new.HasIndex(kV).False() {
867 action = plans.Delete
868 } else if eqV := old.Index(kV).Equals(new.Index(kV)); eqV.IsKnown() && eqV.True() {
869 action = plans.NoOp
870 } else {
871 action = plans.Update
872 }
873
874 path := append(path, cty.IndexStep{Key: kV})
875
876 p.writeActionSymbol(action)
877 p.writeValue(kV, action, indent+4)
878 p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
879 p.buf.WriteString(" = ")
880 switch action {
881 case plans.Create, plans.NoOp:
882 v := new.Index(kV)
883 p.writeValue(v, action, indent+4)
884 case plans.Delete:
885 oldV := old.Index(kV)
886 newV := cty.NullVal(oldV.Type())
887 p.writeValueDiff(oldV, newV, indent+4, path)
888 default:
889 oldV := old.Index(kV)
890 newV := new.Index(kV)
891 p.writeValueDiff(oldV, newV, indent+4, path)
892 }
893
894 p.buf.WriteByte('\n')
895 }
896
897 p.buf.WriteString(strings.Repeat(" ", indent))
898 p.buf.WriteString("}")
899 return
900 case ty.IsObjectType():
901 p.buf.WriteString("{")
902 p.buf.WriteString("\n")
903
904 forcesNewResource := p.pathForcesNewResource(path)
905
906 var allKeys []string
907 keyLen := 0
908 for it := old.ElementIterator(); it.Next(); {
909 k, _ := it.Element()
910 keyStr := k.AsString()
911 allKeys = append(allKeys, keyStr)
912 if len(keyStr) > keyLen {
913 keyLen = len(keyStr)
914 }
915 }
916 for it := new.ElementIterator(); it.Next(); {
917 k, _ := it.Element()
918 keyStr := k.AsString()
919 allKeys = append(allKeys, keyStr)
920 if len(keyStr) > keyLen {
921 keyLen = len(keyStr)
922 }
923 }
924
925 sort.Strings(allKeys)
926
927 lastK := ""
928 for i, k := range allKeys {
929 if i > 0 && lastK == k {
930 continue // skip duplicates (list is sorted)
931 }
932 lastK = k
933
934 p.buf.WriteString(strings.Repeat(" ", indent+2))
935 kV := k
936 var action plans.Action
937 if !old.Type().HasAttribute(kV) {
938 action = plans.Create
939 } else if !new.Type().HasAttribute(kV) {
940 action = plans.Delete
941 } else if eqV := old.GetAttr(kV).Equals(new.GetAttr(kV)); eqV.IsKnown() && eqV.True() {
942 action = plans.NoOp
943 } else {
944 action = plans.Update
945 }
946
947 path := append(path, cty.GetAttrStep{Name: kV})
948
949 p.writeActionSymbol(action)
950 p.buf.WriteString(k)
951 p.buf.WriteString(strings.Repeat(" ", keyLen-len(k)))
952 p.buf.WriteString(" = ")
953
954 switch action {
955 case plans.Create, plans.NoOp:
956 v := new.GetAttr(kV)
957 p.writeValue(v, action, indent+4)
958 case plans.Delete:
959 oldV := old.GetAttr(kV)
960 newV := cty.NullVal(oldV.Type())
961 p.writeValueDiff(oldV, newV, indent+4, path)
962 default:
963 oldV := old.GetAttr(kV)
964 newV := new.GetAttr(kV)
965 p.writeValueDiff(oldV, newV, indent+4, path)
966 }
967
968 p.buf.WriteString("\n")
969 }
970
971 p.buf.WriteString(strings.Repeat(" ", indent))
972 p.buf.WriteString("}")
973
974 if forcesNewResource {
975 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
976 }
977 return
978 }
979 }
980
981 // In all other cases, we just show the new and old values as-is
982 p.writeValue(old, plans.Delete, indent)
983 if new.IsNull() {
984 p.buf.WriteString(p.color.Color(" [dark_gray]->[reset] "))
985 } else {
986 p.buf.WriteString(p.color.Color(" [yellow]->[reset] "))
987 }
988
989 p.writeValue(new, plans.Create, indent)
990 if p.pathForcesNewResource(path) {
991 p.buf.WriteString(p.color.Color(forcesNewResourceCaption))
992 }
993}
994
995// writeActionSymbol writes a symbol to represent the given action, followed
996// by a space.
997//
998// It only supports the actions that can be represented with a single character:
999// Create, Delete, Update and NoAction.
1000func (p *blockBodyDiffPrinter) writeActionSymbol(action plans.Action) {
1001 switch action {
1002 case plans.Create:
1003 p.buf.WriteString(p.color.Color("[green]+[reset] "))
1004 case plans.Delete:
1005 p.buf.WriteString(p.color.Color("[red]-[reset] "))
1006 case plans.Update:
1007 p.buf.WriteString(p.color.Color("[yellow]~[reset] "))
1008 case plans.NoOp:
1009 p.buf.WriteString(" ")
1010 default:
1011 // Should never happen
1012 p.buf.WriteString(p.color.Color("? "))
1013 }
1014}
1015
1016func (p *blockBodyDiffPrinter) pathForcesNewResource(path cty.Path) bool {
1017 if !p.action.IsReplace() {
1018 // "requiredReplace" only applies when the instance is being replaced
1019 return false
1020 }
1021 return p.requiredReplace.Has(path)
1022}
1023
1024func ctyEmptyString(value cty.Value) bool {
1025 if !value.IsNull() && value.IsKnown() {
1026 valueType := value.Type()
1027 if valueType == cty.String && value.AsString() == "" {
1028 return true
1029 }
1030 }
1031 return false
1032}
1033
1034func ctyGetAttrMaybeNull(val cty.Value, name string) cty.Value {
1035 attrType := val.Type().AttributeType(name)
1036
1037 if val.IsNull() {
1038 return cty.NullVal(attrType)
1039 }
1040
1041 // We treat "" as null here
1042 // as existing SDK doesn't support null yet.
1043 // This allows us to avoid spurious diffs
1044 // until we introduce null to the SDK.
1045 attrValue := val.GetAttr(name)
1046 if ctyEmptyString(attrValue) {
1047 return cty.NullVal(attrType)
1048 }
1049
1050 return attrValue
1051}
1052
1053func ctyCollectionValues(val cty.Value) []cty.Value {
1054 if !val.IsKnown() || val.IsNull() {
1055 return nil
1056 }
1057
1058 ret := make([]cty.Value, 0, val.LengthInt())
1059 for it := val.ElementIterator(); it.Next(); {
1060 _, value := it.Element()
1061 ret = append(ret, value)
1062 }
1063 return ret
1064}
1065
1066// ctySequenceDiff returns differences between given sequences of cty.Value(s)
1067// in the form of Create, Delete, or Update actions (for objects).
1068func ctySequenceDiff(old, new []cty.Value) []*plans.Change {
1069 var ret []*plans.Change
1070 lcs := objchange.LongestCommonSubsequence(old, new)
1071 var oldI, newI, lcsI int
1072 for oldI < len(old) || newI < len(new) || lcsI < len(lcs) {
1073 for oldI < len(old) && (lcsI >= len(lcs) || !old[oldI].RawEquals(lcs[lcsI])) {
1074 isObjectDiff := old[oldI].Type().IsObjectType() && (newI >= len(new) || new[newI].Type().IsObjectType())
1075 if isObjectDiff && newI < len(new) {
1076 ret = append(ret, &plans.Change{
1077 Action: plans.Update,
1078 Before: old[oldI],
1079 After: new[newI],
1080 })
1081 oldI++
1082 newI++ // we also consume the next "new" in this case
1083 continue
1084 }
1085
1086 ret = append(ret, &plans.Change{
1087 Action: plans.Delete,
1088 Before: old[oldI],
1089 After: cty.NullVal(old[oldI].Type()),
1090 })
1091 oldI++
1092 }
1093 for newI < len(new) && (lcsI >= len(lcs) || !new[newI].RawEquals(lcs[lcsI])) {
1094 ret = append(ret, &plans.Change{
1095 Action: plans.Create,
1096 Before: cty.NullVal(new[newI].Type()),
1097 After: new[newI],
1098 })
1099 newI++
1100 }
1101 if lcsI < len(lcs) {
1102 ret = append(ret, &plans.Change{
1103 Action: plans.NoOp,
1104 Before: lcs[lcsI],
1105 After: lcs[lcsI],
1106 })
1107
1108 // All of our indexes advance together now, since the line
1109 // is common to all three sequences.
1110 lcsI++
1111 oldI++
1112 newI++
1113 }
1114 }
1115 return ret
1116}
1117
1118func ctyEqualWithUnknown(old, new cty.Value) bool {
1119 if !old.IsWhollyKnown() || !new.IsWhollyKnown() {
1120 return false
1121 }
1122 return old.Equals(new).True()
1123}
1124
1125// ctyTypesEqual checks equality of two types more loosely
1126// by avoiding checks of object/tuple elements
1127// as we render differences on element-by-element basis anyway
1128func ctyTypesEqual(oldT, newT cty.Type) bool {
1129 if oldT.IsObjectType() && newT.IsObjectType() {
1130 return true
1131 }
1132 if oldT.IsTupleType() && newT.IsTupleType() {
1133 return true
1134 }
1135 return oldT.Equals(newT)
1136}
1137
1138func ctyEnsurePathCapacity(path cty.Path, minExtra int) cty.Path {
1139 if cap(path)-len(path) >= minExtra {
1140 return path
1141 }
1142 newCap := cap(path) * 2
1143 if newCap < (len(path) + minExtra) {
1144 newCap = len(path) + minExtra
1145 }
1146 newPath := make(cty.Path, len(path), newCap)
1147 copy(newPath, path)
1148 return newPath
1149}
1150
1151// ctyNullBlockListAsEmpty either returns the given value verbatim if it is non-nil
1152// or returns an empty value of a suitable type to serve as a placeholder for it.
1153//
1154// In particular, this function handles the special situation where a "list" is
1155// actually represented as a tuple type where nested blocks contain
1156// dynamically-typed values.
1157func ctyNullBlockListAsEmpty(in cty.Value) cty.Value {
1158 if !in.IsNull() {
1159 return in
1160 }
1161 if ty := in.Type(); ty.IsListType() {
1162 return cty.ListValEmpty(ty.ElementType())
1163 }
1164 return cty.EmptyTupleVal // must need a tuple, then
1165}
1166
1167// ctyNullBlockMapAsEmpty either returns the given value verbatim if it is non-nil
1168// or returns an empty value of a suitable type to serve as a placeholder for it.
1169//
1170// In particular, this function handles the special situation where a "map" is
1171// actually represented as an object type where nested blocks contain
1172// dynamically-typed values.
1173func ctyNullBlockMapAsEmpty(in cty.Value) cty.Value {
1174 if !in.IsNull() {
1175 return in
1176 }
1177 if ty := in.Type(); ty.IsMapType() {
1178 return cty.MapValEmpty(ty.ElementType())
1179 }
1180 return cty.EmptyObjectVal // must need an object, then
1181}
1182
1183// ctyNullBlockSetAsEmpty either returns the given value verbatim if it is non-nil
1184// or returns an empty value of a suitable type to serve as a placeholder for it.
1185func ctyNullBlockSetAsEmpty(in cty.Value) cty.Value {
1186 if !in.IsNull() {
1187 return in
1188 }
1189 // Dynamically-typed attributes are not supported inside blocks backed by
1190 // sets, so our result here is always a set.
1191 return cty.SetValEmpty(in.Type().ElementType())
1192}
diff --git a/vendor/github.com/hashicorp/terraform/command/format/format.go b/vendor/github.com/hashicorp/terraform/command/format/format.go
new file mode 100644
index 0000000..aa8d7de
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/command/format/format.go
@@ -0,0 +1,8 @@
1// Package format contains helpers for formatting various Terraform
2// structures for human-readabout output.
3//
4// This package is used by the official Terraform CLI in formatting any
5// output and is exported to encourage non-official frontends to mimic the
6// output formatting as much as possible so that text formats of Terraform
7// structures have a consistent look and feel.
8package format
diff --git a/vendor/github.com/hashicorp/terraform/command/format/object_id.go b/vendor/github.com/hashicorp/terraform/command/format/object_id.go
new file mode 100644
index 0000000..85ebbfe
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/command/format/object_id.go
@@ -0,0 +1,123 @@
1package format
2
3import (
4 "github.com/zclconf/go-cty/cty"
5)
6
7// ObjectValueID takes a value that is assumed to be an object representation
8// of some resource instance object and attempts to heuristically find an
9// attribute of it that is likely to be a unique identifier in the remote
10// system that it belongs to which will be useful to the user.
11//
12// If such an attribute is found, its name and string value intended for
13// display are returned. Both returned strings are empty if no such attribute
14// exists, in which case the caller should assume that the resource instance
15// address within the Terraform configuration is the best available identifier.
16//
17// This is only a best-effort sort of thing, relying on naming conventions in
18// our resource type schemas. The result is not guaranteed to be unique, but
19// should generally be suitable for display to an end-user anyway.
20//
21// This function will panic if the given value is not of an object type.
22func ObjectValueID(obj cty.Value) (k, v string) {
23 if obj.IsNull() || !obj.IsKnown() {
24 return "", ""
25 }
26
27 atys := obj.Type().AttributeTypes()
28
29 switch {
30
31 case atys["id"] == cty.String:
32 v := obj.GetAttr("id")
33 if v.IsKnown() && !v.IsNull() {
34 return "id", v.AsString()
35 }
36
37 case atys["name"] == cty.String:
38 // "name" isn't always globally unique, but if there isn't also an
39 // "id" then it _often_ is, in practice.
40 v := obj.GetAttr("name")
41 if v.IsKnown() && !v.IsNull() {
42 return "name", v.AsString()
43 }
44 }
45
46 return "", ""
47}
48
49// ObjectValueName takes a value that is assumed to be an object representation
50// of some resource instance object and attempts to heuristically find an
51// attribute of it that is likely to be a human-friendly name in the remote
52// system that it belongs to which will be useful to the user.
53//
54// If such an attribute is found, its name and string value intended for
55// display are returned. Both returned strings are empty if no such attribute
56// exists, in which case the caller should assume that the resource instance
57// address within the Terraform configuration is the best available identifier.
58//
59// This is only a best-effort sort of thing, relying on naming conventions in
60// our resource type schemas. The result is not guaranteed to be unique, but
61// should generally be suitable for display to an end-user anyway.
62//
63// Callers that use both ObjectValueName and ObjectValueID at the same time
64// should be prepared to get the same attribute key and value from both in
65// some cases, since there is overlap betweek the id-extraction and
66// name-extraction heuristics.
67//
68// This function will panic if the given value is not of an object type.
69func ObjectValueName(obj cty.Value) (k, v string) {
70 if obj.IsNull() || !obj.IsKnown() {
71 return "", ""
72 }
73
74 atys := obj.Type().AttributeTypes()
75
76 switch {
77
78 case atys["name"] == cty.String:
79 v := obj.GetAttr("name")
80 if v.IsKnown() && !v.IsNull() {
81 return "name", v.AsString()
82 }
83
84 case atys["tags"].IsMapType() && atys["tags"].ElementType() == cty.String:
85 tags := obj.GetAttr("tags")
86 if tags.IsNull() || !tags.IsWhollyKnown() {
87 break
88 }
89
90 switch {
91 case tags.HasIndex(cty.StringVal("name")).RawEquals(cty.True):
92 v := tags.Index(cty.StringVal("name"))
93 if v.IsKnown() && !v.IsNull() {
94 return "tags.name", v.AsString()
95 }
96 case tags.HasIndex(cty.StringVal("Name")).RawEquals(cty.True):
97 // AWS-style naming convention
98 v := tags.Index(cty.StringVal("Name"))
99 if v.IsKnown() && !v.IsNull() {
100 return "tags.Name", v.AsString()
101 }
102 }
103 }
104
105 return "", ""
106}
107
108// ObjectValueIDOrName is a convenience wrapper around both ObjectValueID
109// and ObjectValueName (in that preference order) to try to extract some sort
110// of human-friendly descriptive string value for an object as additional
111// context about an object when it is being displayed in a compact way (where
112// not all of the attributes are visible.)
113//
114// Just as with the two functions it wraps, it is a best-effort and may return
115// two empty strings if no suitable attribute can be found for a given object.
116func ObjectValueIDOrName(obj cty.Value) (k, v string) {
117 k, v = ObjectValueID(obj)
118 if k != "" {
119 return
120 }
121 k, v = ObjectValueName(obj)
122 return
123}
diff --git a/vendor/github.com/hashicorp/terraform/command/format/plan.go b/vendor/github.com/hashicorp/terraform/command/format/plan.go
new file mode 100644
index 0000000..098653f
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/command/format/plan.go
@@ -0,0 +1,302 @@
1package format
2
3import (
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).
24type Plan struct {
25 Resources []*InstanceDiff
26}
27
28// InstanceDiff is a representation of an instance diff optimized
29// for display, in conjunction with DisplayPlan.
30type 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.
45type 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.
61type PlanStats struct {
62 ToAdd, ToChange, ToDestroy int
63}
64
65// NewPlan produces a display-oriented Plan from a terraform.Plan.
66func 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.
121func (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
155func (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
174func (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.
183func (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.
191func 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.
212func 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}
diff --git a/vendor/github.com/hashicorp/terraform/command/format/state.go b/vendor/github.com/hashicorp/terraform/command/format/state.go
new file mode 100644
index 0000000..f411ef9
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/command/format/state.go
@@ -0,0 +1,286 @@
1package format
2
3import (
4 "bytes"
5 "fmt"
6 "sort"
7 "strings"
8
9 "github.com/zclconf/go-cty/cty"
10
11 "github.com/hashicorp/terraform/addrs"
12 "github.com/hashicorp/terraform/configs/configschema"
13 "github.com/hashicorp/terraform/plans"
14 "github.com/hashicorp/terraform/states"
15 "github.com/hashicorp/terraform/terraform"
16 "github.com/mitchellh/colorstring"
17)
18
19// StateOpts are the options for formatting a state.
20type StateOpts struct {
21 // State is the state to format. This is required.
22 State *states.State
23
24 // Schemas are used to decode attributes. This is required.
25 Schemas *terraform.Schemas
26
27 // Color is the colorizer. This is optional.
28 Color *colorstring.Colorize
29}
30
31// State takes a state and returns a string
32func State(opts *StateOpts) string {
33 if opts.Color == nil {
34 panic("colorize not given")
35 }
36
37 if opts.Schemas == nil {
38 panic("schemas not given")
39 }
40
41 s := opts.State
42 if len(s.Modules) == 0 {
43 return "The state file is empty. No resources are represented."
44 }
45
46 buf := bytes.NewBufferString("[reset]")
47 p := blockBodyDiffPrinter{
48 buf: buf,
49 color: opts.Color,
50 action: plans.NoOp,
51 }
52
53 // Format all the modules
54 for _, m := range s.Modules {
55 formatStateModule(p, m, opts.Schemas)
56 }
57
58 // Write the outputs for the root module
59 m := s.RootModule()
60
61 if m.OutputValues != nil {
62 if len(m.OutputValues) > 0 {
63 p.buf.WriteString("Outputs:\n\n")
64 }
65
66 // Sort the outputs
67 ks := make([]string, 0, len(m.OutputValues))
68 for k := range m.OutputValues {
69 ks = append(ks, k)
70 }
71 sort.Strings(ks)
72
73 // Output each output k/v pair
74 for _, k := range ks {
75 v := m.OutputValues[k]
76 p.buf.WriteString(fmt.Sprintf("%s = ", k))
77 p.writeValue(v.Value, plans.NoOp, 0)
78 p.buf.WriteString("\n\n")
79 }
80 }
81
82 return opts.Color.Color(strings.TrimSpace(p.buf.String()))
83
84}
85
86func formatStateModule(p blockBodyDiffPrinter, m *states.Module, schemas *terraform.Schemas) {
87 // First get the names of all the resources so we can show them
88 // in alphabetical order.
89 names := make([]string, 0, len(m.Resources))
90 for name := range m.Resources {
91 names = append(names, name)
92 }
93 sort.Strings(names)
94
95 // Go through each resource and begin building up the output.
96 for _, key := range names {
97 for k, v := range m.Resources[key].Instances {
98 addr := m.Resources[key].Addr
99
100 taintStr := ""
101 if v.Current.Status == 'T' {
102 taintStr = "(tainted)"
103 }
104 p.buf.WriteString(fmt.Sprintf("# %s: %s\n", addr.Absolute(m.Addr).Instance(k), taintStr))
105
106 var schema *configschema.Block
107 provider := m.Resources[key].ProviderConfig.ProviderConfig.StringCompact()
108 if _, exists := schemas.Providers[provider]; !exists {
109 // This should never happen in normal use because we should've
110 // loaded all of the schemas and checked things prior to this
111 // point. We can't return errors here, but since this is UI code
112 // we will try to do _something_ reasonable.
113 p.buf.WriteString(fmt.Sprintf("# missing schema for provider %q\n\n", provider))
114 continue
115 }
116
117 switch addr.Mode {
118 case addrs.ManagedResourceMode:
119 schema, _ = schemas.ResourceTypeConfig(
120 provider,
121 addr.Mode,
122 addr.Type,
123 )
124 if schema == nil {
125 p.buf.WriteString(fmt.Sprintf(
126 "# missing schema for provider %q resource type %s\n\n", provider, addr.Type))
127 continue
128 }
129
130 p.buf.WriteString(fmt.Sprintf(
131 "resource %q %q {",
132 addr.Type,
133 addr.Name,
134 ))
135 case addrs.DataResourceMode:
136 schema, _ = schemas.ResourceTypeConfig(
137 provider,
138 addr.Mode,
139 addr.Type,
140 )
141 if schema == nil {
142 p.buf.WriteString(fmt.Sprintf(
143 "# missing schema for provider %q data source %s\n\n", provider, addr.Type))
144 continue
145 }
146
147 p.buf.WriteString(fmt.Sprintf(
148 "data %q %q {",
149 addr.Type,
150 addr.Name,
151 ))
152 default:
153 // should never happen, since the above is exhaustive
154 p.buf.WriteString(addr.String())
155 }
156
157 val, err := v.Current.Decode(schema.ImpliedType())
158 if err != nil {
159 fmt.Println(err.Error())
160 break
161 }
162
163 path := make(cty.Path, 0, 3)
164 bodyWritten := p.writeBlockBodyDiff(schema, val.Value, val.Value, 2, path)
165 if bodyWritten {
166 p.buf.WriteString("\n")
167 }
168
169 p.buf.WriteString("}\n\n")
170 }
171 }
172 p.buf.WriteString("[reset]\n")
173}
174
175func formatNestedList(indent string, outputList []interface{}) string {
176 outputBuf := new(bytes.Buffer)
177 outputBuf.WriteString(fmt.Sprintf("%s[", indent))
178
179 lastIdx := len(outputList) - 1
180
181 for i, value := range outputList {
182 outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, " ", value))
183 if i != lastIdx {
184 outputBuf.WriteString(",")
185 }
186 }
187
188 outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
189 return strings.TrimPrefix(outputBuf.String(), "\n")
190}
191
192func formatListOutput(indent, outputName string, outputList []interface{}) string {
193 keyIndent := ""
194
195 outputBuf := new(bytes.Buffer)
196
197 if outputName != "" {
198 outputBuf.WriteString(fmt.Sprintf("%s%s = [", indent, outputName))
199 keyIndent = " "
200 }
201
202 lastIdx := len(outputList) - 1
203
204 for i, value := range outputList {
205 switch typedValue := value.(type) {
206 case string:
207 outputBuf.WriteString(fmt.Sprintf("\n%s%s%s", indent, keyIndent, value))
208 case []interface{}:
209 outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
210 formatNestedList(indent+keyIndent, typedValue)))
211 case map[string]interface{}:
212 outputBuf.WriteString(fmt.Sprintf("\n%s%s", indent,
213 formatNestedMap(indent+keyIndent, typedValue)))
214 }
215
216 if lastIdx != i {
217 outputBuf.WriteString(",")
218 }
219 }
220
221 if outputName != "" {
222 if len(outputList) > 0 {
223 outputBuf.WriteString(fmt.Sprintf("\n%s]", indent))
224 } else {
225 outputBuf.WriteString("]")
226 }
227 }
228
229 return strings.TrimPrefix(outputBuf.String(), "\n")
230}
231
232func formatNestedMap(indent string, outputMap map[string]interface{}) string {
233 ks := make([]string, 0, len(outputMap))
234 for k, _ := range outputMap {
235 ks = append(ks, k)
236 }
237 sort.Strings(ks)
238
239 outputBuf := new(bytes.Buffer)
240 outputBuf.WriteString(fmt.Sprintf("%s{", indent))
241
242 lastIdx := len(outputMap) - 1
243 for i, k := range ks {
244 v := outputMap[k]
245 outputBuf.WriteString(fmt.Sprintf("\n%s%s = %v", indent+" ", k, v))
246
247 if lastIdx != i {
248 outputBuf.WriteString(",")
249 }
250 }
251
252 outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
253
254 return strings.TrimPrefix(outputBuf.String(), "\n")
255}
256
257func formatMapOutput(indent, outputName string, outputMap map[string]interface{}) string {
258 ks := make([]string, 0, len(outputMap))
259 for k, _ := range outputMap {
260 ks = append(ks, k)
261 }
262 sort.Strings(ks)
263
264 keyIndent := ""
265
266 outputBuf := new(bytes.Buffer)
267 if outputName != "" {
268 outputBuf.WriteString(fmt.Sprintf("%s%s = {", indent, outputName))
269 keyIndent = " "
270 }
271
272 for _, k := range ks {
273 v := outputMap[k]
274 outputBuf.WriteString(fmt.Sprintf("\n%s%s%s = %v", indent, keyIndent, k, v))
275 }
276
277 if outputName != "" {
278 if len(outputMap) > 0 {
279 outputBuf.WriteString(fmt.Sprintf("\n%s}", indent))
280 } else {
281 outputBuf.WriteString("}")
282 }
283 }
284
285 return strings.TrimPrefix(outputBuf.String(), "\n")
286}