Skip to content

Commit ca17ae9

Browse files
authored
Resource Identity: Add a new import helper that can pass-through an identity attribute and an import ID. (#1134)
* Resource Identity: Add a new import helper that can pass-through an identity attribute and an import ID. * Update with correct diag returns
1 parent 59870d9 commit ca17ae9

File tree

2 files changed

+177
-3
lines changed

2 files changed

+177
-3
lines changed

internal/fwserver/server_importresourcestate_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ func TestServerImportResourceState(t *testing.T) {
145145
Schema: testSchema,
146146
}
147147

148+
testStatePassThroughIdentity := &tfsdk.State{
149+
Raw: tftypes.NewValue(testType, map[string]tftypes.Value{
150+
"id": tftypes.NewValue(tftypes.String, "id-123"),
151+
"optional": tftypes.NewValue(tftypes.String, nil),
152+
"required": tftypes.NewValue(tftypes.String, nil),
153+
}),
154+
Schema: testSchema,
155+
}
156+
148157
testImportedResourceIdentity := &tfsdk.ResourceIdentity{
149158
Raw: testImportedResourceIdentityValue,
150159
Schema: testIdentitySchema,
@@ -655,6 +664,122 @@ func TestServerImportResourceState(t *testing.T) {
655664
},
656665
},
657666
},
667+
"response-importedresources-passthrough-identity-imported-by-id": {
668+
server: &fwserver.Server{
669+
Provider: &testprovider.Provider{},
670+
},
671+
request: &fwserver.ImportResourceStateRequest{
672+
EmptyState: *testEmptyState,
673+
ID: "id-123",
674+
IdentitySchema: testIdentitySchema,
675+
Resource: &testprovider.ResourceWithImportState{
676+
Resource: &testprovider.Resource{},
677+
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
678+
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("test_id"), req, resp)
679+
},
680+
},
681+
TypeName: "test_resource",
682+
},
683+
expectedResponse: &fwserver.ImportResourceStateResponse{
684+
ImportedResources: []fwserver.ImportedResource{
685+
{
686+
State: *testStatePassThroughIdentity,
687+
Identity: &tfsdk.ResourceIdentity{
688+
Raw: tftypes.NewValue(testIdentityType, nil),
689+
Schema: testIdentitySchema,
690+
},
691+
TypeName: "test_resource",
692+
Private: testEmptyPrivate,
693+
},
694+
},
695+
},
696+
},
697+
"response-importedresources-passthrough-identity-imported-by-identity": {
698+
server: &fwserver.Server{
699+
Provider: &testprovider.Provider{},
700+
},
701+
request: &fwserver.ImportResourceStateRequest{
702+
EmptyState: *testEmptyState,
703+
Identity: testRequestIdentity,
704+
IdentitySchema: testIdentitySchema,
705+
Resource: &testprovider.ResourceWithImportState{
706+
Resource: &testprovider.Resource{},
707+
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
708+
resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("other_test_id"), types.StringValue("new-value-123"))...)
709+
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("test_id"), req, resp)
710+
},
711+
},
712+
TypeName: "test_resource",
713+
},
714+
expectedResponse: &fwserver.ImportResourceStateResponse{
715+
ImportedResources: []fwserver.ImportedResource{
716+
{
717+
State: *testStatePassThroughIdentity,
718+
Identity: testImportedResourceIdentity,
719+
TypeName: "test_resource",
720+
Private: testEmptyPrivate,
721+
},
722+
},
723+
},
724+
},
725+
"response-importedresources-passthrough-identity-invalid-state-path": {
726+
server: &fwserver.Server{
727+
Provider: &testprovider.Provider{},
728+
},
729+
request: &fwserver.ImportResourceStateRequest{
730+
EmptyState: *testEmptyState,
731+
ID: "id-123",
732+
IdentitySchema: testIdentitySchema,
733+
Resource: &testprovider.ResourceWithImportState{
734+
Resource: &testprovider.Resource{},
735+
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
736+
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("not-valid"), path.Root("test_id"), req, resp)
737+
},
738+
},
739+
TypeName: "test_resource",
740+
},
741+
expectedResponse: &fwserver.ImportResourceStateResponse{
742+
Diagnostics: diag.Diagnostics{
743+
diag.NewAttributeErrorDiagnostic(
744+
path.Root("not-valid"),
745+
"State Write Error",
746+
"An unexpected error was encountered trying to retrieve type information at a given path. "+
747+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
748+
"Error: AttributeName(\"not-valid\") still remains in the path: could not find attribute or block "+
749+
"\"not-valid\" in schema",
750+
),
751+
},
752+
},
753+
},
754+
"response-importedresources-passthrough-identity-invalid-identity-path": {
755+
server: &fwserver.Server{
756+
Provider: &testprovider.Provider{},
757+
},
758+
request: &fwserver.ImportResourceStateRequest{
759+
EmptyState: *testEmptyState,
760+
Identity: testRequestIdentity,
761+
IdentitySchema: testIdentitySchema,
762+
Resource: &testprovider.ResourceWithImportState{
763+
Resource: &testprovider.Resource{},
764+
ImportStateMethod: func(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
765+
resource.ImportStatePassthroughWithIdentity(ctx, path.Root("id"), path.Root("not-valid"), req, resp)
766+
},
767+
},
768+
TypeName: "test_resource",
769+
},
770+
expectedResponse: &fwserver.ImportResourceStateResponse{
771+
Diagnostics: diag.Diagnostics{
772+
diag.NewAttributeErrorDiagnostic(
773+
path.Root("not-valid"),
774+
"Resource Identity Read Error",
775+
"An unexpected error was encountered trying to retrieve type information at a given path. "+
776+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
777+
"Error: AttributeName(\"not-valid\") still remains in the path: could not find attribute or block "+
778+
"\"not-valid\" in schema",
779+
),
780+
},
781+
},
782+
},
658783
}
659784

