Skip to content

feat(code-repo): add Bitbucket Cloud provisioning provider#6

Open
pablovilas wants to merge 13 commits into
mainfrom
feat/bitbucket-code-repo
Open

feat(code-repo): add Bitbucket Cloud provisioning provider#6
pablovilas wants to merge 13 commits into
mainfrom
feat/bitbucket-code-repo

Conversation

@pablovilas

@pablovilas pablovilas commented Jul 19, 2026

Copy link
Copy Markdown

Summary

Adds Bitbucket Cloud as a code-repository provider to application-lifecycle-manager, the YAML + bash + np CLI provisioning path, at parity with the existing GitLab provider. Implements Plan 08 of the Bitbucket support program.

The service dispatches repo provisioning to scripts/code-repo/$PROVIDER/ (create_code_repository derives the dir from account.repository_provider). Only gitlab/ existed, so a Bitbucket-backed account failed at a missing path. This adds scripts/code-repo/bitbucket/ with the same six contract scripts GitLab has, plus one shared non-step helper.

The dispatcher (scripts/code-repo/create_code_repository) and the workflow (workflows/create_code_repository.yaml) are unchanged — they already select the provider directory by name.

Authentication — dedicated bot-user Atlassian API token

nullplatform authenticates to Bitbucket Cloud with a single dedicated bot user's Atlassian API token, over HTTP Basic (email:api_token). This is the only credential that can enable Bitbucket Pipelines: enabling Pipelines requires a 2SV-enabled user principal, and (validated live) an OAuth 2LO token is refused there with a permanent 403 while a workspace access token fails the same check.

  • REST: curl -u "$email:$token" on every call.
  • git-over-HTTPS seeding: username is the literal x-bitbucket-api-token-auth, password is the token.
  • Works on every Bitbucket plan (no Premium). App passwords are not used (removed 2026-07-28).

build_context is the only auth-aware step; every other script goes through bb_api / bb_git. No auth_method branch, no OAuth mint — that dual model in Plan 08's auth section was stale and has been superseded (the plan doc is annotated).

