provide contexts to clistate.Lock calls

Add fields required to create an appropriate context for all calls to
clistate.Lock.

Add missing checks for Meta.stateLock, where we would attempt to lock,
even if locking should be skipped.
This commit is contained in:
James Bardin 2017-04-01 15:42:13 -04:00
parent 3f0dcd1308
commit 305ef43aa6
11 changed files with 145 additions and 80 deletions

View File

@ -7,6 +7,7 @@ package backend
import ( import (
"context" "context"
"errors" "errors"
"time"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
@ -132,6 +133,9 @@ type Operation struct {
// state.Lockers for its duration, and Unlock when complete. // state.Lockers for its duration, and Unlock when complete.
LockState bool LockState bool
// The duration to retry obtaining a State lock.
StateLockTimeout time.Duration
// Environment is the named state that should be loaded from the Backend. // Environment is the named state that should be loaded from the Backend.
Environment string Environment string
} }

View File

@ -52,9 +52,12 @@ func (b *Local) opApply(
} }
if op.LockState { if op.LockState {
lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String() lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize()) lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
if err != nil { if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return return

View File

@ -61,9 +61,12 @@ func (b *Local) opPlan(
} }
if op.LockState { if op.LockState {
lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String() lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize()) lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
if err != nil { if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return return

View File

@ -51,9 +51,12 @@ func (b *Local) opRefresh(
} }
if op.LockState { if op.LockState {
lockCtx, cancel := context.WithTimeout(ctx, op.StateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = op.Type.String() lockInfo.Operation = op.Type.String()
lockID, err := clistate.Lock(opState, lockInfo, b.CLI, b.Colorize()) lockID, err := clistate.Lock(lockCtx, opState, lockInfo, b.CLI, b.Colorize())
if err != nil { if err != nil {
runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err) runningOp.Err = errwrap.Wrapf("Error locking state: {{err}}", err)
return return

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
@ -92,15 +93,20 @@ func (c *EnvDeleteCommand) Run(args []string) int {
return 1 return 1
} }
if c.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
defer cancel()
// Lock the state if we can // Lock the state if we can
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "env delete" lockInfo.Operation = "env delete"
lockID, err := clistate.Lock(sMgr, lockInfo, c.Ui, c.Colorize()) lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
if err != nil { if 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 clistate.Unlock(sMgr, lockID, c.Ui, c.Colorize()) defer clistate.Unlock(sMgr, lockID, c.Ui, c.Colorize())
}
err = b.DeleteState(delEnv) err = b.DeleteState(delEnv)
if err != nil { if err != nil {

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -87,15 +88,20 @@ func (c *EnvNewCommand) Run(args []string) int {
return 1 return 1
} }
if c.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
defer cancel()
// Lock the state if we can // Lock the state if we can
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "env new" lockInfo.Operation = "env new"
lockID, err := clistate.Lock(sMgr, lockInfo, c.Ui, c.Colorize()) lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
if err != nil { if 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 clistate.Unlock(sMgr, lockID, c.Ui, c.Colorize()) defer clistate.Unlock(sMgr, lockID, c.Ui, c.Colorize())
}
// read the existing state file // read the existing state file
stateFile, err := os.Open(statePath) stateFile, err := os.Open(statePath)

View File

@ -90,6 +90,9 @@ type Meta struct {
// //
// stateLock is set to false to disable state locking // stateLock is set to false to disable state locking
// //
// stateLockTimeout is the optional duration to retry a state locks locks
// when it is already locked by another process.
//
// forceInitCopy suppresses confirmation for copying state data during // forceInitCopy suppresses confirmation for copying state data during
// init. // init.
statePath string statePath string
@ -99,6 +102,7 @@ type Meta struct {
shadow bool shadow bool
provider string provider string
stateLock bool stateLock bool
stateLockTimeout time.Duration
forceInitCopy bool forceInitCopy bool
} }

View File

@ -4,6 +4,7 @@ package command
// exported and private. // exported and private.
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -170,6 +171,7 @@ func (m *Meta) Operation() *backend.Operation {
Targets: m.targets, Targets: m.targets,
UIIn: m.UIInput(), UIIn: m.UIInput(),
Environment: m.Env(), Environment: m.Env(),
StateLockTimeout: m.stateLockTimeout,
} }
} }
@ -609,15 +611,20 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) {
return nil, fmt.Errorf("Error reading state: %s", err) return nil, fmt.Errorf("Error reading state: %s", err)
} }
if m.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
defer cancel()
// Lock the state if we can // Lock the state if we can
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "backend from plan" lockInfo.Operation = "backend from plan"
lockID, err := clistate.Lock(realMgr, lockInfo, m.Ui, m.Colorize()) lockID, err := clistate.Lock(lockCtx, realMgr, lockInfo, m.Ui, m.Colorize())
if err != nil { if err != nil {
return nil, fmt.Errorf("Error locking state: %s", err) return nil, fmt.Errorf("Error locking state: %s", err)
} }
defer clistate.Unlock(realMgr, lockID, m.Ui, m.Colorize()) defer clistate.Unlock(realMgr, lockID, m.Ui, m.Colorize())
}
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)
@ -1024,15 +1031,20 @@ func (m *Meta) backend_C_r_s(
} }
} }
if m.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
defer cancel()
// Lock the state if we can // Lock the state if we can
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "backend from config" lockInfo.Operation = "backend from config"
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, m.Ui, m.Colorize())
if err != nil { if err != nil {
return nil, fmt.Errorf("Error locking state: %s", err) return nil, fmt.Errorf("Error locking state: %s", err)
} }
defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize()) defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize())
}
// Store the metadata in our saved state location // Store the metadata in our saved state location
s := sMgr.State() s := sMgr.State()
@ -1116,15 +1128,20 @@ func (m *Meta) backend_C_r_S_changed(
} }
} }
if m.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
defer cancel()
// Lock the state if we can // Lock the state if we can
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "backend from config" lockInfo.Operation = "backend from config"
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, m.Ui, m.Colorize())
if err != nil { if err != nil {
return nil, fmt.Errorf("Error locking state: %s", err) return nil, fmt.Errorf("Error locking state: %s", err)
} }
defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize()) defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize())
}
// Update the backend state // Update the backend state
s = sMgr.State() s = sMgr.State()
@ -1272,15 +1289,20 @@ func (m *Meta) backend_C_R_S_unchanged(
} }
} }
if m.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
defer cancel()
// Lock the state if we can // Lock the state if we can
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "backend from config" lockInfo.Operation = "backend from config"
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, m.Ui, m.Colorize())
if err != nil { if err != nil {
return nil, fmt.Errorf("Error locking state: %s", err) return nil, fmt.Errorf("Error locking state: %s", err)
} }
defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize()) defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize())
}
// Unset the remote state // Unset the remote state
s = sMgr.State() s = sMgr.State()

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -217,11 +218,15 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
errMigrateSingleLoadDefault), opts.TwoType, err) errMigrateSingleLoadDefault), opts.TwoType, err)
} }
if m.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
defer cancel()
lockInfoOne := state.NewLockInfo() lockInfoOne := state.NewLockInfo()
lockInfoOne.Operation = "migration" lockInfoOne.Operation = "migration"
lockInfoOne.Info = "source state" lockInfoOne.Info = "source state"
lockIDOne, err := clistate.Lock(stateOne, lockInfoOne, m.Ui, m.Colorize()) lockIDOne, err := clistate.Lock(lockCtx, stateOne, lockInfoOne, m.Ui, m.Colorize())
if err != nil { if err != nil {
return fmt.Errorf("Error locking source state: %s", err) return fmt.Errorf("Error locking source state: %s", err)
} }
@ -231,11 +236,12 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
lockInfoTwo.Operation = "migration" lockInfoTwo.Operation = "migration"
lockInfoTwo.Info = "destination state" lockInfoTwo.Info = "destination state"
lockIDTwo, err := clistate.Lock(stateTwo, lockInfoTwo, m.Ui, m.Colorize()) lockIDTwo, err := clistate.Lock(lockCtx, stateTwo, lockInfoTwo, m.Ui, m.Colorize())
if err != nil { if err != nil {
return fmt.Errorf("Error locking destination state: %s", err) return fmt.Errorf("Error locking destination state: %s", err)
} }
defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize()) defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
}
one := stateOne.State() one := stateOne.State()
two := stateTwo.State() two := stateTwo.State()

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"strings" "strings"
@ -78,10 +79,13 @@ func (c *TaintCommand) Run(args []string) int {
return 1 return 1
} }
if c.Meta.stateLock { if c.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "taint" lockInfo.Operation = "taint"
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize()) lockID, err := clistate.Lock(lockCtx, st, lockInfo, c.Ui, c.Colorize())
if err != nil { if 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

View File

@ -1,6 +1,7 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"strings" "strings"
@ -66,10 +67,13 @@ func (c *UntaintCommand) Run(args []string) int {
return 1 return 1
} }
if c.Meta.stateLock { if c.stateLock {
lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
defer cancel()
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "untaint" lockInfo.Operation = "untaint"
lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize()) lockID, err := clistate.Lock(lockCtx, st, lockInfo, c.Ui, c.Colorize())
if err != nil { if 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