Merge pull request #11836 from hashicorp/jbardin/state-locking
Add locking during backend configuration
This commit is contained in:
commit
4d00c29706
|
@ -530,6 +530,13 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error reading state: %s", err)
|
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 {
|
if err := realMgr.RefreshState(); err != nil {
|
||||||
return nil, fmt.Errorf("Error reading state: %s", err)
|
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.Remote = nil
|
||||||
newState.Backend = nil
|
newState.Backend = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// realMgr locked above
|
||||||
if err := realMgr.WriteState(newState); err != nil {
|
if err := realMgr.WriteState(newState); err != nil {
|
||||||
return nil, fmt.Errorf("Error writing state: %s", err)
|
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
|
// Store the metadata in our saved state location
|
||||||
s := sMgr.State()
|
s := sMgr.State()
|
||||||
if s == nil {
|
if s == nil {
|
||||||
|
@ -984,6 +999,7 @@ func (m *Meta) backend_C_r_s(
|
||||||
Config: c.RawConfig.Raw,
|
Config: c.RawConfig.Raw,
|
||||||
Hash: c.Hash,
|
Hash: c.Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sMgr.WriteState(s); err != nil {
|
if err := sMgr.WriteState(s); err != nil {
|
||||||
return nil, fmt.Errorf(errBackendWriteSaved, err)
|
return nil, fmt.Errorf(errBackendWriteSaved, err)
|
||||||
}
|
}
|
||||||
|
@ -1009,6 +1025,9 @@ func (m *Meta) backend_C_r_S_changed(
|
||||||
strings.TrimSpace(outputBackendReconfigure))))
|
strings.TrimSpace(outputBackendReconfigure))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the old state
|
||||||
|
s := sMgr.State()
|
||||||
|
|
||||||
// Get the backend
|
// Get the backend
|
||||||
b, err := m.backendInitFromConfig(c)
|
b, err := m.backendInitFromConfig(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1020,7 +1039,7 @@ func (m *Meta) backend_C_r_S_changed(
|
||||||
copy, err := m.confirm(&terraform.InputOpts{
|
copy, err := m.confirm(&terraform.InputOpts{
|
||||||
Id: "backend-migrate-to-new",
|
Id: "backend-migrate-to-new",
|
||||||
Query: fmt.Sprintf("Do you want to copy the state from %q?", c.Type),
|
Query: fmt.Sprintf("Do you want to copy the state from %q?", c.Type),
|
||||||
Description: strings.TrimSpace(inputBackendMigrateChange),
|
Description: strings.TrimSpace(fmt.Sprintf(inputBackendMigrateChange, c.Type, s.Backend.Type)),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
|
@ -1037,8 +1056,6 @@ func (m *Meta) backend_C_r_S_changed(
|
||||||
"Error loading previously configured backend: %s", err)
|
"Error loading previously configured backend: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the old state
|
|
||||||
s := sMgr.State()
|
|
||||||
oldState, err := oldB.State()
|
oldState, err := oldB.State()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
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
|
// Update the backend state
|
||||||
s := sMgr.State()
|
s = sMgr.State()
|
||||||
if s == nil {
|
if s == nil {
|
||||||
s = terraform.NewState()
|
s = terraform.NewState()
|
||||||
}
|
}
|
||||||
|
@ -1080,6 +1103,7 @@ func (m *Meta) backend_C_r_S_changed(
|
||||||
Config: c.RawConfig.Raw,
|
Config: c.RawConfig.Raw,
|
||||||
Hash: c.Hash,
|
Hash: c.Hash,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sMgr.WriteState(s); err != nil {
|
if err := sMgr.WriteState(s); err != nil {
|
||||||
return nil, fmt.Errorf(errBackendWriteSaved, err)
|
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
|
// Unset the remote state
|
||||||
s = sMgr.State()
|
s = sMgr.State()
|
||||||
if s == nil {
|
if s == nil {
|
||||||
s = terraform.NewState()
|
s = terraform.NewState()
|
||||||
}
|
}
|
||||||
s.Remote = nil
|
s.Remote = nil
|
||||||
|
|
||||||
if err := sMgr.WriteState(s); err != nil {
|
if err := sMgr.WriteState(s); err != nil {
|
||||||
return nil, fmt.Errorf(strings.TrimSpace(errBackendClearLegacy), err)
|
return nil, fmt.Errorf(strings.TrimSpace(errBackendClearLegacy), err)
|
||||||
}
|
}
|
||||||
|
@ -1368,6 +1399,21 @@ func init() {
|
||||||
backendlegacy.Init(Backends)
|
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
|
// errBackendInitRequired is the final error message shown when reinit
|
||||||
// is required for some reason. The error message includes the reason.
|
// is required for some reason. The error message includes the reason.
|
||||||
var errBackendInitRequired = errors.New(
|
var errBackendInitRequired = errors.New(
|
||||||
|
@ -1572,7 +1618,7 @@ Current Serial: %[2]d
|
||||||
|
|
||||||
const inputBackendMigrateChange = `
|
const inputBackendMigrateChange = `
|
||||||
Would you like to copy the state from your prior backend %q to the
|
Would you like to copy the state from your prior backend %q to the
|
||||||
newly configured backend %q? If you're reconfiguring the same backend,
|
newly configured %q backend? If you're reconfiguring the same backend,
|
||||||
answering "yes" or "no" shouldn't make a difference. Please answer exactly
|
answering "yes" or "no" shouldn't make a difference. Please answer exactly
|
||||||
"yes" or "no".
|
"yes" or "no".
|
||||||
`
|
`
|
||||||
|
|
|
@ -20,7 +20,21 @@ import (
|
||||||
//
|
//
|
||||||
// After migrating the state, the existing state in the first backend
|
// After migrating the state, the existing state in the first backend
|
||||||
// remains untouched.
|
// remains untouched.
|
||||||
|
//
|
||||||
|
// This will attempt to lock both states for the migration.
|
||||||
func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error {
|
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()
|
one := opts.One.State()
|
||||||
two := opts.Two.State()
|
two := opts.Two.State()
|
||||||
|
|
||||||
|
|
|
@ -354,6 +354,7 @@ func TestMetaBackend_configureNewWithState(t *testing.T) {
|
||||||
if state == nil {
|
if state == nil {
|
||||||
t.Fatal("state is nil")
|
t.Fatal("state is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.Lineage != "backend-new-migrate" {
|
if state.Lineage != "backend-new-migrate" {
|
||||||
t.Fatalf("bad: %#v", state)
|
t.Fatalf("bad: %#v", state)
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@ func (s *LocalState) Unlock() error {
|
||||||
fileName := s.stateFileOut.Name()
|
fileName := s.stateFileOut.Name()
|
||||||
|
|
||||||
unlockErr := s.unlock()
|
unlockErr := s.unlock()
|
||||||
|
|
||||||
s.stateFileOut.Close()
|
s.stateFileOut.Close()
|
||||||
s.stateFileOut = nil
|
s.stateFileOut = nil
|
||||||
|
|
||||||
|
@ -201,6 +202,11 @@ func (s *LocalState) RefreshState() error {
|
||||||
reader = f
|
reader = f
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// no state to refresh
|
||||||
|
if s.stateFileOut == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// we have a state file, make sure we're at the start
|
// we have a state file, make sure we're at the start
|
||||||
s.stateFileOut.Seek(0, os.SEEK_SET)
|
s.stateFileOut.Seek(0, os.SEEK_SET)
|
||||||
reader = s.stateFileOut
|
reader = s.stateFileOut
|
||||||
|
|
|
@ -236,7 +236,7 @@ func (c *S3Client) Lock(info string) error {
|
||||||
|
|
||||||
resp, err := c.dynClient.GetItem(getParams)
|
resp, err := c.dynClient.GetItem(getParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("s3 state file %q locked, failed to retrive info: %s", stateName, err)
|
return fmt.Errorf("s3 state file %q locked, failed to retrieve info: %s", stateName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var infoData string
|
var infoData string
|
||||||
|
|
|
@ -1808,7 +1808,9 @@ var ErrNoState = errors.New("no state")
|
||||||
// was written by WriteState.
|
// was written by WriteState.
|
||||||
func ReadState(src io.Reader) (*State, error) {
|
func ReadState(src io.Reader) (*State, error) {
|
||||||
buf := bufio.NewReader(src)
|
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
|
return nil, ErrNoState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -1577,6 +1578,20 @@ func TestReadStateNewVersion(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestReadStateEmptyOrNilFile(t *testing.T) {
|
||||||
|
var emptyState bytes.Buffer
|
||||||
|
_, err := ReadState(&emptyState)
|
||||||
|
if err != ErrNoState {
|
||||||
|
t.Fatal("expected ErrNostate, got", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nilFile *os.File
|
||||||
|
_, err = ReadState(nilFile)
|
||||||
|
if err != ErrNoState {
|
||||||
|
t.Fatal("expected ErrNostate, got", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestReadStateTFVersion(t *testing.T) {
|
func TestReadStateTFVersion(t *testing.T) {
|
||||||
type tfVersion struct {
|
type tfVersion struct {
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
|
|
Loading…
Reference in New Issue