10 "github.com/hashicorp/terraform/addrs"
12 "github.com/hashicorp/terraform/config"
13 "github.com/hashicorp/terraform/configs"
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
23 // Addresses a specific resource that occurs in a list
26 InstanceType InstanceType
30 Mode config.ResourceMode // significant only if InstanceTypeSet
33 // Copy returns a copy of this ResourceAddress
34 func (r *ResourceAddress) Copy() *ResourceAddress {
39 n := &ResourceAddress{
40 Path: make([]string, 0, len(r.Path)),
42 InstanceType: r.InstanceType,
48 n.Path = append(n.Path, r.Path...)
53 // String outputs the address that parses into this address.
54 func (r *ResourceAddress) String() string {
56 for _, p := range r.Path {
57 result = append(result, "module", p)
61 case config.ManagedResourceMode:
63 case config.DataResourceMode:
64 result = append(result, "data")
66 panic(fmt.Errorf("unsupported resource mode %s", r.Mode))
70 result = append(result, r.Type)
75 if r.InstanceTypeSet {
76 switch r.InstanceType {
87 name += fmt.Sprintf("[%d]", r.Index)
89 result = append(result, name)
92 return strings.Join(result, ".")
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 != ""
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{
110 InstanceTypeSet: false,
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.
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
123 func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool {
124 if r.HasResourceSpec() {
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.
130 case config.ManagedResourceMode:
131 if rc.Mode != addrs.ManagedResourceMode {
134 case config.DataResourceMode:
135 if rc.Mode != addrs.DataResourceMode {
139 if r.Type != rc.Type || r.Name != rc.Name {
147 if len(addrPath) == 0 {
153 rawPath := []string(path)
154 return reflect.DeepEqual(addrPath, rawPath)
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)
163 case config.ManagedResourceMode:
165 case config.DataResourceMode:
166 result = fmt.Sprintf("data.%s", result)
168 panic(fmt.Errorf("unknown resource mode: %s", r.Mode))
171 result += fmt.Sprintf(".%d", r.Index)
177 // parseResourceAddressConfig creates a resource address from a config.Resource
178 func parseResourceAddressConfig(r *config.Resource) (*ResourceAddress, error) {
179 return &ResourceAddress{
183 InstanceType: TypePrimary,
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)
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
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)
210 // Build the parts of the resource address that are guaranteed to exist
211 addr := &ResourceAddress{
215 InstanceType: TypePrimary,
219 // If we have more parts, then we have an index. Parse that.
221 idx, err := strconv.ParseInt(parts[2], 0, 0)
223 return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err)
226 addr.Index = int(idx)
232 func ParseResourceAddress(s string) (*ResourceAddress, error) {
233 matches, err := tokenizeResourceAddress(s)
237 mode := config.ManagedResourceMode
238 if matches["data_prefix"] != "" {
239 mode = config.DataResourceMode
241 resourceIndex, err := ParseResourceIndex(matches["index"])
245 instanceType, err := ParseInstanceType(matches["instance_type"])
249 path := ParseResourcePath(matches["path"])
251 // not allowed to say "data." without a type following
252 if mode == config.DataResourceMode && matches["type"] == "" {
253 return nil, fmt.Errorf(
254 "invalid resource address %q: must target specific data instance",
259 return &ResourceAddress{
261 Index: resourceIndex,
262 InstanceType: instanceType,
263 InstanceTypeSet: matches["instance_type"] != "",
264 Name: matches["name"],
265 Type: matches["type"],
270 // ParseResourceAddressForInstanceDiff creates a ResourceAddress for a
271 // resource name as described in a module diff.
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.
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)
292 // NewLegacyResourceAddress creates a ResourceAddress from a new-style
293 // addrs.AbsResource value.
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,
303 switch addr.Resource.Mode {
304 case addrs.ManagedResourceMode:
305 ret.Mode = config.ManagedResourceMode
306 case addrs.DataResourceMode:
307 ret.Mode = config.DataResourceMode
309 panic(fmt.Errorf("cannot shim %s to legacy config.ResourceMode value", addr.Resource.Mode))
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
319 panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey))
330 // NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style
331 // addrs.AbsResource value.
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,
341 switch addr.Resource.Resource.Mode {
342 case addrs.ManagedResourceMode:
343 ret.Mode = config.ManagedResourceMode
344 case addrs.DataResourceMode:
345 ret.Mode = config.DataResourceMode
347 panic(fmt.Errorf("cannot shim %s to legacy config.ResourceMode value", addr.Resource.Resource.Mode))
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
357 panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey))
364 if addr.Resource.Key == addrs.NoKey {
366 } else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok {
369 panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key))
375 // AbsResourceInstanceAddr converts the receiver, a legacy resource address, to
376 // the new resource address type addrs.AbsResourceInstance.
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
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.
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")
394 ret := addrs.AbsResourceInstance{
395 Module: addr.ModuleInstanceAddr(),
396 Resource: addrs.ResourceInstance{
397 Resource: addrs.Resource{
405 case config.ManagedResourceMode:
406 ret.Resource.Resource.Mode = addrs.ManagedResourceMode
407 case config.DataResourceMode:
408 ret.Resource.Resource.Mode = addrs.DataResourceMode
410 panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode))
413 if addr.Index != -1 {
414 ret.Resource.Key = addrs.IntKey(addr.Index)
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}
430 // Contains returns true if and only if the given node is contained within
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 {
438 givenPath := other.Path
439 if len(givenPath) < len(ourPath) {
442 for i := range ourPath {
443 if ourPath[i] != givenPath[i] {
448 // If the receiver is a whole-module address then the path prefix
449 // matching is all we need.
450 if !addr.HasResourceSpec() {
454 if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode {
458 if addr.Index != -1 && addr.Index != other.Index {
462 if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) {
469 // Equals returns true if the receiver matches the given address.
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.
475 // See also Contains, which takes a more heirarchical approach to comparing
477 func (addr *ResourceAddress) Equals(raw interface{}) bool {
478 other, ok := raw.(*ResourceAddress)
483 pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 ||
484 reflect.DeepEqual(addr.Path, other.Path)
486 indexMatch := addr.Index == -1 ||
488 addr.Index == other.Index
490 nameMatch := addr.Name == "" ||
492 addr.Name == other.Name
494 typeMatch := addr.Type == "" ||
496 addr.Type == other.Type
498 // mode is significant only when type is set
499 modeMatch := addr.Type == "" ||
501 addr.Mode == other.Mode
505 addr.InstanceType == other.InstanceType &&
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
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 {
522 case len(addr.Path) != len(other.Path):
523 return len(addr.Path) < len(other.Path)
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
528 // comparing all of the path segments in turn, and lexicographic
529 // comparison is correct for the module path portion.
530 addrStr := addr.String()
531 otherStr := other.String()
532 return addrStr < otherStr
534 case addr.Mode != other.Mode:
535 return addr.Mode == config.DataResourceMode
537 case addr.Type != other.Type:
538 return addr.Type < other.Type
540 case addr.Name != other.Name:
541 return addr.Name < other.Name
543 case addr.Index != other.Index:
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.
547 return addr.Index < other.Index
549 case addr.InstanceTypeSet != other.InstanceTypeSet:
550 return !addr.InstanceTypeSet
552 case addr.InstanceType != other.InstanceType:
553 // InstanceType is actually an enum, so this is just an arbitrary
554 // sort based on the enum numeric values, and thus not particularly
556 return addr.InstanceType < other.InstanceType
564 func ParseResourceIndex(s string) (int, error) {
568 return strconv.Atoi(s)
571 func ParseResourcePath(s string) []string {
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" {
583 path = append(path, s)
588 func ParseInstanceType(s string) (InstanceType, error) {
591 return TypePrimary, nil
593 return TypeDeposed, nil
595 return TypeTainted, nil
597 return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s)
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)
606 `(?P<path>(?:module\.(?P<module_name>[^.]+)\.?)*)` +
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+)\])?` +
617 groupNames := re.SubexpNames()
618 rawMatches := re.FindAllStringSubmatch(s, -1)
619 if len(rawMatches) != 1 {
620 return nil, fmt.Errorf("invalid resource address %q", s)
623 matches := make(map[string]string)
624 for i, m := range rawMatches[0] {
625 matches[groupNames[i]] = m