]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "reflect" | |
6 | "regexp" | |
7 | "strconv" | |
8 | "strings" | |
9 | ||
107c1cdb ND |
10 | "github.com/hashicorp/terraform/addrs" |
11 | ||
bae9f6d2 | 12 | "github.com/hashicorp/terraform/config" |
107c1cdb | 13 | "github.com/hashicorp/terraform/configs" |
bae9f6d2 JC |
14 | ) |
15 | ||
16 | // ResourceAddress is a way of identifying an individual resource (or, | |
17 | // eventually, a subset of resources) within the state. It is used for Targets. | |
18 | type ResourceAddress struct { | |
19 | // Addresses a resource falling somewhere in the module path | |
20 | // When specified alone, addresses all resources within a module path | |
21 | Path []string | |
22 | ||
23 | // Addresses a specific resource that occurs in a list | |
24 | Index int | |
25 | ||
26 | InstanceType InstanceType | |
27 | InstanceTypeSet bool | |
28 | Name string | |
29 | Type string | |
30 | Mode config.ResourceMode // significant only if InstanceTypeSet | |
31 | } | |
32 | ||
33 | // Copy returns a copy of this ResourceAddress | |
34 | func (r *ResourceAddress) Copy() *ResourceAddress { | |
35 | if r == nil { | |
36 | return nil | |
37 | } | |
38 | ||
39 | n := &ResourceAddress{ | |
40 | Path: make([]string, 0, len(r.Path)), | |
41 | Index: r.Index, | |
42 | InstanceType: r.InstanceType, | |
43 | Name: r.Name, | |
44 | Type: r.Type, | |
45 | Mode: r.Mode, | |
46 | } | |
15c0b25d AP |
47 | |
48 | n.Path = append(n.Path, r.Path...) | |
49 | ||
bae9f6d2 JC |
50 | return n |
51 | } | |
52 | ||
53 | // String outputs the address that parses into this address. | |
54 | func (r *ResourceAddress) String() string { | |
55 | var result []string | |
56 | for _, p := range r.Path { | |
57 | result = append(result, "module", p) | |
58 | } | |
59 | ||
60 | switch r.Mode { | |
61 | case config.ManagedResourceMode: | |
62 | // nothing to do | |
63 | case config.DataResourceMode: | |
64 | result = append(result, "data") | |
65 | default: | |
66 | panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) | |
67 | } | |
68 | ||
69 | if r.Type != "" { | |
70 | result = append(result, r.Type) | |
71 | } | |
72 | ||
73 | if r.Name != "" { | |
74 | name := r.Name | |
75 | if r.InstanceTypeSet { | |
76 | switch r.InstanceType { | |
77 | case TypePrimary: | |
78 | name += ".primary" | |
79 | case TypeDeposed: | |
80 | name += ".deposed" | |
81 | case TypeTainted: | |
82 | name += ".tainted" | |
83 | } | |
84 | } | |
85 | ||
86 | if r.Index >= 0 { | |
87 | name += fmt.Sprintf("[%d]", r.Index) | |
88 | } | |
89 | result = append(result, name) | |
90 | } | |
91 | ||
92 | return strings.Join(result, ".") | |
93 | } | |
94 | ||
c680a8e1 RS |
95 | // HasResourceSpec returns true if the address has a resource spec, as |
96 | // defined in the documentation: | |
97 | // https://www.terraform.io/docs/internals/resource-addressing.html | |
98 | // In particular, this returns false if the address contains only | |
99 | // a module path, thus addressing the entire module. | |
100 | func (r *ResourceAddress) HasResourceSpec() bool { | |
101 | return r.Type != "" && r.Name != "" | |
102 | } | |
103 | ||
104 | // WholeModuleAddress returns the resource address that refers to all | |
105 | // resources in the same module as the receiver address. | |
106 | func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress { | |
107 | return &ResourceAddress{ | |
108 | Path: r.Path, | |
109 | Index: -1, | |
110 | InstanceTypeSet: false, | |
111 | } | |
112 | } | |
113 | ||
107c1cdb ND |
114 | // MatchesResourceConfig returns true if the receiver matches the given |
115 | // configuration resource within the given _static_ module path. Note that | |
116 | // the module path in a resource address is a _dynamic_ module path, and | |
117 | // multiple dynamic resource paths may map to a single static path if | |
118 | // count and for_each are in use on module calls. | |
c680a8e1 RS |
119 | // |
120 | // Since resource configuration blocks represent all of the instances of | |
121 | // a multi-instance resource, the index of the address (if any) is not | |
122 | // considered. | |
107c1cdb | 123 | func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool { |
c680a8e1 | 124 | if r.HasResourceSpec() { |
107c1cdb ND |
125 | // FIXME: Some ugliness while we are between worlds. Functionality |
126 | // in "addrs" should eventually replace this ResourceAddress idea | |
127 | // completely, but for now we'll need to translate to the old | |
128 | // way of representing resource modes. | |
129 | switch r.Mode { | |
130 | case config.ManagedResourceMode: | |
131 | if rc.Mode != addrs.ManagedResourceMode { | |
132 | return false | |
133 | } | |
134 | case config.DataResourceMode: | |
135 | if rc.Mode != addrs.DataResourceMode { | |
136 | return false | |
137 | } | |
138 | } | |
139 | if r.Type != rc.Type || r.Name != rc.Name { | |
c680a8e1 RS |
140 | return false |
141 | } | |
142 | } | |
143 | ||
144 | addrPath := r.Path | |
c680a8e1 RS |
145 | |
146 | // normalize | |
147 | if len(addrPath) == 0 { | |
148 | addrPath = nil | |
149 | } | |
107c1cdb ND |
150 | if len(path) == 0 { |
151 | path = nil | |
c680a8e1 | 152 | } |
107c1cdb ND |
153 | rawPath := []string(path) |
154 | return reflect.DeepEqual(addrPath, rawPath) | |
c680a8e1 RS |
155 | } |
156 | ||
bae9f6d2 JC |
157 | // stateId returns the ID that this resource should be entered with |
158 | // in the state. This is also used for diffs. In the future, we'd like to | |
159 | // move away from this string field so I don't export this. | |
160 | func (r *ResourceAddress) stateId() string { | |
161 | result := fmt.Sprintf("%s.%s", r.Type, r.Name) | |
162 | switch r.Mode { | |
163 | case config.ManagedResourceMode: | |
164 | // Done | |
165 | case config.DataResourceMode: | |
166 | result = fmt.Sprintf("data.%s", result) | |
167 | default: | |
168 | panic(fmt.Errorf("unknown resource mode: %s", r.Mode)) | |
169 | } | |
170 | if r.Index >= 0 { | |
171 | result += fmt.Sprintf(".%d", r.Index) | |
172 | } | |
173 | ||
174 | return result | |
175 | } | |
176 | ||
177 | // parseResourceAddressConfig creates a resource address from a config.Resource | |
178 | func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) { | |
179 | return &ResourceAddress{ | |
180 | Type: r.Type, | |
181 | Name: r.Name, | |
182 | Index: -1, | |
183 | InstanceType: TypePrimary, | |
184 | Mode: r.Mode, | |
185 | }, nil | |
186 | } | |
187 | ||
188 | // parseResourceAddressInternal parses the somewhat bespoke resource | |
189 | // identifier used in states and diffs, such as "instance.name.0". | |
190 | func parseResourceAddressInternal(s string) (*ResourceAddress, error) { | |
191 | // Split based on ".". Every resource address should have at least two | |
192 | // elements (type and name). | |
193 | parts := strings.Split(s, ".") | |
194 | if len(parts) < 2 || len(parts) > 4 { | |
195 | return nil, fmt.Errorf("Invalid internal resource address format: %s", s) | |
196 | } | |
197 | ||
198 | // Data resource if we have at least 3 parts and the first one is data | |
199 | mode := config.ManagedResourceMode | |
200 | if len(parts) > 2 && parts[0] == "data" { | |
201 | mode = config.DataResourceMode | |
202 | parts = parts[1:] | |
203 | } | |
204 | ||
205 | // If we're not a data resource and we have more than 3, then it is an error | |
206 | if len(parts) > 3 && mode != config.DataResourceMode { | |
207 | return nil, fmt.Errorf("Invalid internal resource address format: %s", s) | |
208 | } | |
209 | ||
210 | // Build the parts of the resource address that are guaranteed to exist | |
211 | addr := &ResourceAddress{ | |
212 | Type: parts[0], | |
213 | Name: parts[1], | |
214 | Index: -1, | |
215 | InstanceType: TypePrimary, | |
216 | Mode: mode, | |
217 | } | |
218 | ||
219 | // If we have more parts, then we have an index. Parse that. | |
220 | if len(parts) > 2 { | |
221 | idx, err := strconv.ParseInt(parts[2], 0, 0) | |
222 | if err != nil { | |
223 | return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) | |
224 | } | |
225 | ||
226 | addr.Index = int(idx) | |
227 | } | |
228 | ||
229 | return addr, nil | |
230 | } | |
231 | ||
232 | func ParseResourceAddress(s string) (*ResourceAddress, error) { | |
233 | matches, err := tokenizeResourceAddress(s) | |
234 | if err != nil { | |
235 | return nil, err | |
236 | } | |
237 | mode := config.ManagedResourceMode | |
238 | if matches["data_prefix"] != "" { | |
239 | mode = config.DataResourceMode | |
240 | } | |
241 | resourceIndex, err := ParseResourceIndex(matches["index"]) | |
242 | if err != nil { | |
243 | return nil, err | |
244 | } | |
245 | instanceType, err := ParseInstanceType(matches["instance_type"]) | |
246 | if err != nil { | |
247 | return nil, err | |
248 | } | |
249 | path := ParseResourcePath(matches["path"]) | |
250 | ||
251 | // not allowed to say "data." without a type following | |
252 | if mode == config.DataResourceMode && matches["type"] == "" { | |
c680a8e1 RS |
253 | return nil, fmt.Errorf( |
254 | "invalid resource address %q: must target specific data instance", | |
255 | s, | |
256 | ) | |
bae9f6d2 JC |
257 | } |
258 | ||
259 | return &ResourceAddress{ | |
260 | Path: path, | |
261 | Index: resourceIndex, | |
262 | InstanceType: instanceType, | |
263 | InstanceTypeSet: matches["instance_type"] != "", | |
264 | Name: matches["name"], | |
265 | Type: matches["type"], | |
266 | Mode: mode, | |
267 | }, nil | |
268 | } | |
269 | ||
c680a8e1 RS |
270 | // ParseResourceAddressForInstanceDiff creates a ResourceAddress for a |
271 | // resource name as described in a module diff. | |
272 | // | |
273 | // For historical reasons a different addressing format is used in this | |
274 | // context. The internal format should not be shown in the UI and instead | |
275 | // this function should be used to translate to a ResourceAddress and | |
276 | // then, where appropriate, use the String method to produce a canonical | |
277 | // resource address string for display in the UI. | |
278 | // | |
279 | // The given path slice must be empty (or nil) for the root module, and | |
280 | // otherwise consist of a sequence of module names traversing down into | |
281 | // the module tree. If a non-nil path is provided, the caller must not | |
282 | // modify its underlying array after passing it to this function. | |
283 | func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) { | |
284 | addr, err := parseResourceAddressInternal(key) | |
285 | if err != nil { | |
286 | return nil, err | |
287 | } | |
288 | addr.Path = path | |
289 | return addr, nil | |
290 | } | |
291 | ||
107c1cdb ND |
292 | // NewLegacyResourceAddress creates a ResourceAddress from a new-style |
293 | // addrs.AbsResource value. | |
294 | // | |
295 | // This is provided for shimming purposes so that we can still easily call into | |
296 | // older functions that expect the ResourceAddress type. | |
297 | func NewLegacyResourceAddress(addr addrs.AbsResource) *ResourceAddress { | |
298 | ret := &ResourceAddress{ | |
299 | Type: addr.Resource.Type, | |
300 | Name: addr.Resource.Name, | |
301 | } | |
302 | ||
303 | switch addr.Resource.Mode { | |
304 | case addrs.ManagedResourceMode: | |
305 | ret.Mode = config.ManagedResourceMode | |
306 | case addrs.DataResourceMode: | |
307 | ret.Mode = config.DataResourceMode | |
308 | default: | |
309 | panic(fmt.Errorf("cannot shim %s to legacy config.ResourceMode value", addr.Resource.Mode)) | |
310 | } | |
311 | ||
312 | path := make([]string, len(addr.Module)) | |
313 | for i, step := range addr.Module { | |
314 | if step.InstanceKey != addrs.NoKey { | |
315 | // At the time of writing this can't happen because we don't | |
316 | // ket generate keyed module instances. This legacy codepath must | |
317 | // be removed before we can support "count" and "for_each" for | |
318 | // modules. | |
319 | panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) | |
320 | } | |
321 | ||
322 | path[i] = step.Name | |
323 | } | |
324 | ret.Path = path | |
325 | ret.Index = -1 | |
326 | ||
327 | return ret | |
328 | } | |
329 | ||
330 | // NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style | |
331 | // addrs.AbsResource value. | |
332 | // | |
333 | // This is provided for shimming purposes so that we can still easily call into | |
334 | // older functions that expect the ResourceAddress type. | |
335 | func NewLegacyResourceInstanceAddress(addr addrs.AbsResourceInstance) *ResourceAddress { | |
336 | ret := &ResourceAddress{ | |
337 | Type: addr.Resource.Resource.Type, | |
338 | Name: addr.Resource.Resource.Name, | |
339 | } | |
340 | ||
341 | switch addr.Resource.Resource.Mode { | |
342 | case addrs.ManagedResourceMode: | |
343 | ret.Mode = config.ManagedResourceMode | |
344 | case addrs.DataResourceMode: | |
345 | ret.Mode = config.DataResourceMode | |
346 | default: | |
347 | panic(fmt.Errorf("cannot shim %s to legacy config.ResourceMode value", addr.Resource.Resource.Mode)) | |
348 | } | |
349 | ||
350 | path := make([]string, len(addr.Module)) | |
351 | for i, step := range addr.Module { | |
352 | if step.InstanceKey != addrs.NoKey { | |
353 | // At the time of writing this can't happen because we don't | |
354 | // ket generate keyed module instances. This legacy codepath must | |
355 | // be removed before we can support "count" and "for_each" for | |
356 | // modules. | |
357 | panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) | |
358 | } | |
359 | ||
360 | path[i] = step.Name | |
361 | } | |
362 | ret.Path = path | |
363 | ||
364 | if addr.Resource.Key == addrs.NoKey { | |
365 | ret.Index = -1 | |
366 | } else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok { | |
367 | ret.Index = int(ik) | |
368 | } else { | |
369 | panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key)) | |
370 | } | |
371 | ||
372 | return ret | |
373 | } | |
374 | ||
375 | // AbsResourceInstanceAddr converts the receiver, a legacy resource address, to | |
376 | // the new resource address type addrs.AbsResourceInstance. | |
377 | // | |
378 | // This method can be used only on an address that has a resource specification. | |
379 | // It will panic if called on a module-path-only ResourceAddress. Use | |
380 | // method HasResourceSpec to check before calling, in contexts where it is | |
381 | // unclear. | |
382 | // | |
383 | // addrs.AbsResourceInstance does not represent the "tainted" and "deposed" | |
384 | // states, and so if these are present on the receiver then they are discarded. | |
385 | // | |
386 | // This is provided for shimming purposes so that we can easily adapt functions | |
387 | // that are returning the legacy ResourceAddress type, for situations where | |
388 | // the new type is required. | |
389 | func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance { | |
390 | if !addr.HasResourceSpec() { | |
391 | panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec") | |
392 | } | |
393 | ||
394 | ret := addrs.AbsResourceInstance{ | |
395 | Module: addr.ModuleInstanceAddr(), | |
396 | Resource: addrs.ResourceInstance{ | |
397 | Resource: addrs.Resource{ | |
398 | Type: addr.Type, | |
399 | Name: addr.Name, | |
400 | }, | |
401 | }, | |
402 | } | |
403 | ||
404 | switch addr.Mode { | |
405 | case config.ManagedResourceMode: | |
406 | ret.Resource.Resource.Mode = addrs.ManagedResourceMode | |
407 | case config.DataResourceMode: | |
408 | ret.Resource.Resource.Mode = addrs.DataResourceMode | |
409 | default: | |
410 | panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode)) | |
411 | } | |
412 | ||
413 | if addr.Index != -1 { | |
414 | ret.Resource.Key = addrs.IntKey(addr.Index) | |
415 | } | |
416 | ||
417 | return ret | |
418 | } | |
419 | ||
420 | // ModuleInstanceAddr returns the module path portion of the receiver as a | |
421 | // addrs.ModuleInstance value. | |
422 | func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance { | |
423 | path := make(addrs.ModuleInstance, len(addr.Path)) | |
424 | for i, name := range addr.Path { | |
425 | path[i] = addrs.ModuleInstanceStep{Name: name} | |
426 | } | |
427 | return path | |
428 | } | |
429 | ||
c680a8e1 RS |
430 | // Contains returns true if and only if the given node is contained within |
431 | // the receiver. | |
432 | // | |
433 | // Containment is defined in terms of the module and resource heirarchy: | |
434 | // a resource is contained within its module and any ancestor modules, | |
435 | // an indexed resource instance is contained with the unindexed resource, etc. | |
436 | func (addr *ResourceAddress) Contains(other *ResourceAddress) bool { | |
437 | ourPath := addr.Path | |
438 | givenPath := other.Path | |
439 | if len(givenPath) < len(ourPath) { | |
440 | return false | |
441 | } | |
442 | for i := range ourPath { | |
443 | if ourPath[i] != givenPath[i] { | |
444 | return false | |
445 | } | |
446 | } | |
447 | ||
448 | // If the receiver is a whole-module address then the path prefix | |
449 | // matching is all we need. | |
450 | if !addr.HasResourceSpec() { | |
451 | return true | |
452 | } | |
453 | ||
454 | if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode { | |
455 | return false | |
456 | } | |
457 | ||
458 | if addr.Index != -1 && addr.Index != other.Index { | |
459 | return false | |
460 | } | |
461 | ||
462 | if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) { | |
463 | return false | |
464 | } | |
465 | ||
466 | return true | |
467 | } | |
468 | ||
469 | // Equals returns true if the receiver matches the given address. | |
470 | // | |
471 | // The name of this method is a misnomer, since it doesn't test for exact | |
472 | // equality. Instead, it tests that the _specified_ parts of each | |
473 | // address match, treating any unspecified parts as wildcards. | |
474 | // | |
475 | // See also Contains, which takes a more heirarchical approach to comparing | |
476 | // addresses. | |
bae9f6d2 JC |
477 | func (addr *ResourceAddress) Equals(raw interface{}) bool { |
478 | other, ok := raw.(*ResourceAddress) | |
479 | if !ok { | |
480 | return false | |
481 | } | |
482 | ||
483 | pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || | |
484 | reflect.DeepEqual(addr.Path, other.Path) | |
485 | ||
486 | indexMatch := addr.Index == -1 || | |
487 | other.Index == -1 || | |
488 | addr.Index == other.Index | |
489 | ||
490 | nameMatch := addr.Name == "" || | |
491 | other.Name == "" || | |
492 | addr.Name == other.Name | |
493 | ||
494 | typeMatch := addr.Type == "" || | |
495 | other.Type == "" || | |
496 | addr.Type == other.Type | |
497 | ||
498 | // mode is significant only when type is set | |
499 | modeMatch := addr.Type == "" || | |
500 | other.Type == "" || | |
501 | addr.Mode == other.Mode | |
502 | ||
503 | return pathMatch && | |
504 | indexMatch && | |
505 | addr.InstanceType == other.InstanceType && | |
506 | nameMatch && | |
507 | typeMatch && | |
508 | modeMatch | |
509 | } | |
510 | ||
c680a8e1 RS |
511 | // Less returns true if and only if the receiver should be sorted before |
512 | // the given address when presenting a list of resource addresses to | |
513 | // an end-user. | |
514 | // | |
515 | // This sort uses lexicographic sorting for most components, but uses | |
516 | // numeric sort for indices, thus causing index 10 to sort after | |
517 | // index 9, rather than after index 1. | |
518 | func (addr *ResourceAddress) Less(other *ResourceAddress) bool { | |
519 | ||
520 | switch { | |
521 | ||
15c0b25d AP |
522 | case len(addr.Path) != len(other.Path): |
523 | return len(addr.Path) < len(other.Path) | |
c680a8e1 RS |
524 | |
525 | case !reflect.DeepEqual(addr.Path, other.Path): | |
526 | // If the two paths are the same length but don't match, we'll just | |
527 | // cheat and compare the string forms since it's easier than | |
15c0b25d AP |
528 | // comparing all of the path segments in turn, and lexicographic |
529 | // comparison is correct for the module path portion. | |
c680a8e1 RS |
530 | addrStr := addr.String() |
531 | otherStr := other.String() | |
532 | return addrStr < otherStr | |
533 | ||
15c0b25d AP |
534 | case addr.Mode != other.Mode: |
535 | return addr.Mode == config.DataResourceMode | |
c680a8e1 | 536 | |
15c0b25d AP |
537 | case addr.Type != other.Type: |
538 | return addr.Type < other.Type | |
c680a8e1 | 539 | |
15c0b25d AP |
540 | case addr.Name != other.Name: |
541 | return addr.Name < other.Name | |
c680a8e1 | 542 | |
15c0b25d | 543 | case addr.Index != other.Index: |
c680a8e1 RS |
544 | // Since "Index" is -1 for an un-indexed address, this also conveniently |
545 | // sorts unindexed addresses before indexed ones, should they both | |
546 | // appear for some reason. | |
15c0b25d | 547 | return addr.Index < other.Index |
c680a8e1 | 548 | |
15c0b25d AP |
549 | case addr.InstanceTypeSet != other.InstanceTypeSet: |
550 | return !addr.InstanceTypeSet | |
c680a8e1 | 551 | |
15c0b25d | 552 | case addr.InstanceType != other.InstanceType: |
c680a8e1 RS |
553 | // InstanceType is actually an enum, so this is just an arbitrary |
554 | // sort based on the enum numeric values, and thus not particularly | |
555 | // meaningful. | |
15c0b25d | 556 | return addr.InstanceType < other.InstanceType |
c680a8e1 RS |
557 | |
558 | default: | |
559 | return false | |
560 | ||
561 | } | |
562 | } | |
563 | ||
bae9f6d2 JC |
564 | func ParseResourceIndex(s string) (int, error) { |
565 | if s == "" { | |
566 | return -1, nil | |
567 | } | |
568 | return strconv.Atoi(s) | |
569 | } | |
570 | ||
571 | func ParseResourcePath(s string) []string { | |
572 | if s == "" { | |
573 | return nil | |
574 | } | |
575 | parts := strings.Split(s, ".") | |
576 | path := make([]string, 0, len(parts)) | |
577 | for _, s := range parts { | |
578 | // Due to the limitations of the regexp match below, the path match has | |
579 | // some noise in it we have to filter out :| | |
580 | if s == "" || s == "module" { | |
581 | continue | |
582 | } | |
583 | path = append(path, s) | |
584 | } | |
585 | return path | |
586 | } | |
587 | ||
588 | func ParseInstanceType(s string) (InstanceType, error) { | |
589 | switch s { | |
590 | case "", "primary": | |
591 | return TypePrimary, nil | |
592 | case "deposed": | |
593 | return TypeDeposed, nil | |
594 | case "tainted": | |
595 | return TypeTainted, nil | |
596 | default: | |
597 | return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) | |
598 | } | |
599 | } | |
600 | ||
601 | func tokenizeResourceAddress(s string) (map[string]string, error) { | |
602 | // Example of portions of the regexp below using the | |
603 | // string "aws_instance.web.tainted[1]" | |
604 | re := regexp.MustCompile(`\A` + | |
605 | // "module.foo.module.bar" (optional) | |
c680a8e1 | 606 | `(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` + |
bae9f6d2 JC |
607 | // possibly "data.", if targeting is a data resource |
608 | `(?P<data_prefix>(?:data\.)?)` + | |
609 | // "aws_instance.web" (optional when module path specified) | |
610 | `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` + | |
611 | // "tainted" (optional, omission implies: "primary") | |
612 | `(?:\.(?P<instance_type>\w+))?` + | |
613 | // "1" (optional, omission implies: "0") | |
614 | `(?:\[(?P<index>\d+)\])?` + | |
615 | `\z`) | |
616 | ||
617 | groupNames := re.SubexpNames() | |
618 | rawMatches := re.FindAllStringSubmatch(s, -1) | |
619 | if len(rawMatches) != 1 { | |
c680a8e1 | 620 | return nil, fmt.Errorf("invalid resource address %q", s) |
bae9f6d2 JC |
621 | } |
622 | ||
623 | matches := make(map[string]string) | |
624 | for i, m := range rawMatches[0] { | |
625 | matches[groupNames[i]] = m | |
626 | } | |
627 | ||
628 | return matches, nil | |
629 | } |