Skip to content

Commit

Permalink
Add resource schema-based support for static default values (#674)
Browse files Browse the repository at this point in the history
* Adding Default Value interfaces (#668)

* Fixing docs (#668)

* Adding framework-defined default value interfaces (#668)

* Add framework schema interface (#668)

* Add resource attribute interface (#668)

* Adding TransformPlanDefaults for BoolAttribute (#668)

* Modifying TransformDefaults and incorporating into PlanResourceChange (#668)

* Add handling for float64, int64, number and string defaults in TransformDefaults (#668)

* Add handling to prevent MarkComputedNilsAsUnknown overwriting default values (#668)

* Add handling for list, map, object and set (#668)

* Add handling for list, map, object and set in PlanResourceChange (#668)

* Adding test coverage for list nested attribute (#668)

* Refactoring func name to {TYPE}DefaultValue() (#668)

* Removing TransformDefaults() from tfsdk.State so as not to expose it in the public API (#668)

* Updating documentation for Default on resource schema attributes (#668)

* Adding Default to remaining resource schema attributes (#668)

* Adding further test coverage for TransformDefaults() for list, map, set, single nested attributes (#668)

* Adding further test coverage for PlanResourceChange() for map, set and single nested attributes (#668)

* Removing erroneous character (#668)

* Updating tests to use Computed everywhere (#668)

* Adding schema validation to only permit default values on computed attributes (#668)

* Adding docs for default values (#668)

* Further updates for docs (#668)

* Apply suggestions from code review

Co-authored-by: Brian Flad <bflad417@gmail.com>

* Updates following code review (#668)

* Apply suggestions from code review

Co-authored-by: Brian Flad <bflad417@gmail.com>

* Renaming test functions for consistency (#668)

* Fixing link (#668)

---------

Co-authored-by: Brian Flad <bflad417@gmail.com>
  • Loading branch information
bendbennett and bflad committed Mar 20, 2023
1 parent 89889d8 commit cbd1cfe
Show file tree
Hide file tree
Showing 97 changed files with 13,326 additions and 168 deletions.
77 changes: 77 additions & 0 deletions internal/fwschema/attribute_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package fwschema

import (
"github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults"
)

// AttributeWithBoolDefaultValue is an optional interface on Attribute which
// enables Bool default value support.
type AttributeWithBoolDefaultValue interface {
Attribute

BoolDefaultValue() defaults.Bool
}

// AttributeWithFloat64DefaultValue is an optional interface on Attribute which
// enables Float64 default value support.
type AttributeWithFloat64DefaultValue interface {
Attribute

Float64DefaultValue() defaults.Float64
}

// AttributeWithInt64DefaultValue is an optional interface on Attribute which
// enables Int64 default value support.
type AttributeWithInt64DefaultValue interface {
Attribute

Int64DefaultValue() defaults.Int64
}

// AttributeWithListDefaultValue is an optional interface on Attribute which
// enables List default value support.
type AttributeWithListDefaultValue interface {
Attribute

ListDefaultValue() defaults.List
}

// AttributeWithMapDefaultValue is an optional interface on Attribute which
// enables Map default value support.
type AttributeWithMapDefaultValue interface {
Attribute

MapDefaultValue() defaults.Map
}

// AttributeWithNumberDefaultValue is an optional interface on Attribute which
// enables Number default value support.
type AttributeWithNumberDefaultValue interface {
Attribute

NumberDefaultValue() defaults.Number
}

// AttributeWithObjectDefaultValue is an optional interface on Attribute which
// enables Object default value support.
type AttributeWithObjectDefaultValue interface {
Attribute

ObjectDefaultValue() defaults.Object
}

// AttributeWithSetDefaultValue is an optional interface on Attribute which
// enables Set default value support.
type AttributeWithSetDefaultValue interface {
Attribute

SetDefaultValue() defaults.Set
}

// AttributeWithStringDefaultValue is an optional interface on Attribute which
// enables String default value support.
type AttributeWithStringDefaultValue interface {
Attribute

StringDefaultValue() defaults.String
}
151 changes: 151 additions & 0 deletions internal/fwschemadata/data_default.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package fwschemadata

import (
"context"
"errors"
"fmt"

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

"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/internal/fromtftypes"
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/defaults"
)

// TransformDefaults walks the schema and applies schema defined default values
// when configRaw 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) {
fwPath, fwPathDiags := fromtftypes.AttributePath(ctx, tfTypePath, d.Schema)

diags.Append(fwPathDiags...)

// Do not transform if path cannot be converted.
// Checking against fwPathDiags will capture all errors.
if fwPathDiags.HasError() {
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, err := d.Schema.AttributeAtTerraformPath(ctx, tfTypePath)

if err != nil {
if errors.Is(err, fwschema.ErrPathInsideAtomicAttribute) {
// ignore attributes/elements inside schema.Attributes, they have no schema of their own
logging.FrameworkTrace(ctx, "attribute is a non-schema attribute, not setting default")
return tfTypeValue, nil
}

if errors.Is(err, fwschema.ErrPathIsBlock) {
// ignore blocks, they do not have a computed field
logging.FrameworkTrace(ctx, "attribute is a block, not setting default")
return tfTypeValue, nil
}

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

switch a := attrAtPath.(type) {
case fwschema.AttributeWithBoolDefaultValue:
defaultValue := a.BoolDefaultValue()
if defaultValue != nil {
resp := defaults.BoolResponse{}
defaultValue.DefaultBool(ctx, defaults.BoolRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithFloat64DefaultValue:
defaultValue := a.Float64DefaultValue()
if defaultValue != nil {
resp := defaults.Float64Response{}
defaultValue.DefaultFloat64(ctx, defaults.Float64Request{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithInt64DefaultValue:
defaultValue := a.Int64DefaultValue()
if defaultValue != nil {
resp := defaults.Int64Response{}
defaultValue.DefaultInt64(ctx, defaults.Int64Request{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithListDefaultValue:
defaultValue := a.ListDefaultValue()
if defaultValue != nil {
resp := defaults.ListResponse{}
defaultValue.DefaultList(ctx, defaults.ListRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithMapDefaultValue:
defaultValue := a.MapDefaultValue()
if defaultValue != nil {
resp := defaults.MapResponse{}
defaultValue.DefaultMap(ctx, defaults.MapRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithNumberDefaultValue:
defaultValue := a.NumberDefaultValue()
if defaultValue != nil {
resp := defaults.NumberResponse{}
defaultValue.DefaultNumber(ctx, defaults.NumberRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithObjectDefaultValue:
defaultValue := a.ObjectDefaultValue()
if defaultValue != nil {
resp := defaults.ObjectResponse{}
defaultValue.DefaultObject(ctx, defaults.ObjectRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithSetDefaultValue:
defaultValue := a.SetDefaultValue()
if defaultValue != nil {
resp := defaults.SetResponse{}
defaultValue.DefaultSet(ctx, defaults.SetRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
case fwschema.AttributeWithStringDefaultValue:
defaultValue := a.StringDefaultValue()
if defaultValue != nil {
resp := defaults.StringResponse{}
defaultValue.DefaultString(ctx, defaults.StringRequest{}, &resp)
logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath.String(), resp.PlanValue.String()))
return resp.PlanValue.ToTerraformValue(ctx)
}
}

return tfTypeValue, nil
})

return diags
}

0 comments on commit cbd1cfe

Please sign in to comment.