Merge pull request #27787 from hashicorp/alisdair/command-views-state-locker

clistate: Update clistate.Locker for command views
This commit is contained in:
Alisdair McDiarmid 2021-02-16 17:37:18 -05:00 committed by GitHub
commit 6375c6ce6b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 641 additions and 245 deletions

View File

@ -10,7 +10,6 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"time"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
@ -216,17 +215,13 @@ type Operation struct {
// ShowDiagnostics prints diagnostic messages to the UI. // ShowDiagnostics prints diagnostic messages to the UI.
ShowDiagnostics func(vals ...interface{}) ShowDiagnostics func(vals ...interface{})
// If LockState is true, the Operation must Lock any
// statemgr.Lockers for its duration, and Unlock when complete.
LockState bool
// StateLocker is used to lock the state while providing UI feedback to the // StateLocker is used to lock the state while providing UI feedback to the
// user. This will be supplied by the Backend itself. // user. This will be replaced by the Backend to update the context.
//
// If state locking is not necessary, this should be set to a no-op
// implementation of clistate.Locker.
StateLocker clistate.Locker StateLocker clistate.Locker
// The duration to retry obtaining a State lock.
StateLockTimeout time.Duration
// Workspace is the name of the workspace that this operation should run // Workspace is the name of the workspace that this operation should run
// in, which controls which named state is used. // in, which controls which named state is used.
Workspace string Workspace string

View File

@ -12,7 +12,6 @@ import (
"sync" "sync"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
@ -326,11 +325,7 @@ func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.
cancelCtx, cancel := context.WithCancel(context.Background()) cancelCtx, cancel := context.WithCancel(context.Background())
runningOp.Cancel = cancel runningOp.Cancel = cancel
if op.LockState { op.StateLocker = op.StateLocker.WithContext(stopCtx)
op.StateLocker = clistate.NewLocker(stopCtx, op.StateLockTimeout, b.CLI, b.Colorize())
} else {
op.StateLocker = clistate.NewNoopLocker()
}
// Do it // Do it
go func() { go func() {

View File

@ -52,9 +52,9 @@ func (b *Local) opApply(
// the state was locked during succesfull context creation; unlock the state // the state was locked during succesfull context creation; unlock the state
// when the operation completes // when the operation completes
defer func() { defer func() {
err := op.StateLocker.Unlock(nil) diags := op.StateLocker.Unlock()
if err != nil { if diags.HasErrors() {
op.ShowDiagnostics(err) op.ShowDiagnostics(diags)
runningOp.Result = backend.OperationFailure runningOp.Result = backend.OperationFailure
} }
}() }()

View File

@ -13,6 +13,7 @@ import (
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/providers"
@ -293,6 +294,7 @@ func testOperationApply(t *testing.T, configDir string) (*backend.Operation, fun
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
ShowDiagnostics: testLogDiagnostics(t), ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewNoopLocker(),
}, configCleanup }, configCleanup
} }

View File

@ -8,7 +8,6 @@ import (
"github.com/hashicorp/errwrap" "github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configload" "github.com/hashicorp/terraform/configs/configload"
"github.com/hashicorp/terraform/plans/planfile" "github.com/hashicorp/terraform/plans/planfile"
@ -24,11 +23,7 @@ func (b *Local) Context(op *backend.Operation) (*terraform.Context, statemgr.Ful
// to ask for input/validate. // to ask for input/validate.
op.Type = backend.OperationTypeInvalid op.Type = backend.OperationTypeInvalid
if op.LockState { op.StateLocker = op.StateLocker.WithContext(context.Background())
op.StateLocker = clistate.NewLocker(context.Background(), op.StateLockTimeout, b.CLI, b.Colorize())
} else {
op.StateLocker = clistate.NewNoopLocker()
}
ctx, _, stateMgr, diags := b.context(op) ctx, _, stateMgr, diags := b.context(op)
return ctx, stateMgr, diags return ctx, stateMgr, diags
@ -45,8 +40,7 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.
return nil, nil, nil, diags return nil, nil, nil, diags
} }
log.Printf("[TRACE] backend/local: requesting state lock for workspace %q", op.Workspace) log.Printf("[TRACE] backend/local: requesting state lock for workspace %q", op.Workspace)
if err := op.StateLocker.Lock(s, op.Type.String()); err != nil { if diags := op.StateLocker.Lock(s, op.Type.String()); diags.HasErrors() {
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
return nil, nil, nil, diags return nil, nil, nil, diags
} }
@ -54,10 +48,7 @@ func (b *Local) context(op *backend.Operation) (*terraform.Context, *configload.
// If we're returning with errors, and thus not producing a valid // If we're returning with errors, and thus not producing a valid
// context, we'll want to avoid leaving the workspace locked. // context, we'll want to avoid leaving the workspace locked.
if diags.HasErrors() { if diags.HasErrors() {
err := op.StateLocker.Unlock(nil) diags = diags.Append(op.StateLocker.Unlock())
if err != nil {
diags = diags.Append(errwrap.Wrapf("Error unlocking state: {{err}}", err))
}
} }
}() }()

View File

@ -4,7 +4,11 @@ import (
"testing" "testing"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
) )
func TestLocalContext(t *testing.T) { func TestLocalContext(t *testing.T) {
@ -15,11 +19,15 @@ func TestLocalContext(t *testing.T) {
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
defer configCleanup() defer configCleanup()
streams, _ := terminal.StreamsForTesting(t)
view := views.NewView(streams)
stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
op := &backend.Operation{ op := &backend.Operation{
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
Workspace: backend.DefaultStateName, Workspace: backend.DefaultStateName,
LockState: true, StateLocker: stateLocker,
} }
_, _, diags := b.Context(op) _, _, diags := b.Context(op)
@ -39,11 +47,15 @@ func TestLocalContext_error(t *testing.T) {
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
defer configCleanup() defer configCleanup()
streams, _ := terminal.StreamsForTesting(t)
view := views.NewView(streams)
stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
op := &backend.Operation{ op := &backend.Operation{
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
Workspace: backend.DefaultStateName, Workspace: backend.DefaultStateName,
LockState: true, StateLocker: stateLocker,
} }
_, _, diags := b.Context(op) _, _, diags := b.Context(op)
@ -53,5 +65,4 @@ func TestLocalContext_error(t *testing.T) {
// Context() unlocks the state on failure // Context() unlocks the state on failure
assertBackendStateUnlocked(t, b) assertBackendStateUnlocked(t, b)
} }

View File

@ -73,9 +73,9 @@ func (b *Local) opPlan(
// the state was locked during succesfull context creation; unlock the state // the state was locked during succesfull context creation; unlock the state
// when the operation completes // when the operation completes
defer func() { defer func() {
err := op.StateLocker.Unlock(nil) diags := op.StateLocker.Unlock()
if err != nil { if diags.HasErrors() {
op.ShowDiagnostics(err) op.ShowDiagnostics(diags)
runningOp.Result = backend.OperationFailure runningOp.Result = backend.OperationFailure
} }
}() }()

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
@ -740,6 +741,7 @@ func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
ShowDiagnostics: testLogDiagnostics(t), ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewNoopLocker(),
}, configCleanup }, configCleanup
} }

View File

@ -55,9 +55,9 @@ func (b *Local) opRefresh(
// the state was locked during succesfull context creation; unlock the state // the state was locked during succesfull context creation; unlock the state
// when the operation completes // when the operation completes
defer func() { defer func() {
err := op.StateLocker.Unlock(nil) diags := op.StateLocker.Unlock()
if err != nil { if diags.HasErrors() {
op.ShowDiagnostics(err) op.ShowDiagnostics(diags)
runningOp.Result = backend.OperationFailure runningOp.Result = backend.OperationFailure
} }
}() }()

View File

@ -8,6 +8,7 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs/configschema" "github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/providers"
@ -260,8 +261,8 @@ func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, f
Type: backend.OperationTypeRefresh, Type: backend.OperationTypeRefresh,
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
LockState: true,
ShowDiagnostics: testLogDiagnostics(t), ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewNoopLocker(),
}, configCleanup }, configCleanup
} }

