Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions helper/resource/importstate/import_block_as_first_step_test.go
Original file line number Diff line number Diff line change
@@ -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")),
},
},
},
},
})
}
2 changes: 1 addition & 1 deletion helper/resource/importstate/import_block_with_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
Original file line number Diff line number Diff line change
@@ -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
},
},
},
})
}
14 changes: 14 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
17 changes: 14 additions & 3 deletions helper/resource/testing_new_import_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,30 +178,31 @@ 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 {
t.Fatalf("Error running init: %s", err)
}
}

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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Should we wrap the error messages produced by the plan checks for readability?

    return fmt.Errorf("Import plan check(s) failed:\n%w", err)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Noted for a follow-up PR -- it will be easier to see the current picture after we merge this & the two PRs "stacked" atop it.

}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this PR, but I noticed that we don't immediately return after running this import/plan and we probably should? Or wrap the remaining of the logic in this function in the following else block.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Noted for a follow-up PR -- it will be easier to see the current picture after we merge this & the two PRs "stacked" atop it.

} 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 {
Expand All @@ -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")
Expand Down
Loading