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