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