Skip to content

feat: discover contexts on login, select with ctx use#149

Open
scotwells wants to merge 10 commits intomainfrom
feat/context-discovery
Open

feat: discover contexts on login, select with ctx use#149
scotwells wants to merge 10 commits intomainfrom
feat/context-discovery

Conversation

@scotwells
Copy link
Copy Markdown
Contributor

@scotwells scotwells commented Apr 14, 2026

What changes for users

No more --project or --organization on every command.

Before

datumctl auth login
datumctl get organizations              # find your org ID
datumctl get projects --organization X  # find your project ID
datumctl get dnszones --project Y       # pass it on every command, forever

After

datumctl login                          # auth + pick a context in one step
datumctl get dnszones                   # just works

New commands

Command What it does
datumctl login Authenticate (PKCE, device, or machine-account) and select a default context
datumctl login --no-browser Device-code flow for headless / CI environments
datumctl login --credentials FILE Machine account login via credentials file
datumctl logout [email | --all] Remove credentials
datumctl whoami Show who you are and what context you're in
datumctl ctx List available contexts (tree view by org)
datumctl ctx --refresh Pull fresh org/project list from the API
datumctl ctx use [context] Switch context, interactive picker if no argument
datumctl auth list List authenticated users
datumctl auth switch [email] Switch users (restores their last context)

Resource commands (get, apply, create, etc.) automatically use the active context.

Removed commands

Command Replacement
datumctl auth login datumctl login (all auth modes consolidated here)
datumctl config * datumctl ctx

First login

Run datumctl login. A browser opens for authentication, then the CLI fetches your orgs and projects and shows a picker:

✓ Authenticated as Jane Doe (jane@acme.com)

Discovering organizations and projects...

You have access to 2 organization(s):

  acme-corp (3 project(s))
  personal (1 project(s))

┃ Select a context to work in
┃ > acme-corp  (Acme Corp)
┃     infra
┃     web-app
┃     billing-api
┃   personal
┃     sandbox

✓ Context set to acme-corp/infra

If you only have one project, the picker is skipped.

Day-to-day

$ datumctl ctx
  DISPLAY NAME         NAME                       TYPE      CURRENT
  Acme Corp            acme-corp                  org
    Infrastructure     acme-corp/infra            project   *
    Web App            acme-corp/web-app          project
  Personal             personal                   org
    Sandbox            personal/sandbox           project

$ datumctl ctx use acme-corp/web-app
✓ Switched to acme-corp/web-app

$ datumctl whoami
User:         Jane Doe (jane@acme.com)
Context:      acme-corp/web-app
Organization: Acme Corp (acme-corp)
Project:      Web App (web-app)

Switching users

$ datumctl auth list
  USER                 STATUS
  jane@acme.com        Active
  bob@acme.com

$ datumctl auth switch bob@acme.com
✓ Switched to Bob Smith (bob@acme.com)
  Context:  acme-corp/billing-api

Each login session remembers its last-used context, so switching users drops you right back where you left off.

CI and scripting

For non-interactive use, environment variables override the active context per-invocation:

DATUM_PROJECT=my-project datumctl get dnszones
DATUM_ORGANIZATION=my-org datumctl get projects

--project and --organization flags still work too. For machine-to-machine auth:

datumctl login --credentials ./my-key.json --hostname auth.datum.net

Existing users

datumctl auth login has been removed — use datumctl login instead. It supports all the same auth modes (PKCE, --no-browser for device flow, --credentials for machine accounts).

Pre-existing keyring credentials are bootstrapped into a v1beta1 session on first run, so previously authenticated users won't have to log in again. They will, however, want to run datumctl ctx use (or rely on DATUM_PROJECT/DATUM_ORGANIZATION) to pick a context.

Anyone with a kubeconfig generated by an earlier datumctl auth update-kubeconfig will need to re-run the command — the kubectl exec plugin args now reference session names rather than the legacy format.

🤖 Generated with Claude Code

@gianarb
Copy link
Copy Markdown
Collaborator

gianarb commented Apr 14, 2026

this looks good to me, do you want to get it out of draft so we can move it over and merge?

@scotwells scotwells marked this pull request as ready for review April 14, 2026 20:45
@scotwells scotwells requested a review from gianarb April 14, 2026 20:45
@scotwells scotwells force-pushed the feat/context-discovery branch from 459d18b to 7241d91 Compare April 15, 2026 21:19
@scotwells scotwells changed the base branch from feat/persistent-layer to main April 15, 2026 23:05
First slice of the multi-user / context-discovery rework, ported onto
current main. Adds the new packages and helpers; does not yet wire the
new commands into the CLI or refactor the existing auth command tree —
that lands in a follow-up so the integration with main's machine-account
and device-auth flows can be reviewed separately.

Adds:
- internal/datumconfig: v1beta1 schema with sessions and discovered
  contexts, alongside the existing utilities.
- internal/discovery: API-driven org/project discovery + on-disk cache.
- internal/picker: Bubble Tea / huh-based interactive picker.
- internal/cmd/{login,logout,whoami,ctx}: top-level command packages.
- internal/authutil/login.go: PKCE login helper that produces a v1beta1
  Session.
- internal/authutil/known_users.go: keyring-backed multi-user index.
- internal/miloapi/urls.go: control-plane URL helpers.

