Skip to content

DRAFT: cli: entire ci buildkite org connect/list/cluster (M1)#1758

Closed
dvydra wants to merge 1 commit into
dv/ci-buildkite-watchfrom
dv/ci-buildkite-org
Closed

DRAFT: cli: entire ci buildkite org connect/list/cluster (M1)#1758
dvydra wants to merge 1 commit into
dv/ci-buildkite-watchfrom
dv/ci-buildkite-org

Conversation

@dvydra

@dvydra dvydra commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Why

M1 of the customer-facing CI-webhooks surface (COR-976): an org/project admin
needs to connect their own Buildkite organization to an Entire org — supply a
Buildkite API token (stored encrypted server-side) and register the hosted
clusters their pipelines run on. This adds the CLI verbs for that flow.

The backend endpoints this drives live in the DRAFT backend
entiredb#2744 (branch dv/ci-org-credentials-m1) and are not yet in the
generated coreapi spec
. This PR is therefore also a DRAFT — do not
merge until entiredb#2744 lands and passes security review.

This PR stacks on #1756#1755#1754#1753 (base branch
dv/ci-buildkite-watch). Review/merge those first.

What

Mounts an org subgroup under entire ci buildkite (internal build tag only,
absent from released binaries):

  • entire ci buildkite org connect <entire-org> --bk-org <slug> [--bk-token <t>]
    POST /api/v1/orgs/{orgId}/ci/buildkite/credential with body
    {bk_organization, bk_api_token}. Prints Connected (201) / Rotated
    (200) plus token_len; the token is never echoed. --json supported.
  • entire ci buildkite org list <entire-org> [--json]
    GET .../credentials, then GET .../clusters?bk_organization=<slug> per
    connected credential. Renders a credentials table + a clusters table (no
    token material — only token_len).
  • entire ci buildkite org cluster add <entire-org> --bk-org <slug> --bk-cluster-id <id> [--auth-plugin-ref <s>]
    POST .../clusters. Prints Registered (201) / Updated (200).
  • entire ci buildkite org disconnect <entire-org> --bk-org <slug>
    DELETE .../credential/{bkOrg}.

Org resolution. <entire-org> accepts an org ULID (passed through after a
shape check, no network call) or an org name (resolved via the control plane's
case-insensitive ?name= filter). New ResolveOrg helper next to
ResolveNativeRepo, mirroring its ULID-or-name contract; distinct
no org named … error for an unknown name.

Token handling. The Buildkite API token is sourced, in order, from
--bk-token (with a one-line warning that it lands in shell history), the
BUILDKITE_API_TOKEN env var, then an interactive no-echo prompt
(term.ReadPassword) when a TTY is available. It is sent to the server over
the existing TLS client only — never logged, never echoed, and absent from all
command output (only token_len surfaces).

Calling the draft backend. These endpoints aren't in the generated client
yet, so the requests are hand-rolled by extending the C3 escape hatch:
PostJSON / DeleteJSON added alongside GetJSON in
internal/coreapi/client.go, reusing the same serverURL, bearer
SecuritySource, and cross-jurisdiction transport (no second auth path).
PostJSON returns the status code so the verbs can distinguish 201-created
from 200-updated. Both carry a removal note: swap these calls for the
generated Invoker methods once entiredb#2744 merges and the spec
regenerates, then delete the local DTOs + escape-hatch helpers.

Verification

  • go build ./... and go build -tags internal ./... both pass.
  • New tests (fake core via httptest):
    • resolve_test.go (untagged): ResolveOrg ULID passthrough (zero HTTP
      calls), name lookup (server-side ?name=), unknown-name error, empty ref.
    • buildkite_org_internal_test.go (internal-tagged): connect body assembly +
      Connected/Rotated verbs + token never on stdout/stderr, --bk-token
      shell-history warning, no-token-source failure, --bk-org required; list
      table + --json (no token material) + empty case (clusters not queried);
      cluster add body + Registered/Updated + required flags; disconnect path.
    • go test ./cmd/entire/cli/ci/... && go test -tags internal ./cmd/entire/cli/ci/...
      and go test ./internal/coreapi/... all green.
  • mise run fmt clean; golangci-lint run --build-tags internal ./cmd/entire/cli/ci/...
    and full mise run lint report 0 issues.
  • Smoke: go build -tags internal ./cmd/entire then
    entire ci buildkite org {connect,list,cluster add,disconnect} --help all
    render.

Follow-ups

  • Regenerate the coreapi spec once entiredb#2744 merges, switch the four
    verbs to the generated Invoker methods, and delete PostJSON/DeleteJSON
    and the local DTOs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CHPhjYDtZx71THMtVwc83E


Note

High Risk
Handles Buildkite API tokens (collection, transport, and shell-history exposure) and depends on draft backend APIs via hand-rolled HTTP until spec regeneration; mistakes could leak credentials or misconfigure org CI auth.

Overview
Adds an internal-only entire ci buildkite org command group so org admins can connect Buildkite API credentials, list credentials and registered hosted clusters, register/update clusters, and disconnect credentials against draft org-scoped /api/v1/orgs/{orgId}/ci/buildkite/… endpoints.

Org targeting introduces ResolveOrg (ULID passthrough or name via ?name=) and renames the shared ULID shape helper to looksLikeULID for repo and org refs.

API client extends the coreapi escape hatch with PostJSON and DeleteJSON (alongside existing GetJSON) until the OpenAPI spec includes these routes; verbs use local DTOs and distinguish 201 vs 200 in user output.

