github.com/magodo/terraform@v0.11.12-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-variables") 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_planNoChanges(t *testing.T) { 303 b := testBackendDefault(t) 304 305 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-no-changes") 306 defer modCleanup() 307 308 op := testOperationPlan() 309 op.Module = mod 310 op.Workspace = backend.DefaultStateName 311 312 run, err := b.Operation(context.Background(), op) 313 if err != nil { 314 t.Fatalf("error starting operation: %v", err) 315 } 316 317 <-run.Done() 318 if run.Err != nil { 319 t.Fatalf("error running operation: %v", run.Err) 320 } 321 if !run.PlanEmpty { 322 t.Fatalf("expected plan to be empty") 323 } 324 325 output := b.CLI.(*cli.MockUi).OutputWriter.String() 326 if !strings.Contains(output, "No changes. Infrastructure is up-to-date.") { 327 t.Fatalf("expected no changes in plan summery: %s", output) 328 } 329 if !strings.Contains(output, "Sentinel Result: true") { 330 t.Fatalf("expected policy check result in output: %s", output) 331 } 332 } 333 334 func TestRemote_planForceLocal(t *testing.T) { 335 // Set TF_FORCE_LOCAL_BACKEND so the remote backend will use 336 // the local backend with itself as embedded backend. 337 if err := os.Setenv("TF_FORCE_LOCAL_BACKEND", "1"); err != nil { 338 t.Fatalf("error setting environment variable TF_FORCE_LOCAL_BACKEND: %v", err) 339 } 340 defer os.Unsetenv("TF_FORCE_LOCAL_BACKEND") 341 342 b := testBackendDefault(t) 343 344 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 345 defer modCleanup() 346 347 op := testOperationPlan() 348 op.Module = mod 349 op.Workspace = backend.DefaultStateName 350 351 run, err := b.Operation(context.Background(), op) 352 if err != nil { 353 t.Fatalf("error starting operation: %v", err) 354 } 355 356 <-run.Done() 357 if run.Err != nil { 358 t.Fatalf("error running operation: %v", run.Err) 359 } 360 if run.PlanEmpty { 361 t.Fatalf("expected a non-empty plan") 362 } 363 364 output := b.CLI.(*cli.MockUi).OutputWriter.String() 365 if strings.Contains(output, "Running plan in the remote backend") { 366 t.Fatalf("unexpected remote backend header in output: %s", output) 367 } 368 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 369 t.Fatalf("expected plan summery in output: %s", output) 370 } 371 } 372 373 func TestRemote_planWithoutOperationsEntitlement(t *testing.T) { 374 b := testBackendNoOperations(t) 375 376 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 377 defer modCleanup() 378 379 op := testOperationPlan() 380 op.Module = mod 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 output := b.CLI.(*cli.MockUi).OutputWriter.String() 397 if strings.Contains(output, "Running plan in the remote backend") { 398 t.Fatalf("unexpected remote backend header in output: %s", output) 399 } 400 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 401 t.Fatalf("expected plan summery in output: %s", output) 402 } 403 } 404 405 func TestRemote_planWorkspaceWithoutOperations(t *testing.T) { 406 b := testBackendNoDefault(t) 407 ctx := context.Background() 408 409 // Create a named workspace that doesn't allow operations. 410 _, err := b.client.Workspaces.Create( 411 ctx, 412 b.organization, 413 tfe.WorkspaceCreateOptions{ 414 Name: tfe.String(b.prefix + "no-operations"), 415 }, 416 ) 417 if err != nil { 418 t.Fatalf("error creating named workspace: %v", err) 419 } 420 421 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 422 defer modCleanup() 423 424 op := testOperationPlan() 425 op.Module = mod 426 op.Workspace = "no-operations" 427 428 run, err := b.Operation(context.Background(), op) 429 if err != nil { 430 t.Fatalf("error starting operation: %v", err) 431 } 432 433 <-run.Done() 434 if run.Err != nil { 435 t.Fatalf("error running operation: %v", run.Err) 436 } 437 if run.PlanEmpty { 438 t.Fatalf("expected a non-empty plan") 439 } 440 441 output := b.CLI.(*cli.MockUi).OutputWriter.String() 442 if strings.Contains(output, "Running plan in the remote backend") { 443 t.Fatalf("unexpected remote backend header in output: %s", output) 444 } 445 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 446 t.Fatalf("expected plan summery in output: %s", output) 447 } 448 } 449 450 func TestRemote_planLockTimeout(t *testing.T) { 451 b := testBackendDefault(t) 452 ctx := context.Background() 453 454 // Retrieve the workspace used to run this operation in. 455 w, err := b.client.Workspaces.Read(ctx, b.organization, b.workspace) 456 if err != nil { 457 t.Fatalf("error retrieving workspace: %v", err) 458 } 459 460 // Create a new configuration version. 461 c, err := b.client.ConfigurationVersions.Create(ctx, w.ID, tfe.ConfigurationVersionCreateOptions{}) 462 if err != nil { 463 t.Fatalf("error creating configuration version: %v", err) 464 } 465 466 // Create a pending run to block this run. 467 _, err = b.client.Runs.Create(ctx, tfe.RunCreateOptions{ 468 ConfigurationVersion: c, 469 Workspace: w, 470 }) 471 if err != nil { 472 t.Fatalf("error creating pending run: %v", err) 473 } 474 475 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 476 defer modCleanup() 477 478 input := testInput(t, map[string]string{ 479 "cancel": "yes", 480 "approve": "yes", 481 }) 482 483 op := testOperationPlan() 484 op.StateLockTimeout = 5 * time.Second 485 op.Module = mod 486 op.UIIn = input 487 op.UIOut = b.CLI 488 op.Workspace = backend.DefaultStateName 489 490 _, err = b.Operation(context.Background(), op) 491 if err != nil { 492 t.Fatalf("error starting operation: %v", err) 493 } 494 495 sigint := make(chan os.Signal, 1) 496 signal.Notify(sigint, syscall.SIGINT) 497 select { 498 case <-sigint: 499 // Stop redirecting SIGINT signals. 500 signal.Stop(sigint) 501 case <-time.After(10 * time.Second): 502 t.Fatalf("expected lock timeout after 5 seconds, waited 10 seconds") 503 } 504 505 if len(input.answers) != 2 { 506 t.Fatalf("expected unused answers, got: %v", input.answers) 507 } 508 509 output := b.CLI.(*cli.MockUi).OutputWriter.String() 510 if !strings.Contains(output, "Lock timeout exceeded") { 511 t.Fatalf("missing lock timout error in output: %s", output) 512 } 513 if strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 514 t.Fatalf("unexpected plan summery in output: %s", output) 515 } 516 } 517 518 func TestRemote_planDestroy(t *testing.T) { 519 b := testBackendDefault(t) 520 521 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan") 522 defer modCleanup() 523 524 op := testOperationPlan() 525 op.Destroy = true 526 op.Module = mod 527 op.Workspace = backend.DefaultStateName 528 529 run, err := b.Operation(context.Background(), op) 530 if err != nil { 531 t.Fatalf("error starting operation: %v", err) 532 } 533 534 <-run.Done() 535 if run.Err != nil { 536 t.Fatalf("unexpected plan error: %v", run.Err) 537 } 538 if run.PlanEmpty { 539 t.Fatalf("expected a non-empty plan") 540 } 541 } 542 543 func TestRemote_planDestroyNoConfig(t *testing.T) { 544 b := testBackendDefault(t) 545 546 op := testOperationPlan() 547 op.Destroy = true 548 op.Module = nil 549 op.Workspace = backend.DefaultStateName 550 551 run, err := b.Operation(context.Background(), op) 552 if err != nil { 553 t.Fatalf("error starting operation: %v", err) 554 } 555 556 <-run.Done() 557 if run.Err != nil { 558 t.Fatalf("unexpected plan error: %v", run.Err) 559 } 560 if run.PlanEmpty { 561 t.Fatalf("expected a non-empty plan") 562 } 563 } 564 565 func TestRemote_planWithWorkingDirectory(t *testing.T) { 566 b := testBackendDefault(t) 567 568 options := tfe.WorkspaceUpdateOptions{ 569 WorkingDirectory: tfe.String("terraform"), 570 } 571 572 // Configure the workspace to use a custom working direcrtory. 573 _, err := b.client.Workspaces.Update(context.Background(), b.organization, b.workspace, options) 574 if err != nil { 575 t.Fatalf("error configuring working directory: %v", err) 576 } 577 578 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-working-directory/terraform") 579 defer modCleanup() 580 581 op := testOperationPlan() 582 op.Module = mod 583 op.Workspace = backend.DefaultStateName 584 585 run, err := b.Operation(context.Background(), op) 586 if err != nil { 587 t.Fatalf("error starting operation: %v", err) 588 } 589 590 <-run.Done() 591 if run.Err != nil { 592 t.Fatalf("error running operation: %v", run.Err) 593 } 594 if run.PlanEmpty { 595 t.Fatalf("expected a non-empty plan") 596 } 597 598 output := b.CLI.(*cli.MockUi).OutputWriter.String() 599 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 600 t.Fatalf("missing plan summery in output: %s", output) 601 } 602 } 603 604 func TestRemote_planPolicyPass(t *testing.T) { 605 b := testBackendDefault(t) 606 607 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-policy-passed") 608 defer modCleanup() 609 610 input := testInput(t, map[string]string{}) 611 612 op := testOperationPlan() 613 op.Module = mod 614 op.UIIn = input 615 op.UIOut = b.CLI 616 op.Workspace = backend.DefaultStateName 617 618 run, err := b.Operation(context.Background(), op) 619 if err != nil { 620 t.Fatalf("error starting operation: %v", err) 621 } 622 623 <-run.Done() 624 if run.Err != nil { 625 t.Fatalf("error running operation: %v", run.Err) 626 } 627 if run.PlanEmpty { 628 t.Fatalf("expected a non-empty plan") 629 } 630 631 output := b.CLI.(*cli.MockUi).OutputWriter.String() 632 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 633 t.Fatalf("missing plan summery in output: %s", output) 634 } 635 if !strings.Contains(output, "Sentinel Result: true") { 636 t.Fatalf("missing polic check result in output: %s", output) 637 } 638 } 639 640 func TestRemote_planPolicyHardFail(t *testing.T) { 641 b := testBackendDefault(t) 642 643 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-policy-hard-failed") 644 defer modCleanup() 645 646 input := testInput(t, map[string]string{}) 647 648 op := testOperationPlan() 649 op.Module = mod 650 op.UIIn = input 651 op.UIOut = b.CLI 652 op.Workspace = backend.DefaultStateName 653 654 run, err := b.Operation(context.Background(), op) 655 if err != nil { 656 t.Fatalf("error starting operation: %v", err) 657 } 658 659 <-run.Done() 660 if run.Err == nil { 661 t.Fatalf("expected a plan error, got: %v", run.Err) 662 } 663 if !run.PlanEmpty { 664 t.Fatalf("expected plan to be empty") 665 } 666 if !strings.Contains(run.Err.Error(), "hard failed") { 667 t.Fatalf("expected a policy check error, got: %v", run.Err) 668 } 669 670 output := b.CLI.(*cli.MockUi).OutputWriter.String() 671 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 672 t.Fatalf("missing plan summery in output: %s", output) 673 } 674 if !strings.Contains(output, "Sentinel Result: false") { 675 t.Fatalf("missing policy check result in output: %s", output) 676 } 677 } 678 679 func TestRemote_planPolicySoftFail(t *testing.T) { 680 b := testBackendDefault(t) 681 682 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-policy-soft-failed") 683 defer modCleanup() 684 685 input := testInput(t, map[string]string{}) 686 687 op := testOperationPlan() 688 op.Module = mod 689 op.UIIn = input 690 op.UIOut = b.CLI 691 op.Workspace = backend.DefaultStateName 692 693 run, err := b.Operation(context.Background(), op) 694 if err != nil { 695 t.Fatalf("error starting operation: %v", err) 696 } 697 698 <-run.Done() 699 if run.Err == nil { 700 t.Fatalf("expected a plan error, got: %v", run.Err) 701 } 702 if !run.PlanEmpty { 703 t.Fatalf("expected plan to be empty") 704 } 705 if !strings.Contains(run.Err.Error(), "soft failed") { 706 t.Fatalf("expected a policy check error, got: %v", run.Err) 707 } 708 709 output := b.CLI.(*cli.MockUi).OutputWriter.String() 710 if !strings.Contains(output, "1 to add, 0 to change, 0 to destroy") { 711 t.Fatalf("missing plan summery in output: %s", output) 712 } 713 if !strings.Contains(output, "Sentinel Result: false") { 714 t.Fatalf("missing policy check result in output: %s", output) 715 } 716 } 717 718 func TestRemote_planWithRemoteError(t *testing.T) { 719 b := testBackendDefault(t) 720 721 mod, modCleanup := module.TestTree(t, "./test-fixtures/plan-with-error") 722 defer modCleanup() 723 724 op := testOperationPlan() 725 op.Module = mod 726 op.Workspace = backend.DefaultStateName 727 728 run, err := b.Operation(context.Background(), op) 729 if err != nil { 730 t.Fatalf("error starting operation: %v", err) 731 } 732 733 <-run.Done() 734 if run.Err != nil { 735 t.Fatalf("error running operation: %v", run.Err) 736 } 737 if run.ExitCode != 1 { 738 t.Fatalf("expected exit code 1, got %d", run.ExitCode) 739 } 740 741 output := b.CLI.(*cli.MockUi).OutputWriter.String() 742 if !strings.Contains(output, "null_resource.foo: 1 error") { 743 t.Fatalf("missing plan error in output: %s", output) 744 } 745 }