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