View File

@ -14,7 +14,11 @@ import (
version "github.com/hashicorp/go-version" version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/plans/planfile" "github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -26,14 +30,24 @@ import (
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func()) { func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func()) {
t.Helper() t.Helper()
return testOperationApplyWithTimeout(t, configDir, 0)
}
func testOperationApplyWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func()) {
t.Helper()
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
streams, _ := terminal.StreamsForTesting(t)
view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
return &backend.Operation{ return &backend.Operation{
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
Parallelism: defaultParallelism, Parallelism: defaultParallelism,
PlanRefresh: true, PlanRefresh: true,
ShowDiagnostics: testLogDiagnostics(t), ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewLocker(timeout, view),
Type: backend.OperationTypeApply, Type: backend.OperationTypeApply,
}, configCleanup }, configCleanup
} }
@ -878,7 +892,7 @@ func TestRemote_applyLockTimeout(t *testing.T) {
t.Fatalf("error creating pending run: %v", err) t.Fatalf("error creating pending run: %v", err)
} }
op, configCleanup := testOperationApply(t, "./testdata/apply") op, configCleanup := testOperationApplyWithTimeout(t, "./testdata/apply", 50*time.Millisecond)
defer configCleanup() defer configCleanup()
input := testInput(t, map[string]string{ input := testInput(t, map[string]string{
@ -886,7 +900,6 @@ func TestRemote_applyLockTimeout(t *testing.T) {
"approve": "yes", "approve": "yes",
}) })
op.StateLockTimeout = 50 * time.Millisecond
op.UIIn = input op.UIIn = input
op.UIOut = b.CLI op.UIOut = b.CLI
op.Workspace = backend.DefaultStateName op.Workspace = backend.DefaultStateName

View File

@ -11,7 +11,6 @@ import (
"github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax" "github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -23,11 +22,7 @@ import (
func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics) { func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Full, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
if op.LockState { op.StateLocker = op.StateLocker.WithContext(context.Background())
op.StateLocker = clistate.NewLocker(context.Background(), op.StateLockTimeout, b.CLI, b.cliColorize())
} else {
op.StateLocker = clistate.NewNoopLocker()
}
// Get the remote workspace name. // Get the remote workspace name.
remoteWorkspaceName := b.getRemoteWorkspaceName(op.Workspace) remoteWorkspaceName := b.getRemoteWorkspaceName(op.Workspace)
@ -41,8 +36,7 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
} }
log.Printf("[TRACE] backend/remote: requesting state lock for workspace %q", remoteWorkspaceName) log.Printf("[TRACE] backend/remote: requesting state lock for workspace %q", remoteWorkspaceName)
if err := op.StateLocker.Lock(stateMgr, op.Type.String()); err != nil { if diags := op.StateLocker.Lock(stateMgr, op.Type.String()); diags.HasErrors() {
diags = diags.Append(errwrap.Wrapf("Error locking state: {{err}}", err))
return nil, nil, diags return nil, nil, diags
} }
@ -50,10 +44,7 @@ func (b *Remote) Context(op *backend.Operation) (*terraform.Context, statemgr.Fu
// If we're returning with errors, and thus not producing a valid // If we're returning with errors, and thus not producing a valid
// context, we'll want to avoid leaving the remote workspace locked. // context, we'll want to avoid leaving the remote workspace locked.
if diags.HasErrors() { if diags.HasErrors() {
err := op.StateLocker.Unlock(nil) diags = diags.Append(op.StateLocker.Unlock())
if err != nil {
diags = diags.Append(errwrap.Wrapf("Error unlocking state: {{err}}", err))
}
} }
}() }()

View File

