github.com/opentofu/opentofu@v1.7.1/internal/command/refresh_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 "fmt" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strings" 15 "testing" 16 17 "github.com/davecgh/go-spew/spew" 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/opentofu/opentofu/internal/addrs" 24 "github.com/opentofu/opentofu/internal/configs/configschema" 25 "github.com/opentofu/opentofu/internal/encryption" 26 "github.com/opentofu/opentofu/internal/providers" 27 "github.com/opentofu/opentofu/internal/states" 28 "github.com/opentofu/opentofu/internal/states/statefile" 29 "github.com/opentofu/opentofu/internal/states/statemgr" 30 "github.com/opentofu/opentofu/internal/tfdiags" 31 ) 32 33 var equateEmpty = cmpopts.EquateEmpty() 34 35 func TestRefresh(t *testing.T) { 36 // Create a temporary working directory that is empty 37 td := t.TempDir() 38 testCopyDir(t, testFixturePath("refresh"), td) 39 defer testChdir(t, td)() 40 41 state := testState() 42 statePath := testStateFile(t, state) 43 44 p := testProvider() 45 view, done := testView(t) 46 c := &RefreshCommand{ 47 Meta: Meta{ 48 testingOverrides: metaOverridesForProvider(p), 49 View: view, 50 }, 51 } 52 53 p.GetProviderSchemaResponse = refreshFixtureSchema() 54 p.ReadResourceFn = nil 55 p.ReadResourceResponse = &providers.ReadResourceResponse{ 56 NewState: cty.ObjectVal(map[string]cty.Value{ 57 "id": cty.StringVal("yes"), 58 }), 59 } 60 61 args := []string{ 62 "-state", statePath, 63 } 64 code := c.Run(args) 65 output := done(t) 66 if code != 0 { 67 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 68 } 69 70 if !p.ReadResourceCalled { 71 t.Fatal("ReadResource should have been called") 72 } 73 74 f, err := os.Open(statePath) 75 if err != nil { 76 t.Fatalf("err: %s", err) 77 } 78 79 newStateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled()) 80 f.Close() 81 if err != nil { 82 t.Fatalf("err: %s", err) 83 } 84 85 actual := strings.TrimSpace(newStateFile.State.String()) 86 expected := strings.TrimSpace(testRefreshStr) 87 if actual != expected { 88 t.Fatalf("bad:\n\n%s", actual) 89 } 90 } 91 92 func TestRefresh_empty(t *testing.T) { 93 // Create a temporary working directory that is empty 94 td := t.TempDir() 95 testCopyDir(t, testFixturePath("refresh-empty"), td) 96 defer testChdir(t, td)() 97 98 p := testProvider() 99 view, done := testView(t) 100 c := &RefreshCommand{ 101 Meta: Meta{ 102 testingOverrides: metaOverridesForProvider(p), 103 View: view, 104 }, 105 } 106 107 p.ReadResourceFn = nil 108 p.ReadResourceResponse = &providers.ReadResourceResponse{ 109 NewState: cty.ObjectVal(map[string]cty.Value{ 110 "id": cty.StringVal("yes"), 111 }), 112 } 113 114 args := []string{} 115 code := c.Run(args) 116 output := done(t) 117 if code != 0 { 118 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 119 } 120 121 if p.ReadResourceCalled { 122 t.Fatal("ReadResource should not have been called") 123 } 124 } 125 126 func TestRefresh_lockedState(t *testing.T) { 127 // Create a temporary working directory that is empty 128 td := t.TempDir() 129 testCopyDir(t, testFixturePath("refresh"), td) 130 defer testChdir(t, td)() 131 132 state := testState() 133 statePath := testStateFile(t, state) 134 135 unlock, err := testLockState(t, testDataDir, statePath) 136 if err != nil { 137 t.Fatal(err) 138 } 139 defer unlock() 140 141 p := testProvider() 142 view, done := testView(t) 143 c := &RefreshCommand{ 144 Meta: Meta{ 145 testingOverrides: metaOverridesForProvider(p), 146 View: view, 147 }, 148 } 149 150 p.GetProviderSchemaResponse = refreshFixtureSchema() 151 p.ReadResourceFn = nil 152 p.ReadResourceResponse = &providers.ReadResourceResponse{ 153 NewState: cty.ObjectVal(map[string]cty.Value{ 154 "id": cty.StringVal("yes"), 155 }), 156 } 157 158 args := []string{ 159 "-state", statePath, 160 } 161 162 code := c.Run(args) 163 output := done(t) 164 if code == 0 { 165 t.Fatal("expected error") 166 } 167 168 got := output.Stderr() 169 if !strings.Contains(got, "lock") { 170 t.Fatal("command output does not look like a lock error:", got) 171 } 172 } 173 174 func TestRefresh_cwd(t *testing.T) { 175 cwd, err := os.Getwd() 176 if err != nil { 177 t.Fatalf("err: %s", err) 178 } 179 if err := os.Chdir(testFixturePath("refresh")); err != nil { 180 t.Fatalf("err: %s", err) 181 } 182 defer os.Chdir(cwd) 183 184 state := testState() 185 statePath := testStateFile(t, state) 186 187 p := testProvider() 188 view, done := testView(t) 189 c := &RefreshCommand{ 190 Meta: Meta{ 191 testingOverrides: metaOverridesForProvider(p), 192 View: view, 193 }, 194 } 195 196 p.GetProviderSchemaResponse = refreshFixtureSchema() 197 p.ReadResourceFn = nil 198 p.ReadResourceResponse = &providers.ReadResourceResponse{ 199 NewState: cty.ObjectVal(map[string]cty.Value{ 200 "id": cty.StringVal("yes"), 201 }), 202 } 203 204 args := []string{ 205 "-state", statePath, 206 } 207 code := c.Run(args) 208 output := done(t) 209 if code != 0 { 210 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 211 } 212 213 if !p.ReadResourceCalled { 214 t.Fatal("ReadResource should have been called") 215 } 216 217 f, err := os.Open(statePath) 218 if err != nil { 219 t.Fatalf("err: %s", err) 220 } 221 222 newStateFile, err := statefile.Read(f, encryption.StateEncryptionDisabled()) 223 f.Close() 224 if err != nil { 225 t.Fatalf("err: %s", err) 226 } 227 228 actual := strings.TrimSpace(newStateFile.State.String()) 229 expected := strings.TrimSpace(testRefreshCwdStr) 230 if actual != expected { 231 t.Fatalf("bad:\n\n%s", actual) 232 } 233 } 234 235 func TestRefresh_defaultState(t *testing.T) { 236 // Create a temporary working directory that is empty 237 td := t.TempDir() 238 testCopyDir(t, testFixturePath("refresh"), td) 239 defer testChdir(t, td)() 240 241 originalState := testState() 242 243 // Write the state file in a temporary directory with the 244 // default filename. 245 statePath := testStateFile(t, originalState) 246 247 localState := statemgr.NewFilesystem(statePath, encryption.StateEncryptionDisabled()) 248 if err := localState.RefreshState(); err != nil { 249 t.Fatal(err) 250 } 251 s := localState.State() 252 if s == nil { 253 t.Fatal("empty test state") 254 } 255 256 // Change to that directory 257 cwd, err := os.Getwd() 258 if err != nil { 259 t.Fatalf("err: %s", err) 260 } 261 if err := os.Chdir(filepath.Dir(statePath)); err != nil { 262 t.Fatalf("err: %s", err) 263 } 264 defer os.Chdir(cwd) 265 266 p := testProvider() 267 view, done := testView(t) 268 c := &RefreshCommand{ 269 Meta: Meta{ 270 testingOverrides: metaOverridesForProvider(p), 271 View: view, 272 }, 273 } 274 275 p.GetProviderSchemaResponse = refreshFixtureSchema() 276 p.ReadResourceFn = nil 277 p.ReadResourceResponse = &providers.ReadResourceResponse{ 278 NewState: cty.ObjectVal(map[string]cty.Value{ 279 "id": cty.StringVal("yes"), 280 }), 281 } 282 283 args := []string{ 284 "-state", statePath, 285 } 286 code := c.Run(args) 287 output := done(t) 288 if code != 0 { 289 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 290 } 291 292 if !p.ReadResourceCalled { 293 t.Fatal("ReadResource should have been called") 294 } 295 296 newState := testStateRead(t, statePath) 297 298 actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current 299 expected := &states.ResourceInstanceObjectSrc{ 300 Status: states.ObjectReady, 301 AttrsJSON: []byte("{\n \"ami\": null,\n \"id\": \"yes\"\n }"), 302 Dependencies: []addrs.ConfigResource{}, 303 } 304 if !reflect.DeepEqual(actual, expected) { 305 t.Fatalf("wrong new object\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(expected)) 306 } 307 308 backupState := testStateRead(t, statePath+DefaultBackupExtension) 309 310 actual = backupState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current 311 expected = originalState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current 312 if !reflect.DeepEqual(actual, expected) { 313 t.Fatalf("wrong new object\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(expected)) 314 } 315 } 316 317 func TestRefresh_outPath(t *testing.T) { 318 // Create a temporary working directory that is empty 319 td := t.TempDir() 320 testCopyDir(t, testFixturePath("refresh"), td) 321 defer testChdir(t, td)() 322 323 state := testState() 324 statePath := testStateFile(t, state) 325 326 // Output path 327 outf, err := os.CreateTemp(td, "tf") 328 if err != nil { 329 t.Fatalf("err: %s", err) 330 } 331 outPath := outf.Name() 332 outf.Close() 333 os.Remove(outPath) 334 335 p := testProvider() 336 view, done := testView(t) 337 c := &RefreshCommand{ 338 Meta: Meta{ 339 testingOverrides: metaOverridesForProvider(p), 340 View: view, 341 }, 342 } 343 344 p.GetProviderSchemaResponse = refreshFixtureSchema() 345 p.ReadResourceFn = nil 346 p.ReadResourceResponse = &providers.ReadResourceResponse{ 347 NewState: cty.ObjectVal(map[string]cty.Value{ 348 "id": cty.StringVal("yes"), 349 }), 350 } 351 352 args := []string{ 353 "-state", statePath, 354 "-state-out", outPath, 355 } 356 code := c.Run(args) 357 output := done(t) 358 if code != 0 { 359 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 360 } 361 362 newState := testStateRead(t, statePath) 363 if !reflect.DeepEqual(newState, state) { 364 t.Fatalf("bad: %#v", newState) 365 } 366 367 newState = testStateRead(t, outPath) 368 actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current 369 expected := &states.ResourceInstanceObjectSrc{ 370 Status: states.ObjectReady, 371 AttrsJSON: []byte("{\n \"ami\": null,\n \"id\": \"yes\"\n }"), 372 Dependencies: []addrs.ConfigResource{}, 373 } 374 if !reflect.DeepEqual(actual, expected) { 375 t.Fatalf("wrong new object\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(expected)) 376 } 377 378 if _, err := os.Stat(outPath + DefaultBackupExtension); !os.IsNotExist(err) { 379 if err != nil { 380 t.Fatalf("failed to test for backup file: %s", err) 381 } 382 t.Fatalf("backup file exists, but it should not because output file did not initially exist") 383 } 384 } 385 386 func TestRefresh_var(t *testing.T) { 387 // Create a temporary working directory that is empty 388 td := t.TempDir() 389 testCopyDir(t, testFixturePath("refresh-var"), td) 390 defer testChdir(t, td)() 391 392 state := testState() 393 statePath := testStateFile(t, state) 394 395 p := testProvider() 396 view, done := testView(t) 397 c := &RefreshCommand{ 398 Meta: Meta{ 399 testingOverrides: metaOverridesForProvider(p), 400 View: view, 401 }, 402 } 403 p.GetProviderSchemaResponse = refreshVarFixtureSchema() 404 405 args := []string{ 406 "-var", "foo=bar", 407 "-state", statePath, 408 } 409 code := c.Run(args) 410 output := done(t) 411 if code != 0 { 412 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 413 } 414 415 if !p.ConfigureProviderCalled { 416 t.Fatal("configure should be called") 417 } 418 if got, want := p.ConfigureProviderRequest.Config.GetAttr("value"), cty.StringVal("bar"); !want.RawEquals(got) { 419 t.Fatalf("wrong provider configuration\ngot: %#v\nwant: %#v", got, want) 420 } 421 } 422 423 func TestRefresh_varFile(t *testing.T) { 424 // Create a temporary working directory that is empty 425 td := t.TempDir() 426 testCopyDir(t, testFixturePath("refresh-var"), td) 427 defer testChdir(t, td)() 428 429 state := testState() 430 statePath := testStateFile(t, state) 431 432 p := testProvider() 433 view, done := testView(t) 434 c := &RefreshCommand{ 435 Meta: Meta{ 436 testingOverrides: metaOverridesForProvider(p), 437 View: view, 438 }, 439 } 440 p.GetProviderSchemaResponse = refreshVarFixtureSchema() 441 442 varFilePath := testTempFile(t) 443 if err := os.WriteFile(varFilePath, []byte(refreshVarFile), 0644); err != nil { 444 t.Fatalf("err: %s", err) 445 } 446 447 args := []string{ 448 "-var-file", varFilePath, 449 "-state", statePath, 450 } 451 code := c.Run(args) 452 output := done(t) 453 if code != 0 { 454 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 455 } 456 457 if !p.ConfigureProviderCalled { 458 t.Fatal("configure should be called") 459 } 460 if got, want := p.ConfigureProviderRequest.Config.GetAttr("value"), cty.StringVal("bar"); !want.RawEquals(got) { 461 t.Fatalf("wrong provider configuration\ngot: %#v\nwant: %#v", got, want) 462 } 463 } 464 465 func TestRefresh_varFileDefault(t *testing.T) { 466 // Create a temporary working directory that is empty 467 td := t.TempDir() 468 testCopyDir(t, testFixturePath("refresh-var"), td) 469 defer testChdir(t, td)() 470 471 state := testState() 472 statePath := testStateFile(t, state) 473 474 p := testProvider() 475 view, done := testView(t) 476 c := &RefreshCommand{ 477 Meta: Meta{ 478 testingOverrides: metaOverridesForProvider(p), 479 View: view, 480 }, 481 } 482 p.GetProviderSchemaResponse = refreshVarFixtureSchema() 483 484 varFilePath := filepath.Join(td, "terraform.tfvars") 485 if err := os.WriteFile(varFilePath, []byte(refreshVarFile), 0644); err != nil { 486 t.Fatalf("err: %s", err) 487 } 488 489 args := []string{ 490 "-state", statePath, 491 } 492 code := c.Run(args) 493 output := done(t) 494 if code != 0 { 495 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 496 } 497 498 if !p.ConfigureProviderCalled { 499 t.Fatal("configure should be called") 500 } 501 if got, want := p.ConfigureProviderRequest.Config.GetAttr("value"), cty.StringVal("bar"); !want.RawEquals(got) { 502 t.Fatalf("wrong provider configuration\ngot: %#v\nwant: %#v", got, want) 503 } 504 } 505 506 func TestRefresh_varsUnset(t *testing.T) { 507 // Create a temporary working directory that is empty 508 td := t.TempDir() 509 testCopyDir(t, testFixturePath("refresh-unset-var"), td) 510 defer testChdir(t, td)() 511 512 // Disable test mode so input would be asked 513 test = false 514 defer func() { test = true }() 515 516 defaultInputReader = bytes.NewBufferString("bar\n") 517 518 state := testState() 519 statePath := testStateFile(t, state) 520 521 p := testProvider() 522 ui := new(cli.MockUi) 523 view, done := testView(t) 524 c := &RefreshCommand{ 525 Meta: Meta{ 526 testingOverrides: metaOverridesForProvider(p), 527 Ui: ui, 528 View: view, 529 }, 530 } 531 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 532 ResourceTypes: map[string]providers.Schema{ 533 "test_instance": { 534 Block: &configschema.Block{ 535 Attributes: map[string]*configschema.Attribute{ 536 "id": {Type: cty.String, Optional: true, Computed: true}, 537 "ami": {Type: cty.String, Optional: true}, 538 }, 539 }, 540 }, 541 }, 542 } 543 544 args := []string{ 545 "-state", statePath, 546 } 547 code := c.Run(args) 548 output := done(t) 549 if code != 0 { 550 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 551 } 552 } 553 554 func TestRefresh_backup(t *testing.T) { 555 // Create a temporary working directory that is empty 556 td := t.TempDir() 557 testCopyDir(t, testFixturePath("refresh"), td) 558 defer testChdir(t, td)() 559 560 state := testState() 561 statePath := testStateFile(t, state) 562 563 // Output path 564 outf, err := os.CreateTemp(td, "tf") 565 if err != nil { 566 t.Fatalf("err: %s", err) 567 } 568 outPath := outf.Name() 569 defer outf.Close() 570 571 // Need to put some state content in the output file so that there's 572 // something to back up. 573 err = statefile.Write(statefile.New(state, "baz", 0), outf, encryption.StateEncryptionDisabled()) 574 if err != nil { 575 t.Fatalf("error writing initial output state file %s", err) 576 } 577 578 // Backup path 579 backupf, err := os.CreateTemp(td, "tf") 580 if err != nil { 581 t.Fatalf("err: %s", err) 582 } 583 backupPath := backupf.Name() 584 backupf.Close() 585 os.Remove(backupPath) 586 587 p := testProvider() 588 view, done := testView(t) 589 c := &RefreshCommand{ 590 Meta: Meta{ 591 testingOverrides: metaOverridesForProvider(p), 592 View: view, 593 }, 594 } 595 596 p.GetProviderSchemaResponse = refreshFixtureSchema() 597 p.ReadResourceFn = nil 598 p.ReadResourceResponse = &providers.ReadResourceResponse{ 599 NewState: cty.ObjectVal(map[string]cty.Value{ 600 "id": cty.StringVal("changed"), 601 }), 602 } 603 604 args := []string{ 605 "-state", statePath, 606 "-state-out", outPath, 607 "-backup", backupPath, 608 } 609 code := c.Run(args) 610 output := done(t) 611 if code != 0 { 612 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 613 } 614 615 newState := testStateRead(t, statePath) 616 if !cmp.Equal(newState, state, cmpopts.EquateEmpty()) { 617 t.Fatalf("got:\n%s\nexpected:\n%s\n", newState, state) 618 } 619 620 newState = testStateRead(t, outPath) 621 actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current 622 expected := &states.ResourceInstanceObjectSrc{ 623 Status: states.ObjectReady, 624 AttrsJSON: []byte("{\n \"ami\": null,\n \"id\": \"changed\"\n }"), 625 Dependencies: []addrs.ConfigResource{}, 626 } 627 if !reflect.DeepEqual(actual, expected) { 628 t.Fatalf("wrong new object\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(expected)) 629 } 630 631 backupState := testStateRead(t, backupPath) 632 actualStr := strings.TrimSpace(backupState.String()) 633 expectedStr := strings.TrimSpace(state.String()) 634 if actualStr != expectedStr { 635 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 636 } 637 } 638 639 func TestRefresh_disableBackup(t *testing.T) { 640 // Create a temporary working directory that is empty 641 td := t.TempDir() 642 testCopyDir(t, testFixturePath("refresh"), td) 643 defer testChdir(t, td)() 644 645 state := testState() 646 statePath := testStateFile(t, state) 647 648 // Output path 649 outf, err := os.CreateTemp(td, "tf") 650 if err != nil { 651 t.Fatalf("err: %s", err) 652 } 653 outPath := outf.Name() 654 outf.Close() 655 os.Remove(outPath) 656 657 p := testProvider() 658 view, done := testView(t) 659 c := &RefreshCommand{ 660 Meta: Meta{ 661 testingOverrides: metaOverridesForProvider(p), 662 View: view, 663 }, 664 } 665 666 p.GetProviderSchemaResponse = refreshFixtureSchema() 667 p.ReadResourceFn = nil 668 p.ReadResourceResponse = &providers.ReadResourceResponse{ 669 NewState: cty.ObjectVal(map[string]cty.Value{ 670 "id": cty.StringVal("yes"), 671 }), 672 } 673 674 args := []string{ 675 "-state", statePath, 676 "-state-out", outPath, 677 "-backup", "-", 678 } 679 code := c.Run(args) 680 output := done(t) 681 if code != 0 { 682 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 683 } 684 685 newState := testStateRead(t, statePath) 686 if !cmp.Equal(state, newState, equateEmpty) { 687 spew.Config.DisableMethods = true 688 fmt.Println(cmp.Diff(state, newState, equateEmpty)) 689 t.Fatalf("bad: %s", newState) 690 } 691 692 newState = testStateRead(t, outPath) 693 actual := newState.RootModule().Resources["test_instance.foo"].Instances[addrs.NoKey].Current 694 expected := &states.ResourceInstanceObjectSrc{ 695 Status: states.ObjectReady, 696 AttrsJSON: []byte("{\n \"ami\": null,\n \"id\": \"yes\"\n }"), 697 Dependencies: []addrs.ConfigResource{}, 698 } 699 if !reflect.DeepEqual(actual, expected) { 700 t.Fatalf("wrong new object\ngot: %swant: %s", spew.Sdump(actual), spew.Sdump(expected)) 701 } 702 703 // Ensure there is no backup 704 _, err = os.Stat(outPath + DefaultBackupExtension) 705 if err == nil || !os.IsNotExist(err) { 706 t.Fatalf("backup should not exist") 707 } 708 _, err = os.Stat("-") 709 if err == nil || !os.IsNotExist(err) { 710 t.Fatalf("backup should not exist") 711 } 712 } 713 714 func TestRefresh_displaysOutputs(t *testing.T) { 715 // Create a temporary working directory that is empty 716 td := t.TempDir() 717 testCopyDir(t, testFixturePath("refresh-output"), td) 718 defer testChdir(t, td)() 719 720 state := testState() 721 statePath := testStateFile(t, state) 722 723 p := testProvider() 724 view, done := testView(t) 725 c := &RefreshCommand{ 726 Meta: Meta{ 727 testingOverrides: metaOverridesForProvider(p), 728 View: view, 729 }, 730 } 731 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 732 ResourceTypes: map[string]providers.Schema{ 733 "test_instance": { 734 Block: &configschema.Block{ 735 Attributes: map[string]*configschema.Attribute{ 736 "id": {Type: cty.String, Optional: true, Computed: true}, 737 "ami": {Type: cty.String, Optional: true}, 738 }, 739 }, 740 }, 741 }, 742 } 743 744 args := []string{ 745 "-state", statePath, 746 } 747 code := c.Run(args) 748 output := done(t) 749 if code != 0 { 750 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 751 } 752 753 // Test that outputs were displayed 754 outputValue := "foo.example.com" 755 actual := output.Stdout() 756 if !strings.Contains(actual, outputValue) { 757 t.Fatalf("Expected:\n%s\n\nTo include: %q", actual, outputValue) 758 } 759 } 760 761 // Config with multiple resources, targeting refresh of a subset 762 func TestRefresh_targeted(t *testing.T) { 763 td := t.TempDir() 764 testCopyDir(t, testFixturePath("refresh-targeted"), td) 765 defer testChdir(t, td)() 766 767 state := testState() 768 statePath := testStateFile(t, state) 769 770 p := testProvider() 771 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 772 ResourceTypes: map[string]providers.Schema{ 773 "test_instance": { 774 Block: &configschema.Block{ 775 Attributes: map[string]*configschema.Attribute{ 776 "id": {Type: cty.String, Computed: true}, 777 }, 778 }, 779 }, 780 }, 781 } 782 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 783 return providers.PlanResourceChangeResponse{ 784 PlannedState: req.ProposedNewState, 785 } 786 } 787 788 view, done := testView(t) 789 c := &RefreshCommand{ 790 Meta: Meta{ 791 testingOverrides: metaOverridesForProvider(p), 792 View: view, 793 }, 794 } 795 796 args := []string{ 797 "-target", "test_instance.foo", 798 "-state", statePath, 799 } 800 code := c.Run(args) 801 output := done(t) 802 if code != 0 { 803 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 804 } 805 806 got := output.Stdout() 807 if want := "test_instance.foo: Refreshing"; !strings.Contains(got, want) { 808 t.Fatalf("expected output to contain %q, got:\n%s", want, got) 809 } 810 if doNotWant := "test_instance.bar: Refreshing"; strings.Contains(got, doNotWant) { 811 t.Fatalf("expected output not to contain %q, got:\n%s", doNotWant, got) 812 } 813 } 814 815 // Diagnostics for invalid -target flags 816 func TestRefresh_targetFlagsDiags(t *testing.T) { 817 testCases := map[string]string{ 818 "test_instance.": "Dot must be followed by attribute name.", 819 "test_instance": "Resource specification must include a resource type and name.", 820 } 821 822 for target, wantDiag := range testCases { 823 t.Run(target, func(t *testing.T) { 824 td := testTempDir(t) 825 defer os.RemoveAll(td) 826 defer testChdir(t, td)() 827 828 view, done := testView(t) 829 c := &RefreshCommand{ 830 Meta: Meta{ 831 View: view, 832 }, 833 } 834 835 args := []string{ 836 "-target", target, 837 } 838 code := c.Run(args) 839 output := done(t) 840 if code != 1 { 841 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 842 } 843 844 got := output.Stderr() 845 if !strings.Contains(got, target) { 846 t.Fatalf("bad error output, want %q, got:\n%s", target, got) 847 } 848 if !strings.Contains(got, wantDiag) { 849 t.Fatalf("bad error output, want %q, got:\n%s", wantDiag, got) 850 } 851 }) 852 } 853 } 854 855 func TestRefresh_warnings(t *testing.T) { 856 // Create a temporary working directory that is empty 857 td := t.TempDir() 858 testCopyDir(t, testFixturePath("apply"), td) 859 defer testChdir(t, td)() 860 861 p := testProvider() 862 p.GetProviderSchemaResponse = refreshFixtureSchema() 863 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 864 return providers.PlanResourceChangeResponse{ 865 PlannedState: req.ProposedNewState, 866 Diagnostics: tfdiags.Diagnostics{ 867 tfdiags.SimpleWarning("warning 1"), 868 tfdiags.SimpleWarning("warning 2"), 869 }, 870 } 871 } 872 873 t.Run("full warnings", func(t *testing.T) { 874 view, done := testView(t) 875 c := &RefreshCommand{ 876 Meta: Meta{ 877 testingOverrides: metaOverridesForProvider(p), 878 View: view, 879 }, 880 } 881 882 code := c.Run([]string{}) 883 output := done(t) 884 if code != 0 { 885 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 886 } 887 wantWarnings := []string{ 888 "warning 1", 889 "warning 2", 890 } 891 for _, want := range wantWarnings { 892 if !strings.Contains(output.Stdout(), want) { 893 t.Errorf("missing warning %s", want) 894 } 895 } 896 }) 897 898 t.Run("compact warnings", func(t *testing.T) { 899 view, done := testView(t) 900 c := &RefreshCommand{ 901 Meta: Meta{ 902 testingOverrides: metaOverridesForProvider(p), 903 View: view, 904 }, 905 } 906 907 code := c.Run([]string{"-compact-warnings"}) 908 output := done(t) 909 if code != 0 { 910 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 911 } 912 // the output should contain 2 warnings and a message about -compact-warnings 913 wantWarnings := []string{ 914 "warning 1", 915 "warning 2", 916 "To see the full warning notes, run OpenTofu without -compact-warnings.", 917 } 918 for _, want := range wantWarnings { 919 if !strings.Contains(output.Stdout(), want) { 920 t.Errorf("missing warning %s", want) 921 } 922 } 923 }) 924 } 925 926 // configuration in testdata/refresh . This schema should be 927 // assigned to a mock provider named "test". 928 func refreshFixtureSchema() *providers.GetProviderSchemaResponse { 929 return &providers.GetProviderSchemaResponse{ 930 ResourceTypes: map[string]providers.Schema{ 931 "test_instance": { 932 Block: &configschema.Block{ 933 Attributes: map[string]*configschema.Attribute{ 934 "id": {Type: cty.String, Optional: true, Computed: true}, 935 "ami": {Type: cty.String, Optional: true}, 936 }, 937 }, 938 }, 939 }, 940 } 941 } 942 943 // refreshVarFixtureSchema returns a schema suitable for processing the 944 // configuration in testdata/refresh-var . This schema should be 945 // assigned to a mock provider named "test". 946 func refreshVarFixtureSchema() *providers.GetProviderSchemaResponse { 947 return &providers.GetProviderSchemaResponse{ 948 Provider: providers.Schema{ 949 Block: &configschema.Block{ 950 Attributes: map[string]*configschema.Attribute{ 951 "value": {Type: cty.String, Optional: true}, 952 }, 953 }, 954 }, 955 ResourceTypes: map[string]providers.Schema{ 956 "test_instance": { 957 Block: &configschema.Block{ 958 Attributes: map[string]*configschema.Attribute{ 959 "id": {Type: cty.String, Optional: true, Computed: true}, 960 }, 961 }, 962 }, 963 }, 964 } 965 } 966 967 const refreshVarFile = ` 968 foo = "bar" 969 ` 970 971 const testRefreshStr = ` 972 test_instance.foo: 973 ID = yes 974 provider = provider["registry.opentofu.org/hashicorp/test"] 975 ` 976 const testRefreshCwdStr = ` 977 test_instance.foo: 978 ID = yes 979 provider = provider["registry.opentofu.org/hashicorp/test"] 980 `