Merge pull request #13262 from hashicorp/jbardin/lock-timeouts

lock timeouts
This commit is contained in:
James Bardin 2017-04-04 14:30:20 -04:00 committed by GitHub
commit d059939f88
35 changed files with 489 additions and 147 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

@ -9,7 +9,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"
clistate "github.com/hashicorp/terraform/command/state" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -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

@ -10,8 +10,8 @@ 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/command/clistate"
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -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

@ -9,7 +9,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"
clistate "github.com/hashicorp/terraform/command/state" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
) )
@ -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

@ -102,22 +102,19 @@ func (b *Backend) State(name string) (state.State, error) {
stateMgr = &state.LockDisabled{Inner: stateMgr} stateMgr = &state.LockDisabled{Inner: stateMgr}
} }
// Get the locker, which we know always exists
stateMgrLocker := stateMgr.(state.Locker)
// Grab a lock, we use this to write an empty state if one doesn't // Grab a lock, we use this to write an empty state if one doesn't
// exist already. We have to write an empty state as a sentinel value // exist already. We have to write an empty state as a sentinel value
// so States() knows it exists. // so States() knows it exists.
lockInfo := state.NewLockInfo() lockInfo := state.NewLockInfo()
lockInfo.Operation = "init" lockInfo.Operation = "init"
lockId, err := stateMgrLocker.Lock(lockInfo) lockId, err := stateMgr.Lock(lockInfo)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to lock state in Consul: %s", err) return nil, fmt.Errorf("failed to lock state in Consul: %s", err)
} }
// Local helper function so we can call it multiple places // Local helper function so we can call it multiple places
lockUnlock := func(parent error) error { lockUnlock := func(parent error) error {
if err := stateMgrLocker.Unlock(lockId); err != nil { if err := stateMgr.Unlock(lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err) return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
} }

View File

@ -48,6 +48,7 @@ func (c *ApplyCommand) Run(args []string) int {
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.stateLock, "lock", true, "lock state") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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
@ -183,7 +184,6 @@ 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.stateLock
// Perform the operation // Perform the operation
ctx, ctxCancel := context.WithCancel(context.Background()) ctx, ctxCancel := context.WithCancel(context.Background())
@ -276,6 +276,8 @@ Options:
-lock=true Lock the state file when locking is supported. -lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-input=true Ask for input for variables if not directly set. -input=true Ask for input for variables if not directly set.
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
@ -325,6 +327,8 @@ Options:
-lock=true Lock the state file when locking is supported. -lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-parallelism=n Limit the number of concurrent operations. -parallelism=n Limit the number of concurrent operations.

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@ -92,6 +93,42 @@ func TestApply_lockedState(t *testing.T) {
} }
} }
// test apply with locked state, waiting for unlock
func TestApply_lockedStateWait(t *testing.T) {
statePath := testTempFile(t)
unlock, err := testLockState("./testdata", statePath)
if err != nil {
t.Fatal(err)
}
// unlock during apply
go func() {
time.Sleep(500 * time.Millisecond)
unlock()
}()
p := testProvider()
ui := new(cli.MockUi)
c := &ApplyCommand{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
// wait 4s just in case the lock process doesn't release in under a second,
// and we want our context to be alive for a second retry at the 3s mark.
args := []string{
"-state", statePath,
"-lock-timeout", "4s",
testFixturePath("apply"),
}
if code := c.Run(args); code != 0 {
log.Fatalf("lock should have succeed in less than 3s: %s", ui.ErrorWriter)
}
}
// high water mark counter // high water mark counter
type hwm struct { type hwm struct {
sync.Mutex sync.Mutex

View File

@ -2,9 +2,10 @@
// //
// This is a separate package so that backends can use this for consistent // This is a separate package so that backends can use this for consistent
// messaging without creating a circular reference to the command package. // messaging without creating a circular reference to the command package.
package message package clistate
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
@ -48,17 +49,13 @@ that no one else is holding a lock.
) )
// Lock locks the given state and outputs to the user if locking // Lock locks the given state and outputs to the user if locking
// is taking longer than the threshold. // is taking longer than the threshold. The lock is retried until the context
func Lock(s state.State, info *state.LockInfo, ui cli.Ui, color *colorstring.Colorize) (string, error) { // is cancelled.
sl, ok := s.(state.Locker) func Lock(ctx context.Context, s state.State, info *state.LockInfo, ui cli.Ui, color *colorstring.Colorize) (string, error) {
if !ok {
return "", nil
}
var lockID string var lockID string
err := slowmessage.Do(LockThreshold, func() error { err := slowmessage.Do(LockThreshold, func() error {
id, err := sl.Lock(info) id, err := state.LockWithContext(ctx, s, info)
lockID = id lockID = id
return err return err
}, func() { }, func() {
@ -77,13 +74,8 @@ func Lock(s state.State, info *state.LockInfo, ui cli.Ui, color *colorstring.Col
// Unlock unlocks the given state and outputs to the user if the // Unlock unlocks the given state and outputs to the user if the
// unlock fails what can be done. // unlock fails what can be done.
func Unlock(s state.State, id string, ui cli.Ui, color *colorstring.Colorize) error { func Unlock(s state.State, id string, ui cli.Ui, color *colorstring.Colorize) error {
sl, ok := s.(state.Locker)
if !ok {
return nil
}
err := slowmessage.Do(LockThreshold, func() error { err := slowmessage.Do(LockThreshold, func() error {
return sl.Unlock(id) return s.Unlock(id)
}, func() { }, func() {
if ui != nil { if ui != nil {
ui.Output(color.Color(UnlockMessage)) ui.Output(color.Color(UnlockMessage))

View File

@ -1,13 +1,13 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
clistate "github.com/hashicorp/terraform/command/state"
) )
type EnvDeleteCommand struct { type EnvDeleteCommand struct {
@ -93,15 +93,20 @@ func (c *EnvDeleteCommand) Run(args []string) int {
return 1 return 1
} }
// Lock the state if we can if c.stateLock {
lockInfo := state.NewLockInfo() lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
lockInfo.Operation = "env delete" defer cancel()
lockID, err := clistate.Lock(sMgr, lockInfo, c.Ui, c.Colorize())
if err != nil { // Lock the state if we can
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) lockInfo := state.NewLockInfo()
return 1 lockInfo.Operation = "env delete"
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
if err != nil {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
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,15 +1,15 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
clistate "github.com/hashicorp/terraform/command/state"
) )
type EnvNewCommand struct { type EnvNewCommand struct {
@ -88,15 +88,20 @@ func (c *EnvNewCommand) Run(args []string) int {
return 1 return 1
} }
// Lock the state if we can if c.stateLock {
lockInfo := state.NewLockInfo() lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
lockInfo.Operation = "env new" defer cancel()
lockID, err := clistate.Lock(sMgr, lockInfo, c.Ui, c.Colorize())
if err != nil { // Lock the state if we can
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) lockInfo := state.NewLockInfo()
return 1 lockInfo.Operation = "env new"
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
if err != nil {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
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

@ -35,6 +35,8 @@ func (c *ImportCommand) Run(args []string) int {
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path") cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
cmdFlags.StringVar(&configPath, "config", pwd, "path") cmdFlags.StringVar(&configPath, "config", pwd, "path")
cmdFlags.StringVar(&c.Meta.provider, "provider", "", "provider") cmdFlags.StringVar(&c.Meta.provider, "provider", "", "provider")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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
@ -162,6 +164,10 @@ Options:
-input=true Ask for input for variables if not directly set. -input=true Ask for input for variables if not directly set.
-lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-provider=provider Specific provider to use for import. This is used for -provider=provider Specific provider to use for import. This is used for

View File

@ -28,6 +28,8 @@ func (c *InitCommand) Run(args []string) int {
cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "")
cmdFlags.BoolVar(&flagGet, "get", true, "") cmdFlags.BoolVar(&flagGet, "get", true, "")
cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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 {
@ -226,6 +228,10 @@ Options:
-input=true Ask for input if necessary. If false, will error if -input=true Ask for input if necessary. If false, will error if
input was required. input was required.
-lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-force-copy Suppress prompts about copying state data. This is -force-copy Suppress prompts about copying state data. This is

View File

@ -90,16 +90,20 @@ 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
stateOutPath string stateOutPath string
backupPath string backupPath string
parallelism int parallelism int
shadow bool shadow bool
provider string provider string
stateLock bool stateLock bool
forceInitCopy bool stateLockTimeout time.Duration
forceInitCopy bool
} }
// initStatePaths is used to initialize the default values for // initStatePaths is used to initialize the default values for

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"
@ -16,13 +17,13 @@ import (
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl" "github.com/hashicorp/hcl"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
backendinit "github.com/hashicorp/terraform/backend/init" "github.com/hashicorp/terraform/command/clistate"
clistate "github.com/hashicorp/terraform/command/state"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
backendinit "github.com/hashicorp/terraform/backend/init"
backendlocal "github.com/hashicorp/terraform/backend/local" backendlocal "github.com/hashicorp/terraform/backend/local"
) )
@ -166,10 +167,12 @@ func (m *Meta) IsLocalBackend(b backend.Backend) bool {
// be called. // be called.
func (m *Meta) Operation() *backend.Operation { func (m *Meta) Operation() *backend.Operation {
return &backend.Operation{ return &backend.Operation{
PlanOutBackend: m.backendState, PlanOutBackend: m.backendState,
Targets: m.targets, Targets: m.targets,
UIIn: m.UIInput(), UIIn: m.UIInput(),
Environment: m.Env(), Environment: m.Env(),
LockState: m.stateLock,
StateLockTimeout: m.stateLockTimeout,
} }
} }
@ -609,15 +612,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)
} }
// Lock the state if we can if m.stateLock {
lockInfo := state.NewLockInfo() lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
lockInfo.Operation = "backend from plan" defer cancel()
lockID, err := clistate.Lock(realMgr, lockInfo, m.Ui, m.Colorize()) // Lock the state if we can
if err != nil { lockInfo := state.NewLockInfo()
return nil, fmt.Errorf("Error locking state: %s", err) lockInfo.Operation = "backend from plan"
lockID, err := clistate.Lock(lockCtx, realMgr, lockInfo, m.Ui, m.Colorize())
if err != nil {
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)
@ -1040,15 +1048,20 @@ func (m *Meta) backend_C_r_s(
} }
} }
// Lock the state if we can if m.stateLock {
lockInfo := state.NewLockInfo() lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
lockInfo.Operation = "backend from config" defer cancel()
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) // Lock the state if we can
if err != nil { lockInfo := state.NewLockInfo()
return nil, fmt.Errorf("Error locking state: %s", err) lockInfo.Operation = "backend from config"
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, m.Ui, m.Colorize())
if err != nil {
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()
@ -1132,15 +1145,20 @@ func (m *Meta) backend_C_r_S_changed(
} }
} }
// Lock the state if we can if m.stateLock {
lockInfo := state.NewLockInfo() lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
lockInfo.Operation = "backend from config" defer cancel()
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) // Lock the state if we can
if err != nil { lockInfo := state.NewLockInfo()
return nil, fmt.Errorf("Error locking state: %s", err) lockInfo.Operation = "backend from config"
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, m.Ui, m.Colorize())
if err != nil {
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()
@ -1288,15 +1306,20 @@ func (m *Meta) backend_C_R_S_unchanged(
} }
} }
// Lock the state if we can if m.stateLock {
lockInfo := state.NewLockInfo() lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
lockInfo.Operation = "backend from config" defer cancel()
lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) // Lock the state if we can
if err != nil { lockInfo := state.NewLockInfo()
return nil, fmt.Errorf("Error locking state: %s", err) lockInfo.Operation = "backend from config"
lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, m.Ui, m.Colorize())
if err != nil {
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"
@ -9,7 +10,7 @@ import (
"strings" "strings"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
clistate "github.com/hashicorp/terraform/command/state" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -217,25 +218,30 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
errMigrateSingleLoadDefault), opts.TwoType, err) errMigrateSingleLoadDefault), opts.TwoType, err)
} }
lockInfoOne := state.NewLockInfo() if m.stateLock {
lockInfoOne.Operation = "migration" lockCtx, cancel := context.WithTimeout(context.Background(), m.stateLockTimeout)
lockInfoOne.Info = "source state" defer cancel()
lockIDOne, err := clistate.Lock(stateOne, lockInfoOne, m.Ui, m.Colorize()) lockInfoOne := state.NewLockInfo()
if err != nil { lockInfoOne.Operation = "migration"
return fmt.Errorf("Error locking source state: %s", err) lockInfoOne.Info = "source state"
lockIDOne, err := clistate.Lock(lockCtx, stateOne, lockInfoOne, m.Ui, m.Colorize())
if err != nil {
return fmt.Errorf("Error locking source state: %s", err)
}
defer clistate.Unlock(stateOne, lockIDOne, m.Ui, m.Colorize())
lockInfoTwo := state.NewLockInfo()
lockInfoTwo.Operation = "migration"
lockInfoTwo.Info = "destination state"
lockIDTwo, err := clistate.Lock(lockCtx, stateTwo, lockInfoTwo, m.Ui, m.Colorize())
if err != nil {
return fmt.Errorf("Error locking destination state: %s", err)
}
defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
} }
defer clistate.Unlock(stateOne, lockIDOne, m.Ui, m.Colorize())
lockInfoTwo := state.NewLockInfo()
lockInfoTwo.Operation = "migration"
lockInfoTwo.Info = "destination state"
lockIDTwo, err := clistate.Lock(stateTwo, lockInfoTwo, m.Ui, m.Colorize())
if err != nil {
return fmt.Errorf("Error locking destination state: %s", err)
}
defer clistate.Unlock(stateTwo, lockIDTwo, m.Ui, m.Colorize())
one := stateOne.State() one := stateOne.State()
two := stateTwo.State() two := stateTwo.State()

