github.com/pulumi/terraform@v1.4.0/pkg/cloud/backend_plan_test.go (about)

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