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
5 changes: 5 additions & 0 deletions .changes/unreleased/BREAKING CHANGES-20250404-170656.yaml
Copy link
Member

Choose a reason for hiding this comment

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

This is less of a nit and more of a discussion our team should probably have at some point.

I'm not really sure how to do the changelogs for alpha/beta releases. I've currently been doing just one or two NOTE changelogs mentioning the general features and not specific deltas between releases, but I can see why you'd want to be more explicit in-case anyone was trying the features out 🤔

Copy link
Member

Choose a reason for hiding this comment

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

Also, I failed to call out the import test changes in the first changelog for alpha, so maybe a general note would be useful.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is less of a nit and more of a discussion our team should probably have at some point.

💯

I can see why you'd want to be more explicit in-case anyone was trying the features out 🤔

Right, I'm thinking to bias toward clarity here. Pre-releases will have partial implementations, so a little expectation-setting can go a long way.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BREAKING CHANGES
body: 'importstate: `ImportStatePersist` and `ImportStateVerify` are not supported for plannable import (`ImportBlockWith*`) and will return an error'
time: 2025-04-04T17:06:56.900935-04:00
custom:
Issue: "476"
5 changes: 5 additions & 0 deletions .changes/unreleased/BREAKING CHANGES-20250404-171048.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BREAKING CHANGES
body: 'importstate: renamed `ImportStateWithId` to `ImportStateWithID` and renamed `ImportCommandWithId` to `ImportCommandWithID`.'
time: 2025-04-04T17:10:48.525611-04:00
custom:
Issue: "465"
5 changes: 5 additions & 0 deletions .changes/unreleased/BUG FIXES-20250404-170734.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: BUG FIXES
body: 'importstate: plannable import (`ImportBlockWith*`) fixed for a resource with a dependency'
time: 2025-04-04T17:07:34.428542-04:00
custom:
Issue: "476"
112 changes: 92 additions & 20 deletions helper/resource/importstate/examplecloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
package importstate_test

import (
"context"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider"
Expand All @@ -29,11 +31,7 @@ func examplecloudDataSource() testprovider.DataSource {
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
{
Name: "id",
Type: tftypes.String,
Computed: true,
},
ComputedStringAttribute("id"),
},
},
},
Expand Down Expand Up @@ -95,21 +93,95 @@ func examplecloudResource() testprovider.Resource {
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,
},
ComputedStringAttribute("id"),
RequiredStringAttribute("location"),
RequiredStringAttribute("name"),
},
},
},
},
}
}

// examplecloudZone is a test resource that mimics a DNS zone resource.
func examplecloudZone() testprovider.Resource {
value := tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"id": tftypes.String,
"name": tftypes.String,
},
},
map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "5381dd14-6d75-4f32-9096-47f5500b1507"),
"name": tftypes.NewValue(tftypes.String, "example.net"),
},
)

return testprovider.Resource{
CreateResponse: &resource.CreateResponse{
NewState: value,
},
ReadResponse: &resource.ReadResponse{
NewState: value,
},
ImportStateResponse: &resource.ImportStateResponse{
State: value,
},
SchemaResponse: &resource.SchemaResponse{
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
ComputedStringAttribute("id"),
RequiredStringAttribute("name"),
},
},
},
},
}
}

// examplecloudZoneRecord is a test resource that mimics a DNS zone record resource.
// It models a resource dependency; specifically, it depends on a DNS zone ID and will
// plan a replacement if the zone ID changes.
func examplecloudZoneRecord() testprovider.Resource {
value := tftypes.NewValue(
tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"id": tftypes.String,
"zone_id": tftypes.String,
"name": tftypes.String,
},
},
map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "f00911be-e188-433d-9ccd-d0393a9f5d05"),
"zone_id": tftypes.NewValue(tftypes.String, "5381dd14-6d75-4f32-9096-47f5500b1507"),
"name": tftypes.NewValue(tftypes.String, "www"),
},
)