View File

@ -32,6 +32,7 @@ func (c *PlanCommand) Run(args []string) int {
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path") cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode") cmdFlags.BoolVar(&detailed, "detailed-exitcode", false, "detailed-exitcode")
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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
@ -85,7 +86,6 @@ func (c *PlanCommand) Run(args []string) int {
opReq.PlanRefresh = refresh opReq.PlanRefresh = refresh
opReq.PlanOutPath = outPath opReq.PlanOutPath = outPath
opReq.Type = backend.OperationTypePlan opReq.Type = backend.OperationTypePlan
opReq.LockState = c.Meta.stateLock
// Perform the operation // Perform the operation
op, err := b.Operation(context.Background(), opReq) op, err := b.Operation(context.Background(), opReq)
@ -145,6 +145,8 @@ Options:
-lock=true Lock the state file when locking is supported. -lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-module-depth=n Specifies the depth of modules to show in the output. -module-depth=n Specifies the depth of modules to show in the output.
This does not affect the plan itself, only the output This does not affect the plan itself, only the output
shown. By default, this is -1, which will expand all. shown. By default, this is -1, which will expand all.

View File

@ -24,6 +24,7 @@ func (c *RefreshCommand) Run(args []string) int {
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.stateLock, "lock", true, "lock state") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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
@ -53,7 +54,6 @@ func (c *RefreshCommand) Run(args []string) int {
opReq := c.Operation() opReq := c.Operation()
opReq.Type = backend.OperationTypeRefresh opReq.Type = backend.OperationTypeRefresh
opReq.Module = mod opReq.Module = mod
opReq.LockState = c.Meta.stateLock
// Perform the operation // Perform the operation
op, err := b.Operation(context.Background(), opReq) op, err := b.Operation(context.Background(), opReq)
@ -98,6 +98,8 @@ Options:
-lock=true Lock the state file when locking is supported. -lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-no-color If specified, output won't contain any color. -no-color If specified, output won't contain any color.
-state=path Path to read and save state (unless state-out -state=path Path to read and save state (unless state-out

View File

@ -1,11 +1,12 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"strings" "strings"
clistate "github.com/hashicorp/terraform/command/state" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -28,6 +29,7 @@ func (c *TaintCommand) Run(args []string) int {
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.stateLock, "lock", true, "lock state") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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
@ -78,10 +80,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
@ -188,6 +193,8 @@ Options:
-lock=true Lock the state file when locking is supported. -lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-module=path The module path where the resource lives. By -module=path The module path where the resource lives. By
default this will be root. Child modules can be specified default this will be root. Child modules can be specified
by names. Ex. "consul" or "consul.vpc" (nested modules). by names. Ex. "consul" or "consul.vpc" (nested modules).

View File

@ -59,13 +59,6 @@ func (c *UnlockCommand) Run(args []string) int {
return 1 return 1
} }
s, ok := st.(state.Locker)
if !ok {
c.Ui.Error("The remote state backend in use does not support locking, and therefor\n" +
"cannot be unlocked.")
return 1
}
isLocal := false isLocal := false
switch s := st.(type) { switch s := st.(type) {
case *state.BackupState: case *state.BackupState:
@ -103,7 +96,7 @@ func (c *UnlockCommand) Run(args []string) int {
} }
} }
if err := s.Unlock(lockID); err != nil { if err := st.Unlock(lockID); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err)) c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err))
return 1 return 1
} }

