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.
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.
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.
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 , 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.
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
}