diff --git a/command/pull.go b/command/pull.go index 135d9419c..e5976f2a5 100644 --- a/command/pull.go +++ b/command/pull.go @@ -5,7 +5,7 @@ import ( "fmt" "strings" - "github.com/hashicorp/terraform/remote" + "github.com/hashicorp/terraform/state" ) type PullCommand struct { @@ -20,32 +20,50 @@ func (c *PullCommand) Run(args []string) int { return 1 } - // Recover the local state if any - local, _, err := remote.ReadLocalState() + // Read out our state + s, err := c.State() if err != nil { - c.Ui.Error(fmt.Sprintf("%s", err)) + c.Ui.Error(fmt.Sprintf("Failed to read state: %s", err)) return 1 } - if local == nil || local.Remote == nil { + localState := s.State() + + // If remote state isn't enabled, it is a problem. + if !localState.IsRemote() { c.Ui.Error("Remote state not enabled!") return 1 } - // Attempt the state refresh - change, err := remote.RefreshState(local.Remote) - if err != nil { + // We need the CacheState structure in order to do anything + var cache *state.CacheState + if bs, ok := s.(*state.BackupState); ok { + if cs, ok := bs.Real.(*state.CacheState); ok { + cache = cs + } + } + if cache == nil { c.Ui.Error(fmt.Sprintf( - "Failed to refresh from remote state: %v", err)) + "Failed to extract internal CacheState from remote state.\n" + + "This is an internal error, please report it as a bug.")) + return 1 + } + + // Refresh the state + if err := cache.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf( + "Failed to refresh from remote state: %s", err)) return 1 } // Use an error exit code if the update was not a success + change := cache.RefreshResult() if !change.SuccessfulPull() { c.Ui.Error(fmt.Sprintf("%s", change)) return 1 } else { c.Ui.Output(fmt.Sprintf("%s", change)) } + return 0 } diff --git a/command/pull_test.go b/command/pull_test.go index 28201b27c..d0bd08b86 100644 --- a/command/pull_test.go +++ b/command/pull_test.go @@ -7,9 +7,10 @@ import ( "encoding/json" "net/http" "net/http/httptest" + "os" + "path/filepath" "testing" - "github.com/hashicorp/terraform/remote" "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" ) @@ -46,10 +47,19 @@ func TestPull_local(t *testing.T) { defer srv.Close() // Store the local state - buf := bytes.NewBuffer(nil) - terraform.WriteState(s, buf) - remote.EnsureDirectory() - remote.Persist(buf) + statePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename) + if err := os.MkdirAll(filepath.Dir(statePath), 0755); err != nil { + t.Fatalf("err: %s", err) + } + f, err := os.Create(statePath) + if err != nil { + t.Fatalf("err: %s", err) + } + err = terraform.WriteState(s, f) + f.Close() + if err != nil { + t.Fatalf("err: %s", err) + } ui := new(cli.MockUi) c := &PullCommand{ diff --git a/command/state.go b/command/state.go index 5b033fab3..ecacfdd9a 100644 --- a/command/state.go +++ b/command/state.go @@ -70,10 +70,15 @@ func State(opts *StateOpts) (state.State, string, error) { } } else { if result != nil { - // We already have a remote state... that is an error. - return nil, "", fmt.Errorf( - "Remote state found, but state file '%s' also present.", - opts.LocalPath) + if !local.State().Empty() { + // We already have a remote state... that is an error. + return nil, "", fmt.Errorf( + "Remote state found, but state file '%s' also present.", + opts.LocalPath) + } + + // Empty state + local = nil } } if err != nil { @@ -81,10 +86,12 @@ func State(opts *StateOpts) (state.State, string, error) { "Error reading local state: {{err}}", err) } - result = local - resultPath = opts.LocalPath - if opts.LocalPathOut != "" { - resultPath = opts.LocalPathOut + if local != nil { + result = local + resultPath = opts.LocalPath + if opts.LocalPathOut != "" { + resultPath = opts.LocalPathOut + } } } diff --git a/state/cache.go b/state/cache.go index 386cc9f59..3322ab5ac 100644 --- a/state/cache.go +++ b/state/cache.go @@ -211,3 +211,23 @@ func (sc CacheRefreshResult) String() string { return fmt.Sprintf("Unknown state change type: %d", sc) } } + +// SuccessfulPull is used to clasify the CacheRefreshResult for +// a refresh operation. This is different by operation, but can be used +// to determine a proper exit code. +func (sc CacheRefreshResult) SuccessfulPull() bool { + switch sc { + case CacheRefreshNoop: + return true + case CacheRefreshInit: + return true + case CacheRefreshUpdateLocal: + return true + case CacheRefreshLocalNewer: + return false + case CacheRefreshConflict: + return false + default: + return false + } +}