github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/cloud/backend_apply_test.go (about)

     1  package cloud
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"os/signal"
     8  	"strings"
     9  	"syscall"
    10  	"testing"
    11  	"time"
    12  
    13  	gomock "github.com/golang/mock/gomock"
    14  	"github.com/google/go-cmp/cmp"
    15  	tfe "github.com/hashicorp/go-tfe"
    16  	mocks "github.com/hashicorp/go-tfe/mocks"
    17  	version "github.com/hashicorp/go-version"
    18  	"github.com/hashicorp/terraform/internal/addrs"
    19  	"github.com/hashicorp/terraform/internal/backend"
    20  	"github.com/hashicorp/terraform/internal/command/arguments"
    21  	"github.com/hashicorp/terraform/internal/command/clistate"
    22  	"github.com/hashicorp/terraform/internal/command/jsonformat"
    23  	"github.com/hashicorp/terraform/internal/command/views"
    24  	"github.com/hashicorp/terraform/internal/depsfile"
    25  	"github.com/hashicorp/terraform/internal/initwd"
    26  	"github.com/hashicorp/terraform/internal/plans"
    27  	"github.com/hashicorp/terraform/internal/plans/planfile"
    28  	"github.com/hashicorp/terraform/internal/states/statemgr"
    29  	"github.com/hashicorp/terraform/internal/terminal"
    30  	"github.com/hashicorp/terraform/internal/terraform"
    31  	tfversion "github.com/hashicorp/terraform/version"
    32  	"github.com/mitchellh/cli"
    33  )
    34  
    35  func testOperationApply(t *testing.T, configDir string) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
    36  	t.Helper()
    37  
    38  	return testOperationApplyWithTimeout(t, configDir, 0)
    39  }
    40  
    41  func testOperationApplyWithTimeout(t *testing.T, configDir string, timeout time.Duration) (*backend.Operation, func(), func(*testing.T) *terminal.TestOutput) {
    42  	t.Helper()
    43  
    44  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
    45  
    46  	streams, done := terminal.StreamsForTesting(t)
    47  	view := views.NewView(streams)
    48  	stateLockerView := views.NewStateLocker(arguments.ViewHuman, view)
    49  	operationView := views.NewOperation(arguments.ViewHuman, false, view)
    50  
    51  	// Many of our tests use an overridden "null" provider that's just in-memory
    52  	// inside the test process, not a separate plugin on disk.
    53  	depLocks := depsfile.NewLocks()
    54  	depLocks.SetProviderOverridden(addrs.MustParseProviderSourceString("registry.terraform.io/hashicorp/null"))
    55  
    56  	return &backend.Operation{
    57  		ConfigDir:       configDir,
    58  		ConfigLoader:    configLoader,
    59  		PlanRefresh:     true,
    60  		StateLocker:     clistate.NewLocker(timeout, stateLockerView),
    61  		Type:            backend.OperationTypeApply,
    62  		View:            operationView,
    63  		DependencyLocks: depLocks,
    64  	}, configCleanup, done
    65  }
    66  
    67  func TestCloud_applyBasic(t *testing.T) {
    68  	b, bCleanup := testBackendWithName(t)
    69  	defer bCleanup()
    70  
    71  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
    72  	defer configCleanup()
    73  	defer done(t)
    74  
    75  	input := testInput(t, map[string]string{
    76  		"approve": "yes",
    77  	})
    78  
    79  	op.UIIn = input
    80  	op.UIOut = b.CLI
    81  	op.Workspace = testBackendSingleWorkspaceName
    82  
    83  	run, err := b.Operation(context.Background(), op)
    84  	if err != nil {
    85  		t.Fatalf("error starting operation: %v", err)
    86  	}
    87  
    88  	<-run.Done()
    89  	if run.Result != backend.OperationSuccess {
    90  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
    91  	}
    92  	if run.PlanEmpty {
    93  		t.Fatalf("expected a non-empty plan")
    94  	}
    95  
    96  	if len(input.answers) > 0 {
    97  		t.Fatalf("expected no unused answers, got: %v", input.answers)
    98  	}
    99  
   100  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   101  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
   102  		t.Fatalf("expected TFC header in output: %s", output)
   103  	}
   104  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   105  		t.Fatalf("expected plan summery in output: %s", output)
   106  	}
   107  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   108  		t.Fatalf("expected apply summery in output: %s", output)
   109  	}
   110  
   111  	stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   112  	// An error suggests that the state was not unlocked after apply
   113  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   114  		t.Fatalf("unexpected error locking state after apply: %s", err.Error())
   115  	}
   116  }
   117  
   118  func TestCloud_applyJSONBasic(t *testing.T) {
   119  	b, bCleanup := testBackendWithName(t)
   120  	defer bCleanup()
   121  
   122  	stream, close := terminal.StreamsForTesting(t)
   123  
   124  	b.renderer = &jsonformat.Renderer{
   125  		Streams:  stream,
   126  		Colorize: mockColorize(),
   127  	}
   128  
   129  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-json")
   130  	defer configCleanup()
   131  	defer done(t)
   132  
   133  	input := testInput(t, map[string]string{
   134  		"approve": "yes",
   135  	})
   136  
   137  	op.UIIn = input
   138  	op.UIOut = b.CLI
   139  	op.Workspace = testBackendSingleWorkspaceName
   140  
   141  	mockSROWorkspace(t, b, op.Workspace)
   142  
   143  	run, err := b.Operation(context.Background(), op)
   144  	if err != nil {
   145  		t.Fatalf("error starting operation: %v", err)
   146  	}
   147  
   148  	<-run.Done()
   149  	if run.Result != backend.OperationSuccess {
   150  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   151  	}
   152  	if run.PlanEmpty {
   153  		t.Fatalf("expected a non-empty plan")
   154  	}
   155  
   156  	if len(input.answers) > 0 {
   157  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   158  	}
   159  
   160  	outp := close(t)
   161  	gotOut := outp.Stdout()
   162  
   163  	if !strings.Contains(gotOut, "1 to add, 0 to change, 0 to destroy") {
   164  		t.Fatalf("expected plan summary in output: %s", gotOut)
   165  	}
   166  	if !strings.Contains(gotOut, "1 added, 0 changed, 0 destroyed") {
   167  		t.Fatalf("expected apply summary in output: %s", gotOut)
   168  	}
   169  
   170  	stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   171  	// An error suggests that the state was not unlocked after apply
   172  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   173  		t.Fatalf("unexpected error locking state after apply: %s", err.Error())
   174  	}
   175  }
   176  
   177  func TestCloud_applyJSONWithOutputs(t *testing.T) {
   178  	b, bCleanup := testBackendWithName(t)
   179  	defer bCleanup()
   180  
   181  	stream, close := terminal.StreamsForTesting(t)
   182  
   183  	b.renderer = &jsonformat.Renderer{
   184  		Streams:  stream,
   185  		Colorize: mockColorize(),
   186  	}
   187  
   188  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-json-with-outputs")
   189  	defer configCleanup()
   190  	defer done(t)
   191  
   192  	input := testInput(t, map[string]string{
   193  		"approve": "yes",
   194  	})
   195  
   196  	op.UIIn = input
   197  	op.UIOut = b.CLI
   198  	op.Workspace = testBackendSingleWorkspaceName
   199  
   200  	mockSROWorkspace(t, b, op.Workspace)
   201  
   202  	run, err := b.Operation(context.Background(), op)
   203  	if err != nil {
   204  		t.Fatalf("error starting operation: %v", err)
   205  	}
   206  
   207  	<-run.Done()
   208  	if run.Result != backend.OperationSuccess {
   209  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   210  	}
   211  	if run.PlanEmpty {
   212  		t.Fatalf("expected a non-empty plan")
   213  	}
   214  
   215  	if len(input.answers) > 0 {
   216  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   217  	}
   218  
   219  	outp := close(t)
   220  	gotOut := outp.Stdout()
   221  	expectedSimpleOutput := `simple = [
   222          "some",
   223          "list",
   224      ]`
   225  	expectedSensitiveOutput := `secret = (sensitive value)`
   226  	expectedComplexOutput := `complex = {
   227          keyA = {
   228              someList = [
   229                  1,
   230                  2,
   231                  3,
   232              ]
   233          }
   234          keyB = {
   235              someBool = true
   236              someStr  = "hello"
   237          }
   238      }`
   239  
   240  	if !strings.Contains(gotOut, "1 to add, 0 to change, 0 to destroy") {
   241  		t.Fatalf("expected plan summary in output: %s", gotOut)
   242  	}
   243  	if !strings.Contains(gotOut, "1 added, 0 changed, 0 destroyed") {
   244  		t.Fatalf("expected apply summary in output: %s", gotOut)
   245  	}
   246  	if !strings.Contains(gotOut, "Outputs:") {
   247  		t.Fatalf("expected output header: %s", gotOut)
   248  	}
   249  	if !strings.Contains(gotOut, expectedSimpleOutput) {
   250  		t.Fatalf("expected output: %s, got: %s", expectedSimpleOutput, gotOut)
   251  	}
   252  	if !strings.Contains(gotOut, expectedSensitiveOutput) {
   253  		t.Fatalf("expected output: %s, got: %s", expectedSensitiveOutput, gotOut)
   254  	}
   255  	if !strings.Contains(gotOut, expectedComplexOutput) {
   256  		t.Fatalf("expected output: %s, got: %s", expectedComplexOutput, gotOut)
   257  	}
   258  	stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   259  	// An error suggests that the state was not unlocked after apply
   260  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   261  		t.Fatalf("unexpected error locking state after apply: %s", err.Error())
   262  	}
   263  }
   264  
   265  func TestCloud_applyCanceled(t *testing.T) {
   266  	b, bCleanup := testBackendWithName(t)
   267  	defer bCleanup()
   268  
   269  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   270  	defer configCleanup()
   271  	defer done(t)
   272  
   273  	op.Workspace = testBackendSingleWorkspaceName
   274  
   275  	run, err := b.Operation(context.Background(), op)
   276  	if err != nil {
   277  		t.Fatalf("error starting operation: %v", err)
   278  	}
   279  
   280  	// Stop the run to simulate a Ctrl-C.
   281  	run.Stop()
   282  
   283  	<-run.Done()
   284  	if run.Result == backend.OperationSuccess {
   285  		t.Fatal("expected apply operation to fail")
   286  	}
   287  
   288  	stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   289  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   290  		t.Fatalf("unexpected error locking state after cancelling apply: %s", err.Error())
   291  	}
   292  }
   293  
   294  func TestCloud_applyWithoutPermissions(t *testing.T) {
   295  	b, bCleanup := testBackendWithTags(t)
   296  	defer bCleanup()
   297  
   298  	// Create a named workspace without permissions.
   299  	w, err := b.client.Workspaces.Create(
   300  		context.Background(),
   301  		b.organization,
   302  		tfe.WorkspaceCreateOptions{
   303  			Name: tfe.String("prod"),
   304  		},
   305  	)
   306  	if err != nil {
   307  		t.Fatalf("error creating named workspace: %v", err)
   308  	}
   309  	w.Permissions.CanQueueApply = false
   310  
   311  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   312  	defer configCleanup()
   313  
   314  	op.UIOut = b.CLI
   315  	op.Workspace = "prod"
   316  
   317  	run, err := b.Operation(context.Background(), op)
   318  	if err != nil {
   319  		t.Fatalf("error starting operation: %v", err)
   320  	}
   321  
   322  	<-run.Done()
   323  	output := done(t)
   324  	if run.Result == backend.OperationSuccess {
   325  		t.Fatal("expected apply operation to fail")
   326  	}
   327  
   328  	errOutput := output.Stderr()
   329  	if !strings.Contains(errOutput, "Insufficient rights to apply changes") {
   330  		t.Fatalf("expected a permissions error, got: %v", errOutput)
   331  	}
   332  }
   333  
   334  func TestCloud_applyWithVCS(t *testing.T) {
   335  	b, bCleanup := testBackendWithTags(t)
   336  	defer bCleanup()
   337  
   338  	// Create a named workspace with a VCS.
   339  	_, err := b.client.Workspaces.Create(
   340  		context.Background(),
   341  		b.organization,
   342  		tfe.WorkspaceCreateOptions{
   343  			Name:    tfe.String("prod"),
   344  			VCSRepo: &tfe.VCSRepoOptions{},
   345  		},
   346  	)
   347  	if err != nil {
   348  		t.Fatalf("error creating named workspace: %v", err)
   349  	}
   350  
   351  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   352  	defer configCleanup()
   353  
   354  	op.Workspace = "prod"
   355  
   356  	run, err := b.Operation(context.Background(), op)
   357  	if err != nil {
   358  		t.Fatalf("error starting operation: %v", err)
   359  	}
   360  
   361  	<-run.Done()
   362  	output := done(t)
   363  	if run.Result == backend.OperationSuccess {
   364  		t.Fatal("expected apply operation to fail")
   365  	}
   366  	if !run.PlanEmpty {
   367  		t.Fatalf("expected plan to be empty")
   368  	}
   369  
   370  	errOutput := output.Stderr()
   371  	if !strings.Contains(errOutput, "not allowed for workspaces with a VCS") {
   372  		t.Fatalf("expected a VCS error, got: %v", errOutput)
   373  	}
   374  }
   375  
   376  func TestCloud_applyWithParallelism(t *testing.T) {
   377  	b, bCleanup := testBackendWithName(t)
   378  	defer bCleanup()
   379  
   380  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   381  	defer configCleanup()
   382  
   383  	if b.ContextOpts == nil {
   384  		b.ContextOpts = &terraform.ContextOpts{}
   385  	}
   386  	b.ContextOpts.Parallelism = 3
   387  	op.Workspace = testBackendSingleWorkspaceName
   388  
   389  	run, err := b.Operation(context.Background(), op)
   390  	if err != nil {
   391  		t.Fatalf("error starting operation: %v", err)
   392  	}
   393  
   394  	<-run.Done()
   395  	output := done(t)
   396  	if run.Result == backend.OperationSuccess {
   397  		t.Fatal("expected apply operation to fail")
   398  	}
   399  
   400  	errOutput := output.Stderr()
   401  	if !strings.Contains(errOutput, "parallelism values are currently not supported") {
   402  		t.Fatalf("expected a parallelism error, got: %v", errOutput)
   403  	}
   404  }
   405  
   406  func TestCloud_applyWithPlan(t *testing.T) {
   407  	b, bCleanup := testBackendWithName(t)
   408  	defer bCleanup()
   409  
   410  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   411  	defer configCleanup()
   412  
   413  	op.PlanFile = &planfile.Reader{}
   414  	op.Workspace = testBackendSingleWorkspaceName
   415  
   416  	run, err := b.Operation(context.Background(), op)
   417  	if err != nil {
   418  		t.Fatalf("error starting operation: %v", err)
   419  	}
   420  
   421  	<-run.Done()
   422  	output := done(t)
   423  	if run.Result == backend.OperationSuccess {
   424  		t.Fatal("expected apply operation to fail")
   425  	}
   426  	if !run.PlanEmpty {
   427  		t.Fatalf("expected plan to be empty")
   428  	}
   429  
   430  	errOutput := output.Stderr()
   431  	if !strings.Contains(errOutput, "saved plan is currently not supported") {
   432  		t.Fatalf("expected a saved plan error, got: %v", errOutput)
   433  	}
   434  }
   435  
   436  func TestCloud_applyWithoutRefresh(t *testing.T) {
   437  	b, bCleanup := testBackendWithName(t)
   438  	defer bCleanup()
   439  
   440  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   441  	defer configCleanup()
   442  	defer done(t)
   443  
   444  	op.PlanRefresh = false
   445  	op.Workspace = testBackendSingleWorkspaceName
   446  
   447  	run, err := b.Operation(context.Background(), op)
   448  	if err != nil {
   449  		t.Fatalf("error starting operation: %v", err)
   450  	}
   451  
   452  	<-run.Done()
   453  	if run.Result != backend.OperationSuccess {
   454  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   455  	}
   456  	if run.PlanEmpty {
   457  		t.Fatalf("expected plan to be non-empty")
   458  	}
   459  
   460  	// We should find a run inside the mock client that has refresh set
   461  	// to false.
   462  	runsAPI := b.client.Runs.(*MockRuns)
   463  	if got, want := len(runsAPI.Runs), 1; got != want {
   464  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   465  	}
   466  	for _, run := range runsAPI.Runs {
   467  		if diff := cmp.Diff(false, run.Refresh); diff != "" {
   468  			t.Errorf("wrong Refresh setting in the created run\n%s", diff)
   469  		}
   470  	}
   471  }
   472  
   473  func TestCloud_applyWithRefreshOnly(t *testing.T) {
   474  	b, bCleanup := testBackendWithName(t)
   475  	defer bCleanup()
   476  
   477  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   478  	defer configCleanup()
   479  	defer done(t)
   480  
   481  	op.PlanMode = plans.RefreshOnlyMode
   482  	op.Workspace = testBackendSingleWorkspaceName
   483  
   484  	run, err := b.Operation(context.Background(), op)
   485  	if err != nil {
   486  		t.Fatalf("error starting operation: %v", err)
   487  	}
   488  
   489  	<-run.Done()
   490  	if run.Result != backend.OperationSuccess {
   491  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   492  	}
   493  	if run.PlanEmpty {
   494  		t.Fatalf("expected plan to be non-empty")
   495  	}
   496  
   497  	// We should find a run inside the mock client that has refresh-only set
   498  	// to true.
   499  	runsAPI := b.client.Runs.(*MockRuns)
   500  	if got, want := len(runsAPI.Runs), 1; got != want {
   501  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   502  	}
   503  	for _, run := range runsAPI.Runs {
   504  		if diff := cmp.Diff(true, run.RefreshOnly); diff != "" {
   505  			t.Errorf("wrong RefreshOnly setting in the created run\n%s", diff)
   506  		}
   507  	}
   508  }
   509  
   510  func TestCloud_applyWithTarget(t *testing.T) {
   511  	b, bCleanup := testBackendWithName(t)
   512  	defer bCleanup()
   513  
   514  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   515  	defer configCleanup()
   516  	defer done(t)
   517  
   518  	addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
   519  
   520  	op.Targets = []addrs.Targetable{addr}
   521  	op.Workspace = testBackendSingleWorkspaceName
   522  
   523  	run, err := b.Operation(context.Background(), op)
   524  	if err != nil {
   525  		t.Fatalf("error starting operation: %v", err)
   526  	}
   527  
   528  	<-run.Done()
   529  	if run.Result != backend.OperationSuccess {
   530  		t.Fatal("expected apply operation to succeed")
   531  	}
   532  	if run.PlanEmpty {
   533  		t.Fatalf("expected plan to be non-empty")
   534  	}
   535  
   536  	// We should find a run inside the mock client that has the same
   537  	// target address we requested above.
   538  	runsAPI := b.client.Runs.(*MockRuns)
   539  	if got, want := len(runsAPI.Runs), 1; got != want {
   540  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   541  	}
   542  	for _, run := range runsAPI.Runs {
   543  		if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" {
   544  			t.Errorf("wrong TargetAddrs in the created run\n%s", diff)
   545  		}
   546  	}
   547  }
   548  
   549  func TestCloud_applyWithReplace(t *testing.T) {
   550  	b, bCleanup := testBackendWithName(t)
   551  	defer bCleanup()
   552  
   553  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   554  	defer configCleanup()
   555  	defer done(t)
   556  
   557  	addr, _ := addrs.ParseAbsResourceInstanceStr("null_resource.foo")
   558  
   559  	op.ForceReplace = []addrs.AbsResourceInstance{addr}
   560  	op.Workspace = testBackendSingleWorkspaceName
   561  
   562  	run, err := b.Operation(context.Background(), op)
   563  	if err != nil {
   564  		t.Fatalf("error starting operation: %v", err)
   565  	}
   566  
   567  	<-run.Done()
   568  	if run.Result != backend.OperationSuccess {
   569  		t.Fatal("expected plan operation to succeed")
   570  	}
   571  	if run.PlanEmpty {
   572  		t.Fatalf("expected plan to be non-empty")
   573  	}
   574  
   575  	// We should find a run inside the mock client that has the same
   576  	// refresh address we requested above.
   577  	runsAPI := b.client.Runs.(*MockRuns)
   578  	if got, want := len(runsAPI.Runs), 1; got != want {
   579  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   580  	}
   581  	for _, run := range runsAPI.Runs {
   582  		if diff := cmp.Diff([]string{"null_resource.foo"}, run.ReplaceAddrs); diff != "" {
   583  			t.Errorf("wrong ReplaceAddrs in the created run\n%s", diff)
   584  		}
   585  	}
   586  }
   587  
   588  func TestCloud_applyWithRequiredVariables(t *testing.T) {
   589  	b, bCleanup := testBackendWithName(t)
   590  	defer bCleanup()
   591  
   592  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-variables")
   593  	defer configCleanup()
   594  	defer done(t)
   595  
   596  	op.Variables = testVariables(terraform.ValueFromNamedFile, "foo") // "bar" variable value missing
   597  	op.Workspace = testBackendSingleWorkspaceName
   598  
   599  	run, err := b.Operation(context.Background(), op)
   600  	if err != nil {
   601  		t.Fatalf("error starting operation: %v", err)
   602  	}
   603  
   604  	<-run.Done()
   605  	// The usual error of a required variable being missing is deferred and the operation
   606  	// is successful
   607  	if run.Result != backend.OperationSuccess {
   608  		t.Fatal("expected plan operation to succeed")
   609  	}
   610  
   611  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   612  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
   613  		t.Fatalf("unexpected TFC header in output: %s", output)
   614  	}
   615  }
   616  
   617  func TestCloud_applyNoConfig(t *testing.T) {
   618  	b, bCleanup := testBackendWithName(t)
   619  	defer bCleanup()
   620  
   621  	op, configCleanup, done := testOperationApply(t, "./testdata/empty")
   622  	defer configCleanup()
   623  
   624  	op.Workspace = testBackendSingleWorkspaceName
   625  
   626  	run, err := b.Operation(context.Background(), op)
   627  	if err != nil {
   628  		t.Fatalf("error starting operation: %v", err)
   629  	}
   630  
   631  	<-run.Done()
   632  	output := done(t)
   633  	if run.Result == backend.OperationSuccess {
   634  		t.Fatal("expected apply operation to fail")
   635  	}
   636  	if !run.PlanEmpty {
   637  		t.Fatalf("expected plan to be empty")
   638  	}
   639  
   640  	errOutput := output.Stderr()
   641  	if !strings.Contains(errOutput, "configuration files found") {
   642  		t.Fatalf("expected configuration files error, got: %v", errOutput)
   643  	}
   644  
   645  	stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
   646  	// An error suggests that the state was not unlocked after apply
   647  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
   648  		t.Fatalf("unexpected error locking state after failed apply: %s", err.Error())
   649  	}
   650  }
   651  
   652  func TestCloud_applyNoChanges(t *testing.T) {
   653  	b, bCleanup := testBackendWithName(t)
   654  	defer bCleanup()
   655  
   656  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-no-changes")
   657  	defer configCleanup()
   658  	defer done(t)
   659  
   660  	op.Workspace = testBackendSingleWorkspaceName
   661  
   662  	run, err := b.Operation(context.Background(), op)
   663  	if err != nil {
   664  		t.Fatalf("error starting operation: %v", err)
   665  	}
   666  
   667  	<-run.Done()
   668  	if run.Result != backend.OperationSuccess {
   669  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   670  	}
   671  	if !run.PlanEmpty {
   672  		t.Fatalf("expected plan to be empty")
   673  	}
   674  
   675  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   676  	if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
   677  		t.Fatalf("expected no changes in plan summery: %s", output)
   678  	}
   679  	if !strings.Contains(output, "Sentinel Result: true") {
   680  		t.Fatalf("expected policy check result in output: %s", output)
   681  	}
   682  }
   683  
   684  func TestCloud_applyNoApprove(t *testing.T) {
   685  	b, bCleanup := testBackendWithName(t)
   686  	defer bCleanup()
   687  
   688  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   689  	defer configCleanup()
   690  
   691  	input := testInput(t, map[string]string{
   692  		"approve": "no",
   693  	})
   694  
   695  	op.UIIn = input
   696  	op.UIOut = b.CLI
   697  	op.Workspace = testBackendSingleWorkspaceName
   698  
   699  	run, err := b.Operation(context.Background(), op)
   700  	if err != nil {
   701  		t.Fatalf("error starting operation: %v", err)
   702  	}
   703  
   704  	<-run.Done()
   705  	output := done(t)
   706  	if run.Result == backend.OperationSuccess {
   707  		t.Fatal("expected apply operation to fail")
   708  	}
   709  	if !run.PlanEmpty {
   710  		t.Fatalf("expected plan to be empty")
   711  	}
   712  
   713  	if len(input.answers) > 0 {
   714  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   715  	}
   716  
   717  	errOutput := output.Stderr()
   718  	if !strings.Contains(errOutput, "Apply discarded") {
   719  		t.Fatalf("expected an apply discarded error, got: %v", errOutput)
   720  	}
   721  }
   722  
   723  func TestCloud_applyAutoApprove(t *testing.T) {
   724  	b, bCleanup := testBackendWithName(t)
   725  	defer bCleanup()
   726  	ctrl := gomock.NewController(t)
   727  
   728  	applyMock := mocks.NewMockApplies(ctrl)
   729  	// This needs three new lines because we check for a minimum of three lines
   730  	// in the parsing of logs in `opApply` function.
   731  	logs := strings.NewReader(applySuccessOneResourceAdded)
   732  	applyMock.EXPECT().Logs(gomock.Any(), gomock.Any()).Return(logs, nil)
   733  	b.client.Applies = applyMock
   734  
   735  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   736  	defer configCleanup()
   737  	defer done(t)
   738  
   739  	input := testInput(t, map[string]string{
   740  		"approve": "no",
   741  	})
   742  
   743  	op.AutoApprove = true
   744  	op.UIIn = input
   745  	op.UIOut = b.CLI
   746  	op.Workspace = testBackendSingleWorkspaceName
   747  
   748  	run, err := b.Operation(context.Background(), op)
   749  	if err != nil {
   750  		t.Fatalf("error starting operation: %v", err)
   751  	}
   752  
   753  	<-run.Done()
   754  	if run.Result != backend.OperationSuccess {
   755  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   756  	}
   757  	if run.PlanEmpty {
   758  		t.Fatalf("expected a non-empty plan")
   759  	}
   760  
   761  	if len(input.answers) != 1 {
   762  		t.Fatalf("expected an unused answer, got: %v", input.answers)
   763  	}
   764  
   765  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   766  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
   767  		t.Fatalf("expected TFC header in output: %s", output)
   768  	}
   769  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   770  		t.Fatalf("expected plan summery in output: %s", output)
   771  	}
   772  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   773  		t.Fatalf("expected apply summery in output: %s", output)
   774  	}
   775  }
   776  
   777  func TestCloud_applyApprovedExternally(t *testing.T) {
   778  	b, bCleanup := testBackendWithName(t)
   779  	defer bCleanup()
   780  
   781  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   782  	defer configCleanup()
   783  	defer done(t)
   784  
   785  	input := testInput(t, map[string]string{
   786  		"approve": "wait-for-external-update",
   787  	})
   788  
   789  	op.UIIn = input
   790  	op.UIOut = b.CLI
   791  	op.Workspace = testBackendSingleWorkspaceName
   792  
   793  	ctx := context.Background()
   794  
   795  	run, err := b.Operation(ctx, op)
   796  	if err != nil {
   797  		t.Fatalf("error starting operation: %v", err)
   798  	}
   799  
   800  	// Wait 50 milliseconds to make sure the run started.
   801  	time.Sleep(50 * time.Millisecond)
   802  
   803  	wl, err := b.client.Workspaces.List(
   804  		ctx,
   805  		b.organization,
   806  		nil,
   807  	)
   808  	if err != nil {
   809  		t.Fatalf("unexpected error listing workspaces: %v", err)
   810  	}
   811  	if len(wl.Items) != 1 {
   812  		t.Fatalf("expected 1 workspace, got %d workspaces", len(wl.Items))
   813  	}
   814  
   815  	rl, err := b.client.Runs.List(ctx, wl.Items[0].ID, nil)
   816  	if err != nil {
   817  		t.Fatalf("unexpected error listing runs: %v", err)
   818  	}
   819  	if len(rl.Items) != 1 {
   820  		t.Fatalf("expected 1 run, got %d runs", len(rl.Items))
   821  	}
   822  
   823  	err = b.client.Runs.Apply(context.Background(), rl.Items[0].ID, tfe.RunApplyOptions{})
   824  	if err != nil {
   825  		t.Fatalf("unexpected error approving run: %v", err)
   826  	}
   827  
   828  	<-run.Done()
   829  	if run.Result != backend.OperationSuccess {
   830  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   831  	}
   832  	if run.PlanEmpty {
   833  		t.Fatalf("expected a non-empty plan")
   834  	}
   835  
   836  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   837  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
   838  		t.Fatalf("expected TFC header in output: %s", output)
   839  	}
   840  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   841  		t.Fatalf("expected plan summery in output: %s", output)
   842  	}
   843  	if !strings.Contains(output, "approved using the UI or API") {
   844  		t.Fatalf("expected external approval in output: %s", output)
   845  	}
   846  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   847  		t.Fatalf("expected apply summery in output: %s", output)
   848  	}
   849  }
   850  
   851  func TestCloud_applyDiscardedExternally(t *testing.T) {
   852  	b, bCleanup := testBackendWithName(t)
   853  	defer bCleanup()
   854  
   855  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   856  	defer configCleanup()
   857  	defer done(t)
   858  
   859  	input := testInput(t, map[string]string{
   860  		"approve": "wait-for-external-update",
   861  	})
   862  
   863  	op.UIIn = input
   864  	op.UIOut = b.CLI
   865  	op.Workspace = testBackendSingleWorkspaceName
   866  
   867  	ctx := context.Background()
   868  
   869  	run, err := b.Operation(ctx, op)
   870  	if err != nil {
   871  		t.Fatalf("error starting operation: %v", err)
   872  	}
   873  
   874  	// Wait 50 milliseconds to make sure the run started.
   875  	time.Sleep(50 * time.Millisecond)
   876  
   877  	wl, err := b.client.Workspaces.List(
   878  		ctx,
   879  		b.organization,
   880  		nil,
   881  	)
   882  	if err != nil {
   883  		t.Fatalf("unexpected error listing workspaces: %v", err)
   884  	}
   885  	if len(wl.Items) != 1 {
   886  		t.Fatalf("expected 1 workspace, got %d workspaces", len(wl.Items))
   887  	}
   888  
   889  	rl, err := b.client.Runs.List(ctx, wl.Items[0].ID, nil)
   890  	if err != nil {
   891  		t.Fatalf("unexpected error listing runs: %v", err)
   892  	}
   893  	if len(rl.Items) != 1 {
   894  		t.Fatalf("expected 1 run, got %d runs", len(rl.Items))
   895  	}
   896  
   897  	err = b.client.Runs.Discard(context.Background(), rl.Items[0].ID, tfe.RunDiscardOptions{})
   898  	if err != nil {
   899  		t.Fatalf("unexpected error discarding run: %v", err)
   900  	}
   901  
   902  	<-run.Done()
   903  	if run.Result == backend.OperationSuccess {
   904  		t.Fatal("expected apply operation to fail")
   905  	}
   906  	if !run.PlanEmpty {
   907  		t.Fatalf("expected plan to be empty")
   908  	}
   909  
   910  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   911  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
   912  		t.Fatalf("expected TFC header in output: %s", output)
   913  	}
   914  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   915  		t.Fatalf("expected plan summery in output: %s", output)
   916  	}
   917  	if !strings.Contains(output, "discarded using the UI or API") {
   918  		t.Fatalf("expected external discard output: %s", output)
   919  	}
   920  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   921  		t.Fatalf("unexpected apply summery in output: %s", output)
   922  	}
   923  }
   924  
   925  func TestCloud_applyWithAutoApprove(t *testing.T) {
   926  	b, bCleanup := testBackendWithTags(t)
   927  	defer bCleanup()
   928  	ctrl := gomock.NewController(t)
   929  
   930  	applyMock := mocks.NewMockApplies(ctrl)
   931  	// This needs three new lines because we check for a minimum of three lines
   932  	// in the parsing of logs in `opApply` function.
   933  	logs := strings.NewReader(applySuccessOneResourceAdded)
   934  	applyMock.EXPECT().Logs(gomock.Any(), gomock.Any()).Return(logs, nil)
   935  	b.client.Applies = applyMock
   936  
   937  	// Create a named workspace that auto applies.
   938  	_, err := b.client.Workspaces.Create(
   939  		context.Background(),
   940  		b.organization,
   941  		tfe.WorkspaceCreateOptions{
   942  			Name: tfe.String("prod"),
   943  		},
   944  	)
   945  	if err != nil {
   946  		t.Fatalf("error creating named workspace: %v", err)
   947  	}
   948  
   949  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
   950  	defer configCleanup()
   951  	defer done(t)
   952  
   953  	input := testInput(t, map[string]string{
   954  		"approve": "yes",
   955  	})
   956  
   957  	op.UIIn = input
   958  	op.UIOut = b.CLI
   959  	op.Workspace = "prod"
   960  	op.AutoApprove = true
   961  
   962  	run, err := b.Operation(context.Background(), op)
   963  	if err != nil {
   964  		t.Fatalf("error starting operation: %v", err)
   965  	}
   966  
   967  	<-run.Done()
   968  	if run.Result != backend.OperationSuccess {
   969  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   970  	}
   971  	if run.PlanEmpty {
   972  		t.Fatalf("expected a non-empty plan")
   973  	}
   974  
   975  	if len(input.answers) != 1 {
   976  		t.Fatalf("expected an unused answer, got: %v", input.answers)
   977  	}
   978  
   979  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   980  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
   981  		t.Fatalf("expected TFC header in output: %s", output)
   982  	}
   983  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   984  		t.Fatalf("expected plan summery in output: %s", output)
   985  	}
   986  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   987  		t.Fatalf("expected apply summery in output: %s", output)
   988  	}
   989  }
   990  
   991  func TestCloud_applyForceLocal(t *testing.T) {
   992  	// Set TF_FORCE_LOCAL_BACKEND so the cloud backend will use
   993  	// the local backend with itself as embedded backend.
   994  	if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil {
   995  		t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err)
   996  	}
   997  	defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND")
   998  
   999  	b, bCleanup := testBackendWithName(t)
  1000  	defer bCleanup()
  1001  
  1002  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
  1003  	defer configCleanup()
  1004  	defer done(t)
  1005  
  1006  	input := testInput(t, map[string]string{
  1007  		"approve": "yes",
  1008  	})
  1009  
  1010  	op.UIIn = input
  1011  	op.UIOut = b.CLI
  1012  	op.Workspace = testBackendSingleWorkspaceName
  1013  
  1014  	streams, done := terminal.StreamsForTesting(t)
  1015  	view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
  1016  	op.View = view
  1017  
  1018  	run, err := b.Operation(context.Background(), op)
  1019  	if err != nil {
  1020  		t.Fatalf("error starting operation: %v", err)
  1021  	}
  1022  
  1023  	<-run.Done()
  1024  	if run.Result != backend.OperationSuccess {
  1025  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1026  	}
  1027  	if run.PlanEmpty {
  1028  		t.Fatalf("expected a non-empty plan")
  1029  	}
  1030  
  1031  	if len(input.answers) > 0 {
  1032  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1033  	}
  1034  
  1035  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1036  	if strings.Contains(output, "Running apply in Terraform Cloud") {
  1037  		t.Fatalf("unexpected TFC header in output: %s", output)
  1038  	}
  1039  	if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1040  		t.Fatalf("expected plan summary in output: %s", output)
  1041  	}
  1042  	if !run.State.HasManagedResourceInstanceObjects() {
  1043  		t.Fatalf("expected resources in state")
  1044  	}
  1045  }
  1046  
  1047  func TestCloud_applyWorkspaceWithoutOperations(t *testing.T) {
  1048  	b, bCleanup := testBackendWithTags(t)
  1049  	defer bCleanup()
  1050  
  1051  	ctx := context.Background()
  1052  
  1053  	// Create a named workspace that doesn't allow operations.
  1054  	_, err := b.client.Workspaces.Create(
  1055  		ctx,
  1056  		b.organization,
  1057  		tfe.WorkspaceCreateOptions{
  1058  			Name: tfe.String("no-operations"),
  1059  		},
  1060  	)
  1061  	if err != nil {
  1062  		t.Fatalf("error creating named workspace: %v", err)
  1063  	}
  1064  
  1065  	op, configCleanup, done := testOperationApply(t, "./testdata/apply")
  1066  	defer configCleanup()
  1067  	defer done(t)
  1068  
  1069  	input := testInput(t, map[string]string{
  1070  		"approve": "yes",
  1071  	})
  1072  
  1073  	op.UIIn = input
  1074  	op.UIOut = b.CLI
  1075  	op.Workspace = "no-operations"
  1076  
  1077  	streams, done := terminal.StreamsForTesting(t)
  1078  	view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
  1079  	op.View = view
  1080  
  1081  	run, err := b.Operation(ctx, op)
  1082  	if err != nil {
  1083  		t.Fatalf("error starting operation: %v", err)
  1084  	}
  1085  
  1086  	<-run.Done()
  1087  	if run.Result != backend.OperationSuccess {
  1088  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1089  	}
  1090  	if run.PlanEmpty {
  1091  		t.Fatalf("expected a non-empty plan")
  1092  	}
  1093  
  1094  	if len(input.answers) > 0 {
  1095  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1096  	}
  1097  
  1098  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1099  	if strings.Contains(output, "Running apply in Terraform Cloud") {
  1100  		t.Fatalf("unexpected TFC header in output: %s", output)
  1101  	}
  1102  	if output := done(t).Stdout(); !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1103  		t.Fatalf("expected plan summary in output: %s", output)
  1104  	}
  1105  	if !run.State.HasManagedResourceInstanceObjects() {
  1106  		t.Fatalf("expected resources in state")
  1107  	}
  1108  }
  1109  
  1110  func TestCloud_applyLockTimeout(t *testing.T) {
  1111  	b, bCleanup := testBackendWithName(t)
  1112  	defer bCleanup()
  1113  
  1114  	ctx := context.Background()
  1115  
  1116  	// Retrieve the workspace used to run this operation in.
  1117  	w, err := b.client.Workspaces.Read(ctx, b.organization, b.WorkspaceMapping.Name)
  1118  	if err != nil {
  1119  		t.Fatalf("error retrieving workspace: %v", err)
  1120  	}
  1121  
  1122  	// Create a new configuration version.
  1123  	c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
  1124  	if err != nil {
  1125  		t.Fatalf("error creating configuration version: %v", err)
  1126  	}
  1127  
  1128  	// Create a pending run to block this run.
  1129  	_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
  1130  		ConfigurationVersion: c,
  1131  		Workspace:            w,
  1132  	})
  1133  	if err != nil {
  1134  		t.Fatalf("error creating pending run: %v", err)
  1135  	}
  1136  
  1137  	op, configCleanup, done := testOperationApplyWithTimeout(t, "./testdata/apply", 50*time.Millisecond)
  1138  	defer configCleanup()
  1139  	defer done(t)
  1140  
  1141  	input := testInput(t, map[string]string{
  1142  		"cancel":  "yes",
  1143  		"approve": "yes",
  1144  	})
  1145  
  1146  	op.UIIn = input
  1147  	op.UIOut = b.CLI
  1148  	op.Workspace = testBackendSingleWorkspaceName
  1149  
  1150  	_, err = b.Operation(context.Background(), op)
  1151  	if err != nil {
  1152  		t.Fatalf("error starting operation: %v", err)
  1153  	}
  1154  
  1155  	sigint := make(chan os.Signal, 1)
  1156  	signal.Notify(sigint, syscall.SIGINT)
  1157  	select {
  1158  	case <-sigint:
  1159  		// Stop redirecting SIGINT signals.
  1160  		signal.Stop(sigint)
  1161  	case <-time.After(200 * time.Millisecond):
  1162  		t.Fatalf("expected lock timeout after 50 milliseconds, waited 200 milliseconds")
  1163  	}
  1164  
  1165  	if len(input.answers) != 2 {
  1166  		t.Fatalf("expected unused answers, got: %v", input.answers)
  1167  	}
  1168  
  1169  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1170  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
  1171  		t.Fatalf("expected TFC header in output: %s", output)
  1172  	}
  1173  	if !strings.Contains(output, "Lock timeout exceeded") {
  1174  		t.Fatalf("expected lock timout error in output: %s", output)
  1175  	}
  1176  	if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1177  		t.Fatalf("unexpected plan summery in output: %s", output)
  1178  	}
  1179  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
  1180  		t.Fatalf("unexpected apply summery in output: %s", output)
  1181  	}
  1182  }
  1183  
  1184  func TestCloud_applyDestroy(t *testing.T) {
  1185  	b, bCleanup := testBackendWithName(t)
  1186  	defer bCleanup()
  1187  
  1188  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-destroy")
  1189  	defer configCleanup()
  1190  	defer done(t)
  1191  
  1192  	input := testInput(t, map[string]string{
  1193  		"approve": "yes",
  1194  	})
  1195  
  1196  	op.PlanMode = plans.DestroyMode
  1197  	op.UIIn = input
  1198  	op.UIOut = b.CLI
  1199  	op.Workspace = testBackendSingleWorkspaceName
  1200  
  1201  	run, err := b.Operation(context.Background(), op)
  1202  	if err != nil {
  1203  		t.Fatalf("error starting operation: %v", err)
  1204  	}
  1205  
  1206  	<-run.Done()
  1207  	if run.Result != backend.OperationSuccess {
  1208  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1209  	}
  1210  	if run.PlanEmpty {
  1211  		t.Fatalf("expected a non-empty plan")
  1212  	}
  1213  
  1214  	if len(input.answers) > 0 {
  1215  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1216  	}
  1217  
  1218  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1219  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
  1220  		t.Fatalf("expected TFC header in output: %s", output)
  1221  	}
  1222  	if !strings.Contains(output, "0 to add, 0 to change, 1 to destroy") {
  1223  		t.Fatalf("expected plan summery in output: %s", output)
  1224  	}
  1225  	if !strings.Contains(output, "0 added, 0 changed, 1 destroyed") {
  1226  		t.Fatalf("expected apply summery in output: %s", output)
  1227  	}
  1228  }
  1229  
  1230  func TestCloud_applyDestroyNoConfig(t *testing.T) {
  1231  	b, bCleanup := testBackendWithName(t)
  1232  	defer bCleanup()
  1233  
  1234  	input := testInput(t, map[string]string{
  1235  		"approve": "yes",
  1236  	})
  1237  
  1238  	op, configCleanup, done := testOperationApply(t, "./testdata/empty")
  1239  	defer configCleanup()
  1240  	defer done(t)
  1241  
  1242  	op.PlanMode = plans.DestroyMode
  1243  	op.UIIn = input
  1244  	op.UIOut = b.CLI
  1245  	op.Workspace = testBackendSingleWorkspaceName
  1246  
  1247  	run, err := b.Operation(context.Background(), op)
  1248  	if err != nil {
  1249  		t.Fatalf("error starting operation: %v", err)
  1250  	}
  1251  
  1252  	<-run.Done()
  1253  	if run.Result != backend.OperationSuccess {
  1254  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1255  	}
  1256  	if run.PlanEmpty {
  1257  		t.Fatalf("expected a non-empty plan")
  1258  	}
  1259  
  1260  	if len(input.answers) > 0 {
  1261  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1262  	}
  1263  }
  1264  
  1265  func TestCloud_applyJSONWithProvisioner(t *testing.T) {
  1266  	b, bCleanup := testBackendWithName(t)
  1267  	defer bCleanup()
  1268  
  1269  	stream, close := terminal.StreamsForTesting(t)
  1270  
  1271  	b.renderer = &jsonformat.Renderer{
  1272  		Streams:  stream,
  1273  		Colorize: mockColorize(),
  1274  	}
  1275  	input := testInput(t, map[string]string{
  1276  		"approve": "yes",
  1277  	})
  1278  
  1279  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-json-with-provisioner")
  1280  	defer configCleanup()
  1281  	defer done(t)
  1282  
  1283  	op.UIIn = input
  1284  	op.UIOut = b.CLI
  1285  	op.Workspace = testBackendSingleWorkspaceName
  1286  
  1287  	mockSROWorkspace(t, b, op.Workspace)
  1288  
  1289  	run, err := b.Operation(context.Background(), op)
  1290  	if err != nil {
  1291  		t.Fatalf("error starting operation: %v", err)
  1292  	}
  1293  
  1294  	<-run.Done()
  1295  	if run.Result != backend.OperationSuccess {
  1296  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1297  	}
  1298  
  1299  	if run.PlanEmpty {
  1300  		t.Fatalf("expected a non-empty plan")
  1301  	}
  1302  
  1303  	if len(input.answers) > 0 {
  1304  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1305  	}
  1306  
  1307  	outp := close(t)
  1308  	gotOut := outp.Stdout()
  1309  	if !strings.Contains(gotOut, "null_resource.foo: Provisioning with 'local-exec'") {
  1310  		t.Fatalf("expected provisioner local-exec start in logs: %s", gotOut)
  1311  	}
  1312  
  1313  	if !strings.Contains(gotOut, "null_resource.foo: (local-exec):") {
  1314  		t.Fatalf("expected provisioner local-exec progress in logs: %s", gotOut)
  1315  	}
  1316  
  1317  	if !strings.Contains(gotOut, "Hello World!") {
  1318  		t.Fatalf("expected provisioner local-exec output in logs: %s", gotOut)
  1319  	}
  1320  
  1321  	stateMgr, _ := b.StateMgr(testBackendSingleWorkspaceName)
  1322  	// An error suggests that the state was not unlocked after apply
  1323  	if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
  1324  		t.Fatalf("unexpected error locking state after apply: %s", err.Error())
  1325  	}
  1326  }
  1327  
  1328  func TestCloud_applyJSONWithProvisionerError(t *testing.T) {
  1329  	b, bCleanup := testBackendWithName(t)
  1330  	defer bCleanup()
  1331  
  1332  	stream, close := terminal.StreamsForTesting(t)
  1333  
  1334  	b.renderer = &jsonformat.Renderer{
  1335  		Streams:  stream,
  1336  		Colorize: mockColorize(),
  1337  	}
  1338  
  1339  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-json-with-provisioner-error")
  1340  	defer configCleanup()
  1341  	defer done(t)
  1342  
  1343  	op.Workspace = testBackendSingleWorkspaceName
  1344  
  1345  	mockSROWorkspace(t, b, op.Workspace)
  1346  
  1347  	run, err := b.Operation(context.Background(), op)
  1348  	if err != nil {
  1349  		t.Fatalf("error starting operation: %v", err)
  1350  	}
  1351  
  1352  	<-run.Done()
  1353  
  1354  	outp := close(t)
  1355  	gotOut := outp.Stdout()
  1356  
  1357  	if !strings.Contains(gotOut, "local-exec provisioner error") {
  1358  		t.Fatalf("unexpected error in apply logs: %s", gotOut)
  1359  	}
  1360  }
  1361  
  1362  func TestCloud_applyPolicyPass(t *testing.T) {
  1363  	b, bCleanup := testBackendWithName(t)
  1364  	defer bCleanup()
  1365  
  1366  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-passed")
  1367  	defer configCleanup()
  1368  	defer done(t)
  1369  
  1370  	input := testInput(t, map[string]string{
  1371  		"approve": "yes",
  1372  	})
  1373  
  1374  	op.UIIn = input
  1375  	op.UIOut = b.CLI
  1376  	op.Workspace = testBackendSingleWorkspaceName
  1377  
  1378  	run, err := b.Operation(context.Background(), op)
  1379  	if err != nil {
  1380  		t.Fatalf("error starting operation: %v", err)
  1381  	}
  1382  
  1383  	<-run.Done()
  1384  	if run.Result != backend.OperationSuccess {
  1385  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1386  	}
  1387  	if run.PlanEmpty {
  1388  		t.Fatalf("expected a non-empty plan")
  1389  	}
  1390  
  1391  	if len(input.answers) > 0 {
  1392  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1393  	}
  1394  
  1395  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1396  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
  1397  		t.Fatalf("expected TFC header in output: %s", output)
  1398  	}
  1399  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1400  		t.Fatalf("expected plan summery in output: %s", output)
  1401  	}
  1402  	if !strings.Contains(output, "Sentinel Result: true") {
  1403  		t.Fatalf("expected policy check result in output: %s", output)
  1404  	}
  1405  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
  1406  		t.Fatalf("expected apply summery in output: %s", output)
  1407  	}
  1408  }
  1409  
  1410  func TestCloud_applyPolicyHardFail(t *testing.T) {
  1411  	b, bCleanup := testBackendWithName(t)
  1412  	defer bCleanup()
  1413  
  1414  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-hard-failed")
  1415  	defer configCleanup()
  1416  
  1417  	input := testInput(t, map[string]string{
  1418  		"approve": "yes",
  1419  	})
  1420  
  1421  	op.UIIn = input
  1422  	op.UIOut = b.CLI
  1423  	op.Workspace = testBackendSingleWorkspaceName
  1424  
  1425  	run, err := b.Operation(context.Background(), op)
  1426  	if err != nil {
  1427  		t.Fatalf("error starting operation: %v", err)
  1428  	}
  1429  
  1430  	<-run.Done()
  1431  	viewOutput := done(t)
  1432  	if run.Result == backend.OperationSuccess {
  1433  		t.Fatal("expected apply operation to fail")
  1434  	}
  1435  	if !run.PlanEmpty {
  1436  		t.Fatalf("expected plan to be empty")
  1437  	}
  1438  
  1439  	if len(input.answers) != 1 {
  1440  		t.Fatalf("expected an unused answers, got: %v", input.answers)
  1441  	}
  1442  
  1443  	errOutput := viewOutput.Stderr()
  1444  	if !strings.Contains(errOutput, "hard failed") {
  1445  		t.Fatalf("expected a policy check error, got: %v", errOutput)
  1446  	}
  1447  
  1448  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1449  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
  1450  		t.Fatalf("expected TFC header in output: %s", output)
  1451  	}
  1452  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1453  		t.Fatalf("expected plan summery in output: %s", output)
  1454  	}
  1455  	if !strings.Contains(output, "Sentinel Result: false") {
  1456  		t.Fatalf("expected policy check result in output: %s", output)
  1457  	}
  1458  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
  1459  		t.Fatalf("unexpected apply summery in output: %s", output)
  1460  	}
  1461  }
  1462  
  1463  func TestCloud_applyPolicySoftFail(t *testing.T) {
  1464  	b, bCleanup := testBackendWithName(t)
  1465  	defer bCleanup()
  1466  
  1467  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-soft-failed")
  1468  	defer configCleanup()
  1469  	defer done(t)
  1470  
  1471  	input := testInput(t, map[string]string{
  1472  		"override": "override",
  1473  		"approve":  "yes",
  1474  	})
  1475  
  1476  	op.AutoApprove = false
  1477  	op.UIIn = input
  1478  	op.UIOut = b.CLI
  1479  	op.Workspace = testBackendSingleWorkspaceName
  1480  
  1481  	run, err := b.Operation(context.Background(), op)
  1482  	if err != nil {
  1483  		t.Fatalf("error starting operation: %v", err)
  1484  	}
  1485  
  1486  	<-run.Done()
  1487  	if run.Result != backend.OperationSuccess {
  1488  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1489  	}
  1490  	if run.PlanEmpty {
  1491  		t.Fatalf("expected a non-empty plan")
  1492  	}
  1493  
  1494  	if len(input.answers) > 0 {
  1495  		t.Fatalf("expected no unused answers, got: %v", input.answers)
  1496  	}
  1497  
  1498  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1499  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
  1500  		t.Fatalf("expected TFC header in output: %s", output)
  1501  	}
  1502  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1503  		t.Fatalf("expected plan summery in output: %s", output)
  1504  	}
  1505  	if !strings.Contains(output, "Sentinel Result: false") {
  1506  		t.Fatalf("expected policy check result in output: %s", output)
  1507  	}
  1508  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
  1509  		t.Fatalf("expected apply summery in output: %s", output)
  1510  	}
  1511  }
  1512  
  1513  func TestCloud_applyPolicySoftFailAutoApproveSuccess(t *testing.T) {
  1514  	b, bCleanup := testBackendWithName(t)
  1515  	defer bCleanup()
  1516  	ctrl := gomock.NewController(t)
  1517  
  1518  	policyCheckMock := mocks.NewMockPolicyChecks(ctrl)
  1519  	// This needs three new lines because we check for a minimum of three lines
  1520  	// in the parsing of logs in `opApply` function.
  1521  	logs := strings.NewReader(fmt.Sprintf("%s\n%s", sentinelSoftFail, applySuccessOneResourceAdded))
  1522  
  1523  	pc := &tfe.PolicyCheck{
  1524  		ID: "pc-1",
  1525  		Actions: &tfe.PolicyActions{
  1526  			IsOverridable: true,
  1527  		},
  1528  		Permissions: &tfe.PolicyPermissions{
  1529  			CanOverride: true,
  1530  		},
  1531  		Scope:  tfe.PolicyScopeOrganization,
  1532  		Status: tfe.PolicySoftFailed,
  1533  	}
  1534  	policyCheckMock.EXPECT().Read(gomock.Any(), gomock.Any()).Return(pc, nil)
  1535  	policyCheckMock.EXPECT().Logs(gomock.Any(), gomock.Any()).Return(logs, nil)
  1536  	policyCheckMock.EXPECT().Override(gomock.Any(), gomock.Any()).Return(nil, nil)
  1537  	b.client.PolicyChecks = policyCheckMock
  1538  	applyMock := mocks.NewMockApplies(ctrl)
  1539  	// This needs three new lines because we check for a minimum of three lines
  1540  	// in the parsing of logs in `opApply` function.
  1541  	logs = strings.NewReader("\n\n\n1 added, 0 changed, 0 destroyed")
  1542  	applyMock.EXPECT().Logs(gomock.Any(), gomock.Any()).Return(logs, nil)
  1543  	b.client.Applies = applyMock
  1544  
  1545  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-soft-failed")
  1546  	defer configCleanup()
  1547  
  1548  	input := testInput(t, map[string]string{})
  1549  
  1550  	op.AutoApprove = true
  1551  	op.UIIn = input
  1552  	op.UIOut = b.CLI
  1553  	op.Workspace = testBackendSingleWorkspaceName
  1554  
  1555  	run, err := b.Operation(context.Background(), op)
  1556  	if err != nil {
  1557  		t.Fatalf("error starting operation: %v", err)
  1558  	}
  1559  
  1560  	<-run.Done()
  1561  	viewOutput := done(t)
  1562  	if run.Result != backend.OperationSuccess {
  1563  		t.Fatal("expected apply operation to success due to auto-approve")
  1564  	}
  1565  
  1566  	if run.PlanEmpty {
  1567  		t.Fatalf("expected plan to not be empty, plan opertion completed without error")
  1568  	}
  1569  
  1570  	if len(input.answers) != 0 {
  1571  		t.Fatalf("expected no answers, got: %v", input.answers)
  1572  	}
  1573  
  1574  	errOutput := viewOutput.Stderr()
  1575  	if strings.Contains(errOutput, "soft failed") {
  1576  		t.Fatalf("expected no policy check errors, instead got: %v", errOutput)
  1577  	}
  1578  
  1579  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1580  	if !strings.Contains(output, "Sentinel Result: false") {
  1581  		t.Fatalf("expected policy check to be false, insead got: %s", output)
  1582  	}
  1583  	if !strings.Contains(output, "Apply complete!") {
  1584  		t.Fatalf("expected apply to be complete, instead got: %s", output)
  1585  	}
  1586  
  1587  	if !strings.Contains(output, "Resources: 1 added, 0 changed, 0 destroyed") {
  1588  		t.Fatalf("expected resources, instead got: %s", output)
  1589  	}
  1590  }
  1591  
  1592  func TestCloud_applyPolicySoftFailAutoApprove(t *testing.T) {
  1593  	b, bCleanup := testBackendWithName(t)
  1594  	defer bCleanup()
  1595  	ctrl := gomock.NewController(t)
  1596  
  1597  	applyMock := mocks.NewMockApplies(ctrl)
  1598  	// This needs three new lines because we check for a minimum of three lines
  1599  	// in the parsing of logs in `opApply` function.
  1600  	logs := strings.NewReader(applySuccessOneResourceAdded)
  1601  	applyMock.EXPECT().Logs(gomock.Any(), gomock.Any()).Return(logs, nil)
  1602  	b.client.Applies = applyMock
  1603  
  1604  	// Create a named workspace that auto applies.
  1605  	_, err := b.client.Workspaces.Create(
  1606  		context.Background(),
  1607  		b.organization,
  1608  		tfe.WorkspaceCreateOptions{
  1609  			Name: tfe.String("prod"),
  1610  		},
  1611  	)
  1612  	if err != nil {
  1613  		t.Fatalf("error creating named workspace: %v", err)
  1614  	}
  1615  
  1616  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-policy-soft-failed")
  1617  	defer configCleanup()
  1618  	defer done(t)
  1619  
  1620  	input := testInput(t, map[string]string{
  1621  		"override": "override",
  1622  		"approve":  "yes",
  1623  	})
  1624  
  1625  	op.UIIn = input
  1626  	op.UIOut = b.CLI
  1627  	op.Workspace = "prod"
  1628  	op.AutoApprove = true
  1629  
  1630  	run, err := b.Operation(context.Background(), op)
  1631  	if err != nil {
  1632  		t.Fatalf("error starting operation: %v", err)
  1633  	}
  1634  
  1635  	<-run.Done()
  1636  	if run.Result != backend.OperationSuccess {
  1637  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1638  	}
  1639  	if run.PlanEmpty {
  1640  		t.Fatalf("expected a non-empty plan")
  1641  	}
  1642  
  1643  	if len(input.answers) != 2 {
  1644  		t.Fatalf("expected an unused answer, got: %v", input.answers)
  1645  	}
  1646  
  1647  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1648  	if !strings.Contains(output, "Running apply in Terraform Cloud") {
  1649  		t.Fatalf("expected TFC header in output: %s", output)
  1650  	}
  1651  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
  1652  		t.Fatalf("expected plan summery in output: %s", output)
  1653  	}
  1654  	if !strings.Contains(output, "Sentinel Result: false") {
  1655  		t.Fatalf("expected policy check result in output: %s", output)
  1656  	}
  1657  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
  1658  		t.Fatalf("expected apply summery in output: %s", output)
  1659  	}
  1660  }
  1661  
  1662  func TestCloud_applyWithRemoteError(t *testing.T) {
  1663  	b, bCleanup := testBackendWithName(t)
  1664  	defer bCleanup()
  1665  
  1666  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-with-error")
  1667  	defer configCleanup()
  1668  	defer done(t)
  1669  
  1670  	op.Workspace = testBackendSingleWorkspaceName
  1671  
  1672  	run, err := b.Operation(context.Background(), op)
  1673  	if err != nil {
  1674  		t.Fatalf("error starting operation: %v", err)
  1675  	}
  1676  
  1677  	<-run.Done()
  1678  	if run.Result == backend.OperationSuccess {
  1679  		t.Fatal("expected apply operation to fail")
  1680  	}
  1681  	if run.Result.ExitStatus() != 1 {
  1682  		t.Fatalf("expected exit code 1, got %d", run.Result.ExitStatus())
  1683  	}
  1684  
  1685  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1686  	if !strings.Contains(output, "null_resource.foo: 1 error") {
  1687  		t.Fatalf("expected apply error in output: %s", output)
  1688  	}
  1689  }
  1690  
  1691  func TestCloud_applyJSONWithRemoteError(t *testing.T) {
  1692  	b, bCleanup := testBackendWithName(t)
  1693  	defer bCleanup()
  1694  
  1695  	stream, close := terminal.StreamsForTesting(t)
  1696  
  1697  	b.renderer = &jsonformat.Renderer{
  1698  		Streams:  stream,
  1699  		Colorize: mockColorize(),
  1700  	}
  1701  
  1702  	op, configCleanup, done := testOperationApply(t, "./testdata/apply-json-with-error")
  1703  	defer configCleanup()
  1704  	defer done(t)
  1705  
  1706  	op.Workspace = testBackendSingleWorkspaceName
  1707  
  1708  	mockSROWorkspace(t, b, op.Workspace)
  1709  
  1710  	run, err := b.Operation(context.Background(), op)
  1711  	if err != nil {
  1712  		t.Fatalf("error starting operation: %v", err)
  1713  	}
  1714  
  1715  	<-run.Done()
  1716  	if run.Result == backend.OperationSuccess {
  1717  		t.Fatal("expected apply operation to fail")
  1718  	}
  1719  	if run.Result.ExitStatus() != 1 {
  1720  		t.Fatalf("expected exit code 1, got %d", run.Result.ExitStatus())
  1721  	}
  1722  
  1723  	outp := close(t)
  1724  	gotOut := outp.Stdout()
  1725  
  1726  	if !strings.Contains(gotOut, "Unsupported block type") {
  1727  		t.Fatalf("unexpected plan error in output: %s", gotOut)
  1728  	}
  1729  }
  1730  
  1731  func TestCloud_applyVersionCheck(t *testing.T) {
  1732  	testCases := map[string]struct {
  1733  		localVersion  string
  1734  		remoteVersion string
  1735  		forceLocal    bool
  1736  		executionMode string
  1737  		wantErr       string
  1738  	}{
  1739  		"versions can be different for remote apply": {
  1740  			localVersion:  "0.14.0",
  1741  			remoteVersion: "0.13.5",
  1742  			executionMode: "remote",
  1743  		},
  1744  		"versions can be different for local apply": {
  1745  			localVersion:  "0.14.0",
  1746  			remoteVersion: "0.13.5",
  1747  			executionMode: "local",
  1748  		},
  1749  		"force local with remote operations and different versions is acceptable": {
  1750  			localVersion:  "0.14.0",
  1751  			remoteVersion: "0.14.0-acme-provider-bundle",
  1752  			forceLocal:    true,
  1753  			executionMode: "remote",
  1754  		},
  1755  		"no error if versions are identical": {
  1756  			localVersion:  "0.14.0",
  1757  			remoteVersion: "0.14.0",
  1758  			forceLocal:    true,
  1759  			executionMode: "remote",
  1760  		},
  1761  		"no error if force local but workspace has remote operations disabled": {
  1762  			localVersion:  "0.14.0",
  1763  			remoteVersion: "0.13.5",
  1764  			forceLocal:    true,
  1765  			executionMode: "local",
  1766  		},
  1767  	}
  1768  
  1769  	for name, tc := range testCases {
  1770  		t.Run(name, func(t *testing.T) {
  1771  			b, bCleanup := testBackendWithName(t)
  1772  			defer bCleanup()
  1773  
  1774  			// SETUP: Save original local version state and restore afterwards
  1775  			p := tfversion.Prerelease
  1776  			v := tfversion.Version
  1777  			s := tfversion.SemVer
  1778  			defer func() {
  1779  				tfversion.Prerelease = p
  1780  				tfversion.Version = v
  1781  				tfversion.SemVer = s
  1782  			}()
  1783  
  1784  			// SETUP: Set local version for the test case
  1785  			tfversion.Prerelease = ""
  1786  			tfversion.Version = tc.localVersion
  1787  			tfversion.SemVer = version.Must(version.NewSemver(tc.localVersion))
  1788  
  1789  			// SETUP: Set force local for the test case
  1790  			b.forceLocal = tc.forceLocal
  1791  
  1792  			ctx := context.Background()
  1793  
  1794  			// SETUP: set the operations and Terraform Version fields on the
  1795  			// remote workspace
  1796  			_, err := b.client.Workspaces.Update(
  1797  				ctx,
  1798  				b.organization,
  1799  				b.WorkspaceMapping.Name,
  1800  				tfe.WorkspaceUpdateOptions{
  1801  					ExecutionMode:    tfe.String(tc.executionMode),
  1802  					TerraformVersion: tfe.String(tc.remoteVersion),
  1803  				},
  1804  			)
  1805  			if err != nil {
  1806  				t.Fatalf("error creating named workspace: %v", err)
  1807  			}
  1808  
  1809  			// RUN: prepare the apply operation and run it
  1810  			op, configCleanup, opDone := testOperationApply(t, "./testdata/apply")
  1811  			defer configCleanup()
  1812  			defer opDone(t)
  1813  
  1814  			streams, done := terminal.StreamsForTesting(t)
  1815  			view := views.NewOperation(arguments.ViewHuman, false, views.NewView(streams))
  1816  			op.View = view
  1817  
  1818  			input := testInput(t, map[string]string{
  1819  				"approve": "yes",
  1820  			})
  1821  
  1822  			op.UIIn = input
  1823  			op.UIOut = b.CLI
  1824  			op.Workspace = testBackendSingleWorkspaceName
  1825  
  1826  			run, err := b.Operation(ctx, op)
  1827  			if err != nil {
  1828  				t.Fatalf("error starting operation: %v", err)
  1829  			}
  1830  
  1831  			// RUN: wait for completion
  1832  			<-run.Done()
  1833  			output := done(t)
  1834  
  1835  			if tc.wantErr != "" {
  1836  				// ASSERT: if the test case wants an error, check for failure
  1837  				// and the error message
  1838  				if run.Result != backend.OperationFailure {
  1839  					t.Fatalf("expected run to fail, but result was %#v", run.Result)
  1840  				}
  1841  				errOutput := output.Stderr()
  1842  				if !strings.Contains(errOutput, tc.wantErr) {
  1843  					t.Fatalf("missing error %q\noutput: %s", tc.wantErr, errOutput)
  1844  				}
  1845  			} else {
  1846  				// ASSERT: otherwise, check for success and appropriate output
  1847  				// based on whether the run should be local or remote
  1848  				if run.Result != backend.OperationSuccess {
  1849  					t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
  1850  				}
  1851  				output := b.CLI.(*cli.MockUi).OutputWriter.String()
  1852  				hasRemote := strings.Contains(output, "Running apply in Terraform Cloud")
  1853  				hasSummary := strings.Contains(output, "1 added, 0 changed, 0 destroyed")
  1854  				hasResources := run.State.HasManagedResourceInstanceObjects()
  1855  				if !tc.forceLocal && !isLocalExecutionMode(tc.executionMode) {
  1856  					if !hasRemote {
  1857  						t.Errorf("missing TFC header in output: %s", output)
  1858  					}
  1859  					if !hasSummary {
  1860  						t.Errorf("expected apply summary in output: %s", output)
  1861  					}
  1862  				} else {
  1863  					if hasRemote {
  1864  						t.Errorf("unexpected TFC header in output: %s", output)
  1865  					}
  1866  					if !hasResources {
  1867  						t.Errorf("expected resources in state")
  1868  					}
  1869  				}
  1870  			}
  1871  		})
  1872  	}
  1873  }
  1874  
  1875  const applySuccessOneResourceAdded = `
  1876  Terraform v0.11.10
  1877  
  1878  Initializing plugins and modules...
  1879  null_resource.hello: Creating...
  1880  null_resource.hello: Creation complete after 0s (ID: 8657651096157629581)
  1881  
  1882  Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
  1883  `
  1884  
  1885  const sentinelSoftFail = `
  1886  Sentinel Result: false
  1887  
  1888  Sentinel evaluated to false because one or more Sentinel policies evaluated
  1889  to false. This false was not due to an undefined value or runtime error.
  1890  
  1891  1 policies evaluated.
  1892  
  1893  ## Policy 1: Passthrough.sentinel (soft-mandatory)
  1894  
  1895  Result: false
  1896  
  1897  FALSE - Passthrough.sentinel:1:1 - Rule "main"
  1898  `