Replace computed attribue value based on another attribute change

I’d like to replace a computed attribute once another attribute has been changed. As far as I got, plan modifiers is the way to go. However, it’s not clear how to implement “if” function, e.g. how to get another attribute value and understand whether it has been changed.

Hi @dimuon :wave: Welcome to HashiCorp Discuss and thank you for asking this great question.

You are correct that in terraform-plugin-framework, if you know what a specific attribute value will become based on the value of another attribute that attribute or resource plan modification is the way to handle this case. These abstractions allow provider developers to enhance the Terraform’s plan to make it more useful for practitioners.

The framework doesn’t currently provide any “built in” plan modifiers for this particular situation, but you can implement your own. If you are directly affecting a single attribute, an attribute plan modifier is probably the way to go, which means implementing the tfsdk.AttributePlanModifier interface. Attribute plan modifiers allow you to modify the planned value for the attribute its placed on and have access to the entire configuration, plan, and prior state during Terraform’s planning phase.

To setup a contrived example, let us say there is this example schema:

map[string]tfsdk.Attribute{
  "required_attribute": {
    Required: true,
    Type:     types.StringType,
  },
  "computed_attribute": {
    Computed: true,
    Type:     types.StringType,
  },
}

We can create an attribute plan modifier which sets a known value for computed_attribute based on required_attribute:

// Verify our implementation satisfies the interface
var _ tfsdk.AttributePlanModifier = examplePlanModifier{}

func valueBasedOn(otherAttribute path.Path) tfsdk.AttributePlanModifier {
  return valueBasedOnPlanModifier{
    otherAttribute: otherAttribute,
  }
}

// Attribute plan modifier implementation
type valueBasedOnPlanModifier struct{
  otherAttribute path.Path
}

func (m valueBasedOnPlanModifier) Description(ctx context.Context) string {
  return fmt.Sprintf("Sets the planned attribute value to XXX if %s is XXX, otherwise the value is YYY", m.otherAttribute)
}

func (m valueBasedOnPlanModifier) MarkdownDescription(ctx context.Context) string {
  return m.Description(ctx)
}

func (m valueBasedOnPlanModifier) Modify(ctx context.Context, req tfsdk.ModifyAttributePlanRequest, resp *tfsdk.ModifyAttributePlanResponse) {
  var otherValue types.String // contrived example is using string attributes

  resp.Diagnostics.Append(req.Plan.GetAttribute(ctx, m.otherAttribute, &otherValue)...)

  if resp.Diagnostics.HasError() {
    return
  }

  if otherValue.Value != "XXX" {
    resp.AttributePlan = types.String{Value: "YYY"}
    return
  }

  resp.AttributePlan = types.String{Value: "XXX"}
}

Then update the schema to include that plan modifier:

map[string]tfsdk.Attribute{
  "required_attribute": {
    Required: true,
    Type:     types.StringType,
  },
  "computed_attribute": {
    Computed: true,
    Type:     types.StringType,
    PlanModifiers: []tfsdk.AttributePlanModifier{
      valueBasedOn(path.Root("required_attribute")),
    },
  },
}

The attribute plan modifier itself can be implemented in a more generic and powerful fashion as necessary, but hopefully this contrived example gives you enough context to show the concepts here. Please reach out if it does not!

Hi @bflad,

Thank you for the welcoming and for the detailed answer. Much appreciated!

It totally makes sense, at least for now :slight_smile:

Thank you again,
Dmitry