]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/plans/objchange/normalize_obj.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / plans / objchange / normalize_obj.go
1 package objchange
2
3 import (
4 "github.com/hashicorp/terraform/configs/configschema"
5 "github.com/zclconf/go-cty/cty"
6 )
7
8 // NormalizeObjectFromLegacySDK takes an object that may have been generated
9 // by the legacy Terraform SDK (i.e. returned from a provider with the
10 // LegacyTypeSystem opt-out set) and does its best to normalize it for the
11 // assumptions we would normally enforce if the provider had not opted out.
12 //
13 // In particular, this function guarantees that a value representing a nested
14 // block will never itself be unknown or null, instead representing that as
15 // a non-null value that may contain null/unknown values.
16 //
17 // The input value must still conform to the implied type of the given schema,
18 // or else this function may produce garbage results or panic. This is usually
19 // okay because type consistency is enforced when deserializing the value
20 // returned from the provider over the RPC wire protocol anyway.
21 func NormalizeObjectFromLegacySDK(val cty.Value, schema *configschema.Block) cty.Value {
22 if val == cty.NilVal || val.IsNull() {
23 // This should never happen in reasonable use, but we'll allow it
24 // and normalize to a null of the expected type rather than panicking
25 // below.
26 return cty.NullVal(schema.ImpliedType())
27 }
28
29 vals := make(map[string]cty.Value)
30 for name := range schema.Attributes {
31 // No normalization for attributes, since them being type-conformant
32 // is all that we require.
33 vals[name] = val.GetAttr(name)
34 }
35 for name, blockS := range schema.BlockTypes {
36 lv := val.GetAttr(name)
37
38 // Legacy SDK never generates dynamically-typed attributes and so our
39 // normalization code doesn't deal with them, but we need to make sure
40 // we still pass them through properly so that we don't interfere with
41 // objects generated by other SDKs.
42 if ty := blockS.Block.ImpliedType(); ty.HasDynamicTypes() {
43 vals[name] = lv
44 continue
45 }
46
47 switch blockS.Nesting {
48 case configschema.NestingSingle, configschema.NestingGroup:
49 if lv.IsKnown() {
50 if lv.IsNull() && blockS.Nesting == configschema.NestingGroup {
51 vals[name] = blockS.EmptyValue()
52 } else {
53 vals[name] = NormalizeObjectFromLegacySDK(lv, &blockS.Block)
54 }
55 } else {
56 vals[name] = unknownBlockStub(&blockS.Block)
57 }
58 case configschema.NestingList:
59 switch {
60 case !lv.IsKnown():
61 vals[name] = cty.ListVal([]cty.Value{unknownBlockStub(&blockS.Block)})
62 case lv.IsNull() || lv.LengthInt() == 0:
63 vals[name] = cty.ListValEmpty(blockS.Block.ImpliedType())
64 default:
65 subVals := make([]cty.Value, 0, lv.LengthInt())
66 for it := lv.ElementIterator(); it.Next(); {
67 _, subVal := it.Element()
68 subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block))
69 }
70 vals[name] = cty.ListVal(subVals)
71 }
72 case configschema.NestingSet:
73 switch {
74 case !lv.IsKnown():
75 vals[name] = cty.SetVal([]cty.Value{unknownBlockStub(&blockS.Block)})
76 case lv.IsNull() || lv.LengthInt() == 0:
77 vals[name] = cty.SetValEmpty(blockS.Block.ImpliedType())
78 default:
79 subVals := make([]cty.Value, 0, lv.LengthInt())
80 for it := lv.ElementIterator(); it.Next(); {
81 _, subVal := it.Element()
82 subVals = append(subVals, NormalizeObjectFromLegacySDK(subVal, &blockS.Block))
83 }
84 vals[name] = cty.SetVal(subVals)
85 }
86 default:
87 // The legacy SDK doesn't support NestingMap, so we just assume
88 // maps are always okay. (If not, we would've detected and returned
89 // an error to the user before we got here.)
90 vals[name] = lv
91 }
92 }
93 return cty.ObjectVal(vals)
94 }
95
96 // unknownBlockStub constructs an object value that approximates an unknown
97 // block by producing a known block object with all of its leaf attribute
98 // values set to unknown.
99 //
100 // Blocks themselves cannot be unknown, so if the legacy SDK tries to return
101 // such a thing, we'll use this result instead. This convention mimics how
102 // the dynamic block feature deals with being asked to iterate over an unknown
103 // value, because our value-checking functions already accept this convention
104 // as a special case.
105 func unknownBlockStub(schema *configschema.Block) cty.Value {
106 vals := make(map[string]cty.Value)
107 for name, attrS := range schema.Attributes {
108 vals[name] = cty.UnknownVal(attrS.Type)
109 }
110 for name, blockS := range schema.BlockTypes {
111 switch blockS.Nesting {
112 case configschema.NestingSingle, configschema.NestingGroup:
113 vals[name] = unknownBlockStub(&blockS.Block)
114 case configschema.NestingList:
115 // In principle we may be expected to produce a tuple value here,
116 // if there are any dynamically-typed attributes in our nested block,
117 // but the legacy SDK doesn't support that, so we just assume it'll
118 // never be necessary to normalize those. (Incorrect usage in any
119 // other SDK would be caught and returned as an error before we
120 // get here.)
121 vals[name] = cty.ListVal([]cty.Value{unknownBlockStub(&blockS.Block)})
122 case configschema.NestingSet:
123 vals[name] = cty.SetVal([]cty.Value{unknownBlockStub(&blockS.Block)})
124 case configschema.NestingMap:
125 // A nesting map can never be unknown since we then wouldn't know
126 // what the keys are. (Legacy SDK doesn't support NestingMap anyway,
127 // so this should never arise.)
128 vals[name] = cty.MapValEmpty(blockS.Block.ImpliedType())
129 }
130 }
131 return cty.ObjectVal(vals)
132 }