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