From 16ef3f57334a04e8b4945d6feaac1d86f577a3a0 Mon Sep 17 00:00:00 2001 From: Armon Dadgar Date: Sun, 27 Jul 2014 23:38:41 -0400 Subject: [PATCH] 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" `