Token handling for org connect prefers env or a no-echo prompt over --bk-token, warns when the flag is used, and never prints token material (only token_len). Tests cover HTTP wiring, validation, and secret hygiene.

Reviewed by Cursor Bugbot for commit 4830786. Configure here.

Mount an `org` subgroup under `entire ci buildkite` (internal-gated) for
org/project admins to connect their own Buildkite organization and register
its hosted clusters:

- `org connect <entire-org> --bk-org <slug>` — POST the BK API token
  (from --bk-token, BUILDKITE_API_TOKEN, or a no-echo prompt). The token is
  sent over TLS only, never echoed or logged; only token_len is reported.
- `org list <entire-org> [--json]` — GET the connected credentials plus each
  BK org's registered clusters. No token material rendered.
- `org cluster add <entire-org> --bk-org --bk-cluster-id [--auth-plugin-ref]`
  — POST a hosted-cluster registration (idempotent upsert).
- `org disconnect <entire-org> --bk-org <slug>` — DELETE the credential.

Org resolution mirrors ResolveNativeRepo: a raw org ULID passes through, a
name resolves via the control plane's ?name= filter (new ResolveOrg helper).

These endpoints live in the DRAFT backend entiredb#2744 and are not yet in
the generated coreapi spec, so the requests are hand-rolled via the escape
hatch: PostJSON/DeleteJSON added alongside GetJSON (same serverURL, bearer,
and transport, no second auth path). Swap to the generated client once
entiredb#2744 merges and the spec regenerates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CHPhjYDtZx71THMtVwc83E
Copilot AI review requested due to automatic review settings July 15, 2026 05:54

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 4830786. Configure here.

func resolveBuildkiteToken(cmd *cobra.Command, flagToken string) (string, error) {
if strings.TrimSpace(flagToken) != "" {
fmt.Fprintln(cmd.ErrOrStderr(), "warning: --bk-token is visible in your shell history; prefer BUILDKITE_API_TOKEN or the interactive prompt")
return flagToken, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flag token keeps surrounding whitespace

Medium Severity

When --bk-token is non-blank after trim, resolveBuildkiteToken returns the raw flag value instead of the trimmed string. Leading or trailing spaces are sent in bk_api_token, which can cause Buildkite to reject a otherwise valid token.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4830786. Configure here.

cmd.SilenceUsage = true
return err
}
return runCore(cmd, func(ctx context.Context, c *coreapi.Client) error {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Buildkite token before Entire login

Low Severity

org connect resolves the Buildkite API token (including an interactive no-echo prompt) before runCore establishes an Entire control-plane session. If the user is not logged in, they may enter a secret first and only then see an authentication failure from the CLI.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4830786. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the internal-only entire ci buildkite org command surface for connecting/listing/disconnecting Buildkite org credentials and registering hosted clusters, backed by draft org-scoped core endpoints (pre–coreapi spec regen) via new coreapi.Client JSON escape hatches.

Changes:

  • Extend internal/coreapi.Client with PostJSON and DeleteJSON escape hatches to call draft endpoints while reusing existing auth/transport behavior.
  • Add ResolveOrg (ULID passthrough or case-insensitive by-name lookup) alongside the existing CI repo resolver.
  • Introduce internal-gated entire ci buildkite org {connect,list,cluster add,disconnect} commands plus httptest coverage for request wiring and secret-hygiene expectations.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/coreapi/client.go Adds PostJSON/DeleteJSON escape hatches (temporary until OpenAPI regen) reusing the core client’s auth + transport.
cmd/entire/cli/ci/resolve.go Adds ResolveOrg and generalizes ULID shape checking for org + repo refs.
cmd/entire/cli/ci/resolve_test.go Adds unit tests for ResolveOrg ULID passthrough, name lookup, empty ref, and unknown name.
cmd/entire/cli/ci/buildkite_org_internal.go Implements internal-only ci buildkite org verbs, token sourcing/prompting, and human/JSON renderers.
cmd/entire/cli/ci/buildkite_org_internal_test.go Adds internal-tagged command tests using httptest, including assertions that token material never appears in output.
cmd/entire/cli/ci/buildkite_internal.go Wires the new buildkite org subgroup into the existing internal buildkite command tree.

Comment on lines +280 to +291
func promptBuildkiteToken(cmd *cobra.Command) (string, error) {
fmt.Fprint(cmd.ErrOrStderr(), "Buildkite API token: ")
raw, err := term.ReadPassword(int(os.Stdin.Fd())) //nolint:gosec // G115: uintptr->int is safe for a stdin fd
fmt.Fprintln(cmd.ErrOrStderr()) // terminate the (hidden) input line
if err != nil {
return "", fmt.Errorf("read Buildkite API token: %w", err)
}
token := strings.TrimSpace(string(raw))
if token == "" {
return "", errors.New("no Buildkite API token entered")
}
return token, nil
Comment on lines +264 to +267
if strings.TrimSpace(flagToken) != "" {
fmt.Fprintln(cmd.ErrOrStderr(), "warning: --bk-token is visible in your shell history; prefer BUILDKITE_API_TOKEN or the interactive prompt")
return flagToken, nil
}
@dvydra

dvydra commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Superseded. entire-ci now ships as a standalone plugin — https://github.com/entireio/entire-ci — discovered by the CLI as entire ci … on PATH, rather than a build-tagged command group compiled into the entire binary. Closing in favor of that approach (see ADR 20260717-entire-ci-pluggable-client).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants