Skip to content

🤖 feat: bootstrap coderd operator access from CoderControlPlane#49

Merged
ThomasK33 merged 9 commits into
mainfrom
control-plane-5q3h
Feb 11, 2026
Merged

🤖 feat: bootstrap coderd operator access from CoderControlPlane#49
ThomasK33 merged 9 commits into
mainfrom
control-plane-5q3h

Conversation

@ThomasK33
Copy link
Copy Markdown
Member

Summary

This PR bootstraps durable coderd API access from CoderControlPlane by provisioning a controller-owned system user and token directly in coderd Postgres, then storing the minted token in a managed Kubernetes Secret.

Background

We need in-cluster components to authenticate to the coderd instance created by CoderControlPlane without manual setup. The implementation preserves first-user setup UX by creating a coder-k8s-operator system user (is_system=true) and managing token issuance in the backing database.

Implementation

  • Added spec.operatorAccess fields (disabled, generatedTokenSecretName) and status fields (operatorTokenSecretRef, operatorAccessReady) to CoderControlPlane API.
  • Implemented internal/coderbootstrap/PostgresOperatorAccessProvisioner to:
    • upsert coder-k8s-operator as a system owner user,
    • ensure organization-admin membership across organizations,
    • rotate a stable-named API token with coder:all scope and return plaintext token.
  • Wired provisioner into CoderControlPlaneReconciler and controller app startup.
  • Extended reconcile flow to resolve CODER_PG_CONNECTION_URL from spec.extraEnv (literal or secretKeyRef), create/update managed token Secret, and update status readiness/ref fields.
  • Added controller tests for missing PG URL, disabled operator access, literal URL provisioning, and secretRef URL provisioning.
  • Added provisioner unit tests for request validation and token-part generation behavior.
  • Regenerated/updated deepcopy, CRD manifests, API reference docs, and vendored postgres dependency (github.com/lib/pq).

Validation

  • make verify-vendor
  • make test
  • make build
  • make lint

Risks

  • Medium: Direct DB provisioning depends on coderd schema invariants for users/memberships/api keys. This is mitigated with explicit assertions, idempotent upserts, and controller retry behavior on transient provisioning failures.

📋 Implementation Plan

Plan: Bootstrap coderd API access from CoderControlPlane

Context / Why

We want the CoderControlPlane reconciler to establish durable API access to the coderd instance it deploys, so other in-cluster components (e.g. the aggregated API server in a later PR) can authenticate to coderd and perform CRUD.

Key requirements:

  • The operator must create/ensure a coder-k8s-operator Coder user by default.
  • There must be an option to disable this behavior.
  • It must not disrupt the first-user (initial setup) experience when a human opens the Coder URL for the first time.
  • After the operator user exists, mint an API token scoped to coder:all for now.
  • Non-goal for this PR: wiring the aggregated API server to use the token.

Reality check / constraint (validated against upstream Coder)

Coder does not expose a supported, unauthenticated REST “admin backdoor” that would let the operator create users/tokens after the first user exists.

However, upstream Coder’s first-user check explicitly excludes system users (is_system=true). That means we can safely provision a coder-k8s-operator system user without disabling the first-user setup UI.

So the default bootstrap approach becomes:

  • Do not call CreateFirstUser automatically (preserve first-user UX).
  • As soon as the control plane is configured with a Postgres URL (CODER_PG_CONNECTION_URL) and the DB schema is ready, connect to Postgres and upsert:
    • a coder-k8s-operator system user with site role owner
    • org membership in all orgs (role organization-admin)
    • an API token with scope coder:all
  • Store that token in a Kubernetes Secret owned by the CoderControlPlane.

Evidence (repo + upstream Coder)

  • internal/controller/codercontrolplane_controller.go: currently reconciles only Deployment/Service and overwrites status each reconcile (we’ll need to preserve operator-access fields).
  • internal/controller/workspaceproxy_controller.go: shows the established pattern of generating/storing a token in a Secret owned by the CR.
  • CODER_PG_CONNECTION_URL is the canonical env var for coderd’s Postgres connection URL (see examples/cloudnativepg/codercontrolplane.yaml and the bundled Coder docs under .mux/skills/coder-docs/).
  • Upstream Coder (coder/coder):
    • first-user detection excludes is_system=true users (so system users do not block the setup wizard)

    • API tokens live in api_keys and are formatted {id}-{secret} with hashed_secret = sha256(secret); long-lived tokens use login_type='token', include scopes={'coder:all'}, and require a non-empty allow_list (default *:*)

    • org membership is stored in organization_members, and the upstream create-admin-user flow adds admins to all orgs

    • dev scripts (scripts/develop.sh) bootstrap the human first user via coder login --first-user-* (API path); system users (e.g. prebuilds in coderd/database/migrations/000308_system_user.up.sql) are inserted directly in Postgres with is_system=true, matching this plan’s approach for coder-k8s-operator.

