Have backend operations properly unlock state

Make sure unlock is called with the correct LockID during operations
This commit is contained in:
James Bardin 2017-02-15 11:53:19 -05:00
parent 08cff7cc13
commit f2e496a14c
9 changed files with 57 additions and 23 deletions

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
)
@ -35,10 +36,17 @@ func (b *Local) opApply(
return
}
// If we're locking state, unlock when we're done
if op.LockState {
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return
}
defer func() {
if err := clistate.Unlock(opState, "", b.CLI, b.Colorize()); err != nil {
if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil {
runningOp.Err = multierror.Append(runningOp.Err, err)
}
}()

View File

@ -8,7 +8,6 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
)
@ -29,15 +28,6 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
}
if op.LockState {
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
_, err := clistate.Lock(s, lockInfo, b.CLI, b.Colorize())
if err != nil {
return nil, nil, errwrap.Wrapf("Error locking state: {{err}}", err)
}
}
if err := s.RefreshState(); err != nil {
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
}

View File

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/terraform/command/format"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
)
@ -59,10 +60,17 @@ func (b *Local) opPlan(
return
}
// If we're locking state, unlock when we're done
if op.LockState {
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return
}
defer func() {
if err := clistate.Unlock(opState, "", b.CLI, b.Colorize()); err != nil {
if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil {
runningOp.Err = multierror.Append(runningOp.Err, err)
}
}()

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/backend"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/state"
)
func (b *Local) opRefresh(
@ -48,10 +49,17 @@ func (b *Local) opRefresh(
return
}
// If we're locking state, unlock when we're done
if op.LockState {
lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize())
if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return
}
defer func() {
if err := clistate.Unlock(opState, "", b.CLI, b.Colorize()); err != nil {
if err := clistate.Unlock(opState, lockID, b.CLI, b.Colorize()); err != nil {
runningOp.Err = multierror.Append(runningOp.Err, err)
}
}()

View File

@ -45,6 +45,7 @@ func TestApply_destroy(t *testing.T) {
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
t.Log(ui.OutputWriter.String())
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

View File

@ -583,7 +583,8 @@ func testLockState(sourceDir, path string) (func(), error) {
return deferFunc, fmt.Errorf("read from statelocker returned: %s", err)
}
if string(buf[:n]) != "LOCKED" {
output := string(buf[:n])
if !strings.HasPrefix(output, "LOCKID") {
return deferFunc, fmt.Errorf("statelocker wrote: %s", string(buf[:n]))
}
return deferFunc, nil

View File

@ -34,7 +34,7 @@ func main() {
}
// signal to the caller that we're locked
io.WriteString(os.Stdout, "LOCKED")
io.WriteString(os.Stdout, "LOCKID "+lockID)
defer func() {
if err := s.Unlock(lockID); err != nil {

View File

@ -6,6 +6,7 @@ import (
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
// UnlockCommand is a cli.Command implementation that manually unlocks
@ -25,9 +26,21 @@ func (c *UnlockCommand) Run(args []string) int {
return 1
}
args = cmdFlags.Args()
if len(args) == 0 {
c.Ui.Error("unlock requires a lock id argument")
return cli.RunResultHelp
}
lockID := args[0]
if len(args) > 1 {
args = args[1:]
}
// assume everything is initialized. The user can manually init if this is
// required.
configPath, err := ModulePath(cmdFlags.Args())
configPath, err := ModulePath(args)
if err != nil {
c.Ui.Error(err.Error())
return 1
@ -93,7 +106,7 @@ func (c *UnlockCommand) Run(args []string) int {
}
// FIXME: unlock should require the lock ID
if err := s.Unlock(""); err != nil {
if err := s.Unlock(lockID); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err))
return 1
}
@ -104,7 +117,7 @@ func (c *UnlockCommand) Run(args []string) int {
func (c *UnlockCommand) Help() string {
helpText := `
Usage: terraform force-unlock [DIR]
Usage: terraform force-unlock LOCK_ID [DIR]
Manually unlock the state for the defined configuration.

View File

@ -40,7 +40,12 @@ func TestUnlock(t *testing.T) {
},
}
if code := c.Run([]string{"-force"}); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
args := []string{
"-force",
"LOCK_ID",
}
if code := c.Run(args); code != 1 {
t.Fatalf("bad: %d\n%s\n%s", code, ui.OutputWriter.String(), ui.ErrorWriter.String())
}
}