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 }