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