github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote/backend_plan_test.go (about)

     1  package remote
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"os/signal"
     7  	"strings"
     8  	"syscall"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/google/go-cmp/cmp"
    13  	tfe "github.com/hashicorp/go-tfe"
    14  	"github.com/hashicorp/terraform/addrs"
    15  	"github.com/hashicorp/terraform/backend"
    16  	"github.com/hashicorp/terraform/internal/initwd"
    17  	"github.com/hashicorp/terraform/plans/planfile"
    18  	"github.com/hashicorp/terraform/terraform"
    19  	"github.com/mitchellh/cli"
    20  )
    21  
    22  func testOperationPlan(t *testing.T, configDir string) (*backend.Operation, func()) {
    23  	t.Helper()
    24  
    25  	_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir)
    26  
    27  	return &backend.Operation{
    28  		ConfigDir:    configDir,
    29  		ConfigLoader: configLoader,
    30  		Parallelism:  defaultParallelism,
    31  		PlanRefresh:  true,
    32  		Type:         backend.OperationTypePlan,
    33  	}, configCleanup
    34  }
    35  
    36  func TestRemote_planBasic(t *testing.T) {
    37  	b, bCleanup := testBackendDefault(t)
    38  	defer bCleanup()
    39  
    40  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
    41  	defer configCleanup()
    42  
    43  	op.Workspace = backend.DefaultStateName
    44  
    45  	run, err := b.Operation(context.Background(), op)
    46  	if err != nil {
    47  		t.Fatalf("error starting operation: %v", err)
    48  	}
    49  
    50  	<-run.Done()
    51  	if run.Result != backend.OperationSuccess {
    52  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
    53  	}
    54  	if run.PlanEmpty {
    55  		t.Fatal("expected a non-empty plan")
    56  	}
    57  
    58  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
    59  	if !strings.Contains(output, "Running plan in the remote backend") {
    60  		t.Fatalf("expected remote backend header in output: %s", output)
    61  	}
    62  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
    63  		t.Fatalf("expected plan summary in output: %s", output)
    64  	}
    65  }
    66  
    67  func TestRemote_planCanceled(t *testing.T) {
    68  	b, bCleanup := testBackendDefault(t)
    69  	defer bCleanup()
    70  
    71  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
    72  	defer configCleanup()
    73  
    74  	op.Workspace = backend.DefaultStateName
    75  
    76  	run, err := b.Operation(context.Background(), op)
    77  	if err != nil {
    78  		t.Fatalf("error starting operation: %v", err)
    79  	}
    80  
    81  	// Stop the run to simulate a Ctrl-C.
    82  	run.Stop()
    83  
    84  	<-run.Done()
    85  	if run.Result == backend.OperationSuccess {
    86  		t.Fatal("expected plan operation to fail")
    87  	}
    88  }
    89  
    90  func TestRemote_planLongLine(t *testing.T) {
    91  	b, bCleanup := testBackendDefault(t)
    92  	defer bCleanup()
    93  
    94  	op, configCleanup := testOperationPlan(t, "./testdata/plan-long-line")
    95  	defer configCleanup()
    96  
    97  	op.Workspace = backend.DefaultStateName
    98  
    99  	run, err := b.Operation(context.Background(), op)
   100  	if err != nil {
   101  		t.Fatalf("error starting operation: %v", err)
   102  	}
   103  
   104  	<-run.Done()
   105  	if run.Result != backend.OperationSuccess {
   106  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   107  	}
   108  	if run.PlanEmpty {
   109  		t.Fatal("expected a non-empty plan")
   110  	}
   111  
   112  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   113  	if !strings.Contains(output, "Running plan in the remote backend") {
   114  		t.Fatalf("expected remote backend header in output: %s", output)
   115  	}
   116  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   117  		t.Fatalf("expected plan summary in output: %s", output)
   118  	}
   119  }
   120  
   121  func TestRemote_planWithoutPermissions(t *testing.T) {
   122  	b, bCleanup := testBackendNoDefault(t)
   123  	defer bCleanup()
   124  
   125  	// Create a named workspace without permissions.
   126  	w, err := b.client.Workspaces.Create(
   127  		context.Background(),
   128  		b.organization,
   129  		tfe.WorkspaceCreateOptions{
   130  			Name: tfe.String(b.prefix + "prod"),
   131  		},
   132  	)
   133  	if err != nil {
   134  		t.Fatalf("error creating named workspace: %v", err)
   135  	}
   136  	w.Permissions.CanQueueRun = false
   137  
   138  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   139  	defer configCleanup()
   140  
   141  	op.Workspace = "prod"
   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.Fatal("expected plan operation to fail")
   151  	}
   152  
   153  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   154  	if !strings.Contains(errOutput, "Insufficient rights to generate a plan") {
   155  		t.Fatalf("expected a permissions error, got: %v", errOutput)
   156  	}
   157  }
   158  
   159  func TestRemote_planWithParallelism(t *testing.T) {
   160  	b, bCleanup := testBackendDefault(t)
   161  	defer bCleanup()
   162  
   163  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   164  	defer configCleanup()
   165  
   166  	op.Parallelism = 3
   167  	op.Workspace = backend.DefaultStateName
   168  
   169  	run, err := b.Operation(context.Background(), op)
   170  	if err != nil {
   171  		t.Fatalf("error starting operation: %v", err)
   172  	}
   173  
   174  	<-run.Done()
   175  	if run.Result == backend.OperationSuccess {
   176  		t.Fatal("expected plan operation to fail")
   177  	}
   178  
   179  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   180  	if !strings.Contains(errOutput, "parallelism values are currently not supported") {
   181  		t.Fatalf("expected a parallelism error, got: %v", errOutput)
   182  	}
   183  }
   184  
   185  func TestRemote_planWithPlan(t *testing.T) {
   186  	b, bCleanup := testBackendDefault(t)
   187  	defer bCleanup()
   188  
   189  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   190  	defer configCleanup()
   191  
   192  	op.PlanFile = &planfile.Reader{}
   193  	op.Workspace = backend.DefaultStateName
   194  
   195  	run, err := b.Operation(context.Background(), op)
   196  	if err != nil {
   197  		t.Fatalf("error starting operation: %v", err)
   198  	}
   199  
   200  	<-run.Done()
   201  	if run.Result == backend.OperationSuccess {
   202  		t.Fatal("expected plan operation to fail")
   203  	}
   204  	if !run.PlanEmpty {
   205  		t.Fatalf("expected plan to be empty")
   206  	}
   207  
   208  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   209  	if !strings.Contains(errOutput, "saved plan is currently not supported") {
   210  		t.Fatalf("expected a saved plan error, got: %v", errOutput)
   211  	}
   212  }
   213  
   214  func TestRemote_planWithPath(t *testing.T) {
   215  	b, bCleanup := testBackendDefault(t)
   216  	defer bCleanup()
   217  
   218  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   219  	defer configCleanup()
   220  
   221  	op.PlanOutPath = "./testdata/plan"
   222  	op.Workspace = backend.DefaultStateName
   223  
   224  	run, err := b.Operation(context.Background(), op)
   225  	if err != nil {
   226  		t.Fatalf("error starting operation: %v", err)
   227  	}
   228  
   229  	<-run.Done()
   230  	if run.Result == backend.OperationSuccess {
   231  		t.Fatal("expected plan operation to fail")
   232  	}
   233  	if !run.PlanEmpty {
   234  		t.Fatalf("expected plan to be empty")
   235  	}
   236  
   237  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   238  	if !strings.Contains(errOutput, "generated plan is currently not supported") {
   239  		t.Fatalf("expected a generated plan error, got: %v", errOutput)
   240  	}
   241  }
   242  
   243  func TestRemote_planWithoutRefresh(t *testing.T) {
   244  	b, bCleanup := testBackendDefault(t)
   245  	defer bCleanup()
   246  
   247  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   248  	defer configCleanup()
   249  
   250  	op.PlanRefresh = false
   251  	op.Workspace = backend.DefaultStateName
   252  
   253  	run, err := b.Operation(context.Background(), op)
   254  	if err != nil {
   255  		t.Fatalf("error starting operation: %v", err)
   256  	}
   257  
   258  	<-run.Done()
   259  	if run.Result == backend.OperationSuccess {
   260  		t.Fatal("expected plan operation to fail")
   261  	}
   262  
   263  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   264  	if !strings.Contains(errOutput, "refresh is currently not supported") {
   265  		t.Fatalf("expected a refresh error, got: %v", errOutput)
   266  	}
   267  }
   268  
   269  func TestRemote_planWithTarget(t *testing.T) {
   270  	b, bCleanup := testBackendDefault(t)
   271  	defer bCleanup()
   272  
   273  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   274  	defer configCleanup()
   275  
   276  	addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
   277  
   278  	op.Targets = []addrs.Targetable{addr}
   279  	op.Workspace = backend.DefaultStateName
   280  
   281  	run, err := b.Operation(context.Background(), op)
   282  	if err != nil {
   283  		t.Fatalf("error starting operation: %v", err)
   284  	}
   285  
   286  	<-run.Done()
   287  	if run.Result != backend.OperationSuccess {
   288  		t.Fatal("expected plan operation to succeed")
   289  	}
   290  	if run.PlanEmpty {
   291  		t.Fatalf("expected plan to be non-empty")
   292  	}
   293  
   294  	// We should find a run inside the mock client that has the same
   295  	// target address we requested above.
   296  	runsAPI := b.client.Runs.(*mockRuns)
   297  	if got, want := len(runsAPI.runs), 1; got != want {
   298  		t.Fatalf("wrong number of runs in the mock client %d; want %d", got, want)
   299  	}
   300  	for _, run := range runsAPI.runs {
   301  		if diff := cmp.Diff([]string{"null_resource.foo"}, run.TargetAddrs); diff != "" {
   302  			t.Errorf("wrong TargetAddrs in the created run\n%s", diff)
   303  		}
   304  
   305  		if !strings.Contains(run.Message, "using -target") {
   306  			t.Fatalf("incorrrect Message on the created run: %s", run.Message)
   307  		}
   308  	}
   309  }
   310  
   311  func TestRemote_planWithTargetIncompatibleAPIVersion(t *testing.T) {
   312  	b, bCleanup := testBackendDefault(t)
   313  	defer bCleanup()
   314  
   315  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   316  	defer configCleanup()
   317  
   318  	// Set the tfe client's RemoteAPIVersion to an empty string, to mimic
   319  	// API versions prior to 2.3.
   320  	b.client.SetFakeRemoteAPIVersion("")
   321  
   322  	addr, _ := addrs.ParseAbsResourceStr("null_resource.foo")
   323  
   324  	op.Targets = []addrs.Targetable{addr}
   325  	op.Workspace = backend.DefaultStateName
   326  
   327  	run, err := b.Operation(context.Background(), op)
   328  	if err != nil {
   329  		t.Fatalf("error starting operation: %v", err)
   330  	}
   331  
   332  	<-run.Done()
   333  	if run.Result == backend.OperationSuccess {
   334  		t.Fatal("expected plan operation to fail")
   335  	}
   336  	if !run.PlanEmpty {
   337  		t.Fatalf("expected plan to be empty")
   338  	}
   339  
   340  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   341  	if !strings.Contains(errOutput, "Resource targeting is not supported") {
   342  		t.Fatalf("expected a targeting error, got: %v", errOutput)
   343  	}
   344  }
   345  
   346  func TestRemote_planWithVariables(t *testing.T) {
   347  	b, bCleanup := testBackendDefault(t)
   348  	defer bCleanup()
   349  
   350  	op, configCleanup := testOperationPlan(t, "./testdata/plan-variables")
   351  	defer configCleanup()
   352  
   353  	op.Variables = testVariables(terraform.ValueFromCLIArg, "foo", "bar")
   354  	op.Workspace = backend.DefaultStateName
   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  	if run.Result == backend.OperationSuccess {
   363  		t.Fatal("expected plan operation to fail")
   364  	}
   365  
   366  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   367  	if !strings.Contains(errOutput, "variables are currently not supported") {
   368  		t.Fatalf("expected a variables error, got: %v", errOutput)
   369  	}
   370  }
   371  
   372  func TestRemote_planNoConfig(t *testing.T) {
   373  	b, bCleanup := testBackendDefault(t)
   374  	defer bCleanup()
   375  
   376  	op, configCleanup := testOperationPlan(t, "./testdata/empty")
   377  	defer configCleanup()
   378  
   379  	op.Workspace = backend.DefaultStateName
   380  
   381  	run, err := b.Operation(context.Background(), op)
   382  	if err != nil {
   383  		t.Fatalf("error starting operation: %v", err)
   384  	}
   385  
   386  	<-run.Done()
   387  	if run.Result == backend.OperationSuccess {
   388  		t.Fatal("expected plan operation to fail")
   389  	}
   390  	if !run.PlanEmpty {
   391  		t.Fatalf("expected plan to be empty")
   392  	}
   393  
   394  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   395  	if !strings.Contains(errOutput, "configuration files found") {
   396  		t.Fatalf("expected configuration files error, got: %v", errOutput)
   397  	}
   398  }
   399  
   400  func TestRemote_planNoChanges(t *testing.T) {
   401  	b, bCleanup := testBackendDefault(t)
   402  	defer bCleanup()
   403  
   404  	op, configCleanup := testOperationPlan(t, "./testdata/plan-no-changes")
   405  	defer configCleanup()
   406  
   407  	op.Workspace = backend.DefaultStateName
   408  
   409  	run, err := b.Operation(context.Background(), op)
   410  	if err != nil {
   411  		t.Fatalf("error starting operation: %v", err)
   412  	}
   413  
   414  	<-run.Done()
   415  	if run.Result != backend.OperationSuccess {
   416  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   417  	}
   418  	if !run.PlanEmpty {
   419  		t.Fatalf("expected plan to be empty")
   420  	}
   421  
   422  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   423  	if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
   424  		t.Fatalf("expected no changes in plan summary: %s", output)
   425  	}
   426  	if !strings.Contains(output, "Sentinel Result: true") {
   427  		t.Fatalf("expected policy check result in output: %s", output)
   428  	}
   429  }
   430  
   431  func TestRemote_planForceLocal(t *testing.T) {
   432  	// Set TF_FORCE_LOCAL_BACKEND so the remote backend will use
   433  	// the local backend with itself as embedded backend.
   434  	if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil {
   435  		t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err)
   436  	}
   437  	defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND")
   438  
   439  	b, bCleanup := testBackendDefault(t)
   440  	defer bCleanup()
   441  
   442  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   443  	defer configCleanup()
   444  
   445  	op.Workspace = backend.DefaultStateName
   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 a non-empty plan")
   458  	}
   459  
   460  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   461  	if strings.Contains(output, "Running plan in the remote backend") {
   462  		t.Fatalf("unexpected remote backend header in output: %s", output)
   463  	}
   464  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   465  		t.Fatalf("expected plan summary in output: %s", output)
   466  	}
   467  }
   468  
   469  func TestRemote_planWithoutOperationsEntitlement(t *testing.T) {
   470  	b, bCleanup := testBackendNoOperations(t)
   471  	defer bCleanup()
   472  
   473  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   474  	defer configCleanup()
   475  
   476  	op.Workspace = backend.DefaultStateName
   477  
   478  	run, err := b.Operation(context.Background(), op)
   479  	if err != nil {
   480  		t.Fatalf("error starting operation: %v", err)
   481  	}
   482  
   483  	<-run.Done()
   484  	if run.Result != backend.OperationSuccess {
   485  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   486  	}
   487  	if run.PlanEmpty {
   488  		t.Fatalf("expected a non-empty plan")
   489  	}
   490  
   491  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   492  	if strings.Contains(output, "Running plan in the remote backend") {
   493  		t.Fatalf("unexpected remote backend header in output: %s", output)
   494  	}
   495  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   496  		t.Fatalf("expected plan summary in output: %s", output)
   497  	}
   498  }
   499  
   500  func TestRemote_planWorkspaceWithoutOperations(t *testing.T) {
   501  	b, bCleanup := testBackendNoDefault(t)
   502  	defer bCleanup()
   503  
   504  	ctx := context.Background()
   505  
   506  	// Create a named workspace that doesn't allow operations.
   507  	_, err := b.client.Workspaces.Create(
   508  		ctx,
   509  		b.organization,
   510  		tfe.WorkspaceCreateOptions{
   511  			Name: tfe.String(b.prefix + "no-operations"),
   512  		},
   513  	)
   514  	if err != nil {
   515  		t.Fatalf("error creating named workspace: %v", err)
   516  	}
   517  
   518  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   519  	defer configCleanup()
   520  
   521  	op.Workspace = "no-operations"
   522  
   523  	run, err := b.Operation(ctx, 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.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   531  	}
   532  	if run.PlanEmpty {
   533  		t.Fatalf("expected a non-empty plan")
   534  	}
   535  
   536  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   537  	if strings.Contains(output, "Running plan in the remote backend") {
   538  		t.Fatalf("unexpected remote backend header in output: %s", output)
   539  	}
   540  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   541  		t.Fatalf("expected plan summary in output: %s", output)
   542  	}
   543  }
   544  
   545  func TestRemote_planLockTimeout(t *testing.T) {
   546  	b, bCleanup := testBackendDefault(t)
   547  	defer bCleanup()
   548  
   549  	ctx := context.Background()
   550  
   551  	// Retrieve the workspace used to run this operation in.
   552  	w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace)
   553  	if err != nil {
   554  		t.Fatalf("error retrieving workspace: %v", err)
   555  	}
   556  
   557  	// Create a new configuration version.
   558  	c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
   559  	if err != nil {
   560  		t.Fatalf("error creating configuration version: %v", err)
   561  	}
   562  
   563  	// Create a pending run to block this run.
   564  	_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
   565  		ConfigurationVersion: c,
   566  		Workspace:            w,
   567  	})
   568  	if err != nil {
   569  		t.Fatalf("error creating pending run: %v", err)
   570  	}
   571  
   572  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   573  	defer configCleanup()
   574  
   575  	input := testInput(t, map[string]string{
   576  		"cancel":  "yes",
   577  		"approve": "yes",
   578  	})
   579  
   580  	op.StateLockTimeout = 5 * time.Second
   581  	op.UIIn = input
   582  	op.UIOut = b.CLI
   583  	op.Workspace = backend.DefaultStateName
   584  
   585  	_, err = b.Operation(context.Background(), op)
   586  	if err != nil {
   587  		t.Fatalf("error starting operation: %v", err)
   588  	}
   589  
   590  	sigint := make(chan os.Signal, 1)
   591  	signal.Notify(sigint, syscall.SIGINT)
   592  	select {
   593  	case <-sigint:
   594  		// Stop redirecting SIGINT signals.
   595  		signal.Stop(sigint)
   596  	case <-time.After(10 * time.Second):
   597  		t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds")
   598  	}
   599  
   600  	if len(input.answers) != 2 {
   601  		t.Fatalf("expected unused answers, got: %v", input.answers)
   602  	}
   603  
   604  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   605  	if !strings.Contains(output, "Running plan in the remote backend") {
   606  		t.Fatalf("expected remote backend header in output: %s", output)
   607  	}
   608  	if !strings.Contains(output, "Lock timeout exceeded") {
   609  		t.Fatalf("expected lock timout error in output: %s", output)
   610  	}
   611  	if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   612  		t.Fatalf("unexpected plan summary in output: %s", output)
   613  	}
   614  }
   615  
   616  func TestRemote_planDestroy(t *testing.T) {
   617  	b, bCleanup := testBackendDefault(t)
   618  	defer bCleanup()
   619  
   620  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   621  	defer configCleanup()
   622  
   623  	op.Destroy = true
   624  	op.Workspace = backend.DefaultStateName
   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  	if run.Result != backend.OperationSuccess {
   633  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   634  	}
   635  	if run.PlanEmpty {
   636  		t.Fatalf("expected a non-empty plan")
   637  	}
   638  }
   639  
   640  func TestRemote_planDestroyNoConfig(t *testing.T) {
   641  	b, bCleanup := testBackendDefault(t)
   642  	defer bCleanup()
   643  
   644  	op, configCleanup := testOperationPlan(t, "./testdata/empty")
   645  	defer configCleanup()
   646  
   647  	op.Destroy = true
   648  	op.Workspace = backend.DefaultStateName
   649  
   650  	run, err := b.Operation(context.Background(), op)
   651  	if err != nil {
   652  		t.Fatalf("error starting operation: %v", err)
   653  	}
   654  
   655  	<-run.Done()
   656  	if run.Result != backend.OperationSuccess {
   657  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   658  	}
   659  	if run.PlanEmpty {
   660  		t.Fatalf("expected a non-empty plan")
   661  	}
   662  }
   663  
   664  func TestRemote_planWithWorkingDirectory(t *testing.T) {
   665  	b, bCleanup := testBackendDefault(t)
   666  	defer bCleanup()
   667  
   668  	options := tfe.WorkspaceUpdateOptions{
   669  		WorkingDirectory: tfe.String("terraform"),
   670  	}
   671  
   672  	// Configure the workspace to use a custom working directory.
   673  	_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
   674  	if err != nil {
   675  		t.Fatalf("error configuring working directory: %v", err)
   676  	}
   677  
   678  	op, configCleanup := testOperationPlan(t, "./testdata/plan-with-working-directory/terraform")
   679  	defer configCleanup()
   680  
   681  	op.Workspace = backend.DefaultStateName
   682  
   683  	run, err := b.Operation(context.Background(), op)
   684  	if err != nil {
   685  		t.Fatalf("error starting operation: %v", err)
   686  	}
   687  
   688  	<-run.Done()
   689  	if run.Result != backend.OperationSuccess {
   690  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   691  	}
   692  	if run.PlanEmpty {
   693  		t.Fatalf("expected a non-empty plan")
   694  	}
   695  
   696  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   697  	if !strings.Contains(output, "The remote workspace is configured to work with configuration") {
   698  		t.Fatalf("expected working directory warning: %s", output)
   699  	}
   700  	if !strings.Contains(output, "Running plan in the remote backend") {
   701  		t.Fatalf("expected remote backend header in output: %s", output)
   702  	}
   703  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   704  		t.Fatalf("expected plan summary in output: %s", output)
   705  	}
   706  }
   707  
   708  func TestRemote_planWithWorkingDirectoryFromCurrentPath(t *testing.T) {
   709  	b, bCleanup := testBackendDefault(t)
   710  	defer bCleanup()
   711  
   712  	options := tfe.WorkspaceUpdateOptions{
   713  		WorkingDirectory: tfe.String("terraform"),
   714  	}
   715  
   716  	// Configure the workspace to use a custom working directory.
   717  	_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
   718  	if err != nil {
   719  		t.Fatalf("error configuring working directory: %v", err)
   720  	}
   721  
   722  	wd, err := os.Getwd()
   723  	if err != nil {
   724  		t.Fatalf("error getting current working directory: %v", err)
   725  	}
   726  
   727  	// We need to change into the configuration directory to make sure
   728  	// the logic to upload the correct slug is working as expected.
   729  	if err := os.Chdir("./testdata/plan-with-working-directory/terraform"); err != nil {
   730  		t.Fatalf("error changing directory: %v", err)
   731  	}
   732  	defer os.Chdir(wd) // Make sure we change back again when were done.
   733  
   734  	// For this test we need to give our current directory instead of the
   735  	// full path to the configuration as we already changed directories.
   736  	op, configCleanup := testOperationPlan(t, ".")
   737  	defer configCleanup()
   738  
   739  	op.Workspace = backend.DefaultStateName
   740  
   741  	run, err := b.Operation(context.Background(), op)
   742  	if err != nil {
   743  		t.Fatalf("error starting operation: %v", err)
   744  	}
   745  
   746  	<-run.Done()
   747  	if run.Result != backend.OperationSuccess {
   748  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   749  	}
   750  	if run.PlanEmpty {
   751  		t.Fatalf("expected a non-empty plan")
   752  	}
   753  
   754  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   755  	if !strings.Contains(output, "Running plan in the remote backend") {
   756  		t.Fatalf("expected remote backend header in output: %s", output)
   757  	}
   758  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   759  		t.Fatalf("expected plan summary in output: %s", output)
   760  	}
   761  }
   762  
   763  func TestRemote_planCostEstimation(t *testing.T) {
   764  	b, bCleanup := testBackendDefault(t)
   765  	defer bCleanup()
   766  
   767  	op, configCleanup := testOperationPlan(t, "./testdata/plan-cost-estimation")
   768  	defer configCleanup()
   769  
   770  	op.Workspace = backend.DefaultStateName
   771  
   772  	run, err := b.Operation(context.Background(), op)
   773  	if err != nil {
   774  		t.Fatalf("error starting operation: %v", err)
   775  	}
   776  
   777  	<-run.Done()
   778  	if run.Result != backend.OperationSuccess {
   779  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   780  	}
   781  	if run.PlanEmpty {
   782  		t.Fatalf("expected a non-empty plan")
   783  	}
   784  
   785  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   786  	if !strings.Contains(output, "Running plan in the remote backend") {
   787  		t.Fatalf("expected remote backend header in output: %s", output)
   788  	}
   789  	if !strings.Contains(output, "Resources: 1 of 1 estimated") {
   790  		t.Fatalf("expected cost estimate result in output: %s", output)
   791  	}
   792  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   793  		t.Fatalf("expected plan summary in output: %s", output)
   794  	}
   795  }
   796  
   797  func TestRemote_planPolicyPass(t *testing.T) {
   798  	b, bCleanup := testBackendDefault(t)
   799  	defer bCleanup()
   800  
   801  	op, configCleanup := testOperationPlan(t, "./testdata/plan-policy-passed")
   802  	defer configCleanup()
   803  
   804  	op.Workspace = backend.DefaultStateName
   805  
   806  	run, err := b.Operation(context.Background(), op)
   807  	if err != nil {
   808  		t.Fatalf("error starting operation: %v", err)
   809  	}
   810  
   811  	<-run.Done()
   812  	if run.Result != backend.OperationSuccess {
   813  		t.Fatalf("operation failed: %s", b.CLI.(*cli.MockUi).ErrorWriter.String())
   814  	}
   815  	if run.PlanEmpty {
   816  		t.Fatalf("expected a non-empty plan")
   817  	}
   818  
   819  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   820  	if !strings.Contains(output, "Running plan in the remote backend") {
   821  		t.Fatalf("expected remote backend header in output: %s", output)
   822  	}
   823  	if !strings.Contains(output, "Sentinel Result: true") {
   824  		t.Fatalf("expected policy check result in output: %s", output)
   825  	}
   826  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   827  		t.Fatalf("expected plan summary in output: %s", output)
   828  	}
   829  }
   830  
   831  func TestRemote_planPolicyHardFail(t *testing.T) {
   832  	b, bCleanup := testBackendDefault(t)
   833  	defer bCleanup()
   834  
   835  	op, configCleanup := testOperationPlan(t, "./testdata/plan-policy-hard-failed")
   836  	defer configCleanup()
   837  
   838  	op.Workspace = backend.DefaultStateName
   839  
   840  	run, err := b.Operation(context.Background(), op)
   841  	if err != nil {
   842  		t.Fatalf("error starting operation: %v", err)
   843  	}
   844  
   845  	<-run.Done()
   846  	if run.Result == backend.OperationSuccess {
   847  		t.Fatal("expected plan operation to fail")
   848  	}
   849  	if !run.PlanEmpty {
   850  		t.Fatalf("expected plan to be empty")
   851  	}
   852  
   853  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   854  	if !strings.Contains(errOutput, "hard failed") {
   855  		t.Fatalf("expected a policy check error, got: %v", errOutput)
   856  	}
   857  
   858  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   859  	if !strings.Contains(output, "Running plan in the remote backend") {
   860  		t.Fatalf("expected remote backend header in output: %s", output)
   861  	}
   862  	if !strings.Contains(output, "Sentinel Result: false") {
   863  		t.Fatalf("expected policy check result in output: %s", output)
   864  	}
   865  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   866  		t.Fatalf("expected plan summary in output: %s", output)
   867  	}
   868  }
   869  
   870  func TestRemote_planPolicySoftFail(t *testing.T) {
   871  	b, bCleanup := testBackendDefault(t)
   872  	defer bCleanup()
   873  
   874  	op, configCleanup := testOperationPlan(t, "./testdata/plan-policy-soft-failed")
   875  	defer configCleanup()
   876  
   877  	op.Workspace = backend.DefaultStateName
   878  
   879  	run, err := b.Operation(context.Background(), op)
   880  	if err != nil {
   881  		t.Fatalf("error starting operation: %v", err)
   882  	}
   883  
   884  	<-run.Done()
   885  	if run.Result == backend.OperationSuccess {
   886  		t.Fatal("expected plan operation to fail")
   887  	}
   888  	if !run.PlanEmpty {
   889  		t.Fatalf("expected plan to be empty")
   890  	}
   891  
   892  	errOutput := b.CLI.(*cli.MockUi).ErrorWriter.String()
   893  	if !strings.Contains(errOutput, "soft failed") {
   894  		t.Fatalf("expected a policy check error, got: %v", errOutput)
   895  	}
   896  
   897  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   898  	if !strings.Contains(output, "Running plan in the remote backend") {
   899  		t.Fatalf("expected remote backend header in output: %s", output)
   900  	}
   901  	if !strings.Contains(output, "Sentinel Result: false") {
   902  		t.Fatalf("expected policy check result in output: %s", output)
   903  	}
   904  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   905  		t.Fatalf("expected plan summary in output: %s", output)
   906  	}
   907  }
   908  
   909  func TestRemote_planWithRemoteError(t *testing.T) {
   910  	b, bCleanup := testBackendDefault(t)
   911  	defer bCleanup()
   912  
   913  	op, configCleanup := testOperationPlan(t, "./testdata/plan-with-error")
   914  	defer configCleanup()
   915  
   916  	op.Workspace = backend.DefaultStateName
   917  
   918  	run, err := b.Operation(context.Background(), op)
   919  	if err != nil {
   920  		t.Fatalf("error starting operation: %v", err)
   921  	}
   922  
   923  	<-run.Done()
   924  	if run.Result == backend.OperationSuccess {
   925  		t.Fatal("expected plan operation to fail")
   926  	}
   927  	if run.Result.ExitStatus() != 1 {
   928  		t.Fatalf("expected exit code 1, got %d", run.Result.ExitStatus())
   929  	}
   930  
   931  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   932  	if !strings.Contains(output, "Running plan in the remote backend") {
   933  		t.Fatalf("expected remote backend header in output: %s", output)
   934  	}
   935  	if !strings.Contains(output, "null_resource.foo: 1 error") {
   936  		t.Fatalf("expected plan error in output: %s", output)
   937  	}
   938  }
   939  
   940  func TestRemote_planOtherError(t *testing.T) {
   941  	b, bCleanup := testBackendDefault(t)
   942  	defer bCleanup()
   943  
   944  	op, configCleanup := testOperationPlan(t, "./testdata/plan")
   945  	defer configCleanup()
   946  
   947  	op.Workspace = "network-error" // custom error response in backend_mock.go
   948  
   949  	_, err := b.Operation(context.Background(), op)
   950  	if err == nil {
   951  		t.Errorf("expected error, got success")
   952  	}
   953  
   954  	if !strings.Contains(err.Error(),
   955  		"The configured \"remote\" backend encountered an unexpected error:\n\nI'm a little teacup") {
   956  		t.Fatalf("expected error message, got: %s", err.Error())
   957  	}
   958  }