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