Proposed design

1) API/CRD additions (coder.com/v1alpha1)

Add an operator-access block to the CoderControlPlane spec, enabled by default.

// api/v1alpha1/codercontrolplane_types.go

type CoderControlPlaneSpec struct {
    ...

    // OperatorAccess configures bootstrap API access to the coderd instance.
    // +kubebuilder:default={}
    OperatorAccess OperatorAccessSpec `json:"operatorAccess,omitempty"`
}

type OperatorAccessSpec struct {
    // Disabled turns off creation/management of the `coder-k8s-operator` user
    // and its API token.
    // +kubebuilder:default=false
    Disabled bool `json:"disabled,omitempty"`

    // GeneratedTokenSecretName controls where the controller stores the minted
    // `coder-k8s-operator` API token (key: DefaultTokenSecretKey).
    // If unset, defaults to "<controlplane-name>-operator-token" (with safe truncation).
    GeneratedTokenSecretName string `json:"generatedTokenSecretName,omitempty"`
}

Bootstrap input: the controller needs the coderd Postgres URL so it can provision the operator user/token directly in the DB.

  • It will resolve the URL from spec.extraEnv entry CODER_PG_CONNECTION_URL (either a literal value or valueFrom.secretKeyRef).
  • If the env var is missing/unresolvable, the controller will surface a not-ready condition and keep requeuing.

Add status fields so later components can discover the token location.

// api/v1alpha1/codercontrolplane_types.go

type CoderControlPlaneStatus struct {
    ...

    // OperatorTokenSecretRef points to the Secret key containing the
    // `coder-k8s-operator` API token.
    OperatorTokenSecretRef *SecretKeySelector `json:"operatorTokenSecretRef,omitempty"`

    // OperatorAccessReady reports whether the operator has successfully minted
    // a token.
    OperatorAccessReady bool `json:"operatorAccessReady,omitempty"`
}

Notes:

  • Keep naming consistent with WorkspaceProxy (GeneratedTokenSecretName, SecretKeySelector).
  • We can later replace OperatorAccessReady with full metav1.Condition usage, but start minimal.

2) Operator access provisioning via Postgres (no bootstrap token)

Instead of trying to bootstrap via the coderd REST API, we provision the operator user/token directly in Postgres.

Implement a small provisioner that:

  1. Resolves the Postgres URL from CODER_PG_CONNECTION_URL in spec.extraEnv (supports literal value or valueFrom.secretKeyRef).
  2. Connects to Postgres and upserts:
    • system user coder-k8s-operator (users.is_system=true, users.login_type='none', users.rbac_roles={'owner'})
    • org membership for all orgs (organization_members.roles={'organization-admin'})
    • a long-lived API token row in api_keys with:
      • token format {id}-{secret}
      • hashed_secret = sha256(secret)
      • login_type='token'
      • scopes={'coder:all'}
      • a non-empty allow_list (default *:*)
  3. Returns the token string so the controller can store it in a Kubernetes Secret.

Because the user is created with is_system=true, it does not affect the first-user setup wizard.

Implementation notes:

  • Prefer reusing upstream token helpers from github.com/coder/coder/v2/coderd/apikey (Generate/HashSecret, random string lengths/charset) to avoid drifting from Coder’s constraints.
  • Use a transaction and idempotent upserts keyed by username (user) and a stable token identifier (e.g. token_name='coder-k8s-operator').

Suggested interface shape (for wiring + tests):

// internal/coderbootstrap/operator_access_postgres.go

type OperatorAccessProvisioner interface {
    EnsureOperatorToken(ctx context.Context, req EnsureOperatorTokenRequest) (string, error)
}

type EnsureOperatorTokenRequest struct {
    PostgresURL string

    OperatorUsername string
    OperatorEmail    string

    // TokenName is written to `api_keys.token_name`.
    TokenName string

    // TokenLifetime controls expires_at / lifetime_seconds.
    TokenLifetime time.Duration
}

