github.com/rhenning/terraform@v0.8.0-beta2/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 ) 12 13 // ResourceAddress is a way of identifying an individual resource (or, 14 // eventually, a subset of resources) within the state. It is used for Targets. 15 type ResourceAddress struct { 16 // Addresses a resource falling somewhere in the module path 17 // When specified alone, addresses all resources within a module path 18 Path []string 19 20 // Addresses a specific resource that occurs in a list 21 Index int 22 23 InstanceType InstanceType 24 InstanceTypeSet bool 25 Name string 26 Type string 27 Mode config.ResourceMode // significant only if InstanceTypeSet 28 } 29 30 // Copy returns a copy of this ResourceAddress 31 func (r *ResourceAddress) Copy() *ResourceAddress { 32 if r == nil { 33 return nil 34 } 35 36 n := &ResourceAddress{ 37 Path: make([]string, 0, len(r.Path)), 38 Index: r.Index, 39 InstanceType: r.InstanceType, 40 Name: r.Name, 41 Type: r.Type, 42 Mode: r.Mode, 43 } 44 for _, p := range r.Path { 45 n.Path = append(n.Path, p) 46 } 47 return n 48 } 49 50 // String outputs the address that parses into this address. 51 func (r *ResourceAddress) String() string { 52 var result []string 53 for _, p := range r.Path { 54 result = append(result, "module", p) 55 } 56 57 switch r.Mode { 58 case config.ManagedResourceMode: 59 // nothing to do 60 case config.DataResourceMode: 61 result = append(result, "data") 62 default: 63 panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) 64 } 65 66 if r.Type != "" { 67 result = append(result, r.Type) 68 } 69 70 if r.Name != "" { 71 name := r.Name 72 if r.InstanceTypeSet { 73 switch r.InstanceType { 74 case TypePrimary: 75 name += ".primary" 76 case TypeDeposed: 77 name += ".deposed" 78 case TypeTainted: 79 name += ".tainted" 80 } 81 } 82 83 if r.Index >= 0 { 84 name += fmt.Sprintf("[%d]", r.Index) 85 } 86 result = append(result, name) 87 } 88 89 return strings.Join(result, ".") 90 } 91 92 // stateId returns the ID that this resource should be entered with 93 // in the state. This is also used for diffs. In the future, we'd like to 94 // move away from this string field so I don't export this. 95 func (r *ResourceAddress) stateId() string { 96 result := fmt.Sprintf("%s.%s", r.Type, r.Name) 97 switch r.Mode { 98 case config.ManagedResourceMode: 99 // Done 100 case config.DataResourceMode: 101 result = fmt.Sprintf("data.%s", result) 102 default: 103 panic(fmt.Errorf("unknown resource mode: %s", r.Mode)) 104 } 105 106 return result 107 } 108 109 // parseResourceAddressConfig creates a resource address from a config.Resource 110 func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) { 111 return &ResourceAddress{ 112 Type: r.Type, 113 Name: r.Name, 114 Index: -1, 115 InstanceType: TypePrimary, 116 Mode: r.Mode, 117 }, nil 118 } 119 120 // parseResourceAddressInternal parses the somewhat bespoke resource 121 // identifier used in states and diffs, such as "instance.name.0". 122 func parseResourceAddressInternal(s string) (*ResourceAddress, error) { 123 // Split based on ".". Every resource address should have at least two 124 // elements (type and name). 125 parts := strings.Split(s, ".") 126 if len(parts) < 2 || len(parts) > 4 { 127 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 128 } 129 130 // Data resource if we have at least 3 parts and the first one is data 131 mode := config.ManagedResourceMode 132 if len(parts) > 2 && parts[0] == "data" { 133 mode = config.DataResourceMode 134 parts = parts[1:] 135 } 136 137 // If we're not a data resource and we have more than 3, then it is an error 138 if len(parts) > 3 && mode != config.DataResourceMode { 139 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 140 } 141 142 // Build the parts of the resource address that are guaranteed to exist 143 addr := &ResourceAddress{ 144 Type: parts[0], 145 Name: parts[1], 146 Index: -1, 147 InstanceType: TypePrimary, 148 Mode: mode, 149 } 150 151 // If we have more parts, then we have an index. Parse that. 152 if len(parts) > 2 { 153 idx, err := strconv.ParseInt(parts[2], 0, 0) 154 if err != nil { 155 return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) 156 } 157 158 addr.Index = int(idx) 159 } 160 161 return addr, nil 162 } 163 164 func ParseResourceAddress(s string) (*ResourceAddress, error) { 165 matches, err := tokenizeResourceAddress(s) 166 if err != nil { 167 return nil, err 168 } 169 mode := config.ManagedResourceMode 170 if matches["data_prefix"] != "" { 171 mode = config.DataResourceMode 172 } 173 resourceIndex, err := ParseResourceIndex(matches["index"]) 174 if err != nil { 175 return nil, err 176 } 177 instanceType, err := ParseInstanceType(matches["instance_type"]) 178 if err != nil { 179 return nil, err 180 } 181 path := ParseResourcePath(matches["path"]) 182 183 // not allowed to say "data." without a type following 184 if mode == config.DataResourceMode && matches["type"] == "" { 185 return nil, fmt.Errorf("must target specific data instance") 186 } 187 188 return &ResourceAddress{ 189 Path: path, 190 Index: resourceIndex, 191 InstanceType: instanceType, 192 InstanceTypeSet: matches["instance_type"] != "", 193 Name: matches["name"], 194 Type: matches["type"], 195 Mode: mode, 196 }, nil 197 } 198 199 func (addr *ResourceAddress) Equals(raw interface{}) bool { 200 other, ok := raw.(*ResourceAddress) 201 if !ok { 202 return false 203 } 204 205 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || 206 reflect.DeepEqual(addr.Path, other.Path) 207 208 indexMatch := addr.Index == -1 || 209 other.Index == -1 || 210 addr.Index == other.Index 211 212 nameMatch := addr.Name == "" || 213 other.Name == "" || 214 addr.Name == other.Name 215 216 typeMatch := addr.Type == "" || 217 other.Type == "" || 218 addr.Type == other.Type 219 220 // mode is significant only when type is set 221 modeMatch := addr.Type == "" || 222 other.Type == "" || 223 addr.Mode == other.Mode 224 225 return pathMatch && 226 indexMatch && 227 addr.InstanceType == other.InstanceType && 228 nameMatch && 229 typeMatch && 230 modeMatch 231 } 232 233 func ParseResourceIndex(s string) (int, error) { 234 if s == "" { 235 return -1, nil 236 } 237 return strconv.Atoi(s) 238 } 239 240 func ParseResourcePath(s string) []string { 241 if s == "" { 242 return nil 243 } 244 parts := strings.Split(s, ".") 245 path := make([]string, 0, len(parts)) 246 for _, s := range parts { 247 // Due to the limitations of the regexp match below, the path match has 248 // some noise in it we have to filter out :| 249 if s == "" || s == "module" { 250 continue 251 } 252 path = append(path, s) 253 } 254 return path 255 } 256 257 func ParseInstanceType(s string) (InstanceType, error) { 258 switch s { 259 case "", "primary": 260 return TypePrimary, nil 261 case "deposed": 262 return TypeDeposed, nil 263 case "tainted": 264 return TypeTainted, nil 265 default: 266 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) 267 } 268 } 269 270 func tokenizeResourceAddress(s string) (map[string]string, error) { 271 // Example of portions of the regexp below using the 272 // string "aws_instance.web.tainted[1]" 273 re := regexp.MustCompile(`\A` + 274 // "module.foo.module.bar" (optional) 275 `(?P<path>(?:module\.[^.]+\.?)*)` + 276 // possibly "data.", if targeting is a data resource 277 `(?P<data_prefix>(?:data\.)?)` + 278 // "aws_instance.web" (optional when module path specified) 279 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + 280 // "tainted" (optional, omission implies: "primary") 281 `(?:\.(?P<instance_type>\w+))?` + 282 // "1" (optional, omission implies: "0") 283 `(?:\[(?P<index>\d+)\])?` + 284 `\z`) 285 groupNames := re.SubexpNames() 286 rawMatches := re.FindAllStringSubmatch(s, -1) 287 if len(rawMatches) != 1 { 288 return nil, fmt.Errorf("Problem parsing address: %q", s) 289 } 290 matches := make(map[string]string) 291 for i, m := range rawMatches[0] { 292 matches[groupNames[i]] = m 293 } 294 return matches, nil 295 }