github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 if r.Index >= 0 { 106 result += fmt.Sprintf(".%d", r.Index) 107 } 108 109 return result 110 } 111 112 // parseResourceAddressConfig creates a resource address from a config.Resource 113 func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) { 114 return &ResourceAddress{ 115 Type: r.Type, 116 Name: r.Name, 117 Index: -1, 118 InstanceType: TypePrimary, 119 Mode: r.Mode, 120 }, nil 121 } 122 123 // parseResourceAddressInternal parses the somewhat bespoke resource 124 // identifier used in states and diffs, such as "instance.name.0". 125 func parseResourceAddressInternal(s string) (*ResourceAddress, error) { 126 // Split based on ".". Every resource address should have at least two 127 // elements (type and name). 128 parts := strings.Split(s, ".") 129 if len(parts) < 2 || len(parts) > 4 { 130 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 131 } 132 133 // Data resource if we have at least 3 parts and the first one is data 134 mode := config.ManagedResourceMode 135 if len(parts) > 2 && parts[0] == "data" { 136 mode = config.DataResourceMode 137 parts = parts[1:] 138 } 139 140 // If we're not a data resource and we have more than 3, then it is an error 141 if len(parts) > 3 && mode != config.DataResourceMode { 142 return nil, fmt.Errorf("Invalid internal resource address format: %s", s) 143 } 144 145 // Build the parts of the resource address that are guaranteed to exist 146 addr := &ResourceAddress{ 147 Type: parts[0], 148 Name: parts[1], 149 Index: -1, 150 InstanceType: TypePrimary, 151 Mode: mode, 152 } 153 154 // If we have more parts, then we have an index. Parse that. 155 if len(parts) > 2 { 156 idx, err := strconv.ParseInt(parts[2], 0, 0) 157 if err != nil { 158 return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) 159 } 160 161 addr.Index = int(idx) 162 } 163 164 return addr, nil 165 } 166 167 func ParseResourceAddress(s string) (*ResourceAddress, error) { 168 matches, err := tokenizeResourceAddress(s) 169 if err != nil { 170 return nil, err 171 } 172 mode := config.ManagedResourceMode 173 if matches["data_prefix"] != "" { 174 mode = config.DataResourceMode 175 } 176 resourceIndex, err := ParseResourceIndex(matches["index"]) 177 if err != nil { 178 return nil, err 179 } 180 instanceType, err := ParseInstanceType(matches["instance_type"]) 181 if err != nil { 182 return nil, err 183 } 184 path := ParseResourcePath(matches["path"]) 185 186 // not allowed to say "data." without a type following 187 if mode == config.DataResourceMode && matches["type"] == "" { 188 return nil, fmt.Errorf("must target specific data instance") 189 } 190 191 return &ResourceAddress{ 192 Path: path, 193 Index: resourceIndex, 194 InstanceType: instanceType, 195 InstanceTypeSet: matches["instance_type"] != "", 196 Name: matches["name"], 197 Type: matches["type"], 198 Mode: mode, 199 }, nil 200 } 201 202 func (addr *ResourceAddress) Equals(raw interface{}) bool { 203 other, ok := raw.(*ResourceAddress) 204 if !ok { 205 return false 206 } 207 208 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || 209 reflect.DeepEqual(addr.Path, other.Path) 210 211 indexMatch := addr.Index == -1 || 212 other.Index == -1 || 213 addr.Index == other.Index 214 215 nameMatch := addr.Name == "" || 216 other.Name == "" || 217 addr.Name == other.Name 218 219 typeMatch := addr.Type == "" || 220 other.Type == "" || 221 addr.Type == other.Type 222 223 // mode is significant only when type is set 224 modeMatch := addr.Type == "" || 225 other.Type == "" || 226 addr.Mode == other.Mode 227 228 return pathMatch && 229 indexMatch && 230 addr.InstanceType == other.InstanceType && 231 nameMatch && 232 typeMatch && 233 modeMatch 234 } 235 236 func ParseResourceIndex(s string) (int, error) { 237 if s == "" { 238 return -1, nil 239 } 240 return strconv.Atoi(s) 241 } 242 243 func ParseResourcePath(s string) []string { 244 if s == "" { 245 return nil 246 } 247 parts := strings.Split(s, ".") 248 path := make([]string, 0, len(parts)) 249 for _, s := range parts { 250 // Due to the limitations of the regexp match below, the path match has 251 // some noise in it we have to filter out :| 252 if s == "" || s == "module" { 253 continue 254 } 255 path = append(path, s) 256 } 257 return path 258 } 259 260 func ParseInstanceType(s string) (InstanceType, error) { 261 switch s { 262 case "", "primary": 263 return TypePrimary, nil 264 case "deposed": 265 return TypeDeposed, nil 266 case "tainted": 267 return TypeTainted, nil 268 default: 269 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) 270 } 271 } 272 273 func tokenizeResourceAddress(s string) (map[string]string, error) { 274 // Example of portions of the regexp below using the 275 // string "aws_instance.web.tainted[1]" 276 re := regexp.MustCompile(`\A` + 277 // "module.foo.module.bar" (optional) 278 `(?P<path>(?:module\.[^.]+\.?)*)` + 279 // possibly "data.", if targeting is a data resource 280 `(?P<data_prefix>(?:data\.)?)` + 281 // "aws_instance.web" (optional when module path specified) 282 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + 283 // "tainted" (optional, omission implies: "primary") 284 `(?:\.(?P<instance_type>\w+))?` + 285 // "1" (optional, omission implies: "0") 286 `(?:\[(?P<index>\d+)\])?` + 287 `\z`) 288 289 groupNames := re.SubexpNames() 290 rawMatches := re.FindAllStringSubmatch(s, -1) 291 if len(rawMatches) != 1 { 292 return nil, fmt.Errorf("Problem parsing address: %q", s) 293 } 294 295 matches := make(map[string]string) 296 for i, m := range rawMatches[0] { 297 matches[groupNames[i]] = m 298 } 299 300 return matches, nil 301 }