return testprovider.Resource{
CreateResponse: &resource.CreateResponse{
NewState: value,
},
PlanChangeFunc: func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) {
resp.RequiresReplace = []*tftypes.AttributePath{
tftypes.NewAttributePath().WithAttributeName("zone_id"),
}
},
ReadResponse: &resource.ReadResponse{
NewState: value,
},
ImportStateResponse: &resource.ImportStateResponse{
State: value,
},
SchemaResponse: &resource.SchemaResponse{
Schema: &tfprotov6.Schema{
Block: &tfprotov6.SchemaBlock{
Attributes: []*tfprotov6.SchemaAttribute{
ComputedStringAttribute("id"),
RequiredStringAttribute("zone_id"),
RequiredStringAttribute("name"),
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func Test_ImportBlock_AsFirstStep(t *testing.T) {
func TestImportBlock_AsFirstStep(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand All @@ -38,12 +38,10 @@ func Test_ImportBlock_AsFirstStep(t *testing.T) {
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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package importstate_test

import (
"testing"

"github.com/hashicorp/terraform-plugin-go/tfprotov6"
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
"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/tfversion"
)

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

config := `
resource "examplecloud_zone" "zone" {
name = "example.net"
}

resource "examplecloud_zone_record" "record" {
zone_id = examplecloud_zone.zone.id
name = "www"
}
`
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_zone": examplecloudZone(),
"examplecloud_zone_record": examplecloudZoneRecord(),
},
}),
},
Steps: []r.TestStep{
{
Config: config,
},
{
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ResourceName: "examplecloud_zone_record.record",
},
},
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func Test_ImportBlock_InConfigDirectory(t *testing.T) {
func TestImportBlock_InConfigDirectory(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand All @@ -35,11 +35,10 @@ func Test_ImportBlock_InConfigDirectory(t *testing.T) {
ConfigDirectory: config.StaticDirectory(`testdata/1`),
},
{
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ImportStateVerify: true,
ConfigDirectory: config.StaticDirectory(`testdata/2`),
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ConfigDirectory: config.StaticDirectory(`testdata/2`),
},
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func Test_ImportBlock_InConfigFile(t *testing.T) {
func TestImportBlock_InConfigFile(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand All @@ -35,11 +35,10 @@ func Test_ImportBlock_InConfigFile(t *testing.T) {
ConfigFile: config.StaticFile(`testdata/1/examplecloud_container.tf`),
},
{
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ImportStateVerify: true,
ConfigFile: config.StaticFile(`testdata/2/examplecloud_container_import.tf`),
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ConfigFile: config.StaticFile(`testdata/2/examplecloud_container_import.tf`),
},
},
})
Expand Down
46 changes: 19 additions & 27 deletions 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 Test_TestStep_ImportBlockId(t *testing.T) {
func TestImportBlock_WithID(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand All @@ -44,16 +44,15 @@ func Test_TestStep_ImportBlockId(t *testing.T) {
}`,
},
{
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ImportStateVerify: true,
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
},
},
})
}

func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) {
func TestImportBlock_WithID_ExpectError(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand Down Expand Up @@ -81,17 +80,16 @@ func TestTest_TestStep_ImportBlockId_ExpectError(t *testing.T) {
location = "eastus"
name = "somevalue"
}`,
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ImportStateVerify: true,
ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test: expected a no-op resource action, got "update" action with plan(.?)`),
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ExpectError: regexp.MustCompile(`importing resource examplecloud_container.test: expected a no-op import operation, got.*\["update"\] action with plan(.?)`),
},
},
})
}

func TestTest_TestStep_ImportBlockId_FailWhenPlannableImportIsNotSupported(t *testing.T) {
func TestImportBlock_WithID_FailWhenNotSupported(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand Down Expand Up @@ -130,7 +128,7 @@ func TestTest_TestStep_ImportBlockId_FailWhenPlannableImportIsNotSupported(t *te
})
}

func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) {
func TestImportBlock_WithID_SkipsDataSources(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand Down Expand Up @@ -174,9 +172,7 @@ func TestTest_TestStep_ImportBlockId_SkipDataSourceState(t *testing.T) {
})
}

// 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) {
func TestImportBlock_WithID_WithBlankOptionalAttribute_GeneratesCorrectPlan(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.
Expand Down Expand Up @@ -290,17 +286,15 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore_Real_Example(t *tes
resource "examplecloud_container" "test" {
name = "somename"
}`,
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password"},
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
},
},
})
}

func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) {
func TestImportBlock_WithID_WithBlankComputedAttribute_GeneratesCorrectPlan(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand Down Expand Up @@ -375,11 +369,9 @@ func TestTest_TestStep_ImportBlockId_ImportStateVerifyIgnore(t *testing.T) {
Config: `resource "examplecloud_container" "test" {}`,
},
{
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"password"},
ResourceName: "examplecloud_container.test",
ImportState: true,
ImportStateKind: r.ImportBlockWithID,
},
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func Test_ImportCommand_AsFirstStep(t *testing.T) {
func TestImportCommand_AsFirstStep(t *testing.T) {
t.Parallel()

r.UnitTest(t, r.TestCase{
Expand Down
Loading