Recreate Resource If Expired

I am writing a custom terraform provider using terraform-plugin-framework.

The provider creates JWTs which have expiry set (a computed value which is a unix timestamp in the future). I want to recreate/update the resource if the expiry has passed.

No resource inputs have changed, but the JWT is now expired and needs re-creating.
I have tried int64planmodifier.RequiresReplaceIf(UserKeyExpired) but based on my testing and the docs, this is only triggered if “- The resource is planned for update.” already.

How can I accomplish this?

I believe I have solved this, but would still be keen to know if this is the desired way or if there is a best practice I should be following here:

func (r *UserResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
	var data UserResourceModel

	tflog.Info(ctx, "Reading the user resource")

	// Read Terraform prior state data into the model
	resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

	if resp.Diagnostics.HasError() {
		return
	}

	// If the user JWT has expired, remove the resource
	if time.Now().Unix() > data.Expires.ValueInt64() {
		// removeresource
		resp.State.RemoveResource(ctx)
		return
	}

	// Save updated data into Terraform state
	resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

In the Read() function of my Resource, I check if the expiry has passed, if so, I set the resource for removal and return without setting the existing values on the state.

This seems to work.

Hi @Tim-Blyth-SiteHost,

Unless you are cleaning up the old resource elsewhere in your code, this is going to prevent Terraform from deleting the old resource during the next apply. It will also make the resource seem to just disappear from the user’s perspective, and only show that it will be creating a new resource. Now if the resource type actually does disappear when it expires, then maybe this is the correct solution, but most of the time removing the old resource takes some sort of manual call to complete.

It might be better to mark the resource as requiring replacement during the next plan, rather than pretending it doesn’t exist during the read operation.

2 Likes

Thanks for the reply.

Given the ephemeral nature of NATS Users, once a JWT is expired it can essentially be discarded because it no longer has any use. Though depending on how it is stored, I can see how acting as if it is removed, rather than updating it could cause issues.

So I think marking it as disappeared works for my purposes, even if it’s not technically correct.

I am interested in your suggestion though, how can I mark it as needing recreation in the next plan? I have attempted to use PlanModifiers as mentioned in the post, however nothing happened on plans after expiry as no value on the resource had changed, so the resource was never planned for any changes which would trigger the plan modifier.

I might be misunderstanding how plan modifiers work to achieve marking this for update, so if there are any docs or an example you can point me at, that would be great.

You can also use computed fields to trigger replacement of the instance. If you want to replace your instance, at least one of the computed values must be changing and should be marked as computed, which means there should be a change planned.

Hey there @Tim-Blyth-SiteHost :wave: , all managed resources are given the opportunity to produce a plan by Terraform, but as @jbardin mentioned, something will need to be planned to change for a replacement to be valid.


I believe part of what you’re observing is the behavior of the built-in RequiresReplaceIf plan modifier, of which that implementation has those specific rules: terraform-plugin-framework/resource/schema/stringplanmodifier/requires_replace_if.go at c126fe413fcc7fe9668a298ffe53c09bd6f99e64 · hashicorp/terraform-plugin-framework · GitHub

If you want to write your own plan modifier where the logic for checking the expiration is always run, you could build your own plan modifier: Plugin Development - Framework: Plan Modification | Terraform | HashiCorp Developer

A pseudo-code implementation of that plan modifier might look like:

// .. other methods

func (m replaceOnExpireModifier) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) {
	// Do not replace on resource creation.
	if req.State.Raw.IsNull() {
		return
	}

	// Do not replace on resource destroy.
	if req.Plan.Raw.IsNull() {
		return
	}

	// Check req.StateValue to see if it expired, if not expired, return


	// If expired, mark the plan value as unknown (this is just one option for producing a change)
	resp.PlanValue = types.StringUnknown()
	// Set requires_replace to true so Terraform will recreate the instance.
	resp.RequiresReplace = true
}

An alternative option would be the same logic, but implemented in the optional resource-level ModifyPlan method:

Great feedback, I will try these methods out and see if they work for my use-case. Thanks for your time @jbardin @austin.valle