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