660785
for name, testCase := range testCases {

resource/import_state.go

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/hashicorp/terraform-plugin-framework/internal/privatestate"
1111
"github.com/hashicorp/terraform-plugin-framework/path"
1212
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
13+
"github.com/hashicorp/terraform-plugin-framework/types"
1314
)
1415

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

111113
// 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)
@@ -114,3 +116,50 @@ func ImportStatePassthroughID(ctx context.Context, attrPath path.Path, req Impor
114116
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, attrPath, req.ID)...)
115117
}
116118
}
119+
120+
// ImportStatePassthroughWithIdentity is a helper function to retrieve either the import identifier
121+
// or a given identity attribute that is then used to set to given attribute path in state, based on the method used
122+
// by the practitioner to import. The identity and state attributes provided must be of type string.
123+
//
124+
// The helper method should only be used on resources that support identity via the resource.ResourceWithIdentity interface.
125+
//
126+
// This method will also automatically pass through the Identity field if imported by
127+
// the identity attribute of a import config block (Terraform 1.12+ and later).
128+
func ImportStatePassthroughWithIdentity(ctx context.Context, stateAttrPath, identityAttrPath path.Path, req ImportStateRequest, resp *ImportStateResponse) {
129+
if stateAttrPath.Equal(path.Empty()) {
130+
resp.Diagnostics.AddError(
131+
"Resource Import Passthrough Missing State Attribute Path",
132+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
133+
"Resource ImportState method call to ImportStatePassthroughWithIdentity path must be set to a valid state attribute path that can accept a string value.",
134+
)
135+
}
136+
137+
if identityAttrPath.Equal(path.Empty()) {
138+
resp.Diagnostics.AddError(
139+
"Resource Import Passthrough Missing Identity Attribute Path",
140+
"This is always an error in the provider. Please report the following to the provider developer:\n\n"+
141+
"Resource ImportState method call to ImportStatePassthroughWithIdentity path must be set to a valid identity attribute path that is a string value.",
142+
)
143+
}
144+
145+
if resp.Diagnostics.HasError() {
146+
return
147+
}
148+
149+
// If the import is using the import identifier, (either via the "terraform import" CLI command, or a config block with the "id" attribute set)
150+
// pass through the ID to the designated state attribute.
151+
if req.ID != "" {
152+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, stateAttrPath, req.ID)...)
153+
return
154+
}
155+
156+
// The import isn't using the import identifier, so it must be using identity. Grab the designated
157+
// identity attribute string and set it to state.
158+
var identityAttrVal types.String
159+
resp.Diagnostics.Append(req.Identity.GetAttribute(ctx, identityAttrPath, &identityAttrVal)...)
160+
if resp.Diagnostics.HasError() {
161+
return
162+
}
163+
164+
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, stateAttrPath, identityAttrVal)...)
165+
}

0 commit comments

Comments
 (0)