github.com/opentofu/opentofu@v1.7.1/internal/command/import_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 "fmt" 10 "log" 11 "os" 12 "path/filepath" 13 "strings" 14 "testing" 15 16 "github.com/mitchellh/cli" 17 "github.com/zclconf/go-cty/cty" 18 19 "github.com/opentofu/opentofu/internal/configs/configschema" 20 "github.com/opentofu/opentofu/internal/copy" 21 "github.com/opentofu/opentofu/internal/providers" 22 "github.com/opentofu/opentofu/internal/tfdiags" 23 ) 24 25 func TestImport(t *testing.T) { 26 defer testChdir(t, testFixturePath("import-provider-implicit"))() 27 28 statePath := testTempFile(t) 29 30 p := testProvider() 31 ui := new(cli.MockUi) 32 view, _ := testView(t) 33 c := &ImportCommand{ 34 Meta: Meta{ 35 testingOverrides: metaOverridesForProvider(p), 36 Ui: ui, 37 View: view, 38 }, 39 } 40 41 p.ImportResourceStateFn = nil 42 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 43 ImportedResources: []providers.ImportedResource{ 44 { 45 TypeName: "test_instance", 46 State: cty.ObjectVal(map[string]cty.Value{ 47 "id": cty.StringVal("yay"), 48 }), 49 }, 50 }, 51 } 52 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 53 ResourceTypes: map[string]providers.Schema{ 54 "test_instance": { 55 Block: &configschema.Block{ 56 Attributes: map[string]*configschema.Attribute{ 57 "id": {Type: cty.String, Optional: true, Computed: true}, 58 }, 59 }, 60 }, 61 }, 62 } 63 64 args := []string{ 65 "-state", statePath, 66 "test_instance.foo", 67 "bar", 68 } 69 if code := c.Run(args); code != 0 { 70 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 71 } 72 73 if !p.ImportResourceStateCalled { 74 t.Fatal("ImportResourceState should be called") 75 } 76 77 testStateOutput(t, statePath, testImportStr) 78 } 79 80 func TestImport_providerConfig(t *testing.T) { 81 defer testChdir(t, testFixturePath("import-provider"))() 82 83 statePath := testTempFile(t) 84 85 p := testProvider() 86 ui := new(cli.MockUi) 87 view, _ := testView(t) 88 c := &ImportCommand{ 89 Meta: Meta{ 90 testingOverrides: metaOverridesForProvider(p), 91 Ui: ui, 92 View: view, 93 }, 94 } 95 96 p.ImportResourceStateFn = nil 97 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 98 ImportedResources: []providers.ImportedResource{ 99 { 100 TypeName: "test_instance", 101 State: cty.ObjectVal(map[string]cty.Value{ 102 "id": cty.StringVal("yay"), 103 }), 104 }, 105 }, 106 } 107 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 108 Provider: providers.Schema{ 109 Block: &configschema.Block{ 110 Attributes: map[string]*configschema.Attribute{ 111 "foo": {Type: cty.String, Optional: true}, 112 }, 113 }, 114 }, 115 ResourceTypes: map[string]providers.Schema{ 116 "test_instance": { 117 Block: &configschema.Block{ 118 Attributes: map[string]*configschema.Attribute{ 119 "id": {Type: cty.String, Optional: true, Computed: true}, 120 }, 121 }, 122 }, 123 }, 124 } 125 126 configured := false 127 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse { 128 configured = true 129 130 cfg := req.Config 131 if !cfg.Type().HasAttribute("foo") { 132 return providers.ConfigureProviderResponse{ 133 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("configuration has no foo argument")), 134 } 135 } 136 if got, want := cfg.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) { 137 return providers.ConfigureProviderResponse{ 138 Diagnostics: tfdiags.Diagnostics{}.Append(fmt.Errorf("foo argument is %#v, but want %#v", got, want)), 139 } 140 } 141 142 return providers.ConfigureProviderResponse{} 143 } 144 145 args := []string{ 146 "-state", statePath, 147 "test_instance.foo", 148 "bar", 149 } 150 if code := c.Run(args); code != 0 { 151 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 152 } 153 154 // Verify that we were called 155 if !configured { 156 t.Fatal("Configure should be called") 157 } 158 159 if !p.ImportResourceStateCalled { 160 t.Fatal("ImportResourceState should be called") 161 } 162 163 testStateOutput(t, statePath, testImportStr) 164 } 165 166 // "remote" state provided by the "local" backend 167 func TestImport_remoteState(t *testing.T) { 168 td := t.TempDir() 169 testCopyDir(t, testFixturePath("import-provider-remote-state"), td) 170 defer testChdir(t, td)() 171 172 statePath := "imported.tfstate" 173 174 providerSource, close := newMockProviderSource(t, map[string][]string{ 175 "test": []string{"1.2.3"}, 176 }) 177 defer close() 178 179 // init our backend 180 ui := cli.NewMockUi() 181 view, _ := testView(t) 182 m := Meta{ 183 testingOverrides: metaOverridesForProvider(testProvider()), 184 Ui: ui, 185 View: view, 186 ProviderSource: providerSource, 187 } 188 189 ic := &InitCommand{ 190 Meta: m, 191 } 192 193 // (Using log here rather than t.Log so that these messages interleave with other trace logs) 194 log.Print("[TRACE] TestImport_remoteState running: tofu init") 195 if code := ic.Run([]string{}); code != 0 { 196 t.Fatalf("init failed\n%s", ui.ErrorWriter) 197 } 198 199 p := testProvider() 200 ui = new(cli.MockUi) 201 c := &ImportCommand{ 202 Meta: Meta{ 203 testingOverrides: metaOverridesForProvider(p), 204 Ui: ui, 205 View: view, 206 }, 207 } 208 209 p.ImportResourceStateFn = nil 210 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 211 ImportedResources: []providers.ImportedResource{ 212 { 213 TypeName: "test_instance", 214 State: cty.ObjectVal(map[string]cty.Value{ 215 "id": cty.StringVal("yay"), 216 }), 217 }, 218 }, 219 } 220 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 221 Provider: providers.Schema{ 222 Block: &configschema.Block{ 223 Attributes: map[string]*configschema.Attribute{ 224 "foo": {Type: cty.String, Optional: true}, 225 }, 226 }, 227 }, 228 ResourceTypes: map[string]providers.Schema{ 229 "test_instance": { 230 Block: &configschema.Block{ 231 Attributes: map[string]*configschema.Attribute{ 232 "id": {Type: cty.String, Optional: true, Computed: true}, 233 }, 234 }, 235 }, 236 }, 237 } 238 239 configured := false 240 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse { 241 var diags tfdiags.Diagnostics 242 configured = true 243 if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) { 244 diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want)) 245 } 246 return providers.ConfigureProviderResponse{ 247 Diagnostics: diags, 248 } 249 } 250 251 args := []string{ 252 "test_instance.foo", 253 "bar", 254 } 255 log.Printf("[TRACE] TestImport_remoteState running: tofu import %s %s", args[0], args[1]) 256 if code := c.Run(args); code != 0 { 257 fmt.Println(ui.OutputWriter) 258 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 259 } 260 261 // verify that the local state was unlocked after import 262 if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) { 263 t.Fatal("state left locked after import") 264 } 265 266 // Verify that we were called 267 if !configured { 268 t.Fatal("Configure should be called") 269 } 270 271 if !p.ImportResourceStateCalled { 272 t.Fatal("ImportResourceState should be called") 273 } 274 275 testStateOutput(t, statePath, testImportStr) 276 } 277 278 // early failure on import should not leave stale lock 279 func TestImport_initializationErrorShouldUnlock(t *testing.T) { 280 td := t.TempDir() 281 testCopyDir(t, testFixturePath("import-provider-remote-state"), td) 282 defer testChdir(t, td)() 283 284 statePath := "imported.tfstate" 285 286 providerSource, close := newMockProviderSource(t, map[string][]string{ 287 "test": []string{"1.2.3"}, 288 }) 289 defer close() 290 291 // init our backend 292 ui := cli.NewMockUi() 293 view, _ := testView(t) 294 m := Meta{ 295 testingOverrides: metaOverridesForProvider(testProvider()), 296 Ui: ui, 297 View: view, 298 ProviderSource: providerSource, 299 } 300 301 ic := &InitCommand{ 302 Meta: m, 303 } 304 305 // (Using log here rather than t.Log so that these messages interleave with other trace logs) 306 log.Print("[TRACE] TestImport_initializationErrorShouldUnlock running: tofu init") 307 if code := ic.Run([]string{}); code != 0 { 308 t.Fatalf("init failed\n%s", ui.ErrorWriter) 309 } 310 311 // overwrite the config with one including a resource from an invalid provider 312 copy.CopyFile(filepath.Join(testFixturePath("import-provider-invalid"), "main.tf"), filepath.Join(td, "main.tf")) 313 314 p := testProvider() 315 ui = new(cli.MockUi) 316 c := &ImportCommand{ 317 Meta: Meta{ 318 testingOverrides: metaOverridesForProvider(p), 319 Ui: ui, 320 View: view, 321 }, 322 } 323 324 args := []string{ 325 "unknown_instance.baz", 326 "bar", 327 } 328 log.Printf("[TRACE] TestImport_initializationErrorShouldUnlock running: tofu import %s %s", args[0], args[1]) 329 330 // this should fail 331 if code := c.Run(args); code != 1 { 332 fmt.Println(ui.OutputWriter) 333 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 334 } 335 336 // specifically, it should fail due to a missing provider 337 msg := strings.ReplaceAll(ui.ErrorWriter.String(), "\n", " ") 338 if want := `provider registry.opentofu.org/hashicorp/unknown: required by this configuration but no version is selected`; !strings.Contains(msg, want) { 339 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 340 } 341 342 // verify that the local state was unlocked after initialization error 343 if _, err := os.Stat(filepath.Join(td, fmt.Sprintf(".%s.lock.info", statePath))); !os.IsNotExist(err) { 344 t.Fatal("state left locked after import") 345 } 346 } 347 348 func TestImport_providerConfigWithVar(t *testing.T) { 349 defer testChdir(t, testFixturePath("import-provider-var"))() 350 351 statePath := testTempFile(t) 352 353 p := testProvider() 354 ui := new(cli.MockUi) 355 view, _ := testView(t) 356 c := &ImportCommand{ 357 Meta: Meta{ 358 testingOverrides: metaOverridesForProvider(p), 359 Ui: ui, 360 View: view, 361 }, 362 } 363 364 p.ImportResourceStateFn = nil 365 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 366 ImportedResources: []providers.ImportedResource{ 367 { 368 TypeName: "test_instance", 369 State: cty.ObjectVal(map[string]cty.Value{ 370 "id": cty.StringVal("yay"), 371 }), 372 }, 373 }, 374 } 375 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 376 Provider: providers.Schema{ 377 Block: &configschema.Block{ 378 Attributes: map[string]*configschema.Attribute{ 379 "foo": {Type: cty.String, Optional: true}, 380 }, 381 }, 382 }, 383 ResourceTypes: map[string]providers.Schema{ 384 "test_instance": { 385 Block: &configschema.Block{ 386 Attributes: map[string]*configschema.Attribute{ 387 "id": {Type: cty.String, Optional: true, Computed: true}, 388 }, 389 }, 390 }, 391 }, 392 } 393 394 configured := false 395 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse { 396 var diags tfdiags.Diagnostics 397 configured = true 398 if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) { 399 diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want)) 400 } 401 return providers.ConfigureProviderResponse{ 402 Diagnostics: diags, 403 } 404 } 405 406 args := []string{ 407 "-state", statePath, 408 "-var", "foo=bar", 409 "test_instance.foo", 410 "bar", 411 } 412 if code := c.Run(args); code != 0 { 413 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 414 } 415 416 // Verify that we were called 417 if !configured { 418 t.Fatal("Configure should be called") 419 } 420 421 if !p.ImportResourceStateCalled { 422 t.Fatal("ImportResourceState should be called") 423 } 424 425 testStateOutput(t, statePath, testImportStr) 426 } 427 428 func TestImport_providerConfigWithDataSource(t *testing.T) { 429 defer testChdir(t, testFixturePath("import-provider-datasource"))() 430 431 statePath := testTempFile(t) 432 433 p := testProvider() 434 ui := new(cli.MockUi) 435 view, _ := testView(t) 436 c := &ImportCommand{ 437 Meta: Meta{ 438 testingOverrides: metaOverridesForProvider(p), 439 Ui: ui, 440 View: view, 441 }, 442 } 443 444 p.ImportResourceStateFn = nil 445 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 446 ImportedResources: []providers.ImportedResource{ 447 { 448 TypeName: "test_instance", 449 State: cty.ObjectVal(map[string]cty.Value{ 450 "id": cty.StringVal("yay"), 451 }), 452 }, 453 }, 454 } 455 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 456 Provider: providers.Schema{ 457 Block: &configschema.Block{ 458 Attributes: map[string]*configschema.Attribute{ 459 "foo": {Type: cty.String, Optional: true}, 460 }, 461 }, 462 }, 463 ResourceTypes: map[string]providers.Schema{ 464 "test_instance": { 465 Block: &configschema.Block{ 466 Attributes: map[string]*configschema.Attribute{ 467 "id": {Type: cty.String, Optional: true, Computed: true}, 468 }, 469 }, 470 }, 471 }, 472 DataSources: map[string]providers.Schema{ 473 "test_data": { 474 Block: &configschema.Block{ 475 Attributes: map[string]*configschema.Attribute{ 476 "foo": {Type: cty.String, Optional: true}, 477 }, 478 }, 479 }, 480 }, 481 } 482 483 args := []string{ 484 "-state", statePath, 485 "test_instance.foo", 486 "bar", 487 } 488 if code := c.Run(args); code != 1 { 489 t.Fatalf("bad, wanted error: %d\n\n%s", code, ui.ErrorWriter.String()) 490 } 491 } 492 493 func TestImport_providerConfigWithVarDefault(t *testing.T) { 494 defer testChdir(t, testFixturePath("import-provider-var-default"))() 495 496 statePath := testTempFile(t) 497 498 p := testProvider() 499 ui := new(cli.MockUi) 500 view, _ := testView(t) 501 c := &ImportCommand{ 502 Meta: Meta{ 503 testingOverrides: metaOverridesForProvider(p), 504 Ui: ui, 505 View: view, 506 }, 507 } 508 509 p.ImportResourceStateFn = nil 510 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 511 ImportedResources: []providers.ImportedResource{ 512 { 513 TypeName: "test_instance", 514 State: cty.ObjectVal(map[string]cty.Value{ 515 "id": cty.StringVal("yay"), 516 }), 517 }, 518 }, 519 } 520 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 521 Provider: providers.Schema{ 522 Block: &configschema.Block{ 523 Attributes: map[string]*configschema.Attribute{ 524 "foo": {Type: cty.String, Optional: true}, 525 }, 526 }, 527 }, 528 ResourceTypes: map[string]providers.Schema{ 529 "test_instance": { 530 Block: &configschema.Block{ 531 Attributes: map[string]*configschema.Attribute{ 532 "id": {Type: cty.String, Optional: true, Computed: true}, 533 }, 534 }, 535 }, 536 }, 537 } 538 539 configured := false 540 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse { 541 var diags tfdiags.Diagnostics 542 configured = true 543 if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) { 544 diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want)) 545 } 546 return providers.ConfigureProviderResponse{ 547 Diagnostics: diags, 548 } 549 } 550 551 args := []string{ 552 "-state", statePath, 553 "test_instance.foo", 554 "bar", 555 } 556 if code := c.Run(args); code != 0 { 557 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 558 } 559 560 // Verify that we were called 561 if !configured { 562 t.Fatal("Configure should be called") 563 } 564 565 if !p.ImportResourceStateCalled { 566 t.Fatal("ImportResourceState should be called") 567 } 568 569 testStateOutput(t, statePath, testImportStr) 570 } 571 572 func TestImport_providerConfigWithVarFile(t *testing.T) { 573 defer testChdir(t, testFixturePath("import-provider-var-file"))() 574 575 statePath := testTempFile(t) 576 577 p := testProvider() 578 ui := new(cli.MockUi) 579 view, _ := testView(t) 580 c := &ImportCommand{ 581 Meta: Meta{ 582 testingOverrides: metaOverridesForProvider(p), 583 Ui: ui, 584 View: view, 585 }, 586 } 587 588 p.ImportResourceStateFn = nil 589 p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{ 590 ImportedResources: []providers.ImportedResource{ 591 { 592 TypeName: "test_instance", 593 State: cty.ObjectVal(map[string]cty.Value{ 594 "id": cty.StringVal("yay"), 595 }), 596 }, 597 }, 598 } 599 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 600 Provider: providers.Schema{ 601 Block: &configschema.Block{ 602 Attributes: map[string]*configschema.Attribute{ 603 "foo": {Type: cty.String, Optional: true}, 604 }, 605 }, 606 }, 607 ResourceTypes: map[string]providers.Schema{ 608 "test_instance": { 609 Block: &configschema.Block{ 610 Attributes: map[string]*configschema.Attribute{ 611 "id": {Type: cty.String, Optional: true, Computed: true}, 612 }, 613 }, 614 }, 615 }, 616 } 617 618 configured := false 619 p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) providers.ConfigureProviderResponse { 620 var diags tfdiags.Diagnostics 621 configured = true 622 if got, want := req.Config.GetAttr("foo"), cty.StringVal("bar"); !want.RawEquals(got) { 623 diags = diags.Append(fmt.Errorf("wrong \"foo\" value %#v; want %#v", got, want)) 624 } 625 return providers.ConfigureProviderResponse{ 626 Diagnostics: diags, 627 } 628 } 629 630 args := []string{ 631 "-state", statePath, 632 "-var-file", "blah.tfvars", 633 "test_instance.foo", 634 "bar", 635 } 636 if code := c.Run(args); code != 0 { 637 t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) 638 } 639 640 // Verify that we were called 641 if !configured { 642 t.Fatal("Configure should be called") 643 } 644 645 if !p.ImportResourceStateCalled { 646 t.Fatal("ImportResourceState should be called") 647 } 648 649 testStateOutput(t, statePath, testImportStr) 650 } 651 652 func TestImport_emptyConfig(t *testing.T) { 653 defer testChdir(t, testFixturePath("empty"))() 654 655 statePath := testTempFile(t) 656 657 p := testProvider() 658 ui := new(cli.MockUi) 659 view, _ := testView(t) 660 c := &ImportCommand{ 661 Meta: Meta{ 662 testingOverrides: metaOverridesForProvider(p), 663 Ui: ui, 664 View: view, 665 }, 666 } 667 668 args := []string{ 669 "-state", statePath, 670 "test_instance.foo", 671 "bar", 672 } 673 code := c.Run(args) 674 if code != 1 { 675 t.Fatalf("import succeeded; expected failure") 676 } 677 678 msg := ui.ErrorWriter.String() 679 if want := `No OpenTofu configuration files`; !strings.Contains(msg, want) { 680 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 681 } 682 } 683 684 func TestImport_missingResourceConfig(t *testing.T) { 685 defer testChdir(t, testFixturePath("import-missing-resource-config"))() 686 687 statePath := testTempFile(t) 688 689 p := testProvider() 690 ui := new(cli.MockUi) 691 view, _ := testView(t) 692 c := &ImportCommand{ 693 Meta: Meta{ 694 testingOverrides: metaOverridesForProvider(p), 695 Ui: ui, 696 View: view, 697 }, 698 } 699 700 args := []string{ 701 "-state", statePath, 702 "test_instance.foo", 703 "bar", 704 } 705 code := c.Run(args) 706 if code != 1 { 707 t.Fatalf("import succeeded; expected failure") 708 } 709 710 msg := ui.ErrorWriter.String() 711 if want := `resource address "test_instance.foo" does not exist`; !strings.Contains(msg, want) { 712 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 713 } 714 } 715 716 func TestImport_missingModuleConfig(t *testing.T) { 717 defer testChdir(t, testFixturePath("import-missing-resource-config"))() 718 719 statePath := testTempFile(t) 720 721 p := testProvider() 722 ui := new(cli.MockUi) 723 view, _ := testView(t) 724 c := &ImportCommand{ 725 Meta: Meta{ 726 testingOverrides: metaOverridesForProvider(p), 727 Ui: ui, 728 View: view, 729 }, 730 } 731 732 args := []string{ 733 "-state", statePath, 734 "module.baz.test_instance.foo", 735 "bar", 736 } 737 code := c.Run(args) 738 if code != 1 { 739 t.Fatalf("import succeeded; expected failure") 740 } 741 742 msg := ui.ErrorWriter.String() 743 if want := `module.baz is not defined in the configuration`; !strings.Contains(msg, want) { 744 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 745 } 746 } 747 748 func TestImportModuleVarFile(t *testing.T) { 749 td := t.TempDir() 750 testCopyDir(t, testFixturePath("import-module-var-file"), td) 751 defer testChdir(t, td)() 752 753 statePath := testTempFile(t) 754 755 p := testProvider() 756 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 757 ResourceTypes: map[string]providers.Schema{ 758 "test_instance": { 759 Block: &configschema.Block{ 760 Attributes: map[string]*configschema.Attribute{ 761 "foo": {Type: cty.String, Optional: true}, 762 }, 763 }, 764 }, 765 }, 766 } 767 768 providerSource, close := newMockProviderSource(t, map[string][]string{ 769 "test": []string{"1.2.3"}, 770 }) 771 defer close() 772 773 // init to install the module 774 ui := new(cli.MockUi) 775 view, _ := testView(t) 776 m := Meta{ 777 testingOverrides: metaOverridesForProvider(testProvider()), 778 Ui: ui, 779 View: view, 780 ProviderSource: providerSource, 781 } 782 783 ic := &InitCommand{ 784 Meta: m, 785 } 786 if code := ic.Run([]string{}); code != 0 { 787 t.Fatalf("init failed\n%s", ui.ErrorWriter) 788 } 789 790 // import 791 ui = new(cli.MockUi) 792 c := &ImportCommand{ 793 Meta: Meta{ 794 testingOverrides: metaOverridesForProvider(p), 795 Ui: ui, 796 View: view, 797 }, 798 } 799 args := []string{ 800 "-state", statePath, 801 "module.child.test_instance.foo", 802 "bar", 803 } 804 code := c.Run(args) 805 if code != 0 { 806 t.Fatalf("import failed; expected success") 807 } 808 } 809 810 // This test covers an edge case where a module with a complex input variable 811 // of nested objects has an invalid default which is overridden by the calling 812 // context, and is used in locals. If we don't evaluate module call variables 813 // for the import walk, this results in an error. 814 // 815 // The specific example has a variable "foo" which is a nested object: 816 // 817 // foo = { bar = { baz = true } } 818 // 819 // This is used as foo = var.foo in the call to the child module, which then 820 // uses the traversal foo.bar.baz in a local. A default value in the child 821 // module of {} causes this local evaluation to error, breaking import. 822 func TestImportModuleInputVariableEvaluation(t *testing.T) { 823 td := t.TempDir() 824 testCopyDir(t, testFixturePath("import-module-input-variable"), td) 825 defer testChdir(t, td)() 826 827 statePath := testTempFile(t) 828 829 p := testProvider() 830 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 831 ResourceTypes: map[string]providers.Schema{ 832 "test_instance": { 833 Block: &configschema.Block{ 834 Attributes: map[string]*configschema.Attribute{ 835 "foo": {Type: cty.String, Optional: true}, 836 }, 837 }, 838 }, 839 }, 840 } 841 842 providerSource, close := newMockProviderSource(t, map[string][]string{ 843 "test": {"1.2.3"}, 844 }) 845 defer close() 846 847 // init to install the module 848 ui := new(cli.MockUi) 849 view, _ := testView(t) 850 m := Meta{ 851 testingOverrides: metaOverridesForProvider(testProvider()), 852 Ui: ui, 853 View: view, 854 ProviderSource: providerSource, 855 } 856 857 ic := &InitCommand{ 858 Meta: m, 859 } 860 if code := ic.Run([]string{}); code != 0 { 861 t.Fatalf("init failed\n%s", ui.ErrorWriter) 862 } 863 864 // import 865 ui = new(cli.MockUi) 866 c := &ImportCommand{ 867 Meta: Meta{ 868 testingOverrides: metaOverridesForProvider(p), 869 Ui: ui, 870 View: view, 871 }, 872 } 873 args := []string{ 874 "-state", statePath, 875 "module.child.test_instance.foo", 876 "bar", 877 } 878 code := c.Run(args) 879 if code != 0 { 880 t.Fatalf("import failed; expected success") 881 } 882 } 883 884 func TestImport_dataResource(t *testing.T) { 885 defer testChdir(t, testFixturePath("import-missing-resource-config"))() 886 887 statePath := testTempFile(t) 888 889 p := testProvider() 890 ui := new(cli.MockUi) 891 view, _ := testView(t) 892 c := &ImportCommand{ 893 Meta: Meta{ 894 testingOverrides: metaOverridesForProvider(p), 895 Ui: ui, 896 View: view, 897 }, 898 } 899 900 args := []string{ 901 "-state", statePath, 902 "data.test_data_source.foo", 903 "bar", 904 } 905 code := c.Run(args) 906 if code != 1 { 907 t.Fatalf("import succeeded; expected failure") 908 } 909 910 msg := ui.ErrorWriter.String() 911 if want := `A managed resource address is required`; !strings.Contains(msg, want) { 912 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 913 } 914 } 915 916 func TestImport_invalidResourceAddr(t *testing.T) { 917 defer testChdir(t, testFixturePath("import-missing-resource-config"))() 918 919 statePath := testTempFile(t) 920 921 p := testProvider() 922 ui := new(cli.MockUi) 923 view, _ := testView(t) 924 c := &ImportCommand{ 925 Meta: Meta{ 926 testingOverrides: metaOverridesForProvider(p), 927 Ui: ui, 928 View: view, 929 }, 930 } 931 932 args := []string{ 933 "-state", statePath, 934 "bananas", 935 "bar", 936 } 937 code := c.Run(args) 938 if code != 1 { 939 t.Fatalf("import succeeded; expected failure") 940 } 941 942 msg := ui.ErrorWriter.String() 943 if want := `Error: Invalid address`; !strings.Contains(msg, want) { 944 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 945 } 946 } 947 948 func TestImport_targetIsModule(t *testing.T) { 949 defer testChdir(t, testFixturePath("import-missing-resource-config"))() 950 951 statePath := testTempFile(t) 952 953 p := testProvider() 954 ui := new(cli.MockUi) 955 view, _ := testView(t) 956 c := &ImportCommand{ 957 Meta: Meta{ 958 testingOverrides: metaOverridesForProvider(p), 959 Ui: ui, 960 View: view, 961 }, 962 } 963 964 args := []string{ 965 "-state", statePath, 966 "module.foo", 967 "bar", 968 } 969 code := c.Run(args) 970 if code != 1 { 971 t.Fatalf("import succeeded; expected failure") 972 } 973 974 msg := ui.ErrorWriter.String() 975 if want := `Error: Invalid address`; !strings.Contains(msg, want) { 976 t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) 977 } 978 } 979 980 const testImportStr = ` 981 test_instance.foo: 982 ID = yay 983 provider = provider["registry.opentofu.org/hashicorp/test"] 984 `