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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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