Merge pull request #19411 from hashicorp/svh/f-state-push

command/state: lock when pushing state
This commit is contained in:
Sander van Harmelen 2018-11-20 16:26:08 +01:00 committed by GitHub
commit d58d10e204
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 99 additions and 35 deletions

View File

@ -127,7 +127,7 @@ func TestApply_destroyLockedState(t *testing.T) {
})
statePath := testStateFile(t, originalState)
unlock, err := testLockState("./testdata", statePath)
unlock, err := testLockState(testDataDir, statePath)
if err != nil {
t.Fatal(err)
}

View File

@ -64,7 +64,7 @@ func TestApply(t *testing.T) {
func TestApply_lockedState(t *testing.T) {
statePath := testTempFile(t)
unlock, err := testLockState("./testdata", statePath)
unlock, err := testLockState(testDataDir, statePath)
if err != nil {
t.Fatal(err)
}
@ -98,7 +98,7 @@ func TestApply_lockedState(t *testing.T) {
func TestApply_lockedStateWait(t *testing.T) {
statePath := testTempFile(t)
unlock, err := testLockState("./testdata", statePath)
unlock, err := testLockState(testDataDir, statePath)
if err != nil {
t.Fatal(err)
}

View File

@ -38,8 +38,11 @@ import (
backendInit "github.com/hashicorp/terraform/backend/init"
)
// This is the directory where our test fixtures are.
var fixtureDir = "./test-fixtures"
// These are the directories for our test data and fixtures.
var (
fixtureDir = "./test-fixtures"
testDataDir = "./testdata"
)
// a top level temp directory which will be cleaned after all tests
var testingDir string
@ -50,14 +53,19 @@ func init() {
// Initialize the backends
backendInit.Init(nil)
// Expand the fixture dir on init because we change the working
// directory in some tests.
// Expand the data and fixture dirs on init because
// we change the working directory in some tests.
var err error
fixtureDir, err = filepath.Abs(fixtureDir)
if err != nil {
panic(err)
}
testDataDir, err = filepath.Abs(testDataDir)
if err != nil {
panic(err)
}
testingDir, err = ioutil.TempDir(testingDir, "tf")
if err != nil {
panic(err)
@ -783,7 +791,7 @@ func testRemoteState(t *testing.T, s *states.State, c int) (*terraform.State, *h
// testlockState calls a separate process to the lock the state file at path.
// deferFunc should be called in the caller to properly unlock the file.
// Since many tests change the working durectory, the sourcedir argument must be
// Since many tests change the working directory, the sourcedir argument must be
// supplied to locate the statelocker.go source.
func testLockState(sourceDir, path string) (func(), error) {
// build and run the binary ourselves so we can quickly terminate it for cleanup
@ -798,7 +806,10 @@ func testLockState(sourceDir, path string) (func(), error) {
source := filepath.Join(sourceDir, "statelocker.go")
lockBin := filepath.Join(buildDir, "statelocker")
out, err := exec.Command("go", "build", "-mod=vendor", "-o", lockBin, source).CombinedOutput()
cmd := exec.Command("go", "build", "-mod=vendor", "-o", lockBin, source)
cmd.Dir = filepath.Dir(sourceDir)
out, err := cmd.CombinedOutput()
if err != nil {
cleanFunc()
return nil, fmt.Errorf("%s %s", err, out)

View File

@ -235,6 +235,7 @@ func (c *ImportCommand) Run(args []string) int {
return 1
}
// Make sure to unlock the state
defer func() {
err := opReq.StateLocker.Unlock(nil)
if err != nil {

View File

@ -56,7 +56,7 @@ func TestPlan_lockedState(t *testing.T) {
}
testPath := testFixturePath("plan")
unlock, err := testLockState("./testdata", filepath.Join(testPath, DefaultStateFilename))
unlock, err := testLockState(testDataDir, filepath.Join(testPath, DefaultStateFilename))
if err != nil {
t.Fatal(err)
}

View File

@ -115,7 +115,7 @@ func TestRefresh_lockedState(t *testing.T) {
state := testState()
statePath := testStateFile(t, state)
unlock, err := testLockState("./testdata", statePath)
unlock, err := testLockState(testDataDir, statePath)
if err != nil {
t.Fatal(err)
}

View File

@ -1,11 +1,13 @@
package command
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/mitchellh/cli"
@ -77,6 +79,16 @@ func (c *StatePushCommand) Run(args []string) int {
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
return 1
}
if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
if err := stateLocker.Lock(stateMgr, "taint"); err != nil {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
return 1
}
defer stateLocker.Unlock(nil)
}
if err := stateMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err))
return 1

View File

@ -3,6 +3,7 @@ package command
import (
"bytes"
"os"
"strings"
"testing"
"github.com/hashicorp/terraform/backend"
@ -41,6 +42,37 @@ func TestStatePush_empty(t *testing.T) {
}
}
func TestStatePush_lockedState(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)
copy.CopyDir(testFixturePath("state-push-good"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
p := testProvider()
ui := new(cli.MockUi)
c := &StatePushCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
unlock, err := testLockState(testDataDir, "local-state.tfstate")
if err != nil {
t.Fatal(err)
}
defer unlock()
args := []string{"replace.tfstate"}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d", code)
}
if !strings.Contains(ui.ErrorWriter.String(), "Error acquiring the state lock") {
t.Fatalf("expected a lock error, got: %s", ui.ErrorWriter.String())
}
}
func TestStatePush_replaceMatch(t *testing.T) {
// Create a temporary working directory that is empty
td := tempDir(t)

View File

@ -79,6 +79,14 @@ func (c *StateShowCommand) Run(args []string) int {
return 1
}
// Make sure to unlock the state
defer func() {
err := opReq.StateLocker.Unlock(nil)
if err != nil {
c.Ui.Error(err.Error())
}
}()
// Get the schemas from the context
schemas := ctx.Schemas()

View File

@ -76,7 +76,7 @@ func (c *TaintCommand) Run(args []string) int {
// Get the state
env := c.Workspace()
st, err := b.StateMgr(env)
stateMgr, err := b.StateMgr(env)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -84,21 +84,21 @@ func (c *TaintCommand) Run(args []string) int {
if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
if err := stateLocker.Lock(st, "taint"); err != nil {
if err := stateLocker.Lock(stateMgr, "taint"); err != nil {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
return 1
}
defer stateLocker.Unlock(nil)
}
if err := st.RefreshState(); err != nil {
if err := stateMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
// Get the actual state structure
s := st.State()
if s.Empty() {
state := stateMgr.State()
if state.Empty() {
if allowMissing {
return c.allowMissingExit(addr)
}
@ -112,11 +112,11 @@ func (c *TaintCommand) Run(args []string) int {
return 1
}
state := s.SyncWrapper()
ss := state.SyncWrapper()
// Get the resource and instance we're going to taint
rs := state.Resource(addr.ContainingResource())
is := state.ResourceInstance(addr)
rs := ss.Resource(addr.ContainingResource())
is := ss.ResourceInstance(addr)
if is == nil {
if allowMissing {
return c.allowMissingExit(addr)
@ -152,13 +152,13 @@ func (c *TaintCommand) Run(args []string) int {
}
obj.Status = states.ObjectTainted
state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
if err := st.WriteState(s); err != nil {
if err := stateMgr.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}
if err := st.PersistState(); err != nil {
if err := stateMgr.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}

View File

@ -64,7 +64,7 @@ func TestTaint_lockedState(t *testing.T) {
})
statePath := testStateFile(t, state)
unlock, err := testLockState("./testdata", statePath)
unlock, err := testLockState(testDataDir, statePath)
if err != nil {
t.Fatal(err)
}

View File

@ -72,7 +72,7 @@ func (c *UntaintCommand) Run(args []string) int {
// Get the state
workspace := c.Workspace()
st, err := b.StateMgr(workspace)
stateMgr, err := b.StateMgr(workspace)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
@ -80,21 +80,21 @@ func (c *UntaintCommand) Run(args []string) int {
if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
if err := stateLocker.Lock(st, "untaint"); err != nil {
if err := stateLocker.Lock(stateMgr, "untaint"); err != nil {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
return 1
}
defer stateLocker.Unlock(nil)
}
if err := st.RefreshState(); err != nil {
if err := stateMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
// Get the actual state structure
s := st.State()
if s.Empty() {
state := stateMgr.State()
if state.Empty() {
if allowMissing {
return c.allowMissingExit(addr)
}
@ -108,11 +108,11 @@ func (c *UntaintCommand) Run(args []string) int {
return 1
}
state := s.SyncWrapper()
ss := state.SyncWrapper()
// Get the resource and instance we're going to taint
rs := state.Resource(addr.ContainingResource())
is := state.ResourceInstance(addr)
rs := ss.Resource(addr.ContainingResource())
is := ss.ResourceInstance(addr)
if is == nil {
if allowMissing {
return c.allowMissingExit(addr)
@ -157,13 +157,13 @@ func (c *UntaintCommand) Run(args []string) int {
return 1
}
obj.Status = states.ObjectReady
state.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
if err := st.WriteState(s); err != nil {
if err := stateMgr.WriteState(state); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}
if err := st.PersistState(); err != nil {
if err := stateMgr.PersistState(); err != nil {
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}

View File

@ -67,7 +67,7 @@ func TestUntaint_lockedState(t *testing.T) {
)
})
statePath := testStateFile(t, state)
unlock, err := testLockState("./testdata", statePath)
unlock, err := testLockState(testDataDir, statePath)
if err != nil {
t.Fatal(err)
}