Skip to content

Commit

Permalink
tfsdk: Support block plan modification (#224)
Browse files Browse the repository at this point in the history
Reference: #222

Adds `PlanModifiers` within `Block`s and ensures any children attribute and block plan modifiers of a `Block` are executed.
  • Loading branch information
bflad authored Nov 19, 2021
1 parent 8493a88 commit 4356dd7
Show file tree
Hide file tree
Showing 10 changed files with 2,301 additions and 257 deletions.
171 changes: 164 additions & 7 deletions tfsdk/attribute.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ type Attribute struct {
// resource-level plan modifications.
//
// Any errors will prevent further execution of this sequence
// of modifiers and modifiers associated with any nested Attribute, but will not
// prevent execution of PlanModifiers on any other Attribute in the Schema.
// of modifiers and modifiers associated with any nested Attribute, but
// will not prevent execution of PlanModifiers on any other Attribute or
// Block in the Schema.
//
// Plan modification only applies to resources, not data sources or
// providers. Setting PlanModifiers on a data source or provider attribute
Expand Down Expand Up @@ -477,9 +478,10 @@ func (a Attribute) validateAttributes(ctx context.Context, req ValidateAttribute
}

// modifyPlan runs all AttributePlanModifiers
func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifyAttributePlanResponse) {
func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanRequest, resp *ModifySchemaPlanResponse) {
attrConfig, diags := req.Config.getAttributeValue(ctx, req.AttributePath)
resp.Diagnostics.Append(diags...)

// Only on new errors.
if diags.HasError() {
return
Expand All @@ -488,6 +490,7 @@ func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanReques

attrState, diags := req.State.getAttributeValue(ctx, req.AttributePath)
resp.Diagnostics.Append(diags...)

// Only on new errors.
if diags.HasError() {
return
Expand All @@ -496,28 +499,182 @@ func (a Attribute) modifyPlan(ctx context.Context, req ModifyAttributePlanReques

attrPlan, diags := req.Plan.getAttributeValue(ctx, req.AttributePath)
resp.Diagnostics.Append(diags...)

// Only on new errors.
if diags.HasError() {
return
}
req.AttributePlan = attrPlan

var requiresReplace bool
for _, planModifier := range a.PlanModifiers {
modifyResp := &ModifyAttributePlanResponse{
AttributePlan: resp.AttributePlan,
RequiresReplace: resp.RequiresReplace,
AttributePlan: req.AttributePlan,
RequiresReplace: requiresReplace,
}

planModifier.Modify(ctx, req, modifyResp)

req.AttributePlan = modifyResp.AttributePlan
resp.AttributePlan = modifyResp.AttributePlan
resp.Diagnostics.Append(modifyResp.Diagnostics...)
resp.RequiresReplace = modifyResp.RequiresReplace
requiresReplace = modifyResp.RequiresReplace

// Only on new errors.
if modifyResp.Diagnostics.HasError() {
return
}
}

if requiresReplace {
resp.RequiresReplace = append(resp.RequiresReplace, req.AttributePath)
}

setAttrDiags := resp.Plan.SetAttribute(ctx, req.AttributePath, req.AttributePlan)
resp.Diagnostics.Append(setAttrDiags...)

if setAttrDiags.HasError() {
return
}

if !a.definesAttributes() {
return
}

nm := a.Attributes.GetNestingMode()
switch nm {
case NestingModeList:
l, ok := req.AttributePlan.(types.List)

if !ok {
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath)
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Attribute Plan Modification Error",
"Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(),
)

return
}

for idx := range l.Elems {
for name, attr := range a.Attributes.GetAttributes() {
attrReq := ModifyAttributePlanRequest{
AttributePath: req.AttributePath.WithElementKeyInt(idx).WithAttributeName(name),
Config: req.Config,
Plan: resp.Plan,
ProviderMeta: req.ProviderMeta,
State: req.State,
}

attr.modifyPlan(ctx, attrReq, resp)
}
}
case NestingModeSet:
s, ok := req.AttributePlan.(types.Set)

if !ok {
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath)
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Attribute Plan Modification Error",
"Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(),
)

return
}

for _, value := range s.Elems {
tfValueRaw, err := value.ToTerraformValue(ctx)

if err != nil {
err := fmt.Errorf("error running ToTerraformValue on element value: %v", value)
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Attribute Plan Modification Error",
"Attribute plan modification cannot convert element into a Terraform value. Report this to the provider developer:\n\n"+err.Error(),
)

return
}

tfValue := tftypes.NewValue(s.ElemType.TerraformType(ctx), tfValueRaw)

for name, attr := range a.Attributes.GetAttributes() {
attrReq := ModifyAttributePlanRequest{
AttributePath: req.AttributePath.WithElementKeyValue(tfValue).WithAttributeName(name),
Config: req.Config,
Plan: resp.Plan,
ProviderMeta: req.ProviderMeta,
State: req.State,
}

attr.modifyPlan(ctx, attrReq, resp)
}
}
case NestingModeMap:
m, ok := req.AttributePlan.(types.Map)

if !ok {
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath)
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Attribute Plan Modification Error",
"Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(),
)

