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