]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
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 | } |