Skip to content

feat: config, contexts, name resolution, proxy and CA bundle - #14

Open
RogerCecilpot wants to merge 2 commits into
feat/250-test-harnessfrom
feat/244-config-contexts
Open

RogerCecilpot wants to merge 2 commits into
feat/250-test-harnessfrom
feat/244-config-contexts

Conversation

@RogerCecilpot

Copy link
Copy Markdown

Closes devlyft/devlyft#244. Next in the stacked chain after #11 (base feat/250-test-harness); retarget as the stack merges.

What

  • Config file (internal/config): config.yaml in the config directory (~/.config/devlyft, %AppData%\devlyft), 0600 file in a 0700 directory, atomic writes. Schema: api_url, ca_bundle, current_context, contexts{name: {account, org, project, env, location}}. Unknown keys are preserved (top level and per context) so an older CLI never drops what a newer one wrote.
  • Scope (config.ResolveScope): flag > DEVLYFT_* env > context (or the file for api_url/ca_bundle) > default, for every value; --context/DEVLYFT_CONTEXT override the current context for one invocation. Each value records its source.
  • Commands: config get|set|list (validated; an empty value unsets), context use|list|show|delete.
    • context use NAME [--org --project --environment --location --account] switches to NAME, creating it if missing, and saves the scope flags given on that command line; --project "" clears one. This keeps to the spec's verbs (no separate create/update).
    • context show with no name prints every resolved value and its source (flag/env/context/config/default), for debugging precedence; context show NAME prints the saved context.
    • context delete confirms or needs --yes; deleting the current context unsets current_context.
    • config/context commands skip scope resolution, so a dangling current_context or a bad DEVLYFT_API_URL can still be repaired; version/completion don't read the config at all.
  • Name resolution (internal/resolve): org by UUID, slug (unique, wins) or name; project/env by UUID or name (needs the parent); location by ID ignoring case. Exact name first, then case-insensitive. Ambiguity is a usage error (exit 2) listing the ID and name of every match, with details.matches in JSON and a hint to use IDs in CI. No match is not_found (exit 4). Nothing is cached. No command uses it yet; #247 is the first consumer.
  • Network: Factory.HTTPClient() is the single client for everything that dials out (the API facade now, the #254 log stream next). Proxy variables are read per invocation via x/net/http/httpproxy (not http.ProxyFromEnvironment, which caches them for the process lifetime). DEVLYFT_CA_BUNDLE / ca_bundle certificates are added to the system roots, with a one-line stderr notice whenever a bundle is active, printed even with --quiet, since trusting extra CAs changes who can read the traffic.
  • Accounts: credentials are looked up and stored under the active context's account, or default when it has none, so credentials stored by #245 keep working with no migration.

Decisions worth a look

  • --environment, not --env, for the global flag: spec §6.2 uses deploy --env K=V for environment variables, and a persistent --env would collide with it. The environment variable stays DEVLYFT_ENV and the context key env.
  • No DEVLYFT_ACCOUNT variable: the spec puts the account on the context only.
  • Ambiguous names exit 2 (usage) rather than 1: it is a user-input problem the CLI detects, and the fix is to pass an ID.
  • An org UUID is used as-is without a lookup (no extra round trip); the API returns 404 if it is wrong.

Tests

  • Precedence table for every key (flag/env/context/default); context selection (--context, DEVLYFT_CONTEXT, current_context, unknown, dangling); CA bundle source.
  • Config: missing/empty/invalid file, round trip keeping unknown keys, file and directory modes (skipped on Windows), no temp files left behind, Set validation.
  • Resolution with gomock: slug vs name, exact vs case-insensitive, ambiguity details, not found, parent required, API errors passed through, locations.
  • Network: a TLS httptest server fails without the bundle and works with it; a forward proxy receives the request; NO_PROXY bypasses it.
  • Golden transcripts of a full context/config session and of scope errors; the CA notice via DEVLYFT_CA_BUNDLE and ca_bundle.
  • clitest now gives each run a temporary config directory, so tests never read the developer's own config.yaml.

Coverage: 87.6% total (internal/config 87%, internal/resolve 93.7%). make lint, make test, make vuln, make cover-check, Windows and macOS cross-builds and actionlint pass locally.

🤖 Generated with Claude Code

RogerCecilpot and others added 2 commits October 25, 2026 14:30
- config.yaml in the config directory (0600 file, 0700 dir, atomic
  writes); unknown keys are kept when the CLI rewrites it.
- Scope resolution with one precedence for every value: flag, then
  DEVLYFT_* variable, then the context (or the file for api_url and
  ca_bundle), then the default. --context / DEVLYFT_CONTEXT pick a context
  for one invocation. New global flags --context, --org, --project,
  --environment and --location (--environment, not --env, because
  `deploy --env K=V` sets environment variables).
- `config get|set|list` for api_url, ca_bundle and current_context, with
  validation; `context use|list|show|delete`. `context use NAME` creates
  the context if needed and saves the scope flags given with it;
  `context show` without a name prints each resolved value and its source.
  config and context commands still run when the scope cannot resolve
  (e.g. a dangling current_context), so they can repair it.
- internal/resolve: org by ID, slug or name; project and environment by
  ID or name; location by ID ignoring case. Ambiguous names are a usage
  error listing the matches; unknown names exit 4.
- Factory.HTTPClient honours HTTPS_PROXY/HTTP_PROXY/NO_PROXY (read per
  invocation, not cached) and DEVLYFT_CA_BUNDLE / ca_bundle on top of the
  system roots, with a stderr notice whenever a bundle is in use. The API
  client uses it; the log stream can too.
- Credentials are stored under the context's account, or "default".
- Tests never read the developer's config: clitest uses a temp dir.

Refs devlyft/devlyft#244

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
testscript leaves AppData unset on Windows, so os.UserConfigDir failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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