github.com/hugorut/terraform@v1.1.3/src/backend/remote/backend_plan_test.go (about)

     1  package remote
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"os/signal"
     7  	"strings"
     8  	"syscall"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	tfe "github.com/hashicorp/go-tfe"
    14  	"github.com/hugorut/terraform/src/addrs"
    15  	"github.com/hugorut/terraform/src/backend"
    16  	"github.com/hugorut/terraform/src/cloud"
    17  	"github.com/hugorut/terraform/src/command/arguments"
    18  	"github.com/hugorut/terraform/src/command/clistate"
    19  	"github.com/hugorut/terraform/src/command/views"
    20  	"github.com/hugorut/terraform/src/depsfile"
    21  	"github.com/hugorut/terraform/src/initwd"
    22  	"github.com/hugorut/terraform/src/plans"
    23  	"github.com/hugorut/terraform/src/plans/planfile"
    24  	"github.com/hugorut/terraform/src/states/statemgr"
    25  	"github.com/hugorut/terraform/src/terminal"
    26  	"github.com/hugorut/terraform/src/terraform"
    27  	"github.com/mitchellh/cli"
    28  )
    29  
    30  func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
    31  	t.Helper()
    32  
    33  	return testOperationPlanWithTimeout(t, configDir, 0)
    34  }
    35  
    36  func testOperationPlanWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
    37  	t.Helper()
    38  
    39  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
    40  
    41  	streams, done := terminal.StreamsForTesting(t)
    42  	view := views.NewView(streams)
    43  	stateLockerView := views.NewStateLocker(arguments.ViewHuman, view)
    44  	operationView := views.NewOperation(arguments.ViewHuman, false, view)
    45  
    46  	// Many of our tests use an overridden "null" provider that's just in-memory
    47  	// inside the test process, not a separate plugin on disk.
    48  	depLocks := depsfile.NewLocks()
    49  	depLocks.SetProviderOverridden(addrs.MustParseProviderSourceString("registry.terraform.io/hashicorp/null"))
    50  
    51  	return &backend.Operation{
    52  		ConfigDir:       configDir,
    53  		ConfigLoader:    configLoader,
    54  		PlanRefresh:     true,
    55  		StateLocker:     clistate.NewLocker(timeout, stateLockerView),
    56  		Type:            backend.OperationTypePlan,
    57  		View:            operationView,
    58  		DependencyLocks: depLocks,
    59  	}, configCleanup, done
    60  }
    61  
    62  func TestRemote_planBasic(t *testing.T) {
    63  	b, bCleanup := testBackendDefault(t)
    64  	defer bCleanup()
    65  
    66  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
    67  	defer configCleanup()
    68  	defer done(t)
    69  
    70  	op.Workspace = backend.DefaultStateName
    71  
    72  	run, err := b.Operation(context.Background(), op)
    73  	if err != nil {
    74  		t.Fatalf("error starting operation: %v", err)
    75  	}
    76  
    77  	<-run.Done()
    78  	if run.Result != backend.OperationSuccess {
    79  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
    80  	}
    81  	if run.PlanEmpty {
    82  		t.Fatal("expected a non-empty plan")
    83  	}
    84  
    85  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
    86  	if !strings.Contains(output, "Running plan in the remote backend") {
    87  		t.Fatalf("expected remote backend header in output: %s", output)
    88  	}
    89  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
    90  		t.Fatalf("expected plan summary in output: %s", output)
    91  	}
    92  
    93  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
    94  	// An error suggests that the state was not unlocked after the operation finished
    95  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
    96  		t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
    97  	}
    98  }
    99  
   100  func TestRemote_planCanceled(t *testing.T) {
   101  	b, bCleanup := testBackendDefault(t)
   102  	defer bCleanup()
   103  
   104  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   105  	defer configCleanup()
   106  	defer done(t)
   107  
   108  	op.Workspace = backend.DefaultStateName
   109  
   110  	run, err := b.Operation(context.Background(), op)
   111  	if err != nil {
   112  		t.Fatalf("error starting operation: %v", err)
   113  	}
   114  
   115  	// Stop the run to simulate a Ctrl-C.
   116  	run.Stop()
   117  
   118  	<-run.Done()
   119  	if run.Result == backend.OperationSuccess {
   120  		t.Fatal("expected plan operation to fail")
   121  	}
   122  
   123  	stateMgr, _ := b.StateMgr(backend.DefaultStateName)
   124  	// An error suggests that the state was not unlocked after the operation finished
   125  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   126  		t.Fatalf("unexpected error locking state after cancelled plan: %s", err.Error())
   127  	}
   128  }
   129  
   130  func TestRemote_planLongLine(t *testing.T) {
   131  	b, bCleanup := testBackendDefault(t)
   132  	defer bCleanup()
   133  
   134  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-long-line")
   135  	defer configCleanup()
   136  	defer done(t)
   137  
   138  	op.Workspace = backend.DefaultStateName
   139  
   140  	run, err := b.Operation(context.Background(), op)
   141  	if err != nil {
   142  		t.Fatalf("error starting operation: %v", err)
   143  	}
   144  
   145  	<-run.Done()
   146  	if run.Result != backend.OperationSuccess {
   147  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   148  	}
   149  	if run.PlanEmpty {
   150  		t.Fatal("expected a non-empty plan")
   151  	}
   152  
   153  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   154  	if !strings.Contains(output, "Running plan in the remote backend") {
   155  		t.Fatalf("expected remote backend header in output: %s", output)
   156  	}
   157  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   158  		t.Fatalf("expected plan summary in output: %s", output)
   159  	}
   160  }
   161  
   162  func TestRemote_planWithoutPermissions(t *testing.T) {
   163  	b, bCleanup := testBackendNoDefault(t)
   164  	defer bCleanup()
   165  
   166  	// Create a named workspace without permissions.
   167  	w, err := b.client.Workspaces.Create(
   168  		context.Background(),
   169  		b.organization,
   170  		tfe.WorkspaceCreateOptions{
   171  			Name: tfe.String(b.prefix + "prod"),
   172  		},
   173  	)
   174  	if err != nil {
   175  		t.Fatalf("error creating named workspace: %v", err)
   176  	}
   177  	w.Permissions.CanQueueRun = false
   178  
   179  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   180  	defer configCleanup()
   181  
   182  	op.Workspace = "prod"
   183  
   184  	run, err := b.Operation(context.Background(), op)
   185  	if err != nil {
   186  		t.Fatalf("error starting operation: %v", err)
   187  	}
   188  
   189  	<-run.Done()
   190  	output := done(t)
   191  	if run.Result == backend.OperationSuccess {
   192  		t.Fatal("expected plan operation to fail")
   193  	}
   194  
   195  	errOutput := output.Stderr()
   196  	if !strings.Contains(errOutput, "Insufficient rights to generate a plan") {
   197  		t.Fatalf("expected a permissions error, got: %v", errOutput)
   198  	}
   199  }
   200  
   201  func TestRemote_planWithParallelism(t *testing.T) {
   202  	b, bCleanup := testBackendDefault(t)
   203  	defer bCleanup()
   204  
   205  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   206  	defer configCleanup()
   207  
   208  	if b.ContextOpts == nil {
   209  		b.ContextOpts = &terraform.ContextOpts{}
   210  	}
   211  	b.ContextOpts.Parallelism = 3
   212  	op.Workspace = backend.DefaultStateName
   213  
   214  	run, err := b.Operation(context.Background(), op)
   215  	if err != nil {
   216  		t.Fatalf("error starting operation: %v", err)
   217  	}
   218  
   219  	<-run.Done()
   220  	output := done(t)
   221  	if run.Result == backend.OperationSuccess {
   222  		t.Fatal("expected plan operation to fail")
   223  	}
   224  
   225  	errOutput := output.Stderr()
   226  	if !strings.Contains(errOutput, "parallelism values are currently not supported") {
   227  		t.Fatalf("expected a parallelism error, got: %v", errOutput)
   228  	}
   229  }
   230  
   231  func TestRemote_planWithPlan(t *testing.T) {
   232  	b, bCleanup := testBackendDefault(t)
   233  	defer bCleanup()
   234  
   235  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   236  	defer configCleanup()
   237  
   238  	op.PlanFile = &planfile.Reader{}
   239  	op.Workspace = backend.DefaultStateName
   240  
   241  	run, err := b.Operation(context.Background(), op)
   242  	if err != nil {
   243  		t.Fatalf("error starting operation: %v", err)
   244  	}
   245  
   246  	<-run.Done()
   247  	output := done(t)
   248  	if run.Result == backend.OperationSuccess {
   249  		t.Fatal("expected plan operation to fail")
   250  	}
   251  	if !run.PlanEmpty {
   252  		t.Fatalf("expected plan to be empty")
   253  	}
   254  
   255  	errOutput := output.Stderr()
   256  	if !strings.Contains(errOutput, "saved plan is currently not supported") {
   257  		t.Fatalf("expected a saved plan error, got: %v", errOutput)
   258  	}
   259  }
   260  
   261  func TestRemote_planWithPath(t *testing.T) {
   262  	b, bCleanup := testBackendDefault(t)
   263  	defer bCleanup()
   264  
   265  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   266  	defer configCleanup()
   267  
   268  	op.PlanOutPath = "./testdata/plan"
   269  	op.Workspace = backend.DefaultStateName
   270  
   271  	run, err := b.Operation(context.Background(), op)
   272  	if err != nil {
   273  		t.Fatalf("error starting operation: %v", err)
   274  	}
   275  
   276  	<-run.Done()
   277  	output := done(t)
   278  	if run.Result == backend.OperationSuccess {
   279  		t.Fatal("expected plan operation to fail")
   280  	}
   281  	if !run.PlanEmpty {
   282  		t.Fatalf("expected plan to be empty")
   283  	}
   284  
   285  	errOutput := output.Stderr()
   286  	if !strings.Contains(errOutput, "generated plan is currently not supported") {
   287  		t.Fatalf("expected a generated plan error, got: %v", errOutput)
   288  	}
   289  }
   290  
   291  func TestRemote_planWithoutRefresh(t *testing.T) {
   292  	b, bCleanup := testBackendDefault(t)
   293  	defer bCleanup()
   294  
   295  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   296  	defer configCleanup()
   297  	defer done(t)
   298  
   299  	op.PlanRefresh = false
   300  	op.Workspace = backend.DefaultStateName
   301  
   302  	run, err := b.Operation(context.Background(), op)
   303  	if err != nil {
   304  		t.Fatalf("error starting operation: %v", err)
   305  	}
   306  
   307  	<-run.Done()
   308  	if run.Result != backend.OperationSuccess {
   309  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   310  	}
   311  	if run.PlanEmpty {
   312  		t.Fatal("expected a non-empty plan")
   313  	}
   314  
   315  	// We should find a run inside the mock client that has refresh set
   316  	// to false.
   317  	runsAPI := b.client.Runs.(*cloud.MockRuns)
   318  	if got, want := len(runsAPI.Runs), 1; got != want {
   319  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   320  	}
   321  	for _, run := range runsAPI.Runs {
   322  		if diff := cmp.Diff(false, run.Refresh); diff != "" {
   323  			t.Errorf("wrong Refresh setting in the created run\n%s", diff)
   324  		}
   325  	}
   326  }
   327  
   328  func TestRemote_planWithoutRefreshIncompatibleAPIVersion(t *testing.T) {
   329  	b, bCleanup := testBackendDefault(t)
   330  	defer bCleanup()
   331  
   332  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   333  	defer configCleanup()
   334  
   335  	b.client.SetFakeRemoteAPIVersion("2.3")
   336  
   337  	op.PlanRefresh = false
   338  	op.Workspace = backend.DefaultStateName
   339  
   340  	run, err := b.Operation(context.Background(), op)
   341  	if err != nil {
   342  		t.Fatalf("error starting operation: %v", err)
   343  	}
   344  
   345  	<-run.Done()
   346  	output := done(t)
   347  	if run.Result == backend.OperationSuccess {
   348  		t.Fatal("expected plan operation to fail")
   349  	}
   350  	if !run.PlanEmpty {
   351  		t.Fatalf("expected plan to be empty")
   352  	}
   353  
   354  	errOutput := output.Stderr()
   355  	if !strings.Contains(errOutput, "Planning without refresh is not supported") {
   356  		t.Fatalf("expected not supported error, got: %v", errOutput)
   357  	}
   358  }
   359  
   360  func TestRemote_planWithRefreshOnly(t *testing.T) {
   361  	b, bCleanup := testBackendDefault(t)
   362  	defer bCleanup()
   363  
   364  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   365  	defer configCleanup()
   366  	defer done(t)
   367  
   368  	op.PlanMode = plans.RefreshOnlyMode
   369  	op.Workspace = backend.DefaultStateName
   370  
   371  	run, err := b.Operation(context.Background(), op)
   372  	if err != nil {
   373  		t.Fatalf("error starting operation: %v", err)
   374  	}
   375  
   376  	<-run.Done()
   377  	if run.Result != backend.OperationSuccess {
   378  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   379  	}
   380  	if run.PlanEmpty {
   381  		t.Fatal("expected a non-empty plan")
   382  	}
   383  
   384  	// We should find a run inside the mock client that has refresh-only set
   385  	// to true.
   386  	runsAPI := b.client.Runs.(*cloud.MockRuns)
   387  	if got, want := len(runsAPI.Runs), 1; got != want {
   388  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   389  	}
   390  	for _, run := range runsAPI.Runs {
   391  		if diff := cmp.Diff(true, run.RefreshOnly); diff != "" {
   392  			t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff)
   393  		}
   394  	}
   395  }
   396  
   397  func TestRemote_planWithRefreshOnlyIncompatibleAPIVersion(t *testing.T) {
   398  	b, bCleanup := testBackendDefault(t)
   399  	defer bCleanup()
   400  
   401  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   402  	defer configCleanup()
   403  
   404  	b.client.SetFakeRemoteAPIVersion("2.3")
   405  
   406  	op.PlanMode = plans.RefreshOnlyMode
   407  	op.Workspace = backend.DefaultStateName
   408  
   409  	run, err := b.Operation(context.Background(), op)
   410  	if err != nil {
   411  		t.Fatalf("error starting operation: %v", err)
   412  	}
   413  
   414  	<-run.Done()
   415  	output := done(t)
   416  	if run.Result == backend.OperationSuccess {
   417  		t.Fatal("expected plan operation to fail")
   418  	}
   419  	if !run.PlanEmpty {
   420  		t.Fatalf("expected plan to be empty")
   421  	}
   422  
   423  	errOutput := output.Stderr()
   424  	if !strings.Contains(errOutput, "Refresh-only mode is not supported") {
   425  		t.Fatalf("expected not supported error, got: %v", errOutput)
   426  	}
   427  }
   428  
   429  func TestRemote_planWithTarget(t *testing.T) {
   430  	b, bCleanup := testBackendDefault(t)
   431  	defer bCleanup()
   432  
   433  	// When the backend code creates a new run, we'll tweak it so that it
   434  	// has a cost estimation object with the "skipped_due_to_targeting" status,
   435  	// emulating how a real server is expected to behave in that case.
   436  	b.client.Runs.(*cloud.MockRuns).ModifyNewRun = func(client *cloud.MockClient, options tfe.RunCreateOptions, run *tfe.Run) {
   437  		const fakeID = "fake"
   438  		// This is the cost estimate object embedded in the run itself which
   439  		// the backend will use to learn the ID to request from the cost
   440  		// estimates endpoint. It's pending to simulate what a freshly-created
   441  		// run is likely to look like.
   442  		run.CostEstimate = &tfe.CostEstimate{
   443  			ID:     fakeID,
   444  			Status: "pending",
   445  		}
   446  		// The backend will then use the main cost estimation API to retrieve
   447  		// the same ID indicated in the object above, where we'll then return
   448  		// the status "skipped_due_to_targeting" to trigger the special skip
   449  		// message in the backend output.
   450  		client.CostEstimates.Estimations[fakeID] = &tfe.CostEstimate{
   451  			ID:     fakeID,
   452  			Status: "skipped_due_to_targeting",
   453  		}
   454  	}
   455  
   456  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   457  	defer configCleanup()
   458  	defer done(t)
   459  
   460  	addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
   461  
   462  	op.Targets = []addrs.Targetable{addr}
   463  	op.Workspace = backend.DefaultStateName
   464  
   465  	run, err := b.Operation(context.Background(), op)
   466  	if err != nil {
   467  		t.Fatalf("error starting operation: %v", err)
   468  	}
   469  
   470  	<-run.Done()
   471  	if run.Result != backend.OperationSuccess {
   472  		t.Fatal("expected plan operation to succeed")
   473  	}
   474  	if run.PlanEmpty {
   475  		t.Fatalf("expected plan to be non-empty")
   476  	}
   477  
   478  	// testBackendDefault above attached a "mock UI" to our backend, so we
   479  	// can retrieve its non-error output via the OutputWriter in-memory buffer.
   480  	gotOutput := b.CLI.(*cli.MockUi).OutputWriter.String()
   481  	if wantOutput := "Not available for this plan, because it was created with the -target option."; !strings.Contains(gotOutput, wantOutput) {
   482  		t.Errorf("missing message about skipped cost estimation\ngot:\n%s\nwant substring: %s", gotOutput, wantOutput)
   483  	}
   484  
   485  	// We should find a run inside the mock client that has the same
   486  	// target address we requested above.
   487  	runsAPI := b.client.Runs.(*cloud.MockRuns)
   488  	if got, want := len(runsAPI.Runs), 1; got != want {
   489  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   490  	}
   491  	for _, run := range runsAPI.Runs {
   492  		if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" {
   493  			t.Errorf("wrong TargetAddrs in the created run\n%s", diff)
   494  		}
   495  	}
   496  }
   497  
   498  func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
   499  	b, bCleanup := testBackendDefault(t)
   500  	defer bCleanup()
   501  
   502  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   503  	defer configCleanup()
   504  
   505  	// Set the tfe client's RemoteAPIVersion to an empty string, to mimic
   506  	// API versions prior to 2.3.
   507  	b.client.SetFakeRemoteAPIVersion("")
   508  
   509  	addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
   510  
   511  	op.Targets = []addrs.Targetable{addr}
   512  	op.Workspace = backend.DefaultStateName
   513  
   514  	run, err := b.Operation(context.Background(), op)
   515  	if err != nil {
   516  		t.Fatalf("error starting operation: %v", err)
   517  	}
   518  
   519  	<-run.Done()
   520  	output := done(t)
   521  	if run.Result == backend.OperationSuccess {
   522  		t.Fatal("expected plan operation to fail")
   523  	}
   524  	if !run.PlanEmpty {
   525  		t.Fatalf("expected plan to be empty")
   526  	}
   527  
   528  	errOutput := output.Stderr()
   529  	if !strings.Contains(errOutput, "Resource targeting is not supported") {
   530  		t.Fatalf("expected a targeting error, got: %v", errOutput)
   531  	}
   532  }
   533  
   534  func TestRemote_planWithReplace(t *testing.T) {
   535  	b, bCleanup := testBackendDefault(t)
   536  	defer bCleanup()
   537  
   538  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   539  	defer configCleanup()
   540  	defer done(t)
   541  
   542  	addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
   543  
   544  	op.ForceReplace = []addrs.AbsResourceInstance{addr}
   545  	op.Workspace = backend.DefaultStateName
   546  
   547  	run, err := b.Operation(context.Background(), op)
   548  	if err != nil {
   549  		t.Fatalf("error starting operation: %v", err)
   550  	}
   551  
   552  	<-run.Done()
   553  	if run.Result != backend.OperationSuccess {
   554  		t.Fatal("expected plan operation to succeed")
   555  	}
   556  	if run.PlanEmpty {
   557  		t.Fatalf("expected plan to be non-empty")
   558  	}
   559  
   560  	// We should find a run inside the mock client that has the same
   561  	// refresh address we requested above.
   562  	runsAPI := b.client.Runs.(*cloud.MockRuns)
   563  	if got, want := len(runsAPI.Runs), 1; got != want {
   564  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   565  	}
   566  	for _, run := range runsAPI.Runs {
   567  		if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" {
   568  			t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff)
   569  		}
   570  	}
   571  }
   572  
   573  func TestRemote_planWithReplaceIncompatibleAPIVersion(t *testing.T) {
   574  	b, bCleanup := testBackendDefault(t)
   575  	defer bCleanup()
   576  
   577  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   578  	defer configCleanup()
   579  
   580  	b.client.SetFakeRemoteAPIVersion("2.3")
   581  
   582  	addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
   583  
   584  	op.ForceReplace = []addrs.AbsResourceInstance{addr}
   585  	op.Workspace = backend.DefaultStateName
   586  
   587  	run, err := b.Operation(context.Background(), op)
   588  	if err != nil {
   589  		t.Fatalf("error starting operation: %v", err)
   590  	}
   591  
   592  	<-run.Done()
   593  	output := done(t)
   594  	if run.Result == backend.OperationSuccess {
   595  		t.Fatal("expected plan operation to fail")
   596  	}
   597  	if !run.PlanEmpty {
   598  		t.Fatalf("expected plan to be empty")
   599  	}
   600  
   601  	errOutput := output.Stderr()
   602  	if !strings.Contains(errOutput, "Planning resource replacements is not supported") {
   603  		t.Fatalf("expected not supported error, got: %v", errOutput)
   604  	}
   605  }
   606  
   607  func TestRemote_planWithVariables(t *testing.T) {
   608  	b, bCleanup := testBackendDefault(t)
   609  	defer bCleanup()
   610  
   611  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-variables")
   612  	defer configCleanup()
   613  
   614  	op.Variables = testVariables(terraform.ValueFromCLIArg, "foo", "bar")
   615  	op.Workspace = backend.DefaultStateName
   616  
   617  	run, err := b.Operation(context.Background(), op)
   618  	if err != nil {
   619  		t.Fatalf("error starting operation: %v", err)
   620  	}
   621  
   622  	<-run.Done()
   623  	output := done(t)
   624  	if run.Result == backend.OperationSuccess {
   625  		t.Fatal("expected plan operation to fail")
   626  	}
   627  
   628  	errOutput := output.Stderr()
   629  	if !strings.Contains(errOutput, "variables are currently not supported") {
   630  		t.Fatalf("expected a variables error, got: %v", errOutput)
   631  	}
   632  }
   633  
   634  func TestRemote_planNoConfig(t *testing.T) {
   635  	b, bCleanup := testBackendDefault(t)
   636  	defer bCleanup()
   637  
   638  	op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
   639  	defer configCleanup()
   640  
   641  	op.Workspace = backend.DefaultStateName
   642  
   643  	run, err := b.Operation(context.Background(), op)
   644  	if err != nil {
   645  		t.Fatalf("error starting operation: %v", err)
   646  	}
   647  
   648  	<-run.Done()
   649  	output := done(t)
   650  	if run.Result == backend.OperationSuccess {
   651  		t.Fatal("expected plan operation to fail")
   652  	}
   653  	if !run.PlanEmpty {
   654  		t.Fatalf("expected plan to be empty")
   655  	}
   656  
   657  	errOutput := output.Stderr()
   658  	if !strings.Contains(errOutput, "configuration files found") {
   659  		t.Fatalf("expected configuration files error, got: %v", errOutput)
   660  	}
   661  }
   662  
   663  func TestRemote_planNoChanges(t *testing.T) {
   664  	b, bCleanup := testBackendDefault(t)
   665  	defer bCleanup()
   666  
   667  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-no-changes")
   668  	defer configCleanup()
   669  	defer done(t)
   670  
   671  	op.Workspace = backend.DefaultStateName
   672  
   673  	run, err := b.Operation(context.Background(), op)
   674  	if err != nil {
   675  		t.Fatalf("error starting operation: %v", err)
   676  	}
   677  
   678  	<-run.Done()
   679  	if run.Result != backend.OperationSuccess {
   680  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   681  	}
   682  	if !run.PlanEmpty {
   683  		t.Fatalf("expected plan to be empty")
   684  	}
   685  
   686  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   687  	if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
   688  		t.Fatalf("expected no changes in plan summary: %s", output)
   689  	}
   690  	if !strings.Contains(output, "Sentinel Result: true") {
   691  		t.Fatalf("expected policy check result in output: %s", output)
   692  	}
   693  }
   694  
   695  func TestRemote_planForceLocal(t *testing.T) {
   696  	// Set TF_FORCE_LOCAL_BACKEND so the remote backend will use
   697  	// the local backend with itself as embedded backend.
   698  	if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil {
   699  		t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err)
   700  	}
   701  	defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND")
   702  
   703  	b, bCleanup := testBackendDefault(t)
   704  	defer bCleanup()
   705  
   706  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   707  	defer configCleanup()
   708  	defer done(t)
   709  
   710  	op.Workspace = backend.DefaultStateName
   711  
   712  	streams, done := terminal.StreamsForTesting(t)
   713  	view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
   714  	op.View = view
   715  
   716  	run, err := b.Operation(context.Background(), op)
   717  	if err != nil {
   718  		t.Fatalf("error starting operation: %v", err)
   719  	}
   720  
   721  	<-run.Done()
   722  	if run.Result != backend.OperationSuccess {
   723  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   724  	}
   725  	if run.PlanEmpty {
   726  		t.Fatalf("expected a non-empty plan")
   727  	}
   728  
   729  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   730  	if strings.Contains(output, "Running plan in the remote backend") {
   731  		t.Fatalf("unexpected remote backend header in output: %s", output)
   732  	}
   733  	if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   734  		t.Fatalf("expected plan summary in output: %s", output)
   735  	}
   736  }
   737  
   738  func TestRemote_planWithoutOperationsEntitlement(t *testing.T) {
   739  	b, bCleanup := testBackendNoOperations(t)
   740  	defer bCleanup()
   741  
   742  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   743  	defer configCleanup()
   744  	defer done(t)
   745  
   746  	op.Workspace = backend.DefaultStateName
   747  
   748  	streams, done := terminal.StreamsForTesting(t)
   749  	view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
   750  	op.View = view
   751  
   752  	run, err := b.Operation(context.Background(), op)
   753  	if err != nil {
   754  		t.Fatalf("error starting operation: %v", err)
   755  	}
   756  
   757  	<-run.Done()
   758  	if run.Result != backend.OperationSuccess {
   759  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   760  	}
   761  	if run.PlanEmpty {
   762  		t.Fatalf("expected a non-empty plan")
   763  	}
   764  
   765  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   766  	if strings.Contains(output, "Running plan in the remote backend") {
   767  		t.Fatalf("unexpected remote backend header in output: %s", output)
   768  	}
   769  	if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   770  		t.Fatalf("expected plan summary in output: %s", output)
   771  	}
   772  }
   773  
   774  func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
   775  	b, bCleanup := testBackendNoDefault(t)
   776  	defer bCleanup()
   777  
   778  	ctx := context.Background()
   779  
   780  	// Create a named workspace that doesn't allow operations.
   781  	_, err := b.client.Workspaces.Create(
   782  		ctx,
   783  		b.organization,
   784  		tfe.WorkspaceCreateOptions{
   785  			Name: tfe.String(b.prefix + "no-operations"),
   786  		},
   787  	)
   788  	if err != nil {
   789  		t.Fatalf("error creating named workspace: %v", err)
   790  	}
   791  
   792  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   793  	defer configCleanup()
   794  	defer done(t)
   795  
   796  	op.Workspace = "no-operations"
   797  
   798  	streams, done := terminal.StreamsForTesting(t)
   799  	view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
   800  	op.View = view
   801  
   802  	run, err := b.Operation(ctx, op)
   803  	if err != nil {
   804  		t.Fatalf("error starting operation: %v", err)
   805  	}
   806  
   807  	<-run.Done()
   808  	if run.Result != backend.OperationSuccess {
   809  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   810  	}
   811  	if run.PlanEmpty {
   812  		t.Fatalf("expected a non-empty plan")
   813  	}
   814  
   815  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   816  	if strings.Contains(output, "Running plan in the remote backend") {
   817  		t.Fatalf("unexpected remote backend header in output: %s", output)
   818  	}
   819  	if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   820  		t.Fatalf("expected plan summary in output: %s", output)
   821  	}
   822  }
   823  
   824  func TestRemote_planLockTimeout(t *testing.T) {
   825  	b, bCleanup := testBackendDefault(t)
   826  	defer bCleanup()
   827  
   828  	ctx := context.Background()
   829  
   830  	// Retrieve the workspace used to run this operation in.
   831  	w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace)
   832  	if err != nil {
   833  		t.Fatalf("error retrieving workspace: %v", err)
   834  	}
   835  
   836  	// Create a new configuration version.
   837  	c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
   838  	if err != nil {
   839  		t.Fatalf("error creating configuration version: %v", err)
   840  	}
   841  
   842  	// Create a pending run to block this run.
   843  	_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
   844  		ConfigurationVersion: c,
   845  		Workspace:            w,
   846  	})
   847  	if err != nil {
   848  		t.Fatalf("error creating pending run: %v", err)
   849  	}
   850  
   851  	op, configCleanup, done := testOperationPlanWithTimeout(t, "./testdata/plan", 50)
   852  	defer configCleanup()
   853  	defer done(t)
   854  
   855  	input := testInput(t, map[string]string{
   856  		"cancel":  "yes",
   857  		"approve": "yes",
   858  	})
   859  
   860  	op.UIIn = input
   861  	op.UIOut = b.CLI
   862  	op.Workspace = backend.DefaultStateName
   863  
   864  	_, err = b.Operation(context.Background(), op)
   865  	if err != nil {
   866  		t.Fatalf("error starting operation: %v", err)
   867  	}
   868  
   869  	sigint := make(chan os.Signal, 1)
   870  	signal.Notify(sigint, syscall.SIGINT)
   871  	select {
   872  	case <-sigint:
   873  		// Stop redirecting SIGINT signals.
   874  		signal.Stop(sigint)
   875  	case <-time.After(200 * time.Millisecond):
   876  		t.Fatalf("expected lock timeout after 50 milliseconds, waited 200 milliseconds")
   877  	}
   878  
   879  	if len(input.answers) != 2 {
   880  		t.Fatalf("expected unused answers, got: %v", input.answers)
   881  	}
   882  
   883  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   884  	if !strings.Contains(output, "Running plan in the remote backend") {
   885  		t.Fatalf("expected remote backend header in output: %s", output)
   886  	}
   887  	if !strings.Contains(output, "Lock timeout exceeded") {
   888  		t.Fatalf("expected lock timout error in output: %s", output)
   889  	}
   890  	if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   891  		t.Fatalf("unexpected plan summary in output: %s", output)
   892  	}
   893  }
   894  
   895  func TestRemote_planDestroy(t *testing.T) {
   896  	b, bCleanup := testBackendDefault(t)
   897  	defer bCleanup()
   898  
   899  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
   900  	defer configCleanup()
   901  	defer done(t)
   902  
   903  	op.PlanMode = plans.DestroyMode
   904  	op.Workspace = backend.DefaultStateName
   905  
   906  	run, err := b.Operation(context.Background(), op)
   907  	if err != nil {
   908  		t.Fatalf("error starting operation: %v", err)
   909  	}
   910  
   911  	<-run.Done()
   912  	if run.Result != backend.OperationSuccess {
   913  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   914  	}
   915  	if run.PlanEmpty {
   916  		t.Fatalf("expected a non-empty plan")
   917  	}
   918  }
   919  
   920  func TestRemote_planDestroyNoConfig(t *testing.T) {
   921  	b, bCleanup := testBackendDefault(t)
   922  	defer bCleanup()
   923  
   924  	op, configCleanup, done := testOperationPlan(t, "./testdata/empty")
   925  	defer configCleanup()
   926  	defer done(t)
   927  
   928  	op.PlanMode = plans.DestroyMode
   929  	op.Workspace = backend.DefaultStateName
   930  
   931  	run, err := b.Operation(context.Background(), op)
   932  	if err != nil {
   933  		t.Fatalf("error starting operation: %v", err)
   934  	}
   935  
   936  	<-run.Done()
   937  	if run.Result != backend.OperationSuccess {
   938  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   939  	}
   940  	if run.PlanEmpty {
   941  		t.Fatalf("expected a non-empty plan")
   942  	}
   943  }
   944  
   945  func TestRemote_planWithWorkingDirectory(t *testing.T) {
   946  	b, bCleanup := testBackendDefault(t)
   947  	defer bCleanup()
   948  
   949  	options := tfe.WorkspaceUpdateOptions{
   950  		WorkingDirectory: tfe.String("terraform"),
   951  	}
   952  
   953  	// Configure the workspace to use a custom working directory.
   954  	_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
   955  	if err != nil {
   956  		t.Fatalf("error configuring working directory: %v", err)
   957  	}
   958  
   959  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-with-working-directory/terraform")
   960  	defer configCleanup()
   961  	defer done(t)
   962  
   963  	op.Workspace = backend.DefaultStateName
   964  
   965  	run, err := b.Operation(context.Background(), op)
   966  	if err != nil {
   967  		t.Fatalf("error starting operation: %v", err)
   968  	}
   969  
   970  	<-run.Done()
   971  	if run.Result != backend.OperationSuccess {
   972  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   973  	}
   974  	if run.PlanEmpty {
   975  		t.Fatalf("expected a non-empty plan")
   976  	}
   977  
   978  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   979  	if !strings.Contains(output, "The remote workspace is configured to work with configuration") {
   980  		t.Fatalf("expected working directory warning: %s", output)
   981  	}
   982  	if !strings.Contains(output, "Running plan in the remote backend") {
   983  		t.Fatalf("expected remote backend header in output: %s", output)
   984  	}
   985  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   986  		t.Fatalf("expected plan summary in output: %s", output)
   987  	}
   988  }
   989  
   990  func TestRemote_planWithWorkingDirectoryFromCurrentPath(t *testing.T) {
   991  	b, bCleanup := testBackendDefault(t)
   992  	defer bCleanup()
   993  
   994  	options := tfe.WorkspaceUpdateOptions{
   995  		WorkingDirectory: tfe.String("terraform"),
   996  	}
   997  
   998  	// Configure the workspace to use a custom working directory.
   999  	_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
  1000  	if err != nil {
  1001  		t.Fatalf("error configuring working directory: %v", err)
  1002  	}
  1003  
  1004  	wd, err := os.Getwd()
  1005  	if err != nil {
  1006  		t.Fatalf("error getting current working directory: %v", err)
  1007  	}
  1008  
  1009  	// We need to change into the configuration directory to make sure
  1010  	// the logic to upload the correct slug is working as expected.
  1011  	if err := os.Chdir("./testdata/plan-with-working-directory/terraform"); err != nil {
  1012  		t.Fatalf("error changing directory: %v", err)
  1013  	}
  1014  	defer os.Chdir(wd) // Make sure we change back again when were done.
  1015  
  1016  	// For this test we need to give our current directory instead of the
  1017  	// full path to the configuration as we already changed directories.
  1018  	op, configCleanup, done := testOperationPlan(t, ".")
  1019  	defer configCleanup()
  1020  	defer done(t)
  1021  
  1022  	op.Workspace = backend.DefaultStateName
  1023  
  1024  	run, err := b.Operation(context.Background(), op)
  1025  	if err != nil {
  1026  		t.Fatalf("error starting operation: %v", err)
  1027  	}
  1028  
  1029  	<-run.Done()
  1030  	if run.Result != backend.OperationSuccess {
  1031  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1032  	}
  1033  	if run.PlanEmpty {
  1034  		t.Fatalf("expected a non-empty plan")
  1035  	}
  1036  
  1037  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1038  	if !strings.Contains(output, "Running plan in the remote backend") {
  1039  		t.Fatalf("expected remote backend header in output: %s", output)
  1040  	}
  1041  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1042  		t.Fatalf("expected plan summary in output: %s", output)
  1043  	}
  1044  }
  1045  
  1046  func TestRemote_planCostEstimation(t *testing.T) {
  1047  	b, bCleanup := testBackendDefault(t)
  1048  	defer bCleanup()
  1049  
  1050  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-cost-estimation")
  1051  	defer configCleanup()
  1052  	defer done(t)
  1053  
  1054  	op.Workspace = backend.DefaultStateName
  1055  
  1056  	run, err := b.Operation(context.Background(), op)
  1057  	if err != nil {
  1058  		t.Fatalf("error starting operation: %v", err)
  1059  	}
  1060  
  1061  	<-run.Done()
  1062  	if run.Result != backend.OperationSuccess {
  1063  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1064  	}
  1065  	if run.PlanEmpty {
  1066  		t.Fatalf("expected a non-empty plan")
  1067  	}
  1068  
  1069  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1070  	if !strings.Contains(output, "Running plan in the remote backend") {
  1071  		t.Fatalf("expected remote backend header in output: %s", output)
  1072  	}
  1073  	if !strings.Contains(output, "Resources: 1 of 1 estimated") {
  1074  		t.Fatalf("expected cost estimate result in output: %s", output)
  1075  	}
  1076  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1077  		t.Fatalf("expected plan summary in output: %s", output)
  1078  	}
  1079  }
  1080  
  1081  func TestRemote_planPolicyPass(t *testing.T) {
  1082  	b, bCleanup := testBackendDefault(t)
  1083  	defer bCleanup()
  1084  
  1085  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-passed")
  1086  	defer configCleanup()
  1087  	defer done(t)
  1088  
  1089  	op.Workspace = backend.DefaultStateName
  1090  
  1091  	run, err := b.Operation(context.Background(), op)
  1092  	if err != nil {
  1093  		t.Fatalf("error starting operation: %v", err)
  1094  	}
  1095  
  1096  	<-run.Done()
  1097  	if run.Result != backend.OperationSuccess {
  1098  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1099  	}
  1100  	if run.PlanEmpty {
  1101  		t.Fatalf("expected a non-empty plan")
  1102  	}
  1103  
  1104  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1105  	if !strings.Contains(output, "Running plan in the remote backend") {
  1106  		t.Fatalf("expected remote backend header in output: %s", output)
  1107  	}
  1108  	if !strings.Contains(output, "Sentinel Result: true") {
  1109  		t.Fatalf("expected policy check result in output: %s", output)
  1110  	}
  1111  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1112  		t.Fatalf("expected plan summary in output: %s", output)
  1113  	}
  1114  }
  1115  
  1116  func TestRemote_planPolicyHardFail(t *testing.T) {
  1117  	b, bCleanup := testBackendDefault(t)
  1118  	defer bCleanup()
  1119  
  1120  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-hard-failed")
  1121  	defer configCleanup()
  1122  
  1123  	op.Workspace = backend.DefaultStateName
  1124  
  1125  	run, err := b.Operation(context.Background(), op)
  1126  	if err != nil {
  1127  		t.Fatalf("error starting operation: %v", err)
  1128  	}
  1129  
  1130  	<-run.Done()
  1131  	viewOutput := done(t)
  1132  	if run.Result == backend.OperationSuccess {
  1133  		t.Fatal("expected plan operation to fail")
  1134  	}
  1135  	if !run.PlanEmpty {
  1136  		t.Fatalf("expected plan to be empty")
  1137  	}
  1138  
  1139  	errOutput := viewOutput.Stderr()
  1140  	if !strings.Contains(errOutput, "hard failed") {
  1141  		t.Fatalf("expected a policy check error, got: %v", errOutput)
  1142  	}
  1143  
  1144  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1145  	if !strings.Contains(output, "Running plan in the remote backend") {
  1146  		t.Fatalf("expected remote backend header in output: %s", output)
  1147  	}
  1148  	if !strings.Contains(output, "Sentinel Result: false") {
  1149  		t.Fatalf("expected policy check result in output: %s", output)
  1150  	}
  1151  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1152  		t.Fatalf("expected plan summary in output: %s", output)
  1153  	}
  1154  }
  1155  
  1156  func TestRemote_planPolicySoftFail(t *testing.T) {
  1157  	b, bCleanup := testBackendDefault(t)
  1158  	defer bCleanup()
  1159  
  1160  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-policy-soft-failed")
  1161  	defer configCleanup()
  1162  
  1163  	op.Workspace = backend.DefaultStateName
  1164  
  1165  	run, err := b.Operation(context.Background(), op)
  1166  	if err != nil {
  1167  		t.Fatalf("error starting operation: %v", err)
  1168  	}
  1169  
  1170  	<-run.Done()
  1171  	viewOutput := done(t)
  1172  	if run.Result == backend.OperationSuccess {
  1173  		t.Fatal("expected plan operation to fail")
  1174  	}
  1175  	if !run.PlanEmpty {
  1176  		t.Fatalf("expected plan to be empty")
  1177  	}
  1178  
  1179  	errOutput := viewOutput.Stderr()
  1180  	if !strings.Contains(errOutput, "soft failed") {
  1181  		t.Fatalf("expected a policy check error, got: %v", errOutput)
  1182  	}
  1183  
  1184  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1185  	if !strings.Contains(output, "Running plan in the remote backend") {
  1186  		t.Fatalf("expected remote backend header in output: %s", output)
  1187  	}
  1188  	if !strings.Contains(output, "Sentinel Result: false") {
  1189  		t.Fatalf("expected policy check result in output: %s", output)
  1190  	}
  1191  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1192  		t.Fatalf("expected plan summary in output: %s", output)
  1193  	}
  1194  }
  1195  
  1196  func TestRemote_planWithRemoteError(t *testing.T) {
  1197  	b, bCleanup := testBackendDefault(t)
  1198  	defer bCleanup()
  1199  
  1200  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan-with-error")
  1201  	defer configCleanup()
  1202  	defer done(t)
  1203  
  1204  	op.Workspace = backend.DefaultStateName
  1205  
  1206  	run, err := b.Operation(context.Background(), op)
  1207  	if err != nil {
  1208  		t.Fatalf("error starting operation: %v", err)
  1209  	}
  1210  
  1211  	<-run.Done()
  1212  	if run.Result == backend.OperationSuccess {
  1213  		t.Fatal("expected plan operation to fail")
  1214  	}
  1215  	if run.Result.ExitStatus() != 1 {
  1216  		t.Fatalf("expected exit code 1, got %d", run.Result.ExitStatus())
  1217  	}
  1218  
  1219  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1220  	if !strings.Contains(output, "Running plan in the remote backend") {
  1221  		t.Fatalf("expected remote backend header in output: %s", output)
  1222  	}
  1223  	if !strings.Contains(output, "null_resource.foo: 1 error") {
  1224  		t.Fatalf("expected plan error in output: %s", output)
  1225  	}
  1226  }
  1227  
  1228  func TestRemote_planOtherError(t *testing.T) {
  1229  	b, bCleanup := testBackendDefault(t)
  1230  	defer bCleanup()
  1231  
  1232  	op, configCleanup, done := testOperationPlan(t, "./testdata/plan")
  1233  	defer configCleanup()
  1234  	defer done(t)
  1235  
  1236  	op.Workspace = "network-error" // custom error response in backend_mock.go
  1237  
  1238  	_, err := b.Operation(context.Background(), op)
  1239  	if err == nil {
  1240  		t.Errorf("expected error, got success")
  1241  	}
  1242  
  1243  	if !strings.Contains(err.Error(),
  1244  		"The configured \"remote\" backend encountered an unexpected error:\n\nI'm a little teacup") {
  1245  		t.Fatalf("expected error message, got: %s", err.Error())
  1246  	}
  1247  }