]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/plans/objchange/objchange.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / plans / objchange / objchange.go
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 }