Skip to content

feat(cli): client create reads the cluster anchor — idempotent get-or-create + 409 (#84) - #102

Merged
saadqbal merged 3 commits into
developfrom
feat/cli-create-anchor
Jun 24, 2026
Merged

feat(cli): client create reads the cluster anchor — idempotent get-or-create + 409 (#84)#102
saadqbal merged 3 commits into
developfrom
feat/cli-create-anchor

Conversation

@LukasWodka

Copy link
Copy Markdown
Contributor

What

Revises tracebloc client create to be idempotent on the cluster anchor (RFC-0001 §7.2, against the backend#883 / Appendix C.3 contract). Re-running on the same cluster now adopts the existing client instead of minting a duplicate.

  • Reads the cluster anchor — new cluster.ClusterID returns the kube-system namespace UID (the stable per-cluster fingerprint). client create sends it as cluster_id, behind --kubeconfig / --context (kubectl conventions, mirroring cluster info).
  • Best-effort + never-silent: if the cluster isn't reachable, it provisions without an anchor (a plain mint) and prints a clear hint that re-runs won't be idempotent — it never blocks.
  • api.CreateClient reports adopt (HTTP 200) vs mint (201):
    • mint → prints the machine credential (interim — see scope below).
    • adopt → "this cluster is already registered as client X — adopted it", no credential reprinted (the existing one stands; the backend keeps the existing identity per §7.2).
    • 409 → a clear "registered to a different tracebloc account" (cluster_conflict, R6); 403 still routes to ask-an-admin.

Scope (locked: anchor + idempotency only)

Deferred to the installer reorder (#838), where the installer owns writing the credential into the cluster secret and knows the in-cluster TB_CLIENT_ID:

  • never-show (writing the credential into a 0600 cluster/Helm secret instead of printing it) — the mint-time print stays as the interim.
  • R7 existing-fleet backfill (matching a live TB_CLIENT_ID whose cluster_id is null and PATCHing it). The PATCH primitive lands with its caller in #838, not here — so this PR ships no unused/dead API surface.

Tests

go build / vet / test ./... green (Go 1.26):

  • cluster.clusterIDFrom via a fake clientset (reads kube-system UID; missing-namespace errors).
  • api.CreateClient mint/adopt (status→adopted) + 409→APIError.
  • cli create: anchor-mint (asserts cluster_id is sent), adopt-idempotency over two runs (both adopt the same id, no credential reprinted, password still sent in the body), 409→friendly message, and no-anchor→full mint + never-silent hint.

Reviewer notes

  • Built against backend#883 (the cluster_id anchor — get-or-create + 409 + adopt), currently in review. The CLI sends string(ns.UID) (already clean), so it's robust regardless of the backend's input canonicalization.
  • Adversarially reviewed (correctness/UX-safety, contract-match, test-genuineness) before pushing; findings folded in: dropped the not-yet-called PATCH primitive (→ #838), made the adopt-idempotency test load-bearing, and reordered the mint path so a config-save failure can't cost the user the only copy of the credential.

Closes part of #84 (the create revision); list/use/picker + selected-vs-connected remain.

🤖 Generated with Claude Code

…-create + 409 (#84)
RFC-0001 §7.2 / backend#883: `client create` now reads the cluster's kube-system UID
and sends it as cluster_id, so the backend does get-or-create keyed on it.
- Reads the anchor via a new cluster.ClusterID (kube-system namespace UID) behind
--kubeconfig/--context flags. Best-effort + never-silent: if the cluster isn't
reachable it provisions WITHOUT an anchor (a plain mint) and says so.
- api.CreateClient returns adopted (HTTP 200) vs minted (201): an idempotent re-run
on the same cluster adopts the existing client (no new credential printed) instead
of duplicating; a 409 → a clear "registered to a different account" (cluster_conflict).
- Adds api.BackfillClusterID (PATCH /edge-device/<id>/) for the adopt-backfill path
(the installer #838 orchestrates the full R7 flow).
Scope: anchor + idempotency only. never-show (writing the credential into the cluster
secret) and the R7 in-cluster TB_CLIENT_ID backfill orchestration land with the
installer reorder (#838); the mint-time credential print stays as the interim.
Tests: cluster.clusterIDFrom (fake clientset); api CreateClient mint/adopt/409 +
BackfillClusterID; cli create anchor-mint / adopt-idempotent / 409 / no-cluster-warns.
go build/vet/test ./... green (Go 1.26).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LukasWodka
LukasWodka requested a review from saadqbalJune 24, 2026 13:02

@saadqbalsaadqbal left a comment

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.

Two refinements from review — both small, neither blocking. Details inline.

Comment threadinternal/cluster/identity.go
Comment threadinternal/cli/client.go Outdated
saadqbaland others added 2 commits June 24, 2026 18:18
Two review fixes folded into the create-anchor work (#84):
- cluster.ClusterID: cap the best-effort kube-system read with an 8s
rest.Config timeout. A kubeconfig pointing at an unreachable API
server would otherwise hang the GET for the OS TCP timeout; now
`client create` degrades to a non-anchored mint promptly instead of
stalling before the review prompt.
- cli client create: on an idempotent adopt, print the result before
saving the active-client pointer and treat a save failure as a hint
(mirroring the mint path), so a config-save error can't bury the
"adopted it" message or the recovery hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
ContributorAuthor

Ready for another look @saadqbal:

Code-complete, 0 unresolved threads; just needs your re-review.

@LukasWodka
LukasWodka requested a review from saadqbalJune 24, 2026 13:32
Sign up for freeto 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.

2 participants

@LukasWodka@saadqbal