From 7ec3f96e3aa46367a2d0762b8835fc18c5d2f996 Mon Sep 17 00:00:00 2001 From: Sander van Harmelen Date: Thu, 25 Oct 2018 15:41:00 +0200 Subject: [PATCH] command/state: update and fix the state mv command --- command/state_meta.go | 20 ++ command/state_mv.go | 259 ++++++++++---- command/state_mv_test.go | 53 ++- command/state_rm.go | 32 +- command/state_rm_test.go | 10 +- states/state_filter.go | 36 +- states/state_filter_test.go | 5 +- terraform/state_add.go | 374 ------------------- terraform/state_add_test.go | 695 ------------------------------------ 9 files changed, 282 insertions(+), 1202 deletions(-) delete mode 100644 terraform/state_add.go delete mode 100644 terraform/state_add_test.go diff --git a/command/state_meta.go b/command/state_meta.go index 4cf4034c0..247e66207 100644 --- a/command/state_meta.go +++ b/command/state_meta.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "sort" "time" "github.com/hashicorp/terraform/addrs" @@ -111,6 +112,25 @@ func (c *StateMeta) filter(state *states.State, args []string) ([]*states.Filter } } + // Sort the results + sort.Slice(results, func(i, j int) bool { + a, b := results[i], results[j] + + // If the length is different, sort on the length so that the + // best match is the first result. + if len(a.Address.String()) != len(b.Address.String()) { + return len(a.Address.String()) < len(b.Address.String()) + } + + // If the addresses are different it is just lexographic sorting + if a.Address.String() != b.Address.String() { + return a.Address.String() < b.Address.String() + } + + // Addresses are the same, which means it matters on the type + return a.SortedType() < b.SortedType() + }) + return results, nil } diff --git a/command/state_mv.go b/command/state_mv.go index 85a759b3e..db6ae74a0 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/states" "github.com/mitchellh/cli" ) @@ -22,7 +23,9 @@ func (c *StateMvCommand) Run(args []string) int { // We create two metas to track the two states var backupPathOut, statePathOut string + var dryRun bool cmdFlags := c.Meta.flagSet("state mv") + cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run") cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") cmdFlags.StringVar(&c.statePath, "state", "", "path") cmdFlags.StringVar(&backupPathOut, "backup-out", "-", "backup") @@ -37,127 +40,228 @@ func (c *StateMvCommand) Run(args []string) int { } // Read the from state - stateFrom, err := c.State() + stateFromMgr, err := c.State() if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return 1 } - - if err := stateFrom.RefreshState(); err != nil { - c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) + if err := stateFromMgr.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err)) return 1 } - stateFromReal := stateFrom.State() - if stateFromReal == nil { + stateFrom := stateFromMgr.State() + if stateFrom == nil { c.Ui.Error(fmt.Sprintf(errStateNotFound)) return 1 } // Read the destination state + stateToMgr := stateFromMgr stateTo := stateFrom - stateToReal := stateFromReal if statePathOut != "" { c.statePath = statePathOut c.backupPath = backupPathOut - stateTo, err = c.State() + + stateToMgr, err = c.State() if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return 1 } - - if err := stateTo.RefreshState(); err != nil { - c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) + if err := stateToMgr.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf("Failed to refresh state: %s", err)) return 1 } - stateToReal = stateTo.State() - if stateToReal == nil { - stateToReal = states.NewState() + stateTo = stateToMgr.State() + if stateTo == nil { + stateTo = states.NewState() } } - c.Ui.Error("state mv command not yet updated for new state types") - return 1 - /* - // Filter what we're moving - filter := &terraform.StateFilter{State: stateFromReal} - results, err := filter.Filter(args[0]) - if err != nil { - c.Ui.Error(fmt.Sprintf(errStateMv, err)) - return cli.RunResultHelp - } - if len(results) == 0 { - c.Ui.Output(fmt.Sprintf("Item to move doesn't exist: %s", args[0])) - return 1 - } + // Filter what we are moving. + results, err := c.filter(stateFrom, []string{args[0]}) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateFilter, err)) + return cli.RunResultHelp + } - // Get the item to add to the state - add := c.addableResult(results) - - // Do the actual move - if err := stateFromReal.Remove(args[0]); err != nil { - c.Ui.Error(fmt.Sprintf(errStateMv, err)) - return 1 + // If we have no results, exit early as we're not going to do anything. + if len(results) == 0 { + if dryRun { + c.Ui.Output("Would have moved nothing.") + } else { + c.Ui.Output("No matching objects found.") } + return 0 + } - if err := stateToReal.Add(args[0], args[1], add); err != nil { - c.Ui.Error(fmt.Sprintf(errStateMv, err)) - return 1 - } + prefix := "Move" + if dryRun { + prefix = "Would move" + } - // Write the new state - if err := stateTo.WriteState(stateToReal); err != nil { - c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) - return 1 - } - - if err := stateTo.PersistState(); err != nil { - c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) - return 1 - } - - // Write the old state if it is different - if stateTo != stateFrom { - if err := stateFrom.WriteState(stateFromReal); err != nil { - c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) + var moved int + ssFrom := stateFrom.SyncWrapper() + for _, result := range c.moveableResult(results) { + switch addrFrom := result.Address.(type) { + case addrs.ModuleInstance: + search, err := addrs.ParseModuleInstanceStr(args[0]) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateMv, err)) + return 1 + } + addrTo, err := addrs.ParseModuleInstanceStr(args[1]) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateMv, err)) return 1 } - if err := stateFrom.PersistState(); err != nil { - c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) + if len(search) < len(addrFrom) { + addrTo = append(addrTo, addrFrom[len(search):]...) + } + + if stateTo.Module(addrTo) != nil { + c.Ui.Error(fmt.Sprintf(errStateMv, "destination module already exists")) return 1 } - } - */ - c.Ui.Output(fmt.Sprintf( - "Moved %s to %s", args[0], args[1])) + moved++ + c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String())) + if !dryRun { + ssFrom.RemoveModule(addrFrom) + + // Update the address before adding it to the state. + m := result.Value.(*states.Module) + m.Addr = addrTo + stateTo.Modules[addrTo.String()] = m + } + + case addrs.AbsResource: + addrTo, err := addrs.ParseAbsResourceStr(args[1]) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateMv, err)) + return 1 + } + + if addrFrom.Resource.Type != addrTo.Resource.Type { + c.Ui.Error(fmt.Sprintf( + errStateMv, "resource types do not match")) + return 1 + } + if stateTo.Module(addrTo.Module) == nil { + c.Ui.Error(fmt.Sprintf( + errStateMv, "destination module does not exist")) + return 1 + } + if stateTo.Resource(addrTo) != nil { + c.Ui.Error(fmt.Sprintf( + errStateMv, "destination resource already exists")) + return 1 + } + + moved++ + c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), addrTo.String())) + if !dryRun { + ssFrom.RemoveResource(addrFrom) + + // Update the address before adding it to the state. + rs := result.Value.(*states.Resource) + rs.Addr = addrTo.Resource + stateTo.Module(addrTo.Module).Resources[addrTo.Resource.String()] = rs + } + + case addrs.AbsResourceInstance: + addrTo, err := addrs.ParseAbsResourceInstanceStr(args[1]) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateMv, err)) + return 1 + } + + if stateTo.Module(addrTo.Module) == nil { + c.Ui.Error(fmt.Sprintf( + errStateMv, "destination module does not exist")) + return 1 + } + if stateTo.Resource(addrTo.ContainingResource()) == nil { + c.Ui.Error(fmt.Sprintf( + errStateMv, "destination resource does not exist")) + return 1 + } + if stateTo.ResourceInstance(addrTo) != nil { + c.Ui.Error(fmt.Sprintf( + errStateMv, "destination resource instance already exists")) + return 1 + } + + moved++ + c.Ui.Output(fmt.Sprintf("%s %q to %q", prefix, addrFrom.String(), args[1])) + if !dryRun { + ssFrom.ForgetResourceInstanceAll(addrFrom) + ssFrom.RemoveResourceIfEmpty(addrFrom.ContainingResource()) + + rs := stateTo.Resource(addrTo.ContainingResource()) + rs.Instances[addrTo.Resource.Key] = result.Value.(*states.ResourceInstance) + } + } + } + + if dryRun { + if moved == 0 { + c.Ui.Output("Would have moved nothing.") + } + return 0 // This is as far as we go in dry-run mode + } + + // Write the new state + if err := stateToMgr.WriteState(stateTo); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + if err := stateToMgr.PersistState(); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + + // Write the old state if it is different + if stateTo != stateFrom { + if err := stateFromMgr.WriteState(stateFrom); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + if err := stateFromMgr.PersistState(); err != nil { + c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) + return 1 + } + } + + if moved == 0 { + c.Ui.Output("No matching objects found.") + } else { + c.Ui.Output(fmt.Sprintf("Successfully moved %d object(s).", moved)) + } return 0 } -// addableResult takes the result from a filter operation and returns what to -// call State.Add with. The reason we do this is because in the module case +// moveableResult takes the result from a filter operation and returns what +// object(s) to move. The reason we do this is because in the module case // we must add the list of all modules returned versus just the root module. -func (c *StateMvCommand) addableResult(results []*states.FilterResult) interface{} { - switch v := results[0].Value.(type) { - case *states.Module: - // If a state module then we should add the full list of modules - result := []*states.Module{v} - if len(results) > 1 { +func (c *StateMvCommand) moveableResult(results []*states.FilterResult) []*states.FilterResult { + result := results[:1] + + if len(results) > 1 { + // If a state module then we should add the full list of modules. + if _, ok := result[0].Address.(addrs.ModuleInstance); ok { for _, r := range results[1:] { - if ms, ok := r.Value.(*states.Module); ok { - result = append(result, ms) + if _, ok := r.Address.(addrs.ModuleInstance); ok { + result = append(result, r) } } } - return result - - default: - // By default just add the first result - return v } + + return result } func (c *StateMvCommand) Help() string { @@ -182,6 +286,9 @@ Usage: terraform state mv [options] SOURCE DESTINATION Options: + -dry-run If set, prints out what would've been moved but doesn't + actually move anything. + -backup=PATH Path where Terraform should write the backup for the original state. This can't be disabled. If not set, Terraform will write it to the same path as the statefile with @@ -209,7 +316,7 @@ func (c *StateMvCommand) Synopsis() string { return "Move an item in the state" } -const errStateMv = `Error moving state: %[1]s +const errStateMv = `Error moving state: %s Please ensure your addresses and state paths are valid. No state was persisted. Your existing states are untouched.` diff --git a/command/state_mv_test.go b/command/state_mv_test.go index 2e00059f8..06c26c8f8 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "testing" "github.com/mitchellh/cli" @@ -74,6 +75,48 @@ func TestStateMv(t *testing.T) { testStateOutput(t, backups[0], testStateMvOutputOriginal) } +func TestStateMv_differentResourceTypes(t *testing.T) { + state := states.BuildState(func(s *states.SyncState) { + s.SetResourceInstanceCurrent( + addrs.Resource{ + Mode: addrs.ManagedResourceMode, + Type: "test_instance", + Name: "foo", + }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), + &states.ResourceInstanceObjectSrc{ + AttrsJSON: []byte(`{"id":"bar","foo":"value","bar":"value"}`), + Status: states.ObjectReady, + }, + addrs.ProviderConfig{Type: "test"}.Absolute(addrs.RootModuleInstance), + ) + }) + statePath := testStateFile(t, state) + + p := testProvider() + ui := new(cli.MockUi) + c := &StateMvCommand{ + StateMeta{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + }, + } + + args := []string{ + "-state", statePath, + "test_instance.foo", + "test_network.bar", + } + if code := c.Run(args); code == 0 { + t.Fatalf("expected error output, got:\n%s", ui.OutputWriter.String()) + } + + if !strings.Contains(ui.ErrorWriter.String(), "resource types do not match") { + t.Fatalf("expected initialization error, got:\n%s", ui.ErrorWriter.String()) + } +} + // don't modify backend state is we supply a -state flag func TestStateMv_explicitWithBackend(t *testing.T) { td := tempDir(t) @@ -152,10 +195,6 @@ func TestStateMv_explicitWithBackend(t *testing.T) { } func TestStateMv_backupExplicit(t *testing.T) { - td := tempDir(t) - defer os.RemoveAll(td) - backupPath := filepath.Join(td, "backup") - state := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ @@ -183,6 +222,7 @@ func TestStateMv_backupExplicit(t *testing.T) { ) }) statePath := testStateFile(t, state) + backupPath := statePath + ".backup.test" p := testProvider() ui := new(cli.MockUi) @@ -913,8 +953,6 @@ test_instance.foo.10: const testStateMvNestedModule_stateOut = ` -module.bar: - module.bar.child1: test_instance.foo: ID = bar @@ -935,8 +973,6 @@ const testStateMvNestedModule_stateOutSrc = ` const testStateMvNestedModule_stateOutOriginal = ` -module.foo: - module.foo.child1: test_instance.foo: ID = bar @@ -983,6 +1019,7 @@ test_instance.bar: foo = value test_instance.qux: ID = bar + provider = provider.test ` const testStateMvExisting_stateSrcOriginal = ` diff --git a/command/state_rm.go b/command/state_rm.go index e3b2afd62..0c57a33c1 100644 --- a/command/state_rm.go +++ b/command/state_rm.go @@ -5,10 +5,9 @@ import ( "sort" "strings" - "github.com/mitchellh/cli" - "github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/states" + "github.com/mitchellh/cli" ) // StateRmCommand is a Command implementation that shows a single resource. @@ -22,18 +21,19 @@ func (c *StateRmCommand) Run(args []string) int { return 1 } + var dryRun bool cmdFlags := c.Meta.flagSet("state show") + cmdFlags.BoolVar(&dryRun, "dry-run", false, "dry run") cmdFlags.StringVar(&c.backupPath, "backup", "-", "backup") cmdFlags.StringVar(&c.statePath, "state", "", "path") - dryRun := cmdFlags.Bool("dry-run", false, "dry run") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp } args = cmdFlags.Args() if len(args) < 1 { - c.Ui.Error("At least one resource address is required.") - return 1 + c.Ui.Error("At least one address is required.\n") + return cli.RunResultHelp } // Get the state @@ -53,18 +53,16 @@ func (c *StateRmCommand) Run(args []string) int { return 1 } + // Filter what we are removing. results, err := c.filter(state, args) if err != nil { c.Ui.Error(fmt.Sprintf(errStateFilter, err)) return cli.RunResultHelp } - // If we have no results, just exit early, we're not going to do anything. - // While what happens below is fairly fast, this is an important early - // exit since the prune below might modify the state more and we don't - // want to modify the state if we don't have to. + // If we have no results, exit early as we're not going to do anything. if len(results) == 0 { - if *dryRun { + if dryRun { c.Ui.Output("Would have removed nothing.") } else { c.Ui.Output("No matching resources found.") @@ -73,7 +71,7 @@ func (c *StateRmCommand) Run(args []string) int { } prefix := "Remove resource " - if *dryRun { + if dryRun { prefix = "Would remove resource " } @@ -92,7 +90,7 @@ func (c *StateRmCommand) Run(args []string) int { if len(output) > 0 { c.Ui.Output(strings.Join(sort.StringSlice(output), "\n")) } - if !*dryRun { + if !dryRun { ss.RemoveModule(addr) } @@ -105,29 +103,27 @@ func (c *StateRmCommand) Run(args []string) int { if len(output) > 0 { c.Ui.Output(strings.Join(sort.StringSlice(output), "\n")) } - if !*dryRun { + if !dryRun { ss.RemoveResource(addr) } case addrs.AbsResourceInstance: isCount++ c.Ui.Output(prefix + addr.String()) - if !*dryRun { + if !dryRun { ss.ForgetResourceInstanceAll(addr) + ss.RemoveResourceIfEmpty(addr.ContainingResource()) } } } - if *dryRun { + if dryRun { if isCount == 0 { c.Ui.Output("Would have removed nothing.") } return 0 // This is as far as we go in dry-run mode } - // Prune the state before writing and persisting it. - state.PruneResourceHusks() - if err := stateMgr.WriteState(state); err != nil { c.Ui.Error(fmt.Sprintf(errStateRmPersist, err)) return 1 diff --git a/command/state_rm_test.go b/command/state_rm_test.go index 7274c8f53..fa5c9d769 100644 --- a/command/state_rm_test.go +++ b/command/state_rm_test.go @@ -115,11 +115,11 @@ func TestStateRmNoArgs(t *testing.T) { args := []string{ "-state", statePath, } - if code := c.Run(args); code != 1 { - t.Errorf("wrong exit status %d; want %d", code, 1) + if code := c.Run(args); code == 0 { + t.Errorf("expected non-zero exit code, got: %d", code) } - if msg := ui.ErrorWriter.String(); !strings.Contains(msg, "At least one resource address") { + if msg := ui.ErrorWriter.String(); !strings.Contains(msg, "At least one address") { t.Errorf("not the error we were looking for:\n%s", msg) } @@ -207,7 +207,7 @@ func TestStateRm_backupExplicit(t *testing.T) { ) }) statePath := testStateFile(t, state) - backupPath := statePath + ".mybackup" + backupPath := statePath + ".backup.test" p := testProvider() ui := new(cli.MockUi) @@ -251,7 +251,7 @@ func TestStateRm_noState(t *testing.T) { }, } - args := []string{} + args := []string{"foo"} if code := c.Run(args); code != 1 { t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) } diff --git a/states/state_filter.go b/states/state_filter.go index bec5f57f2..0f10a58dc 100644 --- a/states/state_filter.go +++ b/states/state_filter.go @@ -63,8 +63,19 @@ func (f *Filter) Filter(fs ...string) ([]*FilterResult, error) { results = append(results, v) } - // Sort them and return - sort.Sort(FilterResultSlice(results)) + // Sort the results + sort.Slice(results, func(i, j int) bool { + a, b := results[i], results[j] + + // If the addresses are different it is just lexographic sorting + if a.Address.String() != b.Address.String() { + return a.Address.String() < b.Address.String() + } + + // Addresses are the same, which means it matters on the type + return a.SortedType() < b.SortedType() + }) + return results, nil } @@ -155,7 +166,7 @@ func (r *FilterResult) String() string { return fmt.Sprintf("%T: %s", r.Value, r.Address) } -func (r *FilterResult) sortedType() int { +func (r *FilterResult) SortedType() int { switch r.Value.(type) { case *Module: return 0 @@ -167,22 +178,3 @@ func (r *FilterResult) sortedType() int { return 50 } } - -// FilterResultSlice is a slice of results that implements -// sort.Interface. The sorting goal is what is most appealing to -// human output. -type FilterResultSlice []*FilterResult - -func (s FilterResultSlice) Len() int { return len(s) } -func (s FilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s FilterResultSlice) Less(i, j int) bool { - a, b := s[i], s[j] - - // If the addresses are different it is just lexographic sorting - if a.Address.String() != b.Address.String() { - return a.Address.String() < b.Address.String() - } - - // Addresses are the same, which means it matters on the type - return a.sortedType() < b.sortedType() -} diff --git a/states/state_filter_test.go b/states/state_filter_test.go index 5303ce653..bba2b915b 100644 --- a/states/state_filter_test.go +++ b/states/state_filter_test.go @@ -399,7 +399,7 @@ func testStateSmall() *State { root := addrs.RootModuleInstance boot, _ := addrs.ParseModuleInstanceStr("module.boot") - state := BuildState(func(s *SyncState) { + return BuildState(func(s *SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, @@ -457,9 +457,6 @@ func testStateSmall() *State { }.Absolute(boot), ) }) - // fmt.Printf("mods: %#v\n", state.Modules) - // fmt.Printf("boot: %#+v\n", state.Modules["module.boot"]) - return state } // testStateSmallTestInstance returns a test State structure. diff --git a/terraform/state_add.go b/terraform/state_add.go deleted file mode 100644 index dc642dc03..000000000 --- a/terraform/state_add.go +++ /dev/null @@ -1,374 +0,0 @@ -package terraform - -import "fmt" - -// Add adds the item in the state at the given address. -// -// The item can be a ModuleState, ResourceState, or InstanceState. Depending -// on the item type, the address may or may not be valid. For example, a -// module cannot be moved to a resource address, however a resource can be -// moved to a module address (it retains the same name, under that resource). -// -// The item can also be a []*ModuleState, which is the case for nested -// modules. In this case, Add will expect the zero-index to be the top-most -// module to add and will only nest children from there. For semantics, this -// is equivalent to module => module. -// -// The full semantics of Add: -// -// ┌───────────────────┬───────────────────┬───────────────────┐ -// │ Module Address │ Resource Address │ Instance Address │ -// ┌─────────────────┼───────────────────┼───────────────────┼───────────────────┤ -// │ ModuleState │ ✓ │ x │ x │ -// ├─────────────────┼───────────────────┼───────────────────┼───────────────────┤ -// │ ResourceState │ ✓ │ ✓ │ maybe* │ -// ├─────────────────┼───────────────────┼───────────────────┼───────────────────┤ -// │ Instance State │ ✓ │ ✓ │ ✓ │ -// └─────────────────┴───────────────────┴───────────────────┴───────────────────┘ -// -// *maybe - Resources can be added at an instance address only if the resource -// represents a single instance (primary). Example: -// "aws_instance.foo" can be moved to "aws_instance.bar.tainted" -// -func (s *State) Add(fromAddrRaw string, toAddrRaw string, raw interface{}) error { - // Parse the address - - toAddr, err := ParseResourceAddress(toAddrRaw) - if err != nil { - return err - } - - // Parse the from address - fromAddr, err := ParseResourceAddress(fromAddrRaw) - if err != nil { - return err - } - - // Determine the types - from := detectValueAddLoc(raw) - to := detectAddrAddLoc(toAddr) - - // Find the function to do this - fromMap, ok := stateAddFuncs[from] - if !ok { - return fmt.Errorf("invalid source to add to state: %T", raw) - } - f, ok := fromMap[to] - if !ok { - return fmt.Errorf("invalid destination: %s (%d)", toAddr, to) - } - - // Call the migrator - if err := f(s, fromAddr, toAddr, raw); err != nil { - return err - } - - // Prune the state - s.prune() - return nil -} - -func stateAddFunc_Module_Module(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error { - // raw can be either *ModuleState or []*ModuleState. The former means - // we're moving just one module. The latter means we're moving a module - // and children. - root := raw - var rest []*ModuleState - if list, ok := raw.([]*ModuleState); ok { - // We need at least one item - if len(list) == 0 { - return fmt.Errorf("module move with no value to: %s", addr) - } - - // The first item is always the root - root = list[0] - if len(list) > 1 { - rest = list[1:] - } - } - - // Get the actual module state - src := root.(*ModuleState).deepcopy() - - // If the target module exists, it is an error - path := normalizeModulePath(addr.Path) - if s.ModuleByPath(path) != nil { - return fmt.Errorf("module target is not empty: %s", addr) - } - - // Create it and copy our outputs and dependencies - mod := s.AddModule(path) - mod.Outputs = src.Outputs - mod.Dependencies = src.Dependencies - - // Go through the resources perform an add for each of those - for k, v := range src.Resources { - resourceKey, err := ParseResourceStateKey(k) - if err != nil { - return err - } - - // Update the resource address for this - addrCopy := *addr - addrCopy.Type = resourceKey.Type - addrCopy.Name = resourceKey.Name - addrCopy.Index = resourceKey.Index - addrCopy.Mode = resourceKey.Mode - - // Perform an add - if err := s.Add(fromAddr.String(), addrCopy.String(), v); err != nil { - return err - } - } - - // Add all the children if we have them - for _, item := range rest { - // If item isn't a descendent of our root, then ignore it - if !src.IsDescendent(item) { - continue - } - - // It is! Strip the leading prefix and attach that to our address - extra := item.Path[len(src.Path):] - addrCopy := addr.Copy() - addrCopy.Path = append(addrCopy.Path, extra...) - - // Add it - s.Add(fromAddr.String(), addrCopy.String(), item) - } - - return nil -} - -func stateAddFunc_Resource_Module( - s *State, from, to *ResourceAddress, raw interface{}) error { - // Build the more specific to addr - addr := *to - addr.Type = from.Type - addr.Name = from.Name - - return s.Add(from.String(), addr.String(), raw) -} - -func stateAddFunc_Resource_Resource(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error { - // raw can be either *ResourceState or []*ResourceState. The former means - // we're moving just one resource. The latter means we're moving a count - // of resources. - if list, ok := raw.([]*ResourceState); ok { - // We need at least one item - if len(list) == 0 { - return fmt.Errorf("resource move with no value to: %s", addr) - } - - // If there is an index, this is an error since we can't assign - // a set of resources to a single index - if addr.Index >= 0 && len(list) > 1 { - return fmt.Errorf( - "multiple resources can't be moved to a single index: "+ - "%s => %s", fromAddr, addr) - } - - // Add each with a specific index - for i, rs := range list { - addrCopy := addr.Copy() - addrCopy.Index = i - - if err := s.Add(fromAddr.String(), addrCopy.String(), rs); err != nil { - return err - } - } - - return nil - } - - src := raw.(*ResourceState).deepcopy() - - // Initialize the resource - resourceRaw, exists := stateAddInitAddr(s, addr) - if exists { - return fmt.Errorf("resource exists and not empty: %s", addr) - } - resource := resourceRaw.(*ResourceState) - resource.Type = src.Type - resource.Dependencies = src.Dependencies - resource.Provider = src.Provider - - // Move the primary - if src.Primary != nil { - addrCopy := *addr - addrCopy.InstanceType = TypePrimary - addrCopy.InstanceTypeSet = true - if err := s.Add(fromAddr.String(), addrCopy.String(), src.Primary); err != nil { - return err - } - } - - // Move all deposed - if len(src.Deposed) > 0 { - resource.Deposed = src.Deposed - } - - return nil -} - -func stateAddFunc_Instance_Instance(s *State, fromAddr, addr *ResourceAddress, raw interface{}) error { - src := raw.(*InstanceState).DeepCopy() - - // Create the instance - instanceRaw, _ := stateAddInitAddr(s, addr) - instance := instanceRaw.(*InstanceState) - - // Set it - instance.Set(src) - - return nil -} - -func stateAddFunc_Instance_Module( - s *State, from, to *ResourceAddress, raw interface{}) error { - addr := *to - addr.Type = from.Type - addr.Name = from.Name - - return s.Add(from.String(), addr.String(), raw) -} - -func stateAddFunc_Instance_Resource( - s *State, from, to *ResourceAddress, raw interface{}) error { - addr := *to - addr.InstanceType = TypePrimary - addr.InstanceTypeSet = true - - return s.Add(from.String(), addr.String(), raw) -} - -// stateAddFunc is the type of function for adding an item to a state -type stateAddFunc func(s *State, from, to *ResourceAddress, item interface{}) error - -// stateAddFuncs has the full matrix mapping of the state adders. -var stateAddFuncs map[stateAddLoc]map[stateAddLoc]stateAddFunc - -func init() { - stateAddFuncs = map[stateAddLoc]map[stateAddLoc]stateAddFunc{ - stateAddModule: { - stateAddModule: stateAddFunc_Module_Module, - }, - stateAddResource: { - stateAddModule: stateAddFunc_Resource_Module, - stateAddResource: stateAddFunc_Resource_Resource, - }, - stateAddInstance: { - stateAddInstance: stateAddFunc_Instance_Instance, - stateAddModule: stateAddFunc_Instance_Module, - stateAddResource: stateAddFunc_Instance_Resource, - }, - } -} - -// stateAddLoc is an enum to represent the location where state is being -// moved from/to. We use this for quick lookups in a function map. -type stateAddLoc uint - -const ( - stateAddInvalid stateAddLoc = iota - stateAddModule - stateAddResource - stateAddInstance -) - -// detectAddrAddLoc detects the state type for the given address. This -// function is specifically not unit tested since we consider the State.Add -// functionality to be comprehensive enough to cover this. -func detectAddrAddLoc(addr *ResourceAddress) stateAddLoc { - if addr.Name == "" { - return stateAddModule - } - - if !addr.InstanceTypeSet { - return stateAddResource - } - - return stateAddInstance -} - -// detectValueAddLoc determines the stateAddLoc value from the raw value -// that is some State structure. -func detectValueAddLoc(raw interface{}) stateAddLoc { - switch raw.(type) { - case *ModuleState: - return stateAddModule - case []*ModuleState: - return stateAddModule - case *ResourceState: - return stateAddResource - case []*ResourceState: - return stateAddResource - case *InstanceState: - return stateAddInstance - default: - return stateAddInvalid - } -} - -// stateAddInitAddr takes a ResourceAddress and creates the non-existing -// resources up to that point, returning the empty (or existing) interface -// at that address. -func stateAddInitAddr(s *State, addr *ResourceAddress) (interface{}, bool) { - addType := detectAddrAddLoc(addr) - - // Get the module - path := normalizeModulePath(addr.Path) - exists := true - mod := s.ModuleByPath(path) - if mod == nil { - mod = s.AddModule(path) - exists = false - } - if addType == stateAddModule { - return mod, exists - } - - // Add the resource - resourceKey := (&ResourceStateKey{ - Name: addr.Name, - Type: addr.Type, - Index: addr.Index, - Mode: addr.Mode, - }).String() - exists = true - resource, ok := mod.Resources[resourceKey] - if !ok { - resource = &ResourceState{Type: addr.Type} - resource.init() - mod.Resources[resourceKey] = resource - exists = false - } - if addType == stateAddResource { - return resource, exists - } - - // Get the instance - exists = true - instance := &InstanceState{} - switch addr.InstanceType { - case TypePrimary, TypeTainted: - if v := resource.Primary; v != nil { - instance = resource.Primary - } else { - exists = false - } - case TypeDeposed: - idx := addr.Index - if addr.Index < 0 { - idx = 0 - } - if len(resource.Deposed) > idx { - instance = resource.Deposed[idx] - } else { - resource.Deposed = append(resource.Deposed, instance) - exists = false - } - } - - return instance, exists -} diff --git a/terraform/state_add_test.go b/terraform/state_add_test.go deleted file mode 100644 index a91dee589..000000000 --- a/terraform/state_add_test.go +++ /dev/null @@ -1,695 +0,0 @@ -package terraform - -import ( - "fmt" - "testing" -) - -func TestStateAdd(t *testing.T) { - cases := []struct { - Name string - Err bool - From, To string - Value interface{} - One, Two *State - }{ - { - "ModuleState => Module Addr (new)", - false, - "", - "module.foo", - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "test_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "test_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ModuleState => Nested Module Addr (new)", - false, - "", - "module.foo.module.bar", - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "test_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo", "bar"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "test_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ModuleState w/ outputs and deps => Module Addr (new)", - false, - "", - "module.foo", - &ModuleState{ - Path: rootModulePath, - Outputs: map[string]*OutputState{ - "foo": &OutputState{ - Type: "string", - Sensitive: false, - Value: "bar", - }, - }, - Dependencies: []string{"foo"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "test_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Outputs: map[string]*OutputState{ - "foo": &OutputState{ - Type: "string", - Sensitive: false, - Value: "bar", - }, - }, - Dependencies: []string{"foo"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "test_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ModuleState => Module Addr (existing)", - true, - "", - "module.foo", - &ModuleState{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Resources: map[string]*ResourceState{ - "test_instance.baz": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - nil, - }, - - { - "ModuleState with children => Module Addr (new)", - false, - "module.foo", - "module.bar", - - []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Resources: map[string]*ResourceState{}, - }, - - &ModuleState{ - Path: []string{"root", "foo", "child1"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - &ModuleState{ - Path: []string{"root", "foo", "child2"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - // Should be ignored - &ModuleState{ - Path: []string{"root", "baz", "child2"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "bar"}, - Resources: map[string]*ResourceState{}, - }, - - &ModuleState{ - Path: []string{"root", "bar", "child1"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - &ModuleState{ - Path: []string{"root", "bar", "child2"}, - Resources: map[string]*ResourceState{ - "test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState => Resource Addr (new)", - false, - "aws_instance.bar", - "aws_instance.foo", - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState w/ deps, provider => Resource Addr (new)", - false, - "aws_instance.bar", - "aws_instance.foo", - &ResourceState{ - Type: "test_instance", - Provider: "foo", - Dependencies: []string{"bar"}, - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "test_instance", - Provider: "foo", - Dependencies: []string{"bar"}, - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState tainted => Resource Addr (new)", - false, - "aws_instance.bar", - "aws_instance.foo", - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - Tainted: true, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - Tainted: true, - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState with count unspecified => Resource Addr (new)", - false, - "aws_instance.bar", - "aws_instance.foo", - []*ResourceState{ - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo.0": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - "aws_instance.foo.1": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState with count unspecified => Resource Addr (new with count)", - true, - "aws_instance.bar", - "aws_instance.foo[0]", - []*ResourceState{ - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "bar", - }, - }, - }, - - &State{}, - nil, - }, - - { - "ResourceState with single count unspecified => Resource Addr (new with count)", - false, - "aws_instance.bar", - "aws_instance.foo[0]", - []*ResourceState{ - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo.0": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState => Resource Addr (new with count)", - false, - "aws_instance.bar", - "aws_instance.foo[0]", - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo.0": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ResourceState => Resource Addr (existing)", - true, - "aws_instance.bar", - "aws_instance.foo", - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - nil, - }, - - { - "ResourceState => Module (new)", - false, - "aws_instance.bar", - "module.foo", - &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Resources: map[string]*ResourceState{ - "aws_instance.bar": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "InstanceState => Resource (new)", - false, - "aws_instance.bar.primary", - "aws_instance.baz", - &InstanceState{ - ID: "foo", - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root"}, - Resources: map[string]*ResourceState{ - "aws_instance.baz": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "InstanceState => Module (new)", - false, - "aws_instance.bar.primary", - "module.foo", - &InstanceState{ - ID: "foo", - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Resources: map[string]*ResourceState{ - "aws_instance.bar": &ResourceState{ - Type: "aws_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - - { - "ModuleState => Module Addr (new with data source)", - false, - "", - "module.foo", - &ModuleState{ - Path: rootModulePath, - Resources: map[string]*ResourceState{ - "data.test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - - &State{}, - &State{ - Modules: []*ModuleState{ - &ModuleState{ - Path: []string{"root", "foo"}, - Resources: map[string]*ResourceState{ - "data.test_instance.foo": &ResourceState{ - Type: "test_instance", - Primary: &InstanceState{ - ID: "foo", - }, - }, - }, - }, - }, - }, - }, - } - - for i, tc := range cases { - t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { - // Make sure they're both initialized as normal - tc.One.init() - if tc.Two != nil { - tc.Two.init() - } - - // Add the value - err := tc.One.Add(tc.From, tc.To, tc.Value) - if (err != nil) != tc.Err { - t.Fatal(err) - } - if tc.Err { - return - } - - // Prune them both to be sure - tc.One.prune() - tc.Two.prune() - - // Verify equality - if !tc.One.Equal(tc.Two) { - //t.Fatalf("Bad: %s\n\n%#v\n\n%#v", k, tc.One, tc.Two) - t.Fatalf("Bad: \n\n%s\n\n%s", tc.One.String(), tc.Two.String()) - } - }) - } -}