github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/resource_address.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "reflect" 6 "regexp" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 12 ) 13 14 // ResourceAddress is a way of identifying an individual resource (or, 15 // eventually, a subset of resources) within the state. It is used for Targets. 16 type ResourceAddress struct { 17 // Addresses a resource falling somewhere in the module path 18 // When specified alone, addresses all resources within a module path 19 Path []string 20 21 // Addresses a specific resource that occurs in a list 22 Index int 23 24 InstanceType InstanceType 25 InstanceTypeSet bool 26 Name string 27 Type string 28 Mode ResourceMode // significant only if InstanceTypeSet 29 } 30 31 // Copy returns a copy of this ResourceAddress 32 func (r *ResourceAddress) Copy() *ResourceAddress { 33 if r == nil { 34 return nil 35 } 36 37 n := &ResourceAddress{ 38 Path: make([]string, 0, len(r.Path)), 39 Index: r.Index, 40 InstanceType: r.InstanceType, 41 Name: r.Name, 42 Type: r.Type, 43 Mode: r.Mode, 44 } 45 46 n.Path = append(n.Path, r.Path...) 47 48 return n 49 } 50 51 // String outputs the address that parses into this address. 52 func (r *ResourceAddress) String() string { 53 var result []string 54 for _, p := range r.Path { 55 result = append(result, "module", p) 56 } 57 58 switch r.Mode { 59 case ManagedResourceMode: 60 // nothing to do 61 case DataResourceMode: 62 result = append(result, "data") 63 default: 64 panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) 65 } 66 67 if r.Type != "" { 68 result = append(result, r.Type) 69 } 70 71 if r.Name != "" { 72 name := r.Name 73 if r.InstanceTypeSet { 74 switch r.InstanceType { 75 case TypePrimary: 76 name += ".primary" 77 case TypeDeposed: 78 name += ".deposed" 79 case TypeTainted: 80 name += ".tainted" 81 } 82 } 83 84 if r.Index >= 0 { 85 name += fmt.Sprintf("[%d]", r.Index) 86 } 87 result = append(result, name) 88 } 89 90 return strings.Join(result, ".") 91 } 92 93 // HasResourceSpec returns true if the address has a resource spec, as 94 // defined in the documentation: 95 // https://www.terraform.io/docs/internals/resource-addressing.html 96 // In particular, this returns false if the address contains only 97 // a module path, thus addressing the entire module. 98 func (r *ResourceAddress) HasResourceSpec() bool { 99 return r.Type != "" && r.Name != "" 100 } 101 102 // WholeModuleAddress returns the resource address that refers to all 103 // resources in the same module as the receiver address. 104 func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress { 105 return &ResourceAddress{ 106 Path: r.Path, 107 Index: -1, 108 InstanceTypeSet: false, 109 } 110 } 111 112 // MatchesResourceConfig returns true if the receiver matches the given 113 // configuration resource within the given _static_ module path. Note that 114 // the module path in a resource address is a _dynamic_ module path, and 115 // multiple dynamic resource paths may map to a single static path if 116 // count and for_each are in use on module calls. 117 // 118 // Since resource configuration blocks represent all of the instances of 119 // a multi-instance resource, the index of the address (if any) is not 120 // considered. 121 func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool { 122 if r.HasResourceSpec() { 123 // FIXME: Some ugliness while we are between worlds. Functionality 124 // in "addrs" should eventually replace this ResourceAddress idea 125 // completely, but for now we'll need to translate to the old 126 // way of representing resource modes. 127 switch r.Mode { 128 case ManagedResourceMode: 129 if rc.Mode != addrs.ManagedResourceMode { 130 return false 131 } 132 case DataResourceMode: 133 if rc.Mode != addrs.DataResourceMode { 134 return false 135 } 136 } 137 if r.Type != rc.Type || r.Name != rc.Name { 138 return false 139 } 140 } 141 142 addrPath := r.Path 143 144 // normalize 145 if len(addrPath) == 0 { 146 addrPath = nil 147 } 148 if len(path) == 0 { 149 path = nil 150 } 151 rawPath := []string(path) 152 return reflect.DeepEqual(addrPath, rawPath) 153 } 154 155 // stateId returns the ID that this resource should be entered with 156 // in the state. This is also used for diffs. In the future, we'd like to 157 // move away from this string field so I don't export this. 158 func (r *ResourceAddress) stateId() string { 159 result := fmt.Sprintf("%s.%s", r.Type, r.Name) 160 switch r.Mode { 161 case ManagedResourceMode: 162 // Done 163 case DataResourceMode: 164 result = fmt.Sprintf("data.%s", result) 165 default: 166 panic(fmt.Errorf("unknown resource mode: %s", r.Mode)) 167 } 168 if r.Index >= 0 { 169 result += fmt.Sprintf(".%d", r.Index) 170 } 171 172 return result 173 } 174 175 // parseResourceAddressInternal parses the somewhat bespoke resource 176 // identifier used in states and diffs, such as "instance.name.0". 177 func parseResourceAddressInternal(s string) (*ResourceAddress, error) { 178 // Split based on ".". Every resource address should have at least two 179 // elements (type and name). 180 parts := strings.Split(s, ".") 181 if len(parts) < 2 || len(parts) > 4 { 182 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 183 } 184 185 // Data resource if we have at least 3 parts and the first one is data 186 mode := ManagedResourceMode 187 if len(parts) > 2 && parts[0] == "data" { 188 mode = DataResourceMode 189 parts = parts[1:] 190 } 191 192 // If we're not a data resource and we have more than 3, then it is an error 193 if len(parts) > 3 && mode != DataResourceMode { 194 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 195 } 196 197 // Build the parts of the resource address that are guaranteed to exist 198 addr := &ResourceAddress{ 199 Type: parts[0], 200 Name: parts[1], 201 Index: -1, 202 InstanceType: TypePrimary, 203 Mode: mode, 204 } 205 206 // If we have more parts, then we have an index. Parse that. 207 if len(parts) > 2 { 208 idx, err := strconv.ParseInt(parts[2], 0, 0) 209 if err != nil { 210 return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) 211 } 212 213 addr.Index = int(idx) 214 } 215 216 return addr, nil 217 } 218 219 func ParseResourceAddress(s string) (*ResourceAddress, error) { 220 matches, err := tokenizeResourceAddress(s) 221 if err != nil { 222 return nil, err 223 } 224 mode := ManagedResourceMode 225 if matches["data_prefix"] != "" { 226 mode = DataResourceMode 227 } 228 resourceIndex, err := ParseResourceIndex(matches["index"]) 229 if err != nil { 230 return nil, err 231 } 232 instanceType, err := ParseInstanceType(matches["instance_type"]) 233 if err != nil { 234 return nil, err 235 } 236 path := ParseResourcePath(matches["path"]) 237 238 // not allowed to say "data." without a type following 239 if mode == DataResourceMode && matches["type"] == "" { 240 return nil, fmt.Errorf( 241 "invalid resource address %q: must target specific data instance", 242 s, 243 ) 244 } 245 246 return &ResourceAddress{ 247 Path: path, 248 Index: resourceIndex, 249 InstanceType: instanceType, 250 InstanceTypeSet: matches["instance_type"] != "", 251 Name: matches["name"], 252 Type: matches["type"], 253 Mode: mode, 254 }, nil 255 } 256 257 // ParseResourceAddressForInstanceDiff creates a ResourceAddress for a 258 // resource name as described in a module diff. 259 // 260 // For historical reasons a different addressing format is used in this 261 // context. The internal format should not be shown in the UI and instead 262 // this function should be used to translate to a ResourceAddress and 263 // then, where appropriate, use the String method to produce a canonical 264 // resource address string for display in the UI. 265 // 266 // The given path slice must be empty (or nil) for the root module, and 267 // otherwise consist of a sequence of module names traversing down into 268 // the module tree. If a non-nil path is provided, the caller must not 269 // modify its underlying array after passing it to this function. 270 func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) { 271 addr, err := parseResourceAddressInternal(key) 272 if err != nil { 273 return nil, err 274 } 275 addr.Path = path 276 return addr, nil 277 } 278 279 // NewLegacyResourceAddress creates a ResourceAddress from a new-style 280 // addrs.AbsResource value. 281 // 282 // This is provided for shimming purposes so that we can still easily call into 283 // older functions that expect the ResourceAddress type. 284 func NewLegacyResourceAddress(addr addrs.AbsResource) *ResourceAddress { 285 ret := &ResourceAddress{ 286 Type: addr.Resource.Type, 287 Name: addr.Resource.Name, 288 } 289 290 switch addr.Resource.Mode { 291 case addrs.ManagedResourceMode: 292 ret.Mode = ManagedResourceMode 293 case addrs.DataResourceMode: 294 ret.Mode = DataResourceMode 295 default: 296 panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Mode)) 297 } 298 299 path := make([]string, len(addr.Module)) 300 for i, step := range addr.Module { 301 if step.InstanceKey != addrs.NoKey { 302 // At the time of writing this can't happen because we don't 303 // ket generate keyed module instances. This legacy codepath must 304 // be removed before we can support "count" and "for_each" for 305 // modules. 306 panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) 307 } 308 309 path[i] = step.Name 310 } 311 ret.Path = path 312 ret.Index = -1 313 314 return ret 315 } 316 317 // NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style 318 // addrs.AbsResource value. 319 // 320 // This is provided for shimming purposes so that we can still easily call into 321 // older functions that expect the ResourceAddress type. 322 func NewLegacyResourceInstanceAddress(addr addrs.AbsResourceInstance) *ResourceAddress { 323 ret := &ResourceAddress{ 324 Type: addr.Resource.Resource.Type, 325 Name: addr.Resource.Resource.Name, 326 } 327 328 switch addr.Resource.Resource.Mode { 329 case addrs.ManagedResourceMode: 330 ret.Mode = ManagedResourceMode 331 case addrs.DataResourceMode: 332 ret.Mode = DataResourceMode 333 default: 334 panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Resource.Mode)) 335 } 336 337 path := make([]string, len(addr.Module)) 338 for i, step := range addr.Module { 339 if step.InstanceKey != addrs.NoKey { 340 // At the time of writing this can't happen because we don't 341 // ket generate keyed module instances. This legacy codepath must 342 // be removed before we can support "count" and "for_each" for 343 // modules. 344 panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) 345 } 346 347 path[i] = step.Name 348 } 349 ret.Path = path 350 351 if addr.Resource.Key == addrs.NoKey { 352 ret.Index = -1 353 } else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok { 354 ret.Index = int(ik) 355 } else if _, ok := addr.Resource.Key.(addrs.StringKey); ok { 356 ret.Index = -1 357 } else { 358 panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key)) 359 } 360 361 return ret 362 } 363 364 // AbsResourceInstanceAddr converts the receiver, a legacy resource address, to 365 // the new resource address type addrs.AbsResourceInstance. 366 // 367 // This method can be used only on an address that has a resource specification. 368 // It will panic if called on a module-path-only ResourceAddress. Use 369 // method HasResourceSpec to check before calling, in contexts where it is 370 // unclear. 371 // 372 // addrs.AbsResourceInstance does not represent the "tainted" and "deposed" 373 // states, and so if these are present on the receiver then they are discarded. 374 // 375 // This is provided for shimming purposes so that we can easily adapt functions 376 // that are returning the legacy ResourceAddress type, for situations where 377 // the new type is required. 378 func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance { 379 if !addr.HasResourceSpec() { 380 panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec") 381 } 382 383 ret := addrs.AbsResourceInstance{ 384 Module: addr.ModuleInstanceAddr(), 385 Resource: addrs.ResourceInstance{ 386 Resource: addrs.Resource{ 387 Type: addr.Type, 388 Name: addr.Name, 389 }, 390 }, 391 } 392 393 switch addr.Mode { 394 case ManagedResourceMode: 395 ret.Resource.Resource.Mode = addrs.ManagedResourceMode 396 case DataResourceMode: 397 ret.Resource.Resource.Mode = addrs.DataResourceMode 398 default: 399 panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode)) 400 } 401 402 if addr.Index != -1 { 403 ret.Resource.Key = addrs.IntKey(addr.Index) 404 } 405 406 return ret 407 } 408 409 // ModuleInstanceAddr returns the module path portion of the receiver as a 410 // addrs.ModuleInstance value. 411 func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance { 412 path := make(addrs.ModuleInstance, len(addr.Path)) 413 for i, name := range addr.Path { 414 path[i] = addrs.ModuleInstanceStep{Name: name} 415 } 416 return path 417 } 418 419 // Contains returns true if and only if the given node is contained within 420 // the receiver. 421 // 422 // Containment is defined in terms of the module and resource heirarchy: 423 // a resource is contained within its module and any ancestor modules, 424 // an indexed resource instance is contained with the unindexed resource, etc. 425 func (addr *ResourceAddress) Contains(other *ResourceAddress) bool { 426 ourPath := addr.Path 427 givenPath := other.Path 428 if len(givenPath) < len(ourPath) { 429 return false 430 } 431 for i := range ourPath { 432 if ourPath[i] != givenPath[i] { 433 return false 434 } 435 } 436 437 // If the receiver is a whole-module address then the path prefix 438 // matching is all we need. 439 if !addr.HasResourceSpec() { 440 return true 441 } 442 443 if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode { 444 return false 445 } 446 447 if addr.Index != -1 && addr.Index != other.Index { 448 return false 449 } 450 451 if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) { 452 return false 453 } 454 455 return true 456 } 457 458 // Equals returns true if the receiver matches the given address. 459 // 460 // The name of this method is a misnomer, since it doesn't test for exact 461 // equality. Instead, it tests that the _specified_ parts of each 462 // address match, treating any unspecified parts as wildcards. 463 // 464 // See also Contains, which takes a more heirarchical approach to comparing 465 // addresses. 466 func (addr *ResourceAddress) Equals(raw interface{}) bool { 467 other, ok := raw.(*ResourceAddress) 468 if !ok { 469 return false 470 } 471 472 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || 473 reflect.DeepEqual(addr.Path, other.Path) 474 475 indexMatch := addr.Index == -1 || 476 other.Index == -1 || 477 addr.Index == other.Index 478 479 nameMatch := addr.Name == "" || 480 other.Name == "" || 481 addr.Name == other.Name 482 483 typeMatch := addr.Type == "" || 484 other.Type == "" || 485 addr.Type == other.Type 486 487 // mode is significant only when type is set 488 modeMatch := addr.Type == "" || 489 other.Type == "" || 490 addr.Mode == other.Mode 491 492 return pathMatch && 493 indexMatch && 494 addr.InstanceType == other.InstanceType && 495 nameMatch && 496 typeMatch && 497 modeMatch 498 } 499 500 // Less returns true if and only if the receiver should be sorted before 501 // the given address when presenting a list of resource addresses to 502 // an end-user. 503 // 504 // This sort uses lexicographic sorting for most components, but uses 505 // numeric sort for indices, thus causing index 10 to sort after 506 // index 9, rather than after index 1. 507 func (addr *ResourceAddress) Less(other *ResourceAddress) bool { 508 509 switch { 510 511 case len(addr.Path) != len(other.Path): 512 return len(addr.Path) < len(other.Path) 513 514 case !reflect.DeepEqual(addr.Path, other.Path): 515 // If the two paths are the same length but don't match, we'll just 516 // cheat and compare the string forms since it's easier than 517 // comparing all of the path segments in turn, and lexicographic 518 // comparison is correct for the module path portion. 519 addrStr := addr.String() 520 otherStr := other.String() 521 return addrStr < otherStr 522 523 case addr.Mode != other.Mode: 524 return addr.Mode == DataResourceMode 525 526 case addr.Type != other.Type: 527 return addr.Type < other.Type 528 529 case addr.Name != other.Name: 530 return addr.Name < other.Name 531 532 case addr.Index != other.Index: 533 // Since "Index" is -1 for an un-indexed address, this also conveniently 534 // sorts unindexed addresses before indexed ones, should they both 535 // appear for some reason. 536 return addr.Index < other.Index 537 538 case addr.InstanceTypeSet != other.InstanceTypeSet: 539 return !addr.InstanceTypeSet 540 541 case addr.InstanceType != other.InstanceType: 542 // InstanceType is actually an enum, so this is just an arbitrary 543 // sort based on the enum numeric values, and thus not particularly 544 // meaningful. 545 return addr.InstanceType < other.InstanceType 546 547 default: 548 return false 549 550 } 551 } 552 553 func ParseResourceIndex(s string) (int, error) { 554 if s == "" { 555 return -1, nil 556 } 557 return strconv.Atoi(s) 558 } 559 560 func ParseResourcePath(s string) []string { 561 if s == "" { 562 return nil 563 } 564 parts := strings.Split(s, ".") 565 path := make([]string, 0, len(parts)) 566 for _, s := range parts { 567 // Due to the limitations of the regexp match below, the path match has 568 // some noise in it we have to filter out :| 569 if s == "" || s == "module" { 570 continue 571 } 572 path = append(path, s) 573 } 574 return path 575 } 576 577 func ParseInstanceType(s string) (InstanceType, error) { 578 switch s { 579 case "", "primary": 580 return TypePrimary, nil 581 case "deposed": 582 return TypeDeposed, nil 583 case "tainted": 584 return TypeTainted, nil 585 default: 586 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) 587 } 588 } 589 590 func tokenizeResourceAddress(s string) (map[string]string, error) { 591 // Example of portions of the regexp below using the 592 // string "aws_instance.web.tainted[1]" 593 re := regexp.MustCompile(`\A` + 594 // "module.foo.module.bar" (optional) 595 `(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` + 596 // possibly "data.", if targeting is a data resource 597 `(?P<data_prefix>(?:data\.)?)` + 598 // "aws_instance.web" (optional when module path specified) 599 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + 600 // "tainted" (optional, omission implies: "primary") 601 `(?:\.(?P<instance_type>\w+))?` + 602 // "1" (optional, omission implies: "0") 603 `(?:\[(?P<index>\d+)\])?` + 604 `\z`) 605 606 groupNames := re.SubexpNames() 607 rawMatches := re.FindAllStringSubmatch(s, -1) 608 if len(rawMatches) != 1 { 609 return nil, fmt.Errorf("invalid resource address %q", s) 610 } 611 612 matches := make(map[string]string) 613 for i, m := range rawMatches[0] { 614 matches[groupNames[i]] = m 615 } 616 617 return matches, nil 618 }