@ -6,8 +6,12 @@ import (
tfe "github.com/hashicorp/go-tfe" tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty"
) )
@ -183,11 +187,14 @@ func TestRemoteContextWithVars(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
streams, _ := terminal.StreamsForTesting(t)
view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
op := &backend.Operation{ op := &backend.Operation{
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
StateLocker: clistate.NewLocker(0, view),
Workspace: backend.DefaultStateName, Workspace: backend.DefaultStateName,
LockState: true,
} }
v := test.Opts v := test.Opts

View File

@ -260,15 +260,16 @@ in order to capture the filesystem context the remote workspace expects:
return r, generalError("Failed to create run", err) return r, generalError("Failed to create run", err)
} }
// When the lock timeout is set, // When the lock timeout is set, if the run is still pending and
if op.StateLockTimeout > 0 { // cancellable after that period, we attempt to cancel it.
if lockTimeout := op.StateLocker.Timeout(); lockTimeout > 0 {
go func() { go func() {
select { select {
case <-stopCtx.Done(): case <-stopCtx.Done():
return return
case <-cancelCtx.Done(): case <-cancelCtx.Done():
return return
case <-time.After(op.StateLockTimeout): case <-time.After(lockTimeout):
// Retrieve the run to get its current status. // Retrieve the run to get its current status.
r, err := b.client.Runs.Read(cancelCtx, r.ID) r, err := b.client.Runs.Read(cancelCtx, r.ID)
if err != nil { if err != nil {

View File

@ -13,7 +13,11 @@ import (
tfe "github.com/hashicorp/go-tfe" tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/initwd" "github.com/hashicorp/terraform/internal/initwd"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/plans/planfile" "github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -24,14 +28,24 @@ import (
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) { func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) {
t.Helper() t.Helper()
return testOperationPlanWithTimeout(t, configDir, 0)
}
func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func()) {
t.Helper()
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
streams, _ := terminal.StreamsForTesting(t)
view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
return &backend.Operation{ return &backend.Operation{
ConfigDir: configDir, ConfigDir: configDir,
ConfigLoader: configLoader, ConfigLoader: configLoader,
Parallelism: defaultParallelism, Parallelism: defaultParallelism,
PlanRefresh: true, PlanRefresh: true,
ShowDiagnostics: testLogDiagnostics(t), ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewLocker(timeout, view),
Type: backend.OperationTypePlan, Type: backend.OperationTypePlan,
}, configCleanup }, configCleanup
} }
@ -625,7 +639,7 @@ func TestRemote_planLockTimeout(t *testing.T) {
t.Fatalf("error creating pending run: %v", err) t.Fatalf("error creating pending run: %v", err)
} }
op, configCleanup := testOperationPlan(t, "./testdata/plan") op, configCleanup := testOperationPlanWithTimeout(t, "./testdata/plan", 50)
defer configCleanup() defer configCleanup()
input := testInput(t, map[string]string{ input := testInput(t, map[string]string{
@ -633,7 +647,6 @@ func TestRemote_planLockTimeout(t *testing.T) {
"approve": "yes", "approve": "yes",
}) })
op.StateLockTimeout = 50 * time.Millisecond
op.UIIn = input op.UIIn = input
op.UIOut = b.CLI op.UIOut = b.CLI
op.Workspace = backend.DefaultStateName op.Workspace = backend.DefaultStateName

View File

@ -940,6 +940,7 @@ func TestApply_planNoModuleFiles(t *testing.T) {
p := applyFixtureProvider() p := applyFixtureProvider()
planPath := applyFixturePlanFile(t) planPath := applyFixturePlanFile(t)
view, _ := testView(t)
view, _ := testView(t) view, _ := testView(t)
apply := &ApplyCommand{ apply := &ApplyCommand{

View File

@ -7,33 +7,25 @@ package clistate
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"sync" "sync"
"time" "time"
"github.com/hashicorp/errwrap" "github.com/hashicorp/terraform/command/views"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/internal/helper/slowmessage" "github.com/hashicorp/terraform/internal/helper/slowmessage"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/mitchellh/cli" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/colorstring"
) )
const ( const (
LockThreshold = 400 * time.Millisecond LockThreshold = 400 * time.Millisecond
LockMessage = "Acquiring state lock. This may take a few moments..." LockErrorMessage = `Error message: %s
LockErrorMessage = `Error acquiring the state lock: {{err}}
Terraform acquires a state lock to protect the state from being written Terraform acquires a state lock to protect the state from being written
by multiple users at the same time. Please resolve the issue above and try by multiple users at the same time. Please resolve the issue above and try
again. For most commands, you can disable locking with the "-lock=false" again. For most commands, you can disable locking with the "-lock=false"
flag, but this is not recommended.` flag, but this is not recommended.`
UnlockMessage = "Releasing state lock. This may take a few moments..." UnlockErrorMessage = `Error message: %s
UnlockErrorMessage = `
[reset][bold][red]Error releasing the state lock![reset][red]
Error message: %s
Terraform acquires a lock when accessing your state to prevent others Terraform acquires a lock when accessing your state to prevent others
running Terraform to potentially modify the state at the same time. An running Terraform to potentially modify the state at the same time. An
@ -46,8 +38,7 @@ In this scenario, please call the "force-unlock" command to unlock the
state manually. This is a very dangerous operation since if it is done state manually. This is a very dangerous operation since if it is done
erroneously it could result in two people modifying state at the same time. erroneously it could result in two people modifying state at the same time.
Only call this command if you're certain that the unlock above failed and Only call this command if you're certain that the unlock above failed and
that no one else is holding a lock. that no one else is holding a lock.`
`
) )
// Locker allows for more convenient usage of the lower-level statemgr.Locker // Locker allows for more convenient usage of the lower-level statemgr.Locker
@ -60,12 +51,17 @@ that no one else is holding a lock.
// Unlock, which is at a minimum the LockID string returned by the // Unlock, which is at a minimum the LockID string returned by the
// statemgr.Locker. // statemgr.Locker.
type Locker interface { type Locker interface {
// Returns a shallow copy of the locker with its context changed to ctx.
WithContext(ctx context.Context) Locker
// Lock the provided state manager, storing the reason string in the LockInfo. // Lock the provided state manager, storing the reason string in the LockInfo.
Lock(s statemgr.Locker, reason string) error Lock(s statemgr.Locker, reason string) tfdiags.Diagnostics
// Unlock the previously locked state. // Unlock the previously locked state.
// An optional error can be passed in, and will be combined with any error Unlock() tfdiags.Diagnostics
// from the Unlock operation.
Unlock(error) error // Timeout returns the configured timeout duration
Timeout() time.Duration
} }
type locker struct { type locker struct {
@ -73,34 +69,43 @@ type locker struct {
ctx context.Context ctx context.Context
timeout time.Duration timeout time.Duration
state statemgr.Locker state statemgr.Locker
ui cli.Ui view views.StateLocker
color *colorstring.Colorize
lockID string lockID string
} }
var _ Locker = (*locker)(nil)
// Create a new Locker. // Create a new Locker.
// This Locker uses state.LockWithContext to retry the lock until the provided // This Locker uses state.LockWithContext to retry the lock until the provided
// timeout is reached, or the context is canceled. Lock progress will be be // timeout is reached, or the context is canceled. Lock progress will be be
// reported to the user through the provided UI. // reported to the user through the provided UI.
func NewLocker( func NewLocker(timeout time.Duration, view views.StateLocker) Locker {
ctx context.Context, return &locker{
timeout time.Duration, ctx: context.Background(),
ui cli.Ui,
color *colorstring.Colorize) Locker {
l := &locker{
ctx: ctx,
timeout: timeout, timeout: timeout,
ui: ui, view: view,
color: color, }
}
// WithContext returns a new Locker with the specified context, copying the
// timeout and view parameters from the original Locker.
func (l *locker) WithContext(ctx context.Context) Locker {
if ctx == nil {
panic("nil context")
}
return &locker{
ctx: ctx,
timeout: l.timeout,
view: l.view,
} }
return l
} }
// Locker locks the given state and outputs to the user if locking is taking // Locker locks the given state and outputs to the user if locking is taking
// longer than the threshold. The lock is retried until the context is // longer than the threshold. The lock is retried until the context is
// cancelled. // cancelled.
func (l *locker) Lock(s statemgr.Locker, reason string) error { func (l *locker) Lock(s statemgr.Locker, reason string) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()
@ -116,46 +121,49 @@ func (l *locker) Lock(s statemgr.Locker, reason string) error {
id, err := statemgr.LockWithContext(ctx, s, lockInfo) id, err := statemgr.LockWithContext(ctx, s, lockInfo)
l.lockID = id l.lockID = id
return err return err
}, func() { }, l.view.Locking)
if l.ui != nil {
l.ui.Output(l.color.Color(LockMessage))
}
})
if err != nil { if err != nil {
return errwrap.Wrapf(strings.TrimSpace(LockErrorMessage), err) diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Error acquiring the state lock",
fmt.Sprintf(LockErrorMessage, err),
))
} }
return nil return diags
} }
func (l *locker) Unlock(parentErr error) error { func (l *locker) Unlock() tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
l.mu.Lock() l.mu.Lock()
defer l.mu.Unlock() defer l.mu.Unlock()
if l.lockID == "" { if l.lockID == "" {
return parentErr return diags
} }
err := slowmessage.Do(LockThreshold, func() error { err := slowmessage.Do(LockThreshold, func() error {
return l.state.Unlock(l.lockID) return l.state.Unlock(l.lockID)
}, func() { }, l.view.Unlocking)
if l.ui != nil {
l.ui.Output(l.color.Color(UnlockMessage))
}
})
if err != nil { if err != nil {
l.ui.Output(l.color.Color(fmt.Sprintf( diags = diags.Append(tfdiags.Sourceless(
"\n"+strings.TrimSpace(UnlockErrorMessage)+"\n", err))) tfdiags.Error,
"Error releasing the state lock",
parentErr = multierror.Append(parentErr, err) fmt.Sprintf(UnlockErrorMessage, err),
))
} }
return parentErr return diags
} }
func (l *locker) Timeout() time.Duration {
return l.timeout
}
type noopLocker struct{} type noopLocker struct{}
// NewNoopLocker returns a valid Locker that does nothing. // NewNoopLocker returns a valid Locker that does nothing.
@ -163,10 +171,20 @@ func NewNoopLocker() Locker {
return noopLocker{} return noopLocker{}
} }
func (l noopLocker) Lock(statemgr.Locker, string) error { var _ Locker = noopLocker{}
func (l noopLocker) WithContext(ctx context.Context) Locker {
return l
}
func (l noopLocker) Lock(statemgr.Locker, string) tfdiags.Diagnostics {
return nil return nil
} }
func (l noopLocker) Unlock(err error) error { func (l noopLocker) Unlock() tfdiags.Diagnostics {
return err return nil
}
func (l noopLocker) Timeout() time.Duration {
return 0
} }

View File

@ -1,23 +1,24 @@
package clistate package clistate
import ( import (
"context"
"testing" "testing"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
) )
func TestUnlock(t *testing.T) { func TestUnlock(t *testing.T) {
ui := new(cli.MockUi) streams, _ := terminal.StreamsForTesting(t)
view := views.NewView(streams)
l := NewLocker(context.Background(), 0, ui, &colorstring.Colorize{Disable: true}) l := NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view))
l.Lock(statemgr.NewUnlockErrorFull(nil, nil), "test-lock") l.Lock(statemgr.NewUnlockErrorFull(nil, nil), "test-lock")
err := l.Unlock(nil) diags := l.Unlock()
if err != nil { if diags.HasErrors() {
t.Log(err.Error()) t.Log(diags.Err().Error())
} else { } else {
t.Error("expected error") t.Error("expected error")
} }

