]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/terraform/terraform/resource_address.go
vendor: github.com/hashicorp/terraform/...@v0.10.0
[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
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.
16type 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
32func (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.
52func (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.
98func (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.
104func (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.
118func (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.
141func (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
159func 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".
171func 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
213func 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.
264func 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.
279func (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
320func (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.
361func (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
406func ParseResourceIndex(s string) (int, error) {
407 if s == "" {
408 return -1, nil
409 }
410 return strconv.Atoi(s)
411}
412
413func 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
430func 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
443func 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}