enable local state locking for apply
Have the LocalBackend lock the state during operations, and enble this for the apply comand.
This commit is contained in:
parent
9acb86a182
commit
9cdba1f199
|
@ -22,7 +22,8 @@ type Backend interface {
|
|||
|
||||
// State returns the current state for this environment. This state may
|
||||
// not be loaded locally: the proper APIs should be called on state.State
|
||||
// to load the state.
|
||||
// to load the state. If the state.State is a state.Locker, it's up to the
|
||||
// caller to call Lock and Unlock as needed.
|
||||
State() (state.State, error)
|
||||
}
|
||||
|
||||
|
@ -38,6 +39,9 @@ type Enhanced interface {
|
|||
// It is up to the implementation to determine what "performing" means.
|
||||
// This DOES NOT BLOCK. The context returned as part of RunningOperation
|
||||
// should be used to block for completion.
|
||||
// If the state used in the operation can be locked, it is the
|
||||
// responsibility of the Backend to lock the state for the duration of the
|
||||
// running operation.
|
||||
Operation(context.Context, *Operation) (*RunningOperation, error)
|
||||
}
|
||||
|
||||
|
@ -99,6 +103,10 @@ type Operation struct {
|
|||
// Input/output/control options.
|
||||
UIIn terraform.UIInput
|
||||
UIOut terraform.UIOutput
|
||||
|
||||
// If LockState is true, the Operation must Lock any
|
||||
// state.Lockers for its duration, and Unlock when complete.
|
||||
LockState bool
|
||||
}
|
||||
|
||||
// RunningOperation is the result of starting an operation.
|
||||
|
|
|
@ -34,6 +34,9 @@ type Local struct {
|
|||
StateOutPath string
|
||||
StateBackupPath string
|
||||
|
||||
// we only want to create a single instance of the local state
|
||||
state state.State
|
||||
|
||||
// ContextOpts are the base context options to set when initializing a
|
||||
// Terraform context. Many of these will be overridden or merged by
|
||||
// Operation. See Operation for more details.
|
||||
|
@ -100,6 +103,10 @@ func (b *Local) State() (state.State, error) {
|
|||
return b.Backend.State()
|
||||
}
|
||||
|
||||
if b.state != nil {
|
||||
return b.state, nil
|
||||
}
|
||||
|
||||
// Otherwise, we need to load the state.
|
||||
var s state.State = &state.LocalState{
|
||||
Path: b.StatePath,
|
||||
|
@ -119,6 +126,7 @@ func (b *Local) State() (state.State, error) {
|
|||
}
|
||||
}
|
||||
|
||||
b.state = s
|
||||
return s, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -28,12 +29,22 @@ func (b *Local) opApply(
|
|||
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook, stateHook)
|
||||
|
||||
// Get our context
|
||||
tfCtx, state, err := b.context(op)
|
||||
tfCtx, opState, err := b.context(op)
|
||||
if err != nil {
|
||||
runningOp.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// context acquired the state, and therefor the lock.
|
||||
// Unlock it when the operation is complete
|
||||
defer func() {
|
||||
if s, ok := opState.(state.Locker); op.LockState && ok {
|
||||
if err := s.Unlock(); err != nil {
|
||||
log.Printf("[ERROR]: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Setup the state
|
||||
runningOp.State = tfCtx.State()
|
||||
|
||||
|
@ -58,7 +69,7 @@ func (b *Local) opApply(
|
|||
}
|
||||
|
||||
// Setup our hook for continuous state updates
|
||||
stateHook.State = state
|
||||
stateHook.State = opState
|
||||
|
||||
// Start the apply in a goroutine so that we can be interrupted.
|
||||
var applyState *terraform.State
|
||||
|
@ -98,11 +109,11 @@ func (b *Local) opApply(
|
|||
runningOp.State = applyState
|
||||
|
||||
// Persist the state
|
||||
if err := state.WriteState(applyState); err != nil {
|
||||
if err := opState.WriteState(applyState); err != nil {
|
||||
runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
|
||||
return
|
||||
}
|
||||
if err := state.PersistState(); err != nil {
|
||||
if err := opState.PersistState(); err != nil {
|
||||
runningOp.Err = fmt.Errorf("Failed to save state: %s", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,6 +23,13 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State,
|
|||
if err != nil {
|
||||
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
||||
}
|
||||
|
||||
if s, ok := s.(state.Locker); op.LockState && ok {
|
||||
if err := s.Lock(op.Type.String()); 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)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/command/format"
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -51,12 +52,22 @@ func (b *Local) opPlan(
|
|||
b.ContextOpts.Hooks = append(b.ContextOpts.Hooks, countHook)
|
||||
|
||||
// Get our context
|
||||
tfCtx, _, err := b.context(op)
|
||||
tfCtx, opState, err := b.context(op)
|
||||
if err != nil {
|
||||
runningOp.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// context acquired the state, and therefor the lock.
|
||||
// Unlock it when the operation is complete
|
||||
defer func() {
|
||||
if s, ok := opState.(state.Locker); op.LockState && ok {
|
||||
if err := s.Unlock(); err != nil {
|
||||
log.Printf("[ERROR]: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Setup the state
|
||||
runningOp.State = tfCtx.State()
|
||||
|
||||
|
|
|
@ -3,10 +3,12 @@ package local
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
)
|
||||
|
||||
func (b *Local) opRefresh(
|
||||
|
@ -40,14 +42,24 @@ func (b *Local) opRefresh(
|
|||
}
|
||||
|
||||
// Get our context
|
||||
tfCtx, state, err := b.context(op)
|
||||
tfCtx, opState, err := b.context(op)
|
||||
if err != nil {
|
||||
runningOp.Err = err
|
||||
return
|
||||
}
|
||||
|
||||
// context acquired the state, and therefor the lock.
|
||||
// Unlock it when the operation is complete
|
||||
defer func() {
|
||||
if s, ok := opState.(state.Locker); op.LockState && ok {
|
||||
if err := s.Unlock(); err != nil {
|
||||
log.Printf("[ERROR]: %s", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Set our state
|
||||
runningOp.State = state.State()
|
||||
runningOp.State = opState.State()
|
||||
|
||||
// Perform operation and write the resulting state to the running op
|
||||
newState, err := tfCtx.Refresh()
|
||||
|
@ -58,11 +70,11 @@ func (b *Local) opRefresh(
|
|||
}
|
||||
|
||||
// Write and persist the state
|
||||
if err := state.WriteState(newState); err != nil {
|
||||
if err := opState.WriteState(newState); err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error writing state: {{err}}", err)
|
||||
return
|
||||
}
|
||||
if err := state.PersistState(); err != nil {
|
||||
if err := opState.PersistState(); err != nil {
|
||||
runningOp.Err = errwrap.Wrapf("Error saving state: {{err}}", err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
|
||||
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
|
||||
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
|
||||
cmdFlags.BoolVar(&c.Meta.lockState, "state-lock", true, "lock state")
|
||||
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
||||
if err := cmdFlags.Parse(args); err != nil {
|
||||
return 1
|
||||
|
@ -182,6 +183,7 @@ func (c *ApplyCommand) Run(args []string) int {
|
|||
opReq.Plan = plan
|
||||
opReq.PlanRefresh = refresh
|
||||
opReq.Type = backend.OperationTypeApply
|
||||
opReq.LockState = c.Meta.lockState
|
||||
|
||||
// Perform the operation
|
||||
ctx, ctxCancel := context.WithCancel(context.Background())
|
||||
|
|
|
@ -83,12 +83,15 @@ type Meta struct {
|
|||
// shadow is used to enable/disable the shadow graph
|
||||
//
|
||||
// provider is to specify specific resource providers
|
||||
//
|
||||
// lockState is set to false to disable state locking
|
||||
statePath string
|
||||
stateOutPath string
|
||||
backupPath string
|
||||
parallelism int
|
||||
shadow bool
|
||||
provider string
|
||||
lockState bool
|
||||
}
|
||||
|
||||
// initStatePaths is used to initialize the default values for
|
||||
|
|
Loading…
Reference in New Issue