From 235e860118358266af3ea7ff36fa1a71b2cab059 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 12 Apr 2016 14:17:59 -0700 Subject: [PATCH] command/state mv: handle -state-out to a different path --- command/state_mv.go | 77 +++++++++++++++++++++++++++++++++------- command/state_mv_test.go | 73 +++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 13 deletions(-) diff --git a/command/state_mv.go b/command/state_mv.go index 5b2a67b79b..ec5597ad52 100644 --- a/command/state_mv.go +++ b/command/state_mv.go @@ -17,11 +17,13 @@ type StateMvCommand struct { func (c *StateMvCommand) Run(args []string) int { args = c.Meta.process(args, true) - var backupPath string + // We create two metas to track the two states + var meta1, meta2 Meta cmdFlags := c.Meta.flagSet("state show") - cmdFlags.StringVar(&backupPath, "backup", "", "backup") - cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path") - cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path") + cmdFlags.StringVar(&meta1.stateOutPath, "backup", "", "backup") + cmdFlags.StringVar(&meta1.statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&meta2.stateOutPath, "backup-out", "", "backup") + cmdFlags.StringVar(&meta2.statePath, "state-out", "", "path") if err := cmdFlags.Parse(args); err != nil { return cli.RunResultHelp } @@ -31,45 +33,87 @@ func (c *StateMvCommand) Run(args []string) int { return cli.RunResultHelp } - state, err := c.StateMeta.State(&c.Meta) + // Copy the `-state` flag for output if we weren't given a custom one + if meta2.statePath == "" { + meta2.statePath = meta1.statePath + } + + // Read the from state + stateFrom, err := c.StateMeta.State(&meta1) if err != nil { c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) return cli.RunResultHelp } - stateReal := state.State() - if stateReal == nil { + stateFromReal := stateFrom.State() + if stateFromReal == nil { c.Ui.Error(fmt.Sprintf(errStateNotFound)) return 1 } - filter := &terraform.StateFilter{State: stateReal} + // Read the destination state + stateTo := stateFrom + stateToReal := stateFromReal + if meta2.statePath != meta1.statePath { + stateTo, err = c.StateMeta.State(&meta2) + if err != nil { + c.Ui.Error(fmt.Sprintf(errStateLoadingState, err)) + return cli.RunResultHelp + } + + stateToReal = stateTo.State() + if stateToReal == nil { + stateToReal = terraform.NewState() + } + } + + // 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 + } - if err := stateReal.Remove(args[0]); err != nil { + // Do the actual move + if err := stateFromReal.Remove(args[0]); err != nil { c.Ui.Error(fmt.Sprintf(errStateMv, err)) return 1 } - if err := stateReal.Add(args[0], args[1], results[0].Value); err != nil { + if err := stateToReal.Add(args[0], args[1], results[0].Value); err != nil { c.Ui.Error(fmt.Sprintf(errStateMv, err)) return 1 } - if err := state.WriteState(stateReal); err != nil { + // Write the new state + if err := stateTo.WriteState(stateToReal); err != nil { c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) return 1 } - if err := state.PersistState(); err != nil { + 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)) + return 1 + } + + if err := stateFrom.PersistState(); err != nil { + c.Ui.Error(fmt.Sprintf(errStateMvPersist, err)) + return 1 + } + } + c.Ui.Output(fmt.Sprintf( "Moved %s to %s", args[0], args[1])) return 0 @@ -93,11 +137,18 @@ Usage: terraform state mv [options] ADDRESS ADDRESS Options: - -backup=PATH Path where Terraform should write the backup + -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 a backup extension. + -backup-out=PATH Path where Terraform should write the backup for the destination + state. This can't be disabled. If not set, Terraform + will write it to the same path as the destination state + file with a backup extension. This only needs + to be specified if -state-out is set to a different path + than -state. + -state=PATH Path to a Terraform state file to use to look up Terraform-managed resources. By default it will use the state "terraform.tfstate" if it exists. diff --git a/command/state_mv_test.go b/command/state_mv_test.go index 06de1a6caa..e8b0391d80 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -71,6 +71,61 @@ func TestStateMv(t *testing.T) { testStateOutput(t, backups[0], testStateMvOutputOriginal) } +func TestStateMv_stateOutNew(t *testing.T) { + state := &terraform.State{ + Modules: []*terraform.ModuleState{ + &terraform.ModuleState{ + Path: []string{"root"}, + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + Type: "test_instance", + Primary: &terraform.InstanceState{ + ID: "bar", + Attributes: map[string]string{ + "foo": "value", + "bar": "value", + }, + }, + }, + }, + }, + }, + } + + statePath := testStateFile(t, state) + stateOutPath := statePath + ".out" + + p := testProvider() + ui := new(cli.MockUi) + c := &StateMvCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "-state-out", stateOutPath, + "test_instance.foo", + "test_instance.bar", + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Test it is correct + testStateOutput(t, stateOutPath, testStateMvOutput_stateOut) + testStateOutput(t, statePath, testStateMvOutput_stateOutSrc) + + // Test we have backups + backups := testStateBackups(t, filepath.Dir(statePath)) + if len(backups) != 1 { + t.Fatalf("bad: %#v", backups) + } + testStateOutput(t, backups[0], testStateMvOutput_stateOutOriginal) +} + func TestStateMv_noState(t *testing.T) { tmp, cwd := testCwd(t) defer testFixCwd(t, tmp, cwd) @@ -111,3 +166,21 @@ test_instance.baz: bar = value foo = value ` + +const testStateMvOutput_stateOut = ` +test_instance.bar: + ID = bar + bar = value + foo = value +` + +const testStateMvOutput_stateOutSrc = ` + +` + +const testStateMvOutput_stateOutOriginal = ` +test_instance.foo: + ID = bar + bar = value + foo = value +`