]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/config/hcl2shim/flatmap.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / config / hcl2shim / flatmap.go
1 package hcl2shim
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7
8 "github.com/zclconf/go-cty/cty/convert"
9
10 "github.com/zclconf/go-cty/cty"
11 )
12
13 // FlatmapValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic
14 // types library that HCL2 uses) to a map compatible with what would be
15 // produced by the "flatmap" package.
16 //
17 // The type of the given value informs the structure of the resulting map.
18 // The value must be of an object type or this function will panic.
19 //
20 // Flatmap values can only represent maps when they are of primitive types,
21 // so the given value must not have any maps of complex types or the result
22 // is undefined.
23 func FlatmapValueFromHCL2(v cty.Value) map[string]string {
24 if v.IsNull() {
25 return nil
26 }
27
28 if !v.Type().IsObjectType() {
29 panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", v.Type()))
30 }
31
32 m := make(map[string]string)
33 flatmapValueFromHCL2Map(m, "", v)
34 return m
35 }
36
37 func flatmapValueFromHCL2Value(m map[string]string, key string, val cty.Value) {
38 ty := val.Type()
39 switch {
40 case ty.IsPrimitiveType() || ty == cty.DynamicPseudoType:
41 flatmapValueFromHCL2Primitive(m, key, val)
42 case ty.IsObjectType() || ty.IsMapType():
43 flatmapValueFromHCL2Map(m, key+".", val)
44 case ty.IsTupleType() || ty.IsListType() || ty.IsSetType():
45 flatmapValueFromHCL2Seq(m, key+".", val)
46 default:
47 panic(fmt.Sprintf("cannot encode %s to flatmap", ty.FriendlyName()))
48 }
49 }
50
51 func flatmapValueFromHCL2Primitive(m map[string]string, key string, val cty.Value) {
52 if !val.IsKnown() {
53 m[key] = UnknownVariableValue
54 return
55 }
56 if val.IsNull() {
57 // Omit entirely
58 return
59 }
60
61 var err error
62 val, err = convert.Convert(val, cty.String)
63 if err != nil {
64 // Should not be possible, since all primitive types can convert to string.
65 panic(fmt.Sprintf("invalid primitive encoding to flatmap: %s", err))
66 }
67 m[key] = val.AsString()
68 }
69
70 func flatmapValueFromHCL2Map(m map[string]string, prefix string, val cty.Value) {
71 if val.IsNull() {
72 // Omit entirely
73 return
74 }
75 if !val.IsKnown() {
76 switch {
77 case val.Type().IsObjectType():
78 // Whole objects can't be unknown in flatmap, so instead we'll
79 // just write all of the attribute values out as unknown.
80 for name, aty := range val.Type().AttributeTypes() {
81 flatmapValueFromHCL2Value(m, prefix+name, cty.UnknownVal(aty))
82 }
83 default:
84 m[prefix+"%"] = UnknownVariableValue
85 }
86 return
87 }
88
89 len := 0
90 for it := val.ElementIterator(); it.Next(); {
91 ak, av := it.Element()
92 name := ak.AsString()
93 flatmapValueFromHCL2Value(m, prefix+name, av)
94 len++
95 }
96 if !val.Type().IsObjectType() { // objects don't have an explicit count included, since their attribute count is fixed
97 m[prefix+"%"] = strconv.Itoa(len)
98 }
99 }
100
101 func flatmapValueFromHCL2Seq(m map[string]string, prefix string, val cty.Value) {
102 if val.IsNull() {
103 // Omit entirely
104 return
105 }
106 if !val.IsKnown() {
107 m[prefix+"#"] = UnknownVariableValue
108 return
109 }
110
111 // For sets this won't actually generate exactly what helper/schema would've
112 // generated, because we don't have access to the set key function it
113 // would've used. However, in practice it doesn't actually matter what the
114 // keys are as long as they are unique, so we'll just generate sequential
115 // indexes for them as if it were a list.
116 //
117 // An important implication of this, however, is that the set ordering will
118 // not be consistent across mutations and so different keys may be assigned
119 // to the same value when round-tripping. Since this shim is intended to
120 // be short-lived and not used for round-tripping, we accept this.
121 i := 0
122 for it := val.ElementIterator(); it.Next(); {
123 _, av := it.Element()
124 key := prefix + strconv.Itoa(i)
125 flatmapValueFromHCL2Value(m, key, av)
126 i++
127 }
128 m[prefix+"#"] = strconv.Itoa(i)
129 }
130
131 // HCL2ValueFromFlatmap converts a map compatible with what would be produced
132 // by the "flatmap" package to a HCL2 (really, the cty dynamic types library
133 // that HCL2 uses) object type.
134 //
135 // The intended result type must be provided in order to guide how the
136 // map contents are decoded. This must be an object type or this function
137 // will panic.
138 //
139 // Flatmap values can only represent maps when they are of primitive types,
140 // so the given type must not have any maps of complex types or the result
141 // is undefined.
142 //
143 // The result may contain null values if the given map does not contain keys
144 // for all of the different key paths implied by the given type.
145 func HCL2ValueFromFlatmap(m map[string]string, ty cty.Type) (cty.Value, error) {
146 if m == nil {
147 return cty.NullVal(ty), nil
148 }
149 if !ty.IsObjectType() {
150 panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", ty))
151 }
152
153 return hcl2ValueFromFlatmapObject(m, "", ty.AttributeTypes())
154 }
155
156 func hcl2ValueFromFlatmapValue(m map[string]string, key string, ty cty.Type) (cty.Value, error) {
157 var val cty.Value
158 var err error
159 switch {
160 case ty.IsPrimitiveType():
161 val, err = hcl2ValueFromFlatmapPrimitive(m, key, ty)
162 case ty.IsObjectType():
163 val, err = hcl2ValueFromFlatmapObject(m, key+".", ty.AttributeTypes())
164 case ty.IsTupleType():
165 val, err = hcl2ValueFromFlatmapTuple(m, key+".", ty.TupleElementTypes())
166 case ty.IsMapType():
167 val, err = hcl2ValueFromFlatmapMap(m, key+".", ty)
168 case ty.IsListType():
169 val, err = hcl2ValueFromFlatmapList(m, key+".", ty)
170 case ty.IsSetType():
171 val, err = hcl2ValueFromFlatmapSet(m, key+".", ty)
172 default:
173 err = fmt.Errorf("cannot decode %s from flatmap", ty.FriendlyName())
174 }
175
176 if err != nil {
177 return cty.DynamicVal, err
178 }
179 return val, nil
180 }
181
182 func hcl2ValueFromFlatmapPrimitive(m map[string]string, key string, ty cty.Type) (cty.Value, error) {
183 rawVal, exists := m[key]
184 if !exists {
185 return cty.NullVal(ty), nil
186 }
187 if rawVal == UnknownVariableValue {
188 return cty.UnknownVal(ty), nil
189 }
190
191 var err error
192 val := cty.StringVal(rawVal)
193 val, err = convert.Convert(val, ty)
194 if err != nil {
195 // This should never happen for _valid_ input, but flatmap data might
196 // be tampered with by the user and become invalid.
197 return cty.DynamicVal, fmt.Errorf("invalid value for %q in state: %s", key, err)
198 }
199
200 return val, nil
201 }
202
203 func hcl2ValueFromFlatmapObject(m map[string]string, prefix string, atys map[string]cty.Type) (cty.Value, error) {
204 vals := make(map[string]cty.Value)
205 for name, aty := range atys {
206 val, err := hcl2ValueFromFlatmapValue(m, prefix+name, aty)
207 if err != nil {
208 return cty.DynamicVal, err
209 }
210 vals[name] = val
211 }
212 return cty.ObjectVal(vals), nil
213 }
214
215 func hcl2ValueFromFlatmapTuple(m map[string]string, prefix string, etys []cty.Type) (cty.Value, error) {
216 var vals []cty.Value
217
218 // if the container is unknown, there is no count string
219 listName := strings.TrimRight(prefix, ".")
220 if m[listName] == UnknownVariableValue {
221 return cty.UnknownVal(cty.Tuple(etys)), nil
222 }
223
224 countStr, exists := m[prefix+"#"]
225 if !exists {
226 return cty.NullVal(cty.Tuple(etys)), nil
227 }
228 if countStr == UnknownVariableValue {
229 return cty.UnknownVal(cty.Tuple(etys)), nil
230 }
231
232 count, err := strconv.Atoi(countStr)
233 if err != nil {
234 return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err)
235 }
236 if count != len(etys) {
237 return cty.DynamicVal, fmt.Errorf("wrong number of values for %q in state: got %d, but need %d", prefix, count, len(etys))
238 }
239
240 vals = make([]cty.Value, len(etys))
241 for i, ety := range etys {
242 key := prefix + strconv.Itoa(i)
243 val, err := hcl2ValueFromFlatmapValue(m, key, ety)
244 if err != nil {
245 return cty.DynamicVal, err
246 }
247 vals[i] = val
248 }
249 return cty.TupleVal(vals), nil
250 }
251
252 func hcl2ValueFromFlatmapMap(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) {
253 vals := make(map[string]cty.Value)
254 ety := ty.ElementType()
255
256 // if the container is unknown, there is no count string
257 listName := strings.TrimRight(prefix, ".")
258 if m[listName] == UnknownVariableValue {
259 return cty.UnknownVal(ty), nil
260 }
261
262 // We actually don't really care about the "count" of a map for our
263 // purposes here, but we do need to check if it _exists_ in order to
264 // recognize the difference between null (not set at all) and empty.
265 if strCount, exists := m[prefix+"%"]; !exists {
266 return cty.NullVal(ty), nil
267 } else if strCount == UnknownVariableValue {
268 return cty.UnknownVal(ty), nil
269 }
270
271 for fullKey := range m {
272 if !strings.HasPrefix(fullKey, prefix) {
273 continue
274 }
275
276 // The flatmap format doesn't allow us to distinguish between keys
277 // that contain periods and nested objects, so by convention a
278 // map is only ever of primitive type in flatmap, and we just assume
279 // that the remainder of the raw key (dots and all) is the key we
280 // want in the result value.
281 key := fullKey[len(prefix):]
282 if key == "%" {
283 // Ignore the "count" key
284 continue
285 }
286
287 val, err := hcl2ValueFromFlatmapValue(m, fullKey, ety)
288 if err != nil {
289 return cty.DynamicVal, err
290 }
291 vals[key] = val
292 }
293
294 if len(vals) == 0 {
295 return cty.MapValEmpty(ety), nil
296 }
297 return cty.MapVal(vals), nil
298 }
299
300 func hcl2ValueFromFlatmapList(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) {
301 var vals []cty.Value
302
303 // if the container is unknown, there is no count string
304 listName := strings.TrimRight(prefix, ".")
305 if m[listName] == UnknownVariableValue {
306 return cty.UnknownVal(ty), nil
307 }
308
309 countStr, exists := m[prefix+"#"]
310 if !exists {
311 return cty.NullVal(ty), nil
312 }
313 if countStr == UnknownVariableValue {
314 return cty.UnknownVal(ty), nil
315 }
316
317 count, err := strconv.Atoi(countStr)
318 if err != nil {
319 return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err)
320 }
321
322 ety := ty.ElementType()
323 if count == 0 {
324 return cty.ListValEmpty(ety), nil
325 }
326
327 vals = make([]cty.Value, count)
328 for i := 0; i < count; i++ {
329 key := prefix + strconv.Itoa(i)
330 val, err := hcl2ValueFromFlatmapValue(m, key, ety)
331 if err != nil {
332 return cty.DynamicVal, err
333 }
334 vals[i] = val
335 }
336
337 return cty.ListVal(vals), nil
338 }
339
340 func hcl2ValueFromFlatmapSet(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) {
341 var vals []cty.Value
342 ety := ty.ElementType()
343
344 // if the container is unknown, there is no count string
345 listName := strings.TrimRight(prefix, ".")
346 if m[listName] == UnknownVariableValue {
347 return cty.UnknownVal(ty), nil
348 }
349
350 strCount, exists := m[prefix+"#"]
351 if !exists {
352 return cty.NullVal(ty), nil
353 } else if strCount == UnknownVariableValue {
354 return cty.UnknownVal(ty), nil
355 }
356
357 // Keep track of keys we've seen, se we don't add the same set value
358 // multiple times. The cty.Set will normally de-duplicate values, but we may
359 // have unknown values that would not show as equivalent.
360 seen := map[string]bool{}
361
362 for fullKey := range m {
363 if !strings.HasPrefix(fullKey, prefix) {
364 continue
365 }
366 subKey := fullKey[len(prefix):]
367 if subKey == "#" {
368 // Ignore the "count" key
369 continue
370 }
371 key := fullKey
372 if dot := strings.IndexByte(subKey, '.'); dot != -1 {
373 key = fullKey[:dot+len(prefix)]
374 }
375
376 if seen[key] {
377 continue
378 }
379
380 seen[key] = true
381
382 // The flatmap format doesn't allow us to distinguish between keys
383 // that contain periods and nested objects, so by convention a
384 // map is only ever of primitive type in flatmap, and we just assume
385 // that the remainder of the raw key (dots and all) is the key we
386 // want in the result value.
387
388 val, err := hcl2ValueFromFlatmapValue(m, key, ety)
389 if err != nil {
390 return cty.DynamicVal, err
391 }
392 vals = append(vals, val)
393 }
394
395 if len(vals) == 0 && strCount == "1" {
396 // An empty set wouldn't be represented in the flatmap, so this must be
397 // a single empty object since the count is actually 1.
398 // Add an appropriately typed null value to the set.
399 var val cty.Value
400 switch {
401 case ety.IsMapType():
402 val = cty.MapValEmpty(ety)
403 case ety.IsListType():
404 val = cty.ListValEmpty(ety)
405 case ety.IsSetType():
406 val = cty.SetValEmpty(ety)
407 case ety.IsObjectType():
408 // TODO: cty.ObjectValEmpty
409 objectMap := map[string]cty.Value{}
410 for attr, ty := range ety.AttributeTypes() {
411 objectMap[attr] = cty.NullVal(ty)
412 }
413 val = cty.ObjectVal(objectMap)
414 default:
415 val = cty.NullVal(ety)
416 }
417 vals = append(vals, val)
418
419 } else if len(vals) == 0 {
420 return cty.SetValEmpty(ety), nil
421 }
422
423 return cty.SetVal(vals), nil
424 }