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