diff --git a/internal/fwserver/server_applyresourcechange.go b/internal/fwserver/server_applyresourcechange.go index 2167ab709..aabbeeba7 100644 --- a/internal/fwserver/server_applyresourcechange.go +++ b/internal/fwserver/server_applyresourcechange.go @@ -76,6 +76,10 @@ func (s *Server) ApplyResourceChange(ctx context.Context, req *ApplyResourceChan deleteReq := &DeleteResourceRequest{ PlannedPrivate: req.PlannedPrivate, PriorState: req.PriorState, + // MAINTAINER NOTE: There isn't a separate data field for prior identity, like there is with prior_state and planned_state. + // Here the planned_identity field will contain what would be considered the prior identity (since the final identity value + // after deleting will be null). + PriorIdentity: req.PlannedIdentity, ProviderMeta: req.ProviderMeta, ResourceSchema: req.ResourceSchema, IdentitySchema: req.IdentitySchema, diff --git a/internal/fwserver/server_applyresourcechange_test.go b/internal/fwserver/server_applyresourcechange_test.go index a8565de7d..0fa2d9ab9 100644 --- a/internal/fwserver/server_applyresourcechange_test.go +++ b/internal/fwserver/server_applyresourcechange_test.go @@ -69,6 +69,11 @@ func TestServerApplyResourceChange(t *testing.T) { Schema: testSchema, } + testEmptyIdentity := &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, nil), + Schema: testIdentitySchema, + } + type testSchemaData struct { TestComputed types.String `tfsdk:"test_computed"` TestRequired types.String `tfsdk:"test_required"` @@ -689,6 +694,52 @@ func TestServerApplyResourceChange(t *testing.T) { NewState: testEmptyState, }, }, + "delete-request-prioridentity": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.ApplyResourceChangeRequest{ + PlannedState: testEmptyPlan, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + PlannedIdentity: &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, map[string]tftypes.Value{ + "test_id": tftypes.NewValue(tftypes.String, "id-123"), + }), + Schema: testIdentitySchema, + }, + IdentitySchema: testIdentitySchema, + ResourceSchema: testSchema, + Resource: &testprovider.ResourceWithIdentity{ + Resource: &testprovider.Resource{ + CreateMethod: func(_ context.Context, _ resource.CreateRequest, resp *resource.CreateResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") + }, + DeleteMethod: func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var identityData testIdentitySchemaData + + resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) + + if identityData.TestID.ValueString() != "id-123" { + resp.Diagnostics.AddError("Unexpected req.Identity Value", "Got: "+identityData.TestID.ValueString()) + } + }, + UpdateMethod: func(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) { + resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Update") + }, + }, + }, + }, + expectedResponse: &fwserver.ApplyResourceChangeResponse{ + NewIdentity: testEmptyIdentity, + NewState: testEmptyState, + }, + }, "delete-request-providermeta": { server: &fwserver.Server{ Provider: &testprovider.Provider{}, @@ -982,6 +1033,12 @@ func TestServerApplyResourceChange(t *testing.T) { }), Schema: testSchema, }, + PlannedIdentity: &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, map[string]tftypes.Value{ + "test_id": tftypes.NewValue(tftypes.String, "id-123"), + }), + Schema: testIdentitySchema, + }, IdentitySchema: testIdentitySchema, ResourceSchema: testSchema, Resource: &testprovider.ResourceWithIdentity{ @@ -990,10 +1047,11 @@ func TestServerApplyResourceChange(t *testing.T) { resp.Diagnostics.AddError("Unexpected Method Call", "Expected: Delete, Got: Create") }, DeleteMethod: func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - if resp.Identity == nil || !resp.Identity.Raw.IsNull() { + // The identity is automatically set to null in the response after the Delete method is called + if resp.Identity == nil || resp.Identity.Raw.IsNull() { resp.Diagnostics.AddError( "Unexpected resp.Identity", - "expected resp.Identity to be a null object of the schema type.", + "expected resp.Identity to be a known non-null object of the schema type.", ) } }, @@ -1004,11 +1062,8 @@ func TestServerApplyResourceChange(t *testing.T) { }, }, expectedResponse: &fwserver.ApplyResourceChangeResponse{ - NewIdentity: &tfsdk.ResourceIdentity{ - Raw: tftypes.NewValue(testIdentityType, nil), - Schema: testIdentitySchema, - }, - NewState: testEmptyState, + NewIdentity: testEmptyIdentity, + NewState: testEmptyState, }, }, "update-request-config": { diff --git a/internal/fwserver/server_deleteresource.go b/internal/fwserver/server_deleteresource.go index c7d04380b..d58e758d0 100644 --- a/internal/fwserver/server_deleteresource.go +++ b/internal/fwserver/server_deleteresource.go @@ -21,6 +21,7 @@ import ( type DeleteResourceRequest struct { PlannedPrivate *privatestate.Data PriorState *tfsdk.State + PriorIdentity *tfsdk.ResourceIdentity ProviderMeta *tfsdk.Config ResourceSchema fwschema.Schema IdentitySchema fwschema.Schema @@ -98,15 +99,27 @@ func (s *Server) DeleteResource(ctx context.Context, req *DeleteResourceRequest, resp.Private = req.PlannedPrivate } - if req.IdentitySchema != nil { + if req.PriorIdentity == nil && req.IdentitySchema != nil { nullIdentityTfValue := tftypes.NewValue(req.IdentitySchema.Type().TerraformType(ctx), nil) - deleteResp.Identity = &tfsdk.ResourceIdentity{ + req.PriorIdentity = &tfsdk.ResourceIdentity{ Schema: req.IdentitySchema, Raw: nullIdentityTfValue.Copy(), } } + if req.PriorIdentity != nil { + deleteReq.Identity = &tfsdk.ResourceIdentity{ + Schema: req.PriorIdentity.Schema, + Raw: req.PriorIdentity.Raw.Copy(), + } + + deleteResp.Identity = &tfsdk.ResourceIdentity{ + Schema: req.PriorIdentity.Schema, + Raw: req.PriorIdentity.Raw.Copy(), + } + } + logging.FrameworkTrace(ctx, "Calling provider defined Resource Delete") req.Resource.Delete(ctx, deleteReq, &deleteResp) logging.FrameworkTrace(ctx, "Called provider defined Resource Delete") diff --git a/internal/fwserver/server_deleteresource_test.go b/internal/fwserver/server_deleteresource_test.go index b47c16c0b..4770f45e9 100644 --- a/internal/fwserver/server_deleteresource_test.go +++ b/internal/fwserver/server_deleteresource_test.go @@ -64,6 +64,11 @@ func TestServerDeleteResource(t *testing.T) { Schema: testSchema, } + testEmptyIdentity := &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, nil), + Schema: testIdentitySchema, + } + type testSchemaData struct { TestComputed types.String `tfsdk:"test_computed"` TestRequired types.String `tfsdk:"test_required"` @@ -163,6 +168,45 @@ func TestServerDeleteResource(t *testing.T) { NewState: testEmptyState, }, }, + "request-prioridentity": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.DeleteResourceRequest{ + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaType, map[string]tftypes.Value{ + "test_computed": tftypes.NewValue(tftypes.String, nil), + "test_required": tftypes.NewValue(tftypes.String, "test-priorstate-value"), + }), + Schema: testSchema, + }, + PriorIdentity: &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, map[string]tftypes.Value{ + "test_id": tftypes.NewValue(tftypes.String, "id-123"), + }), + Schema: testIdentitySchema, + }, + IdentitySchema: testIdentitySchema, + ResourceSchema: testSchema, + Resource: &testprovider.ResourceWithIdentity{ + Resource: &testprovider.Resource{ + DeleteMethod: func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var identityData testIdentitySchemaData + + resp.Diagnostics.Append(req.Identity.Get(ctx, &identityData)...) + + if identityData.TestID.ValueString() != "id-123" { + resp.Diagnostics.AddError("Unexpected req.Identity Value", "Got: "+identityData.TestID.ValueString()) + } + }, + }, + }, + }, + expectedResponse: &fwserver.DeleteResourceResponse{ + NewState: testEmptyState, + NewIdentity: testEmptyIdentity, + }, + }, "request-providermeta": { server: &fwserver.Server{ Provider: &testprovider.Provider{}, @@ -365,25 +409,29 @@ func TestServerDeleteResource(t *testing.T) { }), Schema: testSchema, }, + PriorIdentity: &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, map[string]tftypes.Value{ + "test_id": tftypes.NewValue(tftypes.String, "id-123"), + }), + Schema: testIdentitySchema, + }, IdentitySchema: testIdentitySchema, ResourceSchema: testSchema, Resource: &testprovider.Resource{ DeleteMethod: func(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - if resp.Identity == nil || !resp.Identity.Raw.IsNull() { + // The identity is automatically set to null in the response after the Delete method is called + if resp.Identity == nil || resp.Identity.Raw.IsNull() { resp.Diagnostics.AddError( "Unexpected resp.Identity", - "expected resp.Identity to be a null object of the schema type.", + "expected resp.Identity to be a known non-null object of the schema type.", ) } }, }, }, expectedResponse: &fwserver.DeleteResourceResponse{ - NewIdentity: &tfsdk.ResourceIdentity{ - Raw: tftypes.NewValue(testIdentityType, nil), - Schema: testIdentitySchema, - }, - NewState: testEmptyState, + NewIdentity: testEmptyIdentity, + NewState: testEmptyState, }, }, "response-newidentity-set-to-null": { @@ -398,6 +446,12 @@ func TestServerDeleteResource(t *testing.T) { }), Schema: testSchema, }, + PriorIdentity: &tfsdk.ResourceIdentity{ + Raw: tftypes.NewValue(testIdentityType, map[string]tftypes.Value{ + "test_id": tftypes.NewValue(tftypes.String, "id-123"), + }), + Schema: testIdentitySchema, + }, IdentitySchema: testIdentitySchema, ResourceSchema: testSchema, Resource: &testprovider.Resource{ @@ -410,11 +464,8 @@ func TestServerDeleteResource(t *testing.T) { }, }, expectedResponse: &fwserver.DeleteResourceResponse{ - NewIdentity: &tfsdk.ResourceIdentity{ - Raw: tftypes.NewValue(testIdentityType, nil), - Schema: testIdentitySchema, - }, - NewState: testEmptyState, + NewIdentity: testEmptyIdentity, + NewState: testEmptyState, }, }, "response-invalid-newidentity": { diff --git a/resource/delete.go b/resource/delete.go index 8281dffa1..6644737d9 100644 --- a/resource/delete.go +++ b/resource/delete.go @@ -17,6 +17,10 @@ type DeleteRequest struct { // operation. State tfsdk.State + // Identity is the current identity of the resource prior to the Delete + // operation. If the resource does not support identity, this value will not be set. + Identity *tfsdk.ResourceIdentity + // ProviderMeta is metadata from the provider_meta block of the module. ProviderMeta tfsdk.Config