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.
This commit is contained in:
Mitchell Hashimoto 2017-03-16 12:05:11 -07:00
parent ead7a3758b
commit 2be1f55cbb
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
3 changed files with 57 additions and 35 deletions

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"strings"
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
@ -22,24 +23,17 @@ func (b *Local) opRefresh(
if b.Backend == nil { if b.Backend == nil {
if _, err := os.Stat(b.StatePath); err != nil { if _, err := os.Stat(b.StatePath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
runningOp.Err = fmt.Errorf( err = nil
"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
} }
runningOp.Err = fmt.Errorf( if err != nil {
"There was an error reading the Terraform state that is needed\n"+ runningOp.Err = fmt.Errorf(
"for refreshing. The path and error are shown below.\n\n"+ "There was an error reading the Terraform state that is needed\n"+
"Path: %s\n\nError: %s", "for refreshing. The path and error are shown below.\n\n"+
b.StatePath, err) "Path: %s\n\nError: %s",
return b.StatePath, err)
return
}
} }
} }
@ -74,6 +68,12 @@ func (b *Local) opRefresh(
// Set our state // Set our state
runningOp.State = opState.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 // Perform operation and write the resulting state to the running op
newState, err := tfCtx.Refresh() newState, err := tfCtx.Refresh()
@ -93,3 +93,11 @@ func (b *Local) opRefresh(
return 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.
`

View File

@ -9,6 +9,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/hashicorp/terraform/helper/copy"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "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) { func TestRefresh_lockedState(t *testing.T) {
state := testState() state := testState()
statePath := testStateFile(t, state) 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) { func TestRefresh_cwd(t *testing.T) {
cwd, err := os.Getwd() cwd, err := os.Getwd()
if err != nil { if err != nil {

View File

@ -0,0 +1 @@
# Hello