View File

@ -104,9 +104,9 @@ func (c *ConsoleCommand) Run(args []string) int {
// Successfully creating the context can result in a lock, so ensure we release it // Successfully creating the context can result in a lock, so ensure we release it
defer func() { defer func() {
err := opReq.StateLocker.Unlock(nil) diags := opReq.StateLocker.Unlock()
if err != nil { if diags.HasErrors() {
c.Ui.Error(err.Error()) c.showDiagnostics(diags)
} }
}() }()

View File

@ -27,10 +27,12 @@ func TestConsole_basic(t *testing.T) {
p := testProvider() p := testProvider()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ConsoleCommand{ c := &ConsoleCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -76,10 +78,12 @@ func TestConsole_tfvars(t *testing.T) {
}, },
} }
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ConsoleCommand{ c := &ConsoleCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -127,10 +131,12 @@ func TestConsole_unsetRequiredVars(t *testing.T) {
}, },
} }
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ConsoleCommand{ c := &ConsoleCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -159,10 +165,12 @@ func TestConsole_variables(t *testing.T) {
p := testProvider() p := testProvider()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ConsoleCommand{ c := &ConsoleCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -200,11 +208,13 @@ func TestConsole_modules(t *testing.T) {
p := applyFixtureProvider() p := applyFixtureProvider()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ConsoleCommand{ c := &ConsoleCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }

View File

@ -218,9 +218,9 @@ func (c *ImportCommand) Run(args []string) int {
// Successfully creating the context can result in a lock, so ensure we release it // Successfully creating the context can result in a lock, so ensure we release it
defer func() { defer func() {
err := opReq.StateLocker.Unlock(nil) diags := opReq.StateLocker.Unlock()
if err != nil { if diags.HasErrors() {
c.Ui.Error(err.Error()) c.showDiagnostics(diags)
} }
}() }()

View File

@ -35,10 +35,12 @@ func TestInit_empty(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -56,10 +58,12 @@ func TestInit_multipleArgs(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -80,10 +84,12 @@ func TestInit_fromModule_cwdDest(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -130,10 +136,12 @@ func TestInit_fromModule_dstInSrc(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -157,10 +165,12 @@ func TestInit_get(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -184,10 +194,12 @@ func TestInit_getUpgradeModules(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -214,10 +226,12 @@ func TestInit_backend(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -242,10 +256,12 @@ func TestInit_backendUnset(t *testing.T) {
log.Printf("[TRACE] TestInit_backendUnset: beginning first init") log.Printf("[TRACE] TestInit_backendUnset: beginning first init")
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -272,10 +288,12 @@ func TestInit_backendUnset(t *testing.T) {
} }
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -303,10 +321,12 @@ func TestInit_backendConfigFile(t *testing.T) {
t.Run("good-config-file", func(t *testing.T) { t.Run("good-config-file", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
args := []string{"-backend-config", "input.config"} args := []string{"-backend-config", "input.config"}
@ -324,10 +344,12 @@ func TestInit_backendConfigFile(t *testing.T) {
// the backend config file must not be a full terraform block // the backend config file must not be a full terraform block
t.Run("full-backend-config-file", func(t *testing.T) { t.Run("full-backend-config-file", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
args := []string{"-backend-config", "backend.config"} args := []string{"-backend-config", "backend.config"}
@ -342,10 +364,12 @@ func TestInit_backendConfigFile(t *testing.T) {
// the backend config file must match the schema for the backend // the backend config file must match the schema for the backend
t.Run("invalid-config-file", func(t *testing.T) { t.Run("invalid-config-file", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
args := []string{"-backend-config", "invalid.config"} args := []string{"-backend-config", "invalid.config"}
@ -360,10 +384,12 @@ func TestInit_backendConfigFile(t *testing.T) {
// missing file is an error // missing file is an error
t.Run("missing-config-file", func(t *testing.T) { t.Run("missing-config-file", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
args := []string{"-backend-config", "missing.config"} args := []string{"-backend-config", "missing.config"}
@ -378,10 +404,12 @@ func TestInit_backendConfigFile(t *testing.T) {
// blank filename clears the backend config // blank filename clears the backend config
t.Run("blank-config-file", func(t *testing.T) { t.Run("blank-config-file", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
args := []string{"-backend-config="} args := []string{"-backend-config="}
@ -429,10 +457,12 @@ func TestInit_backendConfigFilePowershellConfusion(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -468,10 +498,12 @@ func TestInit_backendConfigFileChange(t *testing.T) {
})() })()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -495,10 +527,12 @@ func TestInit_backendConfigKV(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -522,10 +556,12 @@ func TestInit_backendConfigKVReInit(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -539,6 +575,7 @@ func TestInit_backendConfigKVReInit(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -583,10 +620,12 @@ func TestInit_backendConfigKVReInitWithConfigDiff(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -600,6 +639,7 @@ func TestInit_backendConfigKVReInitWithConfigDiff(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -629,10 +669,12 @@ func TestInit_backendCli_no_config_block(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -667,10 +709,12 @@ func TestInit_backendReinitWithExtra(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -710,10 +754,12 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -741,6 +787,7 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -766,10 +813,12 @@ func TestInit_inputFalse(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -805,6 +854,7 @@ func TestInit_inputFalse(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -823,6 +873,7 @@ func TestInit_inputFalse(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -842,6 +893,7 @@ func TestInit_getProvider(t *testing.T) {
overrides := metaOverridesForProvider(testProvider()) overrides := metaOverridesForProvider(testProvider())
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
providerSource, close := newMockProviderSource(t, map[string][]string{ providerSource, close := newMockProviderSource(t, map[string][]string{
// looking for an exact version // looking for an exact version
"exact": {"1.2.3"}, "exact": {"1.2.3"},
@ -854,6 +906,7 @@ func TestInit_getProvider(t *testing.T) {
m := Meta{ m := Meta{
testingOverrides: overrides, testingOverrides: overrides,
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -919,7 +972,9 @@ func TestInit_getProvider(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m.Ui = ui m.Ui = ui
m.View = view
c := &InitCommand{ c := &InitCommand{
Meta: m, Meta: m,
} }
@ -944,6 +999,7 @@ func TestInit_getProviderSource(t *testing.T) {
overrides := metaOverridesForProvider(testProvider()) overrides := metaOverridesForProvider(testProvider())
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
providerSource, close := newMockProviderSource(t, map[string][]string{ providerSource, close := newMockProviderSource(t, map[string][]string{
// looking for an exact version // looking for an exact version
"acme/alpha": {"1.2.3"}, "acme/alpha": {"1.2.3"},
@ -955,6 +1011,7 @@ func TestInit_getProviderSource(t *testing.T) {
m := Meta{ m := Meta{
testingOverrides: overrides, testingOverrides: overrides,
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -993,6 +1050,7 @@ func TestInit_getProviderLegacyFromState(t *testing.T) {
overrides := metaOverridesForProvider(testProvider()) overrides := metaOverridesForProvider(testProvider())
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
providerSource, close := newMockProviderSource(t, map[string][]string{ providerSource, close := newMockProviderSource(t, map[string][]string{
"acme/alpha": {"1.2.3"}, "acme/alpha": {"1.2.3"},
}) })
@ -1000,6 +1058,7 @@ func TestInit_getProviderLegacyFromState(t *testing.T) {
m := Meta{ m := Meta{
testingOverrides: overrides, testingOverrides: overrides,
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1033,6 +1092,7 @@ func TestInit_getProviderInvalidPackage(t *testing.T) {
overrides := metaOverridesForProvider(testProvider()) overrides := metaOverridesForProvider(testProvider())
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
// create a provider source which allows installing an invalid package // create a provider source which allows installing an invalid package
addr := addrs.MustParseProviderSourceString("invalid/package") addr := addrs.MustParseProviderSourceString("invalid/package")
@ -1053,6 +1113,7 @@ func TestInit_getProviderInvalidPackage(t *testing.T) {
m := Meta{ m := Meta{
testingOverrides: overrides, testingOverrides: overrides,
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1109,8 +1170,10 @@ func TestInit_getProviderDetectedLegacy(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
Ui: ui, Ui: ui,
View: view,
ProviderSource: multiSource, ProviderSource: multiSource,
} }
@ -1166,9 +1229,11 @@ func TestInit_providerSource(t *testing.T) {
defer close() defer close()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1277,9 +1342,11 @@ func TestInit_cancel(t *testing.T) {
close(shutdownCh) close(shutdownCh)
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
ShutdownCh: shutdownCh, ShutdownCh: shutdownCh,
} }
@ -1320,9 +1387,11 @@ func TestInit_getUpgradePlugins(t *testing.T) {
defer close() defer close()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1444,9 +1513,11 @@ func TestInit_getProviderMissing(t *testing.T) {
defer close() defer close()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1472,10 +1543,12 @@ func TestInit_checkRequiredVersion(t *testing.T) {
defer testChdir(t, td)() defer testChdir(t, td)()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -1505,9 +1578,11 @@ func TestInit_providerLockFile(t *testing.T) {
defer close() defer close()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1555,10 +1630,12 @@ func TestInit_pluginDirReset(t *testing.T) {
defer close() defer close()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &InitCommand{ c := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
}, },
} }
@ -1591,6 +1668,7 @@ func TestInit_pluginDirReset(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, // still empty ProviderSource: providerSource, // still empty
}, },
} }
@ -1623,9 +1701,11 @@ func TestInit_pluginDirProviders(t *testing.T) {
defer close() defer close()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1723,9 +1803,11 @@ func TestInit_pluginDirProvidersDoesNotGet(t *testing.T) {
defer close() defer close()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1795,9 +1877,11 @@ func TestInit_pluginDirWithBuiltIn(t *testing.T) {
defer close() defer close()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }
@ -1832,9 +1916,11 @@ func TestInit_invalidBuiltInProviders(t *testing.T) {
defer close() defer close()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }

View File

@ -18,7 +18,9 @@ import (
"github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/hcl/v2/hcldec"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
remoteBackend "github.com/hashicorp/terraform/backend/remote" remoteBackend "github.com/hashicorp/terraform/backend/remote"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
@ -341,6 +343,12 @@ func (m *Meta) Operation(b backend.Backend) *backend.Operation {
panic(fmt.Sprintf("failed to encode backend configuration for plan: %s", err)) panic(fmt.Sprintf("failed to encode backend configuration for plan: %s", err))
} }
stateLocker := clistate.NewNoopLocker()
if m.stateLock {
view := views.NewStateLocker(arguments.ViewHuman, m.View)
stateLocker = clistate.NewLocker(m.stateLockTimeout, view)
}
return &backend.Operation{ return &backend.Operation{
PlanOutBackend: planOutBackend, PlanOutBackend: planOutBackend,
Parallelism: m.parallelism, Parallelism: m.parallelism,
@ -348,8 +356,7 @@ func (m *Meta) Operation(b backend.Backend) *backend.Operation {
UIIn: m.UIInput(), UIIn: m.UIInput(),
UIOut: m.Ui, UIOut: m.Ui,
Workspace: workspace, Workspace: workspace,
LockState: m.stateLock, StateLocker: stateLocker,
StateLockTimeout: m.stateLockTimeout,
} }
} }
@ -802,12 +809,13 @@ func (m *Meta) backend_C_r_s(c *configs.Backend, cHash int, sMgr *clistate.Local
} }
if m.stateLock { if m.stateLock {
stateLocker := clistate.NewLocker(context.Background(), m.stateLockTimeout, m.Ui, m.Colorize()) view := views.NewStateLocker(arguments.ViewHuman, m.View)
stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
return nil, diags return nil, diags
} }
defer stateLocker.Unlock(nil) defer stateLocker.Unlock()
} }
configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType()) configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType())
@ -886,12 +894,13 @@ func (m *Meta) backend_C_r_S_changed(c *configs.Backend, cHash int, sMgr *clista
} }
if m.stateLock { if m.stateLock {
stateLocker := clistate.NewLocker(context.Background(), m.stateLockTimeout, m.Ui, m.Colorize()) view := views.NewStateLocker(arguments.ViewHuman, m.View)
stateLocker := clistate.NewLocker(m.stateLockTimeout, view)
if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil { if err := stateLocker.Lock(sMgr, "backend from plan"); err != nil {
diags = diags.Append(fmt.Errorf("Error locking state: %s", err)) diags = diags.Append(fmt.Errorf("Error locking state: %s", err))
return nil, diags return nil, diags
} }
defer stateLocker.Unlock(nil) defer stateLocker.Unlock()
} }
configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType()) configJSON, err := ctyjson.Marshal(configVal, b.ConfigSchema().ImpliedType())

View File

@ -12,7 +12,9 @@ import (
"strings" "strings"
"github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
@ -325,17 +327,20 @@ func (m *Meta) backendMigrateState_s_s(opts *backendMigrateOpts) error {
if m.stateLock { if m.stateLock {
lockCtx := context.Background() lockCtx := context.Background()
lockerOne := clistate.NewLocker(lockCtx, m.stateLockTimeout, m.Ui, m.Colorize()) view := views.NewStateLocker(arguments.ViewHuman, m.View)
if err := lockerOne.Lock(stateOne, "migration source state"); err != nil { locker := clistate.NewLocker(m.stateLockTimeout, view)
return fmt.Errorf("Error locking source state: %s", err)
}
defer lockerOne.Unlock(nil)
lockerTwo := clistate.NewLocker(lockCtx, m.stateLockTimeout, m.Ui, m.Colorize()) lockerOne := locker.WithContext(lockCtx)
if err := lockerTwo.Lock(stateTwo, "migration destination state"); err != nil { if diags := lockerOne.Lock(stateOne, "migration source state"); diags.HasErrors() {
return fmt.Errorf("Error locking destination state: %s", err) return diags.Err()
} }
defer lockerTwo.Unlock(nil) defer lockerOne.Unlock()
lockerTwo := locker.WithContext(lockCtx)
if diags := lockerTwo.Lock(stateTwo, "migration destination state"); diags.HasErrors() {
return diags.Err()
}
defer lockerTwo.Unlock()
// We now own a lock, so double check that we have the version // We now own a lock, so double check that we have the version
// corresponding to the lock. // corresponding to the lock.

View File

@ -1876,6 +1876,8 @@ func TestBackendFromState(t *testing.T) {
func testMetaBackend(t *testing.T, args []string) *Meta { func testMetaBackend(t *testing.T, args []string) *Meta {
var m Meta var m Meta
m.Ui = new(cli.MockUi) m.Ui = new(cli.MockUi)
view, _ := testView(t)
m.View = view
m.process(args) m.process(args)
f := m.extendedFlagSet("test") f := m.extendedFlagSet("test")
if err := f.Parse(args); err != nil { if err := f.Parse(args); err != nil {

View File

@ -22,10 +22,12 @@ import (
func TestShow(t *testing.T) { func TestShow(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -46,10 +48,12 @@ func TestShow_noArgs(t *testing.T) {
defer testChdir(t, stateDir)() defer testChdir(t, stateDir)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -93,10 +97,12 @@ func TestShow_aliasedProvider(t *testing.T) {
defer testChdir(t, stateDir)() defer testChdir(t, stateDir)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -121,10 +127,12 @@ func TestShow_noArgsNoState(t *testing.T) {
defer testChdir(t, stateDir)() defer testChdir(t, stateDir)()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -139,10 +147,12 @@ func TestShow_plan(t *testing.T) {
planPath := testPlanFileNoop(t) planPath := testPlanFileNoop(t)
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -164,10 +174,12 @@ func TestShow_planWithChanges(t *testing.T) {
planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate) planPathWithChanges := showFixturePlanFile(t, plans.DeleteThenCreate)
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(showFixtureProvider()), testingOverrides: metaOverridesForProvider(showFixtureProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -190,10 +202,12 @@ func TestShow_plan_json(t *testing.T) {
planPath := showFixturePlanFile(t, plans.Create) planPath := showFixturePlanFile(t, plans.Create)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(showFixtureProvider()), testingOverrides: metaOverridesForProvider(showFixtureProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -212,10 +226,12 @@ func TestShow_state(t *testing.T) {
defer os.RemoveAll(filepath.Dir(statePath)) defer os.RemoveAll(filepath.Dir(statePath))
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &ShowCommand{ c := &ShowCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -354,9 +370,11 @@ func TestShow_json_output_state(t *testing.T) {
p := showFixtureProvider() p := showFixtureProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
m := Meta{ m := Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
ProviderSource: providerSource, ProviderSource: providerSource,
} }

View File

@ -1,12 +1,13 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -49,12 +50,16 @@ func (c *StateMvCommand) Run(args []string) int {
} }
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateFromMgr, "state-mv"); err != nil { if diags := stateLocker.Lock(stateFromMgr, "state-mv"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking source state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
if err := stateFromMgr.RefreshState(); err != nil { if err := stateFromMgr.RefreshState(); err != nil {
@ -83,12 +88,16 @@ func (c *StateMvCommand) Run(args []string) int {
} }
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateToMgr, "state-mv"); err != nil { if diags := stateLocker.Lock(stateToMgr, "state-mv"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking destination state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
if err := stateToMgr.RefreshState(); err != nil { if err := stateToMgr.RefreshState(); err != nil {

View File

@ -58,11 +58,13 @@ func TestStateMv(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -204,11 +206,13 @@ func TestStateMv_resourceToInstance(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -277,12 +281,14 @@ func TestStateMv_resourceToInstanceErr(t *testing.T) {
p := testProvider() p := testProvider()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -344,11 +350,13 @@ func TestStateMv_resourceToInstanceErrInAutomation(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
RunningInAutomation: true, RunningInAutomation: true,
}, },
}, },
@ -416,11 +424,13 @@ func TestStateMv_instanceToResource(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -489,11 +499,13 @@ func TestStateMv_instanceToNewResource(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -560,11 +572,13 @@ func TestStateMv_differentResourceTypes(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -636,10 +650,12 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
// init our backend // init our backend
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
ic := &InitCommand{ ic := &InitCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -656,6 +672,7 @@ func TestStateMv_explicitWithBackend(t *testing.T) {
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -713,11 +730,13 @@ func TestStateMv_backupExplicit(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -762,11 +781,13 @@ func TestStateMv_stateOutNew(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -834,11 +855,13 @@ func TestStateMv_stateOutExisting(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -877,11 +900,13 @@ func TestStateMv_noState(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -945,11 +970,13 @@ func TestStateMv_stateOutNew_count(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -1019,11 +1046,13 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -1089,11 +1118,13 @@ func TestStateMv_stateOutNew_nestedModule(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -1145,11 +1176,13 @@ func TestStateMv_toNewModule(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -1244,11 +1277,13 @@ func TestStateMv_withinBackend(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -1314,11 +1349,13 @@ func TestStateMv_fromBackendToLocal(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -1365,11 +1402,13 @@ func TestStateMv_onlyResourceInModule(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateMvCommand{ c := &StateMvCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }

View File

@ -1,13 +1,14 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings" "strings"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states/statefile" "github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/states/statemgr"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -93,12 +94,16 @@ func (c *StatePushCommand) Run(args []string) int {
} }
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "state-push"); err != nil { if diags := stateLocker.Lock(stateMgr, "state-push"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
if err := stateMgr.RefreshState(); err != nil { if err := stateMgr.RefreshState(); err != nil {

View File

@ -23,10 +23,12 @@ func TestStatePush_empty(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -50,10 +52,12 @@ func TestStatePush_lockedState(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -83,10 +87,12 @@ func TestStatePush_replaceMatch(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -119,10 +125,12 @@ func TestStatePush_replaceMatchStdin(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -148,10 +156,12 @@ func TestStatePush_lineageMismatch(t *testing.T) {
p := testProvider() p := testProvider()
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -177,10 +187,12 @@ func TestStatePush_serialNewer(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -206,10 +218,12 @@ func TestStatePush_serialOlder(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -236,8 +250,9 @@ func TestStatePush_forceRemoteState(t *testing.T) {
// init the backend // init the backend
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
initCmd := &InitCommand{ initCmd := &InitCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
if code := initCmd.Run([]string{}); code != 0 { if code := initCmd.Run([]string{}); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
@ -246,7 +261,7 @@ func TestStatePush_forceRemoteState(t *testing.T) {
// create a new workspace // create a new workspace
ui = new(cli.MockUi) ui = new(cli.MockUi)
newCmd := &WorkspaceNewCommand{ newCmd := &WorkspaceNewCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
if code := newCmd.Run([]string{"test"}); code != 0 { if code := newCmd.Run([]string{"test"}); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -268,7 +283,7 @@ func TestStatePush_forceRemoteState(t *testing.T) {
// push our local state to that new workspace // push our local state to that new workspace
ui = new(cli.MockUi) ui = new(cli.MockUi)
c := &StatePushCommand{ c := &StatePushCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
args := []string{"-force", statePath} args := []string{"-force", statePath}

View File

@ -1,12 +1,13 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -74,12 +75,16 @@ func (c *StateReplaceProviderCommand) Run(args []string) int {
// Acquire lock if requested // Acquire lock if requested
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "state-replace-provider"); err != nil { if diags := stateLocker.Lock(stateMgr, "state-replace-provider"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking source state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
// Refresh and load state // Refresh and load state

View File

@ -65,10 +65,12 @@ func TestStateReplaceProvider(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -99,10 +101,12 @@ func TestStateReplaceProvider(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -133,10 +137,12 @@ func TestStateReplaceProvider(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -166,10 +172,12 @@ func TestStateReplaceProvider(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -193,10 +201,12 @@ func TestStateReplaceProvider(t *testing.T) {
t.Run("invalid flags", func(t *testing.T) { t.Run("invalid flags", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -217,10 +227,12 @@ func TestStateReplaceProvider(t *testing.T) {
t.Run("wrong number of arguments", func(t *testing.T) { t.Run("wrong number of arguments", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -237,10 +249,12 @@ func TestStateReplaceProvider(t *testing.T) {
t.Run("invalid provider strings", func(t *testing.T) { t.Run("invalid provider strings", func(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateReplaceProviderCommand{ c := &StateReplaceProviderCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }

View File

@ -1,12 +1,13 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -44,12 +45,16 @@ func (c *StateRmCommand) Run(args []string) int {
} }
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "state-rm"); err != nil { if diags := stateLocker.Lock(stateMgr, "state-rm"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
if err := stateMgr.RefreshState(); err != nil { if err := stateMgr.RefreshState(); err != nil {

View File

@ -49,11 +49,13 @@ func TestStateRm(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -117,11 +119,13 @@ func TestStateRmNotChildModule(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -203,11 +207,13 @@ func TestStateRmNoArgs(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -262,11 +268,13 @@ func TestStateRmNonExist(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -318,11 +326,13 @@ func TestStateRm_backupExplicit(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -349,11 +359,13 @@ func TestStateRm_noState(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -372,11 +384,13 @@ func TestStateRm_needsInit(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }
@ -446,11 +460,13 @@ func TestStateRm_backendState(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &StateRmCommand{ c := &StateRmCommand{
StateMeta{ StateMeta{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
}, },
} }

View File

@ -1,13 +1,14 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
@ -116,12 +117,16 @@ func (c *TaintCommand) Run(args []string) int {
} }
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "taint"); err != nil { if diags := stateLocker.Lock(stateMgr, "taint"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
if err := stateMgr.RefreshState(); err != nil { if err := stateMgr.RefreshState(); err != nil {

View File

@ -33,9 +33,11 @@ func TestTaint(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -76,9 +78,11 @@ func TestTaint_lockedState(t *testing.T) {
} }
defer unlock() defer unlock()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -122,9 +126,11 @@ func TestTaint_backup(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -165,9 +171,11 @@ func TestTaint_backupDisable(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -188,9 +196,11 @@ func TestTaint_backupDisable(t *testing.T) {
func TestTaint_badState(t *testing.T) { func TestTaint_badState(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -229,9 +239,11 @@ func TestTaint_defaultState(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -271,7 +283,8 @@ func TestTaint_defaultWorkspaceState(t *testing.T) {
path := testStateFileWorkspaceDefault(t, testWorkspace, state) path := testStateFileWorkspaceDefault(t, testWorkspace, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
meta := Meta{Ui: ui} view, _ := testView(t)
meta := Meta{Ui: ui, View: view}
meta.SetWorkspace(testWorkspace) meta.SetWorkspace(testWorkspace)
c := &TaintCommand{ c := &TaintCommand{
Meta: meta, Meta: meta,
@ -308,9 +321,11 @@ func TestTaint_missing(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -344,9 +359,11 @@ func TestTaint_missingAllow(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -399,9 +416,11 @@ func TestTaint_stateOut(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -453,9 +472,11 @@ func TestTaint_module(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -498,10 +519,12 @@ func TestTaint_checkRequiredVersion(t *testing.T) {
path := testStateFile(t, state) path := testStateFile(t, state)
ui := cli.NewMockUi() ui := cli.NewMockUi()
view, _ := testView(t)
c := &TaintCommand{ c := &TaintCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(testProvider()), testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui, Ui: ui,
View: view,
}, },
} }

View File

@ -34,10 +34,12 @@ func TestUnlock(t *testing.T) {
p := testProvider() p := testProvider()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UnlockCommand{ c := &UnlockCommand{
Meta: Meta{ Meta: Meta{
testingOverrides: metaOverridesForProvider(p), testingOverrides: metaOverridesForProvider(p),
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -72,9 +74,11 @@ func TestUnlock_inmemBackend(t *testing.T) {
// init backend // init backend
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
ci := &InitCommand{ ci := &InitCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
if code := ci.Run(nil); code != 0 { if code := ci.Run(nil); code != 0 {
@ -85,6 +89,7 @@ func TestUnlock_inmemBackend(t *testing.T) {
c := &UnlockCommand{ c := &UnlockCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -102,6 +107,7 @@ func TestUnlock_inmemBackend(t *testing.T) {
c = &UnlockCommand{ c = &UnlockCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }

View File

@ -1,12 +1,13 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
@ -81,12 +82,16 @@ func (c *UntaintCommand) Run(args []string) int {
} }
if c.stateLock { if c.stateLock {
stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "untaint"); err != nil { if diags := stateLocker.Lock(stateMgr, "untaint"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
if err := stateMgr.RefreshState(); err != nil { if err := stateMgr.RefreshState(); err != nil {

View File

@ -32,9 +32,11 @@ func TestUntaint(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -80,9 +82,11 @@ func TestUntaint_lockedState(t *testing.T) {
defer unlock() defer unlock()
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -126,9 +130,11 @@ func TestUntaint_backup(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -180,9 +186,11 @@ func TestUntaint_backupDisable(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -207,9 +215,11 @@ test_instance.foo:
func TestUntaint_badState(t *testing.T) { func TestUntaint_badState(t *testing.T) {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -248,9 +258,11 @@ func TestUntaint_defaultState(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -295,7 +307,8 @@ func TestUntaint_defaultWorkspaceState(t *testing.T) {
path := testStateFileWorkspaceDefault(t, testWorkspace, state) path := testStateFileWorkspaceDefault(t, testWorkspace, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
meta := Meta{Ui: ui} view, _ := testView(t)
meta := Meta{Ui: ui, View: view}
meta.SetWorkspace(testWorkspace) meta.SetWorkspace(testWorkspace)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: meta, Meta: meta,
@ -336,9 +349,11 @@ func TestUntaint_missing(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -372,9 +387,11 @@ func TestUntaint_missingAllow(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -427,9 +444,11 @@ func TestUntaint_stateOut(t *testing.T) {
testStateFileDefault(t, state) testStateFileDefault(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }
@ -489,9 +508,11 @@ func TestUntaint_module(t *testing.T) {
statePath := testStateFile(t, state) statePath := testStateFile(t, state)
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
c := &UntaintCommand{ c := &UntaintCommand{
Meta: Meta{ Meta: Meta{
Ui: ui, Ui: ui,
View: view,
}, },
} }

View File

@ -0,0 +1,40 @@
package views
import (
"fmt"
"github.com/hashicorp/terraform/command/arguments"
)
// The StateLocker view is used to display locking/unlocking status messages
// if the state lock process takes longer than expected.
type StateLocker interface {
Locking()
Unlocking()
}
// NewStateLocker returns an initialized StateLocker implementation for the given ViewType.
func NewStateLocker(vt arguments.ViewType, view *View) StateLocker {
switch vt {
case arguments.ViewHuman:
return &StateLockerHuman{View: *view}
default:
panic(fmt.Sprintf("unknown view type %v", vt))
}
}
// StateLockerHuman is an implementation of StateLocker which prints status to
// a terminal.
type StateLockerHuman struct {
View
}
var _ StateLocker = (*StateLockerHuman)(nil)
func (v *StateLockerHuman) Locking() {
v.streams.Println("Acquiring state lock. This may take a few moments...")
}
func (v *StateLockerHuman) Unlocking() {
v.streams.Println("Releasing state lock. This may take a few moments...")
}

View File

@ -34,7 +34,8 @@ func TestWorkspace_createAndChange(t *testing.T) {
args := []string{"test"} args := []string{"test"}
ui := new(cli.MockUi) ui := new(cli.MockUi)
newCmd.Meta = Meta{Ui: ui} view, _ := testView(t)
newCmd.Meta = Meta{Ui: ui, View: view}
if code := newCmd.Run(args); code != 0 { if code := newCmd.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
} }
@ -47,7 +48,7 @@ func TestWorkspace_createAndChange(t *testing.T) {
selCmd := &WorkspaceSelectCommand{} selCmd := &WorkspaceSelectCommand{}
args = []string{backend.DefaultStateName} args = []string{backend.DefaultStateName}
ui = new(cli.MockUi) ui = new(cli.MockUi)
selCmd.Meta = Meta{Ui: ui} selCmd.Meta = Meta{Ui: ui, View: view}
if code := selCmd.Run(args); code != 0 { if code := selCmd.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
} }
@ -83,8 +84,9 @@ func TestWorkspace_createAndList(t *testing.T) {
// create multiple workspaces // create multiple workspaces
for _, env := range envs { for _, env := range envs {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
newCmd := &WorkspaceNewCommand{ newCmd := &WorkspaceNewCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
if code := newCmd.Run([]string{env}); code != 0 { if code := newCmd.Run([]string{env}); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -93,7 +95,8 @@ func TestWorkspace_createAndList(t *testing.T) {
listCmd := &WorkspaceListCommand{} listCmd := &WorkspaceListCommand{}
ui := new(cli.MockUi) ui := new(cli.MockUi)
listCmd.Meta = Meta{Ui: ui} view, _ := testView(t)
listCmd.Meta = Meta{Ui: ui, View: view}
if code := listCmd.Run(nil); code != 0 { if code := listCmd.Run(nil); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -128,7 +131,8 @@ func TestWorkspace_createAndShow(t *testing.T) {
// make sure current workspace show outputs "default" // make sure current workspace show outputs "default"
showCmd := &WorkspaceShowCommand{} showCmd := &WorkspaceShowCommand{}
ui := new(cli.MockUi) ui := new(cli.MockUi)
showCmd.Meta = Meta{Ui: ui} view, _ := testView(t)
showCmd.Meta = Meta{Ui: ui, View: view}
if code := showCmd.Run(nil); code != 0 { if code := showCmd.Run(nil); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -147,21 +151,21 @@ func TestWorkspace_createAndShow(t *testing.T) {
// create test_a workspace // create test_a workspace
ui = new(cli.MockUi) ui = new(cli.MockUi)
newCmd.Meta = Meta{Ui: ui} newCmd.Meta = Meta{Ui: ui, View: view}
if code := newCmd.Run(env); code != 0 { if code := newCmd.Run(env); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
} }
selCmd := &WorkspaceSelectCommand{} selCmd := &WorkspaceSelectCommand{}
ui = new(cli.MockUi) ui = new(cli.MockUi)
selCmd.Meta = Meta{Ui: ui} selCmd.Meta = Meta{Ui: ui, View: view}
if code := selCmd.Run(env); code != 0 { if code := selCmd.Run(env); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
} }
showCmd = &WorkspaceShowCommand{} showCmd = &WorkspaceShowCommand{}
ui = new(cli.MockUi) ui = new(cli.MockUi)
showCmd.Meta = Meta{Ui: ui} showCmd.Meta = Meta{Ui: ui, View: view}
if code := showCmd.Run(nil); code != 0 { if code := showCmd.Run(nil); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -188,8 +192,9 @@ func TestWorkspace_createInvalid(t *testing.T) {
// create multiple workspaces // create multiple workspaces
for _, env := range envs { for _, env := range envs {
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
newCmd := &WorkspaceNewCommand{ newCmd := &WorkspaceNewCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
if code := newCmd.Run([]string{env}); code == 0 { if code := newCmd.Run([]string{env}); code == 0 {
t.Fatalf("expected failure: \n%s", ui.OutputWriter) t.Fatalf("expected failure: \n%s", ui.OutputWriter)
@ -199,7 +204,8 @@ func TestWorkspace_createInvalid(t *testing.T) {
// list workspaces to make sure none were created // list workspaces to make sure none were created
listCmd := &WorkspaceListCommand{} listCmd := &WorkspaceListCommand{}
ui := new(cli.MockUi) ui := new(cli.MockUi)
listCmd.Meta = Meta{Ui: ui} view, _ := testView(t)
listCmd.Meta = Meta{Ui: ui, View: view}
if code := listCmd.Run(nil); code != 0 { if code := listCmd.Run(nil); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -222,8 +228,9 @@ func TestWorkspace_createWithState(t *testing.T) {
// init the backend // init the backend
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
initCmd := &InitCommand{ initCmd := &InitCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
if code := initCmd.Run([]string{}); code != 0 { if code := initCmd.Run([]string{}); code != 0 {
t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
@ -257,7 +264,7 @@ func TestWorkspace_createWithState(t *testing.T) {
args := []string{"-state", "test.tfstate", workspace} args := []string{"-state", "test.tfstate", workspace}
ui = new(cli.MockUi) ui = new(cli.MockUi)
newCmd := &WorkspaceNewCommand{ newCmd := &WorkspaceNewCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
if code := newCmd.Run(args); code != 0 { if code := newCmd.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter) t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter)
@ -303,8 +310,9 @@ func TestWorkspace_delete(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
delCmd := &WorkspaceDeleteCommand{ delCmd := &WorkspaceDeleteCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
current, _ := delCmd.Workspace() current, _ := delCmd.Workspace()
@ -352,8 +360,9 @@ func TestWorkspace_deleteInvalid(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
delCmd := &WorkspaceDeleteCommand{ delCmd := &WorkspaceDeleteCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
// delete the workspace // delete the workspace
@ -406,8 +415,9 @@ func TestWorkspace_deleteWithState(t *testing.T) {
} }
ui := new(cli.MockUi) ui := new(cli.MockUi)
view, _ := testView(t)
delCmd := &WorkspaceDeleteCommand{ delCmd := &WorkspaceDeleteCommand{
Meta: Meta{Ui: ui}, Meta: Meta{Ui: ui, View: view},
} }
args := []string{"test"} args := []string{"test"}
if code := delCmd.Run(args); code == 0 { if code := delCmd.Run(args); code == 0 {

View File

@ -1,12 +1,13 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
"github.com/posener/complete" "github.com/posener/complete"
@ -107,9 +108,9 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
var stateLocker clistate.Locker var stateLocker clistate.Locker
if stateLock { if stateLock {
stateLocker = clistate.NewLocker(context.Background(), stateLockTimeout, c.Ui, c.Colorize()) stateLocker = clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "workspace_delete"); err != nil { if diags := stateLocker.Lock(stateMgr, "state-replace-provider"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
} else { } else {
@ -118,7 +119,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
if err := stateMgr.RefreshState(); err != nil { if err := stateMgr.RefreshState(); err != nil {
// We need to release the lock before exit // We need to release the lock before exit
stateLocker.Unlock(nil) stateLocker.Unlock()
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1
} }
@ -127,7 +128,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
if hasResources && !force { if hasResources && !force {
// We need to release the lock before exit // We need to release the lock before exit
stateLocker.Unlock(nil) stateLocker.Unlock()
c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), workspace)) c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), workspace))
return 1 return 1
} }
@ -141,7 +142,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
// state deletion, i.e. in a CI environment. Adding Delete() as a // state deletion, i.e. in a CI environment. Adding Delete() as a
// required method of States would allow the removal of the resource to // required method of States would allow the removal of the resource to
// be delegated from the Backend to the State itself. // be delegated from the Backend to the State itself.
stateLocker.Unlock(nil) stateLocker.Unlock()
err = b.DeleteWorkspace(workspace) err = b.DeleteWorkspace(workspace)
if err != nil { if err != nil {

View File

@ -1,13 +1,14 @@
package command package command
import ( import (
"context"
"fmt" "fmt"
"os" "os"
"strings" "strings"
"time" "time"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/states/statefile" "github.com/hashicorp/terraform/states/statefile"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -124,12 +125,16 @@ func (c *WorkspaceNewCommand) Run(args []string) int {
} }
if stateLock { if stateLock {
stateLocker := clistate.NewLocker(context.Background(), stateLockTimeout, c.Ui, c.Colorize()) stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if err := stateLocker.Lock(stateMgr, "workspace_new"); err != nil { if diags := stateLocker.Lock(stateMgr, "workspace-new"); diags.HasErrors() {
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) c.showDiagnostics(diags)
return 1 return 1
} }
defer stateLocker.Unlock(nil) defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
} }
// read the existing state file // read the existing state file