package refactoring

import (
	"fmt"
	"sort"
	"strings"
	"testing"

	"github.com/davecgh/go-spew/spew"
	"github.com/google/go-cmp/cmp"
	"github.com/hashicorp/hcl/v2"
	"github.com/hashicorp/hcl/v2/hclsyntax"
	"github.com/hashicorp/terraform/internal/addrs"
	"github.com/hashicorp/terraform/internal/states"
)

func TestApplyMoves(t *testing.T) {
	providerAddr := addrs.AbsProviderConfig{
		Module:   addrs.RootModule,
		Provider: addrs.MustParseProviderSourceString("example.com/foo/bar"),
	}

	moduleBoo, _ := addrs.ParseModuleInstanceStr("module.boo")
	moduleBar, _ := addrs.ParseModuleInstanceStr("module.bar")
	moduleBarKey, _ := addrs.ParseModuleInstanceStr("module.bar[0]")
	moduleBooHoo, _ := addrs.ParseModuleInstanceStr("module.boo.module.hoo")
	moduleBarHoo, _ := addrs.ParseModuleInstanceStr("module.bar.module.hoo")

	instAddrs := map[string]addrs.AbsResourceInstance{
		"foo.from": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),

		"foo.mid": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "mid",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),

		"foo.to": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "to",
		}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),

		"foo.from[0]": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),

		"foo.to[0]": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "to",
		}.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance),

		"module.boo.foo.from": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.NoKey).Absolute(moduleBoo),

		"module.boo.foo.mid": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "mid",
		}.Instance(addrs.NoKey).Absolute(moduleBoo),

		"module.boo.foo.to": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "to",
		}.Instance(addrs.NoKey).Absolute(moduleBoo),

		"module.boo.foo.from[0]": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.IntKey(0)).Absolute(moduleBoo),

		"module.boo.foo.to[0]": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "to",
		}.Instance(addrs.IntKey(0)).Absolute(moduleBoo),

		"module.bar.foo.from": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.NoKey).Absolute(moduleBar),

		"module.bar[0].foo.from": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.NoKey).Absolute(moduleBarKey),

		"module.bar[0].foo.mid": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "mid",
		}.Instance(addrs.NoKey).Absolute(moduleBarKey),

		"module.bar[0].foo.to": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "to",
		}.Instance(addrs.NoKey).Absolute(moduleBarKey),

		"module.bar[0].foo.from[0]": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.IntKey(0)).Absolute(moduleBarKey),

		"module.bar[0].foo.to[0]": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "to",
		}.Instance(addrs.IntKey(0)).Absolute(moduleBarKey),

		"module.boo.module.hoo.foo.from": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.NoKey).Absolute(moduleBooHoo),

		"module.bar.module.hoo.foo.from": addrs.Resource{
			Mode: addrs.ManagedResourceMode,
			Type: "foo",
			Name: "from",
		}.Instance(addrs.NoKey).Absolute(moduleBarHoo),
	}

	emptyResults := MoveResults{
		Changes: map[addrs.UniqueKey]MoveSuccess{},
		Blocked: map[addrs.UniqueKey]MoveBlocked{},
	}

	tests := map[string]struct {
		Stmts []MoveStatement
		State *states.State

		WantResults       MoveResults
		WantInstanceAddrs []string
	}{
		"no moves and empty state": {
			[]MoveStatement{},
			states.NewState(),
			emptyResults,
			nil,
		},
		"no moves": {
			[]MoveStatement{},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			emptyResults,
			[]string{
				`foo.from`,
			},
		},
		"single move of whole singleton resource": {
			[]MoveStatement{
				testMoveStatement(t, "", "foo.from", "foo.to"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["foo.to"].UniqueKey(): {
						From: instAddrs["foo.from"],
						To:   instAddrs["foo.to"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`foo.to`,
			},
		},
		"single move of whole 'count' resource": {
			[]MoveStatement{
				testMoveStatement(t, "", "foo.from", "foo.to"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["foo.to[0]"].UniqueKey(): {
						From: instAddrs["foo.from[0]"],
						To:   instAddrs["foo.to[0]"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`foo.to[0]`,
			},
		},
		"chained move of whole singleton resource": {
			[]MoveStatement{
				testMoveStatement(t, "", "foo.from", "foo.mid"),
				testMoveStatement(t, "", "foo.mid", "foo.to"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["foo.to"].UniqueKey(): {
						From: instAddrs["foo.from"],
						To:   instAddrs["foo.to"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`foo.to`,
			},
		},

		"move whole resource into module": {
			[]MoveStatement{
				testMoveStatement(t, "", "foo.from", "module.boo.foo.to"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.boo.foo.to[0]"].UniqueKey(): {
						From: instAddrs["foo.from[0]"],
						To:   instAddrs["module.boo.foo.to[0]"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.boo.foo.to[0]`,
			},
		},

		"move resource instance between modules": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.boo.foo.from[0]", "module.bar[0].foo.to[0]"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.from[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.bar[0].foo.to[0]"].UniqueKey(): {
						From: instAddrs["module.boo.foo.from[0]"],
						To:   instAddrs["module.bar[0].foo.to[0]"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.bar[0].foo.to[0]`,
			},
		},

		"module move with child module": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.boo", "module.bar"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.module.hoo.foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.bar.foo.from"].UniqueKey(): {
						From: instAddrs["module.boo.foo.from"],
						To:   instAddrs["module.bar.foo.from"],
					},
					instAddrs["module.bar.module.hoo.foo.from"].UniqueKey(): {
						From: instAddrs["module.boo.module.hoo.foo.from"],
						To:   instAddrs["module.bar.module.hoo.foo.from"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.bar.foo.from`,
				`module.bar.module.hoo.foo.from`,
			},
		},

		"move whole single module to indexed module": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.boo", "module.bar[0]"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.from[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.bar[0].foo.from[0]"].UniqueKey(): {
						From: instAddrs["module.boo.foo.from[0]"],
						To:   instAddrs["module.bar[0].foo.from[0]"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.bar[0].foo.from[0]`,
			},
		},

		"move whole module to indexed module and move instance chained": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.boo", "module.bar[0]"),
				testMoveStatement(t, "bar", "foo.from[0]", "foo.to[0]"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.from[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.bar[0].foo.to[0]"].UniqueKey(): {
						From: instAddrs["module.boo.foo.from[0]"],
						To:   instAddrs["module.bar[0].foo.to[0]"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.bar[0].foo.to[0]`,
			},
		},

		"move instance to indexed module and instance chained": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.boo.foo.from[0]", "module.bar[0].foo.from[0]"),
				testMoveStatement(t, "bar", "foo.from[0]", "foo.to[0]"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.from[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.bar[0].foo.to[0]"].UniqueKey(): {
						From: instAddrs["module.boo.foo.from[0]"],
						To:   instAddrs["module.bar[0].foo.to[0]"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.bar[0].foo.to[0]`,
			},
		},

		"move module instance to already-existing module instance": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.bar[0]", "module.boo"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.bar[0].foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.to[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				// Nothing moved, because the module.b address is already
				// occupied by another module.
				Changes: map[addrs.UniqueKey]MoveSuccess{},
				Blocked: map[addrs.UniqueKey]MoveBlocked{
					instAddrs["module.bar[0].foo.from"].Module.UniqueKey(): {
						Wanted: instAddrs["module.boo.foo.to[0]"].Module,
						Actual: instAddrs["module.bar[0].foo.from"].Module,
					},
				},
			},
			[]string{
				`module.bar[0].foo.from`,
				`module.boo.foo.to[0]`,
			},
		},

		"move resource to already-existing resource": {
			[]MoveStatement{
				testMoveStatement(t, "", "foo.from", "foo.to"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
				s.SetResourceInstanceCurrent(
					instAddrs["foo.to"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				// Nothing moved, because the from.to address is already
				// occupied by another resource.
				Changes: map[addrs.UniqueKey]MoveSuccess{},
				Blocked: map[addrs.UniqueKey]MoveBlocked{
					instAddrs["foo.from"].ContainingResource().UniqueKey(): {
						Wanted: instAddrs["foo.to"].ContainingResource(),
						Actual: instAddrs["foo.from"].ContainingResource(),
					},
				},
			},
			[]string{
				`foo.from`,
				`foo.to`,
			},
		},

		"move resource instance to already-existing resource instance": {
			[]MoveStatement{
				testMoveStatement(t, "", "foo.from", "foo.to[0]"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
				s.SetResourceInstanceCurrent(
					instAddrs["foo.to[0]"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				// Nothing moved, because the from.to[0] address is already
				// occupied by another resource instance.
				Changes: map[addrs.UniqueKey]MoveSuccess{},
				Blocked: map[addrs.UniqueKey]MoveBlocked{
					instAddrs["foo.from"].UniqueKey(): {
						Wanted: instAddrs["foo.to[0]"],
						Actual: instAddrs["foo.from"],
					},
				},
			},
			[]string{
				`foo.from`,
				`foo.to[0]`,
			},
		},
		"move resource and containing module": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.boo", "module.bar[0]"),
				testMoveStatement(t, "boo", "foo.from", "foo.to"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.boo.foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.bar[0].foo.to"].UniqueKey(): {
						From: instAddrs["module.boo.foo.from"],
						To:   instAddrs["module.bar[0].foo.to"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.bar[0].foo.to`,
			},
		},

		"move module and then move resource into it": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.bar[0]", "module.boo"),
				testMoveStatement(t, "", "foo.from", "module.boo.foo.from"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.bar[0].foo.to"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{
					instAddrs["module.boo.foo.from"].UniqueKey(): {
						instAddrs["foo.from"],
						instAddrs["module.boo.foo.from"],
					},
					instAddrs["module.boo.foo.to"].UniqueKey(): {
						instAddrs["module.bar[0].foo.to"],
						instAddrs["module.boo.foo.to"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{},
			},
			[]string{
				`module.boo.foo.from`,
				`module.boo.foo.to`,
			},
		},

		"module move collides with resource move": {
			[]MoveStatement{
				testMoveStatement(t, "", "module.bar[0]", "module.boo"),
				testMoveStatement(t, "", "foo.from", "module.boo.foo.from"),
			},
			states.BuildState(func(s *states.SyncState) {
				s.SetResourceInstanceCurrent(
					instAddrs["module.bar[0].foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
				s.SetResourceInstanceCurrent(
					instAddrs["foo.from"],
					&states.ResourceInstanceObjectSrc{
						Status:    states.ObjectReady,
						AttrsJSON: []byte(`{}`),
					},
					providerAddr,
				)
			}),
			MoveResults{
				Changes: map[addrs.UniqueKey]MoveSuccess{

					instAddrs["module.boo.foo.from"].UniqueKey(): {
						instAddrs["module.bar[0].foo.from"],
						instAddrs["module.boo.foo.from"],
					},
				},
				Blocked: map[addrs.UniqueKey]MoveBlocked{
					instAddrs["foo.from"].ContainingResource().UniqueKey(): {
						Actual: instAddrs["foo.from"].ContainingResource(),
						Wanted: instAddrs["module.boo.foo.from"].ContainingResource(),
					},
				},
			},
			[]string{
				`foo.from`,
				`module.boo.foo.from`,
			},
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			var stmtsBuf strings.Builder
			for _, stmt := range test.Stmts {
				fmt.Fprintf(&stmtsBuf, "• from: %s\n  to:   %s\n", stmt.From, stmt.To)
			}
			t.Logf("move statements:\n%s", stmtsBuf.String())

			t.Logf("resource instances in prior state:\n%s", spew.Sdump(allResourceInstanceAddrsInState(test.State)))

			state := test.State.DeepCopy() // don't modify the test case in-place
			gotResults := ApplyMoves(test.Stmts, state)

			if diff := cmp.Diff(test.WantResults, gotResults); diff != "" {
				t.Errorf("wrong results\n%s", diff)
			}

			gotInstAddrs := allResourceInstanceAddrsInState(state)
			if diff := cmp.Diff(test.WantInstanceAddrs, gotInstAddrs); diff != "" {
				t.Errorf("wrong resource instances in final state\n%s", diff)
			}
		})
	}
}

func testMoveStatement(t *testing.T, module string, from string, to string) MoveStatement {
	t.Helper()

	moduleAddr := addrs.RootModule
	if len(module) != 0 {
		moduleAddr = addrs.Module(strings.Split(module, "."))
	}

	fromTraversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(from), "from", hcl.InitialPos)
	if hclDiags.HasErrors() {
		t.Fatalf("invalid 'from' argument: %s", hclDiags.Error())
	}
	fromAddr, diags := addrs.ParseMoveEndpoint(fromTraversal)
	if diags.HasErrors() {
		t.Fatalf("invalid 'from' argument: %s", diags.Err().Error())
	}
	toTraversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(to), "to", hcl.InitialPos)
	if diags.HasErrors() {
		t.Fatalf("invalid 'to' argument: %s", hclDiags.Error())
	}
	toAddr, diags := addrs.ParseMoveEndpoint(toTraversal)
	if diags.HasErrors() {
		t.Fatalf("invalid 'from' argument: %s", diags.Err().Error())
	}

	fromInModule, toInModule := addrs.UnifyMoveEndpoints(moduleAddr, fromAddr, toAddr)
	if fromInModule == nil || toInModule == nil {
		t.Fatalf("incompatible endpoints")
	}

	return MoveStatement{
		From: fromInModule,
		To:   toInModule,

		// DeclRange not populated because it's unimportant for our tests
	}
}

func allResourceInstanceAddrsInState(state *states.State) []string {
	var ret []string
	for _, ms := range state.Modules {
		for _, rs := range ms.Resources {
			for key := range rs.Instances {
				ret = append(ret, rs.Addr.Instance(key).String())
			}
		}
	}
	sort.Strings(ret)
	return ret
}
