feat(bootstrap): add bootstrap command for first-time Greenhouse access - #83
onuryilmaz wants to merge 4 commits into
Conversation
Adds `cloudctl bootstrap` which merges a Greenhouse kubeconfig into the user's local kubeconfig so they can reach the Greenhouse API server with kubectl and run `cloudctl sync`. Two input modes are supported: - `--data=<base64-kubeconfig>`: decodes a standard kubeconfig downloaded from the Greenhouse Web UI; supports any auth type (OIDC auth-provider, exec-plugin, token, cert) and preserves all fields verbatim. - Individual OIDC flags: `--greenhouse-server`, `--greenhouse-org`, `--greenhouse-idp-issuer-url`, `--greenhouse-client-id`, `--greenhouse-client-secret`, `--greenhouse-extra-scopes`, `--greenhouse-ca-data`, `--greenhouse-namespace`. Produces the same auth-provider/oidc kubeconfig shape as the Greenhouse UI download. Both modes: - Ask interactively for context name and whether to set it as current context (skipped when `--context-name` / `--set-current-context` are given or when not on a TTY). - Rename the context triple (cluster + user + context) to the chosen name. - Never overwrite existing unmanaged kubeconfig entries. - Are idempotent: running twice with the same input is safe. - Support `--dry-run` to preview without writing. - Print a next-step hint: `cloudctl sync -n <org>`. Closes #80 Signed-off-by: onuryilmaz <onur.yilmaz@sap.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Collision handling and insufficient input validation can produce broken kubeconfigs, and the required token-based mode is missing.
Review effort: Balanced
Findings: 4
Open (10)
Add greenhouse-token support to individual-flags mode · New Validate exactly one usable context before merging · New Preserve shared keys and reject cluster target collisions · New Preflight bootstrap unit collisions before merging · New Load merged KUBECONFIG while retaining the first file as write target · New Preserve organization before renaming the selected context · New Report CurrentContext changes as modifications · New Report CurrentContext changes as modifications · New Correct documentation for emitted empty client secret · New Use omitzero for new slice JSON fields · New
What changed in this PR
Adds first-time Greenhouse kubeconfig bootstrapping to the CLI.
Changes:
- Adds blob and OIDC flag input modes, interactive prompts, merging, and dry-run support.
- Adds structured bootstrap output and terminal formatting.
- Adds comprehensive unit and command tests.
| File | Description |
|---|---|
cmd/bootstrap.go |
Implements the bootstrap command and kubeconfig merging. |
cmd/bootstrap_test.go |
Tests construction, merging, and command behavior. |
cmd/output/types.go |
Defines structured bootstrap results. |
cmd/output/plain_printer.go |
Formats plain-text bootstrap results. |
cmd/output/interactive_printer.go |
Formats interactive terminal results. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…org hint - fix misleading doc comment in buildOIDCKubeconfig (client-secret is always emitted, not omitted when empty) - fix Added/Skipped JSON/YAML tags from omitempty to omitzero (Go 1.25 idiom) - add CurrentContextUpdated field to BootstrapResult; printers now show "nothing new" only when no entries were added AND current-context was not changed - capture org from the selected context's namespace before renameKubeconfigContext so the sync hint is correct when --context-name differs from greenhouse-<org> - when --kubeconfig is not explicitly set, load through clientcmd.NewDefaultClientConfigLoadingRules to honour multi-file KUBECONFIG - make renameKubeconfigContext safe for shared cluster/authinfo references: only delete old keys if no other context still references them - validate blob context in resolveIncomingKubeconfig: error when current-context is missing or ambiguous, or when context references a non-existent cluster/user - add tests: SharedClusterPreserved rename, DataBlobNoCurrentContext, DataBlobMissingClusterRef Signed-off-by: onuryilmaz <onur.yilmaz@sap.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Collision handling can overwrite incoming entries or create mixed, unusable kubeconfig units.
Review effort: Balanced
Findings: 1
Open (1)
Resolved since last review (10)
Preflight bootstrap unit collisions before merging Preserve shared keys and reject cluster target collisions Validate exactly one usable context before merging Add greenhouse-token support to individual-flags mode Report CurrentContext changes as modifications Report CurrentContext changes as modifications Preserve organization before renaming the selected context Load merged KUBECONFIG while retaining the first file as write target Use omitzero for new slice JSON fields Correct documentation for emitted empty client secret
When KUBECONFIG starts with a path separator (e.g. :/second/config), the first segment is empty and the write target is ambiguous. Return a clear error matching the behaviour of resolveWriteTarget in sync.go. Also reset cobra flag Changed state between test runs so that flag.Changed() is accurate regardless of test ordering. Signed-off-by: onuryilmaz <onur.yilmaz@sap.com>
… matches When the incoming context key already equals targetName, the early return skipped renaming the referenced cluster and authinfo entries. This caused the three map entries to have inconsistent names after merge. Separate the "context key is a no-op" case from "entries need renaming" so cluster and authinfo are always aligned to targetName regardless of whether the context key itself needed changing. Signed-off-by: onuryilmaz <onur.yilmaz@sap.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Context renaming and independent map merging can still overwrite incoming target-name entries or create mixed invalid configurations when names collide.
Review effort: Balanced
Findings: None



Summary
Implements
cloudctl bootstrapas described in #80, giving operators a single command to bootstrap first-time access to a Greenhouse cluster.--data=<base64-kubeconfig>— decodes a kubeconfig downloaded from the Greenhouse Web UI (standard kubeconfig YAML, base64-encoded). Supports any auth type the UI provides: OIDCauth-provider, exec-plugin, token, or cert. All fields (certificate-authority-data,namespace, fullauth-providerconfig) are preserved verbatim.--greenhouse-server,--greenhouse-org,--greenhouse-idp-issuer-url,--greenhouse-client-id,--greenhouse-client-secret,--greenhouse-extra-scopes,--greenhouse-ca-data,--greenhouse-namespace. Produces the exact sameauth-provider/oidckubeconfig shape as the Greenhouse UI download, scriptable without a browser.--set-current-contexton TTY; fully non-interactive when flags are provided.--dry-runpreviews without writing.cloudctl sync -n <org>.Test plan
TestBuildOIDCKubeconfig_*— unit tests for OIDC kubeconfig construction, including exact match against the real Greenhouse shapeTestRenameKubeconfigContext_*— rename of all three entries; multi-context blob only renames currentTestMergeBootstrapKubeconfig_*— add/skip/no-overwrite/preserve behaviourTestResolveIncomingKubeconfig_*— both input modes, all missing-flag error pathsTestBootstrapCmd_*— 11 end-to-end cobra command tests: blob write, individual flags write, parity between modes, dry-run file unchanged, idempotency, context rename, preserves existing entries, creates file from scratch, JSON output, missing flags error, raw base64Closes #80