diff --git a/helper/resource/importstate/import_block_as_first_step_test.go b/helper/resource/importstate/import_block_as_first_step_test.go new file mode 100644 index 000000000..59664baa5 --- /dev/null +++ b/helper/resource/importstate/import_block_as_first_step_test.go @@ -0,0 +1,58 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package importstate_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "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/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func Test_ImportBlock_AsFirstStep(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_5_0), // ImportBlockWithId requires Terraform 1.5.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ResourceName: "examplecloud_container.test", + ImportStateId: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportBlockWithId, + // ImportStateVerify: true, + Config: `resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" + }`, + ImportStatePersist: true, + ImportPlanChecks: r.ImportPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("examplecloud_container.test", plancheck.ResourceActionNoop), + plancheck.ExpectKnownValue("examplecloud_container.test", tfjsonpath.New("id"), knownvalue.StringExact("westeurope/somevalue")), + plancheck.ExpectKnownValue("examplecloud_container.test", tfjsonpath.New("name"), knownvalue.StringExact("somevalue")), + plancheck.ExpectKnownValue("examplecloud_container.test", tfjsonpath.New("location"), knownvalue.StringExact("westeurope")), + }, + }, + }, + }, + }) +} diff --git a/helper/resource/importstate/import_block_with_id_test.go b/helper/resource/importstate/import_block_with_id_test.go index 82d949728..e30717772 100644 --- a/helper/resource/importstate/import_block_with_id_test.go +++ b/helper/resource/importstate/import_block_with_id_test.go @@ -21,7 +21,7 @@ import ( r "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -func TestTest_TestStep_ImportBlockId(t *testing.T) { +func Test_TestStep_ImportBlockId(t *testing.T) { t.Parallel() r.UnitTest(t, r.TestCase{ diff --git a/helper/resource/importstate/import_command_as_first_step_test.go b/helper/resource/importstate/import_command_as_first_step_test.go new file mode 100644 index 000000000..2193dfc7e --- /dev/null +++ b/helper/resource/importstate/import_command_as_first_step_test.go @@ -0,0 +1,64 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package importstate_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "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/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func Test_ImportCommand_AsFirstStep(t *testing.T) { + t.Parallel() + + r.UnitTest(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // ProtoV6ProviderFactories needs Terraform 1.0.0 or later + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "examplecloud": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "examplecloud_container": examplecloudResource(), + }, + }), + }, + Steps: []r.TestStep{ + { + ResourceName: "examplecloud_container.test", + ImportStateId: "examplecloud_container.test", + ImportState: true, + ImportStateKind: r.ImportCommandWithId, + // ImportStateVerify: true, + Config: `resource "examplecloud_container" "test" { + name = "somevalue" + location = "westeurope" + }`, + ImportStatePersist: true, + ImportStateCheck: func(states []*terraform.InstanceState) error { + if len(states) != 1 { + return fmt.Errorf("expected 1 state; got %d", len(states)) + } + if states[0].ID != "westeurope/somevalue" { + return fmt.Errorf("unexpected ID: %s", states[0].ID) + } + if states[0].Attributes["name"] != "somevalue" { + return fmt.Errorf("unexpected name: %s", states[0].Attributes["name"]) + } + if states[0].Attributes["location"] != "westeurope" { + return fmt.Errorf("unexpected location: %s", states[0].Attributes["location"]) + } + return nil + }, + }, + }, + }) +} diff --git a/helper/resource/testing.go b/helper/resource/testing.go index 14774ca58..2a3d8db96 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -679,6 +679,13 @@ type TestStep struct { // Terraform version specific logic in provider testing. ImportStateCheck ImportStateCheckFunc + // ImportPlanChecks allows assertions to be made against the plan file at different points of a plannable import test using a plan check. + // Custom plan checks can be created by implementing the [PlanCheck] interface, or by using a PlanCheck implementation from the provided [plancheck] package + // + // [PlanCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck + // [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck + ImportPlanChecks ImportPlanChecks + // ImportStateVerify, if true, will also check that the state values // that are finally put into the state after import match for all the // IDs returned by the Import. Note that this checks for strict equality @@ -810,6 +817,13 @@ type ConfigPlanChecks struct { PostApplyPostRefresh []plancheck.PlanCheck } +// ImportPlanChecks defines the different points in an Import TestStep when plan checks can be run. +type ImportPlanChecks struct { + // PreApply runs all plan checks in the slice. This occurs after the plan of an Import test is computed. This slice cannot be populated + // with TestStep.PlanOnly, as there is no PreApply plan run with that flag set. All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PreApply []plancheck.PlanCheck +} + // RefreshPlanChecks defines the different points in a Refresh TestStep when plan checks can be run. type RefreshPlanChecks struct { // PostRefresh runs all plan checks in the slice. This occurs after the refresh of the Refresh test is run. diff --git a/helper/resource/testing_new_import_state.go b/helper/resource/testing_new_import_state.go index 5cd4f50e8..8d02d3c62 100644 --- a/helper/resource/testing_new_import_state.go +++ b/helper/resource/testing_new_import_state.go @@ -178,10 +178,9 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest t.Fatalf("Error setting test config: %s", err) } - logging.HelperResourceDebug(ctx, "Running Terraform CLI init and import") - if !step.ImportStatePersist { err = runProviderCommand(ctx, t, func() error { + logging.HelperResourceDebug(ctx, "Run terraform init") return importWd.Init(ctx) }, importWd, providers) if err != nil { @@ -189,19 +188,21 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } } + var plan *tfjson.Plan if step.ImportStateKind == ImportBlockWithResourceIdentity || step.ImportStateKind == ImportBlockWithId { var opts []tfexec.PlanOption err = runProviderCommand(ctx, t, func() error { + logging.HelperResourceDebug(ctx, "Run terraform plan") 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 + logging.HelperResourceDebug(ctx, "Run terraform show") plan, err = importWd.SavedPlan(ctx) return err }, importWd, providers) @@ -210,6 +211,8 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } if plan.ResourceChanges != nil { + logging.HelperResourceDebug(ctx, fmt.Sprintf("ImportBlockWithId: %d resource changes", len(plan.ResourceChanges))) + for _, rc := range plan.ResourceChanges { if rc.Address != step.ResourceName { // we're only interested in the changes for the resource being imported @@ -237,8 +240,14 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest } // TODO compare plan to state from previous step + + if err := runPlanChecks(ctx, t, plan, step.ImportPlanChecks.PreApply); err != nil { + return err + } + } else { err = runProviderCommand(ctx, t, func() error { + logging.HelperResourceDebug(ctx, "Run terraform import") return importWd.Import(ctx, step.ResourceName, importId) }, importWd, providers) if err != nil { @@ -258,6 +267,8 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest t.Fatalf("Error getting state: %s", err) } + logging.HelperResourceDebug(ctx, fmt.Sprintf("State after import: %d resources in the root module", len(importState.RootModule().Resources))) + // Go through the imported state and verify if step.ImportStateCheck != nil { logging.HelperResourceTrace(ctx, "Using TestStep ImportStateCheck")