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:
James Bardin 2017-02-01 18:16:16 -05:00
parent 9acb86a182
commit 9cdba1f199
8 changed files with 72 additions and 10 deletions

View File

@ -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.

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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
}

View File

@ -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())

View File

@ -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