(Scopes are hardcoded to coder:all for now; we’ll scope down later.)

DB upsert sketch (for the provisioner)
  • Upsert user by username (users.username='coder-k8s-operator'):
    • is_system=true, login_type='none', status='active', rbac_roles={'owner'}, hashed_password='none'
  • Ensure org membership for all orgs:
    • INSERT ... SELECT id FROM organizations ... into organization_members with role organization-admin (upsert on (organization_id,user_id))
  • Create token row in api_keys:
    • generate {id}-{secret} (id len 10, secret len 22, [0-9A-Za-z]), store hashed_secret=sha256(secret)
    • set login_type='token', token_name='coder-k8s-operator', scopes={'coder:all'}, allow_list={'*:*'}, expires_at=now()+lifetime, lifetime_seconds=...

3) Controller changes (CoderControlPlaneReconciler)

Update internal/controller/codercontrolplane_controller.go to:

  • Add RBAC for secrets (needed to read CODER_PG_CONNECTION_URL when it’s a Secret ref, and to write the generated operator token Secret):

    • +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch;delete
  • Add an injectable provisioner (so controller tests don’t need a real Postgres instance):

type CoderControlPlaneReconciler struct {
    client.Client
    Scheme *runtime.Scheme

    OperatorAccessProvisioner coderbootstrap.OperatorAccessProvisioner
}
  • Reconcile order:
    1. Reconcile Deployment
    2. Reconcile Service
    3. Compute base status (ObservedGeneration, ReadyReplicas, URL, Phase)
    4. If operator access is enabled, run reconcileOperatorAccess(...) (DB-based; it can succeed even while the Deployment is Pending)
    5. Update status once with both base fields + operator-access fields (avoid overwriting operator fields the way reconcileStatus currently does).

reconcileOperatorAccess behavior (DB-based)

Pseudo-flow:

if cp.Spec.OperatorAccess.Disabled {
    // Stop managing operator access. (Optional: delete the managed Secret.)
    clear status.OperatorAccessReady / OperatorTokenSecretRef
    return
}

// Operator access is DB-based, so we can provision even while the Deployment is Pending.
// If the DB schema/migrations aren’t ready yet, treat that as a retryable error and requeue.

// If we already have a managed operator token secret with a token, stop.
operatorSecretName := resolveGeneratedTokenSecretName(cp) // default <cp-name>-operator-token
if existing, err := readSecretValue(...operatorSecretName...); err == nil && existing != "" {
    set status.OperatorTokenSecretRef and OperatorAccessReady=true
    return
}

// No need to wait for the human first-user flow: system users (`is_system=true`) don't affect it.

pgURL, err := resolvePostgresURLFromExtraEnv(ctx, cp) // reads CODER_PG_CONNECTION_URL
if err != nil {
    status.OperatorAccessReady = false
    return ctrl.Result{RequeueAfter: 30s}, nil
}

// Provision system user + API token directly in Postgres.
token, err := r.OperatorAccessProvisioner.EnsureOperatorToken(ctx, coderbootstrap.EnsureOperatorTokenRequest{PostgresURL: pgURL, ...})
if err != nil {
    return ctrl.Result{RequeueAfter: 30s}, nil
}

ensureTokenSecret(ctx, cp, operatorSecretName, DefaultTokenSecretKey, token)
status.OperatorTokenSecretRef = &SecretKeySelector{Name: operatorSecretName, Key: DefaultTokenSecretKey}
status.OperatorAccessReady = true

Notes:

  • Create the operator as a system user (is_system=true) with rbac_roles={'owner'} and login_type='none'.

    • Upstream first-user setup ignores system users, so this does not disrupt the initial setup UI.
  • Ensure org membership for all orgs (organization_members) with role organization-admin.

  • Token row in api_keys should be created with:

    • login_type='token'
    • scopes={'coder:all'}
    • allow_list={'*:*'} (schema constraint requires non-empty)
  • Use stable defaults (unless we later add config fields):

    • username: coder-k8s-operator
    • email: coder-k8s-operator@coder-k8s.invalid
    • token_name: coder-k8s-operator
    • token_lifetime: e.g. 365d

4) Wiring (controller app)

Update internal/app/controllerapp/controllerapp.go to construct and pass a real Postgres-backed provisioner into the control plane reconciler (mirrors how WorkspaceProxyReconciler gets a bootstrap client today):