Refactors:
- internal/authutil/credentials.go: extracts shared
  GetTokenSourceForUser / GetUserIDFromTokenForUser /
  GetAPIHostnameForUser variants so multi-user flows can re-use the same
  refresh-and-persist logic that the active-user functions use.
…strap

Introduces the session layer that sits between v1beta1 config and the
existing keyring-backed credential store:

- GetUserKeyForCurrentSession: resolves the active session's user key,
  bootstrapping a Session from legacy keyring state on first run for
  users who logged in before the config file existed.
- GetUserKey: thin wrapper that returns only the user key.
- GetUserKeyForSession: looks up a session by name. Used by the kubectl
  exec plugin path so `auth update-kubeconfig` writes session names and
  `auth get-token --cluster=<name>` resolves them back.
- bootstrapSessionFromKeyring handles both interactive and machine-
  account credentials uniformly — both already populate the fields the
  Session consumes.
Rewires the datumctl Kubernetes client factory to resolve identity via
the active v1beta1 session instead of the old single-user keyring path:

- ToRESTConfig loads the active session and context, uses
  session.UserKey for token sourcing and session.Endpoint.Server for the
  base host, and falls back to GetUserKey + stored credentials when no
  session exists (the bootstrap path covers pre-v1beta1 users).
- resolveScope layers flags → DATUM_PROJECT / DATUM_ORGANIZATION →
  active context, so scripts and CI keep working without touching the
  config file.
- URL construction moved into the miloapi package so factory, user
  context, and future callers share one source of truth.
- user_context.go follows the same session-first pattern for code that
  builds a controller-runtime client outside the factory.
- Adds GetAPIHostnameForUser alongside the existing per-user token
  helpers so multi-user flows have everything they need.
Brings the auth command tree into the v1beta1 session model:

- auth list and auth switch read from the v1beta1 config, so they see
  every login regardless of how it was obtained (PKCE, device, or
  machine-account — Q1 uniform-session model).
- auth get-token grows a --session flag. Without it, behaviour is
  unchanged (uses the active session). With it, the kubectl exec plugin
  can pin each kubeconfig entry to a specific datumctl session so
  multi-user kubectl works correctly.
- auth update-kubeconfig resolves the session up front, uses its
  endpoint as the API hostname when available, and writes
  --session=<name> into the exec plugin args so the kubeconfig is tied
  to that specific session instead of drifting with the active one.

Keeps auth login, auth logout, and auth machine-account functionality
as-is on main. Deprecation of the old login/logout aliases (Q3) can
land in a follow-up once the top-level login achieves feature parity
(PKCE + device + machine-account).
Promotes the first-touch auth and context commands to the top level:

- 'datumctl login' runs PKCE, discovers orgs/projects, and prompts for a
  default context in one step.
- 'datumctl logout' removes credentials for one or all local users.
- 'datumctl whoami' shows the active user and context.
- 'datumctl ctx' lists and switches contexts.

Leaves 'datumctl auth login' / 'auth logout' in place unchanged — they
still carry the full feature matrix (PKCE, device flow, machine account)
and should stay functional until the top-level login reaches parity.

Improves the PKCE browser-open-failure message to point users at
'datumctl auth login --no-browser' when they're running in a headless
environment. A dedicated '--device' flag on 'datumctl login' is future
work once the device flow is lifted out of the auth login command.
- Replaces the auth-login-first flow with the new login + context
  discovery flow.
- Adds a short 'CI and scripting' section covering the DATUM_PROJECT
  and DATUM_ORGANIZATION overrides.
- Removes the tangent about creating an intro project with raw YAML —
  that belongs in the docs site, not the top-level README.
- Points at the standalone datum-mcp project for MCP / AI agent
  integration, matching main's recent removal of the in-tree MCP.
@scotwells scotwells force-pushed the feat/context-discovery branch from 798f400 to 149a280 Compare April 16, 2026 01:39
The session-based rewrite of auth list and auth switch stripped the
verbose help text, aliases, and examples that main's help-text overhaul
(PR #132) added. Restore them with updated wording that references
sessions and the new top-level commands.
@scotwells scotwells force-pushed the feat/context-discovery branch from 073453d to 692ba86 Compare April 16, 2026 01:47
Aligns with the rest of the CLI which uses rodaine/table via
internal/output. Replaces text/tabwriter for consistent formatting.
Moves PKCE, device-code, and machine-account login logic out of
cmd/auth/login.go into authutil, making it callable from the single
top-level 'datumctl login' command. Deletes cmd/auth/login.go and
cmd/auth/machine_account_login.go entirely — there is no longer an
'auth login' subcommand.

authutil/login.go now exports:
- RunInteractiveLogin: PKCE (default) or device-code (--no-browser)
- BuildSession, ResolveClientID, LoginResult (unchanged)

authutil/machine_account_login.go exports:
- RunMachineAccountLogin: reads credentials file, mints JWT, exchanges,
  stores in keyring, returns LoginResult

cmd/login/login.go accepts the full flag set:
  --hostname, --api-hostname, --client-id, --no-browser, --credentials,
  --debug
and routes to the appropriate authutil function before running context
discovery and the interactive picker.
Same consolidation as the login removal — 'datumctl logout' is the
single logout command. Removes cmd/auth/logout.go and its registration.
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.

2 participants