Skip to content

fix: runner token expiration renewal#305

Open
BoxBoxJason wants to merge 8 commits into
crossplane-contrib:masterfrom
BoxBoxJason:fix/runner-expiration-renewal
Open

fix: runner token expiration renewal#305
BoxBoxJason wants to merge 8 commits into
crossplane-contrib:masterfrom
BoxBoxJason:fix/runner-expiration-renewal

Conversation

@BoxBoxJason

@BoxBoxJason BoxBoxJason commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Description of your changes

This PR adds an automatic renewal mechanism on runner token expiration for the project, group & instance runners.

All of the changes are covered by unit tests

Fixes #304

I have:

  • Read and followed Crossplane's contribution process.
  • Run make reviewable test to ensure this PR is ready for review.

How has this code been tested

I have:

  • Successfully built and ran the provider locally against a kubernetes cluster.
  • Successfully created, updated, and deleted resources of the types I changed / created.
  • Ensured reconciliation loops for the changed / created resource complete without error.
    • Creation
    • Update
    • Deletion
  • Deleted the resource on the app side to ensure the provider correctly handles
    unexpected drift. (should result in recreation of the resource if applicable)
  • Updated the resource on the app side to ensure the provider correctly handles
    unexpected drift. (should result in an update of the resource if applicable)

@BoxBoxJason BoxBoxJason marked this pull request as ready for review April 16, 2026 08:06
@BoxBoxJason BoxBoxJason marked this pull request as draft May 21, 2026 23:05
@BoxBoxJason BoxBoxJason force-pushed the fix/runner-expiration-renewal branch from 82874e5 to 91b0933 Compare May 21, 2026 23:05
)

if runners.ShouldRotateRunnerToken(&cr.Status.AtProvider.CommonRunnerObservation, cr.Spec.ForProvider.TokenRenewBeforeDays, time.Now().UTC()) {
return managed.ExternalObservation{ResourceExists: false}, nil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice improvement overall. One thing worries me here: when renewal is due, Observe() returns ResourceExists: false even though the runner still exists in GitLab. That pushes Crossplane down the create path, and Create() provisions a brand-new runner instead of rotating the token on the existing one. In practice this looks like it would orphan the old runner, change the external name, and leave cleanup attached only to the newest runner. Since the runner client already exposes ResetRunnerAuthenticationToken, would it be safer to keep ResourceExists: true here and handle token renewal in Update() instead? I think the same issue exists in the group/project + cluster mirrors.

meta.SetExternalName(cr, strconv.FormatInt(runner.ID, 10))

now := metav1.NewTime(time.Now().UTC())
cr.Status.AtProvider.CommonRunnerObservation.TokenCreatedAt = &now

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small concern on persistence here: TokenCreatedAt / TokenExpiresAt are only written during Create(), and the GitLab details API does not return them later. If these status mutations are not reliably persisted by the managed reconciler, the next Observe() can lose the renewal metadata and the auto-renew path will never trigger consistently. Could we move this onto a status-safe persistence path, or add a reconcile-level test that proves the timestamps survive a full create -> observe cycle?

}
createdAt = obs.TokenCreatedAt.Time
}
t, ok := common.RotationTime(createdAt, obs.TokenExpiresAt.Time, renewBeforeDays)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another edge case worth guarding: tokenRenewBeforeDays only has a minimum bound today. If a user sets a renewal window larger than the actual token lifetime, RotationTime() ends up in the past immediately, so every reconcile treats the token as already due. With the current recreate behavior that would cause perpetual runner churn; even after switching to reset semantics it would still cause endless resets. A validation or runtime guard for renewAt <= createdAt would make this safer.

},
}),
),
result: managed.ExternalObservation{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test is currently locking in the recreate behavior above by expecting ResourceExists: false once the token is expired. If the intended design is to keep the existing runner and rotate its token, this probably wants to assert that the resource still exists but is not up to date, and then verify a reset call in Update(). Might be worth adjusting these expired-token tests now so they encode the intended renewal semantics rather than the current recreate path.

@henrysachs

Copy link
Copy Markdown
Collaborator

@BoxBoxJason left some comments even though it is a draft i would like to have this personally.

@BoxBoxJason

Copy link
Copy Markdown
Contributor Author

Hey there !

Thanks for this pre review with very good insights. I will implement your suggestions and fixes this week end.

@henrysachs

Copy link
Copy Markdown
Collaborator

@BoxBoxJason could you rebase this one too and also remove the draft status? Because i think we're quite close to merging

Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
@BoxBoxJason BoxBoxJason force-pushed the fix/runner-expiration-renewal branch from a1faeb0 to 7eee0d1 Compare June 2, 2026 20:56
@BoxBoxJason

Copy link
Copy Markdown
Contributor Author

Hello !

I just performed the rebase,
I am not that confident to have this merged right now, as I did not have time to test the corrections against my gitlab instance this week end.

I will have more time this thursday to perform all tests and remove the draft status confidently !

Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
Signed-off-by: BoxBoxJason <contact@boxboxjason.dev>
@BoxBoxJason

Copy link
Copy Markdown
Contributor Author

Turns out you were right !

Observation did not properly persist renewAt / createdAt fields set in Create
After a bit of digging around, I found out that using annotations works a lot better for that purpose.

Tell me what you think about this revised (and this time properly tested) version

@BoxBoxJason BoxBoxJason marked this pull request as ready for review June 4, 2026 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Runner Token not renewed on expiration

2 participants