]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/resource_address.go
update vendor and go.mod
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / resource_address.go
CommitLineData
bae9f6d2
JC
1package terraform
2
3import (
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.
18type 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
34func (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.
54func (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.
100func (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.
106func (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 123func (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.
160func (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
178func 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".
190func 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
232func 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.
283func 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.
297func 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.
335func 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)
863486a6
AG
368 } else if _, ok := addr.Resource.Key.(addrs.StringKey); ok {
369 ret.Index = -1
107c1cdb
ND
370 } else {
371 panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key))
372 }
373
374 return ret
375}
376
377// AbsResourceInstanceAddr converts the receiver, a legacy resource address, to
378// the new resource address type addrs.AbsResourceInstance.
379//
380// This method can be used only on an address that has a resource specification.
381// It will panic if called on a module-path-only ResourceAddress. Use
382// method HasResourceSpec to check before calling, in contexts where it is
383// unclear.
384//
385// addrs.AbsResourceInstance does not represent the "tainted" and "deposed"
386// states, and so if these are present on the receiver then they are discarded.
387//
388// This is provided for shimming purposes so that we can easily adapt functions
389// that are returning the legacy ResourceAddress type, for situations where
390// the new type is required.
391func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance {
392 if !addr.HasResourceSpec() {
393 panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec")
394 }
395
396 ret := addrs.AbsResourceInstance{
397 Module: addr.ModuleInstanceAddr(),
398 Resource: addrs.ResourceInstance{
399 Resource: addrs.Resource{
400 Type: addr.Type,
401 Name: addr.Name,
402 },
403 },
404 }
405
406 switch addr.Mode {
407 case config.ManagedResourceMode:
408 ret.Resource.Resource.Mode = addrs.ManagedResourceMode
409 case config.DataResourceMode:
410 ret.Resource.Resource.Mode = addrs.DataResourceMode
411 default:
412 panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode))
413 }
414
415 if addr.Index != -1 {
416 ret.Resource.Key = addrs.IntKey(addr.Index)
417 }
418
419 return ret
420}
421
422// ModuleInstanceAddr returns the module path portion of the receiver as a
423// addrs.ModuleInstance value.
424func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance {
425 path := make(addrs.ModuleInstance, len(addr.Path))
426 for i, name := range addr.Path {
427 path[i] = addrs.ModuleInstanceStep{Name: name}
428 }
429 return path
430}
431
c680a8e1
RS
432// Contains returns true if and only if the given node is contained within
433// the receiver.
434//
435// Containment is defined in terms of the module and resource heirarchy:
436// a resource is contained within its module and any ancestor modules,
437// an indexed resource instance is contained with the unindexed resource, etc.
438func (addr *ResourceAddress) Contains(other *ResourceAddress) bool {
439 ourPath := addr.Path
440 givenPath := other.Path
441 if len(givenPath) < len(ourPath) {
442 return false
443 }
444 for i := range ourPath {
445 if ourPath[i] != givenPath[i] {
446 return false
447 }
448 }
449
450 // If the receiver is a whole-module address then the path prefix
451 // matching is all we need.
452 if !addr.HasResourceSpec() {
453 return true
454 }
455
456 if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode {
457 return false
458 }
459
460 if addr.Index != -1 && addr.Index != other.Index {
461 return false
462 }
463
464 if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) {
465 return false
466 }
467
468 return true
469}
470
471// Equals returns true if the receiver matches the given address.
472//
473// The name of this method is a misnomer, since it doesn't test for exact
474// equality. Instead, it tests that the _specified_ parts of each
475// address match, treating any unspecified parts as wildcards.
476//
477// See also Contains, which takes a more heirarchical approach to comparing
478// addresses.
bae9f6d2
JC
479func (addr *ResourceAddress) Equals(raw interface{}) bool {
480 other, ok := raw.(*ResourceAddress)
481 if !ok {
482 return false
483 }
484
485 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
486 reflect.DeepEqual(addr.Path, other.Path)
487
488 indexMatch := addr.Index == -1 ||
489 other.Index == -1 ||
490 addr.Index == other.Index
491
492 nameMatch := addr.Name == "" ||
493 other.Name == "" ||
494 addr.Name == other.Name
495
496 typeMatch := addr.Type == "" ||
497 other.Type == "" ||
498 addr.Type == other.Type
499
500 // mode is significant only when type is set
501 modeMatch := addr.Type == "" ||
502 other.Type == "" ||
503 addr.Mode == other.Mode
504
505 return pathMatch &&
506 indexMatch &&
507 addr.InstanceType == other.InstanceType &&
508 nameMatch &&
509 typeMatch &&
510 modeMatch
511}
512
c680a8e1
RS
513// Less returns true if and only if the receiver should be sorted before
514// the given address when presenting a list of resource addresses to
515// an end-user.
516//
517// This sort uses lexicographic sorting for most components, but uses
518// numeric sort for indices, thus causing index 10 to sort after
519// index 9, rather than after index 1.
520func (addr *ResourceAddress) Less(other *ResourceAddress) bool {
521
522 switch {
523
15c0b25d
AP
524 case len(addr.Path) != len(other.Path):
525 return len(addr.Path) < len(other.Path)
c680a8e1
RS
526
527 case !reflect.DeepEqual(addr.Path, other.Path):
528 // If the two paths are the same length but don't match, we'll just
529 // cheat and compare the string forms since it's easier than
15c0b25d
AP
530 // comparing all of the path segments in turn, and lexicographic
531 // comparison is correct for the module path portion.
c680a8e1
RS
532 addrStr := addr.String()
533 otherStr := other.String()
534 return addrStr < otherStr
535
15c0b25d
AP
536 case addr.Mode != other.Mode:
537 return addr.Mode == config.DataResourceMode
c680a8e1 538
15c0b25d
AP
539 case addr.Type != other.Type:
540 return addr.Type < other.Type
c680a8e1 541
15c0b25d
AP
542 case addr.Name != other.Name:
543 return addr.Name < other.Name
c680a8e1 544
15c0b25d 545 case addr.Index != other.Index:
c680a8e1
RS
546 // Since "Index" is -1 for an un-indexed address, this also conveniently
547 // sorts unindexed addresses before indexed ones, should they both
548 // appear for some reason.
15c0b25d 549 return addr.Index < other.Index
c680a8e1 550
15c0b25d
AP
551 case addr.InstanceTypeSet != other.InstanceTypeSet:
552 return !addr.InstanceTypeSet
c680a8e1 553
15c0b25d 554 case addr.InstanceType != other.InstanceType:
c680a8e1
RS
555 // InstanceType is actually an enum, so this is just an arbitrary
556 // sort based on the enum numeric values, and thus not particularly
557 // meaningful.
15c0b25d 558 return addr.InstanceType < other.InstanceType
c680a8e1
RS
559
560 default:
561 return false
562
563 }
564}
565
bae9f6d2
JC
566func ParseResourceIndex(s string) (int, error) {
567 if s == "" {
568 return -1, nil
569 }
570 return strconv.Atoi(s)
571}
572
573func ParseResourcePath(s string) []string {
574 if s == "" {
575 return nil
576 }
577 parts := strings.Split(s, ".")
578 path := make([]string, 0, len(parts))
579 for _, s := range parts {
580 // Due to the limitations of the regexp match below, the path match has
581 // some noise in it we have to filter out :|
582 if s == "" || s == "module" {
583 continue
584 }
585 path = append(path, s)
586 }
587 return path
588}
589
590func ParseInstanceType(s string) (InstanceType, error) {
591 switch s {
592 case "", "primary":
593 return TypePrimary, nil
594 case "deposed":
595 return TypeDeposed, nil
596 case "tainted":
597 return TypeTainted, nil
598 default:
599 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
600 }
601}
602
603func tokenizeResourceAddress(s string) (map[string]string, error) {
604 // Example of portions of the regexp below using the
605 // string "aws_instance.web.tainted[1]"
606 re := regexp.MustCompile(`\A` +
607 // "module.foo.module.bar" (optional)
c680a8e1 608 `(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
bae9f6d2
JC
609 // possibly "data.", if targeting is a data resource
610 `(?P<data_prefix>(?:data\.)?)` +
611 // "aws_instance.web" (optional when module path specified)
612 `(?:(?P<type>[^.]+)\.(?P<name>[^.[]+))?` +
613 // "tainted" (optional, omission implies: "primary")
614 `(?:\.(?P<instance_type>\w+))?` +
615 // "1" (optional, omission implies: "0")
616 `(?:\[(?P<index>\d+)\])?` +
617 `\z`)
618
619 groupNames := re.SubexpNames()
620 rawMatches := re.FindAllStringSubmatch(s, -1)
621 if len(rawMatches) != 1 {
c680a8e1 622 return nil, fmt.Errorf("invalid resource address %q", s)
bae9f6d2
JC
623 }
624
625 matches := make(map[string]string)
626 for i, m := range rawMatches[0] {
627 matches[groupNames[i]] = m
628 }
629
630 return matches, nil
631}