return
}

for key := range m.Elems {
for name, attr := range a.Attributes.GetAttributes() {
attrReq := ModifyAttributePlanRequest{
AttributePath: req.AttributePath.WithElementKeyString(key).WithAttributeName(name),
Config: req.Config,
Plan: resp.Plan,
ProviderMeta: req.ProviderMeta,
State: req.State,
}

attr.modifyPlan(ctx, attrReq, resp)
}
}
case NestingModeSingle:
o, ok := req.AttributePlan.(types.Object)

if !ok {
err := fmt.Errorf("unknown attribute value type (%T) for nesting mode (%T) at path: %s", req.AttributePlan, nm, req.AttributePath)
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Attribute Plan Modification Error",
"Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(),
)

return
}

if len(o.Attrs) == 0 {
return
}

for name, attr := range a.Attributes.GetAttributes() {
attrReq := ModifyAttributePlanRequest{
AttributePath: req.AttributePath.WithAttributeName(name),
Config: req.Config,
Plan: resp.Plan,
ProviderMeta: req.ProviderMeta,
State: req.State,
}

attr.modifyPlan(ctx, attrReq, resp)
}
default:
err := fmt.Errorf("unknown attribute nesting mode (%T: %v) at path: %s", nm, nm, req.AttributePath)
resp.Diagnostics.AddAttributeError(
req.AttributePath,
"Attribute Plan Modification Error",
"Attribute plan modifier cannot walk schema. Report this to the provider developer:\n\n"+err.Error(),
)

return
}
}
11 changes: 9 additions & 2 deletions tfsdk/attribute_plan_modification.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package tfsdk

import (
"context"
"errors"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/attr"
Expand Down Expand Up @@ -127,7 +128,10 @@ func (r RequiresReplaceModifier) Modify(ctx context.Context, req ModifyAttribute
}

attrSchema, err := req.State.Schema.AttributeAtPath(req.AttributePath)
if err != nil {

// Path may lead to block instead of attribute. Blocks cannot be Computed.
// If ErrPathIsBlock, attrSchema.Computed will still be false later.
if err != nil && !errors.Is(err, ErrPathIsBlock) {
resp.Diagnostics.AddAttributeError(req.AttributePath,
"Error finding attribute schema",
fmt.Sprintf("An unexpected error was encountered retrieving the schema for this attribute. This is always a bug in the provider.\n\nError: %s", err),
Expand Down Expand Up @@ -264,7 +268,10 @@ func (r RequiresReplaceIfModifier) Modify(ctx context.Context, req ModifyAttribu
}

attrSchema, err := req.State.Schema.AttributeAtPath(req.AttributePath)
if err != nil {

// Path may lead to block instead of attribute. Blocks cannot be Computed.
// If ErrPathIsBlock, attrSchema.Computed will still be false later.
if err != nil && !errors.Is(err, ErrPathIsBlock) {
resp.Diagnostics.AddAttributeError(req.AttributePath,
"Error finding attribute schema",
fmt.Sprintf("An unexpected error was encountered retrieving the schema for this attribute. This is always a bug in the provider.\n\nError: %s", err),
Expand Down
Loading

0 comments on commit 4356dd7

Please sign in to comment.