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
24 changes: 24 additions & 0 deletions helper/resource/importstate/examplecloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -596,3 +596,27 @@ func examplecloudResourceWithNullIdentityAttr() testprovider.Resource {
},
}
}

// This example resource, on update plans, will plan a different identity to test that
// our testing framework assertions catch an identity that differs after import/refresh.
func examplecloudResourceWithChangingIdentity() testprovider.Resource {
exampleCloudResource := examplecloudResource()

exampleCloudResource.PlanChangeFunc = func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) {
// Only on update
if !req.PriorState.IsNull() && !req.ProposedNewState.IsNull() {
resp.PlannedIdentity = teststep.Pointer(tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"id": tftypes.String,
},
},
map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "easteurope/someothervalue"),
},
))
}
}

return exampleCloudResource
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,39 @@ func TestImportBlock_WithResourceIdentity_WithEveryType(t *testing.T) {
})
}

func TestImportBlock_WithResourceIdentity_ChangingIdentityError(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_12_0), // ImportBlockWithResourceIdentity requires Terraform 1.12.0 or later
},
ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){
"examplecloud": providerserver.NewProviderServer(testprovider.Provider{
Resources: map[string]testprovider.Resource{
"examplecloud_container": examplecloudResourceWithChangingIdentity(),
},
}),
},
Steps: []r.TestStep{
{
Config: `
resource "examplecloud_container" "test" {
location = "westeurope"
name = "somevalue"
}`,
},
{
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithResourceIdentity,
// The plan following the import will produce a different identity value then test step 1
ExpectError: regexp.MustCompile(`expected identity values map\[id:westeurope/somevalue\], got map\[id:easteurope/someothervalue\]`),
},
},
})
}

func TestImportBlock_WithResourceIdentity_RequiresVersion1_12_0(t *testing.T) {
t.Parallel()

Expand Down
26 changes: 0 additions & 26 deletions helper/resource/plugin.go
Copy link
Member Author

Choose a reason for hiding this comment

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

These changes are just to satisfy the linter since they are no longer used 👍🏻

Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,6 @@ type providerFactories struct {
protov6 protov6ProviderFactories
}

func runProviderCommandApply(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) error {
t.Helper()

fn := func() error {
return wd.Apply(ctx)
}
return runProviderCommand(ctx, t, wd, factories, fn)
}

func runProviderCommandCreatePlan(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) error {
t.Helper()

Expand All @@ -132,23 +123,6 @@ func runProviderCommandCreatePlan(ctx context.Context, t testing.T, wd *pluginte
return runProviderCommand(ctx, t, wd, factories, fn)
}

func runProviderCommandGetStateJSON(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) (*tfjson.State, error) {
t.Helper()

var stateJSON *tfjson.State
fn := func() error {
var err error
stateJSON, err = wd.State(ctx)
return err
}
err := runProviderCommand(ctx, t, wd, factories, fn)
if err != nil {
return nil, err
}

return stateJSON, nil
}

func runProviderCommandSavedPlan(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) (*tfjson.Plan, error) {
t.Helper()

Expand Down
32 changes: 7 additions & 25 deletions helper/resource/testing_new_import_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest
var priorIdentityValues map[string]any

if kind.plannable() && kind.resourceIdentity() {
priorIdentityValues = identityValuesFromState(stateJSON, resourceName)
priorIdentityValues = identityValuesFromStateValues(stateJSON.Values, resourceName)
if len(priorIdentityValues) == 0 {
return fmt.Errorf("importing resource %s: expected prior state to have resource identity values, got none", resourceName)
}
Expand Down Expand Up @@ -231,32 +231,15 @@ func testImportBlock(ctx context.Context, t testing.T, workingDir *plugintest.Wo
}

if kind.resourceIdentity() {
if err := verifyIdentityValues(ctx, t, workingDir, providers, resourceName, priorIdentityValues); err != nil {
return err
newIdentityValues := identityValuesFromStateValues(plan.PlannedValues, resourceName)
if !cmp.Equal(priorIdentityValues, newIdentityValues) {
return fmt.Errorf("importing resource %s: expected identity values %v, got %v", resourceName, priorIdentityValues, newIdentityValues)
}
}

return nil
}

func verifyIdentityValues(ctx context.Context, t testing.T, workingDir *plugintest.WorkingDir, providers *providerFactories, resourceName string, priorIdentityValues map[string]any) error {
err := runProviderCommandApply(ctx, t, workingDir, providers)
if err != nil {
return fmt.Errorf("applying plan with import config: %s", err)
}

newStateJSON, err := runProviderCommandGetStateJSON(ctx, t, workingDir, providers)
if err != nil {
return fmt.Errorf("getting state after applying plan with import config: %s", err)
}

newIdentityValues := identityValuesFromState(newStateJSON, resourceName)
if !cmp.Equal(priorIdentityValues, newIdentityValues) {
return fmt.Errorf("importing resource %s: expected identity values %v, got %v", resourceName, priorIdentityValues, newIdentityValues)
}
return nil
}

func testImportCommand(ctx context.Context, t testing.T, workingDir *plugintest.WorkingDir, providers *providerFactories, resourceName string, importId string, step TestStep, state *terraform.State) error {
err := runProviderCommand(ctx, t, workingDir, providers, func() error {
return workingDir.Import(ctx, resourceName, importId)
Expand Down Expand Up @@ -515,18 +498,17 @@ func importStatePreconditions(t testing.T, helper *plugintest.Helper, step TestS
return nil
}

func resourcesFromState(state *tfjson.State) []*tfjson.StateResource {
stateValues := state.Values
func resourcesFromState(stateValues *tfjson.StateValues) []*tfjson.StateResource {
if stateValues == nil || stateValues.RootModule == nil {
return []*tfjson.StateResource{}
}

return stateValues.RootModule.Resources
}

func identityValuesFromState(state *tfjson.State, resourceName string) map[string]any {
func identityValuesFromStateValues(stateValues *tfjson.StateValues, resourceName string) map[string]any {
var resource *tfjson.StateResource
resources := resourcesFromState(state)
resources := resourcesFromState(stateValues)

for _, r := range resources {
if r.Address == resourceName {
Expand Down