github.com/pulumi/terraform@v1.4.0/pkg/backend/remote/backend_apply_test.go (about)

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