github.com/magodo/terraform@v0.11.12-beta1/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  	tfe "github.com/hashicorp/go-tfe"
    13  	"github.com/hashicorp/terraform/backend"
    14  	"github.com/hashicorp/terraform/config/module"
    15  	"github.com/hashicorp/terraform/terraform"
    16  	"github.com/mitchellh/cli"
    17  )
    18  
    19  func testOperationApply() *backend.Operation {
    20  	return &backend.Operation{
    21  		Parallelism: defaultParallelism,
    22  		PlanRefresh: true,
    23  		Type:        backend.OperationTypeApply,
    24  	}
    25  }
    26  
    27  func TestRemote_applyBasic(t *testing.T) {
    28  	b := testBackendDefault(t)
    29  
    30  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
    31  	defer modCleanup()
    32  
    33  	input := testInput(t, map[string]string{
    34  		"approve": "yes",
    35  	})
    36  
    37  	op := testOperationApply()
    38  	op.Module = mod
    39  	op.UIIn = input
    40  	op.UIOut = b.CLI
    41  	op.Workspace = backend.DefaultStateName
    42  
    43  	run, err := b.Operation(context.Background(), op)
    44  	if err != nil {
    45  		t.Fatalf("error starting operation: %v", err)
    46  	}
    47  
    48  	<-run.Done()
    49  	if run.Err != nil {
    50  		t.Fatalf("error running operation: %v", run.Err)
    51  	}
    52  	if run.PlanEmpty {
    53  		t.Fatalf("expected a non-empty plan")
    54  	}
    55  
    56  	if len(input.answers) > 0 {
    57  		t.Fatalf("expected no unused answers, got: %v", input.answers)
    58  	}
    59  
    60  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
    61  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
    62  		t.Fatalf("missing plan summery in output: %s", output)
    63  	}
    64  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
    65  		t.Fatalf("missing apply summery in output: %s", output)
    66  	}
    67  }
    68  
    69  func TestRemote_applyWithoutPermissions(t *testing.T) {
    70  	b := testBackendNoDefault(t)
    71  
    72  	// Create a named workspace without permissions.
    73  	w, err := b.client.Workspaces.Create(
    74  		context.Background(),
    75  		b.organization,
    76  		tfe.WorkspaceCreateOptions{
    77  			Name: tfe.String(b.prefix + "prod"),
    78  		},
    79  	)
    80  	if err != nil {
    81  		t.Fatalf("error creating named workspace: %v", err)
    82  	}
    83  	w.Permissions.CanUpdate = false
    84  
    85  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
    86  	defer modCleanup()
    87  
    88  	op := testOperationApply()
    89  	op.Module = mod
    90  	op.Workspace = "prod"
    91  
    92  	run, err := b.Operation(context.Background(), op)
    93  	if err != nil {
    94  		t.Fatalf("error starting operation: %v", err)
    95  	}
    96  
    97  	<-run.Done()
    98  	if run.Err == nil {
    99  		t.Fatalf("expected an apply error, got: %v", run.Err)
   100  	}
   101  	if !strings.Contains(run.Err.Error(), "insufficient rights to apply changes") {
   102  		t.Fatalf("expected a permissions error, got: %v", run.Err)
   103  	}
   104  }
   105  
   106  func TestRemote_applyWithVCS(t *testing.T) {
   107  	b := testBackendNoDefault(t)
   108  
   109  	// Create a named workspace with a VCS.
   110  	_, err := b.client.Workspaces.Create(
   111  		context.Background(),
   112  		b.organization,
   113  		tfe.WorkspaceCreateOptions{
   114  			Name:    tfe.String(b.prefix + "prod"),
   115  			VCSRepo: &tfe.VCSRepoOptions{},
   116  		},
   117  	)
   118  	if err != nil {
   119  		t.Fatalf("error creating named workspace: %v", err)
   120  	}
   121  
   122  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   123  	defer modCleanup()
   124  
   125  	op := testOperationApply()
   126  	op.Module = mod
   127  	op.Workspace = "prod"
   128  
   129  	run, err := b.Operation(context.Background(), op)
   130  	if err != nil {
   131  		t.Fatalf("error starting operation: %v", err)
   132  	}
   133  
   134  	<-run.Done()
   135  	if run.Err == nil {
   136  		t.Fatalf("expected an apply error, got: %v", run.Err)
   137  	}
   138  	if !run.PlanEmpty {
   139  		t.Fatalf("expected plan to be empty")
   140  	}
   141  	if !strings.Contains(run.Err.Error(), "not allowed for workspaces with a VCS") {
   142  		t.Fatalf("expected a VCS error, got: %v", run.Err)
   143  	}
   144  }
   145  
   146  func TestRemote_applyWithParallelism(t *testing.T) {
   147  	b := testBackendDefault(t)
   148  
   149  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   150  	defer modCleanup()
   151  
   152  	op := testOperationApply()
   153  	op.Module = mod
   154  	op.Parallelism = 3
   155  	op.Workspace = backend.DefaultStateName
   156  
   157  	run, err := b.Operation(context.Background(), op)
   158  	if err != nil {
   159  		t.Fatalf("error starting operation: %v", err)
   160  	}
   161  
   162  	<-run.Done()
   163  	if run.Err == nil {
   164  		t.Fatalf("expected an apply error, got: %v", run.Err)
   165  	}
   166  	if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") {
   167  		t.Fatalf("expected a parallelism error, got: %v", run.Err)
   168  	}
   169  }
   170  
   171  func TestRemote_applyWithPlan(t *testing.T) {
   172  	b := testBackendDefault(t)
   173  
   174  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   175  	defer modCleanup()
   176  
   177  	op := testOperationApply()
   178  	op.Module = mod
   179  	op.Plan = &terraform.Plan{}
   180  	op.Workspace = backend.DefaultStateName
   181  
   182  	run, err := b.Operation(context.Background(), op)
   183  	if err != nil {
   184  		t.Fatalf("error starting operation: %v", err)
   185  	}
   186  
   187  	<-run.Done()
   188  	if run.Err == nil {
   189  		t.Fatalf("expected an apply error, got: %v", run.Err)
   190  	}
   191  	if !run.PlanEmpty {
   192  		t.Fatalf("expected plan to be empty")
   193  	}
   194  	if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") {
   195  		t.Fatalf("expected a saved plan error, got: %v", run.Err)
   196  	}
   197  }
   198  
   199  func TestRemote_applyWithoutRefresh(t *testing.T) {
   200  	b := testBackendDefault(t)
   201  
   202  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   203  	defer modCleanup()
   204  
   205  	op := testOperationApply()
   206  	op.Module = mod
   207  	op.PlanRefresh = false
   208  	op.Workspace = backend.DefaultStateName
   209  
   210  	run, err := b.Operation(context.Background(), op)
   211  	if err != nil {
   212  		t.Fatalf("error starting operation: %v", err)
   213  	}
   214  
   215  	<-run.Done()
   216  	if run.Err == nil {
   217  		t.Fatalf("expected an apply error, got: %v", run.Err)
   218  	}
   219  	if !strings.Contains(run.Err.Error(), "refresh is currently not supported") {
   220  		t.Fatalf("expected a refresh error, got: %v", run.Err)
   221  	}
   222  }
   223  
   224  func TestRemote_applyWithTarget(t *testing.T) {
   225  	b := testBackendDefault(t)
   226  
   227  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   228  	defer modCleanup()
   229  
   230  	op := testOperationApply()
   231  	op.Module = mod
   232  	op.Targets = []string{"null_resource.foo"}
   233  	op.Workspace = backend.DefaultStateName
   234  
   235  	run, err := b.Operation(context.Background(), op)
   236  	if err != nil {
   237  		t.Fatalf("error starting operation: %v", err)
   238  	}
   239  
   240  	<-run.Done()
   241  	if run.Err == nil {
   242  		t.Fatalf("expected an apply error, got: %v", run.Err)
   243  	}
   244  	if !run.PlanEmpty {
   245  		t.Fatalf("expected plan to be empty")
   246  	}
   247  	if !strings.Contains(run.Err.Error(), "targeting is currently not supported") {
   248  		t.Fatalf("expected a targeting error, got: %v", run.Err)
   249  	}
   250  }
   251  
   252  func TestRemote_applyWithVariables(t *testing.T) {
   253  	b := testBackendDefault(t)
   254  
   255  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-variables")
   256  	defer modCleanup()
   257  
   258  	op := testOperationApply()
   259  	op.Module = mod
   260  	op.Variables = map[string]interface{}{"foo": "bar"}
   261  	op.Workspace = backend.DefaultStateName
   262  
   263  	run, err := b.Operation(context.Background(), op)
   264  	if err != nil {
   265  		t.Fatalf("error starting operation: %v", err)
   266  	}
   267  
   268  	<-run.Done()
   269  	if run.Err == nil {
   270  		t.Fatalf("expected an apply error, got: %v", run.Err)
   271  	}
   272  	if !strings.Contains(run.Err.Error(), "variables are currently not supported") {
   273  		t.Fatalf("expected a variables error, got: %v", run.Err)
   274  	}
   275  }
   276  
   277  func TestRemote_applyNoConfig(t *testing.T) {
   278  	b := testBackendDefault(t)
   279  
   280  	op := testOperationApply()
   281  	op.Module = nil
   282  	op.Workspace = backend.DefaultStateName
   283  
   284  	run, err := b.Operation(context.Background(), op)
   285  	if err != nil {
   286  		t.Fatalf("error starting operation: %v", err)
   287  	}
   288  
   289  	<-run.Done()
   290  	if run.Err == nil {
   291  		t.Fatalf("expected an apply error, got: %v", run.Err)
   292  	}
   293  	if !run.PlanEmpty {
   294  		t.Fatalf("expected plan to be empty")
   295  	}
   296  	if !strings.Contains(run.Err.Error(), "configuration files found") {
   297  		t.Fatalf("expected configuration files error, got: %v", run.Err)
   298  	}
   299  }
   300  
   301  func TestRemote_applyNoChanges(t *testing.T) {
   302  	b := testBackendDefault(t)
   303  
   304  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-no-changes")
   305  	defer modCleanup()
   306  
   307  	op := testOperationApply()
   308  	op.Module = mod
   309  	op.Workspace = backend.DefaultStateName
   310  
   311  	run, err := b.Operation(context.Background(), op)
   312  	if err != nil {
   313  		t.Fatalf("error starting operation: %v", err)
   314  	}
   315  
   316  	<-run.Done()
   317  	if run.Err != nil {
   318  		t.Fatalf("error running operation: %v", run.Err)
   319  	}
   320  	if !run.PlanEmpty {
   321  		t.Fatalf("expected plan to be empty")
   322  	}
   323  
   324  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   325  	if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") {
   326  		t.Fatalf("expected no changes in plan summery: %s", output)
   327  	}
   328  }
   329  
   330  func TestRemote_applyNoApprove(t *testing.T) {
   331  	b := testBackendDefault(t)
   332  
   333  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   334  	defer modCleanup()
   335  
   336  	input := testInput(t, map[string]string{
   337  		"approve": "no",
   338  	})
   339  
   340  	op := testOperationApply()
   341  	op.Module = mod
   342  	op.UIIn = input
   343  	op.UIOut = b.CLI
   344  	op.Workspace = backend.DefaultStateName
   345  
   346  	run, err := b.Operation(context.Background(), op)
   347  	if err != nil {
   348  		t.Fatalf("error starting operation: %v", err)
   349  	}
   350  
   351  	<-run.Done()
   352  	if run.Err == nil {
   353  		t.Fatalf("expected an apply error, got: %v", run.Err)
   354  	}
   355  	if !run.PlanEmpty {
   356  		t.Fatalf("expected plan to be empty")
   357  	}
   358  	if !strings.Contains(run.Err.Error(), "Apply discarded") {
   359  		t.Fatalf("expected an apply discarded error, got: %v", run.Err)
   360  	}
   361  	if len(input.answers) > 0 {
   362  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   363  	}
   364  }
   365  
   366  func TestRemote_applyAutoApprove(t *testing.T) {
   367  	b := testBackendDefault(t)
   368  
   369  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   370  	defer modCleanup()
   371  
   372  	input := testInput(t, map[string]string{
   373  		"approve": "no",
   374  	})
   375  
   376  	op := testOperationApply()
   377  	op.AutoApprove = true
   378  	op.Module = mod
   379  	op.UIIn = input
   380  	op.UIOut = b.CLI
   381  	op.Workspace = backend.DefaultStateName
   382  
   383  	run, err := b.Operation(context.Background(), op)
   384  	if err != nil {
   385  		t.Fatalf("error starting operation: %v", err)
   386  	}
   387  
   388  	<-run.Done()
   389  	if run.Err != nil {
   390  		t.Fatalf("error running operation: %v", run.Err)
   391  	}
   392  	if run.PlanEmpty {
   393  		t.Fatalf("expected a non-empty plan")
   394  	}
   395  
   396  	if len(input.answers) != 1 {
   397  		t.Fatalf("expected an unused answer, got: %v", input.answers)
   398  	}
   399  
   400  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   401  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   402  		t.Fatalf("missing plan summery in output: %s", output)
   403  	}
   404  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   405  		t.Fatalf("missing apply summery in output: %s", output)
   406  	}
   407  }
   408  
   409  func TestRemote_applyWithAutoApply(t *testing.T) {
   410  	b := testBackendNoDefault(t)
   411  
   412  	// Create a named workspace that auto applies.
   413  	_, err := b.client.Workspaces.Create(
   414  		context.Background(),
   415  		b.organization,
   416  		tfe.WorkspaceCreateOptions{
   417  			AutoApply: tfe.Bool(true),
   418  			Name:      tfe.String(b.prefix + "prod"),
   419  		},
   420  	)
   421  	if err != nil {
   422  		t.Fatalf("error creating named workspace: %v", err)
   423  	}
   424  
   425  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   426  	defer modCleanup()
   427  
   428  	input := testInput(t, map[string]string{
   429  		"approve": "yes",
   430  	})
   431  
   432  	op := testOperationApply()
   433  	op.Module = mod
   434  	op.UIIn = input
   435  	op.UIOut = b.CLI
   436  	op.Workspace = "prod"
   437  
   438  	run, err := b.Operation(context.Background(), op)
   439  	if err != nil {
   440  		t.Fatalf("error starting operation: %v", err)
   441  	}
   442  
   443  	<-run.Done()
   444  	if run.Err != nil {
   445  		t.Fatalf("error running operation: %v", run.Err)
   446  	}
   447  	if run.PlanEmpty {
   448  		t.Fatalf("expected a non-empty plan")
   449  	}
   450  
   451  	if len(input.answers) != 1 {
   452  		t.Fatalf("expected an unused answer, got: %v", input.answers)
   453  	}
   454  
   455  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   456  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   457  		t.Fatalf("missing plan summery in output: %s", output)
   458  	}
   459  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   460  		t.Fatalf("missing apply summery in output: %s", output)
   461  	}
   462  }
   463  
   464  func TestRemote_applyForceLocal(t *testing.T) {
   465  	// Set TF_FORCE_LOCAL_BACKEND so the remote backend will use
   466  	// the local backend with itself as embedded backend.
   467  	if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil {
   468  		t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err)
   469  	}
   470  	defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND")
   471  
   472  	b := testBackendDefault(t)
   473  
   474  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   475  	defer modCleanup()
   476  
   477  	input := testInput(t, map[string]string{
   478  		"approve": "yes",
   479  	})
   480  
   481  	op := testOperationApply()
   482  	op.Module = mod
   483  	op.UIIn = input
   484  	op.UIOut = b.CLI
   485  	op.Workspace = backend.DefaultStateName
   486  
   487  	run, err := b.Operation(context.Background(), op)
   488  	if err != nil {
   489  		t.Fatalf("error starting operation: %v", err)
   490  	}
   491  
   492  	<-run.Done()
   493  	if run.Err != nil {
   494  		t.Fatalf("error running operation: %v", run.Err)
   495  	}
   496  	if run.PlanEmpty {
   497  		t.Fatalf("expected a non-empty plan")
   498  	}
   499  
   500  	if len(input.answers) > 0 {
   501  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   502  	}
   503  
   504  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   505  	if strings.Contains(output, "Running apply in the remote backend") {
   506  		t.Fatalf("unexpected remote backend header in output: %s", output)
   507  	}
   508  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   509  		t.Fatalf("expected plan summery in output: %s", output)
   510  	}
   511  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   512  		t.Fatalf("expected apply summery in output: %s", output)
   513  	}
   514  }
   515  
   516  func TestRemote_applyWorkspaceWithoutOperations(t *testing.T) {
   517  	b := testBackendNoDefault(t)
   518  	ctx := context.Background()
   519  
   520  	// Create a named workspace that doesn't allow operations.
   521  	_, err := b.client.Workspaces.Create(
   522  		ctx,
   523  		b.organization,
   524  		tfe.WorkspaceCreateOptions{
   525  			Name: tfe.String(b.prefix + "no-operations"),
   526  		},
   527  	)
   528  	if err != nil {
   529  		t.Fatalf("error creating named workspace: %v", err)
   530  	}
   531  
   532  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   533  	defer modCleanup()
   534  
   535  	input := testInput(t, map[string]string{
   536  		"approve": "yes",
   537  	})
   538  
   539  	op := testOperationApply()
   540  	op.Module = mod
   541  	op.UIIn = input
   542  	op.UIOut = b.CLI
   543  	op.Workspace = "no-operations"
   544  
   545  	run, err := b.Operation(context.Background(), op)
   546  	if err != nil {
   547  		t.Fatalf("error starting operation: %v", err)
   548  	}
   549  
   550  	<-run.Done()
   551  	if run.Err != nil {
   552  		t.Fatalf("error running operation: %v", run.Err)
   553  	}
   554  	if run.PlanEmpty {
   555  		t.Fatalf("expected a non-empty plan")
   556  	}
   557  
   558  	if len(input.answers) > 0 {
   559  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   560  	}
   561  
   562  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   563  	if strings.Contains(output, "Running apply in the remote backend") {
   564  		t.Fatalf("unexpected remote backend header in output: %s", output)
   565  	}
   566  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   567  		t.Fatalf("expected plan summery in output: %s", output)
   568  	}
   569  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   570  		t.Fatalf("expected apply summery in output: %s", output)
   571  	}
   572  }
   573  
   574  func TestRemote_applyLockTimeout(t *testing.T) {
   575  	b := testBackendDefault(t)
   576  	ctx := context.Background()
   577  
   578  	// Retrieve the workspace used to run this operation in.
   579  	w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace)
   580  	if err != nil {
   581  		t.Fatalf("error retrieving workspace: %v", err)
   582  	}
   583  
   584  	// Create a new configuration version.
   585  	c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
   586  	if err != nil {
   587  		t.Fatalf("error creating configuration version: %v", err)
   588  	}
   589  
   590  	// Create a pending run to block this run.
   591  	_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
   592  		ConfigurationVersion: c,
   593  		Workspace:            w,
   594  	})
   595  	if err != nil {
   596  		t.Fatalf("error creating pending run: %v", err)
   597  	}
   598  
   599  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   600  	defer modCleanup()
   601  
   602  	input := testInput(t, map[string]string{
   603  		"cancel":  "yes",
   604  		"approve": "yes",
   605  	})
   606  
   607  	op := testOperationApply()
   608  	op.StateLockTimeout = 5 * time.Second
   609  	op.Module = mod
   610  	op.UIIn = input
   611  	op.UIOut = b.CLI
   612  	op.Workspace = backend.DefaultStateName
   613  
   614  	_, err = b.Operation(context.Background(), op)
   615  	if err != nil {
   616  		t.Fatalf("error starting operation: %v", err)
   617  	}
   618  
   619  	sigint := make(chan os.Signal, 1)
   620  	signal.Notify(sigint, syscall.SIGINT)
   621  	select {
   622  	case <-sigint:
   623  		// Stop redirecting SIGINT signals.
   624  		signal.Stop(sigint)
   625  	case <-time.After(10 * time.Second):
   626  		t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds")
   627  	}
   628  
   629  	if len(input.answers) != 2 {
   630  		t.Fatalf("expected unused answers, got: %v", input.answers)
   631  	}
   632  
   633  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   634  	if !strings.Contains(output, "Lock timeout exceeded") {
   635  		t.Fatalf("missing lock timout error in output: %s", output)
   636  	}
   637  	if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   638  		t.Fatalf("unexpected plan summery in output: %s", output)
   639  	}
   640  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   641  		t.Fatalf("unexpected apply summery in output: %s", output)
   642  	}
   643  }
   644  
   645  func TestRemote_applyDestroy(t *testing.T) {
   646  	b := testBackendDefault(t)
   647  
   648  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-destroy")
   649  	defer modCleanup()
   650  
   651  	input := testInput(t, map[string]string{
   652  		"approve": "yes",
   653  	})
   654  
   655  	op := testOperationApply()
   656  	op.Destroy = true
   657  	op.Module = mod
   658  	op.UIIn = input
   659  	op.UIOut = b.CLI
   660  	op.Workspace = backend.DefaultStateName
   661  
   662  	run, err := b.Operation(context.Background(), op)
   663  	if err != nil {
   664  		t.Fatalf("error starting operation: %v", err)
   665  	}
   666  
   667  	<-run.Done()
   668  	if run.Err != nil {
   669  		t.Fatalf("error running operation: %v", run.Err)
   670  	}
   671  	if run.PlanEmpty {
   672  		t.Fatalf("expected a non-empty plan")
   673  	}
   674  
   675  	if len(input.answers) > 0 {
   676  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   677  	}
   678  
   679  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   680  	if !strings.Contains(output, "0 to add, 0 to change, 1 to destroy") {
   681  		t.Fatalf("missing plan summery in output: %s", output)
   682  	}
   683  	if !strings.Contains(output, "0 added, 0 changed, 1 destroyed") {
   684  		t.Fatalf("missing apply summery in output: %s", output)
   685  	}
   686  }
   687  
   688  func TestRemote_applyDestroyNoConfig(t *testing.T) {
   689  	b := testBackendDefault(t)
   690  
   691  	input := testInput(t, map[string]string{
   692  		"approve": "yes",
   693  	})
   694  
   695  	op := testOperationApply()
   696  	op.Destroy = true
   697  	op.Module = nil
   698  	op.UIIn = input
   699  	op.UIOut = b.CLI
   700  	op.Workspace = backend.DefaultStateName
   701  
   702  	run, err := b.Operation(context.Background(), op)
   703  	if err != nil {
   704  		t.Fatalf("error starting operation: %v", err)
   705  	}
   706  
   707  	<-run.Done()
   708  	if run.Err != nil {
   709  		t.Fatalf("unexpected apply error: %v", run.Err)
   710  	}
   711  	if run.PlanEmpty {
   712  		t.Fatalf("expected a non-empty plan")
   713  	}
   714  
   715  	if len(input.answers) > 0 {
   716  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   717  	}
   718  }
   719  
   720  func TestRemote_applyPolicyPass(t *testing.T) {
   721  	b := testBackendDefault(t)
   722  
   723  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-passed")
   724  	defer modCleanup()
   725  
   726  	input := testInput(t, map[string]string{
   727  		"approve": "yes",
   728  	})
   729  
   730  	op := testOperationApply()
   731  	op.Module = mod
   732  	op.UIIn = input
   733  	op.UIOut = b.CLI
   734  	op.Workspace = backend.DefaultStateName
   735  
   736  	run, err := b.Operation(context.Background(), op)
   737  	if err != nil {
   738  		t.Fatalf("error starting operation: %v", err)
   739  	}
   740  
   741  	<-run.Done()
   742  	if run.Err != nil {
   743  		t.Fatalf("error running operation: %v", run.Err)
   744  	}
   745  	if run.PlanEmpty {
   746  		t.Fatalf("expected a non-empty plan")
   747  	}
   748  
   749  	if len(input.answers) > 0 {
   750  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   751  	}
   752  
   753  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   754  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   755  		t.Fatalf("missing plan summery in output: %s", output)
   756  	}
   757  	if !strings.Contains(output, "Sentinel Result: true") {
   758  		t.Fatalf("missing polic check result in output: %s", output)
   759  	}
   760  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   761  		t.Fatalf("missing apply summery in output: %s", output)
   762  	}
   763  }
   764  
   765  func TestRemote_applyPolicyHardFail(t *testing.T) {
   766  	b := testBackendDefault(t)
   767  
   768  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-hard-failed")
   769  	defer modCleanup()
   770  
   771  	input := testInput(t, map[string]string{
   772  		"approve": "yes",
   773  	})
   774  
   775  	op := testOperationApply()
   776  	op.Module = mod
   777  	op.UIIn = input
   778  	op.UIOut = b.CLI
   779  	op.Workspace = backend.DefaultStateName
   780  
   781  	run, err := b.Operation(context.Background(), op)
   782  	if err != nil {
   783  		t.Fatalf("error starting operation: %v", err)
   784  	}
   785  
   786  	<-run.Done()
   787  	if run.Err == nil {
   788  		t.Fatalf("expected an apply error, got: %v", run.Err)
   789  	}
   790  	if !run.PlanEmpty {
   791  		t.Fatalf("expected plan to be empty")
   792  	}
   793  	if !strings.Contains(run.Err.Error(), "hard failed") {
   794  		t.Fatalf("expected a policy check error, got: %v", run.Err)
   795  	}
   796  	if len(input.answers) != 1 {
   797  		t.Fatalf("expected an unused answers, got: %v", input.answers)
   798  	}
   799  
   800  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   801  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   802  		t.Fatalf("missing plan summery in output: %s", output)
   803  	}
   804  	if !strings.Contains(output, "Sentinel Result: false") {
   805  		t.Fatalf("missing policy check result in output: %s", output)
   806  	}
   807  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   808  		t.Fatalf("unexpected apply summery in output: %s", output)
   809  	}
   810  }
   811  
   812  func TestRemote_applyPolicySoftFail(t *testing.T) {
   813  	b := testBackendDefault(t)
   814  
   815  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed")
   816  	defer modCleanup()
   817  
   818  	input := testInput(t, map[string]string{
   819  		"override": "override",
   820  		"approve":  "yes",
   821  	})
   822  
   823  	op := testOperationApply()
   824  	op.Module = mod
   825  	op.UIIn = input
   826  	op.UIOut = b.CLI
   827  	op.Workspace = backend.DefaultStateName
   828  
   829  	run, err := b.Operation(context.Background(), op)
   830  	if err != nil {
   831  		t.Fatalf("error starting operation: %v", err)
   832  	}
   833  
   834  	<-run.Done()
   835  	if run.Err != nil {
   836  		t.Fatalf("error running operation: %v", run.Err)
   837  	}
   838  	if run.PlanEmpty {
   839  		t.Fatalf("expected a non-empty plan")
   840  	}
   841  
   842  	if len(input.answers) > 0 {
   843  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   844  	}
   845  
   846  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   847  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   848  		t.Fatalf("missing plan summery in output: %s", output)
   849  	}
   850  	if !strings.Contains(output, "Sentinel Result: false") {
   851  		t.Fatalf("missing policy check result in output: %s", output)
   852  	}
   853  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   854  		t.Fatalf("missing apply summery in output: %s", output)
   855  	}
   856  }
   857  
   858  func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
   859  	b := testBackendDefault(t)
   860  
   861  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed")
   862  	defer modCleanup()
   863  
   864  	input := testInput(t, map[string]string{
   865  		"override": "override",
   866  	})
   867  
   868  	op := testOperationApply()
   869  	op.AutoApprove = true
   870  	op.Module = mod
   871  	op.UIIn = input
   872  	op.UIOut = b.CLI
   873  	op.Workspace = backend.DefaultStateName
   874  
   875  	run, err := b.Operation(context.Background(), op)
   876  	if err != nil {
   877  		t.Fatalf("error starting operation: %v", err)
   878  	}
   879  
   880  	<-run.Done()
   881  	if run.Err == nil {
   882  		t.Fatalf("expected an apply error, got: %v", run.Err)
   883  	}
   884  	if !run.PlanEmpty {
   885  		t.Fatalf("expected plan to be empty")
   886  	}
   887  	if !strings.Contains(run.Err.Error(), "soft failed") {
   888  		t.Fatalf("expected a policy check error, got: %v", run.Err)
   889  	}
   890  	if len(input.answers) != 1 {
   891  		t.Fatalf("expected an unused answers, got: %v", input.answers)
   892  	}
   893  
   894  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   895  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   896  		t.Fatalf("missing plan summery in output: %s", output)
   897  	}
   898  	if !strings.Contains(output, "Sentinel Result: false") {
   899  		t.Fatalf("missing policy check result in output: %s", output)
   900  	}
   901  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   902  		t.Fatalf("unexpected apply summery in output: %s", output)
   903  	}
   904  }
   905  
   906  func TestRemote_applyPolicySoftFailAutoApply(t *testing.T) {
   907  	b := testBackendDefault(t)
   908  
   909  	// Create a named workspace that auto applies.
   910  	_, err := b.client.Workspaces.Create(
   911  		context.Background(),
   912  		b.organization,
   913  		tfe.WorkspaceCreateOptions{
   914  			AutoApply: tfe.Bool(true),
   915  			Name:      tfe.String(b.prefix + "prod"),
   916  		},
   917  	)
   918  	if err != nil {
   919  		t.Fatalf("error creating named workspace: %v", err)
   920  	}
   921  
   922  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed")
   923  	defer modCleanup()
   924  
   925  	input := testInput(t, map[string]string{
   926  		"override": "override",
   927  		"approve":  "yes",
   928  	})
   929  
   930  	op := testOperationApply()
   931  	op.Module = mod
   932  	op.UIIn = input
   933  	op.UIOut = b.CLI
   934  	op.Workspace = "prod"
   935  
   936  	run, err := b.Operation(context.Background(), op)
   937  	if err != nil {
   938  		t.Fatalf("error starting operation: %v", err)
   939  	}
   940  
   941  	<-run.Done()
   942  	if run.Err != nil {
   943  		t.Fatalf("error running operation: %v", run.Err)
   944  	}
   945  	if run.PlanEmpty {
   946  		t.Fatalf("expected a non-empty plan")
   947  	}
   948  
   949  	if len(input.answers) != 1 {
   950  		t.Fatalf("expected an unused answer, got: %v", input.answers)
   951  	}
   952  
   953  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   954  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   955  		t.Fatalf("missing plan summery in output: %s", output)
   956  	}
   957  	if !strings.Contains(output, "Sentinel Result: false") {
   958  		t.Fatalf("missing policy check result in output: %s", output)
   959  	}
   960  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   961  		t.Fatalf("missing apply summery in output: %s", output)
   962  	}
   963  }
   964  
   965  func TestRemote_applyWithRemoteError(t *testing.T) {
   966  	b := testBackendDefault(t)
   967  
   968  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-with-error")
   969  	defer modCleanup()
   970  
   971  	op := testOperationApply()
   972  	op.Module = mod
   973  	op.Workspace = backend.DefaultStateName
   974  
   975  	run, err := b.Operation(context.Background(), op)
   976  	if err != nil {
   977  		t.Fatalf("error starting operation: %v", err)
   978  	}
   979  
   980  	<-run.Done()
   981  	if run.Err != nil {
   982  		t.Fatalf("error running operation: %v", run.Err)
   983  	}
   984  	if run.ExitCode != 1 {
   985  		t.Fatalf("expected exit code 1, got %d", run.ExitCode)
   986  	}
   987  
   988  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   989  	if !strings.Contains(output, "null_resource.foo: 1 error") {
   990  		t.Fatalf("missing apply error in output: %s", output)
   991  	}
   992  }