Skip to content
Closed
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
14 changes: 14 additions & 0 deletions helper/resource/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,14 @@ type ExternalProvider struct {
Source string // the provider source
}

type ImportStateKind byte

const (
ImportCommandWithId ImportStateKind = iota
ImportBlockWithId
ImportBlockWithResourceIdentity
)

// TestStep is a single apply sequence of a test, done within the
// context of a state.
//
Expand Down Expand Up @@ -633,6 +641,12 @@ type TestStep struct {
// ID of that resource.
ImportState bool

ImportStateKind ImportStateKind // or ImportStateStrategy or ImportStateSubmode or ImportStateFlavor or ...

// ImportStateBlockConfig, if non-empty, supplies declarative import
// configuration. This is (?mutually exclusive of ImportStateID + ResourceName?).
ImportStateBlockConfig string
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If we can generate config on our first try, then the usefulness of this is secondary.


// 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.
Expand Down
6 changes: 4 additions & 2 deletions helper/resource/testing_new.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,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 appliedMergedCfg string
var stepNumber int

for stepIndex, step := range c.Steps {
Expand Down Expand Up @@ -289,7 +290,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, appliedMergedCfg, providers, stepIndex)
if step.ExpectError != nil {
logging.HelperResourceDebug(ctx, "Checking TestStep ExpectError")
if err == nil {
Expand Down Expand Up @@ -446,7 +447,8 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest
},
}.Exec()

appliedCfg = teststep.Configuration(confRequest)
appliedCfg = teststep.Configuration(confRequest) // magical
appliedMergedCfg = mergedConfig

logging.HelperResourceDebug(ctx, "Finished TestStep")

Expand Down
115 changes: 115 additions & 0 deletions helper/resource/testing_new_import_block_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package resource

import (
"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_ImportBlockVerify(t *testing.T) {
t.Parallel()

UnitTest(t, TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
// import blocks are only available in v1.5.0 and later
tfversion.SkipBelow(tfversion.Version1_5_0),
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"examplecloud": providerserver.NewProviderServer(testprovider.Provider{
Resources: map[string]testprovider.Resource{
"examplecloud_bucket": exampleCloudBucketResource(t),
},
}),
},
Steps: []TestStep{
{
Config: `
resource "examplecloud_bucket" "storage" {
bucket = "test-bucket"
description = "A bucket for testing."
}`,
},
{
ImportState: true,
ImportStateKind: ImportBlockWithResourceIdentity,
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: "bucket", // upgrade to resource identity
ResourceName: "examplecloud_bucket.storage",
},
},
})
}

func exampleCloudBucketResource(t *testing.T) testprovider.Resource {
t.Helper()

return testprovider.Resource{
CreateResponse: &resource.CreateResponse{
NewResourceIdentityData: tftypes.NewValue(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

New and shiny!

tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"region": tftypes.String,
"bucket": tftypes.String,
},
},
map[string]tftypes.Value{
"region": tftypes.NewValue(tftypes.String, "test-region"),
"bucket": tftypes.NewValue(tftypes.String, "test-bucket"),
},
),
NewState: tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bucket": tftypes.String,
"description": tftypes.String,
},
},
map[string]tftypes.Value{
"bucket": tftypes.NewValue(tftypes.String, "test-bucket"),
"description": tftypes.NewValue(tftypes.String, "A bucket for testing."),
},
),
},
ImportStateResponse: &resource.ImportStateResponse{
State: tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bucket": tftypes.String,
"description": tftypes.String,
},
},
map[string]tftypes.Value{
"bucket": tftypes.NewValue(tftypes.String, "test-bucket"),
"description": tftypes.NewValue(tftypes.String, "A bucket for testing."),
},
),
},
SchemaResponse: &resource.SchemaResponse{
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
{
Name: "bucket",
Type: tftypes.String,
Required: true,
},
{
Name: "description",
Type: tftypes.String,
Optional: true,
},
},
},
},
},
}
}
79 changes: 58 additions & 21 deletions helper/resource/testing_new_import_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"github.com/hashicorp/terraform-plugin-testing/internal/plugintest"
)

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, cfg teststep.Config, cfgHcl string, providers *providerFactories, stepIndex int) error {
t.Helper()

configRequest := teststep.PrepareConfigurationRequest{
Expand All @@ -31,9 +31,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest
StepNumber: stepIndex + 1,
TestName: t.Name(),
},
}.Exec()

