github.com/pdecat/terraform@v0.11.9-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")
   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_applyLockTimeout(t *testing.T) {
   465  	b := testBackendDefault(t)
   466  	ctx := context.Background()
   467  
   468  	// Retrieve the workspace used to run this operation in.
   469  	w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace)
   470  	if err != nil {
   471  		t.Fatalf("error retrieving workspace: %v", err)
   472  	}
   473  
   474  	// Create a new configuration version.
   475  	c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
   476  	if err != nil {
   477  		t.Fatalf("error creating configuration version: %v", err)
   478  	}
   479  
   480  	// Create a pending run to block this run.
   481  	_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
   482  		ConfigurationVersion: c,
   483  		Workspace:            w,
   484  	})
   485  	if err != nil {
   486  		t.Fatalf("error creating pending run: %v", err)
   487  	}
   488  
   489  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply")
   490  	defer modCleanup()
   491  
   492  	input := testInput(t, map[string]string{
   493  		"cancel":  "yes",
   494  		"approve": "yes",
   495  	})
   496  
   497  	op := testOperationApply()
   498  	op.StateLockTimeout = 5 * time.Second
   499  	op.Module = mod
   500  	op.UIIn = input
   501  	op.UIOut = b.CLI
   502  	op.Workspace = backend.DefaultStateName
   503  
   504  	_, err = b.Operation(context.Background(), op)
   505  	if err != nil {
   506  		t.Fatalf("error starting operation: %v", err)
   507  	}
   508  
   509  	sigint := make(chan os.Signal, 1)
   510  	signal.Notify(sigint, syscall.SIGINT)
   511  	select {
   512  	case <-sigint:
   513  		// Stop redirecting SIGINT signals.
   514  		signal.Stop(sigint)
   515  	case <-time.After(10 * time.Second):
   516  		t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds")
   517  	}
   518  
   519  	if len(input.answers) != 2 {
   520  		t.Fatalf("expected unused answers, got: %v", input.answers)
   521  	}
   522  
   523  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   524  	if !strings.Contains(output, "Lock timeout exceeded") {
   525  		t.Fatalf("missing lock timout error in output: %s", output)
   526  	}
   527  	if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   528  		t.Fatalf("unexpected plan summery in output: %s", output)
   529  	}
   530  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   531  		t.Fatalf("unexpected apply summery in output: %s", output)
   532  	}
   533  }
   534  
   535  func TestRemote_applyDestroy(t *testing.T) {
   536  	b := testBackendDefault(t)
   537  
   538  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-destroy")
   539  	defer modCleanup()
   540  
   541  	input := testInput(t, map[string]string{
   542  		"approve": "yes",
   543  	})
   544  
   545  	op := testOperationApply()
   546  	op.Destroy = true
   547  	op.Module = mod
   548  	op.UIIn = input
   549  	op.UIOut = b.CLI
   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  	if run.Err != nil {
   559  		t.Fatalf("error running operation: %v", run.Err)
   560  	}
   561  	if run.PlanEmpty {
   562  		t.Fatalf("expected a non-empty plan")
   563  	}
   564  
   565  	if len(input.answers) > 0 {
   566  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   567  	}
   568  
   569  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   570  	if !strings.Contains(output, "0 to add, 0 to change, 1 to destroy") {
   571  		t.Fatalf("missing plan summery in output: %s", output)
   572  	}
   573  	if !strings.Contains(output, "0 added, 0 changed, 1 destroyed") {
   574  		t.Fatalf("missing apply summery in output: %s", output)
   575  	}
   576  }
   577  
   578  func TestRemote_applyDestroyNoConfig(t *testing.T) {
   579  	b := testBackendDefault(t)
   580  
   581  	input := testInput(t, map[string]string{
   582  		"approve": "yes",
   583  	})
   584  
   585  	op := testOperationApply()
   586  	op.Destroy = true
   587  	op.Module = nil
   588  	op.UIIn = input
   589  	op.UIOut = b.CLI
   590  	op.Workspace = backend.DefaultStateName
   591  
   592  	run, err := b.Operation(context.Background(), op)
   593  	if err != nil {
   594  		t.Fatalf("error starting operation: %v", err)
   595  	}
   596  
   597  	<-run.Done()
   598  	if run.Err != nil {
   599  		t.Fatalf("unexpected apply error: %v", run.Err)
   600  	}
   601  	if run.PlanEmpty {
   602  		t.Fatalf("expected a non-empty plan")
   603  	}
   604  
   605  	if len(input.answers) > 0 {
   606  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   607  	}
   608  }
   609  
   610  func TestRemote_applyPolicyPass(t *testing.T) {
   611  	b := testBackendDefault(t)
   612  
   613  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-passed")
   614  	defer modCleanup()
   615  
   616  	input := testInput(t, map[string]string{
   617  		"approve": "yes",
   618  	})
   619  
   620  	op := testOperationApply()
   621  	op.Module = mod
   622  	op.UIIn = input
   623  	op.UIOut = b.CLI
   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.Err != nil {
   633  		t.Fatalf("error running operation: %v", run.Err)
   634  	}
   635  	if run.PlanEmpty {
   636  		t.Fatalf("expected a non-empty plan")
   637  	}
   638  
   639  	if len(input.answers) > 0 {
   640  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   641  	}
   642  
   643  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   644  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   645  		t.Fatalf("missing plan summery in output: %s", output)
   646  	}
   647  	if !strings.Contains(output, "Sentinel Result: true") {
   648  		t.Fatalf("missing polic check result in output: %s", output)
   649  	}
   650  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   651  		t.Fatalf("missing apply summery in output: %s", output)
   652  	}
   653  }
   654  
   655  func TestRemote_applyPolicyHardFail(t *testing.T) {
   656  	b := testBackendDefault(t)
   657  
   658  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-hard-failed")
   659  	defer modCleanup()
   660  
   661  	input := testInput(t, map[string]string{
   662  		"approve": "yes",
   663  	})
   664  
   665  	op := testOperationApply()
   666  	op.Module = mod
   667  	op.UIIn = input
   668  	op.UIOut = b.CLI
   669  	op.Workspace = backend.DefaultStateName
   670  
   671  	run, err := b.Operation(context.Background(), op)
   672  	if err != nil {
   673  		t.Fatalf("error starting operation: %v", err)
   674  	}
   675  
   676  	<-run.Done()
   677  	if run.Err == nil {
   678  		t.Fatalf("expected an apply error, got: %v", run.Err)
   679  	}
   680  	if !run.PlanEmpty {
   681  		t.Fatalf("expected plan to be empty")
   682  	}
   683  	if !strings.Contains(run.Err.Error(), "hard failed") {
   684  		t.Fatalf("expected a policy check error, got: %v", run.Err)
   685  	}
   686  	if len(input.answers) != 1 {
   687  		t.Fatalf("expected an unused answers, got: %v", input.answers)
   688  	}
   689  
   690  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   691  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   692  		t.Fatalf("missing plan summery in output: %s", output)
   693  	}
   694  	if !strings.Contains(output, "Sentinel Result: false") {
   695  		t.Fatalf("missing policy check result in output: %s", output)
   696  	}
   697  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   698  		t.Fatalf("unexpected apply summery in output: %s", output)
   699  	}
   700  }
   701  
   702  func TestRemote_applyPolicySoftFail(t *testing.T) {
   703  	b := testBackendDefault(t)
   704  
   705  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed")
   706  	defer modCleanup()
   707  
   708  	input := testInput(t, map[string]string{
   709  		"override": "override",
   710  		"approve":  "yes",
   711  	})
   712  
   713  	op := testOperationApply()
   714  	op.Module = mod
   715  	op.UIIn = input
   716  	op.UIOut = b.CLI
   717  	op.Workspace = backend.DefaultStateName
   718  
   719  	run, err := b.Operation(context.Background(), op)
   720  	if err != nil {
   721  		t.Fatalf("error starting operation: %v", err)
   722  	}
   723  
   724  	<-run.Done()
   725  	if run.Err != nil {
   726  		t.Fatalf("error running operation: %v", run.Err)
   727  	}
   728  	if run.PlanEmpty {
   729  		t.Fatalf("expected a non-empty plan")
   730  	}
   731  
   732  	if len(input.answers) > 0 {
   733  		t.Fatalf("expected no unused answers, got: %v", input.answers)
   734  	}
   735  
   736  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   737  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   738  		t.Fatalf("missing plan summery in output: %s", output)
   739  	}
   740  	if !strings.Contains(output, "Sentinel Result: false") {
   741  		t.Fatalf("missing policy check result in output: %s", output)
   742  	}
   743  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   744  		t.Fatalf("missing apply summery in output: %s", output)
   745  	}
   746  }
   747  
   748  func TestRemote_applyPolicySoftFailAutoApprove(t *testing.T) {
   749  	b := testBackendDefault(t)
   750  
   751  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed")
   752  	defer modCleanup()
   753  
   754  	input := testInput(t, map[string]string{
   755  		"override": "override",
   756  	})
   757  
   758  	op := testOperationApply()
   759  	op.AutoApprove = true
   760  	op.Module = mod
   761  	op.UIIn = input
   762  	op.UIOut = b.CLI
   763  	op.Workspace = backend.DefaultStateName
   764  
   765  	run, err := b.Operation(context.Background(), op)
   766  	if err != nil {
   767  		t.Fatalf("error starting operation: %v", err)
   768  	}
   769  
   770  	<-run.Done()
   771  	if run.Err == nil {
   772  		t.Fatalf("expected an apply error, got: %v", run.Err)
   773  	}
   774  	if !run.PlanEmpty {
   775  		t.Fatalf("expected plan to be empty")
   776  	}
   777  	if !strings.Contains(run.Err.Error(), "soft failed") {
   778  		t.Fatalf("expected a policy check error, got: %v", run.Err)
   779  	}
   780  	if len(input.answers) != 1 {
   781  		t.Fatalf("expected an unused answers, got: %v", input.answers)
   782  	}
   783  
   784  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   785  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   786  		t.Fatalf("missing plan summery in output: %s", output)
   787  	}
   788  	if !strings.Contains(output, "Sentinel Result: false") {
   789  		t.Fatalf("missing policy check result in output: %s", output)
   790  	}
   791  	if strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   792  		t.Fatalf("unexpected apply summery in output: %s", output)
   793  	}
   794  }
   795  
   796  func TestRemote_applyPolicySoftFailAutoApply(t *testing.T) {
   797  	b := testBackendDefault(t)
   798  
   799  	// Create a named workspace that auto applies.
   800  	_, err := b.client.Workspaces.Create(
   801  		context.Background(),
   802  		b.organization,
   803  		tfe.WorkspaceCreateOptions{
   804  			AutoApply: tfe.Bool(true),
   805  			Name:      tfe.String(b.prefix + "prod"),
   806  		},
   807  	)
   808  	if err != nil {
   809  		t.Fatalf("error creating named workspace: %v", err)
   810  	}
   811  
   812  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-policy-soft-failed")
   813  	defer modCleanup()
   814  
   815  	input := testInput(t, map[string]string{
   816  		"override": "override",
   817  		"approve":  "yes",
   818  	})
   819  
   820  	op := testOperationApply()
   821  	op.Module = mod
   822  	op.UIIn = input
   823  	op.UIOut = b.CLI
   824  	op.Workspace = "prod"
   825  
   826  	run, err := b.Operation(context.Background(), op)
   827  	if err != nil {
   828  		t.Fatalf("error starting operation: %v", err)
   829  	}
   830  
   831  	<-run.Done()
   832  	if run.Err != nil {
   833  		t.Fatalf("error running operation: %v", run.Err)
   834  	}
   835  	if run.PlanEmpty {
   836  		t.Fatalf("expected a non-empty plan")
   837  	}
   838  
   839  	if len(input.answers) != 1 {
   840  		t.Fatalf("expected an unused answer, got: %v", input.answers)
   841  	}
   842  
   843  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   844  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   845  		t.Fatalf("missing plan summery in output: %s", output)
   846  	}
   847  	if !strings.Contains(output, "Sentinel Result: false") {
   848  		t.Fatalf("missing policy check result in output: %s", output)
   849  	}
   850  	if !strings.Contains(output, "1 added, 0 changed, 0 destroyed") {
   851  		t.Fatalf("missing apply summery in output: %s", output)
   852  	}
   853  }
   854  
   855  func TestRemote_applyWithRemoteError(t *testing.T) {
   856  	b := testBackendDefault(t)
   857  
   858  	mod, modCleanup := module.TestTree(t, "./test-fixtures/apply-with-error")
   859  	defer modCleanup()
   860  
   861  	op := testOperationApply()
   862  	op.Module = mod
   863  	op.Workspace = backend.DefaultStateName
   864  
   865  	run, err := b.Operation(context.Background(), op)
   866  	if err != nil {
   867  		t.Fatalf("error starting operation: %v", err)
   868  	}
   869  
   870  	<-run.Done()
   871  	if run.Err != nil {
   872  		t.Fatalf("error running operation: %v", run.Err)
   873  	}
   874  	if run.ExitCode != 1 {
   875  		t.Fatalf("expected exit code 1, got %d", run.ExitCode)
   876  	}
   877  
   878  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   879  	if !strings.Contains(output, "null_resource.foo: 1 error") {
   880  		t.Fatalf("missing apply error in output: %s", output)
   881  	}
   882  }