reconciler := &controller.CoderControlPlaneReconciler{
    Client: client,
    Scheme: managerScheme,
    OperatorAccessProvisioner: coderbootstrap.NewPostgresOperatorAccessProvisioner(),
}

The provisioner should encapsulate:

  • resolving/using a Postgres driver (database/sql)
  • generating token {id}-{secret} using upstream helpers (coderd/apikey)
  • idempotent upserts into users, organization_members, and api_keys

5) Tests

Unit tests (envtest controller tests)

Update/add tests under internal/controller/ (use a fake provisioner so we don’t need a real Postgres instance):

  • TestReconcile_DefaultOperatorAccess_MissingPostgresURL

    • Create CoderControlPlane with no operatorAccess block (defaults apply) and without CODER_PG_CONNECTION_URL.
    • Assert: no operator token Secret created; status shows OperatorAccessReady=false; reconcile returns RequeueAfter.
  • TestReconcile_OperatorAccess_Disabled

    • Set spec.operatorAccess.disabled: true.
    • Assert: controller never calls the provisioner; does not create Secret.
  • TestReconcile_OperatorAccess_ResolvesPostgresURLAndCreatesTokenSecret

    • Fake provisioner EnsureOperatorToken returns token-value.
    • Provide CODER_PG_CONNECTION_URL in spec.extraEnv (cover both literal and secretKeyRef cases).
    • Assert: operator token Secret exists + ownerRef set; status references it; fake provisioner observed the expected Postgres URL.

Implementation detail: add a small fakeOperatorAccessProvisioner (similar to fakeBootstrapClient in workspace proxy tests) so tests don’t need live coderd/Postgres.

6) Generated artifacts + docs

Because the CRD API types change:

  • Run make codegen (deepcopy).
  • Run make manifests (CRDs + RBAC).
  • Run make docs-reference (updates docs/reference/api/codercontrolplane.md).

Consider adding a small snippet to an existing “deploy controller” doc or sample YAML documenting the output token Secret:

  • Ensure CODER_PG_CONNECTION_URL is provided via spec.extraEnv (value or secretKeyRef).
  • (Optional) Complete the first-user setup in the UI — it remains available even after the operator provisions its system user.
  • The controller will create/maintain Secret <controlplane-name>-operator-token (key token) containing the coder-k8s-operator API token.

Validation (local)

  • make test
  • make build
  • make lint
  • make verify-vendor (will include new vendored packages due to Postgres/token helpers)

Appendix: why we don’t auto-create the first user

Coder’s first-user experience relies on no users existing. Automatically creating coder-k8s-operator as the first user would flip the UI into “login” mode immediately and block the initial setup flow.

The chosen approach provisions coder-k8s-operator as a system user (is_system=true) and creates a coder:all token directly in Postgres. System users are excluded from the first-user check, so the setup wizard remains available.

Implementation strategy (agents)

Recommendation: have a single agent implement this end-to-end. This change spans API types, controller logic, DB/token provisioning logic, tests, and generated artifacts (codegen, manifests, docs-reference). A single implementer avoids merge conflicts and “regen drift”.

If you do want to split it across multiple agents, a reasonable breakdown is:

  • Agent A (API surface + generated output): update api/v1alpha1/codercontrolplane_types.go, then run/update make codegen, make manifests, make docs-reference.
  • Agent B (Postgres provisioner): implement internal/coderbootstrap/operator_access_postgres.go (DB upserts + token generation via upstream helpers), with focused unit tests for SQL/idempotency.
  • Agent C (controller integration): wire the provisioner into internal/controller/codercontrolplane_controller.go, resolve CODER_PG_CONNECTION_URL from spec.extraEnv, manage the output Secret, and preserve status fields.
  • Agent D (controller tests): add envtest coverage in internal/controller/codercontrolplane_controller_test.go using a fake provisioner.

Coordination note: only one agent should be responsible for the final regeneration/validation pass to keep the branch consistent.


Generated with mux • Model: openai:gpt-5.3-codex • Thinking: xhigh • Cost: $3.91

Implement default operator-access bootstrap for CoderControlPlane by provisioning a system user and API token directly in coderd Postgres, persisting the token in a managed Secret, and surfacing status fields for consumers.

Includes controller wiring, reconcile logic and tests, Postgres provisioner + unit tests, API/CRD/docs updates, and vendored postgres driver changes.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Please review this PR for the operator-access bootstrap implementation.

