github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/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/config" 11 "github.com/hashicorp/terraform/config/module" 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 config.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 config.ManagedResourceMode: 60 // nothing to do 61 case config.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 // MatchesConfig returns true if the receiver matches the given 113 // configuration resource within the given configuration module. 114 // 115 // Since resource configuration blocks represent all of the instances of 116 // a multi-instance resource, the index of the address (if any) is not 117 // considered. 118 func (r *ResourceAddress) MatchesConfig(mod *module.Tree, rc *config.Resource) bool { 119 if r.HasResourceSpec() { 120 if r.Mode != rc.Mode || r.Type != rc.Type || r.Name != rc.Name { 121 return false 122 } 123 } 124 125 addrPath := r.Path 126 cfgPath := mod.Path() 127 128 // normalize 129 if len(addrPath) == 0 { 130 addrPath = nil 131 } 132 if len(cfgPath) == 0 { 133 cfgPath = nil 134 } 135 return reflect.DeepEqual(addrPath, cfgPath) 136 } 137 138 // stateId returns the ID that this resource should be entered with 139 // in the state. This is also used for diffs. In the future, we'd like to 140 // move away from this string field so I don't export this. 141 func (r *ResourceAddress) stateId() string { 142 result := fmt.Sprintf("%s.%s", r.Type, r.Name) 143 switch r.Mode { 144 case config.ManagedResourceMode: 145 // Done 146 case config.DataResourceMode: 147 result = fmt.Sprintf("data.%s", result) 148 default: 149 panic(fmt.Errorf("unknown resource mode: %s", r.Mode)) 150 } 151 if r.Index >= 0 { 152 result += fmt.Sprintf(".%d", r.Index) 153 } 154 155 return result 156 } 157 158 // parseResourceAddressConfig creates a resource address from a config.Resource 159 func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) { 160 return &ResourceAddress{ 161 Type: r.Type, 162 Name: r.Name, 163 Index: -1, 164 InstanceType: TypePrimary, 165 Mode: r.Mode, 166 }, nil 167 } 168 169 // parseResourceAddressInternal parses the somewhat bespoke resource 170 // identifier used in states and diffs, such as "instance.name.0". 171 func parseResourceAddressInternal(s string) (*ResourceAddress, error) { 172 // Split based on ".". Every resource address should have at least two 173 // elements (type and name). 174 parts := strings.Split(s, ".") 175 if len(parts) < 2 || len(parts) > 4 { 176 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 177 } 178 179 // Data resource if we have at least 3 parts and the first one is data 180 mode := config.ManagedResourceMode 181 if len(parts) > 2 && parts[0] == "data" { 182 mode = config.DataResourceMode 183 parts = parts[1:] 184 } 185 186 // If we're not a data resource and we have more than 3, then it is an error 187 if len(parts) > 3 && mode != config.DataResourceMode { 188 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 189 } 190 191 // Build the parts of the resource address that are guaranteed to exist 192 addr := &ResourceAddress{ 193 Type: parts[0], 194 Name: parts[1], 195 Index: -1, 196 InstanceType: TypePrimary, 197 Mode: mode, 198 } 199 200 // If we have more parts, then we have an index. Parse that. 201 if len(parts) > 2 { 202 idx, err := strconv.ParseInt(parts[2], 0, 0) 203 if err != nil { 204 return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) 205 } 206 207 addr.Index = int(idx) 208 } 209 210 return addr, nil 211 } 212 213 func ParseResourceAddress(s string) (*ResourceAddress, error) { 214 matches, err := tokenizeResourceAddress(s) 215 if err != nil { 216 return nil, err 217 } 218 mode := config.ManagedResourceMode 219 if matches["data_prefix"] != "" { 220 mode = config.DataResourceMode 221 } 222 resourceIndex, err := ParseResourceIndex(matches["index"]) 223 if err != nil { 224 return nil, err 225 } 226 instanceType, err := ParseInstanceType(matches["instance_type"]) 227 if err != nil { 228 return nil, err 229 } 230 path := ParseResourcePath(matches["path"]) 231 232 // not allowed to say "data." without a type following 233 if mode == config.DataResourceMode && matches["type"] == "" { 234 return nil, fmt.Errorf( 235 "invalid resource address %q: must target specific data instance", 236 s, 237 ) 238 } 239 240 return &ResourceAddress{ 241 Path: path, 242 Index: resourceIndex, 243 InstanceType: instanceType, 244 InstanceTypeSet: matches["instance_type"] != "", 245 Name: matches["name"], 246 Type: matches["type"], 247 Mode: mode, 248 }, nil 249 } 250 251 // ParseResourceAddressForInstanceDiff creates a ResourceAddress for a 252 // resource name as described in a module diff. 253 // 254 // For historical reasons a different addressing format is used in this 255 // context. The internal format should not be shown in the UI and instead 256 // this function should be used to translate to a ResourceAddress and 257 // then, where appropriate, use the String method to produce a canonical 258 // resource address string for display in the UI. 259 // 260 // The given path slice must be empty (or nil) for the root module, and 261 // otherwise consist of a sequence of module names traversing down into 262 // the module tree. If a non-nil path is provided, the caller must not 263 // modify its underlying array after passing it to this function. 264 func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) { 265 addr, err := parseResourceAddressInternal(key) 266 if err != nil { 267 return nil, err 268 } 269 addr.Path = path 270 return addr, nil 271 } 272 273 // Contains returns true if and only if the given node is contained within 274 // the receiver. 275 // 276 // Containment is defined in terms of the module and resource heirarchy: 277 // a resource is contained within its module and any ancestor modules, 278 // an indexed resource instance is contained with the unindexed resource, etc. 279 func (addr *ResourceAddress) Contains(other *ResourceAddress) bool { 280 ourPath := addr.Path 281 givenPath := other.Path 282 if len(givenPath) < len(ourPath) { 283 return false 284 } 285 for i := range ourPath { 286 if ourPath[i] != givenPath[i] { 287 return false 288 } 289 } 290 291 // If the receiver is a whole-module address then the path prefix 292 // matching is all we need. 293 if !addr.HasResourceSpec() { 294 return true 295 } 296 297 if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode { 298 return false 299 } 300 301 if addr.Index != -1 && addr.Index != other.Index { 302 return false 303 } 304 305 if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) { 306 return false 307 } 308 309 return true 310 } 311 312 // Equals returns true if the receiver matches the given address. 313 // 314 // The name of this method is a misnomer, since it doesn't test for exact 315 // equality. Instead, it tests that the _specified_ parts of each 316 // address match, treating any unspecified parts as wildcards. 317 // 318 // See also Contains, which takes a more heirarchical approach to comparing 319 // addresses. 320 func (addr *ResourceAddress) Equals(raw interface{}) bool { 321 other, ok := raw.(*ResourceAddress) 322 if !ok { 323 return false 324 } 325 326 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || 327 reflect.DeepEqual(addr.Path, other.Path) 328 329 indexMatch := addr.Index == -1 || 330 other.Index == -1 || 331 addr.Index == other.Index 332 333 nameMatch := addr.Name == "" || 334 other.Name == "" || 335 addr.Name == other.Name 336 337 typeMatch := addr.Type == "" || 338 other.Type == "" || 339 addr.Type == other.Type 340 341 // mode is significant only when type is set 342 modeMatch := addr.Type == "" || 343 other.Type == "" || 344 addr.Mode == other.Mode 345 346 return pathMatch && 347 indexMatch && 348 addr.InstanceType == other.InstanceType && 349 nameMatch && 350 typeMatch && 351 modeMatch 352 } 353 354 // Less returns true if and only if the receiver should be sorted before 355 // the given address when presenting a list of resource addresses to 356 // an end-user. 357 // 358 // This sort uses lexicographic sorting for most components, but uses 359 // numeric sort for indices, thus causing index 10 to sort after 360 // index 9, rather than after index 1. 361 func (addr *ResourceAddress) Less(other *ResourceAddress) bool { 362 363 switch { 364 365 case len(addr.Path) != len(other.Path): 366 return len(addr.Path) < len(other.Path) 367 368 case !reflect.DeepEqual(addr.Path, other.Path): 369 // If the two paths are the same length but don't match, we'll just 370 // cheat and compare the string forms since it's easier than 371 // comparing all of the path segments in turn, and lexicographic 372 // comparison is correct for the module path portion. 373 addrStr := addr.String() 374 otherStr := other.String() 375 return addrStr < otherStr 376 377 case addr.Mode != other.Mode: 378 return addr.Mode == config.DataResourceMode 379 380 case addr.Type != other.Type: 381 return addr.Type < other.Type 382 383 case addr.Name != other.Name: 384 return addr.Name < other.Name 385 386 case addr.Index != other.Index: 387 // Since "Index" is -1 for an un-indexed address, this also conveniently 388 // sorts unindexed addresses before indexed ones, should they both 389 // appear for some reason. 390 return addr.Index < other.Index 391 392 case addr.InstanceTypeSet != other.InstanceTypeSet: 393 return !addr.InstanceTypeSet 394 395 case addr.InstanceType != other.InstanceType: 396 // InstanceType is actually an enum, so this is just an arbitrary 397 // sort based on the enum numeric values, and thus not particularly 398 // meaningful. 399 return addr.InstanceType < other.InstanceType 400 401 default: 402 return false 403 404 } 405 } 406 407 func ParseResourceIndex(s string) (int, error) { 408 if s == "" { 409 return -1, nil 410 } 411 return strconv.Atoi(s) 412 } 413 414 func ParseResourcePath(s string) []string { 415 if s == "" { 416 return nil 417 } 418 parts := strings.Split(s, ".") 419 path := make([]string, 0, len(parts)) 420 for _, s := range parts { 421 // Due to the limitations of the regexp match below, the path match has 422 // some noise in it we have to filter out :| 423 if s == "" || s == "module" { 424 continue 425 } 426 path = append(path, s) 427 } 428 return path 429 } 430 431 func ParseInstanceType(s string) (InstanceType, error) { 432 switch s { 433 case "", "primary": 434 return TypePrimary, nil 435 case "deposed": 436 return TypeDeposed, nil 437 case "tainted": 438 return TypeTainted, nil 439 default: 440 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) 441 } 442 } 443 444 func tokenizeResourceAddress(s string) (map[string]string, error) { 445 // Example of portions of the regexp below using the 446 // string "aws_instance.web.tainted[1]" 447 re := regexp.MustCompile(`\A` + 448 // "module.foo.module.bar" (optional) 449 `(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` + 450 // possibly "data.", if targeting is a data resource 451 `(?P<data_prefix>(?:data\.)?)` + 452 // "aws_instance.web" (optional when module path specified) 453 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + 454 // "tainted" (optional, omission implies: "primary") 455 `(?:\.(?P<instance_type>\w+))?` + 456 // "1" (optional, omission implies: "0") 457 `(?:\[(?P<index>\d+)\])?` + 458 `\z`) 459 460 groupNames := re.SubexpNames() 461 rawMatches := re.FindAllStringSubmatch(s, -1) 462 if len(rawMatches) != 1 { 463 return nil, fmt.Errorf("invalid resource address %q", s) 464 } 465 466 matches := make(map[string]string) 467 for i, m := range rawMatches[0] { 468 matches[groupNames[i]] = m 469 } 470 471 return matches, nil 472 }