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
125 changes: 125 additions & 0 deletions internal/fwserver/server_importresourcestate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ func TestServerImportResourceState(t *testing.T) {
Schema: testSchema,
}

testStatePassThroughIdentity := &tfsdk.State{
Raw: tftypes.NewValue(testType, map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, "id-123"),
"optional": tftypes.NewValue(tftypes.String, nil),
"required": tftypes.NewValue(tftypes.String, nil),
}),
Schema: testSchema,
}

testImportedResourceIdentity := &tfsdk.ResourceIdentity{
Raw: testImportedResourceIdentityValue,
Schema: testIdentitySchema,
Expand Down Expand Up @@ -655,6 +664,122 @@ func TestServerImportResourceState(t *testing.T) {
},
},
},
"response-importedresources-passthrough-identity-imported-by-id": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.ImportResourceStateRequest{
EmptyState: *testEmptyState,
ID: "id-123",
IdentitySchema: testIdentitySchema,
Resource: &testprovider.ResourceWithImportState{
Resource: &testprovider.Resource{},
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("test_id"), req, resp)
},
},
TypeName: "test_resource",
},
expectedResponse: &fwserver.ImportResourceStateResponse{
ImportedResources: []fwserver.ImportedResource{
{
State: *testStatePassThroughIdentity,
Identity: &tfsdk.ResourceIdentity{
Raw: tftypes.NewValue(testIdentityType, nil),
Schema: testIdentitySchema,
},
TypeName: "test_resource",
Private: testEmptyPrivate,
},
},
},
},
"response-importedresources-passthrough-identity-imported-by-identity": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.ImportResourceStateRequest{
EmptyState: *testEmptyState,
Identity: testRequestIdentity,
IdentitySchema: testIdentitySchema,
Resource: &testprovider.ResourceWithImportState{
Resource: &testprovider.Resource{},
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("other_test_id"), types.StringValue("new-value-123"))...)
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("test_id"), req, resp)
},
},
TypeName: "test_resource",
},
expectedResponse: &fwserver.ImportResourceStateResponse{
ImportedResources: []fwserver.ImportedResource{
{
State: *testStatePassThroughIdentity,
Identity: testImportedResourceIdentity,
TypeName: "test_resource",
Private: testEmptyPrivate,
},
},
},
},
"response-importedresources-passthrough-identity-invalid-state-path": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.ImportResourceStateRequest{
EmptyState: *testEmptyState,
ID: "id-123",
IdentitySchema: testIdentitySchema,
Resource: &testprovider.ResourceWithImportState{
Resource: &testprovider.Resource{},
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("not-valid"), path.Root("test_id"), req, resp)
},
},
TypeName: "test_resource",
},
expectedResponse: &fwserver.ImportResourceStateResponse{
Diagnostics: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("not-valid"),
"State Write Error",
"An unexpected error was encountered trying to retrieve type information at a given path. "+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Error: AttributeName(\"not-valid\") still remains in the path: could not find attribute or block "+
"\"not-valid\" in schema",
),
},
},
},
"response-importedresources-passthrough-identity-invalid-identity-path": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.ImportResourceStateRequest{
EmptyState: *testEmptyState,
Identity: testRequestIdentity,
IdentitySchema: testIdentitySchema,
Resource: &testprovider.ResourceWithImportState{
Resource: &testprovider.Resource{},
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("not-valid"), req, resp)
},
},
TypeName: "test_resource",
},
expectedResponse: &fwserver.ImportResourceStateResponse{
Diagnostics: diag.Diagnostics{
diag.NewAttributeErrorDiagnostic(
path.Root("not-valid"),
"Resource Identity Read Error",
"An unexpected error was encountered trying to retrieve type information at a given path. "+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Error: AttributeName(\"not-valid\") still remains in the path: could not find attribute or block "+
"\"not-valid\" in schema",
),
},
},
},
}

for name, testCase := range testCases {
Expand Down
55 changes: 52 additions & 3 deletions resource/import_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// ImportStateClientCapabilities allows Terraform to publish information
Expand Down Expand Up @@ -95,9 +96,9 @@ type ImportStateResponse struct {
// identifier to a given state attribute path. The attribute must accept a
// string value.
//
// This method will also automatically pass through the Identity field if imported by
// the identity attribute of a import config block (Terraform 1.12+ and later). In this
// scenario where identity is provided instead of the string ID, the state field defined
// For resources that support identity, this method will also automatically pass through the
// Identity field if imported by the identity attribute of a import config block (Terraform 1.12+ and later).
// In this scenario where identity is provided instead of the string ID, the state field defined
// at `attrPath` will be set to null.
func ImportStatePassthroughID(ctx context.Context, attrPath path.Path, req ImportStateRequest, resp *ImportStateResponse) {
if attrPath.Equal(path.Empty()) {
Expand All @@ -106,6 +107,7 @@ func ImportStatePassthroughID(ctx context.Context, attrPath path.Path, req Impor
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Resource ImportState method call to ImportStatePassthroughID path must be set to a valid attribute path that can accept a string value.",
)
return
}

// If the import is using the ID string identifier, (either via the "terraform import" CLI command, or a config block with the "id" attribute set)
Expand All @@ -114,3 +116,50 @@ func ImportStatePassthroughID(ctx context.Context, attrPath path.Path, req Impor
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, attrPath, req.ID)...)
}
}

// ImportStatePassthroughWithIdentity is a helper function to retrieve either the import identifier
// or a given identity attribute that is then used to set to given attribute path in state, based on the method used
// by the practitioner to import. The identity and state attributes provided must be of type string.
//
// The helper method should only be used on resources that support identity via the resource.ResourceWithIdentity interface.
//
// This method will also automatically pass through the Identity field if imported by
// the identity attribute of a import config block (Terraform 1.12+ and later).
func ImportStatePassthroughWithIdentity(ctx context.Context, stateAttrPath, identityAttrPath path.Path, req ImportStateRequest, resp *ImportStateResponse) {
if stateAttrPath.Equal(path.Empty()) {
resp.Diagnostics.AddError(
"Resource Import Passthrough Missing State Attribute Path",
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Resource ImportState method call to ImportStatePassthroughWithIdentity path must be set to a valid state attribute path that can accept a string value.",
)
}

if identityAttrPath.Equal(path.Empty()) {
resp.Diagnostics.AddError(
"Resource Import Passthrough Missing Identity Attribute Path",
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
"Resource ImportState method call to ImportStatePassthroughWithIdentity path must be set to a valid identity attribute path that is a string value.",
)
}

if resp.Diagnostics.HasError() {
return
}

// If the import is using the import identifier, (either via the "terraform import" CLI command, or a config block with the "id" attribute set)
// pass through the ID to the designated state attribute.
if req.ID != "" {
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, stateAttrPath, req.ID)...)
return
}

// The import isn't using the import identifier, so it must be using identity. Grab the designated
// identity attribute string and set it to state.
var identityAttrVal types.String
resp.Diagnostics.Append(req.Identity.GetAttribute(ctx, identityAttrPath, &identityAttrVal)...)
if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.Append(resp.State.SetAttribute(ctx, stateAttrPath, identityAttrVal)...)
}