github.com/pdecat/terraform@v0.11.9-beta1/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  	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 testOperationPlan() *backend.Operation {
    20  	return &backend.Operation{
    21  		ModuleDepth: defaultModuleDepth,
    22  		Parallelism: defaultParallelism,
    23  		PlanRefresh: true,
    24  		Type:        backend.OperationTypePlan,
    25  	}
    26  }
    27  
    28  func TestRemote_planBasic(t *testing.T) {
    29  	b := testBackendDefault(t)
    30  
    31  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
    32  	defer modCleanup()
    33  
    34  	op := testOperationPlan()
    35  	op.Module = mod
    36  	op.Workspace = backend.DefaultStateName
    37  
    38  	run, err := b.Operation(context.Background(), op)
    39  	if err != nil {
    40  		t.Fatalf("error starting operation: %v", err)
    41  	}
    42  
    43  	<-run.Done()
    44  	if run.Err != nil {
    45  		t.Fatalf("error running operation: %v", run.Err)
    46  	}
    47  	if run.PlanEmpty {
    48  		t.Fatal("expected a non-empty plan")
    49  	}
    50  
    51  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
    52  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
    53  		t.Fatalf("missing plan summery in output: %s", output)
    54  	}
    55  }
    56  
    57  func TestRemote_planWithoutPermissions(t *testing.T) {
    58  	b := testBackendNoDefault(t)
    59  
    60  	// Create a named workspace without permissions.
    61  	w, err := b.client.Workspaces.Create(
    62  		context.Background(),
    63  		b.organization,
    64  		tfe.WorkspaceCreateOptions{
    65  			Name: tfe.String(b.prefix + "prod"),
    66  		},
    67  	)
    68  	if err != nil {
    69  		t.Fatalf("error creating named workspace: %v", err)
    70  	}
    71  	w.Permissions.CanQueueRun = false
    72  
    73  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
    74  	defer modCleanup()
    75  
    76  	op := testOperationPlan()
    77  	op.Module = mod
    78  	op.Workspace = "prod"
    79  
    80  	run, err := b.Operation(context.Background(), op)
    81  	if err != nil {
    82  		t.Fatalf("error starting operation: %v", err)
    83  	}
    84  
    85  	<-run.Done()
    86  	if run.Err == nil {
    87  		t.Fatalf("expected a plan error, got: %v", run.Err)
    88  	}
    89  	if !strings.Contains(run.Err.Error(), "insufficient rights to generate a plan") {
    90  		t.Fatalf("expected a permissions error, got: %v", run.Err)
    91  	}
    92  }
    93  
    94  func TestRemote_planWithModuleDepth(t *testing.T) {
    95  	b := testBackendDefault(t)
    96  
    97  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
    98  	defer modCleanup()
    99  
   100  	op := testOperationPlan()
   101  	op.Module = mod
   102  	op.ModuleDepth = 1
   103  	op.Workspace = backend.DefaultStateName
   104  
   105  	run, err := b.Operation(context.Background(), op)
   106  	if err != nil {
   107  		t.Fatalf("error starting operation: %v", err)
   108  	}
   109  
   110  	<-run.Done()
   111  	if run.Err == nil {
   112  		t.Fatalf("expected a plan error, got: %v", run.Err)
   113  	}
   114  	if !strings.Contains(run.Err.Error(), "module depths are currently not supported") {
   115  		t.Fatalf("expected a module depth error, got: %v", run.Err)
   116  	}
   117  }
   118  
   119  func TestRemote_planWithParallelism(t *testing.T) {
   120  	b := testBackendDefault(t)
   121  
   122  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   123  	defer modCleanup()
   124  
   125  	op := testOperationPlan()
   126  	op.Module = mod
   127  	op.Parallelism = 3
   128  	op.Workspace = backend.DefaultStateName
   129  
   130  	run, err := b.Operation(context.Background(), op)
   131  	if err != nil {
   132  		t.Fatalf("error starting operation: %v", err)
   133  	}
   134  
   135  	<-run.Done()
   136  	if run.Err == nil {
   137  		t.Fatalf("expected a plan error, got: %v", run.Err)
   138  	}
   139  	if !strings.Contains(run.Err.Error(), "parallelism values are currently not supported") {
   140  		t.Fatalf("expected a parallelism error, got: %v", run.Err)
   141  	}
   142  }
   143  
   144  func TestRemote_planWithPlan(t *testing.T) {
   145  	b := testBackendDefault(t)
   146  
   147  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   148  	defer modCleanup()
   149  
   150  	op := testOperationPlan()
   151  	op.Module = mod
   152  	op.Plan = &terraform.Plan{}
   153  	op.Workspace = backend.DefaultStateName
   154  
   155  	run, err := b.Operation(context.Background(), op)
   156  	if err != nil {
   157  		t.Fatalf("error starting operation: %v", err)
   158  	}
   159  
   160  	<-run.Done()
   161  	if run.Err == nil {
   162  		t.Fatalf("expected a plan error, got: %v", run.Err)
   163  	}
   164  	if !run.PlanEmpty {
   165  		t.Fatalf("expected plan to be empty")
   166  	}
   167  	if !strings.Contains(run.Err.Error(), "saved plan is currently not supported") {
   168  		t.Fatalf("expected a saved plan error, got: %v", run.Err)
   169  	}
   170  }
   171  
   172  func TestRemote_planWithPath(t *testing.T) {
   173  	b := testBackendDefault(t)
   174  
   175  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   176  	defer modCleanup()
   177  
   178  	op := testOperationPlan()
   179  	op.Module = mod
   180  	op.PlanOutPath = "./test-fixtures/plan"
   181  	op.Workspace = backend.DefaultStateName
   182  
   183  	run, err := b.Operation(context.Background(), op)
   184  	if err != nil {
   185  		t.Fatalf("error starting operation: %v", err)
   186  	}
   187  
   188  	<-run.Done()
   189  	if run.Err == nil {
   190  		t.Fatalf("expected a plan error, got: %v", run.Err)
   191  	}
   192  	if !run.PlanEmpty {
   193  		t.Fatalf("expected plan to be empty")
   194  	}
   195  	if !strings.Contains(run.Err.Error(), "generated plan is currently not supported") {
   196  		t.Fatalf("expected a generated plan error, got: %v", run.Err)
   197  	}
   198  }
   199  
   200  func TestRemote_planWithoutRefresh(t *testing.T) {
   201  	b := testBackendDefault(t)
   202  
   203  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   204  	defer modCleanup()
   205  
   206  	op := testOperationPlan()
   207  	op.Module = mod
   208  	op.PlanRefresh = false
   209  	op.Workspace = backend.DefaultStateName
   210  
   211  	run, err := b.Operation(context.Background(), op)
   212  	if err != nil {
   213  		t.Fatalf("error starting operation: %v", err)
   214  	}
   215  
   216  	<-run.Done()
   217  	if run.Err == nil {
   218  		t.Fatalf("expected a plan error, got: %v", run.Err)
   219  	}
   220  	if !strings.Contains(run.Err.Error(), "refresh is currently not supported") {
   221  		t.Fatalf("expected a refresh error, got: %v", run.Err)
   222  	}
   223  }
   224  
   225  func TestRemote_planWithTarget(t *testing.T) {
   226  	b := testBackendDefault(t)
   227  
   228  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   229  	defer modCleanup()
   230  
   231  	op := testOperationPlan()
   232  	op.Module = mod
   233  	op.Targets = []string{"null_resource.foo"}
   234  	op.Workspace = backend.DefaultStateName
   235  
   236  	run, err := b.Operation(context.Background(), op)
   237  	if err != nil {
   238  		t.Fatalf("error starting operation: %v", err)
   239  	}
   240  
   241  	<-run.Done()
   242  	if run.Err == nil {
   243  		t.Fatalf("expected a plan error, got: %v", run.Err)
   244  	}
   245  	if !run.PlanEmpty {
   246  		t.Fatalf("expected plan to be empty")
   247  	}
   248  	if !strings.Contains(run.Err.Error(), "targeting is currently not supported") {
   249  		t.Fatalf("expected a targeting error, got: %v", run.Err)
   250  	}
   251  }
   252  
   253  func TestRemote_planWithVariables(t *testing.T) {
   254  	b := testBackendDefault(t)
   255  
   256  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   257  	defer modCleanup()
   258  
   259  	op := testOperationPlan()
   260  	op.Module = mod
   261  	op.Variables = map[string]interface{}{"foo": "bar"}
   262  	op.Workspace = backend.DefaultStateName
   263  
   264  	run, err := b.Operation(context.Background(), op)
   265  	if err != nil {
   266  		t.Fatalf("error starting operation: %v", err)
   267  	}
   268  
   269  	<-run.Done()
   270  	if run.Err == nil {
   271  		t.Fatalf("expected an plan error, got: %v", run.Err)
   272  	}
   273  	if !strings.Contains(run.Err.Error(), "variables are currently not supported") {
   274  		t.Fatalf("expected a variables error, got: %v", run.Err)
   275  	}
   276  }
   277  
   278  func TestRemote_planNoConfig(t *testing.T) {
   279  	b := testBackendDefault(t)
   280  
   281  	op := testOperationPlan()
   282  	op.Module = nil
   283  	op.Workspace = backend.DefaultStateName
   284  
   285  	run, err := b.Operation(context.Background(), op)
   286  	if err != nil {
   287  		t.Fatalf("error starting operation: %v", err)
   288  	}
   289  
   290  	<-run.Done()
   291  	if run.Err == nil {
   292  		t.Fatalf("expected a plan error, got: %v", run.Err)
   293  	}
   294  	if !run.PlanEmpty {
   295  		t.Fatalf("expected plan to be empty")
   296  	}
   297  	if !strings.Contains(run.Err.Error(), "configuration files found") {
   298  		t.Fatalf("expected configuration files error, got: %v", run.Err)
   299  	}
   300  }
   301  
   302  func TestRemote_planLockTimeout(t *testing.T) {
   303  	b := testBackendDefault(t)
   304  	ctx := context.Background()
   305  
   306  	// Retrieve the workspace used to run this operation in.
   307  	w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace)
   308  	if err != nil {
   309  		t.Fatalf("error retrieving workspace: %v", err)
   310  	}
   311  
   312  	// Create a new configuration version.
   313  	c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{})
   314  	if err != nil {
   315  		t.Fatalf("error creating configuration version: %v", err)
   316  	}
   317  
   318  	// Create a pending run to block this run.
   319  	_, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{
   320  		ConfigurationVersion: c,
   321  		Workspace:            w,
   322  	})
   323  	if err != nil {
   324  		t.Fatalf("error creating pending run: %v", err)
   325  	}
   326  
   327  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   328  	defer modCleanup()
   329  
   330  	input := testInput(t, map[string]string{
   331  		"cancel":  "yes",
   332  		"approve": "yes",
   333  	})
   334  
   335  	op := testOperationPlan()
   336  	op.StateLockTimeout = 5 * time.Second
   337  	op.Module = mod
   338  	op.UIIn = input
   339  	op.UIOut = b.CLI
   340  	op.Workspace = backend.DefaultStateName
   341  
   342  	_, err = b.Operation(context.Background(), op)
   343  	if err != nil {
   344  		t.Fatalf("error starting operation: %v", err)
   345  	}
   346  
   347  	sigint := make(chan os.Signal, 1)
   348  	signal.Notify(sigint, syscall.SIGINT)
   349  	select {
   350  	case <-sigint:
   351  		// Stop redirecting SIGINT signals.
   352  		signal.Stop(sigint)
   353  	case <-time.After(10 * time.Second):
   354  		t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds")
   355  	}
   356  
   357  	if len(input.answers) != 2 {
   358  		t.Fatalf("expected unused answers, got: %v", input.answers)
   359  	}
   360  
   361  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   362  	if !strings.Contains(output, "Lock timeout exceeded") {
   363  		t.Fatalf("missing lock timout error in output: %s", output)
   364  	}
   365  	if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   366  		t.Fatalf("unexpected plan summery in output: %s", output)
   367  	}
   368  }
   369  
   370  func TestRemote_planDestroy(t *testing.T) {
   371  	b := testBackendDefault(t)
   372  
   373  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan")
   374  	defer modCleanup()
   375  
   376  	op := testOperationPlan()
   377  	op.Destroy = true
   378  	op.Module = mod
   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.Err != nil {
   388  		t.Fatalf("unexpected plan error: %v", run.Err)
   389  	}
   390  	if run.PlanEmpty {
   391  		t.Fatalf("expected a non-empty plan")
   392  	}
   393  }
   394  
   395  func TestRemote_planDestroyNoConfig(t *testing.T) {
   396  	b := testBackendDefault(t)
   397  
   398  	op := testOperationPlan()
   399  	op.Destroy = true
   400  	op.Module = nil
   401  	op.Workspace = backend.DefaultStateName
   402  
   403  	run, err := b.Operation(context.Background(), op)
   404  	if err != nil {
   405  		t.Fatalf("error starting operation: %v", err)
   406  	}
   407  
   408  	<-run.Done()
   409  	if run.Err != nil {
   410  		t.Fatalf("unexpected plan error: %v", run.Err)
   411  	}
   412  	if run.PlanEmpty {
   413  		t.Fatalf("expected a non-empty plan")
   414  	}
   415  }
   416  
   417  func TestRemote_planWithWorkingDirectory(t *testing.T) {
   418  	b := testBackendDefault(t)
   419  
   420  	options := tfe.WorkspaceUpdateOptions{
   421  		WorkingDirectory: tfe.String("terraform"),
   422  	}
   423  
   424  	// Configure the workspace to use a custom working direcrtory.
   425  	_, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options)
   426  	if err != nil {
   427  		t.Fatalf("error configuring working directory: %v", err)
   428  	}
   429  
   430  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-working-directory/terraform")
   431  	defer modCleanup()
   432  
   433  	op := testOperationPlan()
   434  	op.Module = mod
   435  	op.Workspace = backend.DefaultStateName
   436  
   437  	run, err := b.Operation(context.Background(), op)
   438  	if err != nil {
   439  		t.Fatalf("error starting operation: %v", err)
   440  	}
   441  
   442  	<-run.Done()
   443  	if run.Err != nil {
   444  		t.Fatalf("error running operation: %v", run.Err)
   445  	}
   446  	if run.PlanEmpty {
   447  		t.Fatalf("expected a non-empty plan")
   448  	}
   449  
   450  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   451  	if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") {
   452  		t.Fatalf("missing plan summery in output: %s", output)
   453  	}
   454  }
   455  
   456  func TestRemote_planWithRemoteError(t *testing.T) {
   457  	b := testBackendDefault(t)
   458  
   459  	mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-error")
   460  	defer modCleanup()
   461  
   462  	op := testOperationPlan()
   463  	op.Module = mod
   464  	op.Workspace = backend.DefaultStateName
   465  
   466  	run, err := b.Operation(context.Background(), op)
   467  	if err != nil {
   468  		t.Fatalf("error starting operation: %v", err)
   469  	}
   470  
   471  	<-run.Done()
   472  	if run.Err != nil {
   473  		t.Fatalf("error running operation: %v", run.Err)
   474  	}
   475  	if run.ExitCode != 1 {
   476  		t.Fatalf("expected exit code 1, got %d", run.ExitCode)
   477  	}
   478  
   479  	output := b.CLI.(*cli.MockUi).OutputWriter.String()
   480  	if !strings.Contains(output, "null_resource.foo: 1 error") {
   481  		t.Fatalf("missing plan error in output: %s", output)
   482  	}
   483  }