]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package flatmap |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "sort" | |
6 | "strconv" | |
7 | "strings" | |
8 | ||
9 | "github.com/hashicorp/hil" | |
10 | ) | |
11 | ||
12 | // Expand takes a map and a key (prefix) and expands that value into | |
13 | // a more complex structure. This is the reverse of the Flatten operation. | |
14 | func Expand(m map[string]string, key string) interface{} { | |
15 | // If the key is exactly a key in the map, just return it | |
16 | if v, ok := m[key]; ok { | |
17 | if v == "true" { | |
18 | return true | |
19 | } else if v == "false" { | |
20 | return false | |
21 | } | |
22 | ||
23 | return v | |
24 | } | |
25 | ||
26 | // Check if the key is an array, and if so, expand the array | |
27 | if v, ok := m[key+".#"]; ok { | |
28 | // If the count of the key is unknown, then just put the unknown | |
29 | // value in the value itself. This will be detected by Terraform | |
30 | // core later. | |
31 | if v == hil.UnknownValue { | |
32 | return v | |
33 | } | |
34 | ||
35 | return expandArray(m, key) | |
36 | } | |
37 | ||
38 | // Check if this is a prefix in the map | |
39 | prefix := key + "." | |
40 | for k := range m { | |
41 | if strings.HasPrefix(k, prefix) { | |
42 | return expandMap(m, prefix) | |
43 | } | |
44 | } | |
45 | ||
46 | return nil | |
47 | } | |
48 | ||
49 | func expandArray(m map[string]string, prefix string) []interface{} { | |
50 | num, err := strconv.ParseInt(m[prefix+".#"], 0, 0) | |
51 | if err != nil { | |
52 | panic(err) | |
53 | } | |
54 | ||
55 | // If the number of elements in this array is 0, then return an | |
56 | // empty slice as there is nothing to expand. Trying to expand it | |
57 | // anyway could lead to crashes as any child maps, arrays or sets | |
58 | // that no longer exist are still shown as empty with a count of 0. | |
59 | if num == 0 { | |
60 | return []interface{}{} | |
61 | } | |
62 | ||
c680a8e1 RS |
63 | // NOTE: "num" is not necessarily accurate, e.g. if a user tampers |
64 | // with state, so the following code should not crash when given a | |
65 | // number of items more or less than what's given in num. The | |
66 | // num key is mainly just a hint that this is a list or set. | |
67 | ||
bae9f6d2 JC |
68 | // The Schema "Set" type stores its values in an array format, but |
69 | // using numeric hash values instead of ordinal keys. Take the set | |
70 | // of keys regardless of value, and expand them in numeric order. | |
71 | // See GH-11042 for more details. | |
72 | keySet := map[int]bool{} | |
73 | computed := map[string]bool{} | |
74 | for k := range m { | |
75 | if !strings.HasPrefix(k, prefix+".") { | |
76 | continue | |
77 | } | |
78 | ||
79 | key := k[len(prefix)+1:] | |
80 | idx := strings.Index(key, ".") | |
81 | if idx != -1 { | |
82 | key = key[:idx] | |
83 | } | |
84 | ||
85 | // skip the count value | |
86 | if key == "#" { | |
87 | continue | |
88 | } | |
89 | ||
90 | // strip the computed flag if there is one | |
91 | if strings.HasPrefix(key, "~") { | |
92 | key = key[1:] | |
93 | computed[key] = true | |
94 | } | |
95 | ||
96 | k, err := strconv.Atoi(key) | |
97 | if err != nil { | |
98 | panic(err) | |
99 | } | |
100 | keySet[int(k)] = true | |
101 | } | |
102 | ||
103 | keysList := make([]int, 0, num) | |
104 | for key := range keySet { | |
105 | keysList = append(keysList, key) | |
106 | } | |
107 | sort.Ints(keysList) | |
108 | ||
c680a8e1 | 109 | result := make([]interface{}, len(keysList)) |
bae9f6d2 JC |
110 | for i, key := range keysList { |
111 | keyString := strconv.Itoa(key) | |
112 | if computed[keyString] { | |
113 | keyString = "~" + keyString | |
114 | } | |
115 | result[i] = Expand(m, fmt.Sprintf("%s.%s", prefix, keyString)) | |
116 | } | |
117 | ||
118 | return result | |
119 | } | |
120 | ||
121 | func expandMap(m map[string]string, prefix string) map[string]interface{} { | |
122 | // Submaps may not have a '%' key, so we can't count on this value being | |
123 | // here. If we don't have a count, just proceed as if we have have a map. | |
124 | if count, ok := m[prefix+"%"]; ok && count == "0" { | |
125 | return map[string]interface{}{} | |
126 | } | |
127 | ||
128 | result := make(map[string]interface{}) | |
129 | for k := range m { | |
130 | if !strings.HasPrefix(k, prefix) { | |
131 | continue | |
132 | } | |
133 | ||
134 | key := k[len(prefix):] | |
135 | idx := strings.Index(key, ".") | |
136 | if idx != -1 { | |
137 | key = key[:idx] | |
138 | } | |
139 | if _, ok := result[key]; ok { | |
140 | continue | |
141 | } | |
142 | ||
143 | // skip the map count value | |
144 | if key == "%" { | |
145 | continue | |
146 | } | |
147 | ||
148 | result[key] = Expand(m, k[:len(prefix)+len(key)]) | |
149 | } | |
150 | ||
151 | return result | |
152 | } |