🤖 feat: surface entitlements and gate provisioner reconciliation#67
Conversation
|
@codex review Please review the entitlement status + provisioner gating changes. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e54c913ecd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
e54c913 to
1887fdd
Compare
|
@codex review Addressed feedback about status-update loops by only updating |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1887fdddbd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
1887fdd to
0efa5b7
Compare
|
@codex review Addressed the new feedback by avoiding no-op status updates on the entitlement |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0efa5b7b64
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
0efa5b7 to
f20b393
Compare
|
@codex review Addressed the stale fast-path concern by re-checking entitlements via API once |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f20b3933b9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
f20b393 to
167caed
Compare
|
@codex review Addressed the stale entitled fast-path concern: stale cached entitlement status |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 167caedd49
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
167caed to
a4ddad1
Compare
|
@codex review Addressed the timestamp freshness concern by refreshing |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a4ddad17c0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Add CoderControlPlane status fields for license tier and external provisioner entitlements, reconcile them from coderd, and gate CoderProvisioner reconciliation when external provisioner daemons are not entitled. Also add a provisioner condition for entitlement state, wire a control-plane watch/index for faster rechecks, and extend bootstrap/tests for entitlements. Follow-ups: - avoid control-plane entitlements status churn while still refreshing `status.entitlementsLastChecked` periodically (and on value changes) and requeueing entitlement checks on the refresh interval - avoid no-op status writes on provisioner entitlement requeue paths so the configured backoff is respected in unlicensed environments - re-check stale control-plane entitlement cache entries (including both entitled and not-entitled fast paths) via API before deciding gating --- _Generated with [`mux`](https://github.com/coder/mux) • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$1.23`_ <!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=1.23 -->
a4ddad1 to
e56084d
Compare
|
@codex review Addressed the latest concern by scheduling periodic entitlement reconciliation |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Surface license tier and entitlements on
CoderControlPlane.status, and gateCoderProvisionerreconciliation on theexternal_provisioner_daemonsentitlement so unlicensed setups short-circuit cleanly.
Background
External provisioner daemons are license-gated in Coder. Before this change,
CoderProvisionerreconciliation could attempt key/deployment work even whenthe deployment was not entitled, causing noisy failures and wasted reconcile
work.
Implementation
licenseTierentitlementsLastCheckedexternalProvisionerDaemonsEntitlement/api/v2/entitlementsusing operator credentials, with best-effort tier derivation.
CoderProvisionerConditionExternalProvisionersEntitledand enforced anentitlement gate in provisioner reconciliation:
Entitlements(ctx, coderURL, sessionToken).CoderControlPlanewatch +spec.controlPlaneRef.nameindex in theprovisioner controller for quicker entitlement re-evaluation after control
plane status changes.
Validation
make codegenmake manifestsmake docs-referencemake verify-vendormake testmake buildmake lintmake docs-buildRisks
controllers.
unavailable; mitigated by explicit condition reasons, retries, and fallback
query behavior.
📋 Implementation Plan
Plan: Surface license tier/entitlements on
CoderControlPlaneand gateCoderProvisionerContext / Why
External provisioner daemons are a paid Coder feature. Today,
CoderProvisionerReconcilerwill attempt to create provisioner keys and deployprovisionerdpods even when the target Coder deployment is not entitled, leading to confusing failures and wasted reconciliation work.We recently merged license reconciliation into the
CoderControlPlanecontroller (it already tracks when a license was last applied). We can extend this to also surface license tier/type and the external provisioner entitlement onCoderControlPlane.status, then use that as a fast path for provisioner reconciliation.Goals:
CoderControlPlane: publishstatus.licenseTier(best-effort) andstatus.externalProvisionerDaemonsEntitlementby querying/api/v2/entitlementswith the operator token.CoderProvisioner: short-circuit early when the control plane reportsexternal_provisioner_daemonsis not entitled, and re-evaluate automatically once it becomes entitled.Non-goals (initially): deleting already-created provisioner Deployments/keys when a license is removed; we can decide policy later.
Evidence (repo + upstream SDK)
CoderControlPlanealready has license reconciliation support and records:status.licenseLastAppliedandstatus.licenseLastAppliedHash(api/v1alpha1/codercontrolplane_types.go).reconcileLicense(...)uploads license viacodersdk.AddLicenseand setsLicenseAppliedcondition (internal/controller/codercontrolplane_controller.go).CoderProvisionerReconcilercurrently:CoderControlPlane, reads bootstrap session token, then callsBootstrapClient.EnsureProvisionerKey(...)and creates Secret/RBAC/Deployment (internal/controller/coderprovisioner_controller.go).codersdkexposes entitlements:Client.Entitlements(ctx) (codersdk.Entitlements, error).codersdk.FeatureExternalProvisionerDaemons = "external_provisioner_daemons".Entitlement.Entitled()is true forentitledandgrace_period.docs/admin/users/groups-roles.md→ maps tocodersdk.FeatureCustomRoles).docs/admin/users/organizations.md→ maps tocodersdk.FeatureMultipleOrganizations).Implementation details
1) Surface license tier/type + provisioner entitlement on
CoderControlPlane.status(fast path)Files:
api/v1alpha1/codercontrolplane_types.gointernal/controller/codercontrolplane_controller.gointernal/app/controllerapp/controllerapp.gointernal/controller/codercontrolplane_controller_test.goAPI additions (status fields)
Add new, user-visible status fields so other controllers can make fast decisions without extra Coder API calls:
Controller logic
EntitlementsInspector(or similar) interface onCoderControlPlaneReconcilerwith an SDK-backed implementation usingcodersdk.Client.Entitlements.controllerapp.SetupControllersso production always reports these fields.reconcileEntitlements(...)step (called afterreconcileLicense(...)so the values reflect the most recently applied license) that:spec.licenseSecretRefis nil (so status reflects licenses applied out-of-band).nextStatus.Phase == Ready,nextStatus.OperatorAccessReady,nextStatus.OperatorTokenSecretRef != nil, andnextStatus.URL != ""./api/v2/entitlements.EntitlementsLastChecked=now,ExternalProvisionerDaemonsEntitlement, andLicenseTier.Tier derivation should be explicit and documented. A practical best-effort heuristic is:
Error handling guidance:
operatorAccessRetryIntervalwhen entitlements cannot be queried, so the status eventually updates when a license is installed later.2) Add a provisioner status condition for entitlement
File:
api/v1alpha1/coderprovisioner_types.goAdd a new condition constant (name bikesheddable; keep it explicit):
Rationale: keeps licensing failures distinct from bootstrap secret / key failures.
3) Add an entitlements fetch helper to
internal/coderbootstrapFiles:
internal/coderbootstrap/client.go(interface + SDK implementation)internal/controller/*_test.gofakes implementing the interfaceExtend
coderbootstrap.Clientwith an entitlements method:Implement on
SDKClientusing the existing authenticated-client helper (reusing timeout + optional rate-limit bypass):Why do this instead of direct
codersdk.New(...)in the reconciler?BootstrapClient.Entitlements.4) Short-circuit in
CoderProvisionerReconciler.ReconcileFile:
internal/controller/coderprovisioner_controller.goAdd a new entitlement gate immediately after we have:
controlPlane.Status.URL(already required), andsessionToken(bootstrap credentials).Fast path:
controlPlane.status.entitlementsLastCheckedis set andcontrolPlane.status.externalProvisionerDaemonsEntitlementis present (and optionally not stale), use it to decide without calling the Coder API.Fallback:
r.BootstrapClient.Entitlements(...)and evaluateexternal_provisioner_daemonsdirectly.Pseudo-flow:
Recommended condition reasons/messages:
Re-evaluation requirement:
RequeueAfterwhen not entitled so the controller will automatically re-check.Placement note:
5) (Optional but recommended) Watch
CoderControlPlanechanges to reconcile fasterFile:
internal/controller/coderprovisioner_controller.go(SetupWithManager)Add a watch on
CoderControlPlanethat enqueuesCoderProvisionerobjects in the same namespace whosespec.controlPlaneRef.namematches the updated control plane.This reduces time-to-recover after a license install from “up to RequeueAfter” to “immediate”.
Implementation sketch:
CoderProvisioner.spec.controlPlaneRef.name.SetupWithManager, addWatches(&coderv1alpha1.CoderControlPlane{}, handler.EnqueueRequestsFromMapFunc(...)).(If we do this, keep the periodic
RequeueAfteranyway; it’s the safety net.)Tests
Unit/envtest:
CoderControlPlanesurfaces license tier + entitlementsFile:
internal/controller/codercontrolplane_controller_test.goAdd coverage for the new status fields:
EntitlementsInspector(or whatever interface we introduce) that returns a controlledcodersdk.Entitlementspayload.CoderControlPlane.statusis populated after reconcile when the control plane is Ready and operator access is available:entitlementsLastCheckedis set.externalProvisionerDaemonsEntitlementmatches the entitlements payload.licenseTieris derived correctly (e.g.,nonewhenhas_license=false,trialwhentrial=true,premiumwhencustom_rolesormultiple_organizationsare entitled, elseenterprise).Unit/envtest:
CoderProvisionershort-circuit behaviorFile:
internal/controller/coderprovisioner_controller_test.goExtend the shared
fakeBootstrapClient(currently incoderworkspaceproxy_controller_test.go) to implement:Add test cases:
Not entitled (fast path)
controlPlane.status.entitlementsLastCheckedandcontrolPlane.status.externalProvisionerDaemonsEntitlement=not_entitled.BootstrapClient.Entitlementsis not called.EnsureProvisionerKeyis not called.ExternalProvisionersEntitled=False.RequeueAfter > 0.Not entitled (fallback API)
features[external_provisioner_daemons].entitlement=not_entitled.Entitled
entitled/grace_periodor have the fake entitlements returnentitled.ExternalProvisionersEntitled=True.Entitlements API forbidden / error
codersdk.Errorwith 401/403 (or generic error).Forbidden/EntitlementsQueryFailedand the reconcile retries.(If we implement the optional watch) verify indexer + mapping function enqueues provisioners.
Validation / Rollout
make test,make build,make lint.CoderControlPlaneStatusfields), runmake codegenandmake manifests, and commit generated changes.Generated with
mux• Model:openai:gpt-5.3-codex• Thinking:xhigh• Cost: $1.23