testStepConfig := teststep.Configuration(configRequest)
}

if step.ResourceName == "" {
t.Fatal("ResourceName is required for an import state test")
Expand Down Expand Up @@ -93,13 +91,24 @@ 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")

testStepConfig = cfg
var testStepConfig teststep.Config
if step.ImportStateKind == ImportBlockWithResourceIdentity {
cfgHcl += `
import {
to = examplecloud_bucket.storage
id = "test-bucket" // to be replaced with identity
}`
configRequest.Raw = cfgHcl
testStepConfig = teststep.Configuration(configRequest.Exec())
} else {
// Create working directory for import tests
if testStepConfig == nil {
t.Fatal("Cannot import state with no specified config")
logging.HelperResourceTrace(ctx, "Using prior TestStep Config for import")

testStepConfig = cfg
if testStepConfig == nil {
t.Fatal("Cannot import state with no specified config")
}
}
}

Expand All @@ -118,22 +127,50 @@ 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")
switch step.ImportStateKind {
case ImportCommandWithId:
logging.HelperResourceDebug(ctx, "Running Terraform CLI init and import")

if !step.ImportStatePersist {
err = runProviderCommand(ctx, t, func() error {
return importWd.Init(ctx)
}, importWd, providers)
if err != nil {
t.Fatalf("Error running init: %s", err)
}
}

if !step.ImportStatePersist {
err = runProviderCommand(ctx, t, func() error {
return importWd.Init(ctx)
return importWd.Import(ctx, step.ResourceName, importId)
}, importWd, providers)
if err != nil {
t.Fatalf("Error running init: %s", err)
return err
}
}

err = runProviderCommand(ctx, t, func() error {
return importWd.Import(ctx, step.ResourceName, importId)
}, importWd, providers)
if err != nil {
return err
case ImportBlockWithId:
t.Fatalf("not yet implemented")
case ImportBlockWithResourceIdentity:
if !step.ImportStatePersist {
err = runProviderCommand(ctx, t, func() error {
return importWd.Init(ctx)
}, importWd, providers)
if err != nil {
t.Fatalf("Error running init: %s", err)
}
}
err = runProviderCommand(ctx, t, func() error {
return importWd.CreatePlan(ctx)
}, importWd, providers)
if err != nil {
t.Fatalf("Error running plan: %s", err)
}
err = runProviderCommand(ctx, t, func() error {
return importWd.Apply(ctx)
}, importWd, providers)
if err != nil {
t.Fatalf("Error running apply: %s", err)
}
default:
t.Fatalf(`\o/`)
}

var importState *terraform.State
Expand Down
12 changes: 10 additions & 2 deletions internal/testing/testsdk/resource/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ type CreateRequest struct {
}

type CreateResponse struct {
Diagnostics []*tfprotov6.Diagnostic
NewState tftypes.Value
Diagnostics []*tfprotov6.Diagnostic
NewState tftypes.Value
NewResourceIdentityData tftypes.Value
}

type DeleteRequest struct {
Expand Down Expand Up @@ -70,6 +71,13 @@ type ReadResponse struct {
NewState tftypes.Value
}

type ResourceIdentitySchemaRequest struct{}

type ResourceIdentitySchemaResponse struct {
Diagnostics []*tfprotov6.Diagnostic
ResourceIdentitySchema tftypes.Value // *tfprotov6.Schema
}

type SchemaRequest struct{}

type SchemaResponse struct {
Expand Down
Loading