Skip to content

Commit

Permalink
internal/fwschemadata: Ensure Default implementations receive request…
Browse files Browse the repository at this point in the history
… Path and have response Diagnostics handled (#780)

Reference: #778

This changeset also includes panic prevention for `defaults.List`, `defaults.Map`, and `defaults.Set` implementations which do not return `Diagnostics` or `PlanValue`.

Previously before logic update:

```
--- FAIL: TestDataDefault (0.00s)
    --- FAIL: TestDataDefault/bool-attribute-response-diagnostics (0.00s)
        /Users/bflad/src/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_default_test.go:6959: unexpected diagnostics difference:   diag.Diagnostics(
            - 	nil,
            + 	{
            + 		diag.ErrorDiagnostic{detail: "test error detail", summary: "test error summary"},
            + 		diag.WarningDiagnostic{detail: "test warning detail", summary: "test warning summary"},
            + 	},
              )

--- FAIL: TestDataDefault (0.01s)
    --- FAIL: TestDataDefault/list-attribute-request-path (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
	panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x2 addr=0x30 pc=0x100a161ec]

goroutine 14 [running]:
testing.tRunner.func1.2({0x1010fe140, 0x10137c890})
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/testing/testing.go:1526 +0x1c8
testing.tRunner.func1()
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/testing/testing.go:1529 +0x384
panic({0x1010fe140, 0x10137c890})
	/opt/homebrew/Cellar/go/1.20.5/libexec/src/runtime/panic.go:884 +0x204
github.com/hashicorp/terraform-plugin-framework/types/basetypes.ListValue.ToTerraformValue({{0x0, 0x0, 0x0}, {0x0, 0x0}, 0x0}, {0x101154860, 0x1400009c018})
	/Users/bflad/src/github.com/hashicorp/terraform-plugin-framework/types/basetypes/list_value.go:208 +0x5c
github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata.(*Data).TransformDefaults.func1(0x140004ecba0?, {{0x101156f08?, 0x14000165530?}, {0x0?, 0x0?}})
	/Users/bflad/src/github.com/hashicorp/terraform-plugin-framework/internal/fwschemadata/data_default.go:168 +0x1324
```
  • Loading branch information
bflad committed Jun 20, 2023
1 parent 7f6bef5 commit 4ededd7
Show file tree
Hide file tree
Showing 14 changed files with 2,077 additions and 159 deletions.
6 changes: 6 additions & 0 deletions .changes/unreleased/BUG FIXES-20230616-140718.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: BUG FIXES
body: 'resource/schema: Ensured `Default` implementations received request `Path`
and have response `Diagnostics` handled'
time: 2023-06-16T14:07:18.774874-04:00
custom:
Issue: "778"
6 changes: 6 additions & 0 deletions .changes/unreleased/BUG FIXES-20230616-140834.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: BUG FIXES
body: 'resource/schema: Prevented panics with `Default` implementations on list, map,
and set where no response `Diagnostics` or `PlanValue` was returned'
time: 2023-06-16T14:08:34.806147-04:00
custom:
Issue: "778"
243 changes: 198 additions & 45 deletions internal/fwschemadata/data_default.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,76 +75,229 @@ func (d *Data) TransformDefaults(ctx context.Context, configRaw tftypes.Value) d
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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.BoolRequest{
Path: fwPath,
}
resp := defaults.BoolResponse{}

defaultValue.DefaultBool(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.Float64Request{
Path: fwPath,
}
resp := defaults.Float64Response{}

defaultValue.DefaultFloat64(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.Int64Request{
Path: fwPath,
}
resp := defaults.Int64Response{}

defaultValue.DefaultInt64(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.ListRequest{
Path: fwPath,
}
resp := defaults.ListResponse{}

defaultValue.DefaultList(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

if resp.PlanValue.ElementType(ctx) == nil {
logging.FrameworkWarn(ctx, "attribute default declared, but returned no value")

return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}
req := defaults.MapRequest{
Path: fwPath,
}
resp := defaults.MapResponse{}

defaultValue.DefaultMap(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

if resp.PlanValue.ElementType(ctx) == nil {
logging.FrameworkWarn(ctx, "attribute default declared, but returned no value")

return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.NumberRequest{
Path: fwPath,
}
resp := defaults.NumberResponse{}

defaultValue.DefaultNumber(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.ObjectRequest{
Path: fwPath,
}
resp := defaults.ObjectResponse{}

defaultValue.DefaultObject(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.SetRequest{
Path: fwPath,
}
resp := defaults.SetResponse{}

defaultValue.DefaultSet(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

if resp.PlanValue.ElementType(ctx) == nil {
logging.FrameworkWarn(ctx, "attribute default declared, but returned no value")

return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

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)

if defaultValue == nil {
return tfTypeValue, nil
}

req := defaults.StringRequest{
Path: fwPath,
}
resp := defaults.StringResponse{}

defaultValue.DefaultString(ctx, req, &resp)

diags.Append(resp.Diagnostics...)

if resp.Diagnostics.HasError() {
return tfTypeValue, nil
}

logging.FrameworkTrace(ctx, fmt.Sprintf("setting attribute %s to default value: %s", fwPath, resp.PlanValue))

return resp.PlanValue.ToTerraformValue(ctx)
}

return tfTypeValue, nil
Expand Down
Loading

0 comments on commit 4ededd7

Please sign in to comment.