]>
Commit | Line | Data |
---|---|---|
1 | package schema | |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "strconv" | |
6 | "strings" | |
7 | "sync" | |
8 | ||
9 | "github.com/hashicorp/terraform/terraform" | |
10 | "github.com/mitchellh/mapstructure" | |
11 | ) | |
12 | ||
13 | // ConfigFieldReader reads fields out of an untyped map[string]string to the | |
14 | // best of its ability. It also applies defaults from the Schema. (The other | |
15 | // field readers do not need default handling because they source fully | |
16 | // populated data structures.) | |
17 | type ConfigFieldReader struct { | |
18 | Config *terraform.ResourceConfig | |
19 | Schema map[string]*Schema | |
20 | ||
21 | indexMaps map[string]map[string]int | |
22 | once sync.Once | |
23 | } | |
24 | ||
25 | func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) { | |
26 | r.once.Do(func() { r.indexMaps = make(map[string]map[string]int) }) | |
27 | return r.readField(address, false) | |
28 | } | |
29 | ||
30 | func (r *ConfigFieldReader) readField( | |
31 | address []string, nested bool) (FieldReadResult, error) { | |
32 | schemaList := addrToSchema(address, r.Schema) | |
33 | if len(schemaList) == 0 { | |
34 | return FieldReadResult{}, nil | |
35 | } | |
36 | ||
37 | if !nested { | |
38 | // If we have a set anywhere in the address, then we need to | |
39 | // read that set out in order and actually replace that part of | |
40 | // the address with the real list index. i.e. set.50 might actually | |
41 | // map to set.12 in the config, since it is in list order in the | |
42 | // config, not indexed by set value. | |
43 | for i, v := range schemaList { | |
44 | // Sets are the only thing that cause this issue. | |
45 | if v.Type != TypeSet { | |
46 | continue | |
47 | } | |
48 | ||
49 | // If we're at the end of the list, then we don't have to worry | |
50 | // about this because we're just requesting the whole set. | |
51 | if i == len(schemaList)-1 { | |
52 | continue | |
53 | } | |
54 | ||
55 | // If we're looking for the count, then ignore... | |
56 | if address[i+1] == "#" { | |
57 | continue | |
58 | } | |
59 | ||
60 | indexMap, ok := r.indexMaps[strings.Join(address[:i+1], ".")] | |
61 | if !ok { | |
62 | // Get the set so we can get the index map that tells us the | |
63 | // mapping of the hash code to the list index | |
64 | _, err := r.readSet(address[:i+1], v) | |
65 | if err != nil { | |
66 | return FieldReadResult{}, err | |
67 | } | |
68 | indexMap = r.indexMaps[strings.Join(address[:i+1], ".")] | |
69 | } | |
70 | ||
71 | index, ok := indexMap[address[i+1]] | |
72 | if !ok { | |
73 | return FieldReadResult{}, nil | |
74 | } | |
75 | ||
76 | address[i+1] = strconv.FormatInt(int64(index), 10) | |
77 | } | |
78 | } | |
79 | ||
80 | k := strings.Join(address, ".") | |
81 | schema := schemaList[len(schemaList)-1] | |
82 | ||
83 | // If we're getting the single element of a promoted list, then | |
84 | // check to see if we have a single element we need to promote. | |
85 | if address[len(address)-1] == "0" && len(schemaList) > 1 { | |
86 | lastSchema := schemaList[len(schemaList)-2] | |
87 | if lastSchema.Type == TypeList && lastSchema.PromoteSingle { | |
88 | k := strings.Join(address[:len(address)-1], ".") | |
89 | result, err := r.readPrimitive(k, schema) | |
90 | if err == nil { | |
91 | return result, nil | |
92 | } | |
93 | } | |
94 | } | |
95 | ||
96 | switch schema.Type { | |
97 | case TypeBool, TypeFloat, TypeInt, TypeString: | |
98 | return r.readPrimitive(k, schema) | |
99 | case TypeList: | |
100 | // If we support promotion then we first check if we have a lone | |
101 | // value that we must promote. | |
102 | // a value that is alone. | |
103 | if schema.PromoteSingle { | |
104 | result, err := r.readPrimitive(k, schema.Elem.(*Schema)) | |
105 | if err == nil && result.Exists { | |
106 | result.Value = []interface{}{result.Value} | |
107 | return result, nil | |
108 | } | |
109 | } | |
110 | ||
111 | return readListField(&nestedConfigFieldReader{r}, address, schema) | |
112 | case TypeMap: | |
113 | return r.readMap(k, schema) | |
114 | case TypeSet: | |
115 | return r.readSet(address, schema) | |
116 | case typeObject: | |
117 | return readObjectField( | |
118 | &nestedConfigFieldReader{r}, | |
119 | address, schema.Elem.(map[string]*Schema)) | |
120 | default: | |
121 | panic(fmt.Sprintf("Unknown type: %s", schema.Type)) | |
122 | } | |
123 | } | |
124 | ||
125 | func (r *ConfigFieldReader) readMap(k string, schema *Schema) (FieldReadResult, error) { | |
126 | // We want both the raw value and the interpolated. We use the interpolated | |
127 | // to store actual values and we use the raw one to check for | |
128 | // computed keys. Actual values are obtained in the switch, depending on | |
129 | // the type of the raw value. | |
130 | mraw, ok := r.Config.GetRaw(k) | |
131 | if !ok { | |
132 | // check if this is from an interpolated field by seeing if it exists | |
133 | // in the config | |
134 | _, ok := r.Config.Get(k) | |
135 | if !ok { | |
136 | // this really doesn't exist | |
137 | return FieldReadResult{}, nil | |
138 | } | |
139 | ||
140 | // We couldn't fetch the value from a nested data structure, so treat the | |
141 | // raw value as an interpolation string. The mraw value is only used | |
142 | // for the type switch below. | |
143 | mraw = "${INTERPOLATED}" | |
144 | } | |
145 | ||
146 | result := make(map[string]interface{}) | |
147 | computed := false | |
148 | switch m := mraw.(type) { | |
149 | case string: | |
150 | // This is a map which has come out of an interpolated variable, so we | |
151 | // can just get the value directly from config. Values cannot be computed | |
152 | // currently. | |
153 | v, _ := r.Config.Get(k) | |
154 | ||
155 | // If this isn't a map[string]interface, it must be computed. | |
156 | mapV, ok := v.(map[string]interface{}) | |
157 | if !ok { | |
158 | return FieldReadResult{ | |
159 | Exists: true, | |
160 | Computed: true, | |
161 | }, nil | |
162 | } | |
163 | ||
164 | // Otherwise we can proceed as usual. | |
165 | for i, iv := range mapV { | |
166 | result[i] = iv | |
167 | } | |
168 | case []interface{}: | |
169 | for i, innerRaw := range m { | |
170 | for ik := range innerRaw.(map[string]interface{}) { | |
171 | key := fmt.Sprintf("%s.%d.%s", k, i, ik) | |
172 | if r.Config.IsComputed(key) { | |
173 | computed = true | |
174 | break | |
175 | } | |
176 | ||
177 | v, _ := r.Config.Get(key) | |
178 | result[ik] = v | |
179 | } | |
180 | } | |
181 | case []map[string]interface{}: | |
182 | for i, innerRaw := range m { | |
183 | for ik := range innerRaw { | |
184 | key := fmt.Sprintf("%s.%d.%s", k, i, ik) | |
185 | if r.Config.IsComputed(key) { | |
186 | computed = true | |
187 | break | |
188 | } | |
189 | ||
190 | v, _ := r.Config.Get(key) | |
191 | result[ik] = v | |
192 | } | |
193 | } | |
194 | case map[string]interface{}: | |
195 | for ik := range m { | |
196 | key := fmt.Sprintf("%s.%s", k, ik) | |
197 | if r.Config.IsComputed(key) { | |
198 | computed = true | |
199 | break | |
200 | } | |
201 | ||
202 | v, _ := r.Config.Get(key) | |
203 | result[ik] = v | |
204 | } | |
205 | default: | |
206 | panic(fmt.Sprintf("unknown type: %#v", mraw)) | |
207 | } | |
208 | ||
209 | err := mapValuesToPrimitive(result, schema) | |
210 | if err != nil { | |
211 | return FieldReadResult{}, nil | |
212 | } | |
213 | ||
214 | var value interface{} | |
215 | if !computed { | |
216 | value = result | |
217 | } | |
218 | ||
219 | return FieldReadResult{ | |
220 | Value: value, | |
221 | Exists: true, | |
222 | Computed: computed, | |
223 | }, nil | |
224 | } | |
225 | ||
226 | func (r *ConfigFieldReader) readPrimitive( | |
227 | k string, schema *Schema) (FieldReadResult, error) { | |
228 | raw, ok := r.Config.Get(k) | |
229 | if !ok { | |
230 | // Nothing in config, but we might still have a default from the schema | |
231 | var err error | |
232 | raw, err = schema.DefaultValue() | |
233 | if err != nil { | |
234 | return FieldReadResult{}, fmt.Errorf("%s, error loading default: %s", k, err) | |
235 | } | |
236 | ||
237 | if raw == nil { | |
238 | return FieldReadResult{}, nil | |
239 | } | |
240 | } | |
241 | ||
242 | var result string | |
243 | if err := mapstructure.WeakDecode(raw, &result); err != nil { | |
244 | return FieldReadResult{}, err | |
245 | } | |
246 | ||
247 | computed := r.Config.IsComputed(k) | |
248 | returnVal, err := stringToPrimitive(result, computed, schema) | |
249 | if err != nil { | |
250 | return FieldReadResult{}, err | |
251 | } | |
252 | ||
253 | return FieldReadResult{ | |
254 | Value: returnVal, | |
255 | Exists: true, | |
256 | Computed: computed, | |
257 | }, nil | |
258 | } | |
259 | ||
260 | func (r *ConfigFieldReader) readSet( | |
261 | address []string, schema *Schema) (FieldReadResult, error) { | |
262 | indexMap := make(map[string]int) | |
263 | // Create the set that will be our result | |
264 | set := schema.ZeroValue().(*Set) | |
265 | ||
266 | raw, err := readListField(&nestedConfigFieldReader{r}, address, schema) | |
267 | if err != nil { | |
268 | return FieldReadResult{}, err | |
269 | } | |
270 | if !raw.Exists { | |
271 | return FieldReadResult{Value: set}, nil | |
272 | } | |
273 | ||
274 | // If the list is computed, the set is necessarilly computed | |
275 | if raw.Computed { | |
276 | return FieldReadResult{ | |
277 | Value: set, | |
278 | Exists: true, | |
279 | Computed: raw.Computed, | |
280 | }, nil | |
281 | } | |
282 | ||
283 | // Build up the set from the list elements | |
284 | for i, v := range raw.Value.([]interface{}) { | |
285 | // Check if any of the keys in this item are computed | |
286 | computed := r.hasComputedSubKeys( | |
287 | fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema) | |
288 | ||
289 | code := set.add(v, computed) | |
290 | indexMap[code] = i | |
291 | } | |
292 | ||
293 | r.indexMaps[strings.Join(address, ".")] = indexMap | |
294 | ||
295 | return FieldReadResult{ | |
296 | Value: set, | |
297 | Exists: true, | |
298 | }, nil | |
299 | } | |
300 | ||
301 | // hasComputedSubKeys walks through a schema and returns whether or not the | |
302 | // given key contains any subkeys that are computed. | |
303 | func (r *ConfigFieldReader) hasComputedSubKeys(key string, schema *Schema) bool { | |
304 | prefix := key + "." | |
305 | ||
306 | switch t := schema.Elem.(type) { | |
307 | case *Resource: | |
308 | for k, schema := range t.Schema { | |
309 | if r.Config.IsComputed(prefix + k) { | |
310 | return true | |
311 | } | |
312 | ||
313 | if r.hasComputedSubKeys(prefix+k, schema) { | |
314 | return true | |
315 | } | |
316 | } | |
317 | } | |
318 | ||
319 | return false | |
320 | } | |
321 | ||
322 | // nestedConfigFieldReader is a funny little thing that just wraps a | |
323 | // ConfigFieldReader to call readField when ReadField is called so that | |
324 | // we don't recalculate the set rewrites in the address, which leads to | |
325 | // an infinite loop. | |
326 | type nestedConfigFieldReader struct { | |
327 | Reader *ConfigFieldReader | |
328 | } | |
329 | ||
330 | func (r *nestedConfigFieldReader) ReadField( | |
331 | address []string) (FieldReadResult, error) { | |
332 | return r.Reader.readField(address, true) | |
333 | } |