From 2be1f55cbb5996404cfd426039b9774a0b376cb2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 16 Mar 2017 12:05:11 -0700 Subject: [PATCH] backend/local: allow refresh on empty/non-existent state This allows a refresh on a non-existent or empty state file. We changed this in 0.9.0 to error which seemed reasonable but it turns out this complicates automation that runs refresh since it now needed to determine if the state file was empty before running. Its easier to just revert this into a warning with exit code zero. The reason this changed is because in 0.8.x and earlier, the output would be simply empty with exit code zero which seemed odd. --- backend/local/backend_refresh.go | 40 +++++++++------- command/refresh_test.go | 51 +++++++++++++-------- command/test-fixtures/refresh-empty/main.tf | 1 + 3 files changed, 57 insertions(+), 35 deletions(-) create mode 100644 command/test-fixtures/refresh-empty/main.tf diff --git a/backend/local/backend_refresh.go b/backend/local/backend_refresh.go index 1de9902d1..c8b23bd32 100644 --- a/backend/local/backend_refresh.go +++ b/backend/local/backend_refresh.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "strings" "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" @@ -22,24 +23,17 @@ func (b *Local) opRefresh( if b.Backend == nil { if _, err := os.Stat(b.StatePath); err != nil { if os.IsNotExist(err) { - runningOp.Err = fmt.Errorf( - "The Terraform state file for your infrastructure does not\n"+ - "exist. The 'refresh' command only works and only makes sense\n"+ - "when there is existing state that Terraform is managing. Please\n"+ - "double-check the value given below and try again. If you\n"+ - "haven't created infrastructure with Terraform yet, use the\n"+ - "'terraform apply' command.\n\n"+ - "Path: %s", - b.StatePath) - return + err = nil } - runningOp.Err = fmt.Errorf( - "There was an error reading the Terraform state that is needed\n"+ - "for refreshing. The path and error are shown below.\n\n"+ - "Path: %s\n\nError: %s", - b.StatePath, err) - return + if err != nil { + runningOp.Err = fmt.Errorf( + "There was an error reading the Terraform state that is needed\n"+ + "for refreshing. The path and error are shown below.\n\n"+ + "Path: %s\n\nError: %s", + b.StatePath, err) + return + } } } @@ -74,6 +68,12 @@ func (b *Local) opRefresh( // Set our state runningOp.State = opState.State() + if runningOp.State.Empty() || !runningOp.State.HasResources() { + if b.CLI != nil { + b.CLI.Output(b.Colorize().Color( + strings.TrimSpace(refreshNoState) + "\n")) + } + } // Perform operation and write the resulting state to the running op newState, err := tfCtx.Refresh() @@ -93,3 +93,11 @@ func (b *Local) opRefresh( return } } + +const refreshNoState = ` +[reset][bold][yellow]Empty or non-existent state file.[reset][yellow] + +Refresh will do nothing. Refresh does not error or return an erroneous +exit status because many automation scripts use refresh, plan, then apply +and may not have a state file yet for the first run. +` diff --git a/command/refresh_test.go b/command/refresh_test.go index 2defaff45..12241e809 100644 --- a/command/refresh_test.go +++ b/command/refresh_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/hashicorp/terraform/helper/copy" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" ) @@ -59,6 +60,37 @@ func TestRefresh(t *testing.T) { } } +func TestRefresh_empty(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("refresh-empty"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + p := testProvider() + ui := new(cli.MockUi) + c := &RefreshCommand{ + Meta: Meta{ + ContextOpts: testCtxConfig(p), + Ui: ui, + }, + } + + p.RefreshFn = nil + p.RefreshReturn = &terraform.InstanceState{ID: "yes"} + + args := []string{ + td, + } + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + if p.RefreshCalled { + t.Fatal("refresh should not be called") + } +} + func TestRefresh_lockedState(t *testing.T) { state := testState() statePath := testStateFile(t, state) @@ -96,25 +128,6 @@ func TestRefresh_lockedState(t *testing.T) { } } -func TestRefresh_badState(t *testing.T) { - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, - }, - } - - args := []string{ - "-state", "i-should-not-exist-ever", - testFixturePath("refresh"), - } - if code := c.Run(args); code != 1 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } -} - func TestRefresh_cwd(t *testing.T) { cwd, err := os.Getwd() if err != nil { diff --git a/command/test-fixtures/refresh-empty/main.tf b/command/test-fixtures/refresh-empty/main.tf new file mode 100644 index 000000000..fec56017d --- /dev/null +++ b/command/test-fixtures/refresh-empty/main.tf @@ -0,0 +1 @@ +# Hello