Skip to content

Commit d8e493d

Browse files
authored
tfsdk: Added automatic RemoveResource() call after Resource type Delete method execution (#301)
Reference: #100 Due to provider developer feedback, the framework will now perform automatic removal of state on successful (no error diagnostic) `Resource` type `Delete` executions. This matches the behavior of terraform-plugin-sdk. Providers can still explicitly call `(State).RemoveResource()` or leave existing implementations, however it is extraneous for most `Delete` logic now.
1 parent 04be243 commit d8e493d

File tree

5 files changed

+102
-2
lines changed

5 files changed

+102
-2
lines changed

.changelog/301.txt

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
```release-note:note
2+
tfsdk: Providers may now optionally remove `RemoveResource()` calls from `Resource` type `Delete` methods
3+
```
4+
5+
```release-note:enhancement
6+
tfsdk: Added automatic `(DeleteResourceResponse.State).RemoveResource()` call after `Resource` type `Delete` method execution if there are no errors
7+
```

tfsdk/resource.go

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ type Resource interface {
4040

4141
// Delete is called when the provider must delete the resource. Config
4242
// values may be read from the DeleteResourceRequest.
43+
//
44+
// If execution completes without error, the framework will automatically
45+
// call DeleteResourceResponse.State.RemoveResource(), so it can be omitted
46+
// from provider logic.
4347
Delete(context.Context, DeleteResourceRequest, *DeleteResourceResponse)
4448

4549
// ImportState is called when the provider must import the resource.

tfsdk/serve.go

+6
Original file line numberDiff line numberDiff line change
@@ -1438,6 +1438,12 @@ func (s *server) applyResourceChange(ctx context.Context, req *tfprotov6.ApplyRe
14381438
}
14391439
resource.Delete(ctx, destroyReq, &destroyResp)
14401440
resp.Diagnostics = destroyResp.Diagnostics
1441+
1442+
if !resp.Diagnostics.HasError() {
1443+
logging.FrameworkTrace(ctx, "No provider defined Delete errors detected, ensuring State is cleared")
1444+
destroyResp.State.RemoveResource(ctx)
1445+
}
1446+
14411447
newState, err := tfprotov6.NewDynamicValue(resourceSchema.TerraformType(ctx), destroyResp.State.Raw)
14421448
if err != nil {
14431449
resp.Diagnostics.AddError(

tfsdk/serve_test.go

+82-2
Original file line numberDiff line numberDiff line change
@@ -4576,7 +4576,8 @@ func TestServerApplyResourceChange(t *testing.T) {
45764576
action: "delete",
45774577
resourceType: testServeResourceTypeOneType,
45784578
destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) {
4579-
resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, nil)
4579+
// Removing the state prior to the framework should not generate errors
4580+
resp.State.RemoveResource(ctx)
45804581
},
45814582
expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil),
45824583
},
@@ -4592,7 +4593,8 @@ func TestServerApplyResourceChange(t *testing.T) {
45924593
action: "delete",
45934594
resourceType: testServeResourceTypeOneType,
45944595
destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) {
4595-
resp.State.Raw = tftypes.NewValue(testServeResourceTypeOneType, nil)
4596+
// Removing the state prior to the framework should not generate errors
4597+
resp.State.RemoveResource(ctx)
45964598
resp.Diagnostics.AddAttributeWarning(
45974599
tftypes.NewAttributePath().WithAttributeName("created_timestamp"),
45984600
"This is a warning",
@@ -4641,6 +4643,84 @@ func TestServerApplyResourceChange(t *testing.T) {
46414643
},
46424644
},
46434645
},
4646+
"one_delete_automatic_removeresource": {
4647+
priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{
4648+
"name": tftypes.NewValue(tftypes.String, "hello, world"),
4649+
"favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{
4650+
tftypes.NewValue(tftypes.String, "red"),
4651+
}),
4652+
"created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"),
4653+
}),
4654+
resource: "test_one",
4655+
action: "delete",
4656+
resourceType: testServeResourceTypeOneType,
4657+
destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) {
4658+
// The framework should automatically call resp.State.RemoveResource()
4659+
},
4660+
expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil),
4661+
},
4662+
"one_delete_diags_warning_automatic_removeresource": {
4663+
priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{
4664+
"name": tftypes.NewValue(tftypes.String, "hello, world"),
4665+
"favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{
4666+
tftypes.NewValue(tftypes.String, "red"),
4667+
}),
4668+
"created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"),
4669+
}),
4670+
resource: "test_one",
4671+
action: "delete",
4672+
resourceType: testServeResourceTypeOneType,
4673+
destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) {
4674+
// The framework should automatically call resp.State.RemoveResource()
4675+
resp.Diagnostics.AddAttributeWarning(
4676+
tftypes.NewAttributePath().WithAttributeName("created_timestamp"),
4677+
"This is a warning",
4678+
"just a warning diagnostic, no behavior changes",
4679+
)
4680+
},
4681+
expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, nil),
4682+
expectedDiags: []*tfprotov6.Diagnostic{
4683+
{
4684+
Severity: tfprotov6.DiagnosticSeverityWarning,
4685+
Summary: "This is a warning",
4686+
Detail: "just a warning diagnostic, no behavior changes",
4687+
Attribute: tftypes.NewAttributePath().WithAttributeName("created_timestamp"),
4688+
},
4689+
},
4690+
},
4691+
"one_delete_diags_error_no_automatic_removeresource": {
4692+
priorState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{
4693+
"name": tftypes.NewValue(tftypes.String, "hello, world"),
4694+
"favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{
4695+
tftypes.NewValue(tftypes.String, "red"),
4696+
}),
4697+
"created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"),
4698+
}),
4699+
resource: "test_one",
4700+
action: "delete",
4701+
resourceType: testServeResourceTypeOneType,
4702+
destroy: func(ctx context.Context, req DeleteResourceRequest, resp *DeleteResourceResponse) {
4703+
// The framework should NOT automatically call resp.State.RemoveResource()
4704+
resp.Diagnostics.AddError(
4705+
"This is an error",
4706+
"Something went wrong, keep the old state around",
4707+
)
4708+
},
4709+
expectedNewState: tftypes.NewValue(testServeResourceTypeOneType, map[string]tftypes.Value{
4710+
"name": tftypes.NewValue(tftypes.String, "hello, world"),
4711+
"favorite_colors": tftypes.NewValue(tftypes.List{ElementType: tftypes.String}, []tftypes.Value{
4712+
tftypes.NewValue(tftypes.String, "red"),
4713+
}),
4714+
"created_timestamp": tftypes.NewValue(tftypes.String, "right now I guess"),
4715+
}),
4716+
expectedDiags: []*tfprotov6.Diagnostic{
4717+
{
4718+
Severity: tfprotov6.DiagnosticSeverityError,
4719+
Summary: "This is an error",
4720+
Detail: "Something went wrong, keep the old state around",
4721+
},
4722+
},
4723+
},
46444724
"two_create": {
46454725
plannedState: tftypes.NewValue(testServeResourceTypeTwoType, map[string]tftypes.Value{
46464726
"id": tftypes.NewValue(tftypes.String, "test-instance"),

tfsdk/state.go

+3
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ func (s State) setAttributeTransformFunc(ctx context.Context, path *tftypes.Attr
326326
}
327327

328328
// RemoveResource removes the entire resource from state.
329+
//
330+
// If a Resource type Delete method is completed without error, this is
331+
// automatically called on the DeleteResourceResponse.State.
329332
func (s *State) RemoveResource(ctx context.Context) {
330333
s.Raw = tftypes.NewValue(s.Schema.TerraformType(ctx), nil)
331334
}

0 commit comments

Comments
 (0)