feat: add env-var-driven headless authentication (BKT_HOST + BKT_TOKEN)#138
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
avivsinai
left a comment
There was a problem hiding this comment.
Thanks again Eran — this is excellent work. The env-var-driven headless auth is exactly the right design, and with OAuth Phase 1 queued up too, you're really pushing bkt forward. We're closing our tactical #141 in favor of this.
Must fix
1. Copy saved host before env overlay
The env overlay in ResolveContext and ResolveHost mutates cfg.Hosts[envKey] in place. If any future code path calls cfg.Save() after resolution, env-sourced Username/AuthMethod values would be silently persisted to the user's config file. Fix: shallow-copy before mutating:
clone := *saved
saved = &cloneThree sites: ResolveContext, ResolveHost case 1, ResolveHost default.
(Note: MarshalYAML already strips Token, which is great defense-in-depth — but Username and AuthMethod aren't stripped, so the copy is needed.)
2. Fix BKT_AUTH_METHOD docs
README and SKILL.md say default is basic, but the actual code behavior is: DC defaults to bearer when BKT_USERNAME is absent, and Cloud always forces basic. No scenario where the default is unconditionally basic. Update docs to reflect actual behavior.
Suggestions (non-blocking)
- DRY up env-overlay logic: The check-saved-host / loadHostToken / overlay-username-authmethod pattern is duplicated 4x across
ResolveContextand the threeResolveHostswitch branches. Consider extracting aresolveEnvHost()helper (~40 lines saved, consistency by construction). - Hint when BKT_HOST is set without BKT_TOKEN: Currently silently ignored with a generic "no hosts configured" error. A "BKT_HOST is set but BKT_TOKEN is not — did you forget to set BKT_TOKEN?" hint would help CI debugging.
- Table-driven tests: The 14
TestHostFromEnv*functions could be one table-drivenTestHostFromEnvwith subtests, per project convention.
What's done well
- Cloud detection via exact hostname matching is correct and the
bitbucket.org.corp.comtest case proves it's not just theoretical errTokenSetButNoHostis actionable and tells users exactly what to do- The env fallback is truly additive — every existing path is preserved when env vars are unset
- Test coverage is thorough: 29 tests covering all
ResolveHostswitch arms,ResolveContextenv path, and edge cases
…T_TOKEN) Fixes avivsinai#134. When BKT_TOKEN and BKT_HOST are set, all commands work in containers and CI pipelines without a prior `bkt auth login` or `bkt context create` step. - Add hostFromEnv / contextFromEnv helpers that synthesise ephemeral host+context from env vars; never written to disk - Patch ResolveContext and ResolveHost (case 0, case 1, default, and --host override paths) to fall back to env vars when no config match - Prefer saved host entry when BKT_HOST key matches config, preserving persisted username / auth_method / auth scheme - Canonicalise bitbucket.org and api.bitbucket.org to the API origin using exact hostname matching (avoids bitbucket.org.corp.com false positives); bare api.bitbucket.org also normalised to /2.0 base - applyRemoteDefaults runs for env-backed contexts so git-remote inference fills project/workspace/repo in cloned CI workspaces - Export BKT_HOST, BKT_USERNAME, BKT_AUTH_METHOD, BKT_PROJECT, BKT_WORKSPACE, BKT_REPO constants from internal/secret - 24 new unit tests; all existing 853 tests continue to pass - Update README.md and skills/bkt/SKILL.md with env-var reference table and headless usage examples for DC and Cloud
P1: hostFromEnv now defaults DC PAT-only flows to bearer auth when neither BKT_USERNAME nor BKT_AUTH_METHOD is set, so headless DC usage works out-of-the-box without BKT_AUTH_METHOD=bearer. Explicitly requesting basic auth without a username now returns a clear error. Cloud now errors immediately when BKT_USERNAME is absent since basic auth is always required. P2: When BKT_HOST matches a saved config host, BKT_USERNAME and BKT_AUTH_METHOD are now overlaid onto the saved entry so headless runs are not silently bound to stale stored credentials.
8772619 to
05a846a
Compare
avivsinai
left a comment
There was a problem hiding this comment.
Thanks for the new commits Eran — the docs reorg into rules/headless.md is a nice improvement and the auth-method-rules table there is correct.
Both original must-fix items are still open though:
1. Shallow-copy saved host before env overlay (still blocking)
The overlay still mutates cfg.Hosts[envKey] in place (saved.Username = u / saved.AuthMethod = m) at three sites. If anything downstream calls cfg.Save(), env-only overrides get silently persisted to disk.
Rather than sprinkling clone := *saved; saved = &clone at each site (manual discipline that breaks the moment someone adds a 4th caller), extract a helper that takes the saved host by value:
func resolveEnvHost(base config.Host) *config.Host {
// overlay env fields onto the copy — caller's map entry is untouched
}The type system enforces immutability — you can't mutate the original because you never have a pointer to it in the overlay function. This also collapses the duplicated overlay logic in ResolveContext and both ResolveHost branches into one call site.
2. README BKT_AUTH_METHOD default (still blocking)
README.md line 68 still says basic (default). The new rules/headless.md table is correct (DC defaults to bearer when no username), but the README needs to match.
…EADME auth method default - Extract mergeEnvHost(executable, key string, base config.Host) helper that takes the saved host entry by value, so env-sourced Username/ AuthMethod overlays never mutate cfg.Hosts in place. Replaces the three inline overlay blocks in ResolveContext and both ResolveHost switch branches with a single call site. - README: BKT_AUTH_METHOD description now reflects actual behaviour — DC defaults to bearer when BKT_USERNAME is absent; Cloud always basic.
… BKT_TOKEN - Collapse 13 individual TestHostFromEnv* functions into one table-driven TestHostFromEnv with subtests, per project convention. - Add errHostSetButNoToken sentinel; return it from ResolveContext and ResolveHost (case 0 and default) when BKT_HOST is set but BKT_TOKEN is absent, giving a "did you forget to set BKT_TOKEN?" hint instead of the generic no-hosts/no-context error. - Update two existing tests whose BKT_HOST fixture now triggers the hint; add TestResolveHostHostSetButNoToken and TestResolveContextHostSetButNoToken.
|
Must fix #1 — shallow-copy / mergeEnvHost Done. Extracted Must fix #2 — README Fixed in the same commit. Non-blocking suggestions All three addressed:
|
|
I'm not sure if I'm doing this correctly, but running headless like this: And I have BKT_TOKEN set in my environment. It works, but it keeps saying The documentation says I need it, but why would I need that? If I look at https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/#api-repositories-workspace-repo-slug-commit-commit-pullrequests-get I'm not sure why I would need to use a username, I don't see it in the documentation. |
|
Or maybe to explain, it doesn't need it for ALL requests. I was tring to use it to view the diff and create comments, which can use Repository tokens, which I guess are different? |
Summary
Adds support for fully config-free CLI usage via environment variables.
Closes #134. When `BKT_TOKEN` and `BKT_HOST` are set, every command works in
containers and CI pipelines without a prior `bkt auth login` or
`bkt context create` step.
Changes
`internal/secret/store.go`
`EnvProject`, `EnvWorkspace`, `EnvRepo`) alongside the existing `EnvToken`
`pkg/cmdutil/context.go`
canonicalises `bitbucket.org` / `api.bitbucket.org` → `https://api.bitbucket.org/2.0\`
using exact hostname matching (prevents false positives like `bitbucket.org.corp.com`);
defaults DC PAT-only flows to bearer auth when `BKT_USERNAME` is absent;
requires `BKT_USERNAME` for Cloud (always basic auth); errors early if
`BKT_AUTH_METHOD=basic` is set without `BKT_USERNAME`
`BKT_WORKSPACE` / `BKT_REPO`; `applyRemoteDefaults` runs so git-remote inference
works from cloned CI repos even without those vars
entry when `BKT_HOST` matches an existing config key (preserves persisted
`username` / `auth_method`); overlays `BKT_USERNAME` / `BKT_AUTH_METHOD` if set
so headless runs are not silently bound to stale stored credentials
`BKT_HOST` may target a different server), and `default` (multiple hosts,
`BKT_HOST` disambiguates); same env auth-field overlay applied to all saved-host paths;
`--host X` flag also synthesises an ephemeral host when X is not in config
`BKT_HOST`; errors from `hostFromEnv` are propagated rather than swallowed
`pkg/cmdutil/context_test.go`
`ResolveContext` env fallback; `t.Setenv` throughout so no env leakage between tests
`README.md` / `skills/bkt/SKILL.md`
Testing
all canonicalise to `https://api.bitbucket.org/2.0\`
New environment variables — no CI configuration needed; these are opt-in,
read at runtime. Existing users with a config file are unaffected.
Risks
`BKT_TOKEN` / `BKT_HOST` are unset. No new dependencies, no CI/CD changes,
no breaking API surface.