]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "reflect" | |
6 | "regexp" | |
7 | "strconv" | |
8 | "strings" | |
9 | ||
10 | "github.com/hashicorp/terraform/config" | |
c680a8e1 | 11 | "github.com/hashicorp/terraform/config/module" |
bae9f6d2 JC |
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 | ||
c680a8e1 RS |
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 | ||
bae9f6d2 JC |
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"] == "" { | |
c680a8e1 RS |
234 | return nil, fmt.Errorf( |
235 | "invalid resource address %q: must target specific data instance", | |
236 | s, | |
237 | ) | |
bae9f6d2 JC |
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 | ||
c680a8e1 RS |
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. | |
bae9f6d2 JC |
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 | ||
c680a8e1 RS |
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 true | |
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. | |
372 | addrStr := addr.String() | |
373 | otherStr := other.String() | |
374 | return addrStr < otherStr | |
375 | ||
376 | case addr.Mode == config.DataResourceMode && other.Mode != config.DataResourceMode: | |
377 | return true | |
378 | ||
379 | case addr.Type < other.Type: | |
380 | return true | |
381 | ||
382 | case addr.Name < other.Name: | |
383 | return true | |
384 | ||
385 | case addr.Index < other.Index: | |
386 | // Since "Index" is -1 for an un-indexed address, this also conveniently | |
387 | // sorts unindexed addresses before indexed ones, should they both | |
388 | // appear for some reason. | |
389 | return true | |
390 | ||
391 | case other.InstanceTypeSet && !addr.InstanceTypeSet: | |
392 | return true | |
393 | ||
394 | case addr.InstanceType < other.InstanceType: | |
395 | // InstanceType is actually an enum, so this is just an arbitrary | |
396 | // sort based on the enum numeric values, and thus not particularly | |
397 | // meaningful. | |
398 | return true | |
399 | ||
400 | default: | |
401 | return false | |
402 | ||
403 | } | |
404 | } | |
405 | ||
bae9f6d2 JC |
406 | func ParseResourceIndex(s string) (int, error) { |
407 | if s == "" { | |
408 | return -1, nil | |
409 | } | |
410 | return strconv.Atoi(s) | |
411 | } | |
412 | ||
413 | func ParseResourcePath(s string) []string { | |
414 | if s == "" { | |
415 | return nil | |
416 | } | |
417 | parts := strings.Split(s, ".") | |
418 | path := make([]string, 0, len(parts)) | |
419 | for _, s := range parts { | |
420 | // Due to the limitations of the regexp match below, the path match has | |
421 | // some noise in it we have to filter out :| | |
422 | if s == "" || s == "module" { | |
423 | continue | |
424 | } | |
425 | path = append(path, s) | |
426 | } | |
427 | return path | |
428 | } | |
429 | ||
430 | func ParseInstanceType(s string) (InstanceType, error) { | |
431 | switch s { | |
432 | case "", "primary": | |
433 | return TypePrimary, nil | |
434 | case "deposed": | |
435 | return TypeDeposed, nil | |
436 | case "tainted": | |
437 | return TypeTainted, nil | |
438 | default: | |
439 | return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) | |
440 | } | |
441 | } | |
442 | ||
443 | func tokenizeResourceAddress(s string) (map[string]string, error) { | |
444 | // Example of portions of the regexp below using the | |
445 | // string "aws_instance.web.tainted[1]" | |
446 | re := regexp.MustCompile(`\A` + | |
447 | // "module.foo.module.bar" (optional) | |
c680a8e1 | 448 | `(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` + |
bae9f6d2 JC |
449 | // possibly "data.", if targeting is a data resource |
450 | `(?P<data_prefix>(?:data\.)?)` + | |
451 | // "aws_instance.web" (optional when module path specified) | |
452 | `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + | |
453 | // "tainted" (optional, omission implies: "primary") | |
454 | `(?:\.(?P<instance_type>\w+))?` + | |
455 | // "1" (optional, omission implies: "0") | |
456 | `(?:\[(?P<index>\d+)\])?` + | |
457 | `\z`) | |
458 | ||
459 | groupNames := re.SubexpNames() | |
460 | rawMatches := re.FindAllStringSubmatch(s, -1) | |
461 | if len(rawMatches) != 1 { | |
c680a8e1 | 462 | return nil, fmt.Errorf("invalid resource address %q", s) |
bae9f6d2 JC |
463 | } |
464 | ||
465 | matches := make(map[string]string) | |
466 | for i, m := range rawMatches[0] { | |
467 | matches[groupNames[i]] = m | |
468 | } | |
469 | ||
470 | return matches, nil | |
471 | } |