github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/apply_test.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "path/filepath" 10 "reflect" 11 "strings" 12 "sync" 13 "testing" 14 "time" 15 16 "github.com/google/go-cmp/cmp" 17 "github.com/google/go-cmp/cmp/cmpopts" 18 "github.com/mitchellh/cli" 19 "github.com/zclconf/go-cty/cty" 20 21 "github.com/hashicorp/terraform/internal/addrs" 22 "github.com/hashicorp/terraform/internal/configs/configschema" 23 "github.com/hashicorp/terraform/internal/plans" 24 "github.com/hashicorp/terraform/internal/providers" 25 "github.com/hashicorp/terraform/internal/states" 26 "github.com/hashicorp/terraform/internal/states/statemgr" 27 "github.com/hashicorp/terraform/internal/terraform" 28 "github.com/hashicorp/terraform/internal/tfdiags" 29 ) 30 31 func TestApply(t *testing.T) { 32 // Create a temporary working directory that is empty 33 td := t.TempDir() 34 testCopyDir(t, testFixturePath("apply"), td) 35 defer testChdir(t, td)() 36 37 statePath := testTempFile(t) 38 39 p := applyFixtureProvider() 40 41 view, done := testView(t) 42 c := &ApplyCommand{ 43 Meta: Meta{ 44 testingOverrides: metaOverridesForProvider(p), 45 View: view, 46 }, 47 } 48 49 args := []string{ 50 "-state", statePath, 51 "-auto-approve", 52 } 53 code := c.Run(args) 54 output := done(t) 55 if code != 0 { 56 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 57 } 58 59 if _, err := os.Stat(statePath); err != nil { 60 t.Fatalf("err: %s", err) 61 } 62 63 state := testStateRead(t, statePath) 64 if state == nil { 65 t.Fatal("state should not be nil") 66 } 67 } 68 69 func TestApply_path(t *testing.T) { 70 // Create a temporary working directory that is empty 71 td := t.TempDir() 72 testCopyDir(t, testFixturePath("apply"), td) 73 defer testChdir(t, td)() 74 75 p := applyFixtureProvider() 76 77 view, done := testView(t) 78 c := &ApplyCommand{ 79 Meta: Meta{ 80 testingOverrides: metaOverridesForProvider(p), 81 View: view, 82 }, 83 } 84 85 args := []string{ 86 "-auto-approve", 87 testFixturePath("apply"), 88 } 89 code := c.Run(args) 90 output := done(t) 91 if code != 1 { 92 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 93 } 94 if !strings.Contains(output.Stderr(), "-chdir") { 95 t.Fatal("expected command output to refer to -chdir flag, but got:", output.Stderr()) 96 } 97 } 98 99 func TestApply_approveNo(t *testing.T) { 100 // Create a temporary working directory that is empty 101 td := t.TempDir() 102 testCopyDir(t, testFixturePath("apply"), td) 103 defer testChdir(t, td)() 104 105 statePath := testTempFile(t) 106 107 defer testInputMap(t, map[string]string{ 108 "approve": "no", 109 })() 110 111 // Do not use the NewMockUi initializer here, as we want to delay 112 // the call to init until after setting up the input mocks 113 ui := new(cli.MockUi) 114 115 p := applyFixtureProvider() 116 view, done := testView(t) 117 c := &ApplyCommand{ 118 Meta: Meta{ 119 testingOverrides: metaOverridesForProvider(p), 120 Ui: ui, 121 View: view, 122 }, 123 } 124 125 args := []string{ 126 "-state", statePath, 127 } 128 code := c.Run(args) 129 output := done(t) 130 if code != 1 { 131 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 132 } 133 if got, want := output.Stdout(), "Apply cancelled"; !strings.Contains(got, want) { 134 t.Fatalf("expected output to include %q, but was:\n%s", want, got) 135 } 136 137 if _, err := os.Stat(statePath); err == nil || !os.IsNotExist(err) { 138 t.Fatalf("state file should not exist") 139 } 140 } 141 142 func TestApply_approveYes(t *testing.T) { 143 // Create a temporary working directory that is empty 144 td := t.TempDir() 145 testCopyDir(t, testFixturePath("apply"), td) 146 defer testChdir(t, td)() 147 148 statePath := testTempFile(t) 149 150 p := applyFixtureProvider() 151 152 defer testInputMap(t, map[string]string{ 153 "approve": "yes", 154 })() 155 156 // Do not use the NewMockUi initializer here, as we want to delay 157 // the call to init until after setting up the input mocks 158 ui := new(cli.MockUi) 159 160 view, done := testView(t) 161 c := &ApplyCommand{ 162 Meta: Meta{ 163 testingOverrides: metaOverridesForProvider(p), 164 Ui: ui, 165 View: view, 166 }, 167 } 168 169 args := []string{ 170 "-state", statePath, 171 } 172 code := c.Run(args) 173 output := done(t) 174 if code != 0 { 175 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 176 } 177 178 if _, err := os.Stat(statePath); err != nil { 179 t.Fatalf("err: %s", err) 180 } 181 182 state := testStateRead(t, statePath) 183 if state == nil { 184 t.Fatal("state should not be nil") 185 } 186 } 187 188 // test apply with locked state 189 func TestApply_lockedState(t *testing.T) { 190 // Create a temporary working directory that is empty 191 td := t.TempDir() 192 testCopyDir(t, testFixturePath("apply"), td) 193 defer testChdir(t, td)() 194 195 statePath := testTempFile(t) 196 197 unlock, err := testLockState(t, testDataDir, statePath) 198 if err != nil { 199 t.Fatal(err) 200 } 201 defer unlock() 202 203 p := applyFixtureProvider() 204 view, done := testView(t) 205 c := &ApplyCommand{ 206 Meta: Meta{ 207 testingOverrides: metaOverridesForProvider(p), 208 View: view, 209 }, 210 } 211 212 args := []string{ 213 "-state", statePath, 214 "-auto-approve", 215 } 216 code := c.Run(args) 217 output := done(t) 218 if code == 0 { 219 t.Fatal("expected error") 220 } 221 222 if !strings.Contains(output.Stderr(), "lock") { 223 t.Fatal("command output does not look like a lock error:", output.Stderr()) 224 } 225 } 226 227 // test apply with locked state, waiting for unlock 228 func TestApply_lockedStateWait(t *testing.T) { 229 // Create a temporary working directory that is empty 230 td := t.TempDir() 231 testCopyDir(t, testFixturePath("apply"), td) 232 defer testChdir(t, td)() 233 234 statePath := testTempFile(t) 235 236 unlock, err := testLockState(t, testDataDir, statePath) 237 if err != nil { 238 t.Fatal(err) 239 } 240 241 // unlock during apply 242 go func() { 243 time.Sleep(500 * time.Millisecond) 244 unlock() 245 }() 246 247 p := applyFixtureProvider() 248 view, done := testView(t) 249 c := &ApplyCommand{ 250 Meta: Meta{ 251 testingOverrides: metaOverridesForProvider(p), 252 View: view, 253 }, 254 } 255 256 // wait 4s just in case the lock process doesn't release in under a second, 257 // and we want our context to be alive for a second retry at the 3s mark. 258 args := []string{ 259 "-state", statePath, 260 "-lock-timeout", "4s", 261 "-auto-approve", 262 } 263 code := c.Run(args) 264 output := done(t) 265 if code != 0 { 266 t.Fatalf("lock should have succeeded in less than 3s: %s", output.Stderr()) 267 } 268 } 269 270 // Verify that the parallelism flag allows no more than the desired number of 271 // concurrent calls to ApplyResourceChange. 272 func TestApply_parallelism(t *testing.T) { 273 // Create a temporary working directory that is empty 274 td := t.TempDir() 275 testCopyDir(t, testFixturePath("parallelism"), td) 276 defer testChdir(t, td)() 277 278 statePath := testTempFile(t) 279 280 par := 4 281 282 // started is a semaphore that we use to ensure that we never have more 283 // than "par" apply operations happening concurrently 284 started := make(chan struct{}, par) 285 286 // beginCtx is used as a starting gate to hold back ApplyResourceChange 287 // calls until we reach the desired concurrency. The cancel func "begin" is 288 // called once we reach the desired concurrency, allowing all apply calls 289 // to proceed in unison. 290 beginCtx, begin := context.WithCancel(context.Background()) 291 292 // Since our mock provider has its own mutex preventing concurrent calls 293 // to ApplyResourceChange, we need to use a number of separate providers 294 // here. They will all have the same mock implementation function assigned 295 // but crucially they will each have their own mutex. 296 providerFactories := map[addrs.Provider]providers.Factory{} 297 for i := 0; i < 10; i++ { 298 name := fmt.Sprintf("test%d", i) 299 provider := &terraform.MockProvider{} 300 provider.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 301 ResourceTypes: map[string]providers.Schema{ 302 name + "_instance": {Block: &configschema.Block{}}, 303 }, 304 } 305 provider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 306 return providers.PlanResourceChangeResponse{ 307 PlannedState: req.ProposedNewState, 308 } 309 } 310 provider.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 311 312 // If we ever have more than our intended parallelism number of 313 // apply operations running concurrently, the semaphore will fail. 314 select { 315 case started <- struct{}{}: 316 defer func() { 317 <-started 318 }() 319 default: 320 t.Fatal("too many concurrent apply operations") 321 } 322 323 // If we never reach our intended parallelism, the context will 324 // never be canceled and the test will time out. 325 if len(started) >= par { 326 begin() 327 } 328 <-beginCtx.Done() 329 330 // do some "work" 331 // Not required for correctness, but makes it easier to spot a 332 // failure when there is more overlap. 333 time.Sleep(10 * time.Millisecond) 334 335 return providers.ApplyResourceChangeResponse{ 336 NewState: cty.EmptyObjectVal, 337 } 338 } 339 providerFactories[addrs.NewDefaultProvider(name)] = providers.FactoryFixed(provider) 340 } 341 testingOverrides := &testingOverrides{ 342 Providers: providerFactories, 343 } 344 345 view, done := testView(t) 346 c := &ApplyCommand{ 347 Meta: Meta{ 348 testingOverrides: testingOverrides, 349 View: view, 350 }, 351 } 352 353 args := []string{ 354 "-state", statePath, 355 "-auto-approve", 356 fmt.Sprintf("-parallelism=%d", par), 357 } 358 359 res := c.Run(args) 360 output := done(t) 361 if res != 0 { 362 t.Fatal(output.Stdout()) 363 } 364 } 365 366 func TestApply_configInvalid(t *testing.T) { 367 // Create a temporary working directory that is empty 368 td := t.TempDir() 369 testCopyDir(t, testFixturePath("apply-config-invalid"), td) 370 defer testChdir(t, td)() 371 372 p := testProvider() 373 view, done := testView(t) 374 c := &ApplyCommand{ 375 Meta: Meta{ 376 testingOverrides: metaOverridesForProvider(p), 377 View: view, 378 }, 379 } 380 381 args := []string{ 382 "-state", testTempFile(t), 383 "-auto-approve", 384 } 385 code := c.Run(args) 386 output := done(t) 387 if code != 1 { 388 t.Fatalf("bad: \n%s", output.Stdout()) 389 } 390 } 391 392 func TestApply_defaultState(t *testing.T) { 393 // Create a temporary working directory that is empty 394 td := t.TempDir() 395 testCopyDir(t, testFixturePath("apply"), td) 396 defer testChdir(t, td)() 397 398 statePath := filepath.Join(td, DefaultStateFilename) 399 400 // Change to the temporary directory 401 cwd, err := os.Getwd() 402 if err != nil { 403 t.Fatalf("err: %s", err) 404 } 405 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 406 t.Fatalf("err: %s", err) 407 } 408 defer os.Chdir(cwd) 409 410 p := applyFixtureProvider() 411 view, done := testView(t) 412 c := &ApplyCommand{ 413 Meta: Meta{ 414 testingOverrides: metaOverridesForProvider(p), 415 View: view, 416 }, 417 } 418 419 // create an existing state file 420 localState := statemgr.NewFilesystem(statePath) 421 if err := localState.WriteState(states.NewState()); err != nil { 422 t.Fatal(err) 423 } 424 425 args := []string{ 426 "-auto-approve", 427 } 428 code := c.Run(args) 429 output := done(t) 430 if code != 0 { 431 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 432 } 433 434 if _, err := os.Stat(statePath); err != nil { 435 t.Fatalf("err: %s", err) 436 } 437 438 state := testStateRead(t, statePath) 439 if state == nil { 440 t.Fatal("state should not be nil") 441 } 442 } 443 444 func TestApply_error(t *testing.T) { 445 // Create a temporary working directory that is empty 446 td := t.TempDir() 447 testCopyDir(t, testFixturePath("apply-error"), td) 448 defer testChdir(t, td)() 449 450 statePath := testTempFile(t) 451 452 p := testProvider() 453 view, done := testView(t) 454 c := &ApplyCommand{ 455 Meta: Meta{ 456 testingOverrides: metaOverridesForProvider(p), 457 View: view, 458 }, 459 } 460 461 var lock sync.Mutex 462 errored := false 463 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 464 lock.Lock() 465 defer lock.Unlock() 466 467 if !errored { 468 errored = true 469 resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("error")) 470 } 471 472 s := req.PlannedState.AsValueMap() 473 s["id"] = cty.StringVal("foo") 474 475 resp.NewState = cty.ObjectVal(s) 476 return 477 } 478 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 479 s := req.ProposedNewState.AsValueMap() 480 s["id"] = cty.UnknownVal(cty.String) 481 resp.PlannedState = cty.ObjectVal(s) 482 return 483 } 484 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 485 ResourceTypes: map[string]providers.Schema{ 486 "test_instance": { 487 Block: &configschema.Block{ 488 Attributes: map[string]*configschema.Attribute{ 489 "id": {Type: cty.String, Optional: true, Computed: true}, 490 "ami": {Type: cty.String, Optional: true}, 491 "error": {Type: cty.Bool, Optional: true}, 492 }, 493 }, 494 }, 495 }, 496 } 497 498 args := []string{ 499 "-state", statePath, 500 "-auto-approve", 501 } 502 code := c.Run(args) 503 output := done(t) 504 if code != 1 { 505 t.Fatalf("wrong exit code %d; want 1\n%s", code, output.Stdout()) 506 } 507 508 if _, err := os.Stat(statePath); err != nil { 509 t.Fatalf("err: %s", err) 510 } 511 512 state := testStateRead(t, statePath) 513 if state == nil { 514 t.Fatal("state should not be nil") 515 } 516 if len(state.RootModule().Resources) == 0 { 517 t.Fatal("no resources in state") 518 } 519 } 520 521 func TestApply_input(t *testing.T) { 522 // Create a temporary working directory that is empty 523 td := t.TempDir() 524 testCopyDir(t, testFixturePath("apply-input"), td) 525 defer testChdir(t, td)() 526 527 // Disable test mode so input would be asked 528 test = false 529 defer func() { test = true }() 530 531 // The configuration for this test includes a declaration of variable 532 // "foo" with no default, and we don't set it on the command line below, 533 // so the apply command will produce an interactive prompt for the 534 // value of var.foo. We'll answer "foo" here, and we expect the output 535 // value "result" to echo that back to us below. 536 defaultInputReader = bytes.NewBufferString("foo\n") 537 defaultInputWriter = new(bytes.Buffer) 538 539 statePath := testTempFile(t) 540 541 p := testProvider() 542 view, done := testView(t) 543 c := &ApplyCommand{ 544 Meta: Meta{ 545 testingOverrides: metaOverridesForProvider(p), 546 View: view, 547 }, 548 } 549 550 args := []string{ 551 "-state", statePath, 552 "-auto-approve", 553 } 554 code := c.Run(args) 555 output := done(t) 556 if code != 0 { 557 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 558 } 559 560 expected := strings.TrimSpace(` 561 <no state> 562 Outputs: 563 564 result = foo 565 `) 566 testStateOutput(t, statePath, expected) 567 } 568 569 // When only a partial set of the variables are set, Terraform 570 // should still ask for the unset ones by default (with -input=true) 571 func TestApply_inputPartial(t *testing.T) { 572 // Create a temporary working directory that is empty 573 td := t.TempDir() 574 testCopyDir(t, testFixturePath("apply-input-partial"), td) 575 defer testChdir(t, td)() 576 577 // Disable test mode so input would be asked 578 test = false 579 defer func() { test = true }() 580 581 // Set some default reader/writers for the inputs 582 defaultInputReader = bytes.NewBufferString("one\ntwo\n") 583 defaultInputWriter = new(bytes.Buffer) 584 585 statePath := testTempFile(t) 586 587 p := testProvider() 588 view, done := testView(t) 589 c := &ApplyCommand{ 590 Meta: Meta{ 591 testingOverrides: metaOverridesForProvider(p), 592 View: view, 593 }, 594 } 595 596 args := []string{ 597 "-state", statePath, 598 "-auto-approve", 599 "-var", "foo=foovalue", 600 } 601 code := c.Run(args) 602 output := done(t) 603 if code != 0 { 604 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 605 } 606 607 expected := strings.TrimSpace(` 608 <no state> 609 Outputs: 610 611 bar = one 612 foo = foovalue 613 `) 614 testStateOutput(t, statePath, expected) 615 } 616 617 func TestApply_noArgs(t *testing.T) { 618 // Create a temporary working directory that is empty 619 td := t.TempDir() 620 testCopyDir(t, testFixturePath("apply"), td) 621 defer testChdir(t, td)() 622 623 statePath := testTempFile(t) 624 625 p := applyFixtureProvider() 626 view, done := testView(t) 627 c := &ApplyCommand{ 628 Meta: Meta{ 629 testingOverrides: metaOverridesForProvider(p), 630 View: view, 631 }, 632 } 633 634 args := []string{ 635 "-state", statePath, 636 "-auto-approve", 637 } 638 code := c.Run(args) 639 output := done(t) 640 if code != 0 { 641 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 642 } 643 644 if _, err := os.Stat(statePath); err != nil { 645 t.Fatalf("err: %s", err) 646 } 647 648 state := testStateRead(t, statePath) 649 if state == nil { 650 t.Fatal("state should not be nil") 651 } 652 } 653 654 func TestApply_plan(t *testing.T) { 655 // Disable test mode so input would be asked 656 test = false 657 defer func() { test = true }() 658 659 // Set some default reader/writers for the inputs 660 defaultInputReader = new(bytes.Buffer) 661 defaultInputWriter = new(bytes.Buffer) 662 663 planPath := applyFixturePlanFile(t) 664 statePath := testTempFile(t) 665 666 p := applyFixtureProvider() 667 view, done := testView(t) 668 c := &ApplyCommand{ 669 Meta: Meta{ 670 testingOverrides: metaOverridesForProvider(p), 671 View: view, 672 }, 673 } 674 675 args := []string{ 676 "-state-out", statePath, 677 planPath, 678 } 679 code := c.Run(args) 680 output := done(t) 681 if code != 0 { 682 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 683 } 684 685 if _, err := os.Stat(statePath); err != nil { 686 t.Fatalf("err: %s", err) 687 } 688 689 state := testStateRead(t, statePath) 690 if state == nil { 691 t.Fatal("state should not be nil") 692 } 693 } 694 695 func TestApply_plan_backup(t *testing.T) { 696 statePath := testTempFile(t) 697 backupPath := testTempFile(t) 698 699 p := applyFixtureProvider() 700 view, done := testView(t) 701 c := &ApplyCommand{ 702 Meta: Meta{ 703 testingOverrides: metaOverridesForProvider(p), 704 View: view, 705 }, 706 } 707 708 // create a state file that needs to be backed up 709 fs := statemgr.NewFilesystem(statePath) 710 fs.StateSnapshotMeta() 711 err := fs.WriteState(states.NewState()) 712 if err != nil { 713 t.Fatal(err) 714 } 715 716 // the plan file must contain the metadata from the prior state to be 717 // backed up 718 planPath := applyFixturePlanFileMatchState(t, fs.StateSnapshotMeta()) 719 720 args := []string{ 721 "-state", statePath, 722 "-backup", backupPath, 723 planPath, 724 } 725 code := c.Run(args) 726 output := done(t) 727 if code != 0 { 728 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 729 } 730 731 // Should have a backup file 732 testStateRead(t, backupPath) 733 } 734 735 func TestApply_plan_noBackup(t *testing.T) { 736 planPath := applyFixturePlanFile(t) 737 statePath := testTempFile(t) 738 739 p := applyFixtureProvider() 740 view, done := testView(t) 741 c := &ApplyCommand{ 742 Meta: Meta{ 743 testingOverrides: metaOverridesForProvider(p), 744 View: view, 745 }, 746 } 747 748 args := []string{ 749 "-state-out", statePath, 750 "-backup", "-", 751 planPath, 752 } 753 code := c.Run(args) 754 output := done(t) 755 if code != 0 { 756 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 757 } 758 759 // Ensure there is no backup 760 _, err := os.Stat(statePath + DefaultBackupExtension) 761 if err == nil || !os.IsNotExist(err) { 762 t.Fatalf("backup should not exist") 763 } 764 765 // Ensure there is no literal "-" 766 _, err = os.Stat("-") 767 if err == nil || !os.IsNotExist(err) { 768 t.Fatalf("backup should not exist") 769 } 770 } 771 772 func TestApply_plan_remoteState(t *testing.T) { 773 // Disable test mode so input would be asked 774 test = false 775 defer func() { test = true }() 776 tmp := testCwd(t) 777 remoteStatePath := filepath.Join(tmp, DefaultDataDir, DefaultStateFilename) 778 if err := os.MkdirAll(filepath.Dir(remoteStatePath), 0755); err != nil { 779 t.Fatalf("err: %s", err) 780 } 781 782 // Set some default reader/writers for the inputs 783 defaultInputReader = new(bytes.Buffer) 784 defaultInputWriter = new(bytes.Buffer) 785 786 // Create a remote state 787 state := testState() 788 _, srv := testRemoteState(t, state, 200) 789 defer srv.Close() 790 791 _, snap := testModuleWithSnapshot(t, "apply") 792 backendConfig := cty.ObjectVal(map[string]cty.Value{ 793 "address": cty.StringVal(srv.URL), 794 "update_method": cty.NullVal(cty.String), 795 "lock_address": cty.NullVal(cty.String), 796 "unlock_address": cty.NullVal(cty.String), 797 "lock_method": cty.NullVal(cty.String), 798 "unlock_method": cty.NullVal(cty.String), 799 "username": cty.NullVal(cty.String), 800 "password": cty.NullVal(cty.String), 801 "skip_cert_verification": cty.NullVal(cty.Bool), 802 "retry_max": cty.NullVal(cty.String), 803 "retry_wait_min": cty.NullVal(cty.String), 804 "retry_wait_max": cty.NullVal(cty.String), 805 "client_ca_certificate_pem": cty.NullVal(cty.String), 806 "client_certificate_pem": cty.NullVal(cty.String), 807 "client_private_key_pem": cty.NullVal(cty.String), 808 }) 809 backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type()) 810 if err != nil { 811 t.Fatal(err) 812 } 813 planPath := testPlanFile(t, snap, state, &plans.Plan{ 814 Backend: plans.Backend{ 815 Type: "http", 816 Config: backendConfigRaw, 817 }, 818 Changes: plans.NewChanges(), 819 }) 820 821 p := testProvider() 822 view, done := testView(t) 823 c := &ApplyCommand{ 824 Meta: Meta{ 825 testingOverrides: metaOverridesForProvider(p), 826 View: view, 827 }, 828 } 829 830 args := []string{ 831 planPath, 832 } 833 code := c.Run(args) 834 output := done(t) 835 if code != 0 { 836 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 837 } 838 839 // State file should be not be installed 840 if _, err := os.Stat(filepath.Join(tmp, DefaultStateFilename)); err == nil { 841 data, _ := ioutil.ReadFile(DefaultStateFilename) 842 t.Fatalf("State path should not exist: %s", string(data)) 843 } 844 845 // Check that there is no remote state config 846 if src, err := ioutil.ReadFile(remoteStatePath); err == nil { 847 t.Fatalf("has %s file; should not\n%s", remoteStatePath, src) 848 } 849 } 850 851 func TestApply_planWithVarFile(t *testing.T) { 852 varFileDir := testTempDir(t) 853 varFilePath := filepath.Join(varFileDir, "terraform.tfvars") 854 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 855 t.Fatalf("err: %s", err) 856 } 857 858 planPath := applyFixturePlanFile(t) 859 statePath := testTempFile(t) 860 861 cwd, err := os.Getwd() 862 if err != nil { 863 t.Fatalf("err: %s", err) 864 } 865 if err := os.Chdir(varFileDir); err != nil { 866 t.Fatalf("err: %s", err) 867 } 868 defer os.Chdir(cwd) 869 870 p := applyFixtureProvider() 871 view, done := testView(t) 872 c := &ApplyCommand{ 873 Meta: Meta{ 874 testingOverrides: metaOverridesForProvider(p), 875 View: view, 876 }, 877 } 878 879 args := []string{ 880 "-state-out", statePath, 881 planPath, 882 } 883 code := c.Run(args) 884 output := done(t) 885 if code != 0 { 886 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 887 } 888 889 if _, err := os.Stat(statePath); err != nil { 890 t.Fatalf("err: %s", err) 891 } 892 893 state := testStateRead(t, statePath) 894 if state == nil { 895 t.Fatal("state should not be nil") 896 } 897 } 898 899 func TestApply_planVars(t *testing.T) { 900 planPath := applyFixturePlanFile(t) 901 statePath := testTempFile(t) 902 903 p := applyFixtureProvider() 904 view, done := testView(t) 905 c := &ApplyCommand{ 906 Meta: Meta{ 907 testingOverrides: metaOverridesForProvider(p), 908 View: view, 909 }, 910 } 911 912 args := []string{ 913 "-state", statePath, 914 "-var", "foo=bar", 915 planPath, 916 } 917 code := c.Run(args) 918 output := done(t) 919 if code == 0 { 920 t.Fatal("should've failed: ", output.Stdout()) 921 } 922 } 923 924 // we should be able to apply a plan file with no other file dependencies 925 func TestApply_planNoModuleFiles(t *testing.T) { 926 // temporary data directory which we can remove between commands 927 td := testTempDir(t) 928 defer os.RemoveAll(td) 929 930 defer testChdir(t, td)() 931 932 p := applyFixtureProvider() 933 planPath := applyFixturePlanFile(t) 934 view, done := testView(t) 935 apply := &ApplyCommand{ 936 Meta: Meta{ 937 testingOverrides: metaOverridesForProvider(p), 938 Ui: new(cli.MockUi), 939 View: view, 940 }, 941 } 942 args := []string{ 943 planPath, 944 } 945 apply.Run(args) 946 done(t) 947 } 948 949 func TestApply_refresh(t *testing.T) { 950 // Create a temporary working directory that is empty 951 td := t.TempDir() 952 testCopyDir(t, testFixturePath("apply"), td) 953 defer testChdir(t, td)() 954 955 originalState := states.BuildState(func(s *states.SyncState) { 956 s.SetResourceInstanceCurrent( 957 addrs.Resource{ 958 Mode: addrs.ManagedResourceMode, 959 Type: "test_instance", 960 Name: "foo", 961 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 962 &states.ResourceInstanceObjectSrc{ 963 AttrsJSON: []byte(`{"ami":"bar"}`), 964 Status: states.ObjectReady, 965 }, 966 addrs.AbsProviderConfig{ 967 Provider: addrs.NewDefaultProvider("test"), 968 Module: addrs.RootModule, 969 }, 970 ) 971 }) 972 statePath := testStateFile(t, originalState) 973 974 p := applyFixtureProvider() 975 view, done := testView(t) 976 c := &ApplyCommand{ 977 Meta: Meta{ 978 testingOverrides: metaOverridesForProvider(p), 979 View: view, 980 }, 981 } 982 983 args := []string{ 984 "-state", statePath, 985 "-auto-approve", 986 } 987 code := c.Run(args) 988 output := done(t) 989 if code != 0 { 990 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 991 } 992 993 if !p.ReadResourceCalled { 994 t.Fatal("should call ReadResource") 995 } 996 997 if _, err := os.Stat(statePath); err != nil { 998 t.Fatalf("err: %s", err) 999 } 1000 1001 state := testStateRead(t, statePath) 1002 if state == nil { 1003 t.Fatal("state should not be nil") 1004 } 1005 1006 // Should have a backup file 1007 backupState := testStateRead(t, statePath+DefaultBackupExtension) 1008 1009 actualStr := strings.TrimSpace(backupState.String()) 1010 expectedStr := strings.TrimSpace(originalState.String()) 1011 if actualStr != expectedStr { 1012 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 1013 } 1014 } 1015 1016 func TestApply_refreshFalse(t *testing.T) { 1017 // Create a temporary working directory that is empty 1018 td := t.TempDir() 1019 testCopyDir(t, testFixturePath("apply"), td) 1020 defer testChdir(t, td)() 1021 1022 originalState := states.BuildState(func(s *states.SyncState) { 1023 s.SetResourceInstanceCurrent( 1024 addrs.Resource{ 1025 Mode: addrs.ManagedResourceMode, 1026 Type: "test_instance", 1027 Name: "foo", 1028 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1029 &states.ResourceInstanceObjectSrc{ 1030 AttrsJSON: []byte(`{"ami":"bar"}`), 1031 Status: states.ObjectReady, 1032 }, 1033 addrs.AbsProviderConfig{ 1034 Provider: addrs.NewDefaultProvider("test"), 1035 Module: addrs.RootModule, 1036 }, 1037 ) 1038 }) 1039 statePath := testStateFile(t, originalState) 1040 1041 p := applyFixtureProvider() 1042 view, done := testView(t) 1043 c := &ApplyCommand{ 1044 Meta: Meta{ 1045 testingOverrides: metaOverridesForProvider(p), 1046 View: view, 1047 }, 1048 } 1049 1050 args := []string{ 1051 "-state", statePath, 1052 "-auto-approve", 1053 "-refresh=false", 1054 } 1055 code := c.Run(args) 1056 output := done(t) 1057 if code != 0 { 1058 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1059 } 1060 1061 if p.ReadResourceCalled { 1062 t.Fatal("should not call ReadResource when refresh=false") 1063 } 1064 } 1065 func TestApply_shutdown(t *testing.T) { 1066 // Create a temporary working directory that is empty 1067 td := t.TempDir() 1068 testCopyDir(t, testFixturePath("apply-shutdown"), td) 1069 defer testChdir(t, td)() 1070 1071 cancelled := make(chan struct{}) 1072 shutdownCh := make(chan struct{}) 1073 1074 statePath := testTempFile(t) 1075 p := testProvider() 1076 1077 view, done := testView(t) 1078 c := &ApplyCommand{ 1079 Meta: Meta{ 1080 testingOverrides: metaOverridesForProvider(p), 1081 View: view, 1082 ShutdownCh: shutdownCh, 1083 }, 1084 } 1085 1086 p.StopFn = func() error { 1087 close(cancelled) 1088 return nil 1089 } 1090 1091 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { 1092 resp.PlannedState = req.ProposedNewState 1093 return 1094 } 1095 1096 var once sync.Once 1097 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) { 1098 // only cancel once 1099 once.Do(func() { 1100 shutdownCh <- struct{}{} 1101 }) 1102 1103 // Because of the internal lock in the MockProvider, we can't 1104 // coordiante directly with the calling of Stop, and making the 1105 // MockProvider concurrent is disruptive to a lot of existing tests. 1106 // Wait here a moment to help make sure the main goroutine gets to the 1107 // Stop call before we exit, or the plan may finish before it can be 1108 // canceled. 1109 time.Sleep(200 * time.Millisecond) 1110 1111 resp.NewState = req.PlannedState 1112 return 1113 } 1114 1115 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1116 ResourceTypes: map[string]providers.Schema{ 1117 "test_instance": { 1118 Block: &configschema.Block{ 1119 Attributes: map[string]*configschema.Attribute{ 1120 "ami": {Type: cty.String, Optional: true}, 1121 }, 1122 }, 1123 }, 1124 }, 1125 } 1126 1127 args := []string{ 1128 "-state", statePath, 1129 "-auto-approve", 1130 } 1131 code := c.Run(args) 1132 output := done(t) 1133 if code != 1 { 1134 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1135 } 1136 1137 if _, err := os.Stat(statePath); err != nil { 1138 t.Fatalf("err: %s", err) 1139 } 1140 1141 select { 1142 case <-cancelled: 1143 default: 1144 t.Fatal("command not cancelled") 1145 } 1146 1147 state := testStateRead(t, statePath) 1148 if state == nil { 1149 t.Fatal("state should not be nil") 1150 } 1151 } 1152 1153 func TestApply_state(t *testing.T) { 1154 // Create a temporary working directory that is empty 1155 td := t.TempDir() 1156 testCopyDir(t, testFixturePath("apply"), td) 1157 defer testChdir(t, td)() 1158 1159 originalState := states.BuildState(func(s *states.SyncState) { 1160 s.SetResourceInstanceCurrent( 1161 addrs.Resource{ 1162 Mode: addrs.ManagedResourceMode, 1163 Type: "test_instance", 1164 Name: "foo", 1165 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1166 &states.ResourceInstanceObjectSrc{ 1167 AttrsJSON: []byte(`{"ami":"foo"}`), 1168 Status: states.ObjectReady, 1169 }, 1170 addrs.AbsProviderConfig{ 1171 Provider: addrs.NewDefaultProvider("test"), 1172 Module: addrs.RootModule, 1173 }, 1174 ) 1175 }) 1176 statePath := testStateFile(t, originalState) 1177 1178 p := applyFixtureProvider() 1179 p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{ 1180 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1181 "ami": cty.StringVal("bar"), 1182 }), 1183 } 1184 p.ApplyResourceChangeResponse = &providers.ApplyResourceChangeResponse{ 1185 NewState: cty.ObjectVal(map[string]cty.Value{ 1186 "ami": cty.StringVal("bar"), 1187 }), 1188 } 1189 1190 view, done := testView(t) 1191 c := &ApplyCommand{ 1192 Meta: Meta{ 1193 testingOverrides: metaOverridesForProvider(p), 1194 View: view, 1195 }, 1196 } 1197 1198 // Run the apply command pointing to our existing state 1199 args := []string{ 1200 "-state", statePath, 1201 "-auto-approve", 1202 } 1203 code := c.Run(args) 1204 output := done(t) 1205 if code != 0 { 1206 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1207 } 1208 1209 // Verify that the provider was called with the existing state 1210 actual := p.PlanResourceChangeRequest.PriorState 1211 expected := cty.ObjectVal(map[string]cty.Value{ 1212 "id": cty.NullVal(cty.String), 1213 "ami": cty.StringVal("foo"), 1214 }) 1215 if !expected.RawEquals(actual) { 1216 t.Fatalf("wrong prior state during plan\ngot: %#v\nwant: %#v", actual, expected) 1217 } 1218 1219 actual = p.ApplyResourceChangeRequest.PriorState 1220 expected = cty.ObjectVal(map[string]cty.Value{ 1221 "id": cty.NullVal(cty.String), 1222 "ami": cty.StringVal("foo"), 1223 }) 1224 if !expected.RawEquals(actual) { 1225 t.Fatalf("wrong prior state during apply\ngot: %#v\nwant: %#v", actual, expected) 1226 } 1227 1228 // Verify a new state exists 1229 if _, err := os.Stat(statePath); err != nil { 1230 t.Fatalf("err: %s", err) 1231 } 1232 1233 state := testStateRead(t, statePath) 1234 if state == nil { 1235 t.Fatal("state should not be nil") 1236 } 1237 1238 backupState := testStateRead(t, statePath+DefaultBackupExtension) 1239 1240 actualStr := strings.TrimSpace(backupState.String()) 1241 expectedStr := strings.TrimSpace(originalState.String()) 1242 if actualStr != expectedStr { 1243 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 1244 } 1245 } 1246 1247 func TestApply_stateNoExist(t *testing.T) { 1248 // Create a temporary working directory that is empty 1249 td := t.TempDir() 1250 testCopyDir(t, testFixturePath("apply"), td) 1251 defer testChdir(t, td)() 1252 1253 p := applyFixtureProvider() 1254 view, done := testView(t) 1255 c := &ApplyCommand{ 1256 Meta: Meta{ 1257 testingOverrides: metaOverridesForProvider(p), 1258 View: view, 1259 }, 1260 } 1261 1262 args := []string{ 1263 "idontexist.tfstate", 1264 } 1265 code := c.Run(args) 1266 output := done(t) 1267 if code != 1 { 1268 t.Fatalf("bad: \n%s", output.Stdout()) 1269 } 1270 } 1271 1272 func TestApply_sensitiveOutput(t *testing.T) { 1273 // Create a temporary working directory that is empty 1274 td := t.TempDir() 1275 testCopyDir(t, testFixturePath("apply-sensitive-output"), td) 1276 defer testChdir(t, td)() 1277 1278 p := testProvider() 1279 view, done := testView(t) 1280 c := &ApplyCommand{ 1281 Meta: Meta{ 1282 testingOverrides: metaOverridesForProvider(p), 1283 View: view, 1284 }, 1285 } 1286 1287 statePath := testTempFile(t) 1288 1289 args := []string{ 1290 "-state", statePath, 1291 "-auto-approve", 1292 } 1293 1294 code := c.Run(args) 1295 output := done(t) 1296 if code != 0 { 1297 t.Fatalf("bad: \n%s", output.Stdout()) 1298 } 1299 1300 stdout := output.Stdout() 1301 if !strings.Contains(stdout, "notsensitive = \"Hello world\"") { 1302 t.Fatalf("bad: output should contain 'notsensitive' output\n%s", stdout) 1303 } 1304 if !strings.Contains(stdout, "sensitive = <sensitive>") { 1305 t.Fatalf("bad: output should contain 'sensitive' output\n%s", stdout) 1306 } 1307 } 1308 1309 func TestApply_vars(t *testing.T) { 1310 // Create a temporary working directory that is empty 1311 td := t.TempDir() 1312 testCopyDir(t, testFixturePath("apply-vars"), td) 1313 defer testChdir(t, td)() 1314 1315 statePath := testTempFile(t) 1316 1317 p := testProvider() 1318 view, done := testView(t) 1319 c := &ApplyCommand{ 1320 Meta: Meta{ 1321 testingOverrides: metaOverridesForProvider(p), 1322 View: view, 1323 }, 1324 } 1325 1326 actual := "" 1327 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1328 ResourceTypes: map[string]providers.Schema{ 1329 "test_instance": { 1330 Block: &configschema.Block{ 1331 Attributes: map[string]*configschema.Attribute{ 1332 "value": {Type: cty.String, Optional: true}, 1333 }, 1334 }, 1335 }, 1336 }, 1337 } 1338 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1339 return providers.ApplyResourceChangeResponse{ 1340 NewState: req.PlannedState, 1341 } 1342 } 1343 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1344 actual = req.ProposedNewState.GetAttr("value").AsString() 1345 return providers.PlanResourceChangeResponse{ 1346 PlannedState: req.ProposedNewState, 1347 } 1348 } 1349 1350 args := []string{ 1351 "-auto-approve", 1352 "-var", "foo=bar", 1353 "-state", statePath, 1354 } 1355 code := c.Run(args) 1356 output := done(t) 1357 if code != 0 { 1358 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1359 } 1360 1361 if actual != "bar" { 1362 t.Fatal("didn't work") 1363 } 1364 } 1365 1366 func TestApply_varFile(t *testing.T) { 1367 // Create a temporary working directory that is empty 1368 td := t.TempDir() 1369 testCopyDir(t, testFixturePath("apply-vars"), td) 1370 defer testChdir(t, td)() 1371 1372 varFilePath := testTempFile(t) 1373 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1374 t.Fatalf("err: %s", err) 1375 } 1376 1377 statePath := testTempFile(t) 1378 1379 p := testProvider() 1380 view, done := testView(t) 1381 c := &ApplyCommand{ 1382 Meta: Meta{ 1383 testingOverrides: metaOverridesForProvider(p), 1384 View: view, 1385 }, 1386 } 1387 1388 actual := "" 1389 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1390 ResourceTypes: map[string]providers.Schema{ 1391 "test_instance": { 1392 Block: &configschema.Block{ 1393 Attributes: map[string]*configschema.Attribute{ 1394 "value": {Type: cty.String, Optional: true}, 1395 }, 1396 }, 1397 }, 1398 }, 1399 } 1400 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1401 return providers.ApplyResourceChangeResponse{ 1402 NewState: req.PlannedState, 1403 } 1404 } 1405 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1406 actual = req.ProposedNewState.GetAttr("value").AsString() 1407 return providers.PlanResourceChangeResponse{ 1408 PlannedState: req.ProposedNewState, 1409 } 1410 } 1411 1412 args := []string{ 1413 "-auto-approve", 1414 "-var-file", varFilePath, 1415 "-state", statePath, 1416 } 1417 code := c.Run(args) 1418 output := done(t) 1419 if code != 0 { 1420 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1421 } 1422 1423 if actual != "bar" { 1424 t.Fatal("didn't work") 1425 } 1426 } 1427 1428 func TestApply_varFileDefault(t *testing.T) { 1429 // Create a temporary working directory that is empty 1430 td := t.TempDir() 1431 testCopyDir(t, testFixturePath("apply-vars"), td) 1432 defer testChdir(t, td)() 1433 1434 varFilePath := filepath.Join(td, "terraform.tfvars") 1435 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFile), 0644); err != nil { 1436 t.Fatalf("err: %s", err) 1437 } 1438 1439 statePath := testTempFile(t) 1440 1441 p := testProvider() 1442 view, done := testView(t) 1443 c := &ApplyCommand{ 1444 Meta: Meta{ 1445 testingOverrides: metaOverridesForProvider(p), 1446 View: view, 1447 }, 1448 } 1449 1450 actual := "" 1451 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1452 ResourceTypes: map[string]providers.Schema{ 1453 "test_instance": { 1454 Block: &configschema.Block{ 1455 Attributes: map[string]*configschema.Attribute{ 1456 "value": {Type: cty.String, Optional: true}, 1457 }, 1458 }, 1459 }, 1460 }, 1461 } 1462 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1463 return providers.ApplyResourceChangeResponse{ 1464 NewState: req.PlannedState, 1465 } 1466 } 1467 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1468 actual = req.ProposedNewState.GetAttr("value").AsString() 1469 return providers.PlanResourceChangeResponse{ 1470 PlannedState: req.ProposedNewState, 1471 } 1472 } 1473 1474 args := []string{ 1475 "-auto-approve", 1476 "-state", statePath, 1477 } 1478 code := c.Run(args) 1479 output := done(t) 1480 if code != 0 { 1481 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1482 } 1483 1484 if actual != "bar" { 1485 t.Fatal("didn't work") 1486 } 1487 } 1488 1489 func TestApply_varFileDefaultJSON(t *testing.T) { 1490 // Create a temporary working directory that is empty 1491 td := t.TempDir() 1492 testCopyDir(t, testFixturePath("apply-vars"), td) 1493 defer testChdir(t, td)() 1494 1495 varFilePath := filepath.Join(td, "terraform.tfvars.json") 1496 if err := ioutil.WriteFile(varFilePath, []byte(applyVarFileJSON), 0644); err != nil { 1497 t.Fatalf("err: %s", err) 1498 } 1499 1500 statePath := testTempFile(t) 1501 1502 p := testProvider() 1503 view, done := testView(t) 1504 c := &ApplyCommand{ 1505 Meta: Meta{ 1506 testingOverrides: metaOverridesForProvider(p), 1507 View: view, 1508 }, 1509 } 1510 1511 actual := "" 1512 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1513 ResourceTypes: map[string]providers.Schema{ 1514 "test_instance": { 1515 Block: &configschema.Block{ 1516 Attributes: map[string]*configschema.Attribute{ 1517 "value": {Type: cty.String, Optional: true}, 1518 }, 1519 }, 1520 }, 1521 }, 1522 } 1523 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1524 return providers.ApplyResourceChangeResponse{ 1525 NewState: req.PlannedState, 1526 } 1527 } 1528 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1529 actual = req.ProposedNewState.GetAttr("value").AsString() 1530 return providers.PlanResourceChangeResponse{ 1531 PlannedState: req.ProposedNewState, 1532 } 1533 } 1534 1535 args := []string{ 1536 "-auto-approve", 1537 "-state", statePath, 1538 } 1539 code := c.Run(args) 1540 output := done(t) 1541 if code != 0 { 1542 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1543 } 1544 1545 if actual != "bar" { 1546 t.Fatal("didn't work") 1547 } 1548 } 1549 1550 func TestApply_backup(t *testing.T) { 1551 // Create a temporary working directory that is empty 1552 td := t.TempDir() 1553 testCopyDir(t, testFixturePath("apply"), td) 1554 defer testChdir(t, td)() 1555 1556 originalState := states.BuildState(func(s *states.SyncState) { 1557 s.SetResourceInstanceCurrent( 1558 addrs.Resource{ 1559 Mode: addrs.ManagedResourceMode, 1560 Type: "test_instance", 1561 Name: "foo", 1562 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1563 &states.ResourceInstanceObjectSrc{ 1564 AttrsJSON: []byte("{\n \"id\": \"bar\"\n }"), 1565 Status: states.ObjectReady, 1566 }, 1567 addrs.AbsProviderConfig{ 1568 Provider: addrs.NewDefaultProvider("test"), 1569 Module: addrs.RootModule, 1570 }, 1571 ) 1572 }) 1573 statePath := testStateFile(t, originalState) 1574 backupPath := testTempFile(t) 1575 1576 p := applyFixtureProvider() 1577 p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{ 1578 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1579 "ami": cty.StringVal("bar"), 1580 }), 1581 } 1582 1583 view, done := testView(t) 1584 c := &ApplyCommand{ 1585 Meta: Meta{ 1586 testingOverrides: metaOverridesForProvider(p), 1587 View: view, 1588 }, 1589 } 1590 1591 // Run the apply command pointing to our existing state 1592 args := []string{ 1593 "-auto-approve", 1594 "-state", statePath, 1595 "-backup", backupPath, 1596 } 1597 code := c.Run(args) 1598 output := done(t) 1599 if code != 0 { 1600 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1601 } 1602 1603 // Verify a new state exists 1604 if _, err := os.Stat(statePath); err != nil { 1605 t.Fatalf("err: %s", err) 1606 } 1607 1608 state := testStateRead(t, statePath) 1609 if state == nil { 1610 t.Fatal("state should not be nil") 1611 } 1612 1613 backupState := testStateRead(t, backupPath) 1614 1615 actual := backupState.RootModule().Resources["test_instance.foo"] 1616 expected := originalState.RootModule().Resources["test_instance.foo"] 1617 if !cmp.Equal(actual, expected, cmpopts.EquateEmpty()) { 1618 t.Fatalf( 1619 "wrong aws_instance.foo state\n%s", 1620 cmp.Diff(expected, actual, cmp.Transformer("bytesAsString", func(b []byte) string { 1621 return string(b) 1622 })), 1623 ) 1624 } 1625 } 1626 1627 func TestApply_disableBackup(t *testing.T) { 1628 // Create a temporary working directory that is empty 1629 td := t.TempDir() 1630 testCopyDir(t, testFixturePath("apply"), td) 1631 defer testChdir(t, td)() 1632 1633 originalState := testState() 1634 statePath := testStateFile(t, originalState) 1635 1636 p := applyFixtureProvider() 1637 p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{ 1638 PlannedState: cty.ObjectVal(map[string]cty.Value{ 1639 "ami": cty.StringVal("bar"), 1640 }), 1641 } 1642 1643 view, done := testView(t) 1644 c := &ApplyCommand{ 1645 Meta: Meta{ 1646 testingOverrides: metaOverridesForProvider(p), 1647 View: view, 1648 }, 1649 } 1650 1651 // Run the apply command pointing to our existing state 1652 args := []string{ 1653 "-auto-approve", 1654 "-state", statePath, 1655 "-backup", "-", 1656 } 1657 code := c.Run(args) 1658 output := done(t) 1659 if code != 0 { 1660 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1661 } 1662 1663 // Verify that the provider was called with the existing state 1664 actual := p.PlanResourceChangeRequest.PriorState 1665 expected := cty.ObjectVal(map[string]cty.Value{ 1666 "id": cty.StringVal("bar"), 1667 "ami": cty.NullVal(cty.String), 1668 }) 1669 if !expected.RawEquals(actual) { 1670 t.Fatalf("wrong prior state during plan\ngot: %#v\nwant: %#v", actual, expected) 1671 } 1672 1673 actual = p.ApplyResourceChangeRequest.PriorState 1674 expected = cty.ObjectVal(map[string]cty.Value{ 1675 "id": cty.StringVal("bar"), 1676 "ami": cty.NullVal(cty.String), 1677 }) 1678 if !expected.RawEquals(actual) { 1679 t.Fatalf("wrong prior state during apply\ngot: %#v\nwant: %#v", actual, expected) 1680 } 1681 1682 // Verify a new state exists 1683 if _, err := os.Stat(statePath); err != nil { 1684 t.Fatalf("err: %s", err) 1685 } 1686 1687 state := testStateRead(t, statePath) 1688 if state == nil { 1689 t.Fatal("state should not be nil") 1690 } 1691 1692 // Ensure there is no backup 1693 _, err := os.Stat(statePath + DefaultBackupExtension) 1694 if err == nil || !os.IsNotExist(err) { 1695 t.Fatalf("backup should not exist") 1696 } 1697 1698 // Ensure there is no literal "-" 1699 _, err = os.Stat("-") 1700 if err == nil || !os.IsNotExist(err) { 1701 t.Fatalf("backup should not exist") 1702 } 1703 } 1704 1705 // Test that the Terraform env is passed through 1706 func TestApply_terraformEnv(t *testing.T) { 1707 // Create a temporary working directory that is empty 1708 td := t.TempDir() 1709 testCopyDir(t, testFixturePath("apply-terraform-env"), td) 1710 defer testChdir(t, td)() 1711 1712 statePath := testTempFile(t) 1713 1714 p := testProvider() 1715 view, done := testView(t) 1716 c := &ApplyCommand{ 1717 Meta: Meta{ 1718 testingOverrides: metaOverridesForProvider(p), 1719 View: view, 1720 }, 1721 } 1722 1723 args := []string{ 1724 "-auto-approve", 1725 "-state", statePath, 1726 } 1727 code := c.Run(args) 1728 output := done(t) 1729 if code != 0 { 1730 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1731 } 1732 1733 expected := strings.TrimSpace(` 1734 <no state> 1735 Outputs: 1736 1737 output = default 1738 `) 1739 testStateOutput(t, statePath, expected) 1740 } 1741 1742 // Test that the Terraform env is passed through 1743 func TestApply_terraformEnvNonDefault(t *testing.T) { 1744 // Create a temporary working directory that is empty 1745 td := t.TempDir() 1746 testCopyDir(t, testFixturePath("apply-terraform-env"), td) 1747 defer testChdir(t, td)() 1748 1749 // Create new env 1750 { 1751 ui := new(cli.MockUi) 1752 newCmd := &WorkspaceNewCommand{ 1753 Meta: Meta{ 1754 Ui: ui, 1755 }, 1756 } 1757 if code := newCmd.Run([]string{"test"}); code != 0 { 1758 t.Fatal("error creating workspace") 1759 } 1760 } 1761 1762 // Switch to it 1763 { 1764 args := []string{"test"} 1765 ui := new(cli.MockUi) 1766 selCmd := &WorkspaceSelectCommand{ 1767 Meta: Meta{ 1768 Ui: ui, 1769 }, 1770 } 1771 if code := selCmd.Run(args); code != 0 { 1772 t.Fatal("error switching workspace") 1773 } 1774 } 1775 1776 p := testProvider() 1777 view, done := testView(t) 1778 c := &ApplyCommand{ 1779 Meta: Meta{ 1780 testingOverrides: metaOverridesForProvider(p), 1781 View: view, 1782 }, 1783 } 1784 1785 args := []string{ 1786 "-auto-approve", 1787 } 1788 code := c.Run(args) 1789 output := done(t) 1790 if code != 0 { 1791 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1792 } 1793 1794 statePath := filepath.Join("terraform.tfstate.d", "test", "terraform.tfstate") 1795 expected := strings.TrimSpace(` 1796 <no state> 1797 Outputs: 1798 1799 output = test 1800 `) 1801 testStateOutput(t, statePath, expected) 1802 } 1803 1804 // Config with multiple resources, targeting apply of a subset 1805 func TestApply_targeted(t *testing.T) { 1806 td := t.TempDir() 1807 testCopyDir(t, testFixturePath("apply-targeted"), td) 1808 defer testChdir(t, td)() 1809 1810 p := testProvider() 1811 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1812 ResourceTypes: map[string]providers.Schema{ 1813 "test_instance": { 1814 Block: &configschema.Block{ 1815 Attributes: map[string]*configschema.Attribute{ 1816 "id": {Type: cty.String, Computed: true}, 1817 }, 1818 }, 1819 }, 1820 }, 1821 } 1822 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1823 return providers.PlanResourceChangeResponse{ 1824 PlannedState: req.ProposedNewState, 1825 } 1826 } 1827 1828 view, done := testView(t) 1829 c := &ApplyCommand{ 1830 Meta: Meta{ 1831 testingOverrides: metaOverridesForProvider(p), 1832 View: view, 1833 }, 1834 } 1835 1836 args := []string{ 1837 "-auto-approve", 1838 "-target", "test_instance.foo", 1839 "-target", "test_instance.baz", 1840 } 1841 code := c.Run(args) 1842 output := done(t) 1843 if code != 0 { 1844 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1845 } 1846 1847 if got, want := output.Stdout(), "3 added, 0 changed, 0 destroyed"; !strings.Contains(got, want) { 1848 t.Fatalf("bad change summary, want %q, got:\n%s", want, got) 1849 } 1850 } 1851 1852 // Diagnostics for invalid -target flags 1853 func TestApply_targetFlagsDiags(t *testing.T) { 1854 testCases := map[string]string{ 1855 "test_instance.": "Dot must be followed by attribute name.", 1856 "test_instance": "Resource specification must include a resource type and name.", 1857 } 1858 1859 for target, wantDiag := range testCases { 1860 t.Run(target, func(t *testing.T) { 1861 td := testTempDir(t) 1862 defer os.RemoveAll(td) 1863 defer testChdir(t, td)() 1864 1865 view, done := testView(t) 1866 c := &ApplyCommand{ 1867 Meta: Meta{ 1868 View: view, 1869 }, 1870 } 1871 1872 args := []string{ 1873 "-auto-approve", 1874 "-target", target, 1875 } 1876 code := c.Run(args) 1877 output := done(t) 1878 if code != 1 { 1879 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 1880 } 1881 1882 got := output.Stderr() 1883 if !strings.Contains(got, target) { 1884 t.Fatalf("bad error output, want %q, got:\n%s", target, got) 1885 } 1886 if !strings.Contains(got, wantDiag) { 1887 t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got) 1888 } 1889 }) 1890 } 1891 } 1892 1893 func TestApply_replace(t *testing.T) { 1894 td := t.TempDir() 1895 testCopyDir(t, testFixturePath("apply-replace"), td) 1896 defer testChdir(t, td)() 1897 1898 originalState := states.BuildState(func(s *states.SyncState) { 1899 s.SetResourceInstanceCurrent( 1900 addrs.Resource{ 1901 Mode: addrs.ManagedResourceMode, 1902 Type: "test_instance", 1903 Name: "a", 1904 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 1905 &states.ResourceInstanceObjectSrc{ 1906 AttrsJSON: []byte(`{"id":"hello"}`), 1907 Status: states.ObjectReady, 1908 }, 1909 addrs.AbsProviderConfig{ 1910 Provider: addrs.NewDefaultProvider("test"), 1911 Module: addrs.RootModule, 1912 }, 1913 ) 1914 }) 1915 statePath := testStateFile(t, originalState) 1916 1917 p := testProvider() 1918 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 1919 ResourceTypes: map[string]providers.Schema{ 1920 "test_instance": { 1921 Block: &configschema.Block{ 1922 Attributes: map[string]*configschema.Attribute{ 1923 "id": {Type: cty.String, Computed: true}, 1924 }, 1925 }, 1926 }, 1927 }, 1928 } 1929 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 1930 return providers.PlanResourceChangeResponse{ 1931 PlannedState: req.ProposedNewState, 1932 } 1933 } 1934 createCount := 0 1935 deleteCount := 0 1936 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 1937 if req.PriorState.IsNull() { 1938 createCount++ 1939 } 1940 if req.PlannedState.IsNull() { 1941 deleteCount++ 1942 } 1943 return providers.ApplyResourceChangeResponse{ 1944 NewState: req.PlannedState, 1945 } 1946 } 1947 1948 view, done := testView(t) 1949 c := &ApplyCommand{ 1950 Meta: Meta{ 1951 testingOverrides: metaOverridesForProvider(p), 1952 View: view, 1953 }, 1954 } 1955 1956 args := []string{ 1957 "-auto-approve", 1958 "-state", statePath, 1959 "-replace", "test_instance.a", 1960 } 1961 code := c.Run(args) 1962 output := done(t) 1963 if code != 0 { 1964 t.Fatalf("wrong exit code %d\n\n%s", code, output.Stderr()) 1965 } 1966 1967 if got, want := output.Stdout(), "1 added, 0 changed, 1 destroyed"; !strings.Contains(got, want) { 1968 t.Errorf("wrong change summary\ngot output:\n%s\n\nwant substring: %s", got, want) 1969 } 1970 1971 if got, want := createCount, 1; got != want { 1972 t.Errorf("wrong create count %d; want %d", got, want) 1973 } 1974 if got, want := deleteCount, 1; got != want { 1975 t.Errorf("wrong create count %d; want %d", got, want) 1976 } 1977 } 1978 1979 func TestApply_pluginPath(t *testing.T) { 1980 // Create a temporary working directory that is empty 1981 td := t.TempDir() 1982 testCopyDir(t, testFixturePath("apply"), td) 1983 defer testChdir(t, td)() 1984 1985 statePath := testTempFile(t) 1986 1987 p := applyFixtureProvider() 1988 1989 view, done := testView(t) 1990 c := &ApplyCommand{ 1991 Meta: Meta{ 1992 testingOverrides: metaOverridesForProvider(p), 1993 View: view, 1994 }, 1995 } 1996 1997 pluginPath := []string{"a", "b", "c"} 1998 1999 if err := c.Meta.storePluginPath(pluginPath); err != nil { 2000 t.Fatal(err) 2001 } 2002 c.Meta.pluginPath = nil 2003 2004 args := []string{ 2005 "-state", statePath, 2006 "-auto-approve", 2007 } 2008 code := c.Run(args) 2009 output := done(t) 2010 if code != 0 { 2011 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 2012 } 2013 2014 if !reflect.DeepEqual(pluginPath, c.Meta.pluginPath) { 2015 t.Fatalf("expected plugin path %#v, got %#v", pluginPath, c.Meta.pluginPath) 2016 } 2017 } 2018 2019 func TestApply_jsonGoldenReference(t *testing.T) { 2020 // Create a temporary working directory that is empty 2021 td := t.TempDir() 2022 testCopyDir(t, testFixturePath("apply"), td) 2023 defer testChdir(t, td)() 2024 2025 statePath := testTempFile(t) 2026 2027 p := applyFixtureProvider() 2028 2029 view, done := testView(t) 2030 c := &ApplyCommand{ 2031 Meta: Meta{ 2032 testingOverrides: metaOverridesForProvider(p), 2033 View: view, 2034 }, 2035 } 2036 2037 args := []string{ 2038 "-json", 2039 "-state", statePath, 2040 "-auto-approve", 2041 } 2042 code := c.Run(args) 2043 output := done(t) 2044 if code != 0 { 2045 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 2046 } 2047 2048 if _, err := os.Stat(statePath); err != nil { 2049 t.Fatalf("err: %s", err) 2050 } 2051 2052 state := testStateRead(t, statePath) 2053 if state == nil { 2054 t.Fatal("state should not be nil") 2055 } 2056 2057 checkGoldenReference(t, output, "apply") 2058 } 2059 2060 func TestApply_warnings(t *testing.T) { 2061 // Create a temporary working directory that is empty 2062 td := t.TempDir() 2063 testCopyDir(t, testFixturePath("apply"), td) 2064 defer testChdir(t, td)() 2065 2066 p := testProvider() 2067 p.GetProviderSchemaResponse = applyFixtureSchema() 2068 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 2069 return providers.PlanResourceChangeResponse{ 2070 PlannedState: req.ProposedNewState, 2071 Diagnostics: tfdiags.Diagnostics{ 2072 tfdiags.SimpleWarning("warning 1"), 2073 tfdiags.SimpleWarning("warning 2"), 2074 }, 2075 } 2076 } 2077 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 2078 return providers.ApplyResourceChangeResponse{ 2079 NewState: cty.UnknownAsNull(req.PlannedState), 2080 } 2081 } 2082 2083 t.Run("full warnings", func(t *testing.T) { 2084 view, done := testView(t) 2085 c := &ApplyCommand{ 2086 Meta: Meta{ 2087 testingOverrides: metaOverridesForProvider(p), 2088 View: view, 2089 }, 2090 } 2091 2092 args := []string{"-auto-approve"} 2093 code := c.Run(args) 2094 output := done(t) 2095 if code != 0 { 2096 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 2097 } 2098 wantWarnings := []string{ 2099 "warning 1", 2100 "warning 2", 2101 } 2102 for _, want := range wantWarnings { 2103 if !strings.Contains(output.Stdout(), want) { 2104 t.Errorf("missing warning %s", want) 2105 } 2106 } 2107 }) 2108 2109 t.Run("compact warnings", func(t *testing.T) { 2110 view, done := testView(t) 2111 c := &ApplyCommand{ 2112 Meta: Meta{ 2113 testingOverrides: metaOverridesForProvider(p), 2114 View: view, 2115 }, 2116 } 2117 2118 code := c.Run([]string{"-auto-approve", "-compact-warnings"}) 2119 output := done(t) 2120 if code != 0 { 2121 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 2122 } 2123 // the output should contain 2 warnings and a message about -compact-warnings 2124 wantWarnings := []string{ 2125 "warning 1", 2126 "warning 2", 2127 "To see the full warning notes, run Terraform without -compact-warnings.", 2128 } 2129 for _, want := range wantWarnings { 2130 if !strings.Contains(output.Stdout(), want) { 2131 t.Errorf("missing warning %s", want) 2132 } 2133 } 2134 }) 2135 } 2136 2137 // applyFixtureSchema returns a schema suitable for processing the 2138 // configuration in testdata/apply . This schema should be 2139 // assigned to a mock provider named "test". 2140 func applyFixtureSchema() *providers.GetProviderSchemaResponse { 2141 return &providers.GetProviderSchemaResponse{ 2142 ResourceTypes: map[string]providers.Schema{ 2143 "test_instance": { 2144 Block: &configschema.Block{ 2145 Attributes: map[string]*configschema.Attribute{ 2146 "id": {Type: cty.String, Optional: true, Computed: true}, 2147 "ami": {Type: cty.String, Optional: true}, 2148 }, 2149 }, 2150 }, 2151 }, 2152 } 2153 } 2154 2155 // applyFixtureProvider returns a mock provider that is configured for basic 2156 // operation with the configuration in testdata/apply. This mock has 2157 // GetSchemaResponse, PlanResourceChangeFn, and ApplyResourceChangeFn populated, 2158 // with the plan/apply steps just passing through the data determined by 2159 // Terraform Core. 2160 func applyFixtureProvider() *terraform.MockProvider { 2161 p := testProvider() 2162 p.GetProviderSchemaResponse = applyFixtureSchema() 2163 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 2164 return providers.PlanResourceChangeResponse{ 2165 PlannedState: req.ProposedNewState, 2166 } 2167 } 2168 p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse { 2169 return providers.ApplyResourceChangeResponse{ 2170 NewState: cty.UnknownAsNull(req.PlannedState), 2171 } 2172 } 2173 return p 2174 } 2175 2176 // applyFixturePlanFile creates a plan file at a temporary location containing 2177 // a single change to create the test_instance.foo that is included in the 2178 // "apply" test fixture, returning the location of that plan file. 2179 func applyFixturePlanFile(t *testing.T) string { 2180 return applyFixturePlanFileMatchState(t, statemgr.SnapshotMeta{}) 2181 } 2182 2183 // applyFixturePlanFileMatchState creates a planfile like applyFixturePlanFile, 2184 // but inserts the state meta information if that plan must match a preexisting 2185 // state. 2186 func applyFixturePlanFileMatchState(t *testing.T, stateMeta statemgr.SnapshotMeta) string { 2187 _, snap := testModuleWithSnapshot(t, "apply") 2188 plannedVal := cty.ObjectVal(map[string]cty.Value{ 2189 "id": cty.UnknownVal(cty.String), 2190 "ami": cty.StringVal("bar"), 2191 }) 2192 priorValRaw, err := plans.NewDynamicValue(cty.NullVal(plannedVal.Type()), plannedVal.Type()) 2193 if err != nil { 2194 t.Fatal(err) 2195 } 2196 plannedValRaw, err := plans.NewDynamicValue(plannedVal, plannedVal.Type()) 2197 if err != nil { 2198 t.Fatal(err) 2199 } 2200 plan := testPlan(t) 2201 plan.Changes.SyncWrapper().AppendResourceInstanceChange(&plans.ResourceInstanceChangeSrc{ 2202 Addr: addrs.Resource{ 2203 Mode: addrs.ManagedResourceMode, 2204 Type: "test_instance", 2205 Name: "foo", 2206 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 2207 ProviderAddr: addrs.AbsProviderConfig{ 2208 Provider: addrs.NewDefaultProvider("test"), 2209 Module: addrs.RootModule, 2210 }, 2211 ChangeSrc: plans.ChangeSrc{ 2212 Action: plans.Create, 2213 Before: priorValRaw, 2214 After: plannedValRaw, 2215 }, 2216 }) 2217 return testPlanFileMatchState( 2218 t, 2219 snap, 2220 states.NewState(), 2221 plan, 2222 stateMeta, 2223 ) 2224 } 2225 2226 const applyVarFile = ` 2227 foo = "bar" 2228 ` 2229 2230 const applyVarFileJSON = ` 2231 { "foo": "bar" } 2232 `