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