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