Credential sourcing — differs from GitLab (and why)

  • Provider-record keys are snake_case. $CODE_REPOSITORY is np provider list output whose attributes use the schema shape, exactly like the GitLab provider reads .attributes.setup.access_token. build_context reads .attributes.setup.{workspace,email,project_key,installation_url,api_token}. The camelCase bitbucket.projectKey etc. are NRN key names only.
  • The API token is a real secret, so the platform read cannot supply it. The bitbucket-configuration spec marks api_token secret: true, and nullplatform nullifies secret attribute values on authenticated provider reads (nullifySecrets), so np provider list returns api_token: null. (GitLab's flow only works because its access_token was never marked secret — a laxity not repeated here.) The token's primary, documented source is the BITBUCKET_API_TOKEN environment variable on the ALM deployment; the non-secret fields still come from the provider record. If the token is absent from both env and record, build_context fails immediately with a message explaining the secret-nullification and how to configure it.
  • np nrn read investigation: it exists and can address raw NRN keys (--nrn/--ids ${ns}.${key}), but it is explicitly deprecated ("might be removed; use Providers instead") and its secret-returning behaviour could not be verified in this environment (no live account with a configured Bitbucket secret). It is therefore not relied upon; ENV is the supported path.

What's included

scripts/code-repo/bitbucket/ (all mode 644 — the scripts are sourced, never executed):

Script Behaviour
_api Shared helpers: bb_api (HTTP Basic → BB_HTTP_CODE/BB_BODY), bb_encode_uuid, bb_scrub (redacts the API token), bb_git, bb_fail. No set -euo pipefail (would leak into the sourcing workflow shell).
build_context The only auth-aware step. Resolves the bot email + API token, enforces mandatory projectKey, normalises the repo slug (strip .git, lower-case), and fails loudly if any credential is missing.
validate_repository_does_not_exist GET /repositories/{ws}/{slug}; 404 = free, 401/403 explicitly ≠ free.
create_repository POST (expects 200, not 201) with mandatory project.key; seeds the template via git clone + squash + push (ADR-1 — Bitbucket has no import API); reconciles repository_url from links.html.href (Contract A).
add_collaborators PUT permissions-config/users|groups/...; per-principal failures don't abort the rest; non-members reported loudly with an actionable message (ADR-3 — no workspace-invite API).
create_secrets Pipeline variables with no upsert (POST→409→list→PUT by percent-encoded uuid); always secured: true; endpoint is pipelines_config (underscore).
run_first_build Enables Pipelines (GET 404 = never enabled, then PUT), then POST /pipelines/; a 403 on enable is surfaced as the bot-user 2SV requirement (not a phantom scopes bug); a missing bitbucket-pipelines.yml (400) warns rather than fails.

Cross-cutting:

  • scripts/code-repo/generate_secrets now emits both NP_API_KEY (unchanged) and NULLPLATFORM_API_KEY at the provider-agnostic seam, so the shared templates work under either provisioning path. (The GitLab scripts' NP_API_KEY-only bug is left untouched — separate ticket.)
  • Contract A is enforced by the producer: canonical URL comes only from links.html.href (never links.clone[], which carries .git + embedded credentials).
  • README.md documents Bitbucket Cloud (config table + auth/collaborator/import limitations); CHANGELOG.md gets a 0.3.0 entry.

Testing

New offline harness (no bats — pure bash + jq + fixture stubs) and a .github/workflows/ci.yaml running shellcheck + the suite.

  • 17/17 offline tests pass — covering the branches where a silently-wrong decision is a production incident: API-token read + missing-credential failure, mandatory project.key, slug normalisation, 404-vs-401 on validate, links.html.href vs links.clone[], Contract A write-back, the 409-no-upsert recovery, pipelines_config 404, enable-before-trigger ordering, and ADR-3's non-member error.
  • shellcheck clean (shellcheck 0.11.0, exact CI invocation) and bash -n clean on every script.
  • tests/smoke_bitbucket_live (operator-run, not CI) added for live scratch-workspace verification — not executed in this PR (needs a real bot-user API token; the bot must have Bitbucket 2SV enabled or the Pipelines-enable step 403s).

Notes for reviewers / program

  • Fixed a latent bug in the plan's own test harness: assert_called_git could never match a git subcommand that immediately followed the recorded leading tab (clone, init, add) — see commit fix(tests): match git subcommands after the leading tab.
  • Runtime prerequisites (not in this PR): account.repo_provider must accept bitbucket (Plan 02 enum + ALTER TYPE) before the dispatcher can select this directory; the bitbucket-configuration provider spec must expose setup.{email,apiToken,workspace,projectKey,installationUrl} (Plan 01).
  • The bot user must have Bitbucket 2SV enabled (Bitbucket 2SV, not Atlassian-account 2FA); otherwise run_first_build fails on the Pipelines enable with a clear message. Atlassian API tokens expire ≤365 days → rotation required.

🤖 Generated with Claude Code

…t auth

Replace the workspace-access-token / OAuth-2LO dual auth model (which never
matched the seeded NRN keys and could not enable Pipelines) with a single
dedicated bot-user Atlassian API token:

- build_context resolves bitbucket.email + bitbucket.apiToken and exports
  BITBUCKET_EMAIL + BITBUCKET_API_TOKEN; no auth_method branch, no OAuth mint.
- _api sends HTTP Basic (email:api_token) on every REST call.
- create_repository seeds git over HTTPS with the username
  x-bitbucket-api-token-auth and the token as password.
- run_first_build surfaces a 403 on pipelines enable as the bot-user 2SV
  requirement, not a phantom scopes bug.
- Tests, smoke runner, README and CHANGELOG updated to the API-token model.
…ken from env

Two production-path bugs in build_context:

- The provider record from `np provider list` uses the schema (snake_case)
  attribute shape, like the GitLab provider reads .attributes.setup.access_token.
  The camelCase paths (projectKey/installationUrl/apiToken) resolved to null;
  read project_key/installation_url/api_token instead. The camelCase forms are
  the NRN key names only, kept in the error-message hints.

- The api token is marked secret, and providers-api nullifies secret attribute
  values on authenticated reads, so `np provider list` returns api_token: null --
  the platform read cannot supply the credential (GitLab only works because its
  access_token was never marked secret). Make the BITBUCKET_API_TOKEN env var the
  primary, documented source; fail loudly explaining the secret-nullification and
  how to configure it when neither env nor record yields a token.

Offline fixtures now mirror real `np provider list` output (snake_case,
api_token null) plus an env-provided-token happy path. README documents the
credential-sourcing difference vs GitLab and its rationale.
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