diff --git a/command/meta_backend.go b/command/meta_backend.go index 3a80f146b..6a07e0724 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -530,6 +530,13 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) { if err != nil { return nil, fmt.Errorf("Error reading state: %s", err) } + + unlock, err := lockState(realMgr, "backend from plan") + if err != nil { + return nil, err + } + defer unlock() + if err := realMgr.RefreshState(); err != nil { return nil, fmt.Errorf("Error reading state: %s", err) } @@ -574,6 +581,8 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) { newState.Remote = nil newState.Backend = nil } + + // realMgr locked above if err := realMgr.WriteState(newState); err != nil { return nil, fmt.Errorf("Error writing state: %s", err) } @@ -974,6 +983,12 @@ func (m *Meta) backend_C_r_s( } } + unlock, err := lockState(sMgr, "backend_C_r_s") + if err != nil { + return nil, err + } + defer unlock() + // Store the metadata in our saved state location s := sMgr.State() if s == nil { @@ -984,6 +999,7 @@ func (m *Meta) backend_C_r_s( Config: c.RawConfig.Raw, Hash: c.Hash, } + if err := sMgr.WriteState(s); err != nil { return nil, fmt.Errorf(errBackendWriteSaved, err) } @@ -1009,6 +1025,9 @@ func (m *Meta) backend_C_r_S_changed( strings.TrimSpace(outputBackendReconfigure)))) } + // Get the old state + s := sMgr.State() + // Get the backend b, err := m.backendInitFromConfig(c) if err != nil { @@ -1037,8 +1056,6 @@ func (m *Meta) backend_C_r_S_changed( "Error loading previously configured backend: %s", err) } - // Get the old state - s := sMgr.State() oldState, err := oldB.State() if err != nil { return nil, fmt.Errorf( @@ -1070,8 +1087,14 @@ func (m *Meta) backend_C_r_S_changed( } } + unlock, err := lockState(sMgr, "backend_C_r_S_changed") + if err != nil { + return nil, err + } + defer unlock() + // Update the backend state - s := sMgr.State() + s = sMgr.State() if s == nil { s = terraform.NewState() } @@ -1080,6 +1103,7 @@ func (m *Meta) backend_C_r_S_changed( Config: c.RawConfig.Raw, Hash: c.Hash, } + if err := sMgr.WriteState(s); err != nil { return nil, fmt.Errorf(errBackendWriteSaved, err) } @@ -1220,12 +1244,19 @@ func (m *Meta) backend_C_R_S_unchanged( } } + unlock, err := lockState(sMgr, "backend_C_R_S_unchanged") + if err != nil { + return nil, err + } + defer unlock() + // Unset the remote state s = sMgr.State() if s == nil { s = terraform.NewState() } s.Remote = nil + if err := sMgr.WriteState(s); err != nil { return nil, fmt.Errorf(strings.TrimSpace(errBackendClearLegacy), err) } @@ -1368,6 +1399,21 @@ func init() { backendlegacy.Init(Backends) } +// simple wrapper to check for a state.Locker and always provide an unlock +// function to defer. +func lockState(s state.State, info string) (func() error, error) { + l, ok := s.(state.Locker) + if !ok { + return func() error { return nil }, nil + } + + if err := l.Lock(info); err != nil { + return nil, err + } + + return l.Unlock, nil +} + // errBackendInitRequired is the final error message shown when reinit // is required for some reason. The error message includes the reason. var errBackendInitRequired = errors.New( diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index 0aaf0d3de..aaf730c15 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -20,7 +20,21 @@ import ( // // After migrating the state, the existing state in the first backend // remains untouched. +// +// This will attempt to lock both states for the migration. func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { + unlockOne, err := lockState(opts.One, "migrate from") + if err != nil { + return err + } + defer unlockOne() + + unlockTwo, err := lockState(opts.Two, "migrate to") + if err != nil { + return err + } + defer unlockTwo() + one := opts.One.State() two := opts.Two.State() diff --git a/command/meta_backend_test.go b/command/meta_backend_test.go index e73fa6b31..de0078164 100644 --- a/command/meta_backend_test.go +++ b/command/meta_backend_test.go @@ -354,6 +354,7 @@ func TestMetaBackend_configureNewWithState(t *testing.T) { if state == nil { t.Fatal("state is nil") } + if state.Lineage != "backend-new-migrate" { t.Fatalf("bad: %#v", state) } diff --git a/state/local.go b/state/local.go index 62454e257..1b7625f2c 100644 --- a/state/local.go +++ b/state/local.go @@ -97,6 +97,7 @@ func (s *LocalState) Unlock() error { fileName := s.stateFileOut.Name() unlockErr := s.unlock() + s.stateFileOut.Close() s.stateFileOut = nil @@ -201,6 +202,11 @@ func (s *LocalState) RefreshState() error { reader = f } } else { + // no state to refresh + if s.stateFileOut == nil { + return nil + } + // we have a state file, make sure we're at the start s.stateFileOut.Seek(0, os.SEEK_SET) reader = s.stateFileOut diff --git a/terraform/state.go b/terraform/state.go index 5dcce55d6..1965c0105 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -1808,7 +1808,9 @@ var ErrNoState = errors.New("no state") // was written by WriteState. func ReadState(src io.Reader) (*State, error) { buf := bufio.NewReader(src) - if _, err := buf.Peek(1); err == io.EOF { + if _, err := buf.Peek(1); err != nil { + // the error is either io.EOF or "invalid argument", and both are from + // an empty state. return nil, ErrNoState }