From 486e61178d460d4b0f35dd815f452d4eda0b29f3 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Mon, 28 Nov 2022 18:43:21 +0000 Subject: [PATCH] Amend Plan Resource Change To Allow For Optional Blocks (#552) Reference: https://github.com/hashicorp/terraform-plugin-framework/issues/551 Modifying handling of blocks during plan resource change to allow optional blocks to be present within configuration --- .changelog/552.txt | 3 + .../fwserver/server_planresourcechange.go | 6 + .../server_planresourcechange_test.go | 111 ++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 .changelog/552.txt diff --git a/.changelog/552.txt b/.changelog/552.txt new file mode 100644 index 000000000..c9deab115 --- /dev/null +++ b/.changelog/552.txt @@ -0,0 +1,3 @@ +```release-note:bug +internal/fwserver: Ensured blocks are ignored when marking computed nils as unknown during resource change planning +``` diff --git a/internal/fwserver/server_planresourcechange.go b/internal/fwserver/server_planresourcechange.go index 18a3e1a2c..4c63d6ce7 100644 --- a/internal/fwserver/server_planresourcechange.go +++ b/internal/fwserver/server_planresourcechange.go @@ -291,6 +291,12 @@ func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour return val, nil } + if errors.Is(err, tfsdk.ErrPathIsBlock) { + // ignore blocks, they do not have a computed field + logging.FrameworkTrace(ctx, "attribute is a block, not marking unknown") + return val, nil + } + logging.FrameworkError(ctx, "couldn't find attribute in resource schema") return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err) diff --git a/internal/fwserver/server_planresourcechange_test.go b/internal/fwserver/server_planresourcechange_test.go index 596bc6ce1..6dc1a9a31 100644 --- a/internal/fwserver/server_planresourcechange_test.go +++ b/internal/fwserver/server_planresourcechange_test.go @@ -274,6 +274,18 @@ func TestServerPlanResourceChange(t *testing.T) { }, } + testSchemaBlockType := tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_required": tftypes.String, + "test_optional_block": tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_optional_one": tftypes.String, + "test_optional_two": tftypes.String, + }, + }, + }, + } + testSchemaTypeComputed := tftypes.Object{ AttributeTypes: map[string]tftypes.Type{ "test_computed": tftypes.String, @@ -293,6 +305,30 @@ func TestServerPlanResourceChange(t *testing.T) { }, } + testSchemaBlock := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "test_required": { + Required: true, + Type: types.StringType, + }, + }, + Blocks: map[string]tfsdk.Block{ + "test_optional_block": { + Attributes: map[string]tfsdk.Attribute{ + "test_optional_one": { + Type: types.StringType, + Optional: true, + }, + "test_optional_two": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeSingle, + }, + }, + } + testEmptyPlan := &tfsdk.Plan{ Raw: tftypes.NewValue(testSchemaType, nil), Schema: testSchema, @@ -308,6 +344,11 @@ func TestServerPlanResourceChange(t *testing.T) { TestRequired types.String `tfsdk:"test_required"` } + type testSchemaDataBlock struct { + TestRequired types.String `tfsdk:"test_required"` + TestOptionalBlock types.Object `tfsdk:"test_optional_block"` + } + testSchemaAttributePlanModifierAttributePlan := tfsdk.Schema{ Attributes: map[string]tfsdk.Attribute{ "test_computed": { @@ -1935,6 +1976,76 @@ func TestServerPlanResourceChange(t *testing.T) { PlannedPrivate: testEmptyPrivate, }, }, + "update-resourcewithmodifyplan-request-config-nil-block": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{}, + }, + request: &fwserver.PlanResourceChangeRequest{ + Config: &tfsdk.Config{ + Raw: tftypes.NewValue(testSchemaBlockType, map[string]tftypes.Value{ + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + "test_optional_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_optional_one": tftypes.String, + "test_optional_two": tftypes.String, + }, + }, nil), + }), + Schema: testSchemaBlock, + }, + ProposedNewState: &tfsdk.Plan{ + Raw: tftypes.NewValue(testSchemaBlockType, map[string]tftypes.Value{ + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + "test_optional_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_optional_one": tftypes.String, + "test_optional_two": tftypes.String, + }, + }, nil), + }), + Schema: testSchemaBlock, + }, + PriorState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaBlockType, map[string]tftypes.Value{ + "test_required": tftypes.NewValue(tftypes.String, "test-old-value"), + "test_optional_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_optional_one": tftypes.String, + "test_optional_two": tftypes.String, + }, + }, nil), + }), + Schema: testSchemaBlock, + }, + ResourceSchema: testSchemaBlock, + Resource: &testprovider.ResourceWithModifyPlan{ + ModifyPlanMethod: func(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + var data testSchemaDataBlock + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if data.TestRequired.ValueString() != "test-new-value" { + resp.Diagnostics.AddError("Unexpected req.Config Value", "Got: "+data.TestRequired.ValueString()) + } + }, + }, + }, + expectedResponse: &fwserver.PlanResourceChangeResponse{ + PlannedState: &tfsdk.State{ + Raw: tftypes.NewValue(testSchemaBlockType, map[string]tftypes.Value{ + "test_required": tftypes.NewValue(tftypes.String, "test-new-value"), + "test_optional_block": tftypes.NewValue(tftypes.Object{ + AttributeTypes: map[string]tftypes.Type{ + "test_optional_one": tftypes.String, + "test_optional_two": tftypes.String, + }, + }, nil), + }), + Schema: testSchemaBlock, + }, + PlannedPrivate: testEmptyPrivate, + }, + }, "update-resourcewithmodifyplan-request-proposednewstate": { server: &fwserver.Server{ Provider: &testprovider.Provider{},