backend: Replace ShowDiagnostics with view.Diagnostics

This commit is contained in:
Alisdair McDiarmid 2021-02-25 10:02:23 -05:00
parent 3737ee5081
commit 7c0ec0107f
15 changed files with 208 additions and 299 deletions

View File

@ -216,9 +216,6 @@ type Operation struct {
UIIn terraform.UIInput
UIOut terraform.UIOutput
// ShowDiagnostics prints diagnostic messages to the UI.
ShowDiagnostics func(vals ...interface{})
// StateLocker is used to lock the state while providing UI feedback to the
// user. This will be replaced by the Backend to update the context.
//
@ -253,7 +250,7 @@ func (o *Operation) Config() (*configs.Config, tfdiags.Diagnostics) {
//
// If the given diagnostics contains errors then the operation's result
// will be set to backend.OperationFailure. It will be set to
// backend.OperationSuccess otherwise. It will then use b.ShowDiagnostics
// backend.OperationSuccess otherwise. It will then use o.View.Diagnostics
// to show the given diagnostics before returning.
//
// Callers should feel free to do each of these operations separately in
@ -266,14 +263,14 @@ func (o *Operation) ReportResult(op *RunningOperation, diags tfdiags.Diagnostics
} else {
op.Result = OperationSuccess
}
if o.ShowDiagnostics != nil {
o.ShowDiagnostics(diags)
if o.View != nil {
o.View.Diagnostics(diags)
} else {
// Shouldn't generally happen, but if it does then we'll at least
// make some noise in the logs to help us spot it.
if len(diags) != 0 {
log.Printf(
"[ERROR] Backend needs to report diagnostics but ShowDiagnostics is not set:\n%s",
"[ERROR] Backend needs to report diagnostics but View is not set:\n%s",
diags.ErrWithWarnings(),
)
}

View File

@ -56,10 +56,6 @@ type CLIOpts struct {
// for tailoring the output to fit the attached terminal, for example.
Streams *terminal.Streams
// ShowDiagnostics is a function that will format and print diagnostic
// messages to the UI.
ShowDiagnostics func(vals ...interface{})
// StatePath is the local path where state is read from.
//
// StateOutPath is the local path where the state will be written.

View File

@ -53,7 +53,7 @@ func (b *Local) opApply(
defer func() {
diags := op.StateLocker.Unlock()
if diags.HasErrors() {
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
runningOp.Result = backend.OperationFailure
}
}()
@ -101,7 +101,7 @@ func (b *Local) opApply(
// We'll show any accumulated warnings before we display the prompt,
// so the user can consider them when deciding how to answer.
if len(diags) > 0 {
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
diags = nil // reset so we won't show the same diagnostics again later
}
@ -168,7 +168,7 @@ func (b *Local) opApply(
// If we've accumulated any warnings along the way then we'll show them
// here just before we show the summary and next steps. If we encountered
// errors then we would've returned early at some other point above.
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
}
// backupStateForError is called in a scenario where we're unable to persist the

View File

@ -101,8 +101,8 @@ func TestLocal_applyEmptyDir(t *testing.T) {
// the backend should be unlocked after a run
assertBackendStateUnlocked(t, b)
if errOutput := done(t).Stderr(); errOutput != "" {
t.Fatalf("unexpected error output:\n%s", errOutput)
if got, want := done(t).Stderr(), "Error: No configuration files"; !strings.Contains(got, want) {
t.Fatalf("unexpected error output:\n%s\nwant: %s", got, want)
}
}
@ -165,7 +165,7 @@ func TestLocal_applyError(t *testing.T) {
ami := r.Config.GetAttr("ami").AsString()
if !errored && ami == "error" {
errored = true
diags = diags.Append(errors.New("error"))
diags = diags.Append(errors.New("ami error"))
return providers.ApplyResourceChangeResponse{
Diagnostics: diags,
}
@ -201,8 +201,8 @@ test_instance.foo:
// the backend should be unlocked after a run
assertBackendStateUnlocked(t, b)
if errOutput := done(t).Stderr(); errOutput != "" {
t.Fatalf("unexpected error output:\n%s", errOutput)
if got, want := done(t).Stderr(), "Error: ami error"; !strings.Contains(got, want) {
t.Fatalf("unexpected error output:\n%s\nwant: %s", got, want)
}
}
@ -229,9 +229,6 @@ func TestLocal_applyBackendFail(t *testing.T) {
op, configCleanup, done := testOperationApply(t, wd+"/testdata/apply")
defer configCleanup()
record, playback := testRecordDiagnostics(t)
op.ShowDiagnostics = record
b.Backend = &backendWithFailingState{}
run, err := b.Operation(context.Background(), op)
@ -239,11 +236,14 @@ func TestLocal_applyBackendFail(t *testing.T) {
t.Fatalf("bad: %s", err)
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatalf("apply succeeded; want error")
}
diagErr := playback().Err().Error()
diagErr := output.Stderr()
if !strings.Contains(diagErr, "Error saving state: fake failure") {
t.Fatalf("missing \"fake failure\" message in diags:\n%s", diagErr)
}
@ -259,10 +259,6 @@ test_instance.foo:
// the backend should be unlocked after a run
assertBackendStateUnlocked(t, b)
if errOutput := done(t).Stderr(); errOutput != "" {
t.Fatalf("unexpected error output:\n%s", errOutput)
}
}
func TestLocal_applyRefreshFalse(t *testing.T) {
@ -320,12 +316,11 @@ func testOperationApply(t *testing.T, configDir string) (*backend.Operation, fun
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
return &backend.Operation{
Type: backend.OperationTypeApply,
ConfigDir: configDir,
ConfigLoader: configLoader,
ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewNoopLocker(),
View: view,
Type: backend.OperationTypeApply,
ConfigDir: configDir,
ConfigLoader: configLoader,
StateLocker: clistate.NewNoopLocker(),
View: view,
}, configCleanup, done
}

View File

@ -64,7 +64,7 @@ func (b *Local) opPlan(
defer func() {
diags := op.StateLocker.Unlock()
if diags.HasErrors() {
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
runningOp.Result = backend.OperationFailure
}
}()
@ -135,7 +135,7 @@ func (b *Local) opPlan(
op.View.PlanNoChanges()
// Even if there are no changes, there still could be some warnings
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
return
}
@ -145,7 +145,7 @@ func (b *Local) opPlan(
// If we've accumulated any warnings along the way then we'll show them
// here just before we show the summary and next steps. If we encountered
// errors then we would've returned early at some other point above.
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
op.View.PlanNextStep(op.PlanOutPath)
}

View File

@ -90,8 +90,6 @@ func TestLocal_planNoConfig(t *testing.T) {
TestLocalProvider(t, b, "test", &terraform.ProviderSchema{})
op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
record, playback := testRecordDiagnostics(t)
op.ShowDiagnostics = record
defer configCleanup()
op.PlanRefresh = true
@ -101,21 +99,18 @@ func TestLocal_planNoConfig(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("plan operation succeeded; want failure")
}
output := playback().Err().Error()
if !strings.Contains(output, "No configuration files") {
t.Fatalf("bad: %s", err)
if stderr := output.Stderr(); !strings.Contains(stderr, "No configuration files") {
t.Fatalf("bad: %s", stderr)
}
// the backend should be unlocked after a run
assertBackendStateUnlocked(t, b)
if errOutput := done(t).Stderr(); errOutput != "" {
t.Fatalf("unexpected error output:\n%s", errOutput)
}
}
// This test validates the state lacking behavior when the inner call to
@ -141,8 +136,8 @@ func TestLocal_plan_context_error(t *testing.T) {
// the backend should be unlocked after a run
assertBackendStateUnlocked(t, b)
if errOutput := done(t).Stderr(); errOutput != "" {
t.Fatalf("unexpected error output:\n%s", errOutput)
if got, want := done(t).Stderr(), "Error: Could not load plugin"; !strings.Contains(got, want) {
t.Fatalf("unexpected error output:\n%s\nwant: %s", got, want)
}
}
@ -723,12 +718,11 @@ func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
return &backend.Operation{
Type: backend.OperationTypePlan,
ConfigDir: configDir,
ConfigLoader: configLoader,
ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewNoopLocker(),
View: view,
Type: backend.OperationTypePlan,
ConfigDir: configDir,
ConfigLoader: configLoader,
StateLocker: clistate.NewNoopLocker(),
View: view,
}, configCleanup, done
}

View File

@ -57,7 +57,7 @@ func (b *Local) opRefresh(
defer func() {
diags := op.StateLocker.Unlock()
if diags.HasErrors() {
op.ShowDiagnostics(diags)
op.View.Diagnostics(diags)
runningOp.Result = backend.OperationFailure
}
}()

View File

@ -238,10 +238,6 @@ func TestLocal_refreshEmptyState(t *testing.T) {
op, configCleanup, done := testOperationRefresh(t, "./testdata/refresh")
defer configCleanup()
defer done(t)
record, playback := testRecordDiagnostics(t)
op.ShowDiagnostics = record
run, err := b.Operation(context.Background(), op)
if err != nil {
@ -249,11 +245,12 @@ func TestLocal_refreshEmptyState(t *testing.T) {
}
<-run.Done()
diags := playback()
if diags.HasErrors() {
t.Fatalf("expected only warning diags, got errors: %s", diags.Err())
output := done(t)
if stderr := output.Stderr(); stderr != "" {
t.Fatalf("expected only warning diags, got errors: %s", stderr)
}
if got, want := diags.ErrWithWarnings().Error(), "Empty or non-existent state"; !strings.Contains(got, want) {
if got, want := output.Stdout(), "Warning: Empty or non-existent state"; !strings.Contains(got, want) {
t.Errorf("wrong diags\n got: %s\nwant: %s", got, want)
}
@ -270,12 +267,11 @@ func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, f
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
return &backend.Operation{
Type: backend.OperationTypeRefresh,
ConfigDir: configDir,
ConfigLoader: configLoader,
ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewNoopLocker(),
View: view,
Type: backend.OperationTypeRefresh,
ConfigDir: configDir,
ConfigLoader: configLoader,
StateLocker: clistate.NewNoopLocker(),
View: view,
}, configCleanup, done
}

View File

@ -15,7 +15,6 @@ import (
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
)
// TestLocal returns a configured Local struct with temporary paths and
@ -245,43 +244,3 @@ func assertBackendStateLocked(t *testing.T, b *Local) bool {
t.Error("unexpected success locking state")
return true
}
// testRecordDiagnostics allows tests to record and later inspect diagnostics
// emitted during an Operation. It returns a record function which can be set
// as the ShowDiagnostics value of an Operation, and a playback function which
// returns the recorded diagnostics for inspection.
func testRecordDiagnostics(t *testing.T) (record func(vals ...interface{}), playback func() tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
record = func(vals ...interface{}) {
diags = diags.Append(vals...)
}
playback = func() tfdiags.Diagnostics {
diags.Sort()
return diags
}
return
}
// testLogDiagnostics returns a function which can be used as the
// ShowDiagnostics value for an Operation, in order to help debugging during
// tests. Any calls to this function result in test logs.
func testLogDiagnostics(t *testing.T) func(vals ...interface{}) {
return func(vals ...interface{}) {
var diags tfdiags.Diagnostics
diags = diags.Append(vals...)
diags.Sort()
for _, diag := range diags {
// NOTE: Since the caller here is not directly the TestLocal
// function, t.Helper doesn't apply and so the log source
// isn't correctly shown in the test log output. This seems
// unavoidable as long as this is happening so indirectly.
desc := diag.Description()
if desc.Detail != "" {
t.Logf("%s: %s", desc.Summary, desc.Detail)
} else {
t.Log(desc.Summary)
}
}
}
}

View File

@ -22,53 +22,44 @@ import (
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
tfversion "github.com/hashicorp/terraform/version"
"github.com/mitchellh/cli"
)
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func()) {
func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
t.Helper()
return testOperationApplyWithTimeout(t, configDir, 0)
}
func testOperationApplyWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func()) {
func testOperationApplyWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
t.Helper()
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
streams, _ := terminal.StreamsForTesting(t)
view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
streams, done := terminal.StreamsForTesting(t)
view := views.NewView(streams)
stateLockerView := views.NewStateLocker(arguments.ViewHuman, view)
operationView := views.NewOperation(arguments.ViewHuman, false, view)
return &backend.Operation{
ConfigDir: configDir,
ConfigLoader: configLoader,
Parallelism: defaultParallelism,
PlanRefresh: true,
ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewLocker(timeout, view),
Type: backend.OperationTypeApply,
}, configCleanup
}
func testOperationApplyWithDiagnostics(t *testing.T, configDir string) (*backend.Operation, func(), func() tfdiags.Diagnostics) {
t.Helper()
op, cleanup := testOperationApply(t, configDir)
record, playback := testRecordDiagnostics(t)
op.ShowDiagnostics = record
return op, cleanup, playback
ConfigDir: configDir,
ConfigLoader: configLoader,
Parallelism: defaultParallelism,
PlanRefresh: true,
StateLocker: clistate.NewLocker(timeout, stateLockerView),
Type: backend.OperationTypeApply,
View: operationView,
}, configCleanup, done
}
func TestRemote_applyBasic(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -117,8 +108,9 @@ func TestRemote_applyCanceled(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -158,7 +150,7 @@ func TestRemote_applyWithoutPermissions(t *testing.T) {
}
w.Permissions.CanQueueApply = false
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
op.UIOut = b.CLI
@ -170,11 +162,12 @@ func TestRemote_applyWithoutPermissions(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Insufficient rights to apply changes") {
t.Fatalf("expected a permissions error, got: %v", errOutput)
}
@ -197,7 +190,7 @@ func TestRemote_applyWithVCS(t *testing.T) {
t.Fatalf("error creating named workspace: %v", err)
}
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
op.Workspace = "prod"
@ -208,6 +201,7 @@ func TestRemote_applyWithVCS(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -215,7 +209,7 @@ func TestRemote_applyWithVCS(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "not allowed for workspaces with a VCS") {
t.Fatalf("expected a VCS error, got: %v", errOutput)
}
@ -225,7 +219,7 @@ func TestRemote_applyWithParallelism(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
op.Parallelism = 3
@ -237,11 +231,12 @@ func TestRemote_applyWithParallelism(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "parallelism values are currently not supported") {
t.Fatalf("expected a parallelism error, got: %v", errOutput)
}
@ -251,7 +246,7 @@ func TestRemote_applyWithPlan(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
op.PlanFile = &planfile.Reader{}
@ -263,6 +258,7 @@ func TestRemote_applyWithPlan(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -270,7 +266,7 @@ func TestRemote_applyWithPlan(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "saved plan is currently not supported") {
t.Fatalf("expected a saved plan error, got: %v", errOutput)
}
@ -280,7 +276,7 @@ func TestRemote_applyWithoutRefresh(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
op.PlanRefresh = false
@ -292,11 +288,12 @@ func TestRemote_applyWithoutRefresh(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "refresh is currently not supported") {
t.Fatalf("expected a refresh error, got: %v", errOutput)
}
@ -306,8 +303,9 @@ func TestRemote_applyWithTarget(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
@ -344,7 +342,7 @@ func TestRemote_applyWithTargetIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
// Set the tfe client's RemoteAPIVersion to an empty string, to mimic
@ -362,6 +360,7 @@ func TestRemote_applyWithTargetIncompatibleAPIVersion(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -369,7 +368,7 @@ func TestRemote_applyWithTargetIncompatibleAPIVersion(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Resource targeting is not supported") {
t.Fatalf("expected a targeting error, got: %v", errOutput)
}
@ -379,7 +378,7 @@ func TestRemote_applyWithVariables(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply-variables")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-variables")
defer configCleanup()
op.Variables = testVariables(terraform.ValueFromNamedFile, "foo", "bar")
@ -391,11 +390,12 @@ func TestRemote_applyWithVariables(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "variables are currently not supported") {
t.Fatalf("expected a variables error, got: %v", errOutput)
}
@ -405,7 +405,7 @@ func TestRemote_applyNoConfig(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/empty")
op, configCleanup, done := testOperationApply(t, "./testdata/empty")
defer configCleanup()
op.Workspace = backend.DefaultStateName
@ -416,6 +416,7 @@ func TestRemote_applyNoConfig(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -423,7 +424,7 @@ func TestRemote_applyNoConfig(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "configuration files found") {
t.Fatalf("expected configuration files error, got: %v", errOutput)
}
@ -439,8 +440,9 @@ func TestRemote_applyNoChanges(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply-no-changes")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-no-changes")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -470,7 +472,7 @@ func TestRemote_applyNoApprove(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
input := testInput(t, map[string]string{
@ -487,6 +489,7 @@ func TestRemote_applyNoApprove(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -498,7 +501,7 @@ func TestRemote_applyNoApprove(t *testing.T) {
t.Fatalf("expected no unused answers, got: %v", input.answers)
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Apply discarded") {
t.Fatalf("expected an apply discarded error, got: %v", errOutput)
}
@ -508,8 +511,9 @@ func TestRemote_applyAutoApprove(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "no",
@ -553,8 +557,9 @@ func TestRemote_applyApprovedExternally(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "wait-for-external-update",
@ -628,8 +633,9 @@ func TestRemote_applyDiscardedExternally(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "wait-for-external-update",
@ -716,8 +722,9 @@ func TestRemote_applyWithAutoApply(t *testing.T) {
t.Fatalf("error creating named workspace: %v", err)
}
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -767,8 +774,9 @@ func TestRemote_applyForceLocal(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -829,8 +837,9 @@ func TestRemote_applyWorkspaceWithoutOperations(t *testing.T) {
t.Fatalf("error creating named workspace: %v", err)
}
op, configCleanup := testOperationApply(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -900,8 +909,9 @@ func TestRemote_applyLockTimeout(t *testing.T) {
t.Fatalf("error creating pending run: %v", err)
}
op, configCleanup := testOperationApplyWithTimeout(t, "./testdata/apply", 50*time.Millisecond)
op, configCleanup, done := testOperationApplyWithTimeout(t, "./testdata/apply", 50*time.Millisecond)
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"cancel": "yes",
@ -950,8 +960,9 @@ func TestRemote_applyDestroy(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply-destroy")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-destroy")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -999,8 +1010,9 @@ func TestRemote_applyDestroyNoConfig(t *testing.T) {
"approve": "yes",
})
op, configCleanup := testOperationApply(t, "./testdata/empty")
op, configCleanup, done := testOperationApply(t, "./testdata/empty")
defer configCleanup()
defer done(t)
op.Destroy = true
op.UIIn = input
@ -1029,8 +1041,9 @@ func TestRemote_applyPolicyPass(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply-policy-passed")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-passed")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -1076,7 +1089,7 @@ func TestRemote_applyPolicyHardFail(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply-policy-hard-failed")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-hard-failed")
defer configCleanup()
input := testInput(t, map[string]string{
@ -1093,6 +1106,7 @@ func TestRemote_applyPolicyHardFail(t *testing.T) {
}
<-run.Done()
viewOutput := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -1104,7 +1118,7 @@ func TestRemote_applyPolicyHardFail(t *testing.T) {
t.Fatalf("expected an unused answers, got: %v", input.answers)
}
errOutput := playback().Err().Error()
errOutput := viewOutput.Stderr()
if !strings.Contains(errOutput, "hard failed") {
t.Fatalf("expected a policy check error, got: %v", errOutput)
}
@ -1128,8 +1142,9 @@ func TestRemote_applyPolicySoftFail(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply-policy-soft-failed")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-soft-failed")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"override": "override",
@ -1176,7 +1191,7 @@ func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply-policy-soft-failed")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-soft-failed")
defer configCleanup()
input := testInput(t, map[string]string{
@ -1194,6 +1209,7 @@ func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
}
<-run.Done()
viewOutput := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected apply operation to fail")
}
@ -1205,7 +1221,7 @@ func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
t.Fatalf("expected an unused answers, got: %v", input.answers)
}
errOutput := playback().Err().Error()
errOutput := viewOutput.Stderr()
if !strings.Contains(errOutput, "soft failed") {
t.Fatalf("expected a policy check error, got: %v", errOutput)
}
@ -1242,8 +1258,9 @@ func TestRemote_applyPolicySoftFailAutoApply(t *testing.T) {
t.Fatalf("error creating named workspace: %v", err)
}
op, configCleanup := testOperationApply(t, "./testdata/apply-policy-soft-failed")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-soft-failed")
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"override": "override",
@ -1290,8 +1307,9 @@ func TestRemote_applyWithRemoteError(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationApply(t, "./testdata/apply-with-error")
op, configCleanup, done := testOperationApply(t, "./testdata/apply-with-error")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -1393,13 +1411,12 @@ func TestRemote_applyVersionCheck(t *testing.T) {
}
// RUN: prepare the apply operation and run it
op, configCleanup, playback := testOperationApplyWithDiagnostics(t, "./testdata/apply")
op, configCleanup, done := testOperationApply(t, "./testdata/apply")
defer configCleanup()
streams, done := terminal.StreamsForTesting(t)
view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
op.View = view
defer done(t)
input := testInput(t, map[string]string{
"approve": "yes",
@ -1416,6 +1433,7 @@ func TestRemote_applyVersionCheck(t *testing.T) {
// RUN: wait for completion
<-run.Done()
output := done(t)
if tc.wantErr != "" {
// ASSERT: if the test case wants an error, check for failure
@ -1423,7 +1441,7 @@ func TestRemote_applyVersionCheck(t *testing.T) {
if run.Result != backend.OperationFailure {
t.Fatalf("expected run to fail, but result was %#v", run.Result)
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, tc.wantErr) {
t.Fatalf("missing error %q\noutput: %s", tc.wantErr, errOutput)
}

View File

@ -21,52 +21,43 @@ import (
"github.com/hashicorp/terraform/plans/planfile"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
)
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) {
func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
t.Helper()
return testOperationPlanWithTimeout(t, configDir, 0)
}
func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func()) {
func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
t.Helper()
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
streams, _ := terminal.StreamsForTesting(t)
view := views.NewStateLocker(arguments.ViewHuman, views.NewView(streams))
streams, done := terminal.StreamsForTesting(t)
view := views.NewView(streams)
stateLockerView := views.NewStateLocker(arguments.ViewHuman, view)
operationView := views.NewOperation(arguments.ViewHuman, false, view)
return &backend.Operation{
ConfigDir: configDir,
ConfigLoader: configLoader,
Parallelism: defaultParallelism,
PlanRefresh: true,
ShowDiagnostics: testLogDiagnostics(t),
StateLocker: clistate.NewLocker(timeout, view),
Type: backend.OperationTypePlan,
}, configCleanup
}
func testOperationPlanWithDiagnostics(t *testing.T, configDir string) (*backend.Operation, func(), func() tfdiags.Diagnostics) {
t.Helper()
op, cleanup := testOperationPlan(t, configDir)
record, playback := testRecordDiagnostics(t)
op.ShowDiagnostics = record
return op, cleanup, playback
ConfigDir: configDir,
ConfigLoader: configLoader,
Parallelism: defaultParallelism,
PlanRefresh: true,
StateLocker: clistate.NewLocker(timeout, stateLockerView),
Type: backend.OperationTypePlan,
View: operationView,
}, configCleanup, done
}
func TestRemote_planBasic(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -102,8 +93,9 @@ func TestRemote_planCanceled(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -131,8 +123,9 @@ func TestRemote_planLongLine(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan-long-line")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-long-line")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -175,7 +168,7 @@ func TestRemote_planWithoutPermissions(t *testing.T) {
}
w.Permissions.CanQueueRun = false
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.Workspace = "prod"
@ -186,11 +179,12 @@ func TestRemote_planWithoutPermissions(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Insufficient rights to generate a plan") {
t.Fatalf("expected a permissions error, got: %v", errOutput)
}
@ -200,7 +194,7 @@ func TestRemote_planWithParallelism(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.Parallelism = 3
@ -212,11 +206,12 @@ func TestRemote_planWithParallelism(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "parallelism values are currently not supported") {
t.Fatalf("expected a parallelism error, got: %v", errOutput)
}
@ -226,7 +221,7 @@ func TestRemote_planWithPlan(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.PlanFile = &planfile.Reader{}
@ -238,6 +233,7 @@ func TestRemote_planWithPlan(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
@ -245,7 +241,7 @@ func TestRemote_planWithPlan(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "saved plan is currently not supported") {
t.Fatalf("expected a saved plan error, got: %v", errOutput)
}
@ -255,7 +251,7 @@ func TestRemote_planWithPath(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.PlanOutPath = "./testdata/plan"
@ -267,6 +263,7 @@ func TestRemote_planWithPath(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
@ -274,7 +271,7 @@ func TestRemote_planWithPath(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "generated plan is currently not supported") {
t.Fatalf("expected a generated plan error, got: %v", errOutput)
}
@ -284,7 +281,7 @@ func TestRemote_planWithoutRefresh(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
op.PlanRefresh = false
@ -296,11 +293,12 @@ func TestRemote_planWithoutRefresh(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "refresh is currently not supported") {
t.Fatalf("expected a refresh error, got: %v", errOutput)
}
@ -333,8 +331,9 @@ func TestRemote_planWithTarget(t *testing.T) {
}
}
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
@ -382,7 +381,7 @@ func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
// Set the tfe client's RemoteAPIVersion to an empty string, to mimic
@ -400,6 +399,7 @@ func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
@ -407,7 +407,7 @@ func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "Resource targeting is not supported") {
t.Fatalf("expected a targeting error, got: %v", errOutput)
}
@ -417,7 +417,7 @@ func TestRemote_planWithVariables(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan-variables")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-variables")
defer configCleanup()
op.Variables = testVariables(terraform.ValueFromCLIArg, "foo", "bar")
@ -429,11 +429,12 @@ func TestRemote_planWithVariables(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "variables are currently not supported") {
t.Fatalf("expected a variables error, got: %v", errOutput)
}
@ -443,7 +444,7 @@ func TestRemote_planNoConfig(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/empty")
op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
defer configCleanup()
op.Workspace = backend.DefaultStateName
@ -454,6 +455,7 @@ func TestRemote_planNoConfig(t *testing.T) {
}
<-run.Done()
output := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
@ -461,7 +463,7 @@ func TestRemote_planNoConfig(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := output.Stderr()
if !strings.Contains(errOutput, "configuration files found") {
t.Fatalf("expected configuration files error, got: %v", errOutput)
}
@ -471,8 +473,9 @@ func TestRemote_planNoChanges(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan-no-changes")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-no-changes")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -509,8 +512,9 @@ func TestRemote_planForceLocal(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -544,8 +548,9 @@ func TestRemote_planWithoutOperationsEntitlement(t *testing.T) {
b, bCleanup := testBackendNoOperations(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -593,8 +598,9 @@ func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
t.Fatalf("error creating named workspace: %v", err)
}
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Workspace = "no-operations"
@ -651,8 +657,9 @@ func TestRemote_planLockTimeout(t *testing.T) {
t.Fatalf("error creating pending run: %v", err)
}
op, configCleanup := testOperationPlanWithTimeout(t, "./testdata/plan", 50)
op, configCleanup, done := testOperationPlanWithTimeout(t, "./testdata/plan", 50)
defer configCleanup()
defer done(t)
input := testInput(t, map[string]string{
"cancel": "yes",
@ -698,8 +705,9 @@ func TestRemote_planDestroy(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Destroy = true
op.Workspace = backend.DefaultStateName
@ -722,8 +730,9 @@ func TestRemote_planDestroyNoConfig(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/empty")
op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
defer configCleanup()
defer done(t)
op.Destroy = true
op.Workspace = backend.DefaultStateName
@ -756,8 +765,9 @@ func TestRemote_planWithWorkingDirectory(t *testing.T) {
t.Fatalf("error configuring working directory: %v", err)
}
op, configCleanup := testOperationPlan(t, "./testdata/plan-with-working-directory/terraform")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-with-working-directory/terraform")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -814,8 +824,9 @@ func TestRemote_planWithWorkingDirectoryFromCurrentPath(t *testing.T) {
// For this test we need to give our current directory instead of the
// full path to the configuration as we already changed directories.
op, configCleanup := testOperationPlan(t, ".")
op, configCleanup, done := testOperationPlan(t, ".")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -845,8 +856,9 @@ func TestRemote_planCostEstimation(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan-cost-estimation")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-cost-estimation")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -879,8 +891,9 @@ func TestRemote_planPolicyPass(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan-policy-passed")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-passed")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -913,7 +926,7 @@ func TestRemote_planPolicyHardFail(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan-policy-hard-failed")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-hard-failed")
defer configCleanup()
op.Workspace = backend.DefaultStateName
@ -924,6 +937,7 @@ func TestRemote_planPolicyHardFail(t *testing.T) {
}
<-run.Done()
viewOutput := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
@ -931,7 +945,7 @@ func TestRemote_planPolicyHardFail(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := viewOutput.Stderr()
if !strings.Contains(errOutput, "hard failed") {
t.Fatalf("expected a policy check error, got: %v", errOutput)
}
@ -952,7 +966,7 @@ func TestRemote_planPolicySoftFail(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup, playback := testOperationPlanWithDiagnostics(t, "./testdata/plan-policy-soft-failed")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-soft-failed")
defer configCleanup()
op.Workspace = backend.DefaultStateName
@ -963,6 +977,7 @@ func TestRemote_planPolicySoftFail(t *testing.T) {
}
<-run.Done()
viewOutput := done(t)
if run.Result == backend.OperationSuccess {
t.Fatal("expected plan operation to fail")
}
@ -970,7 +985,7 @@ func TestRemote_planPolicySoftFail(t *testing.T) {
t.Fatalf("expected plan to be empty")
}
errOutput := playback().Err().Error()
errOutput := viewOutput.Stderr()
if !strings.Contains(errOutput, "soft failed") {
t.Fatalf("expected a policy check error, got: %v", errOutput)
}
@ -991,8 +1006,9 @@ func TestRemote_planWithRemoteError(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan-with-error")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan-with-error")
defer configCleanup()
defer done(t)
op.Workspace = backend.DefaultStateName
@ -1022,8 +1038,9 @@ func TestRemote_planOtherError(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
op, configCleanup := testOperationPlan(t, "./testdata/plan")
op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
defer configCleanup()
defer done(t)
op.Workspace = "network-error" // custom error response in backend_mock.go

View File

@ -297,43 +297,3 @@ func testVariables(s terraform.ValueSourceType, vs ...string) map[string]backend
}
return vars
}
// testRecordDiagnostics allows tests to record and later inspect diagnostics
// emitted during an Operation. It returns a record function which can be set
// as the ShowDiagnostics value of an Operation, and a playback function which
// returns the recorded diagnostics for inspection.
func testRecordDiagnostics(t *testing.T) (record func(vals ...interface{}), playback func() tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
record = func(vals ...interface{}) {
diags = diags.Append(vals...)
}
playback = func() tfdiags.Diagnostics {
diags.Sort()
return diags
}
return
}
// testLogDiagnostics returns a function which can be used as the
// ShowDiagnostics value for an Operation, in order to help debugging during
// tests. Any calls to this function result in test logs.
func testLogDiagnostics(t *testing.T) func(vals ...interface{}) {
return func(vals ...interface{}) {
var diags tfdiags.Diagnostics
diags = diags.Append(vals...)
diags.Sort()
for _, diag := range diags {
// NOTE: Since the caller here is not directly the TestLocal
// function, t.Helper doesn't apply and so the log source
// isn't correctly shown in the test log output. This seems
// unavoidable as long as this is happening so indirectly.
desc := diag.Description()
if desc.Detail != "" {
t.Logf("%s: %s", desc.Summary, desc.Detail)
} else {
t.Log(desc.Summary)
}
}
}
}

View File

@ -252,17 +252,6 @@ func (c *ApplyCommand) OperationRequest(
opReq.Type = backend.OperationTypeApply
opReq.View = view.Operation()
// FIXME: To allow errors to be easily rendered, the showDiagnostics method
// accepts ...interface{}. The backend no longer needs this, as only
// tfdiags.Diagnostics values are used. Once we have migrated plan and refresh
// to use views, we can remove ShowDiagnostics and update the backend code to
// call view.Diagnostics() instead.
opReq.ShowDiagnostics = func(vals ...interface{}) {
var diags tfdiags.Diagnostics
diags = diags.Append(vals...)
view.Diagnostics(diags)
}
var err error
opReq.ConfigLoader, err = c.initConfigLoader()
if err != nil {

View File

@ -147,12 +147,6 @@ func (c *PlanCommand) OperationRequest(
opReq.Targets = args.Targets
opReq.Type = backend.OperationTypePlan
opReq.View = view.Operation()
// FIXME: this shim is needed until the remote backend is migrated to views
opReq.ShowDiagnostics = func(vals ...interface{}) {
var diags tfdiags.Diagnostics
diags = diags.Append(vals...)
view.Diagnostics(diags)
}
var err error
opReq.ConfigLoader, err = c.initConfigLoader()

View File

@ -135,12 +135,6 @@ func (c *RefreshCommand) OperationRequest(be backend.Enhanced, view views.Refres
opReq.Targets = args.Targets
opReq.Type = backend.OperationTypeRefresh
opReq.View = view.Operation()
// FIXME: this shim is needed until the remote backend is migrated to views
opReq.ShowDiagnostics = func(vals ...interface{}) {
var diags tfdiags.Diagnostics
diags = diags.Append(vals...)
view.Diagnostics(diags)
}
var err error
opReq.ConfigLoader, err = c.initConfigLoader()