diff options
author | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
---|---|---|
committer | Nathan Dench <ndenc2@gmail.com> | 2019-05-24 15:16:44 +1000 |
commit | 107c1cdb09c575aa2f61d97f48d8587eb6bada4c (patch) | |
tree | ca7d008643efc555c388baeaf1d986e0b6b3e28c /vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go | |
parent | 844b5a68d8af4791755b8f0ad293cc99f5959183 (diff) | |
download | terraform-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/plans/objchange/objchange.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go b/vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go new file mode 100644 index 0000000..5a8af14 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go | |||
@@ -0,0 +1,390 @@ | |||
1 | package objchange | ||
2 | |||
3 | import ( | ||
4 | "fmt" | ||
5 | |||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | |||
8 | "github.com/hashicorp/terraform/configs/configschema" | ||
9 | ) | ||
10 | |||
11 | // ProposedNewObject constructs a proposed new object value by combining the | ||
12 | // computed attribute values from "prior" with the configured attribute values | ||
13 | // from "config". | ||
14 | // | ||
15 | // Both value must conform to the given schema's implied type, or this function | ||
16 | // will panic. | ||
17 | // | ||
18 | // The prior value must be wholly known, but the config value may be unknown | ||
19 | // or have nested unknown values. | ||
20 | // | ||
21 | // The merging of the two objects includes the attributes of any nested blocks, | ||
22 | // which will be correlated in a manner appropriate for their nesting mode. | ||
23 | // Note in particular that the correlation for blocks backed by sets is a | ||
24 | // heuristic based on matching non-computed attribute values and so it may | ||
25 | // produce strange results with more "extreme" cases, such as a nested set | ||
26 | // block where _all_ attributes are computed. | ||
27 | func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.Value { | ||
28 | // If the config and prior are both null, return early here before | ||
29 | // populating the prior block. The prevents non-null blocks from appearing | ||
30 | // the proposed state value. | ||
31 | if config.IsNull() && prior.IsNull() { | ||
32 | return prior | ||
33 | } | ||
34 | |||
35 | if prior.IsNull() { | ||
36 | // In this case, we will construct a synthetic prior value that is | ||
37 | // similar to the result of decoding an empty configuration block, | ||
38 | // which simplifies our handling of the top-level attributes/blocks | ||
39 | // below by giving us one non-null level of object to pull values from. | ||
40 | prior = AllAttributesNull(schema) | ||
41 | } | ||
42 | return proposedNewObject(schema, prior, config) | ||
43 | } | ||
44 | |||
45 | // PlannedDataResourceObject is similar to ProposedNewObject but tailored for | ||
46 | // planning data resources in particular. Specifically, it replaces the values | ||
47 | // of any Computed attributes not set in the configuration with an unknown | ||
48 | // value, which serves as a placeholder for a value to be filled in by the | ||
49 | // provider when the data resource is finally read. | ||
50 | // | ||
51 | // Data resources are different because the planning of them is handled | ||
52 | // entirely within Terraform Core and not subject to customization by the | ||
53 | // provider. This function is, in effect, producing an equivalent result to | ||
54 | // passing the ProposedNewObject result into a provider's PlanResourceChange | ||
55 | // function, assuming a fixed implementation of PlanResourceChange that just | ||
56 | // fills in unknown values as needed. | ||
57 | func PlannedDataResourceObject(schema *configschema.Block, config cty.Value) cty.Value { | ||
58 | // Our trick here is to run the ProposedNewObject logic with an | ||
59 | // entirely-unknown prior value. Because of cty's unknown short-circuit | ||
60 | // behavior, any operation on prior returns another unknown, and so | ||
61 | // unknown values propagate into all of the parts of the resulting value | ||
62 | // that would normally be filled in by preserving the prior state. | ||
63 | prior := cty.UnknownVal(schema.ImpliedType()) | ||
64 | return proposedNewObject(schema, prior, config) | ||
65 | } | ||
66 | |||
67 | func proposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.Value { | ||
68 | if config.IsNull() || !config.IsKnown() { | ||
69 | // This is a weird situation, but we'll allow it anyway to free | ||
70 | // callers from needing to specifically check for these cases. | ||
71 | return prior | ||
72 | } | ||
73 | if (!prior.Type().IsObjectType()) || (!config.Type().IsObjectType()) { | ||
74 | panic("ProposedNewObject only supports object-typed values") | ||
75 | } | ||
76 | |||
77 | // From this point onwards, we can assume that both values are non-null | ||
78 | // object types, and that the config value itself is known (though it | ||
79 | // may contain nested values that are unknown.) | ||
80 | |||
81 | newAttrs := map[string]cty.Value{} | ||
82 | for name, attr := range schema.Attributes { | ||
83 | priorV := prior.GetAttr(name) | ||
84 | configV := config.GetAttr(name) | ||
85 | var newV cty.Value | ||
86 | switch { | ||
87 | case attr.Computed && attr.Optional: | ||
88 | // This is the trickiest scenario: we want to keep the prior value | ||
89 | // if the config isn't overriding it. Note that due to some | ||
90 | // ambiguity here, setting an optional+computed attribute from | ||
91 | // config and then later switching the config to null in a | ||
92 | // subsequent change causes the initial config value to be "sticky" | ||
93 | // unless the provider specifically overrides it during its own | ||
94 | // plan customization step. | ||
95 | if configV.IsNull() { | ||
96 | newV = priorV | ||
97 | } else { | ||
98 | newV = configV | ||
99 | } | ||
100 | case attr.Computed: | ||
101 | // configV will always be null in this case, by definition. | ||
102 | // priorV may also be null, but that's okay. | ||
103 | newV = priorV | ||
104 | default: | ||
105 | // For non-computed attributes, we always take the config value, | ||
106 | // even if it is null. If it's _required_ then null values | ||
107 | // should've been caught during an earlier validation step, and | ||
108 | // so we don't really care about that here. | ||
109 | newV = configV | ||
110 | } | ||
111 | newAttrs[name] = newV | ||
112 | } | ||
113 | |||
114 | // Merging nested blocks is a little more complex, since we need to | ||
115 | // correlate blocks between both objects and then recursively propose | ||
116 | // a new object for each. The correlation logic depends on the nesting | ||
117 | // mode for each block type. | ||
118 | for name, blockType := range schema.BlockTypes { | ||
119 | priorV := prior.GetAttr(name) | ||
120 | configV := config.GetAttr(name) | ||
121 | var newV cty.Value | ||
122 | switch blockType.Nesting { | ||
123 | |||
124 | case configschema.NestingSingle, configschema.NestingGroup: | ||
125 | newV = ProposedNewObject(&blockType.Block, priorV, configV) | ||
126 | |||
127 | case configschema.NestingList: | ||
128 | // Nested blocks are correlated by index. | ||
129 | configVLen := 0 | ||
130 | if configV.IsKnown() && !configV.IsNull() { | ||
131 | configVLen = configV.LengthInt() | ||
132 | } | ||
133 | if configVLen > 0 { | ||
134 | newVals := make([]cty.Value, 0, configVLen) | ||
135 | for it := configV.ElementIterator(); it.Next(); { | ||
136 | idx, configEV := it.Element() | ||
137 | if priorV.IsKnown() && (priorV.IsNull() || !priorV.HasIndex(idx).True()) { | ||
138 | // If there is no corresponding prior element then | ||
139 | // we just take the config value as-is. | ||
140 | newVals = append(newVals, configEV) | ||
141 | continue | ||
142 | } | ||
143 | priorEV := priorV.Index(idx) | ||
144 | |||
145 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
146 | newVals = append(newVals, newEV) | ||
147 | } | ||
148 | // Despite the name, a NestingList might also be a tuple, if | ||
149 | // its nested schema contains dynamically-typed attributes. | ||
150 | if configV.Type().IsTupleType() { | ||
151 | newV = cty.TupleVal(newVals) | ||
152 | } else { | ||
153 | newV = cty.ListVal(newVals) | ||
154 | } | ||
155 | } else { | ||
156 | // Despite the name, a NestingList might also be a tuple, if | ||
157 | // its nested schema contains dynamically-typed attributes. | ||
158 | if configV.Type().IsTupleType() { | ||
159 | newV = cty.EmptyTupleVal | ||
160 | } else { | ||
161 | newV = cty.ListValEmpty(blockType.ImpliedType()) | ||
162 | } | ||
163 | } | ||
164 | |||
165 | case configschema.NestingMap: | ||
166 | // Despite the name, a NestingMap may produce either a map or | ||
167 | // object value, depending on whether the nested schema contains | ||
168 | // dynamically-typed attributes. | ||
169 | if configV.Type().IsObjectType() { | ||
170 | // Nested blocks are correlated by key. | ||
171 | configVLen := 0 | ||
172 | if configV.IsKnown() && !configV.IsNull() { | ||
173 | configVLen = configV.LengthInt() | ||
174 | } | ||
175 | if configVLen > 0 { | ||
176 | newVals := make(map[string]cty.Value, configVLen) | ||
177 | atys := configV.Type().AttributeTypes() | ||
178 | for name := range atys { | ||
179 | configEV := configV.GetAttr(name) | ||
180 | if !priorV.IsKnown() || priorV.IsNull() || !priorV.Type().HasAttribute(name) { | ||
181 | // If there is no corresponding prior element then | ||
182 | // we just take the config value as-is. | ||
183 | newVals[name] = configEV | ||
184 | continue | ||
185 | } | ||
186 | priorEV := priorV.GetAttr(name) | ||
187 | |||
188 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
189 | newVals[name] = newEV | ||
190 | } | ||
191 | // Although we call the nesting mode "map", we actually use | ||
192 | // object values so that elements might have different types | ||
193 | // in case of dynamically-typed attributes. | ||
194 | newV = cty.ObjectVal(newVals) | ||
195 | } else { | ||
196 | newV = cty.EmptyObjectVal | ||
197 | } | ||
198 | } else { | ||
199 | configVLen := 0 | ||
200 | if configV.IsKnown() && !configV.IsNull() { | ||
201 | configVLen = configV.LengthInt() | ||
202 | } | ||
203 | if configVLen > 0 { | ||
204 | newVals := make(map[string]cty.Value, configVLen) | ||
205 | for it := configV.ElementIterator(); it.Next(); { | ||
206 | idx, configEV := it.Element() | ||
207 | k := idx.AsString() | ||
208 | if priorV.IsKnown() && (priorV.IsNull() || !priorV.HasIndex(idx).True()) { | ||
209 | // If there is no corresponding prior element then | ||
210 | // we just take the config value as-is. | ||
211 | newVals[k] = configEV | ||
212 | continue | ||
213 | } | ||
214 | priorEV := priorV.Index(idx) | ||
215 | |||
216 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
217 | newVals[k] = newEV | ||
218 | } | ||
219 | newV = cty.MapVal(newVals) | ||
220 | } else { | ||
221 | newV = cty.MapValEmpty(blockType.ImpliedType()) | ||
222 | } | ||
223 | } | ||
224 | |||
225 | case configschema.NestingSet: | ||
226 | if !configV.Type().IsSetType() { | ||
227 | panic("configschema.NestingSet value is not a set as expected") | ||
228 | } | ||
229 | |||
230 | // Nested blocks are correlated by comparing the element values | ||
231 | // after eliminating all of the computed attributes. In practice, | ||
232 | // this means that any config change produces an entirely new | ||
233 | // nested object, and we only propagate prior computed values | ||
234 | // if the non-computed attribute values are identical. | ||
235 | var cmpVals [][2]cty.Value | ||
236 | if priorV.IsKnown() && !priorV.IsNull() { | ||
237 | cmpVals = setElementCompareValues(&blockType.Block, priorV, false) | ||
238 | } | ||
239 | configVLen := 0 | ||
240 | if configV.IsKnown() && !configV.IsNull() { | ||
241 | configVLen = configV.LengthInt() | ||
242 | } | ||
243 | if configVLen > 0 { | ||
244 | used := make([]bool, len(cmpVals)) // track used elements in case multiple have the same compare value | ||
245 | newVals := make([]cty.Value, 0, configVLen) | ||
246 | for it := configV.ElementIterator(); it.Next(); { | ||
247 | _, configEV := it.Element() | ||
248 | var priorEV cty.Value | ||
249 | for i, cmp := range cmpVals { | ||
250 | if used[i] { | ||
251 | continue | ||
252 | } | ||
253 | if cmp[1].RawEquals(configEV) { | ||
254 | priorEV = cmp[0] | ||
255 | used[i] = true // we can't use this value on a future iteration | ||
256 | break | ||
257 | } | ||
258 | } | ||
259 | if priorEV == cty.NilVal { | ||
260 | priorEV = cty.NullVal(blockType.ImpliedType()) | ||
261 | } | ||
262 | |||
263 | newEV := ProposedNewObject(&blockType.Block, priorEV, configEV) | ||
264 | newVals = append(newVals, newEV) | ||
265 | } | ||
266 | newV = cty.SetVal(newVals) | ||
267 | } else { | ||
268 | newV = cty.SetValEmpty(blockType.Block.ImpliedType()) | ||
269 | } | ||
270 | |||
271 | default: | ||
272 | // Should never happen, since the above cases are comprehensive. | ||
273 | panic(fmt.Sprintf("unsupported block nesting mode %s", blockType.Nesting)) | ||
274 | } | ||
275 | |||
276 | newAttrs[name] = newV | ||
277 | } | ||
278 | |||
279 | return cty.ObjectVal(newAttrs) | ||
280 | } | ||
281 | |||
282 | // setElementCompareValues takes a known, non-null value of a cty.Set type and | ||
283 | // returns a table -- constructed of two-element arrays -- that maps original | ||
284 | // set element values to corresponding values that have all of the computed | ||
285 | // values removed, making them suitable for comparison with values obtained | ||
286 | // from configuration. The element type of the set must conform to the implied | ||
287 | // type of the given schema, or this function will panic. | ||
288 | // | ||
289 | // In the resulting slice, the zeroth element of each array is the original | ||
290 | // value and the one-indexed element is the corresponding "compare value". | ||
291 | // | ||
292 | // This is intended to help correlate prior elements with configured elements | ||
293 | // in ProposedNewObject. The result is a heuristic rather than an exact science, | ||
294 | // since e.g. two separate elements may reduce to the same value through this | ||
295 | // process. The caller must therefore be ready to deal with duplicates. | ||
296 | func setElementCompareValues(schema *configschema.Block, set cty.Value, isConfig bool) [][2]cty.Value { | ||
297 | ret := make([][2]cty.Value, 0, set.LengthInt()) | ||
298 | for it := set.ElementIterator(); it.Next(); { | ||
299 | _, ev := it.Element() | ||
300 | ret = append(ret, [2]cty.Value{ev, setElementCompareValue(schema, ev, isConfig)}) | ||
301 | } | ||
302 | return ret | ||
303 | } | ||
304 | |||
305 | // setElementCompareValue creates a new value that has all of the same | ||
306 | // non-computed attribute values as the one given but has all computed | ||
307 | // attribute values forced to null. | ||
308 | // | ||
309 | // If isConfig is true then non-null Optional+Computed attribute values will | ||
310 | // be preserved. Otherwise, they will also be set to null. | ||
311 | // | ||
312 | // The input value must conform to the schema's implied type, and the return | ||
313 | // value is guaranteed to conform to it. | ||
314 | func setElementCompareValue(schema *configschema.Block, v cty.Value, isConfig bool) cty.Value { | ||
315 | if v.IsNull() || !v.IsKnown() { | ||
316 | return v | ||
317 | } | ||
318 | |||
319 | attrs := map[string]cty.Value{} | ||
320 | for name, attr := range schema.Attributes { | ||
321 | switch { | ||
322 | case attr.Computed && attr.Optional: | ||
323 | if isConfig { | ||
324 | attrs[name] = v.GetAttr(name) | ||
325 | } else { | ||
326 | attrs[name] = cty.NullVal(attr.Type) | ||
327 | } | ||
328 | case attr.Computed: | ||
329 | attrs[name] = cty.NullVal(attr.Type) | ||
330 | default: | ||
331 | attrs[name] = v.GetAttr(name) | ||
332 | } | ||
333 | } | ||
334 | |||
335 | for name, blockType := range schema.BlockTypes { | ||
336 | switch blockType.Nesting { | ||
337 | |||
338 | case configschema.NestingSingle, configschema.NestingGroup: | ||
339 | attrs[name] = setElementCompareValue(&blockType.Block, v.GetAttr(name), isConfig) | ||
340 | |||
341 | case configschema.NestingList, configschema.NestingSet: | ||
342 | cv := v.GetAttr(name) | ||
343 | if cv.IsNull() || !cv.IsKnown() { | ||
344 | attrs[name] = cv | ||
345 | continue | ||
346 | } | ||
347 | if l := cv.LengthInt(); l > 0 { | ||
348 | elems := make([]cty.Value, 0, l) | ||
349 | for it := cv.ElementIterator(); it.Next(); { | ||
350 | _, ev := it.Element() | ||
351 | elems = append(elems, setElementCompareValue(&blockType.Block, ev, isConfig)) | ||
352 | } | ||
353 | if blockType.Nesting == configschema.NestingSet { | ||
354 | // SetValEmpty would panic if given elements that are not | ||
355 | // all of the same type, but that's guaranteed not to | ||
356 | // happen here because our input value was _already_ a | ||
357 | // set and we've not changed the types of any elements here. | ||
358 | attrs[name] = cty.SetVal(elems) | ||
359 | } else { | ||
360 | attrs[name] = cty.TupleVal(elems) | ||
361 | } | ||
362 | } else { | ||
363 | if blockType.Nesting == configschema.NestingSet { | ||
364 | attrs[name] = cty.SetValEmpty(blockType.Block.ImpliedType()) | ||
365 | } else { | ||
366 | attrs[name] = cty.EmptyTupleVal | ||
367 | } | ||
368 | } | ||
369 | |||
370 | case configschema.NestingMap: | ||
371 | cv := v.GetAttr(name) | ||
372 | if cv.IsNull() || !cv.IsKnown() { | ||
373 | attrs[name] = cv | ||
374 | continue | ||
375 | } | ||
376 | elems := make(map[string]cty.Value) | ||
377 | for it := cv.ElementIterator(); it.Next(); { | ||
378 | kv, ev := it.Element() | ||
379 | elems[kv.AsString()] = setElementCompareValue(&blockType.Block, ev, isConfig) | ||
380 | } | ||
381 | attrs[name] = cty.ObjectVal(elems) | ||
382 | |||
383 | default: | ||
384 | // Should never happen, since the above cases are comprehensive. | ||
385 | panic(fmt.Sprintf("unsupported block nesting mode %s", blockType.Nesting)) | ||
386 | } | ||
387 | } | ||
388 | |||
389 | return cty.ObjectVal(attrs) | ||
390 | } | ||