View File

@ -1,11 +1,12 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"log" "log"
"strings" "strings"
clistate "github.com/hashicorp/terraform/command/state" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/state"
) )
@ -27,6 +28,7 @@ func (c *UntaintCommand) Run(args []string) int {
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.stateLock, "lock", true, "lock state") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
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
@ -66,10 +68,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
@ -176,6 +181,8 @@ Options:
-lock=true Lock the state file when locking is supported. -lock=true Lock the state file when locking is supported.
-lock-timeout=0s Duration to retry a state lock.
-module=path The module path where the resource lives. By -module=path The module path where the resource lives. By
default this will be root. Child modules can be specified default this will be root. Child modules can be specified
by names. Ex. "consul" or "consul.vpc" (nested modules). by names. Ex. "consul" or "consul.vpc" (nested modules).

View File

@ -42,8 +42,9 @@ func init() {
// that to match. // that to match.
PlumbingCommands = map[string]struct{}{ PlumbingCommands = map[string]struct{}{
"state": struct{}{}, // includes all subcommands "state": struct{}{}, // includes all subcommands
"debug": struct{}{}, // includes all subcommands "debug": struct{}{}, // includes all subcommands
"force-unlock": struct{}{},
} }
Commands = map[string]cli.CommandFactory{ Commands = map[string]cli.CommandFactory{
@ -105,12 +106,6 @@ func init() {
}, nil }, nil
}, },
"force-unlock": func() (cli.Command, error) {
return &command.UnlockCommand{
Meta: meta,
}, nil
},
"get": func() (cli.Command, error) { "get": func() (cli.Command, error) {
return &command.GetCommand{ return &command.GetCommand{
Meta: meta, Meta: meta,
@ -215,6 +210,12 @@ func init() {
}, nil }, nil
}, },
"force-unlock": func() (cli.Command, error) {
return &command.UnlockCommand{
Meta: meta,
}, nil
},
"state": func() (cli.Command, error) { "state": func() (cli.Command, error) {
return &command.StateCommand{}, nil return &command.StateCommand{}, nil
}, },

View File

@ -41,19 +41,12 @@ func (s *BackupState) PersistState() error {
return s.Real.PersistState() return s.Real.PersistState()
} }
// all states get wrapped by BackupState, so it has to be a Locker
func (s *BackupState) Lock(info *LockInfo) (string, error) { func (s *BackupState) Lock(info *LockInfo) (string, error) {
if s, ok := s.Real.(Locker); ok { return s.Real.Lock(info)
return s.Lock(info)
}
return "", nil
} }
func (s *BackupState) Unlock(id string) error { func (s *BackupState) Unlock(id string) error {
if s, ok := s.Real.(Locker); ok { return s.Real.Unlock(id)
return s.Unlock(id)
}
return nil
} }
func (s *BackupState) backup() error { func (s *BackupState) backup() error {

View File

@ -1,6 +1,10 @@
package state package state
import ( import (
"errors"
"sync"
"time"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -26,3 +30,59 @@ func (s *InmemState) WriteState(state *terraform.State) error {
func (s *InmemState) PersistState() error { func (s *InmemState) PersistState() error {
return nil return nil
} }
func (s *InmemState) Lock(*LockInfo) (string, error) {
return "", nil
}
func (s *InmemState) Unlock(string) error {
return nil
}
// inmemLocker is an in-memory State implementation for testing locks.
type inmemLocker struct {
*InmemState
mu sync.Mutex
lockInfo *LockInfo
// count the calls to Lock
lockCounter int
}
func (s *inmemLocker) Lock(info *LockInfo) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
s.lockCounter++
lockErr := &LockError{
Info: &LockInfo{},
}
if s.lockInfo != nil {
lockErr.Err = errors.New("state locked")
*lockErr.Info = *s.lockInfo
return "", lockErr
}
info.Created = time.Now().UTC()
s.lockInfo = info
return s.lockInfo.ID, nil
}
func (s *inmemLocker) Unlock(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
lockErr := &LockError{
Info: &LockInfo{},
}
if id != s.lockInfo.ID {
lockErr.Err = errors.New("invalid lock id")
*lockErr.Info = *s.lockInfo
return lockErr
}
s.lockInfo = nil
return nil
}

View File

@ -14,3 +14,39 @@ func TestInmemState_impl(t *testing.T) {
var _ StatePersister = new(InmemState) var _ StatePersister = new(InmemState)
var _ StateRefresher = new(InmemState) var _ StateRefresher = new(InmemState)
} }
func TestInmemLocker(t *testing.T) {
inmem := &InmemState{state: TestStateInitial()}
// test that it correctly wraps the inmem state
s := &inmemLocker{InmemState: inmem}
TestState(t, s)
info := NewLockInfo()
id, err := s.Lock(info)
if err != nil {
t.Fatal(err)
}
if id == "" {
t.Fatal("no lock id from state lock")
}
// locking again should fail
_, err = s.Lock(NewLockInfo())
if err == nil {
t.Fatal("state locked while locked")
}
if err.(*LockError).Info.ID != id {
t.Fatal("wrong lock id from lock failure")
}
if err := s.Unlock(id); err != nil {
t.Fatal(err)
}
if _, err := s.Lock(NewLockInfo()); err != nil {
t.Fatal(err)
}
}

View File

@ -2,6 +2,7 @@ package state
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -28,6 +29,7 @@ type State interface {
StateWriter StateWriter
StateRefresher StateRefresher
StatePersister StatePersister
Locker
} }
// StateReader is the interface for things that can return a state. Retrieving // StateReader is the interface for things that can return a state. Retrieving
@ -72,6 +74,48 @@ type Locker interface {
Unlock(id string) error Unlock(id string) error
} }
// test hook to verify that LockWithContext has attempted a lock
var postLockHook func()
// Lock the state, using the provided context for timeout and cancellation.
// This backs off slightly to an upper limit.
func LockWithContext(ctx context.Context, s State, info *LockInfo) (string, error) {
delay := time.Second
maxDelay := 16 * time.Second
for {
id, err := s.Lock(info)
if err == nil {
return id, nil
}
le, ok := err.(*LockError)
if !ok {
// not a lock error, so we can't retry
return "", err
}
if le.Info.ID == "" {
// the lock has no ID, something is wrong so don't keep trying
return "", fmt.Errorf("lock error missing ID: %s", err)
}
if postLockHook != nil {
postLockHook()
}
// there's an existing lock, wait and try again
select {
case <-ctx.Done():
// return the last lock error with the info
return "", err
case <-time.After(delay):
if delay < maxDelay {
delay *= 2
}
}
}
}
// Generate a LockInfo structure, populating the required fields. // Generate a LockInfo structure, populating the required fields.
func NewLockInfo() *LockInfo { func NewLockInfo() *LockInfo {
// this doesn't need to be cryptographically secure, just unique. // this doesn't need to be cryptographically secure, just unique.

View File

@ -1,12 +1,14 @@
package state package state
import ( import (
"context"
"encoding/json" "encoding/json"
"flag" "flag"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"testing" "testing"
"time"
"github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/helper/logging"
) )
@ -50,3 +52,59 @@ func TestNewLockInfo(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestLockWithContext(t *testing.T) {
inmem := &InmemState{state: TestStateInitial()}
// test that it correctly wraps the inmem state
s := &inmemLocker{InmemState: inmem}
id, err := s.Lock(NewLockInfo())
if err != nil {
t.Fatal(err)
}
// use a cancelled context for an immediate timeout
ctx, cancel := context.WithCancel(context.Background())
cancel()
info := NewLockInfo()
info.Info = "lock with context"
_, err = LockWithContext(ctx, s, info)
if err == nil {
t.Fatal("lock should have failed immediately")
}
// block until LockwithContext has made a first attempt
attempted := make(chan struct{})
postLockHook = func() {
close(attempted)
postLockHook = nil
}
// unlock the state during LockWithContext
unlocked := make(chan struct{})
go func() {
defer close(unlocked)
<-attempted
if err := s.Unlock(id); err != nil {
t.Fatal(err)
}
}()
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
id, err = LockWithContext(ctx, s, info)
if err != nil {
t.Fatal("lock should have completed within 2s:", err)
}
// ensure the goruotine completes
<-unlocked
// Lock should have been called a total of 4 times.
// 1 initial lock, 1 failure, 1 failure + 1 retry
if s.lockCounter != 4 {
t.Fatalf("lock only called %d times", s.lockCounter)
}
}

View File

@ -31,6 +31,10 @@ The command-line flags are all optional. The list of available flags are:
* `-backup=path` - Path to the backup file. Defaults to `-state-out` with * `-backup=path` - Path to the backup file. Defaults to `-state-out` with
the ".backup" extension. Disabled by setting to "-". the ".backup" extension. Disabled by setting to "-".
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-input=true` - Ask for input for variables if not directly set. * `-input=true` - Ask for input for variables if not directly set.
* `-no-color` - Disables output with coloring. * `-no-color` - Disables output with coloring.

View File

@ -42,6 +42,17 @@ The command-line flags are all optional. The list of available flags are:
* `-input=true` - Whether to ask for input for provider configuration. * `-input=true` - Whether to ask for input for provider configuration.
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-no-color` - If specified, output won't contain any color.
* `-provider=provider` - Specified provider to use for import. This is used for
specifying provider aliases, such as "aws.eu". This defaults to the normal
provider based on the prefix of the resource being imported. You usually
don't need to specify this.
* `-state=path` - The path to read and save state files (unless state-out is * `-state=path` - The path to read and save state files (unless state-out is
specified). Ignored when [remote state](/docs/state/remote.html) is used. specified). Ignored when [remote state](/docs/state/remote.html) is used.
@ -49,11 +60,6 @@ The command-line flags are all optional. The list of available flags are:
the state path. Ignored when [remote state](/docs/state/remote.html) is the state path. Ignored when [remote state](/docs/state/remote.html) is
used. used.
* `-provider=provider` - Specified provider to use for import. This is used for
specifying provider aliases, such as "aws.eu". This defaults to the normal
provider based on the prefix of the resource being imported. You usually
don't need to specify this.
* `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag * `-var 'foo=bar'` - Set a variable in the Terraform configuration. This flag
can be set multiple times. Variable values are interpreted as can be set multiple times. Variable values are interpreted as
[HCL](/docs/configuration/syntax.html#HCL), so list and map values can be [HCL](/docs/configuration/syntax.html#HCL), so list and map values can be

View File

@ -33,8 +33,8 @@ Common commands:
apply Builds or changes infrastructure apply Builds or changes infrastructure
console Interactive console for Terraform interpolations console Interactive console for Terraform interpolations
destroy Destroy Terraform-managed infrastructure destroy Destroy Terraform-managed infrastructure
env Environment management
fmt Rewrites config files to canonical format fmt Rewrites config files to canonical format
force-unlock Manually unlock the terraform state
get Download and install modules for the configuration get Download and install modules for the configuration
graph Create a visual graph of Terraform resources graph Create a visual graph of Terraform resources
import Import existing infrastructure into Terraform import Import existing infrastructure into Terraform
@ -51,6 +51,7 @@ Common commands:
All other commands: All other commands:
debug Debug output management (experimental) debug Debug output management (experimental)
force-unlock Manually unlock the terraform state
state Advanced state management state Advanced state management
``` ```

View File

@ -54,6 +54,15 @@ The command-line flags are all optional. The list of available flags are:
* `-input=true` - Ask for input interactively if necessary. If this is false * `-input=true` - Ask for input interactively if necessary. If this is false
and input is required, `init` will error. and input is required, `init` will error.
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-no-color` - If specified, output won't contain any color.
* `-force-copy` - Suppress prompts about copying state data. This is equivalent
to providing a "yes" to all confirmation prompts.
## Backend Config ## Backend Config
The `-backend-config` can take a path or `key=value` pair to specify additional The `-backend-config` can take a path or `key=value` pair to specify additional

View File

@ -39,6 +39,10 @@ The command-line flags are all optional. The list of available flags are:
* `-input=true` - Ask for input for variables if not directly set. * `-input=true` - Ask for input for variables if not directly set.
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-module-depth=n` - Specifies the depth of modules to show in the output. * `-module-depth=n` - Specifies the depth of modules to show in the output.
This does not affect the plan itself, only the output shown. By default, This does not affect the plan itself, only the output shown. By default,
this is -1, which will expand all. this is -1, which will expand all.

View File

@ -31,6 +31,14 @@ The command-line flags are all optional. The list of available flags are:
* `-no-color` - Disables output with coloring * `-no-color` - Disables output with coloring
* `-input=true` - Ask for input for variables if not directly set.
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-no-color` - If specified, output won't contain any color.
* `-state=path` - Path to read and write the state file to. Defaults to "terraform.tfstate". * `-state=path` - Path to read and write the state file to. Defaults to "terraform.tfstate".
Ignored when [remote state](/docs/state/remote.html) is used. Ignored when [remote state](/docs/state/remote.html) is used.

View File

@ -47,6 +47,10 @@ The command-line flags are all optional. The list of available flags are:
* `-backup=path` - Path to the backup file. Defaults to `-state-out` with * `-backup=path` - Path to the backup file. Defaults to `-state-out` with
the ".backup" extension. Disabled by setting to "-". the ".backup" extension. Disabled by setting to "-".
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-module=path` - The module path where the resource to taint exists. * `-module=path` - The module path where the resource to taint exists.
By default this is the root path. Other modules can be specified by By default this is the root path. Other modules can be specified by
a period-separated list. Example: "foo" would reference the module a period-separated list. Example: "foo" would reference the module

View File

@ -47,6 +47,10 @@ certain cases, see above note). The list of available flags are:
time, there is a maxiumum of one tainted instance per resource, so this flag time, there is a maxiumum of one tainted instance per resource, so this flag
can be safely omitted. can be safely omitted.
* `-lock=true` - Lock the state file when locking is supported.
* `-lock-timeout=0s` - Duration to retry a state lock.
* `-module=path` - The module path where the resource to untaint exists. * `-module=path` - The module path where the resource to untaint exists.
By default this is the root path. Other modules can be specified by By default this is the root path. Other modules can be specified by
a period-separated list. Example: "foo" would reference the module a period-separated list. Example: "foo" would reference the module

View File

@ -54,8 +54,8 @@ Common commands:
apply Builds or changes infrastructure apply Builds or changes infrastructure
console Interactive console for Terraform interpolations console Interactive console for Terraform interpolations
destroy Destroy Terraform-managed infrastructure destroy Destroy Terraform-managed infrastructure
env Environment management
fmt Rewrites config files to canonical format fmt Rewrites config files to canonical format
force-unlock Manually unlock the terraform state
get Download and install modules for the configuration get Download and install modules for the configuration
graph Create a visual graph of Terraform resources graph Create a visual graph of Terraform resources
import Import existing infrastructure into Terraform import Import existing infrastructure into Terraform
@ -72,6 +72,7 @@ Common commands:
All other commands: All other commands:
debug Debug output management (experimental) debug Debug output management (experimental)
force-unlock Manually unlock the terraform state
state Advanced state management state Advanced state management
``` ```