]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "log" | |
6 | "os" | |
7 | "strconv" | |
8 | "strings" | |
9 | "sync" | |
10 | ||
11 | "github.com/hashicorp/hil" | |
12 | "github.com/hashicorp/hil/ast" | |
13 | "github.com/hashicorp/terraform/config" | |
14 | "github.com/hashicorp/terraform/config/module" | |
15 | "github.com/hashicorp/terraform/flatmap" | |
16 | ) | |
17 | ||
18 | const ( | |
19 | // VarEnvPrefix is the prefix of variables that are read from | |
20 | // the environment to set variables here. | |
21 | VarEnvPrefix = "TF_VAR_" | |
22 | ) | |
23 | ||
24 | // Interpolater is the structure responsible for determining the values | |
25 | // for interpolations such as `aws_instance.foo.bar`. | |
26 | type Interpolater struct { | |
27 | Operation walkOperation | |
28 | Meta *ContextMeta | |
29 | Module *module.Tree | |
30 | State *State | |
31 | StateLock *sync.RWMutex | |
32 | VariableValues map[string]interface{} | |
33 | VariableValuesLock *sync.Mutex | |
34 | } | |
35 | ||
36 | // InterpolationScope is the current scope of execution. This is required | |
37 | // since some variables which are interpolated are dependent on what we're | |
38 | // operating on and where we are. | |
39 | type InterpolationScope struct { | |
40 | Path []string | |
41 | Resource *Resource | |
42 | } | |
43 | ||
44 | // Values returns the values for all the variables in the given map. | |
45 | func (i *Interpolater) Values( | |
46 | scope *InterpolationScope, | |
47 | vars map[string]config.InterpolatedVariable) (map[string]ast.Variable, error) { | |
48 | if scope == nil { | |
49 | scope = &InterpolationScope{} | |
50 | } | |
51 | ||
52 | result := make(map[string]ast.Variable, len(vars)) | |
53 | ||
54 | // Copy the default variables | |
55 | if i.Module != nil && scope != nil { | |
56 | mod := i.Module | |
57 | if len(scope.Path) > 1 { | |
58 | mod = i.Module.Child(scope.Path[1:]) | |
59 | } | |
60 | for _, v := range mod.Config().Variables { | |
61 | // Set default variables | |
62 | if v.Default == nil { | |
63 | continue | |
64 | } | |
65 | ||
66 | n := fmt.Sprintf("var.%s", v.Name) | |
67 | variable, err := hil.InterfaceToVariable(v.Default) | |
68 | if err != nil { | |
69 | return nil, fmt.Errorf("invalid default map value for %s: %v", v.Name, v.Default) | |
70 | } | |
71 | ||
72 | result[n] = variable | |
73 | } | |
74 | } | |
75 | ||
76 | for n, rawV := range vars { | |
77 | var err error | |
78 | switch v := rawV.(type) { | |
79 | case *config.CountVariable: | |
80 | err = i.valueCountVar(scope, n, v, result) | |
81 | case *config.ModuleVariable: | |
82 | err = i.valueModuleVar(scope, n, v, result) | |
83 | case *config.PathVariable: | |
84 | err = i.valuePathVar(scope, n, v, result) | |
85 | case *config.ResourceVariable: | |
86 | err = i.valueResourceVar(scope, n, v, result) | |
87 | case *config.SelfVariable: | |
88 | err = i.valueSelfVar(scope, n, v, result) | |
89 | case *config.SimpleVariable: | |
90 | err = i.valueSimpleVar(scope, n, v, result) | |
91 | case *config.TerraformVariable: | |
92 | err = i.valueTerraformVar(scope, n, v, result) | |
93 | case *config.UserVariable: | |
94 | err = i.valueUserVar(scope, n, v, result) | |
95 | default: | |
96 | err = fmt.Errorf("%s: unknown variable type: %T", n, rawV) | |
97 | } | |
98 | ||
99 | if err != nil { | |
100 | return nil, err | |
101 | } | |
102 | } | |
103 | ||
104 | return result, nil | |
105 | } | |
106 | ||
107 | func (i *Interpolater) valueCountVar( | |
108 | scope *InterpolationScope, | |
109 | n string, | |
110 | v *config.CountVariable, | |
111 | result map[string]ast.Variable) error { | |
112 | switch v.Type { | |
113 | case config.CountValueIndex: | |
114 | if scope.Resource == nil { | |
115 | return fmt.Errorf("%s: count.index is only valid within resources", n) | |
116 | } | |
117 | result[n] = ast.Variable{ | |
118 | Value: scope.Resource.CountIndex, | |
119 | Type: ast.TypeInt, | |
120 | } | |
121 | return nil | |
122 | default: | |
123 | return fmt.Errorf("%s: unknown count type: %#v", n, v.Type) | |
124 | } | |
125 | } | |
126 | ||
127 | func unknownVariable() ast.Variable { | |
128 | return ast.Variable{ | |
129 | Type: ast.TypeUnknown, | |
130 | Value: config.UnknownVariableValue, | |
131 | } | |
132 | } | |
133 | ||
134 | func unknownValue() string { | |
135 | return hil.UnknownValue | |
136 | } | |
137 | ||
138 | func (i *Interpolater) valueModuleVar( | |
139 | scope *InterpolationScope, | |
140 | n string, | |
141 | v *config.ModuleVariable, | |
142 | result map[string]ast.Variable) error { | |
143 | ||
144 | // Build the path to the child module we want | |
145 | path := make([]string, len(scope.Path), len(scope.Path)+1) | |
146 | copy(path, scope.Path) | |
147 | path = append(path, v.Name) | |
148 | ||
149 | // Grab the lock so that if other interpolations are running or | |
150 | // state is being modified, we'll be safe. | |
151 | i.StateLock.RLock() | |
152 | defer i.StateLock.RUnlock() | |
153 | ||
154 | // Get the module where we're looking for the value | |
155 | mod := i.State.ModuleByPath(path) | |
156 | if mod == nil { | |
157 | // If the module doesn't exist, then we can return an empty string. | |
158 | // This happens usually only in Refresh() when we haven't populated | |
159 | // a state. During validation, we semantically verify that all | |
160 | // modules reference other modules, and graph ordering should | |
161 | // ensure that the module is in the state, so if we reach this | |
162 | // point otherwise it really is a panic. | |
163 | result[n] = unknownVariable() | |
164 | ||
165 | // During apply this is always an error | |
166 | if i.Operation == walkApply { | |
167 | return fmt.Errorf( | |
168 | "Couldn't find module %q for var: %s", | |
169 | v.Name, v.FullKey()) | |
170 | } | |
171 | } else { | |
172 | // Get the value from the outputs | |
173 | if outputState, ok := mod.Outputs[v.Field]; ok { | |
174 | output, err := hil.InterfaceToVariable(outputState.Value) | |
175 | if err != nil { | |
176 | return err | |
177 | } | |
178 | result[n] = output | |
179 | } else { | |
180 | // Same reasons as the comment above. | |
181 | result[n] = unknownVariable() | |
182 | ||
183 | // During apply this is always an error | |
184 | if i.Operation == walkApply { | |
185 | return fmt.Errorf( | |
186 | "Couldn't find output %q for module var: %s", | |
187 | v.Field, v.FullKey()) | |
188 | } | |
189 | } | |
190 | } | |
191 | ||
192 | return nil | |
193 | } | |
194 | ||
195 | func (i *Interpolater) valuePathVar( | |
196 | scope *InterpolationScope, | |
197 | n string, | |
198 | v *config.PathVariable, | |
199 | result map[string]ast.Variable) error { | |
200 | switch v.Type { | |
201 | case config.PathValueCwd: | |
202 | wd, err := os.Getwd() | |
203 | if err != nil { | |
204 | return fmt.Errorf( | |
205 | "Couldn't get cwd for var %s: %s", | |
206 | v.FullKey(), err) | |
207 | } | |
208 | ||
209 | result[n] = ast.Variable{ | |
210 | Value: wd, | |
211 | Type: ast.TypeString, | |
212 | } | |
213 | case config.PathValueModule: | |
214 | if t := i.Module.Child(scope.Path[1:]); t != nil { | |
215 | result[n] = ast.Variable{ | |
216 | Value: t.Config().Dir, | |
217 | Type: ast.TypeString, | |
218 | } | |
219 | } | |
220 | case config.PathValueRoot: | |
221 | result[n] = ast.Variable{ | |
222 | Value: i.Module.Config().Dir, | |
223 | Type: ast.TypeString, | |
224 | } | |
225 | default: | |
226 | return fmt.Errorf("%s: unknown path type: %#v", n, v.Type) | |
227 | } | |
228 | ||
229 | return nil | |
230 | ||
231 | } | |
232 | ||
233 | func (i *Interpolater) valueResourceVar( | |
234 | scope *InterpolationScope, | |
235 | n string, | |
236 | v *config.ResourceVariable, | |
237 | result map[string]ast.Variable) error { | |
238 | // If we're computing all dynamic fields, then module vars count | |
239 | // and we mark it as computed. | |
240 | if i.Operation == walkValidate { | |
241 | result[n] = unknownVariable() | |
242 | return nil | |
243 | } | |
244 | ||
245 | var variable *ast.Variable | |
246 | var err error | |
247 | ||
248 | if v.Multi && v.Index == -1 { | |
249 | variable, err = i.computeResourceMultiVariable(scope, v) | |
250 | } else { | |
251 | variable, err = i.computeResourceVariable(scope, v) | |
252 | } | |
253 | ||
254 | if err != nil { | |
255 | return err | |
256 | } | |
257 | ||
258 | if variable == nil { | |
259 | // During the input walk we tolerate missing variables because | |
260 | // we haven't yet had a chance to refresh state, so dynamic data may | |
261 | // not yet be complete. | |
262 | // If it truly is missing, we'll catch it on a later walk. | |
263 | // This applies only to graph nodes that interpolate during the | |
264 | // config walk, e.g. providers. | |
265 | if i.Operation == walkInput || i.Operation == walkRefresh { | |
266 | result[n] = unknownVariable() | |
267 | return nil | |
268 | } | |
269 | ||
270 | return fmt.Errorf("variable %q is nil, but no error was reported", v.Name) | |
271 | } | |
272 | ||
273 | result[n] = *variable | |
274 | return nil | |
275 | } | |
276 | ||
277 | func (i *Interpolater) valueSelfVar( | |
278 | scope *InterpolationScope, | |
279 | n string, | |
280 | v *config.SelfVariable, | |
281 | result map[string]ast.Variable) error { | |
282 | if scope == nil || scope.Resource == nil { | |
283 | return fmt.Errorf( | |
284 | "%s: invalid scope, self variables are only valid on resources", n) | |
285 | } | |
286 | ||
287 | rv, err := config.NewResourceVariable(fmt.Sprintf( | |
288 | "%s.%s.%d.%s", | |
289 | scope.Resource.Type, | |
290 | scope.Resource.Name, | |
291 | scope.Resource.CountIndex, | |
292 | v.Field)) | |
293 | if err != nil { | |
294 | return err | |
295 | } | |
296 | ||
297 | return i.valueResourceVar(scope, n, rv, result) | |
298 | } | |
299 | ||
300 | func (i *Interpolater) valueSimpleVar( | |
301 | scope *InterpolationScope, | |
302 | n string, | |
303 | v *config.SimpleVariable, | |
304 | result map[string]ast.Variable) error { | |
305 | // This error message includes some information for people who | |
306 | // relied on this for their template_file data sources. We should | |
307 | // remove this at some point but there isn't any rush. | |
308 | return fmt.Errorf( | |
309 | "invalid variable syntax: %q. Did you mean 'var.%s'? If this is part of inline `template` parameter\n"+ | |
310 | "then you must escape the interpolation with two dollar signs. For\n"+ | |
311 | "example: ${a} becomes $${a}.", | |
312 | n, n) | |
313 | } | |
314 | ||
315 | func (i *Interpolater) valueTerraformVar( | |
316 | scope *InterpolationScope, | |
317 | n string, | |
318 | v *config.TerraformVariable, | |
319 | result map[string]ast.Variable) error { | |
c680a8e1 RS |
320 | |
321 | // "env" is supported for backward compatibility, but it's deprecated and | |
322 | // so we won't advertise it as being allowed in the error message. It will | |
323 | // be removed in a future version of Terraform. | |
324 | if v.Field != "workspace" && v.Field != "env" { | |
bae9f6d2 | 325 | return fmt.Errorf( |
c680a8e1 | 326 | "%s: only supported key for 'terraform.X' interpolations is 'workspace'", n) |
bae9f6d2 JC |
327 | } |
328 | ||
329 | if i.Meta == nil { | |
330 | return fmt.Errorf( | |
331 | "%s: internal error: nil Meta. Please report a bug.", n) | |
332 | } | |
333 | ||
334 | result[n] = ast.Variable{Type: ast.TypeString, Value: i.Meta.Env} | |
335 | return nil | |
336 | } | |
337 | ||
338 | func (i *Interpolater) valueUserVar( | |
339 | scope *InterpolationScope, | |
340 | n string, | |
341 | v *config.UserVariable, | |
342 | result map[string]ast.Variable) error { | |
343 | i.VariableValuesLock.Lock() | |
344 | defer i.VariableValuesLock.Unlock() | |
345 | val, ok := i.VariableValues[v.Name] | |
346 | if ok { | |
347 | varValue, err := hil.InterfaceToVariable(val) | |
348 | if err != nil { | |
349 | return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s", | |
350 | v.Name, val, err) | |
351 | } | |
352 | result[n] = varValue | |
353 | return nil | |
354 | } | |
355 | ||
356 | if _, ok := result[n]; !ok && i.Operation == walkValidate { | |
357 | result[n] = unknownVariable() | |
358 | return nil | |
359 | } | |
360 | ||
361 | // Look up if we have any variables with this prefix because | |
362 | // those are map overrides. Include those. | |
363 | for k, val := range i.VariableValues { | |
364 | if strings.HasPrefix(k, v.Name+".") { | |
365 | keyComponents := strings.Split(k, ".") | |
366 | overrideKey := keyComponents[len(keyComponents)-1] | |
367 | ||
368 | mapInterface, ok := result["var."+v.Name] | |
369 | if !ok { | |
370 | return fmt.Errorf("override for non-existent variable: %s", v.Name) | |
371 | } | |
372 | ||
373 | mapVariable := mapInterface.Value.(map[string]ast.Variable) | |
374 | ||
375 | varValue, err := hil.InterfaceToVariable(val) | |
376 | if err != nil { | |
377 | return fmt.Errorf("cannot convert %s value %q to an ast.Variable for interpolation: %s", | |
378 | v.Name, val, err) | |
379 | } | |
380 | mapVariable[overrideKey] = varValue | |
381 | } | |
382 | } | |
383 | ||
384 | return nil | |
385 | } | |
386 | ||
387 | func (i *Interpolater) computeResourceVariable( | |
388 | scope *InterpolationScope, | |
389 | v *config.ResourceVariable) (*ast.Variable, error) { | |
390 | id := v.ResourceId() | |
391 | if v.Multi { | |
392 | id = fmt.Sprintf("%s.%d", id, v.Index) | |
393 | } | |
394 | ||
395 | i.StateLock.RLock() | |
396 | defer i.StateLock.RUnlock() | |
397 | ||
398 | unknownVariable := unknownVariable() | |
399 | ||
400 | // These variables must be declared early because of the use of GOTO | |
401 | var isList bool | |
402 | var isMap bool | |
403 | ||
404 | // Get the information about this resource variable, and verify | |
405 | // that it exists and such. | |
406 | module, cr, err := i.resourceVariableInfo(scope, v) | |
407 | if err != nil { | |
408 | return nil, err | |
409 | } | |
410 | ||
411 | // If we're requesting "count" its a special variable that we grab | |
412 | // directly from the config itself. | |
413 | if v.Field == "count" { | |
414 | var count int | |
415 | if cr != nil { | |
416 | count, err = cr.Count() | |
417 | } else { | |
418 | count, err = i.resourceCountMax(module, cr, v) | |
419 | } | |
420 | if err != nil { | |
421 | return nil, fmt.Errorf( | |
422 | "Error reading %s count: %s", | |
423 | v.ResourceId(), | |
424 | err) | |
425 | } | |
426 | ||
427 | return &ast.Variable{Type: ast.TypeInt, Value: count}, nil | |
428 | } | |
429 | ||
430 | // Get the resource out from the state. We know the state exists | |
431 | // at this point and if there is a state, we expect there to be a | |
432 | // resource with the given name. | |
433 | var r *ResourceState | |
434 | if module != nil && len(module.Resources) > 0 { | |
435 | var ok bool | |
436 | r, ok = module.Resources[id] | |
437 | if !ok && v.Multi && v.Index == 0 { | |
438 | r, ok = module.Resources[v.ResourceId()] | |
439 | } | |
440 | if !ok { | |
441 | r = nil | |
442 | } | |
443 | } | |
444 | if r == nil || r.Primary == nil { | |
445 | if i.Operation == walkApply || i.Operation == walkPlan { | |
446 | return nil, fmt.Errorf( | |
447 | "Resource '%s' not found for variable '%s'", | |
448 | v.ResourceId(), | |
449 | v.FullKey()) | |
450 | } | |
451 | ||
452 | // If we have no module in the state yet or count, return empty. | |
453 | // NOTE(@mitchellh): I actually don't know why this is here. During | |
454 | // a refactor I kept this here to maintain the same behavior, but | |
455 | // I'm not sure why its here. | |
456 | if module == nil || len(module.Resources) == 0 { | |
457 | return nil, nil | |
458 | } | |
459 | ||
460 | goto MISSING | |
461 | } | |
462 | ||
463 | if attr, ok := r.Primary.Attributes[v.Field]; ok { | |
464 | v, err := hil.InterfaceToVariable(attr) | |
465 | return &v, err | |
466 | } | |
467 | ||
468 | // computed list or map attribute | |
469 | _, isList = r.Primary.Attributes[v.Field+".#"] | |
470 | _, isMap = r.Primary.Attributes[v.Field+".%"] | |
471 | if isList || isMap { | |
472 | variable, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes) | |
473 | return &variable, err | |
474 | } | |
475 | ||
476 | // At apply time, we can't do the "maybe has it" check below | |
477 | // that we need for plans since parent elements might be computed. | |
478 | // Therefore, it is an error and we're missing the key. | |
479 | // | |
480 | // TODO: test by creating a state and configuration that is referencing | |
481 | // a non-existent variable "foo.bar" where the state only has "foo" | |
482 | // and verify plan works, but apply doesn't. | |
483 | if i.Operation == walkApply || i.Operation == walkDestroy { | |
484 | goto MISSING | |
485 | } | |
486 | ||
487 | // We didn't find the exact field, so lets separate the dots | |
488 | // and see if anything along the way is a computed set. i.e. if | |
489 | // we have "foo.0.bar" as the field, check to see if "foo" is | |
490 | // a computed list. If so, then the whole thing is computed. | |
491 | if parts := strings.Split(v.Field, "."); len(parts) > 1 { | |
492 | for i := 1; i < len(parts); i++ { | |
493 | // Lists and sets make this | |
494 | key := fmt.Sprintf("%s.#", strings.Join(parts[:i], ".")) | |
495 | if attr, ok := r.Primary.Attributes[key]; ok { | |
496 | v, err := hil.InterfaceToVariable(attr) | |
497 | return &v, err | |
498 | } | |
499 | ||
500 | // Maps make this | |
501 | key = fmt.Sprintf("%s", strings.Join(parts[:i], ".")) | |
502 | if attr, ok := r.Primary.Attributes[key]; ok { | |
503 | v, err := hil.InterfaceToVariable(attr) | |
504 | return &v, err | |
505 | } | |
506 | } | |
507 | } | |
508 | ||
509 | MISSING: | |
510 | // Validation for missing interpolations should happen at a higher | |
511 | // semantic level. If we reached this point and don't have variables, | |
512 | // just return the computed value. | |
513 | if scope == nil && scope.Resource == nil { | |
514 | return &unknownVariable, nil | |
515 | } | |
516 | ||
517 | // If the operation is refresh, it isn't an error for a value to | |
518 | // be unknown. Instead, we return that the value is computed so | |
519 | // that the graph can continue to refresh other nodes. It doesn't | |
520 | // matter because the config isn't interpolated anyways. | |
521 | // | |
522 | // For a Destroy, we're also fine with computed values, since our goal is | |
523 | // only to get destroy nodes for existing resources. | |
524 | // | |
525 | // For an input walk, computed values are okay to return because we're only | |
526 | // looking for missing variables to prompt the user for. | |
527 | if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkInput { | |
528 | return &unknownVariable, nil | |
529 | } | |
530 | ||
531 | return nil, fmt.Errorf( | |
532 | "Resource '%s' does not have attribute '%s' "+ | |
533 | "for variable '%s'", | |
534 | id, | |
535 | v.Field, | |
536 | v.FullKey()) | |
537 | } | |
538 | ||
539 | func (i *Interpolater) computeResourceMultiVariable( | |
540 | scope *InterpolationScope, | |
541 | v *config.ResourceVariable) (*ast.Variable, error) { | |
542 | i.StateLock.RLock() | |
543 | defer i.StateLock.RUnlock() | |
544 | ||
545 | unknownVariable := unknownVariable() | |
546 | ||
547 | // If we're only looking for input, we don't need to expand a | |
548 | // multi-variable. This prevents us from encountering things that should be | |
549 | // known but aren't because the state has yet to be refreshed. | |
550 | if i.Operation == walkInput { | |
551 | return &unknownVariable, nil | |
552 | } | |
553 | ||
554 | // Get the information about this resource variable, and verify | |
555 | // that it exists and such. | |
556 | module, cr, err := i.resourceVariableInfo(scope, v) | |
557 | if err != nil { | |
558 | return nil, err | |
559 | } | |
560 | ||
561 | // Get the keys for all the resources that are created for this resource | |
562 | countMax, err := i.resourceCountMax(module, cr, v) | |
563 | if err != nil { | |
564 | return nil, err | |
565 | } | |
566 | ||
567 | // If count is zero, we return an empty list | |
568 | if countMax == 0 { | |
569 | return &ast.Variable{Type: ast.TypeList, Value: []ast.Variable{}}, nil | |
570 | } | |
571 | ||
572 | // If we have no module in the state yet or count, return unknown | |
573 | if module == nil || len(module.Resources) == 0 { | |
574 | return &unknownVariable, nil | |
575 | } | |
576 | ||
577 | var values []interface{} | |
578 | for idx := 0; idx < countMax; idx++ { | |
579 | id := fmt.Sprintf("%s.%d", v.ResourceId(), idx) | |
580 | ||
581 | // ID doesn't have a trailing index. We try both here, but if a value | |
582 | // without a trailing index is found we prefer that. This choice | |
583 | // is for legacy reasons: older versions of TF preferred it. | |
584 | if id == v.ResourceId()+".0" { | |
585 | potential := v.ResourceId() | |
586 | if _, ok := module.Resources[potential]; ok { | |
587 | id = potential | |
588 | } | |
589 | } | |
590 | ||
591 | r, ok := module.Resources[id] | |
592 | if !ok { | |
593 | continue | |
594 | } | |
595 | ||
596 | if r.Primary == nil { | |
597 | continue | |
598 | } | |
599 | ||
600 | if singleAttr, ok := r.Primary.Attributes[v.Field]; ok { | |
601 | values = append(values, singleAttr) | |
602 | continue | |
603 | } | |
604 | ||
605 | // computed list or map attribute | |
606 | _, isList := r.Primary.Attributes[v.Field+".#"] | |
607 | _, isMap := r.Primary.Attributes[v.Field+".%"] | |
608 | if !(isList || isMap) { | |
609 | continue | |
610 | } | |
611 | multiAttr, err := i.interpolateComplexTypeAttribute(v.Field, r.Primary.Attributes) | |
612 | if err != nil { | |
613 | return nil, err | |
614 | } | |
615 | ||
616 | values = append(values, multiAttr) | |
617 | } | |
618 | ||
619 | if len(values) == 0 { | |
620 | // If the operation is refresh, it isn't an error for a value to | |
621 | // be unknown. Instead, we return that the value is computed so | |
622 | // that the graph can continue to refresh other nodes. It doesn't | |
623 | // matter because the config isn't interpolated anyways. | |
624 | // | |
625 | // For a Destroy, we're also fine with computed values, since our goal is | |
626 | // only to get destroy nodes for existing resources. | |
627 | // | |
628 | // For an input walk, computed values are okay to return because we're only | |
629 | // looking for missing variables to prompt the user for. | |
630 | if i.Operation == walkRefresh || i.Operation == walkPlanDestroy || i.Operation == walkDestroy || i.Operation == walkInput { | |
631 | return &unknownVariable, nil | |
632 | } | |
633 | ||
634 | return nil, fmt.Errorf( | |
635 | "Resource '%s' does not have attribute '%s' "+ | |
636 | "for variable '%s'", | |
637 | v.ResourceId(), | |
638 | v.Field, | |
639 | v.FullKey()) | |
640 | } | |
641 | ||
642 | variable, err := hil.InterfaceToVariable(values) | |
643 | return &variable, err | |
644 | } | |
645 | ||
646 | func (i *Interpolater) interpolateComplexTypeAttribute( | |
647 | resourceID string, | |
648 | attributes map[string]string) (ast.Variable, error) { | |
649 | ||
650 | // We can now distinguish between lists and maps in state by the count field: | |
651 | // - lists (and by extension, sets) use the traditional .# notation | |
652 | // - maps use the newer .% notation | |
653 | // Consequently here we can decide how to deal with the keys appropriately | |
654 | // based on whether the type is a map of list. | |
655 | if lengthAttr, isList := attributes[resourceID+".#"]; isList { | |
656 | log.Printf("[DEBUG] Interpolating computed list element attribute %s (%s)", | |
657 | resourceID, lengthAttr) | |
658 | ||
659 | // In Terraform's internal dotted representation of list-like attributes, the | |
660 | // ".#" count field is marked as unknown to indicate "this whole list is | |
661 | // unknown". We must honor that meaning here so computed references can be | |
662 | // treated properly during the plan phase. | |
663 | if lengthAttr == config.UnknownVariableValue { | |
664 | return unknownVariable(), nil | |
665 | } | |
666 | ||
667 | expanded := flatmap.Expand(attributes, resourceID) | |
668 | return hil.InterfaceToVariable(expanded) | |
669 | } | |
670 | ||
671 | if lengthAttr, isMap := attributes[resourceID+".%"]; isMap { | |
672 | log.Printf("[DEBUG] Interpolating computed map element attribute %s (%s)", | |
673 | resourceID, lengthAttr) | |
674 | ||
675 | // In Terraform's internal dotted representation of map attributes, the | |
676 | // ".%" count field is marked as unknown to indicate "this whole list is | |
677 | // unknown". We must honor that meaning here so computed references can be | |
678 | // treated properly during the plan phase. | |
679 | if lengthAttr == config.UnknownVariableValue { | |
680 | return unknownVariable(), nil | |
681 | } | |
682 | ||
683 | expanded := flatmap.Expand(attributes, resourceID) | |
684 | return hil.InterfaceToVariable(expanded) | |
685 | } | |
686 | ||
687 | return ast.Variable{}, fmt.Errorf("No complex type %s found", resourceID) | |
688 | } | |
689 | ||
690 | func (i *Interpolater) resourceVariableInfo( | |
691 | scope *InterpolationScope, | |
692 | v *config.ResourceVariable) (*ModuleState, *config.Resource, error) { | |
693 | // Get the module tree that contains our current path. This is | |
694 | // either the current module (path is empty) or a child. | |
695 | modTree := i.Module | |
696 | if len(scope.Path) > 1 { | |
697 | modTree = i.Module.Child(scope.Path[1:]) | |
698 | } | |
699 | ||
700 | // Get the resource from the configuration so we can verify | |
701 | // that the resource is in the configuration and so we can access | |
702 | // the configuration if we need to. | |
703 | var cr *config.Resource | |
704 | for _, r := range modTree.Config().Resources { | |
705 | if r.Id() == v.ResourceId() { | |
706 | cr = r | |
707 | break | |
708 | } | |
709 | } | |
710 | ||
711 | // Get the relevant module | |
712 | module := i.State.ModuleByPath(scope.Path) | |
713 | return module, cr, nil | |
714 | } | |
715 | ||
716 | func (i *Interpolater) resourceCountMax( | |
717 | ms *ModuleState, | |
718 | cr *config.Resource, | |
719 | v *config.ResourceVariable) (int, error) { | |
720 | id := v.ResourceId() | |
721 | ||
722 | // If we're NOT applying, then we assume we can read the count | |
723 | // from the state. Plan and so on may not have any state yet so | |
724 | // we do a full interpolation. | |
725 | if i.Operation != walkApply { | |
726 | if cr == nil { | |
727 | return 0, nil | |
728 | } | |
729 | ||
730 | count, err := cr.Count() | |
731 | if err != nil { | |
732 | return 0, err | |
733 | } | |
734 | ||
735 | return count, nil | |
736 | } | |
737 | ||
9b12e4fe JC |
738 | // If we have no module state in the apply walk, that suggests we've hit |
739 | // a rather awkward edge-case: the resource this variable refers to | |
740 | // has count = 0 and is the only resource processed so far on this walk, | |
741 | // and so we've ended up not creating any resource states yet. We don't | |
742 | // create a module state until the first resource is written into it, | |
743 | // so the module state doesn't exist when we get here. | |
744 | // | |
745 | // In this case we act as we would if we had been passed a module | |
746 | // with an empty resource state map. | |
747 | if ms == nil { | |
748 | return 0, nil | |
749 | } | |
750 | ||
bae9f6d2 JC |
751 | // We need to determine the list of resource keys to get values from. |
752 | // This needs to be sorted so the order is deterministic. We used to | |
753 | // use "cr.Count()" but that doesn't work if the count is interpolated | |
754 | // and we can't guarantee that so we instead depend on the state. | |
755 | max := -1 | |
756 | for k, _ := range ms.Resources { | |
757 | // Get the index number for this resource | |
758 | index := "" | |
759 | if k == id { | |
760 | // If the key is the id, then its just 0 (no explicit index) | |
761 | index = "0" | |
762 | } else if strings.HasPrefix(k, id+".") { | |
763 | // Grab the index number out of the state | |
764 | index = k[len(id+"."):] | |
765 | if idx := strings.IndexRune(index, '.'); idx >= 0 { | |
766 | index = index[:idx] | |
767 | } | |
768 | } | |
769 | ||
770 | // If there was no index then this resource didn't match | |
771 | // the one we're looking for, exit. | |
772 | if index == "" { | |
773 | continue | |
774 | } | |
775 | ||
776 | // Turn the index into an int | |
777 | raw, err := strconv.ParseInt(index, 0, 0) | |
778 | if err != nil { | |
779 | return 0, fmt.Errorf( | |
780 | "%s: error parsing index %q as int: %s", | |
781 | id, index, err) | |
782 | } | |
783 | ||
784 | // Keep track of this index if its the max | |
785 | if new := int(raw); new > max { | |
786 | max = new | |
787 | } | |
788 | } | |
789 | ||
790 | // If we never found any matching resources in the state, we | |
791 | // have zero. | |
792 | if max == -1 { | |
793 | return 0, nil | |
794 | } | |
795 | ||
796 | // The result value is "max+1" because we're returning the | |
797 | // max COUNT, not the max INDEX, and we zero-index. | |
798 | return max + 1, nil | |
799 | } |