Skip to content

Commit

Permalink
Modifying TransformDefaults and incorporating into PlanResourceChange (
Browse files Browse the repository at this point in the history
  • Loading branch information
bendbennett committed Feb 15, 2023
1 parent c24881c commit 2ec22b0
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 26 deletions.
37 changes: 24 additions & 13 deletions internal/fwschemadata/data_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults"
)

// TransformPlanDefaults walks the schema and applies schema defined default values
// when the source Data type contains a null value at the same path.
// values. The reverse conversion is ReifyNullCollectionBlocks.
func (d *Data) TransformPlanDefaults(ctx context.Context) diag.Diagnostics {
// TransformDefaults walks the schema and applies schema defined default values
// when the rawConfig Data type contains a null value at the same path.
func (d *Data) TransformDefaults(ctx context.Context, configRaw tftypes.Value) diag.Diagnostics {
var diags diag.Diagnostics

configData := Data{
Description: DataDescriptionConfiguration,
Schema: d.Schema,
TerraformValue: configRaw,
}

// Errors are handled as richer diag.Diagnostics instead.
d.TerraformValue, _ = tftypes.Transform(d.TerraformValue, func(tfTypePath *tftypes.AttributePath, tfTypeValue tftypes.Value) (tftypes.Value, error) {
// Do not transform if value is not null.
if !tfTypeValue.IsNull() {
return tfTypeValue, nil
}

fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, d.Schema)

diags.Append(fwPathDiags...)
Expand All @@ -34,23 +34,34 @@ func (d *Data) TransformPlanDefaults(ctx context.Context) diag.Diagnostics {
return tfTypeValue, nil
}

configValue, configValueDiags := configData.ValueAtPath(ctx, fwPath)

diags.Append(configValueDiags...)

// Do not transform if rawConfig value cannot be retrieved.
if configValueDiags.HasError() {
return tfTypeValue, nil
}

// Do not transform if rawConfig value is not null.
if !configValue.IsNull() {
return tfTypeValue, nil
}

attrAtPath, attrAtPathDiags := d.Schema.AttributeAtPath(context.Background(), fwPath)

diags.Append(attrAtPathDiags...)

// Do not transform if schema attribute path cannot be retrieved.
// Checking against fwPathDiags will capture all errors.
if attrAtPathDiags.HasError() {
return tfTypeValue, nil
}

switch attrAtPath.(type) {
case fwschema.AttributeWithBoolDefaultValue:
attribWithBoolDefaultValue := attrAtPath.(fwschema.AttributeWithBoolDefaultValue)
defVal := attribWithBoolDefaultValue.DefaultValue()

resp := defaults.BoolResponse{}
defVal.DefaultBool(ctx, defaults.BoolRequest{}, &resp)
attribWithBoolDefaultValue.DefaultValue().DefaultBool(ctx, defaults.BoolRequest{}, &resp)

return tftypes.NewValue(tfTypeValue.Type(), resp.PlanValue.ValueBool()), nil
}
Expand Down
52 changes: 40 additions & 12 deletions internal/fwschemadata/data_default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ func TestDataDefault(t *testing.T) {

testCases := map[string]struct {
data *fwschemadata.Data
rawConfig tftypes.Value
expected *fwschemadata.Data
expectedDiags diag.Diagnostics
}{
"bool-attribute-unmodified": {
"bool-attribute-not-null-unmodified": {
data: &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: testschema.Schema{
Expand All @@ -41,10 +42,19 @@ func TestDataDefault(t *testing.T) {
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, true),
"bool_attribute": tftypes.NewValue(tftypes.Bool, false), // value in state
},
),
},
rawConfig: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool_attribute": tftypes.Bool,
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, true), // value in rawConfig
},
),
expected: &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: testschema.Schema{
Expand All @@ -62,12 +72,12 @@ func TestDataDefault(t *testing.T) {
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, true),
"bool_attribute": tftypes.NewValue(tftypes.Bool, false),
},
),
},
},
"bool-attribute-null": {
"bool-attribute-null-unmodified-no-default": {
data: &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: testschema.Schema{
Expand All @@ -85,10 +95,19 @@ func TestDataDefault(t *testing.T) {
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, nil), // intentionally nil
"bool_attribute": tftypes.NewValue(tftypes.Bool, false), // value in state
},
),
},
rawConfig: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool_attribute": tftypes.Bool,
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, nil), // value in rawConfig
},
),
expected: &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: testschema.Schema{
Expand All @@ -106,19 +125,19 @@ func TestDataDefault(t *testing.T) {
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, nil), // intentionally nil
"bool_attribute": tftypes.NewValue(tftypes.Bool, false),
},
),
},
},
"bool-attribute-null-default": {
"bool-attribute-null-modified-default": {
data: &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: testschema.Schema{
Attributes: map[string]fwschema.Attribute{
"bool_attribute": testschema.AttributeWithBoolDefaultValue{
Optional: true,
Default: booldefault.StaticValue(false),
Default: booldefault.StaticValue(true),
},
},
},
Expand All @@ -129,17 +148,26 @@ func TestDataDefault(t *testing.T) {
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, nil), // intentionally nil
"bool_attribute": tftypes.NewValue(tftypes.Bool, false), // value in state
},
),
},
rawConfig: tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"bool_attribute": tftypes.Bool,
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, nil), // value in rawConfig
},
),
expected: &fwschemadata.Data{
Description: fwschemadata.DataDescriptionConfiguration,
Schema: testschema.Schema{
Attributes: map[string]fwschema.Attribute{
"bool_attribute": testschema.AttributeWithBoolDefaultValue{
Optional: true,
Default: booldefault.StaticValue(false),
Default: booldefault.StaticValue(true),
},
},
},
Expand All @@ -150,7 +178,7 @@ func TestDataDefault(t *testing.T) {
},
},
map[string]tftypes.Value{
"bool_attribute": tftypes.NewValue(tftypes.Bool, false), // intentionally nil
"bool_attribute": tftypes.NewValue(tftypes.Bool, true),
},
),
},
Expand All @@ -163,7 +191,7 @@ func TestDataDefault(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()

diags := testCase.data.TransformPlanDefaults(context.Background())
diags := testCase.data.TransformDefaults(context.Background(), testCase.rawConfig)

if diff := cmp.Diff(diags, testCase.expectedDiags); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
Expand Down
8 changes: 8 additions & 0 deletions internal/fwserver/server_planresourcechange.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ func (s *Server) PlanResourceChange(ctx context.Context, req *PlanResourceChange

resp.PlannedState = planToState(*req.ProposedNewState)

resp.PlannedState.TransformDefaults(ctx, req.Config.Raw)

// Execute any AttributePlanModifiers.
//
// This pass is before any Computed-only attributes are marked as unknown
Expand Down Expand Up @@ -355,12 +357,18 @@ func MarkComputedNilsAsUnknown(ctx context.Context, config tftypes.Value, resour

return tftypes.Value{}, fmt.Errorf("couldn't find attribute in resource schema: %w", err)
}

if !attribute.IsComputed() {
logging.FrameworkTrace(ctx, "attribute is not computed in schema, not marking unknown")

return val, nil
}

switch attribute.(type) {
case fwschema.AttributeWithBoolDefaultValue:
return val, nil
}

logging.FrameworkDebug(ctx, "marking computed attribute that is null in the config as unknown")

return tftypes.NewValue(val.Type(), tftypes.UnknownValue), nil
Expand Down
60 changes: 60 additions & 0 deletions internal/fwserver/server_planresourcechange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/provider/metaschema"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-framework/types"
Expand Down Expand Up @@ -350,6 +351,13 @@ func TestServerPlanResourceChange(t *testing.T) {
},
}

testSchemaTypeDefault := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_optional_default": tftypes.Bool,
"test_computed_default": tftypes.Bool,
},
}

