diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 9e1961a46..c7bdcd75e 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -454,6 +454,17 @@ type ExternalProvider struct { Source string // the provider source } +type ImportStateKind byte + +const ( + // ImportCommandWithId imports the state using the import command + ImportCommandWithId ImportStateKind = iota + // ImportBlockWithId imports the state using an import block with an ID + ImportBlockWithId + // ImportBlockWithResourceIdentity imports the state using an import block with a resource identity + ImportBlockWithResourceIdentity +) + // TestStep is a single apply sequence of a test, done within the // context of a state. // @@ -633,6 +644,8 @@ type TestStep struct { // ID of that resource. ImportState bool + ImportStateKind ImportStateKind + // ImportStateId is the ID to perform an ImportState operation with. // This is optional. If it isn't set, then the resource ID is automatically // determined by inspecting the state for ResourceName's ID. diff --git a/helper/resource/testing_new.go b/helper/resource/testing_new.go index 0a7c7e7f7..8e0bc2167 100644 --- a/helper/resource/testing_new.go +++ b/helper/resource/testing_new.go @@ -133,7 +133,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest // use this to track last step successfully applied // acts as default for import tests - var appliedCfg teststep.Config + var appliedCfg string var stepNumber int for stepIndex, step := range c.Steps { @@ -249,7 +249,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest File: step.ConfigFile, Raw: rawCfg, TestStepConfigRequest: config.TestStepConfigRequest{ - StepNumber: stepIndex + 1, + StepNumber: stepNumber, TestName: t.Name(), }, }.Exec() @@ -289,7 +289,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest if step.ImportState { logging.HelperResourceTrace(ctx, "TestStep is ImportState mode") - err := testStepNewImportState(ctx, t, helper, wd, step, appliedCfg, providers, stepIndex) + err := testStepNewImportState(ctx, t, helper, wd, step, appliedCfg, providers, stepNumber) if step.ExpectError != nil { logging.HelperResourceDebug(ctx, "Checking TestStep ExpectError") if err == nil { @@ -426,7 +426,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest } } - mergedConfig, err := step.mergedConfig(ctx, c, hasTerraformBlock, hasProviderBlock, helper.TerraformVersion()) + appliedCfg, err = step.mergedConfig(ctx, c, hasTerraformBlock, hasProviderBlock, helper.TerraformVersion()) if err != nil { logging.HelperResourceError(ctx, @@ -436,18 +436,6 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest t.Fatalf("Error generating merged configuration: %s", err) } - confRequest := teststep.PrepareConfigurationRequest{ - Directory: step.ConfigDirectory, - File: step.ConfigFile, - Raw: mergedConfig, - TestStepConfigRequest: config.TestStepConfigRequest{ - StepNumber: stepIndex + 1, - TestName: t.Name(), - }, - }.Exec() - - appliedCfg = teststep.Configuration(confRequest) - logging.HelperResourceDebug(ctx, "Finished TestStep") continue diff --git a/helper/resource/testing_new_import_block_id_test.go b/helper/resource/testing_new_import_block_id_test.go new file mode 100644 index 000000000..51219b77b --- /dev/null +++ b/helper/resource/testing_new_import_block_id_test.go @@ -0,0 +1,549 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/datasource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestTest_TestStep_ImportBlockId(t *testing.T) { + t.Parallel() + + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ReadResponse: &resource.ReadResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ImportStateResponse: &resource.ImportStateResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "location", + Type: tftypes.String, + Required: true, + }, + { + Name: "name", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: ` + resource "examplecloud_container" "test" { + location = "westeurope" + name = "somevalue" + }`, + }, + { + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: ImportBlockWithId, + ImportStateVerify: true, + }, + }, + }) +} + +func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) { + t.Parallel() + + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ReadResponse: &resource.ReadResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ImportStateResponse: &resource.ImportStateResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "location": tftypes.String, + "name": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "westeurope/somevalue"), + "location": tftypes.NewValue(tftypes.String, "westeurope"), + "name": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "location", + Type: tftypes.String, + Required: true, + }, + { + Name: "name", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: ` + resource "examplecloud_container" "test" { + location = "westeurope" + name = "somevalue" + }`, + }, + { + Config: ` + resource "examplecloud_container" "test" { + location = "eastus" + name = "somevalue" + }`, + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: ImportBlockWithId, + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test should be a no-op, but got action update with plan(.?)`), + }, + }, + }) +} + +func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) { + t.Parallel() + + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + DataSources: map[string]testprovider.DataSource{ + "examplecloud_thing": { + ReadResponse: &datasource.ReadResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "datasource-test"), + }, + ), + }, + SchemaResponse: &datasource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + Resources: map[string]testprovider.Resource{ + "examplecloud_thing": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "resource-test"), + }, + ), + }, + ImportStateResponse: &resource.ImportStateResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "resource-test"), + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: ` + data "examplecloud_thing" "test" {} + resource "examplecloud_thing" "test" {} + `, + }, + { + ResourceName: "examplecloud_thing.test", + ImportState: true, + ImportStateKind: ImportBlockWithId, + ImportStateCheck: func(is []*terraform.InstanceState) error { + if len(is) > 1 { + return fmt.Errorf("expected 1 state, got: %d", len(is)) + } + + return nil + }, + }, + }, + }) +} + +// These tests currently pass but only because the `getState` function which is used on the imported resource +// to do the state comparison doesn't return an error if there is no state in the working directory +func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *testing.T) { + /* + This test tries to imitate a real world example of behaviour we often see in the AzureRM provider which requires + the use of `ImportStateVerifyIgnore` when testing the import of a resource using the import command. + + A sensitive field e.g. a password can be supplied on create but isn't returned in the API response on a subsequent + read, resulting in a different value for password in the two states. + + In the AzureRM provider this is usually handled one of two ways, both requiring `ImportStateVerifyIgnore` to make + the test pass: + + 1. Property doesn't get set in the read + * in pluginSDK at create the config gets written to state because that's what we're expecting + * the subsequent read updates the values to create a post-apply diff and update computed values + * since we don't do anything to the property in the read the imported resource's state has the password missing + compared to the created resource's state + + 2. We retrieve the value from config and set that into state + * the config isn't available at import time using only the import command (I think?) so there is nothing to + retrieve and set into state when importing + + I also need to omit the `password` in the import config, otherwise the value in the config is used when importing the + with an import block and the test ends up passing regardless of whether `ImportStateVerifyIgnore` has been specified or not + */ + t.Parallel() + + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "name": tftypes.String, + "password": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "sometestid"), + "name": tftypes.NewValue(tftypes.String, "somename"), + "password": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ImportStateResponse: &resource.ImportStateResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "name": tftypes.String, + "password": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "sometestid"), + "name": tftypes.NewValue(tftypes.String, "somename"), + "password": tftypes.NewValue(tftypes.String, nil), // this simulates an absent property when importing + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "name", + Type: tftypes.String, + Required: true, + }, + { + Name: "password", + Type: tftypes.String, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: ` + resource "examplecloud_container" "test" { + name = "somename" + password = "somevalue" + }`, + }, + { + Config: ` + terraform { + required_providers { + examplecloud = { + source = "registry.terraform.io/hashicorp/examplecloud" + } + } + } + + resource "examplecloud_container" "test" { + name = "somename" + }`, + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: ImportBlockWithId, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} + +func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) { + t.Parallel() + + UnitTest(t, TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ProtoV6ProviderFactories + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": { + CreateResponse: &resource.CreateResponse{ + NewState: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "name": tftypes.String, + "password": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "sometestid"), + "name": tftypes.NewValue(tftypes.String, "somename"), + "password": tftypes.NewValue(tftypes.String, "somevalue"), + }, + ), + }, + ImportStateResponse: &resource.ImportStateResponse{ + State: tftypes.NewValue( + tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "id": tftypes.String, + "name": tftypes.String, + "password": tftypes.String, + }, + }, + map[string]tftypes.Value{ + "id": tftypes.NewValue(tftypes.String, "sometestid"), + "name": tftypes.NewValue(tftypes.String, "somename"), + "password": tftypes.NewValue(tftypes.String, nil), // this simulates an absent property when importing + }, + ), + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + { + Name: "name", + Type: tftypes.String, + Computed: true, + }, + { + Name: "password", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []TestStep{ + { + Config: `resource "examplecloud_container" "test" {}`, + }, + { + ResourceName: "examplecloud_container.test", + ImportState: true, + ImportStateKind: ImportBlockWithId, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"password"}, + }, + }, + }) +} diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 7dbc0b800..96be26543 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -6,29 +6,38 @@ package resource import ( "context" "fmt" + tfjson "github.com/hashicorp/terraform-json" "reflect" "strings" "github.com/google/go-cmp/cmp" "github.com/mitchellh/go-testing-interface" + "github.com/hashicorp/terraform-exec/tfexec" "github.com/hashicorp/terraform-plugin-testing/config" - "github.com/hashicorp/terraform-plugin-testing/internal/teststep" - "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-testing/internal/logging" "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, wd *plugintest.WorkingDir, step TestStep, cfg teststep.Config, providers *providerFactories, stepIndex int) error { +func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, wd *plugintest.WorkingDir, step TestStep, cfgRaw string, providers *providerFactories, stepNumber int) error { t.Helper() + // Currently import modes `ImportBlockWithId` and `ImportBlockWithResourceIdentity` cannot support config file or directory + // since these modes append the import block to the configuration automatically + if step.ImportStateKind != ImportCommandWithId { + if step.ConfigFile != nil || step.ConfigDirectory != nil { + t.Fatalf("ImportStateKind %q is not supported for config file or directory", step.ImportStateKind) + } + } + configRequest := teststep.PrepareConfigurationRequest{ Directory: step.ConfigDirectory, File: step.ConfigFile, Raw: step.Config, TestStepConfigRequest: config.TestStepConfigRequest{ - StepNumber: stepIndex + 1, + StepNumber: stepNumber, TestName: t.Name(), }, }.Exec() @@ -93,11 +102,39 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest logging.HelperResourceTrace(ctx, fmt.Sprintf("Using import identifier: %s", importId)) - // Create working directory for import tests - if testStepConfig == nil { - logging.HelperResourceTrace(ctx, "Using prior TestStep Config for import") + if testStepConfig == nil || step.Config != "" { + importConfig := step.Config + if importConfig == "" { + logging.HelperResourceTrace(ctx, "Using prior TestStep Config for import") + importConfig = cfgRaw + } - testStepConfig = cfg + // Update the test config dependent on the kind of import test being performed + switch step.ImportStateKind { + case ImportBlockWithResourceIdentity: + t.Fatalf("TODO implement me") + case ImportBlockWithId: + importConfig += fmt.Sprintf(` + import { + to = %s + id = %q + } + `, step.ResourceName, importId) + default: + // Not an import block test so nothing to do here + } + + confRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: importConfig, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepNumber, + TestName: t.Name(), + }, + }.Exec() + + testStepConfig = teststep.Configuration(confRequest) if testStepConfig == nil { t.Fatal("Cannot import state with no specified config") } @@ -129,11 +166,61 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } } - err = runProviderCommand(ctx, t, func() error { - return importWd.Import(ctx, step.ResourceName, importId) - }, importWd, providers) - if err != nil { - return err + if step.ImportStateKind == ImportBlockWithResourceIdentity || step.ImportStateKind == ImportBlockWithId { + var opts []tfexec.PlanOption + + err = runProviderCommand(ctx, t, func() error { + return importWd.CreatePlan(ctx, opts...) + }, importWd, providers) + if err != nil { + return err + } + + var plan *tfjson.Plan + err = runProviderCommand(ctx, t, func() error { + var err error + plan, err = importWd.SavedPlan(ctx) + return err + }, importWd, providers) + if err != nil { + return err + } + + if plan.ResourceChanges != nil { + for _, rc := range plan.ResourceChanges { + if rc.Address != step.ResourceName { + // we're only interested in the changes for the resource being imported + continue + } + if rc.Change != nil && rc.Change.Actions != nil { + // should this be length checked and used as a condition, if it's a no-op then there shouldn't be any other changes here + for _, action := range rc.Change.Actions { + if action != "no-op" { + var stdout string + err = runProviderCommand(ctx, t, func() error { + var err error + stdout, err = importWd.SavedPlanRawStdout(ctx) + return err + }, importWd, providers) + if err != nil { + return fmt.Errorf("retrieving formatted plan output: %w", err) + } + + return fmt.Errorf("importing resource %s should be a no-op, but got action %s with plan \\nstdout:\\n\\n%s", rc.Address, action, stdout) + } + } + } + } + } + + // TODO compare plan to state from previous step + } else { + err = runProviderCommand(ctx, t, func() error { + return importWd.Import(ctx, step.ResourceName, importId) + }, importWd, providers) + if err != nil { + return err + } } var importState *terraform.State