DiffSuppressFunc Alternative in Terraform Framework

I couldn’t find an alternative way with the framework to do what DiffSuppressFunc does in resource schema of a SDK based plugin.

Basically, our plugin will be parsing some certain date time, and we don’t want two date time strings that have a slight difference in format and represent the exact same date and time to be considered as a different value. They might be other use cases later as we are migrating more modules to be with framework.

May I have some recommendations about migrating this? Is there any alternative way to make it working? And is there any plan to implement a similar functionality in the framework?

Hi @zliang-akamai :wave: You can find more information about migrating the terraform-plugin-sdk schema DiffSuppressFunc field on this migration guide page: Attribute Fields: Migrating from SDKv2 to the Framework | Terraform | HashiCorp Developer

Where the migration path there is listed as:

PlanModifiers field on attribute or implementation of ResourceWithModifyPlan interface

Sometimes depending on the API, it is necessary to also prevent unexpected resource drift detection in these cases as well, which was supported by the more recent terraform-plugin-sdk DiffSuppressOnRefresh: true behavior, which has the migration path of:

Read method on resource

The terse migration description there really means implementing logic in the Read method where the incoming API value has a similar “semantic equality” check to keep the prior state value instead of replacing it with the refreshed API value. We can update that migration description to be more clear.

While both those solutions can work for now, we are hoping to introduce new functionality into the framework that solves this issue for both situations (planning and refreshing without changes) a little easier via custom value types. In that case, a custom value type would be able to bake in the “semantic equality” logic (similar to validation logic today) and during certain points of Terraform requests, that logic would be invoked to automatically preserve the prior configuration or state value as appropriate. You can follow Classifying normalization vs. drift · Issue #70 · hashicorp/terraform-plugin-framework · GitHub for updates in that space.

Hope this helps.

1 Like

Hi,
Thank you very much for the information!

May I get some more guidance on how to implement this? I have a non-computed and user configured field (a string of time) in the schema, and I would like Terraform not to update the cloud resource when user put another equivalent value in.

For example, "2100-01-02T03:04:05+00:00" and "2100-01-02T03:04:05Z" can be considered as equivalent and both accepted by RFC3339 standard.

I can’t change the planned value in the modifier since it’s configured by the user nor asking Terraform not to perform an update. May I get some suggestions for this case? Thank you!

Hi again,

The solution in this case is to test either the planned value (Create/Update) or prior state value (Read) is equivalent to the API value before saving it into the response state, preferring the planned/prior value if it is equivalent. I can provide some pseudo-code as an example (below) or more realistic code if you can link to your specific code in question.

type ExampleResourceModel struct {
  // ... other fields ...
  Timestamp types.String `tfsdk:"timestamp"`
}

func (r ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp resource.ReadResponse) {
  var data ExampleResourceModel

  resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

  if resp.Diagnostics.HasError() {
    return
  }

  // ... API call ...

  priorStateTimestamp, err := time.Parse(time.RFC3339, data.Timestamp)

  // err handling

  apiTimestamp, err := time.Parse(time.RFC3339, apiResp.Timestamp)

  // err handling

  // If timestamps are not equal, refresh the state with the updated value.
  // Otherwise, do not update the state.
  if !priorStateTimestamp.Equal(apiTimestamp) {
    data.Timestamp = apiResp.Timestamp
  }

  // ... updating other fields into data ...

  resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}

Just to highlight again, once Classifying normalization vs. drift · Issue #70 · hashicorp/terraform-plugin-framework · GitHub is in place, these implementation details in the logic can be removed in preference of custom value types which can handle these value equivalent details automatically. For example, a RFC3339-based custom value type could likely handle all the parsing and error handling to return time.Time values automatically and semantic equality checking such as this.

type ExampleResourceModel struct {
  // ... other fields ...
  Timestamp timetypes.RFC3339Value{} `tfsdk:"timestamp"` // example custom value type
}

func (r ExampleResource) Read(ctx context.Context, req resource.ReadRequest, resp resource.ReadResponse) {
  var data ExampleResourceModel

  resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

  if resp.Diagnostics.HasError() {
    return
  }

  // API call
  // ... updating all fields into data ...
  // The RFC3339Value implementation would automatically keep prior state value if equivalent

  resp.Diagnostics.Append(resp.State.Set(ctx, data)...)
}
1 Like

Hi @bflad,

Thank you so much for the example and the detailed explanation of the concepts. It will be really exciting to see the custom value types coming!

Do you think Terraform will include some common additional types such as time in the built in library (types) in the future?

These custom types would be built on the extensible type system in terraform-plugin-framework, wholly on the provider side of the protocol, but very likely in separate Go module(s) so that the code for these types can be versioned separately. The plan would be to have some HashiCorp-owned types such as JSON strings, time strings (e.g. RFC3339), and a selection of networking-based strings (e.g. IPv4 address).

1 Like

Thank you! It’s very exciting to see these new features coming!

terraform-plugin-framework v1.3.0 was released today with type-based semantic equality functionality. The documentation for it can be found on the revamped Custom Types page. We’re already making motions towards designing common use case types.

1 Like

@bflad I think my provider migration issue with state drift may be related to this semantic equivalence issue. It feels like the custom type may be the way to solve the issue but I need to read the doc and understand it better first.

My issue occurs for both SetAttribute and StringAttribute where the first one is optional and computed, and the second one is computed only.

For any followers of this thread, you can track the common custom types implementation with this GH issue:

1 Like

The above issue has been completed with the creation of three new Go modules. Check out this comment for more info