github.com/paybyphone/terraform@v0.9.5-0.20170613192930-9706042ddd51/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 for _, p := range r.Path { 46 n.Path = append(n.Path, p) 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 func (addr *ResourceAddress) Equals(raw interface{}) bool { 252 other, ok := raw.(*ResourceAddress) 253 if !ok { 254 return false 255 } 256 257 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || 258 reflect.DeepEqual(addr.Path, other.Path) 259 260 indexMatch := addr.Index == -1 || 261 other.Index == -1 || 262 addr.Index == other.Index 263 264 nameMatch := addr.Name == "" || 265 other.Name == "" || 266 addr.Name == other.Name 267 268 typeMatch := addr.Type == "" || 269 other.Type == "" || 270 addr.Type == other.Type 271 272 // mode is significant only when type is set 273 modeMatch := addr.Type == "" || 274 other.Type == "" || 275 addr.Mode == other.Mode 276 277 return pathMatch && 278 indexMatch && 279 addr.InstanceType == other.InstanceType && 280 nameMatch && 281 typeMatch && 282 modeMatch 283 } 284 285 func ParseResourceIndex(s string) (int, error) { 286 if s == "" { 287 return -1, nil 288 } 289 return strconv.Atoi(s) 290 } 291 292 func ParseResourcePath(s string) []string { 293 if s == "" { 294 return nil 295 } 296 parts := strings.Split(s, ".") 297 path := make([]string, 0, len(parts)) 298 for _, s := range parts { 299 // Due to the limitations of the regexp match below, the path match has 300 // some noise in it we have to filter out :| 301 if s == "" || s == "module" { 302 continue 303 } 304 path = append(path, s) 305 } 306 return path 307 } 308 309 func ParseInstanceType(s string) (InstanceType, error) { 310 switch s { 311 case "", "primary": 312 return TypePrimary, nil 313 case "deposed": 314 return TypeDeposed, nil 315 case "tainted": 316 return TypeTainted, nil 317 default: 318 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) 319 } 320 } 321 322 func tokenizeResourceAddress(s string) (map[string]string, error) { 323 // Example of portions of the regexp below using the 324 // string "aws_instance.web.tainted[1]" 325 re := regexp.MustCompile(`\A` + 326 // "module.foo.module.bar" (optional) 327 `(?P<path>(?:module\.[^.]+\.?)*)` + 328 // possibly "data.", if targeting is a data resource 329 `(?P<data_prefix>(?:data\.)?)` + 330 // "aws_instance.web" (optional when module path specified) 331 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + 332 // "tainted" (optional, omission implies: "primary") 333 `(?:\.(?P<instance_type>\w+))?` + 334 // "1" (optional, omission implies: "0") 335 `(?:\[(?P<index>\d+)\])?` + 336 `\z`) 337 338 groupNames := re.SubexpNames() 339 rawMatches := re.FindAllStringSubmatch(s, -1) 340 if len(rawMatches) != 1 { 341 return nil, fmt.Errorf("invalid resource address %q", s) 342 } 343 344 matches := make(map[string]string) 345 for i, m := range rawMatches[0] { 346 matches[groupNames[i]] = m 347 } 348 349 return matches, nil 350 }