From 74818ba8652985ce14aa59e5acf243bcc71fb91b Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 18 Nov 2020 14:44:06 -0500 Subject: [PATCH] remove legacy resource address types --- terraform/resource_address.go | 618 ------------- terraform/resource_address_test.go | 1329 ---------------------------- 2 files changed, 1947 deletions(-) delete mode 100644 terraform/resource_address.go delete mode 100644 terraform/resource_address_test.go diff --git a/terraform/resource_address.go b/terraform/resource_address.go deleted file mode 100644 index 4acf122b3..000000000 --- a/terraform/resource_address.go +++ /dev/null @@ -1,618 +0,0 @@ -package terraform - -import ( - "fmt" - "reflect" - "regexp" - "strconv" - "strings" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/configs" -) - -// ResourceAddress is a way of identifying an individual resource (or, -// eventually, a subset of resources) within the state. It is used for Targets. -type ResourceAddress struct { - // Addresses a resource falling somewhere in the module path - // When specified alone, addresses all resources within a module path - Path []string - - // Addresses a specific resource that occurs in a list - Index int - - InstanceType InstanceType - InstanceTypeSet bool - Name string - Type string - Mode ResourceMode // significant only if InstanceTypeSet -} - -// Copy returns a copy of this ResourceAddress -func (r *ResourceAddress) Copy() *ResourceAddress { - if r == nil { - return nil - } - - n := &ResourceAddress{ - Path: make([]string, 0, len(r.Path)), - Index: r.Index, - InstanceType: r.InstanceType, - Name: r.Name, - Type: r.Type, - Mode: r.Mode, - } - - n.Path = append(n.Path, r.Path...) - - return n -} - -// String outputs the address that parses into this address. -func (r *ResourceAddress) String() string { - var result []string - for _, p := range r.Path { - result = append(result, "module", p) - } - - switch r.Mode { - case ManagedResourceMode: - // nothing to do - case DataResourceMode: - result = append(result, "data") - default: - panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) - } - - if r.Type != "" { - result = append(result, r.Type) - } - - if r.Name != "" { - name := r.Name - if r.InstanceTypeSet { - switch r.InstanceType { - case TypePrimary: - name += ".primary" - case TypeDeposed: - name += ".deposed" - case TypeTainted: - name += ".tainted" - } - } - - if r.Index >= 0 { - name += fmt.Sprintf("[%d]", r.Index) - } - result = append(result, name) - } - - return strings.Join(result, ".") -} - -// HasResourceSpec returns true if the address has a resource spec, as -// defined in the documentation: -// https://www.terraform.io/docs/internals/resource-addressing.html -// In particular, this returns false if the address contains only -// a module path, thus addressing the entire module. -func (r *ResourceAddress) HasResourceSpec() bool { - return r.Type != "" && r.Name != "" -} - -// WholeModuleAddress returns the resource address that refers to all -// resources in the same module as the receiver address. -func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress { - return &ResourceAddress{ - Path: r.Path, - Index: -1, - InstanceTypeSet: false, - } -} - -// MatchesResourceConfig returns true if the receiver matches the given -// configuration resource within the given _static_ module path. Note that -// the module path in a resource address is a _dynamic_ module path, and -// multiple dynamic resource paths may map to a single static path if -// count and for_each are in use on module calls. -// -// Since resource configuration blocks represent all of the instances of -// a multi-instance resource, the index of the address (if any) is not -// considered. -func (r *ResourceAddress) MatchesResourceConfig(path addrs.Module, rc *configs.Resource) bool { - if r.HasResourceSpec() { - // FIXME: Some ugliness while we are between worlds. Functionality - // in "addrs" should eventually replace this ResourceAddress idea - // completely, but for now we'll need to translate to the old - // way of representing resource modes. - switch r.Mode { - case ManagedResourceMode: - if rc.Mode != addrs.ManagedResourceMode { - return false - } - case DataResourceMode: - if rc.Mode != addrs.DataResourceMode { - return false - } - } - if r.Type != rc.Type || r.Name != rc.Name { - return false - } - } - - addrPath := r.Path - - // normalize - if len(addrPath) == 0 { - addrPath = nil - } - if len(path) == 0 { - path = nil - } - rawPath := []string(path) - return reflect.DeepEqual(addrPath, rawPath) -} - -// stateId returns the ID that this resource should be entered with -// in the state. This is also used for diffs. In the future, we'd like to -// move away from this string field so I don't export this. -func (r *ResourceAddress) stateId() string { - result := fmt.Sprintf("%s.%s", r.Type, r.Name) - switch r.Mode { - case ManagedResourceMode: - // Done - case DataResourceMode: - result = fmt.Sprintf("data.%s", result) - default: - panic(fmt.Errorf("unknown resource mode: %s", r.Mode)) - } - if r.Index >= 0 { - result += fmt.Sprintf(".%d", r.Index) - } - - return result -} - -// parseResourceAddressInternal parses the somewhat bespoke resource -// identifier used in states and diffs, such as "instance.name.0". -func parseResourceAddressInternal(s string) (*ResourceAddress, error) { - // Split based on ".". Every resource address should have at least two - // elements (type and name). - parts := strings.Split(s, ".") - if len(parts) < 2 || len(parts) > 4 { - return nil, fmt.Errorf("Invalid internal resource address format: %s", s) - } - - // Data resource if we have at least 3 parts and the first one is data - mode := ManagedResourceMode - if len(parts) > 2 && parts[0] == "data" { - mode = DataResourceMode - parts = parts[1:] - } - - // If we're not a data resource and we have more than 3, then it is an error - if len(parts) > 3 && mode != DataResourceMode { - return nil, fmt.Errorf("Invalid internal resource address format: %s", s) - } - - // Build the parts of the resource address that are guaranteed to exist - addr := &ResourceAddress{ - Type: parts[0], - Name: parts[1], - Index: -1, - InstanceType: TypePrimary, - Mode: mode, - } - - // If we have more parts, then we have an index. Parse that. - if len(parts) > 2 { - idx, err := strconv.ParseInt(parts[2], 0, 0) - if err != nil { - return nil, fmt.Errorf("Error parsing resource address %q: %s", s, err) - } - - addr.Index = int(idx) - } - - return addr, nil -} - -func ParseResourceAddress(s string) (*ResourceAddress, error) { - matches, err := tokenizeResourceAddress(s) - if err != nil { - return nil, err - } - mode := ManagedResourceMode - if matches["data_prefix"] != "" { - mode = DataResourceMode - } - resourceIndex, err := ParseResourceIndex(matches["index"]) - if err != nil { - return nil, err - } - instanceType, err := ParseInstanceType(matches["instance_type"]) - if err != nil { - return nil, err - } - path := ParseResourcePath(matches["path"]) - - // not allowed to say "data." without a type following - if mode == DataResourceMode && matches["type"] == "" { - return nil, fmt.Errorf( - "invalid resource address %q: must target specific data instance", - s, - ) - } - - return &ResourceAddress{ - Path: path, - Index: resourceIndex, - InstanceType: instanceType, - InstanceTypeSet: matches["instance_type"] != "", - Name: matches["name"], - Type: matches["type"], - Mode: mode, - }, nil -} - -// ParseResourceAddressForInstanceDiff creates a ResourceAddress for a -// resource name as described in a module diff. -// -// For historical reasons a different addressing format is used in this -// context. The internal format should not be shown in the UI and instead -// this function should be used to translate to a ResourceAddress and -// then, where appropriate, use the String method to produce a canonical -// resource address string for display in the UI. -// -// The given path slice must be empty (or nil) for the root module, and -// otherwise consist of a sequence of module names traversing down into -// the module tree. If a non-nil path is provided, the caller must not -// modify its underlying array after passing it to this function. -func ParseResourceAddressForInstanceDiff(path []string, key string) (*ResourceAddress, error) { - addr, err := parseResourceAddressInternal(key) - if err != nil { - return nil, err - } - addr.Path = path - return addr, nil -} - -// NewLegacyResourceAddress creates a ResourceAddress from a new-style -// addrs.AbsResource value. -// -// This is provided for shimming purposes so that we can still easily call into -// older functions that expect the ResourceAddress type. -func NewLegacyResourceAddress(addr addrs.AbsResource) *ResourceAddress { - ret := &ResourceAddress{ - Type: addr.Resource.Type, - Name: addr.Resource.Name, - } - - switch addr.Resource.Mode { - case addrs.ManagedResourceMode: - ret.Mode = ManagedResourceMode - case addrs.DataResourceMode: - ret.Mode = DataResourceMode - default: - panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Mode)) - } - - path := make([]string, len(addr.Module)) - for i, step := range addr.Module { - if step.InstanceKey != addrs.NoKey { - // At the time of writing this can't happen because we don't - // ket generate keyed module instances. This legacy codepath must - // be removed before we can support "count" and "for_each" for - // modules. - panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) - } - - path[i] = step.Name - } - ret.Path = path - ret.Index = -1 - - return ret -} - -// NewLegacyResourceInstanceAddress creates a ResourceAddress from a new-style -// addrs.AbsResource value. -// -// This is provided for shimming purposes so that we can still easily call into -// older functions that expect the ResourceAddress type. -func NewLegacyResourceInstanceAddress(addr addrs.AbsResourceInstance) *ResourceAddress { - ret := &ResourceAddress{ - Type: addr.Resource.Resource.Type, - Name: addr.Resource.Resource.Name, - } - - switch addr.Resource.Resource.Mode { - case addrs.ManagedResourceMode: - ret.Mode = ManagedResourceMode - case addrs.DataResourceMode: - ret.Mode = DataResourceMode - default: - panic(fmt.Errorf("cannot shim %s to legacy ResourceMode value", addr.Resource.Resource.Mode)) - } - - path := make([]string, len(addr.Module)) - for i, step := range addr.Module { - if step.InstanceKey != addrs.NoKey { - // At the time of writing this can't happen because we don't - // ket generate keyed module instances. This legacy codepath must - // be removed before we can support "count" and "for_each" for - // modules. - panic(fmt.Errorf("cannot shim module instance step with key %#v to legacy ResourceAddress.Path", step.InstanceKey)) - } - - path[i] = step.Name - } - ret.Path = path - - if addr.Resource.Key == addrs.NoKey { - ret.Index = -1 - } else if ik, ok := addr.Resource.Key.(addrs.IntKey); ok { - ret.Index = int(ik) - } else if _, ok := addr.Resource.Key.(addrs.StringKey); ok { - ret.Index = -1 - } else { - panic(fmt.Errorf("cannot shim resource instance with key %#v to legacy ResourceAddress.Index", addr.Resource.Key)) - } - - return ret -} - -// AbsResourceInstanceAddr converts the receiver, a legacy resource address, to -// the new resource address type addrs.AbsResourceInstance. -// -// This method can be used only on an address that has a resource specification. -// It will panic if called on a module-path-only ResourceAddress. Use -// method HasResourceSpec to check before calling, in contexts where it is -// unclear. -// -// addrs.AbsResourceInstance does not represent the "tainted" and "deposed" -// states, and so if these are present on the receiver then they are discarded. -// -// This is provided for shimming purposes so that we can easily adapt functions -// that are returning the legacy ResourceAddress type, for situations where -// the new type is required. -func (addr *ResourceAddress) AbsResourceInstanceAddr() addrs.AbsResourceInstance { - if !addr.HasResourceSpec() { - panic("AbsResourceInstanceAddr called on ResourceAddress with no resource spec") - } - - ret := addrs.AbsResourceInstance{ - Module: addr.ModuleInstanceAddr(), - Resource: addrs.ResourceInstance{ - Resource: addrs.Resource{ - Type: addr.Type, - Name: addr.Name, - }, - }, - } - - switch addr.Mode { - case ManagedResourceMode: - ret.Resource.Resource.Mode = addrs.ManagedResourceMode - case DataResourceMode: - ret.Resource.Resource.Mode = addrs.DataResourceMode - default: - panic(fmt.Errorf("cannot shim %s to addrs.ResourceMode value", addr.Mode)) - } - - if addr.Index != -1 { - ret.Resource.Key = addrs.IntKey(addr.Index) - } - - return ret -} - -// ModuleInstanceAddr returns the module path portion of the receiver as a -// addrs.ModuleInstance value. -func (addr *ResourceAddress) ModuleInstanceAddr() addrs.ModuleInstance { - path := make(addrs.ModuleInstance, len(addr.Path)) - for i, name := range addr.Path { - path[i] = addrs.ModuleInstanceStep{Name: name} - } - return path -} - -// Contains returns true if and only if the given node is contained within -// the receiver. -// -// Containment is defined in terms of the module and resource heirarchy: -// a resource is contained within its module and any ancestor modules, -// an indexed resource instance is contained with the unindexed resource, etc. -func (addr *ResourceAddress) Contains(other *ResourceAddress) bool { - ourPath := addr.Path - givenPath := other.Path - if len(givenPath) < len(ourPath) { - return false - } - for i := range ourPath { - if ourPath[i] != givenPath[i] { - return false - } - } - - // If the receiver is a whole-module address then the path prefix - // matching is all we need. - if !addr.HasResourceSpec() { - return true - } - - if addr.Type != other.Type || addr.Name != other.Name || addr.Mode != other.Mode { - return false - } - - if addr.Index != -1 && addr.Index != other.Index { - return false - } - - if addr.InstanceTypeSet && (addr.InstanceTypeSet != other.InstanceTypeSet || addr.InstanceType != other.InstanceType) { - return false - } - - return true -} - -// Equals returns true if the receiver matches the given address. -// -// The name of this method is a misnomer, since it doesn't test for exact -// equality. Instead, it tests that the _specified_ parts of each -// address match, treating any unspecified parts as wildcards. -// -// See also Contains, which takes a more hierarchical approach to comparing -// addresses. -func (addr *ResourceAddress) Equals(raw interface{}) bool { - other, ok := raw.(*ResourceAddress) - if !ok { - return false - } - - pathMatch := len(addr.Path) == 0 && len(other.Path) == 0 || - reflect.DeepEqual(addr.Path, other.Path) - - indexMatch := addr.Index == -1 || - other.Index == -1 || - addr.Index == other.Index - - nameMatch := addr.Name == "" || - other.Name == "" || - addr.Name == other.Name - - typeMatch := addr.Type == "" || - other.Type == "" || - addr.Type == other.Type - - // mode is significant only when type is set - modeMatch := addr.Type == "" || - other.Type == "" || - addr.Mode == other.Mode - - return pathMatch && - indexMatch && - addr.InstanceType == other.InstanceType && - nameMatch && - typeMatch && - modeMatch -} - -// Less returns true if and only if the receiver should be sorted before -// the given address when presenting a list of resource addresses to -// an end-user. -// -// This sort uses lexicographic sorting for most components, but uses -// numeric sort for indices, thus causing index 10 to sort after -// index 9, rather than after index 1. -func (addr *ResourceAddress) Less(other *ResourceAddress) bool { - - switch { - - case len(addr.Path) != len(other.Path): - return len(addr.Path) < len(other.Path) - - case !reflect.DeepEqual(addr.Path, other.Path): - // If the two paths are the same length but don't match, we'll just - // cheat and compare the string forms since it's easier than - // comparing all of the path segments in turn, and lexicographic - // comparison is correct for the module path portion. - addrStr := addr.String() - otherStr := other.String() - return addrStr < otherStr - - case addr.Mode != other.Mode: - return addr.Mode == DataResourceMode - - case addr.Type != other.Type: - return addr.Type < other.Type - - case addr.Name != other.Name: - return addr.Name < other.Name - - case addr.Index != other.Index: - // Since "Index" is -1 for an un-indexed address, this also conveniently - // sorts unindexed addresses before indexed ones, should they both - // appear for some reason. - return addr.Index < other.Index - - case addr.InstanceTypeSet != other.InstanceTypeSet: - return !addr.InstanceTypeSet - - case addr.InstanceType != other.InstanceType: - // InstanceType is actually an enum, so this is just an arbitrary - // sort based on the enum numeric values, and thus not particularly - // meaningful. - return addr.InstanceType < other.InstanceType - - default: - return false - - } -} - -func ParseResourceIndex(s string) (int, error) { - if s == "" { - return -1, nil - } - return strconv.Atoi(s) -} - -func ParseResourcePath(s string) []string { - if s == "" { - return nil - } - parts := strings.Split(s, ".") - path := make([]string, 0, len(parts)) - for _, s := range parts { - // Due to the limitations of the regexp match below, the path match has - // some noise in it we have to filter out :| - if s == "" || s == "module" { - continue - } - path = append(path, s) - } - return path -} - -func ParseInstanceType(s string) (InstanceType, error) { - switch s { - case "", "primary": - return TypePrimary, nil - case "deposed": - return TypeDeposed, nil - case "tainted": - return TypeTainted, nil - default: - return TypeInvalid, fmt.Errorf("Unexpected value for InstanceType field: %q", s) - } -} - -func tokenizeResourceAddress(s string) (map[string]string, error) { - // Example of portions of the regexp below using the - // string "aws_instance.web.tainted[1]" - re := regexp.MustCompile(`\A` + - // "module.foo.module.bar" (optional) - `(?P(?:module\.(?P[^.]+)\.?)*)` + - // possibly "data.", if targeting is a data resource - `(?P(?:data\.)?)` + - // "aws_instance.web" (optional when module path specified) - `(?:(?P[^.]+)\.(?P[^.[]+))?` + - // "tainted" (optional, omission implies: "primary") - `(?:\.(?P\w+))?` + - // "1" (optional, omission implies: "0") - `(?:\[(?P\d+)\])?` + - `\z`) - - groupNames := re.SubexpNames() - rawMatches := re.FindAllStringSubmatch(s, -1) - if len(rawMatches) != 1 { - return nil, fmt.Errorf("invalid resource address %q", s) - } - - matches := make(map[string]string) - for i, m := range rawMatches[0] { - matches[groupNames[i]] = m - } - - return matches, nil -} diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go deleted file mode 100644 index 3bb5f2082..000000000 --- a/terraform/resource_address_test.go +++ /dev/null @@ -1,1329 +0,0 @@ -package terraform - -import ( - "fmt" - "reflect" - "testing" - - "github.com/hashicorp/terraform/addrs" - "github.com/hashicorp/terraform/configs" -) - -func TestParseResourceAddressInternal(t *testing.T) { - cases := map[string]struct { - Input string - Expected *ResourceAddress - Output string - }{ - "basic resource": { - "aws_instance.foo", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "aws_instance.foo", - }, - - "basic resource with count": { - "aws_instance.foo.1", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 1, - }, - "aws_instance.foo[1]", - }, - - "data resource": { - "data.aws_ami.foo", - &ResourceAddress{ - Mode: DataResourceMode, - Type: "aws_ami", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "data.aws_ami.foo", - }, - - "data resource with count": { - "data.aws_ami.foo.1", - &ResourceAddress{ - Mode: DataResourceMode, - Type: "aws_ami", - Name: "foo", - InstanceType: TypePrimary, - Index: 1, - }, - "data.aws_ami.foo[1]", - }, - - "non-data resource with 4 elements": { - "aws_instance.foo.bar.1", - nil, - "", - }, - } - - for tn, tc := range cases { - t.Run(tc.Input, func(t *testing.T) { - out, err := parseResourceAddressInternal(tc.Input) - if (err != nil) != (tc.Expected == nil) { - t.Fatalf("%s: unexpected err: %#v", tn, err) - } - if err != nil { - return - } - - if !reflect.DeepEqual(out, tc.Expected) { - t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) - } - - // Compare outputs if those exist - expected := tc.Input - if tc.Output != "" { - expected = tc.Output - } - if out.String() != expected { - t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out) - } - - // Compare equality because the internal parse is used - // to compare equality to equal inputs. - if !out.Equals(tc.Expected) { - t.Fatalf("expected equality:\n\n%#v\n\n%#v", out, tc.Expected) - } - }) - } -} - -func TestParseResourceAddress(t *testing.T) { - cases := map[string]struct { - Input string - Expected *ResourceAddress - Output string - Err bool - }{ - "implicit primary managed instance, no specific index": { - "aws_instance.foo", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "implicit primary data instance, no specific index": { - "data.aws_instance.foo", - &ResourceAddress{ - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "implicit primary, explicit index": { - "aws_instance.foo[2]", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 2, - }, - "", - false, - }, - "implicit primary, explicit index over ten": { - "aws_instance.foo[12]", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 12, - }, - "", - false, - }, - "explicit primary, explicit index": { - "aws_instance.foo.primary[2]", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - InstanceTypeSet: true, - Index: 2, - }, - "", - false, - }, - "tainted": { - "aws_instance.foo.tainted", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypeTainted, - InstanceTypeSet: true, - Index: -1, - }, - "", - false, - }, - "deposed": { - "aws_instance.foo.deposed", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypeDeposed, - InstanceTypeSet: true, - Index: -1, - }, - "", - false, - }, - "with a hyphen": { - "aws_instance.foo-bar", - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo-bar", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "managed in a module": { - "module.child.aws_instance.foo", - &ResourceAddress{ - Path: []string{"child"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "data in a module": { - "module.child.data.aws_instance.foo", - &ResourceAddress{ - Path: []string{"child"}, - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "nested modules": { - "module.a.module.b.module.forever.aws_instance.foo", - &ResourceAddress{ - Path: []string{"a", "b", "forever"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "just a module": { - "module.a", - &ResourceAddress{ - Path: []string{"a"}, - Type: "", - Name: "", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "just a nested module": { - "module.a.module.b", - &ResourceAddress{ - Path: []string{"a", "b"}, - Type: "", - Name: "", - InstanceType: TypePrimary, - Index: -1, - }, - "", - false, - }, - "module missing resource type": { - "module.name.foo", - nil, - "", - true, - }, - } - - for tn, tc := range cases { - t.Run(tn, func(t *testing.T) { - out, err := ParseResourceAddress(tc.Input) - if (err != nil) != tc.Err { - t.Fatalf("%s: unexpected err: %#v", tn, err) - } - if tc.Err { - return - } - - if !reflect.DeepEqual(out, tc.Expected) { - t.Fatalf("bad: %q\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Expected, out) - } - - expected := tc.Input - if tc.Output != "" { - expected = tc.Output - } - if out.String() != expected { - t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, expected, out) - } - }) - } -} - -func TestResourceAddressContains(t *testing.T) { - tests := []struct { - Address *ResourceAddress - Other *ResourceAddress - Want bool - }{ - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - true, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: 0, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - true, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - true, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - { - &ResourceAddress{ - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - { - &ResourceAddress{ - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"bar"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"bar"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"bar", "baz"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"bar", "baz"}, - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"bar", "baz", "foo", "pizza"}, - InstanceTypeSet: false, - Index: -1, - }, - true, - }, - - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "bar", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - false, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - &ResourceAddress{ - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - false, - }, - { - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"baz"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - false, - }, - { - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"baz", "bar"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: -1, - }, - false, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: true, - InstanceType: TypePrimary, - Index: 0, - }, - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceTypeSet: false, - Index: 0, - }, - false, - }, - { - &ResourceAddress{ - Path: []string{"bar", "baz"}, - InstanceTypeSet: false, - Index: -1, - }, - &ResourceAddress{ - Path: []string{"bar"}, - InstanceTypeSet: false, - Index: -1, - }, - false, - }, - { - &ResourceAddress{ - Type: "aws_instance", - Name: "foo", - Index: 1, - InstanceType: TypePrimary, - Mode: ManagedResourceMode, - }, - &ResourceAddress{ - Type: "aws_instance", - Name: "foo", - Index: -1, - InstanceType: TypePrimary, - Mode: ManagedResourceMode, - }, - false, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("%s contains %s", test.Address, test.Other), func(t *testing.T) { - got := test.Address.Contains(test.Other) - if got != test.Want { - t.Errorf( - "wrong result\nrecv: %s\ngiven: %s\ngot: %#v\nwant: %#v", - test.Address, test.Other, - got, test.Want, - ) - } - }) - } -} - -func TestResourceAddressEquals(t *testing.T) { - cases := map[string]struct { - Address *ResourceAddress - Other interface{} - Expect bool - }{ - "basic match": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: true, - }, - "address does not set index": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 3, - }, - Expect: true, - }, - "other does not set index": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 3, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - Expect: true, - }, - "neither sets index": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - Expect: true, - }, - "index over ten": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 1, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 13, - }, - Expect: false, - }, - "different type": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_vpc", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: false, - }, - "different mode": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Other: &ResourceAddress{ - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: false, - }, - "different name": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "bar", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: false, - }, - "different instance type": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypeTainted, - Index: 0, - }, - Expect: false, - }, - "different index": { - Address: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Other: &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 1, - }, - Expect: false, - }, - "module address matches address of managed resource inside module": { - Address: &ResourceAddress{ - Path: []string{"a", "b"}, - Type: "", - Name: "", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Path: []string{"a", "b"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: true, - }, - "module address matches address of data resource inside module": { - Address: &ResourceAddress{ - Path: []string{"a", "b"}, - Type: "", - Name: "", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Path: []string{"a", "b"}, - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: true, - }, - "module address doesn't match managed resource outside module": { - Address: &ResourceAddress{ - Path: []string{"a", "b"}, - Type: "", - Name: "", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Path: []string{"a"}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: false, - }, - "module address doesn't match data resource outside module": { - Address: &ResourceAddress{ - Path: []string{"a", "b"}, - Type: "", - Name: "", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Path: []string{"a"}, - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: false, - }, - "nil path vs empty path should match": { - Address: &ResourceAddress{ - Path: []string{}, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - Other: &ResourceAddress{ - Path: nil, - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 0, - }, - Expect: true, - }, - } - - for tn, tc := range cases { - actual := tc.Address.Equals(tc.Other) - if actual != tc.Expect { - t.Fatalf("%q: expected equals: %t, got %t for:\n%#v\n%#v", - tn, tc.Expect, actual, tc.Address, tc.Other) - } - } -} - -func TestResourceAddressStateId(t *testing.T) { - cases := map[string]struct { - Input *ResourceAddress - Expected string - }{ - "basic resource": { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "aws_instance.foo", - }, - - "basic resource with index": { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: 2, - }, - "aws_instance.foo.2", - }, - - "data resource": { - &ResourceAddress{ - Mode: DataResourceMode, - Type: "aws_instance", - Name: "foo", - InstanceType: TypePrimary, - Index: -1, - }, - "data.aws_instance.foo", - }, - } - - for tn, tc := range cases { - t.Run(tn, func(t *testing.T) { - actual := tc.Input.stateId() - if actual != tc.Expected { - t.Fatalf("bad: %q\n\nexpected: %s\n\ngot: %s", tn, tc.Expected, actual) - } - }) - } -} - -func TestResourceAddressHasResourceSpec(t *testing.T) { - cases := []struct { - Input string - Want bool - }{ - { - "module.foo", - false, - }, - { - "module.foo.module.bar", - false, - }, - { - "null_resource.baz", - true, - }, - { - "null_resource.baz[0]", - true, - }, - { - "data.null_data_source.baz", - true, - }, - { - "data.null_data_source.baz[0]", - true, - }, - { - "module.foo.null_resource.baz", - true, - }, - { - "module.foo.data.null_data_source.baz", - true, - }, - { - "module.foo.module.bar.null_resource.baz", - true, - }, - } - - for _, test := range cases { - t.Run(test.Input, func(t *testing.T) { - addr, err := ParseResourceAddress(test.Input) - if err != nil { - t.Fatalf("error parsing address: %s", err) - } - got := addr.HasResourceSpec() - if got != test.Want { - t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) - } - }) - } -} - -func TestResourceAddressWholeModuleAddress(t *testing.T) { - cases := []struct { - Input string - Want string - }{ - { - "module.foo", - "module.foo", - }, - { - "module.foo.module.bar", - "module.foo.module.bar", - }, - { - "null_resource.baz", - "", - }, - { - "null_resource.baz[0]", - "", - }, - { - "data.null_data_source.baz", - "", - }, - { - "data.null_data_source.baz[0]", - "", - }, - { - "module.foo.null_resource.baz", - "module.foo", - }, - { - "module.foo.data.null_data_source.baz", - "module.foo", - }, - { - "module.foo.module.bar.null_resource.baz", - "module.foo.module.bar", - }, - } - - for _, test := range cases { - t.Run(test.Input, func(t *testing.T) { - addr, err := ParseResourceAddress(test.Input) - if err != nil { - t.Fatalf("error parsing address: %s", err) - } - gotAddr := addr.WholeModuleAddress() - got := gotAddr.String() - if got != test.Want { - t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) - } - }) - } -} - -func TestResourceAddressMatchesResourceConfig(t *testing.T) { - root := []string(nil) - child := []string{"child"} - grandchild := []string{"child", "grandchild"} - irrelevant := []string{"irrelevant"} - - tests := []struct { - Addr *ResourceAddress - ModulePath []string - Resource *configs.Resource - Want bool - }{ - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - root, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"child"}, - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - child, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"child", "grandchild"}, - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - grandchild, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"child"}, - Index: -1, - }, - child, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - true, - }, - { - &ResourceAddress{ - Path: []string{"child", "grandchild"}, - Index: -1, - }, - grandchild, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - true, - }, - { - &ResourceAddress{ - Mode: DataResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - irrelevant, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - false, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - irrelevant, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "pizza", - }, - false, - }, - { - &ResourceAddress{ - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - irrelevant, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "aws_instance", - Name: "baz", - }, - false, - }, - { - &ResourceAddress{ - Path: []string{"child", "grandchild"}, - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - child, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - false, - }, - { - &ResourceAddress{ - Path: []string{"child"}, - Mode: ManagedResourceMode, - Type: "null_resource", - Name: "baz", - Index: -1, - }, - grandchild, - &configs.Resource{ - Mode: addrs.ManagedResourceMode, - Type: "null_resource", - Name: "baz", - }, - false, - }, - } - - for i, test := range tests { - t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) { - got := test.Addr.MatchesResourceConfig(test.ModulePath, test.Resource) - if got != test.Want { - t.Errorf( - "wrong result\naddr: %s\nmod: %#v\nrsrc: %#v\ngot: %#v\nwant: %#v", - test.Addr, test.ModulePath, test.Resource, got, test.Want, - ) - } - }) - } -} - -func TestResourceAddressLess(t *testing.T) { - tests := []struct { - A string - B string - Want bool - }{ - { - "foo.bar", - "module.baz.foo.bar", - true, - }, - { - "module.baz.foo.bar", - "zzz.bar", // would sort after "module" in lexicographical sort - false, - }, - { - "module.baz.foo.bar", - "module.baz.foo.bar", - false, - }, - { - "module.baz.foo.bar", - "module.boz.foo.bar", - true, - }, - { - "module.boz.foo.bar", - "module.baz.foo.bar", - false, - }, - { - "a.b", - "b.c", - true, - }, - { - "a.b", - "a.c", - true, - }, - { - "c.b", - "b.c", - false, - }, - { - "a.b[9]", - "a.b[10]", - true, - }, - { - "b.b[9]", - "a.b[10]", - false, - }, - { - "a.b", - "a.b.deposed", - true, - }, - { - "a.b.tainted", - "a.b.deposed", - true, - }, - } - - for _, test := range tests { - t.Run(fmt.Sprintf("%s < %s", test.A, test.B), func(t *testing.T) { - addrA, err := ParseResourceAddress(test.A) - if err != nil { - t.Fatal(err) - } - addrB, err := ParseResourceAddress(test.B) - if err != nil { - t.Fatal(err) - } - got := addrA.Less(addrB) - invGot := addrB.Less(addrA) - if got != test.Want { - t.Errorf( - "wrong result\ntest: %s < %s\ngot: %#v\nwant: %#v", - test.A, test.B, got, test.Want, - ) - } - if test.A != test.B { // inverse test doesn't apply when equal - if invGot != !test.Want { - t.Errorf( - "wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v", - test.B, test.A, invGot, !test.Want, - ) - } - } else { - if invGot != test.Want { - t.Errorf( - "wrong inverse result\ntest: %s < %s\ngot: %#v\nwant: %#v", - test.B, test.A, invGot, test.Want, - ) - } - } - }) - } -}