Update the OperatorTokenSecretRef field comment to a single line so generated API reference markdown no longer emits an inline <br />, fixing docs-quality markdownlint failures.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed docs-quality failure by removing inline HTML output in generated API reference docs.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 286437dedd

ℹ️ 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".

Comment thread internal/controller/codercontrolplane_controller.go Outdated
Address Codex feedback by removing the early return that treated Secret presence as readiness.

The reconciler now always invokes the provisioner with any existing Secret token, and the Postgres provisioner validates that token against `api_keys` (hash match + unexpired) before reusing it. If the token is missing, invalid, revoked, or expired, it rotates a replacement and updates the Secret.

Also updated controller and provisioner tests to cover passing existing tokens through reconciliation.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed your P1 by validating existing operator tokens in Postgres before reuse and rotating only when invalid/expired/revoked.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46c473f26d

ℹ️ 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".

Comment thread internal/controller/codercontrolplane_controller.go
Comment thread internal/controller/codercontrolplane_controller.go
Address additional Codex feedback by:
- revoking the managed operator token in Postgres and deleting the managed Secret when `spec.operatorAccess.disabled=true`, and
- treating malformed managed Secrets (missing/empty `token` key) as recoverable so reconciliation can re-provision a valid token.

Also expanded controller/provisioner tests for revoke and malformed-secret recovery behavior.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the latest feedback by revoking managed credentials when operator access is disabled and by treating malformed managed Secrets as recoverable for reprovisioning.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1fcb2d11b

ℹ️ 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".

Comment thread internal/controller/codercontrolplane_controller.go Outdated
Address follow-up Codex feedback by retaining a pending operator token status ref when disable-time cleanup fails.

This ensures revocation is retried on subsequent reconciles even if the managed Secret has already been deleted, preventing orphaned active coderd tokens.

Added controller coverage to verify requeue + retry behavior across repeated revoke failures.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed the retry concern by keeping a pending token ref in status when disable cleanup fails, so revocation is retried on later reconciles even after secret deletion.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 530598a1ff

ℹ️ 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".

Comment thread internal/controller/codercontrolplane_controller.go Outdated
Address Codex feedback by restricting disable-time Secret deletion to Secrets owned by the current `CoderControlPlane`.

The cleanup path now verifies controller ownership before deleting, while still revoking managed DB tokens and retaining retry behavior on transient revoke failures.

Added coverage for unmanaged secret preservation and updated managed cleanup tests to use owner references.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Addressed this by guarding disable-time secret deletion behind controller ownership checks so unmanaged secrets are preserved.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 110cdfa7c6

ℹ️ 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".

Comment thread internal/controller/codercontrolplane_controller.go Outdated
Address Codex feedback by making disable-time cleanup attempt DB token revocation whenever Postgres configuration is available, even when no managed secret/status reference is present.

This prevents stale `coder-k8s-operator` tokens from surviving cases where prior status refs were cleared or secret names changed.

Added controller coverage for disable-time revocation without managed secret/status state.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Updated disable-time cleanup to attempt revocation whenever Postgres config is available, even without managed secret/status ref.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6f98847b45

ℹ️ 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".

Comment thread internal/controller/codercontrolplane_controller.go
Address Codex feedback by deriving a unique coderd token name per `CoderControlPlane` (namespace/name-hashed suffix) instead of sharing a single global token name.

This prevents cross-resource interference when multiple control planes target the same Postgres database.

Also added controller coverage validating distinct token names across two control planes and updated revoke assertions accordingly.

---

_Generated with `mux` • Model: `openai:gpt-5.3-codex` • Thinking: `xhigh` • Cost: `$3.91`_

<!-- mux-attribution: model=openai:gpt-5.3-codex thinking=xhigh costs=3.91 -->
@ThomasK33
Copy link
Copy Markdown
Member Author

@codex review

Scoped operator token names per control plane to avoid cross-resource token interference on shared Postgres.

@chatgpt-codex-connector
Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

ℹ️ 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".

@ThomasK33 ThomasK33 added this pull request to the merge queue Feb 11, 2026
@ThomasK33
Copy link
Copy Markdown
Member Author

Merged via the queue into main with commit c360b6c Feb 11, 2026
11 checks passed
@ThomasK33 ThomasK33 deleted the control-plane-5q3h branch February 11, 2026 14:51
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.

1 participant