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