From 54cc66367db0d655b05592f11b78dee99d6d0f1d Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 27 Jul 2014 18:09:04 -0400 Subject: [PATCH 1/4] command: Adding backup of state file --- command/apply.go | 28 +++++++++++++++++++++++++++- command/command.go | 3 +++ command/meta.go | 4 ++++ command/plan.go | 27 ++++++++++++++++++++++++++- command/refresh.go | 27 ++++++++++++++++++++++++++- 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/command/apply.go b/command/apply.go index 62daea217..00ea50504 100644 --- a/command/apply.go +++ b/command/apply.go @@ -3,6 +3,7 @@ package command import ( "bytes" "fmt" + "log" "os" "sort" "strings" @@ -20,7 +21,7 @@ type ApplyCommand struct { func (c *ApplyCommand) Run(args []string) int { var refresh bool - var statePath, stateOutPath string + var statePath, stateOutPath, backupPath string args = c.Meta.process(args) @@ -28,6 +29,7 @@ func (c *ApplyCommand) Run(args []string) int { cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&stateOutPath, "state-out", "", "path") + cmdFlags.StringVar(&backupPath, "backup", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -59,6 +61,12 @@ func (c *ApplyCommand) Run(args []string) int { stateOutPath = statePath } + // If we don't specify a backup path, default to state out with + // the extention + if backupPath == "" { + backupPath = stateOutPath + DefaultBackupExtention + } + // Build the context based on the arguments given ctx, planned, err := c.Context(configPath, statePath) if err != nil { @@ -69,6 +77,20 @@ func (c *ApplyCommand) Run(args []string) int { return 1 } + // Create a backup of the state before updating + if backupPath != "-" { + log.Printf("[INFO] Writing backup state to: %s", backupPath) + f, err := os.Create(backupPath) + if err == nil { + defer f.Close() + err = terraform.WriteState(c.State, f) + } + if err != nil { + c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) + return 1 + } + } + // Plan if we haven't already if !planned { if refresh { @@ -201,6 +223,10 @@ Usage: terraform apply [options] [dir] Options: + -backup=path Path to backup the existing state file before + modifying. Defaults to the "-state-out" path with + ".backup" extention. Set to "-" to disable backup. + -no-color If specified, output won't contain any color. -refresh=true Update state prior to checking for differences. This diff --git a/command/command.go b/command/command.go index 9d27321d5..faa9cfcc3 100644 --- a/command/command.go +++ b/command/command.go @@ -10,6 +10,9 @@ import ( // DefaultStateFilename is the default filename used for the state file. const DefaultStateFilename = "terraform.tfstate" +// DefaultBackupExtention is added to the state file to form the path +const DefaultBackupExtention = ".backup" + func validateContext(ctx *terraform.Context, ui cli.Ui) bool { if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { ui.Output( diff --git a/command/meta.go b/command/meta.go index e0d3acde7..2dda7a974 100644 --- a/command/meta.go +++ b/command/meta.go @@ -16,6 +16,7 @@ type Meta struct { Color bool ContextOpts *terraform.ContextOpts Ui cli.Ui + State *terraform.State // This can be set by the command itself to provide extra hooks. extraHooks []terraform.Hook @@ -77,6 +78,9 @@ func (m *Meta) Context(path, statePath string) (*terraform.Context, bool, error) } } + // Store the loaded state + m.State = state + config, err := config.LoadDir(path) if err != nil { return nil, false, fmt.Errorf("Error loading config: %s", err) diff --git a/command/plan.go b/command/plan.go index b866482b6..2a5abf3c3 100644 --- a/command/plan.go +++ b/command/plan.go @@ -17,7 +17,7 @@ type PlanCommand struct { func (c *PlanCommand) Run(args []string) int { var destroy, refresh bool - var outPath, statePath string + var outPath, statePath, backupPath string args = c.Meta.process(args) @@ -26,6 +26,7 @@ func (c *PlanCommand) Run(args []string) int { cmdFlags.BoolVar(&refresh, "refresh", true, "refresh") cmdFlags.StringVar(&outPath, "out", "", "path") cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") + cmdFlags.StringVar(&backupPath, "backup", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -58,6 +59,12 @@ func (c *PlanCommand) Run(args []string) int { } } + // If we don't specify a backup path, default to state out with + // the extention + if backupPath == "" { + backupPath = statePath + DefaultBackupExtention + } + ctx, _, err := c.Context(path, statePath) if err != nil { c.Ui.Error(err.Error()) @@ -68,6 +75,20 @@ func (c *PlanCommand) Run(args []string) int { } if refresh { + // Create a backup of the state before updating + if backupPath != "-" { + log.Printf("[INFO] Writing backup state to: %s", backupPath) + f, err := os.Create(backupPath) + if err == nil { + defer f.Close() + err = terraform.WriteState(c.State, f) + } + if err != nil { + c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) + return 1 + } + } + c.Ui.Output("Refreshing Terraform state prior to plan...\n") if _, err := ctx.Refresh(); err != nil { c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) @@ -130,6 +151,10 @@ Usage: terraform plan [options] [dir] Options: + -backup=path Path to backup the existing state file before + modifying. Defaults to the "-state-out" path with + ".backup" extention. Set to "-" to disable backup. + -destroy If set, a plan will be generated to destroy all resources managed by the given configuration and state. diff --git a/command/refresh.go b/command/refresh.go index 93310efa7..186df8e3e 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -16,13 +16,14 @@ type RefreshCommand struct { } func (c *RefreshCommand) Run(args []string) int { - var statePath, stateOutPath string + var statePath, stateOutPath, backupPath string args = c.Meta.process(args) cmdFlags := c.Meta.flagSet("refresh") cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path") cmdFlags.StringVar(&stateOutPath, "state-out", "", "path") + cmdFlags.StringVar(&backupPath, "backup", "", "path") cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } if err := cmdFlags.Parse(args); err != nil { return 1 @@ -50,6 +51,12 @@ func (c *RefreshCommand) Run(args []string) int { stateOutPath = statePath } + // If we don't specify a backup path, default to state out with + // the extention + if backupPath == "" { + backupPath = stateOutPath + DefaultBackupExtention + } + // Verify that the state path exists. The "ContextArg" function below // will actually do this, but we want to provide a richer error message // if possible. @@ -86,6 +93,20 @@ func (c *RefreshCommand) Run(args []string) int { return 1 } + // Create a backup of the state before updating + if backupPath != "-" { + log.Printf("[INFO] Writing backup state to: %s", backupPath) + f, err := os.Create(backupPath) + if err == nil { + defer f.Close() + err = terraform.WriteState(c.State, f) + } + if err != nil { + c.Ui.Error(fmt.Sprintf("Error writing backup state file: %s", err)) + return 1 + } + } + state, err := ctx.Refresh() if err != nil { c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) @@ -119,6 +140,10 @@ Usage: terraform refresh [options] [dir] Options: + -backup=path Path to backup the existing state file before + modifying. Defaults to the "-state-out" path with + ".backup" extention. Set to "-" to disable backup. + -no-color If specified, output won't contain any color. -state=path Path to read and save state (unless state-out From f26f432f4c388e57d142ee08aef4a0a500cfeaa0 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 27 Jul 2014 22:58:12 -0400 Subject: [PATCH 2/4] command/refresh: Testing backup option --- command/refresh.go | 2 +- command/refresh_test.go | 131 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 1 deletion(-) diff --git a/command/refresh.go b/command/refresh.go index 186df8e3e..f3038f96a 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -94,7 +94,7 @@ func (c *RefreshCommand) Run(args []string) int { } // Create a backup of the state before updating - if backupPath != "-" { + if backupPath != "-" && c.State != nil { log.Printf("[INFO] Writing backup state to: %s", backupPath) f, err := os.Create(backupPath) if err == nil { diff --git a/command/refresh_test.go b/command/refresh_test.go index 9525c68a9..ab345c169 100644 --- a/command/refresh_test.go +++ b/command/refresh_test.go @@ -221,6 +221,23 @@ func TestRefresh_defaultState(t *testing.T) { if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } + + f, err = os.Open(statePath + DefaultBackupExtention) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual = backupState.Resources["test_instance.foo"] + expected = originalState.Resources["test_instance.foo"] + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } } func TestRefresh_outPath(t *testing.T) { @@ -295,6 +312,21 @@ func TestRefresh_outPath(t *testing.T) { if !reflect.DeepEqual(actual, expected) { t.Fatalf("bad: %#v", actual) } + + f, err = os.Open(outPath + DefaultBackupExtention) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(backupState, state) { + t.Fatalf("bad: %#v", backupState) + } } func TestRefresh_var(t *testing.T) { @@ -376,6 +408,105 @@ func TestRefresh_varFile(t *testing.T) { } } +func TestRefresh_backup(t *testing.T) { + state := &terraform.State{ + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + ID: "bar", + Type: "test_instance", + }, + }, + } + statePath := testStateFile(t, state) + + // Output path + outf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + outPath := outf.Name() + outf.Close() + os.Remove(outPath) + + // Backup path + backupf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + backupPath := backupf.Name() + backupf.Close() + os.Remove(backupPath) + + p := testProvider() + ui := new(cli.MockUi) + c := &RefreshCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + p.RefreshFn = nil + p.RefreshReturn = &terraform.ResourceState{ID: "yes"} + + args := []string{ + "-state", statePath, + "-state-out", outPath, + "-backup", backupPath, + testFixturePath("refresh"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + f, err := os.Open(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + + newState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(newState, state) { + t.Fatalf("bad: %#v", newState) + } + + f, err = os.Open(outPath) + if err != nil { + t.Fatalf("err: %s", err) + } + + newState, err = terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := newState.Resources["test_instance.foo"] + expected := p.RefreshReturn + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } + + f, err = os.Open(backupPath) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(backupState, state) { + t.Fatalf("bad: %#v", backupState) + } +} + const refreshVarFile = ` foo = "bar" ` From 16ef3f57334a04e8b4945d6feaac1d86f577a3a0 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 27 Jul 2014 23:38:41 -0400 Subject: [PATCH 3/4] command: Testing the -backup feature --- command/apply.go | 2 +- command/apply_test.go | 189 ++++++++++++++++++++++++++++++++++++++++ command/plan.go | 2 +- command/plan_test.go | 145 ++++++++++++++++++++++++++++++ command/refresh_test.go | 81 +++++++++++++++++ 5 files changed, 417 insertions(+), 2 deletions(-) diff --git a/command/apply.go b/command/apply.go index 00ea50504..941d1db88 100644 --- a/command/apply.go +++ b/command/apply.go @@ -78,7 +78,7 @@ func (c *ApplyCommand) Run(args []string) int { } // Create a backup of the state before updating - if backupPath != "-" { + if backupPath != "-" && c.State != nil { log.Printf("[INFO] Writing backup state to: %s", backupPath) f, err := os.Create(backupPath) if err == nil { diff --git a/command/apply_test.go b/command/apply_test.go index ab714172d..7b8289ea2 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -358,6 +358,22 @@ func TestApply_refresh(t *testing.T) { if state == nil { t.Fatal("state should not be nil") } + + // Should have a backup file + f, err = os.Open(statePath + DefaultBackupExtention) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(backupState, originalState) { + t.Fatalf("bad: %#v", backupState) + } } func TestApply_shutdown(t *testing.T) { @@ -517,6 +533,25 @@ func TestApply_state(t *testing.T) { if state == nil { t.Fatal("state should not be nil") } + + // Should have a backup file + f, err = os.Open(statePath + DefaultBackupExtention) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + // nil out the ConnInfo since that should not be restored + originalState.Resources["test_instance.foo"].ConnInfo = nil + + if !reflect.DeepEqual(backupState, originalState) { + t.Fatalf("bad: %#v", backupState) + } } func TestApply_stateNoExist(t *testing.T) { @@ -617,6 +652,160 @@ func TestApply_varFile(t *testing.T) { } } +func TestApply_backup(t *testing.T) { + originalState := &terraform.State{ + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + ID: "bar", + Type: "test_instance", + }, + }, + } + + statePath := testStateFile(t, originalState) + backupPath := testTempFile(t) + + p := testProvider() + p.DiffReturn = &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "ami": &terraform.ResourceAttrDiff{ + New: "bar", + }, + }, + } + + ui := new(cli.MockUi) + c := &ApplyCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + // Run the apply command pointing to our existing state + args := []string{ + "-state", statePath, + "-backup", backupPath, + testFixturePath("apply"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Verify a new state exists + if _, err := os.Stat(statePath); err != nil { + t.Fatalf("err: %s", err) + } + + f, err := os.Open(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + state, err := terraform.ReadState(f) + if err != nil { + t.Fatalf("err: %s", err) + } + if state == nil { + t.Fatal("state should not be nil") + } + + // Should have a backup file + f, err = os.Open(backupPath) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := backupState.Resources["test_instance.foo"] + expected := originalState.Resources["test_instance.foo"] + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v %#v", actual, expected) + } +} + +func TestApply_disableBackup(t *testing.T) { + originalState := &terraform.State{ + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + ID: "bar", + Type: "test_instance", + ConnInfo: make(map[string]string), + }, + }, + } + + statePath := testStateFile(t, originalState) + + p := testProvider() + p.DiffReturn = &terraform.ResourceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "ami": &terraform.ResourceAttrDiff{ + New: "bar", + }, + }, + } + + ui := new(cli.MockUi) + c := &ApplyCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + // Run the apply command pointing to our existing state + args := []string{ + "-state", statePath, + "-backup", "-", + testFixturePath("apply"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Verify that the provider was called with the existing state + expectedState := originalState.Resources["test_instance.foo"] + if !reflect.DeepEqual(p.DiffState, expectedState) { + t.Fatalf("bad: %#v", p.DiffState) + } + + if !reflect.DeepEqual(p.ApplyState, expectedState) { + t.Fatalf("bad: %#v", p.ApplyState) + } + + // Verify a new state exists + if _, err := os.Stat(statePath); err != nil { + t.Fatalf("err: %s", err) + } + + f, err := os.Open(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + defer f.Close() + + state, err := terraform.ReadState(f) + if err != nil { + t.Fatalf("err: %s", err) + } + if state == nil { + t.Fatal("state should not be nil") + } + + // Ensure there is no backup + _, err = os.Stat(statePath + DefaultBackupExtention) + if err == nil || !os.IsNotExist(err) { + t.Fatalf("backup should not exist") + } +} + const applyVarFile = ` foo = "bar" ` diff --git a/command/plan.go b/command/plan.go index 2a5abf3c3..32ee31527 100644 --- a/command/plan.go +++ b/command/plan.go @@ -76,7 +76,7 @@ func (c *PlanCommand) Run(args []string) int { if refresh { // Create a backup of the state before updating - if backupPath != "-" { + if backupPath != "-" && c.State != nil { log.Printf("[INFO] Writing backup state to: %s", backupPath) f, err := os.Create(backupPath) if err == nil { diff --git a/command/plan_test.go b/command/plan_test.go index 7a2f171d9..d02dba1b2 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -78,6 +78,21 @@ func TestPlan_destroy(t *testing.T) { t.Fatalf("bad: %#v", r) } } + + f, err := os.Open(statePath + DefaultBackupExtention) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(backupState, originalState) { + t.Fatalf("bad: %#v", backupState) + } } func TestPlan_noState(t *testing.T) { p := testProvider() @@ -355,6 +370,136 @@ func TestPlan_varFile(t *testing.T) { } } +func TestPlan_backup(t *testing.T) { + // Write out some prior state + tf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + statePath := tf.Name() + defer os.Remove(tf.Name()) + + // Write out some prior state + backupf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + backupPath := backupf.Name() + backupf.Close() + os.Remove(backupPath) + defer os.Remove(backupPath) + + originalState := &terraform.State{ + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + ID: "bar", + Type: "test_instance", + }, + }, + } + + err = terraform.WriteState(originalState, tf) + tf.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + p := testProvider() + ui := new(cli.MockUi) + c := &PlanCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "-backup", backupPath, + testFixturePath("plan"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Verify that the provider was called with the existing state + expectedState := originalState.Resources["test_instance.foo"] + if !reflect.DeepEqual(p.DiffState, expectedState) { + t.Fatalf("bad: %#v", p.DiffState) + } + + // Verify the backup exist + f, err := os.Open(backupPath) + if err != nil { + t.Fatalf("err: %s", err) + } + + backupState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(backupState, originalState) { + t.Fatalf("bad: %#v", backupState) + } +} + +func TestPlan_disableBackup(t *testing.T) { + // Write out some prior state + tf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + statePath := tf.Name() + defer os.Remove(tf.Name()) + + originalState := &terraform.State{ + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + ID: "bar", + Type: "test_instance", + }, + }, + } + + err = terraform.WriteState(originalState, tf) + tf.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + p := testProvider() + ui := new(cli.MockUi) + c := &PlanCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "-backup", "-", + testFixturePath("plan"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + // Verify that the provider was called with the existing state + expectedState := originalState.Resources["test_instance.foo"] + if !reflect.DeepEqual(p.DiffState, expectedState) { + t.Fatalf("bad: %#v", p.DiffState) + } + + // Ensure there is no backup + _, err = os.Stat(statePath + DefaultBackupExtention) + if err == nil || !os.IsNotExist(err) { + t.Fatalf("backup should not exist") + } +} + const planVarFile = ` foo = "bar" ` diff --git a/command/refresh_test.go b/command/refresh_test.go index ab345c169..976612eaf 100644 --- a/command/refresh_test.go +++ b/command/refresh_test.go @@ -507,6 +507,87 @@ func TestRefresh_backup(t *testing.T) { } } +func TestRefresh_disableBackup(t *testing.T) { + state := &terraform.State{ + Resources: map[string]*terraform.ResourceState{ + "test_instance.foo": &terraform.ResourceState{ + ID: "bar", + Type: "test_instance", + }, + }, + } + statePath := testStateFile(t, state) + + // Output path + outf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + outPath := outf.Name() + outf.Close() + os.Remove(outPath) + + p := testProvider() + ui := new(cli.MockUi) + c := &RefreshCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + p.RefreshFn = nil + p.RefreshReturn = &terraform.ResourceState{ID: "yes"} + + args := []string{ + "-state", statePath, + "-state-out", outPath, + "-backup", "-", + testFixturePath("refresh"), + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + f, err := os.Open(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + + newState, err := terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + if !reflect.DeepEqual(newState, state) { + t.Fatalf("bad: %#v", newState) + } + + f, err = os.Open(outPath) + if err != nil { + t.Fatalf("err: %s", err) + } + + newState, err = terraform.ReadState(f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + actual := newState.Resources["test_instance.foo"] + expected := p.RefreshReturn + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("bad: %#v", actual) + } + + // Ensure there is no backup + _, err = os.Stat(outPath + DefaultBackupExtention) + if err == nil || !os.IsNotExist(err) { + t.Fatalf("backup should not exist") + } +} + const refreshVarFile = ` foo = "bar" ` From 8fbcd65eb1853b8054078d09bf5e10bb3987f8a4 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 27 Jul 2014 23:40:18 -0400 Subject: [PATCH 4/4] website: Document the -backup flag --- website/source/docs/commands/apply.html.markdown | 3 +++ website/source/docs/commands/plan.html.markdown | 3 +++ website/source/docs/commands/refresh.html.markdown | 3 +++ 3 files changed, 9 insertions(+) diff --git a/website/source/docs/commands/apply.html.markdown b/website/source/docs/commands/apply.html.markdown index 909770b18..6338c4697 100644 --- a/website/source/docs/commands/apply.html.markdown +++ b/website/source/docs/commands/apply.html.markdown @@ -21,6 +21,9 @@ execute a pre-determined set of actions. The command-line flags are all optional. The list of available flags are: +* `-backup=path` - Path to the backup file. Defaults to `-state-out` with + the ".backup" extention. Disabled by setting to "-". + * `-no-color` - Disables output with coloring. * `-refresh=true` - Update the state for each resource prior to planning diff --git a/website/source/docs/commands/plan.html.markdown b/website/source/docs/commands/plan.html.markdown index 1435d97c3..165c8b9db 100644 --- a/website/source/docs/commands/plan.html.markdown +++ b/website/source/docs/commands/plan.html.markdown @@ -21,6 +21,9 @@ for the configuration and state file to refresh. The command-line flags are all optional. The list of available flags are: +* `-backup=path` - Path to the backup file. Defaults to `-state-out` with + the ".backup" extention. Disabled by setting to "-". + * `-destroy` - If set, generates a plan to destroy all the known resources. * `-no-color` - Disables output with coloring. diff --git a/website/source/docs/commands/refresh.html.markdown b/website/source/docs/commands/refresh.html.markdown index 06f42f7fd..a6858511c 100644 --- a/website/source/docs/commands/refresh.html.markdown +++ b/website/source/docs/commands/refresh.html.markdown @@ -24,6 +24,9 @@ for the configuration and state file to refresh. The command-line flags are all optional. The list of available flags are: +* `-backup=path` - Path to the backup file. Defaults to `-state-out` with + the ".backup" extention. Disabled by setting to "-". + * `-no-color` - Disables output with coloring * `-state=path` - Path to read and write the state file to. Defaults to "terraform.tfstate".