testSchemaBlockType := tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"test_required": tftypes.String,
Expand Down Expand Up @@ -379,6 +387,19 @@ func TestServerPlanResourceChange(t *testing.T) {
},
}

testSchemaDefault := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_optional_default": schema.BoolAttribute{
Optional: true,
Default: booldefault.StaticValue(true),
},
"test_computed_default": schema.BoolAttribute{
Computed: true,
Default: booldefault.StaticValue(true),
},
},
}

testSchemaBlock := schema.Schema{
Attributes: map[string]schema.Attribute{
"test_required": schema.StringAttribute{
Expand Down Expand Up @@ -409,6 +430,11 @@ func TestServerPlanResourceChange(t *testing.T) {
Schema: testSchema,
}

testEmptyStateDefault := &tfsdk.State{
Raw: tftypes.NewValue(testSchemaTypeDefault, nil),
Schema: testSchemaDefault,
}

type testSchemaData struct {
TestComputed types.String `tfsdk:"test_computed"`
TestRequired types.String `tfsdk:"test_required"`
Expand Down Expand Up @@ -574,6 +600,40 @@ func TestServerPlanResourceChange(t *testing.T) {
request *fwserver.PlanResourceChangeRequest
expectedResponse *fwserver.PlanResourceChangeResponse
}{
"resource-set-default": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
},
request: &fwserver.PlanResourceChangeRequest{
Config: &tfsdk.Config{
Raw: tftypes.NewValue(testSchemaTypeDefault, map[string]tftypes.Value{
"test_optional_default": tftypes.NewValue(tftypes.Bool, nil),
"test_computed_default": tftypes.NewValue(tftypes.Bool, nil),
}),
Schema: testSchemaDefault,
},
ProposedNewState: &tfsdk.Plan{
Raw: tftypes.NewValue(testSchemaTypeDefault, map[string]tftypes.Value{
"test_optional_default": tftypes.NewValue(tftypes.Bool, false),
"test_computed_default": tftypes.NewValue(tftypes.Bool, false),
}),
Schema: testSchemaDefault,
},
PriorState: testEmptyStateDefault,
ResourceSchema: testSchemaDefault,
Resource: &testprovider.Resource{},
},
expectedResponse: &fwserver.PlanResourceChangeResponse{
PlannedState: &tfsdk.State{
Raw: tftypes.NewValue(testSchemaTypeDefault, map[string]tftypes.Value{
"test_optional_default": tftypes.NewValue(tftypes.Bool, true),
"test_computed_default": tftypes.NewValue(tftypes.Bool, true),
}),
Schema: testSchemaDefault,
},
PlannedPrivate: testEmptyPrivate,
},
},
"resource-configure-data": {
server: &fwserver.Server{
Provider: &testprovider.Provider{},
Expand Down
19 changes: 18 additions & 1 deletion tfsdk/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-go/tftypes"

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// State represents a Terraform state.
Expand Down Expand Up @@ -101,6 +102,22 @@ func (s *State) RemoveResource(ctx context.Context) {
s.Raw = tftypes.NewValue(s.Schema.Type().TerraformType(ctx), nil)
}

// TransformDefaults traverses the schema, identifies any attributes which are null, and
// if the attribute has a default value specified by the `Default` field on the attribute
// then the default value is assigned.
func (s *State) TransformDefaults(ctx context.Context, configRaw tftypes.Value) diag.Diagnostics {
data := s.data()
diags := data.TransformDefaults(ctx, configRaw)

if diags.HasError() {
return diags
}

s.Raw = data.TerraformValue

return diags
}

func (s State) data() fwschemadata.Data {
return fwschemadata.Data{
Description: fwschemadata.DataDescriptionState,
Expand Down

0 comments on commit 2ec22b0

Please sign in to comment.