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