command/state: lock when pushing state
Next to adding the locking for the `state push` command, this commit also fixes a small bug where the lock would not be propertly released when running the `state show` command. And finally it renames some variables in the `[un]taint` code in order to try to standardize the var names of a few frequently used variables (e.g. statemgr.Full, states.State, states.SyncState).
This commit is contained in:
parent
407fcc0385
commit
79a9a15879
|
@ -127,7 +127,7 @@ func TestApply_destroyLockedState(t *testing.T) {
|
||||||
})
|
})
|
||||||
statePath := testStateFile(t, originalState)
|
statePath := testStateFile(t, originalState)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState(testDataDir, statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ func TestApply(t *testing.T) {
|
||||||
func TestApply_lockedState(t *testing.T) {
|
func TestApply_lockedState(t *testing.T) {
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState(testDataDir, statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ func TestApply_lockedState(t *testing.T) {
|
||||||
func TestApply_lockedStateWait(t *testing.T) {
|
func TestApply_lockedStateWait(t *testing.T) {
|
||||||
statePath := testTempFile(t)
|
statePath := testTempFile(t)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState(testDataDir, statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,8 +38,11 @@ import (
|
||||||
backendInit "github.com/hashicorp/terraform/backend/init"
|
backendInit "github.com/hashicorp/terraform/backend/init"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is the directory where our test fixtures are.
|
// These are the directories for our test data and fixtures.
|
||||||
var fixtureDir = "./test-fixtures"
|
var (
|
||||||
|
fixtureDir = "./test-fixtures"
|
||||||
|
testDataDir = "./testdata"
|
||||||
|
)
|
||||||
|
|
||||||
// a top level temp directory which will be cleaned after all tests
|
// a top level temp directory which will be cleaned after all tests
|
||||||
var testingDir string
|
var testingDir string
|
||||||
|
@ -50,14 +53,19 @@ func init() {
|
||||||
// Initialize the backends
|
// Initialize the backends
|
||||||
backendInit.Init(nil)
|
backendInit.Init(nil)
|
||||||
|
|
||||||
// Expand the fixture dir on init because we change the working
|
// Expand the data and fixture dirs on init because
|
||||||
// directory in some tests.
|
// we change the working directory in some tests.
|
||||||
var err error
|
var err error
|
||||||
fixtureDir, err = filepath.Abs(fixtureDir)
|
fixtureDir, err = filepath.Abs(fixtureDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testDataDir, err = filepath.Abs(testDataDir)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
testingDir, err = ioutil.TempDir(testingDir, "tf")
|
testingDir, err = ioutil.TempDir(testingDir, "tf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
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.
|
// 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.
|
// 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.
|
// supplied to locate the statelocker.go source.
|
||||||
func testLockState(sourceDir, path string) (func(), error) {
|
func testLockState(sourceDir, path string) (func(), error) {
|
||||||
// build and run the binary ourselves so we can quickly terminate it for cleanup
|
// 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")
|
source := filepath.Join(sourceDir, "statelocker.go")
|
||||||
lockBin := filepath.Join(buildDir, "statelocker")
|
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 {
|
if err != nil {
|
||||||
cleanFunc()
|
cleanFunc()
|
||||||
return nil, fmt.Errorf("%s %s", err, out)
|
return nil, fmt.Errorf("%s %s", err, out)
|
||||||
|
|
|
@ -235,6 +235,7 @@ func (c *ImportCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure to unlock the state
|
||||||
defer func() {
|
defer func() {
|
||||||
err := opReq.StateLocker.Unlock(nil)
|
err := opReq.StateLocker.Unlock(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -56,7 +56,7 @@ func TestPlan_lockedState(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
testPath := testFixturePath("plan")
|
testPath := testFixturePath("plan")
|
||||||
unlock, err := testLockState("./testdata", filepath.Join(testPath, DefaultStateFilename))
|
unlock, err := testLockState(testDataDir, filepath.Join(testPath, DefaultStateFilename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,7 @@ func TestRefresh_lockedState(t *testing.T) {
|
||||||
state := testState()
|
state := testState()
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState(testDataDir, statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/command/clistate"
|
||||||
"github.com/hashicorp/terraform/states/statefile"
|
"github.com/hashicorp/terraform/states/statefile"
|
||||||
"github.com/hashicorp/terraform/states/statemgr"
|
"github.com/hashicorp/terraform/states/statemgr"
|
||||||
"github.com/mitchellh/cli"
|
"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))
|
c.Ui.Error(fmt.Sprintf("Failed to load destination state: %s", err))
|
||||||
return 1
|
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 {
|
if err := stateMgr.RefreshState(); err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to refresh destination state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -3,6 +3,7 @@ package command
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/backend"
|
"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) {
|
func TestStatePush_replaceMatch(t *testing.T) {
|
||||||
// Create a temporary working directory that is empty
|
// Create a temporary working directory that is empty
|
||||||
td := tempDir(t)
|
td := tempDir(t)
|
||||||
|
|
|
@ -79,6 +79,14 @@ func (c *StateShowCommand) Run(args []string) int {
|
||||||
return 1
|
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
|
// Get the schemas from the context
|
||||||
schemas := ctx.Schemas()
|
schemas := ctx.Schemas()
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
env := c.Workspace()
|
env := c.Workspace()
|
||||||
st, err := b.StateMgr(env)
|
stateMgr, err := b.StateMgr(env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -84,21 +84,21 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
|
|
||||||
if c.stateLock {
|
if c.stateLock {
|
||||||
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
|
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))
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer stateLocker.Unlock(nil)
|
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))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the actual state structure
|
// Get the actual state structure
|
||||||
s := st.State()
|
state := stateMgr.State()
|
||||||
if s.Empty() {
|
if state.Empty() {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(addr)
|
return c.allowMissingExit(addr)
|
||||||
}
|
}
|
||||||
|
@ -112,11 +112,11 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
state := s.SyncWrapper()
|
ss := state.SyncWrapper()
|
||||||
|
|
||||||
// Get the resource and instance we're going to taint
|
// Get the resource and instance we're going to taint
|
||||||
rs := state.Resource(addr.ContainingResource())
|
rs := ss.Resource(addr.ContainingResource())
|
||||||
is := state.ResourceInstance(addr)
|
is := ss.ResourceInstance(addr)
|
||||||
if is == nil {
|
if is == nil {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(addr)
|
return c.allowMissingExit(addr)
|
||||||
|
@ -152,13 +152,13 @@ func (c *TaintCommand) Run(args []string) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.Status = states.ObjectTainted
|
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))
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
||||||
return 1
|
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))
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ func TestTaint_lockedState(t *testing.T) {
|
||||||
})
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
|
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState(testDataDir, statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
|
|
||||||
// Get the state
|
// Get the state
|
||||||
workspace := c.Workspace()
|
workspace := c.Workspace()
|
||||||
st, err := b.StateMgr(workspace)
|
stateMgr, err := b.StateMgr(workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
|
@ -80,21 +80,21 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
|
|
||||||
if c.stateLock {
|
if c.stateLock {
|
||||||
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
|
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))
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
defer stateLocker.Unlock(nil)
|
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))
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the actual state structure
|
// Get the actual state structure
|
||||||
s := st.State()
|
state := stateMgr.State()
|
||||||
if s.Empty() {
|
if state.Empty() {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(addr)
|
return c.allowMissingExit(addr)
|
||||||
}
|
}
|
||||||
|
@ -108,11 +108,11 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
state := s.SyncWrapper()
|
ss := state.SyncWrapper()
|
||||||
|
|
||||||
// Get the resource and instance we're going to taint
|
// Get the resource and instance we're going to taint
|
||||||
rs := state.Resource(addr.ContainingResource())
|
rs := ss.Resource(addr.ContainingResource())
|
||||||
is := state.ResourceInstance(addr)
|
is := ss.ResourceInstance(addr)
|
||||||
if is == nil {
|
if is == nil {
|
||||||
if allowMissing {
|
if allowMissing {
|
||||||
return c.allowMissingExit(addr)
|
return c.allowMissingExit(addr)
|
||||||
|
@ -157,13 +157,13 @@ func (c *UntaintCommand) Run(args []string) int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
obj.Status = states.ObjectReady
|
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))
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
||||||
return 1
|
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))
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ func TestUntaint_lockedState(t *testing.T) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
statePath := testStateFile(t, state)
|
statePath := testStateFile(t, state)
|
||||||
unlock, err := testLockState("./testdata", statePath)
|
unlock, err := testLockState(testDataDir, statePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue