Skip to content

Latest commit

 

History

History
182 lines (148 loc) · 6.5 KB

File metadata and controls

182 lines (148 loc) · 6.5 KB
page_title description
Resources - CustomizeDiff and PlanModifiers: Migrating from SDKv2 to the Framework
Migrate resource customizediff functions in SDKv2 to plan modifiers in the plugin Framework.

Plan Modification

Your provider can modify the Terraform plan to match the expected end state. This can include replacing unknown values with expected known values or marking a resource that must be replaced. Refer to Plan Modification in the Framework documentation for details.

This page explains how to migrate resource CustomizeDiff functions in SDKv2 to PlanModifiers in the plugin Framework.

SDKv2

In SDKv2, plan modification is implemented with the CustomizeDiff field on the schema.Resource struct. The following code shows a basic implementation of plan modification with SDKv2.

func resourceExample() *schema.Resource {
    return &schema.Resource{
        CustomizeDiff: CustomizeDiffFunc,
        /* ... */

Framework

In the Framework, you implement plan modification either by implementing the ResourceWithModifyPlan interface on your resource type, or by implementing PlanModifiers on individual attributes. This page demonstrates how to implement the plan modifiers on individual attributes. Refer to Attributes - Default Values and Attributes - Force New in this guide for further information on how to implement a plan modifier on an attribute.

The ResourceWithModifyPlan interface requires a ModifyPlan function.

The following code shows how you can implement the ModifyPlan function on your resource.Resource type.

func (r *resourceExample) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
    /* ... */
}

Migration Notes

Remember the following differences between SDKv2 and the Framework when completing the migration.

  • In SDKv2, you implement plan modification with the CustomizeDiff field on the schema.Resource struct. In the Framework, you can either implement plan modification for the entire resource by implementing the ResourceWithModifyPlan interface, or on individual attributes by adding PlanModifiers to your resource attributes.
  • Many existing CustomizeDiff implementations may be better suited to implementation as attribute plan modifiers in the Framework.

Example

SDKv2

In SDKv2, the CustomizeDiff field on the schema.Resource struct refers to a function or set of functions that implement plan modification.

The following example shows the use of CustomizeDiff to keep two attributes synchronized (i.e., ensure that they contain the same value) with SDKv2.

func resourcePassword() *schema.Resource {
    /* ... */
    customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_one", "attribute_two"))
    customizeDiffFuncs = append(customizeDiffFuncs, planSyncIfChange("attribute_two", "attribute_one"))

    return &schema.Resource{
        /* ... */
        CustomizeDiff: customdiff.All(
            customizeDiffFuncs...,
        ),
    }
}

The following example shows the implementation of the planSyncIfChange function.

func planSyncIfChange(key, keyToSync string) func(context.Context, *schema.ResourceDiff, interface{}) error {
    return customdiff.IfValueChange(
        key,
        func(ctx context.Context, oldValue, newValue, meta interface{}) bool {
            return oldValue != newValue
        },
        func(_ context.Context, d *schema.ResourceDiff, _ interface{}) error {
            return d.SetNew(keyToSync, d.Get(key))
        },
    )
}

Framework

Many existing CustomizeDiff implementations would be better suited to migration to attribute plan modifiers in the Framework. This code shows the implementation using attribute plan modifiers with the Framework.

func exampleSchema() schema.Schema {
    return schema.Schema{
        /* ... */
        Attributes: map[string]schema.Attribute{
            /* ... */
            "attribute_one": schema.BoolAttribute{
                /* ... */
                PlanModifiers: []planmodifier.Bool{
                    planmodifiers.SyncAttributePlanModifier(),
                    /* ... */
                },
            },

            "attribute_two": schema.BoolAttribute{
                /* ... */
                PlanModifiers: []planmodifier.Bool{
                    planmodifiers.SyncAttributePlanModifier(),
                    /* ... */
                },
            },

The following shows an implementation of SyncAttributePlanModifier in the Framework.

func SyncAttributePlanModifier() planmodifier.Bool {
    return &syncAttributePlanModifier{}
}

type syncAttributePlanModifier struct {
}

func (d *syncAttributePlanModifier) Description(ctx context.Context) string {
    return "Ensures that attribute_one and attribute_two attributes are kept synchronised."
}

func (d *syncAttributePlanModifier) MarkdownDescription(ctx context.Context) string {
    return d.Description(ctx)
}

func (d *syncAttributePlanModifier) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) {
    var attributeOne types.Bool
    diags := req.Plan.GetAttribute(ctx, path.Root("attribute_one"), &attributeOne)
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }

    var attributeTwo types.Bool
    req.Plan.GetAttribute(ctx, path.Root("attribute_two"), &attributeTwo)
    resp.Diagnostics.Append(diags...)
    if resp.Diagnostics.HasError() {
        return
    }

	if !attributeOne.IsNull() && !attributeTwo.IsNull() && (attributeOne.ValueBool() != attributeTwo.ValueBool()) {
		resp.Diagnostics.AddError(
			"attribute_one and attribute_two are both configured with different values",
			"attribute_one is deprecated, use attribute_two instead",
		)
		return
	}

    // Default to true for both attribute_one and attribute_two when both are null.
    if attributeOne.IsNull() && attributeTwo.IsNull() {
        resp.PlanValue = types.BoolValue(true)
        return
    }

    // Default to using value for attribute_two if attribute_one is null
    if attributeOne.IsNull() && !attributeTwo.IsNull() {
        resp.PlanValue = numericConfig
        return
    }

    // Default to using value for attribute_one if attribute_two is null
    if !attributeOne.IsNull() && attributeTwo.IsNull() {
        resp.PlanValue = numberConfig
        return
    }
}