feat: guard bootstrap against an existing App of Apps and announce the target context - #77
Merged
Merged
Conversation
…e target context Bootstrap previously ran regardless of whether the cluster had already been bootstrapped, silently overwriting the app-of-apps root Application. It also never named the cluster it was about to modify unless --context was passed explicitly. Add two checks that run after the Kubernetes client connects but before anything is written, so aborting leaves the cluster untouched: - Client.GetAppOfApps reads the app-of-apps Application. When one exists, bootstrap reports its repository, revision, path and sync/health and stops. --force overwrites it instead, still reporting what was found. - The resolved kubeconfig context is announced with a 10 second countdown so a run against the wrong cluster can be aborted with Ctrl+C. Skipped by --yes and automatically when stdout is not a terminal, so CI is not delayed. k8s.ResolveContext resolves the context via client-go rather than shelling out to kubectl, so the Configuration stage and the JSON report now name the real target context even when --context is omitted. Also fix two output problems the early abort exposed: - The report rendered zero-valued resource entries, claiming the App of Apps was "updated" on a run that never reached it. Resource lines are now printed only for resources bootstrap actually touched. - Runtime errors dumped the full usage text and printed the error twice. SilenceUsage is set inside runBootstrap, so flag parse errors still show usage, and SilenceErrors is set on the root command since Execute prints errors itself. Note for existing users: re-running bootstrap on an already bootstrapped cluster now requires --force. The underlying operations remain idempotent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
errcheck does not exclude fmt.Fprint* calls that write to a generic io.Writer, only those targeting os.Stdout, os.Stderr or a bytes.Buffer. The guard's output helpers take an io.Writer so they can be tested against a buffer, so discard the return values explicitly. golangci-lint's default max-same-issues of 3 meant CI reported only 3 of the 9 Fprintf call sites; all of them are handled here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
user-cube
self-requested a review
August 31, 2026 16:18
user-cube
approved these changes
Aug 31, 2026
devopsbuddyapp Bot
pushed a commit
that referenced
this pull request
Aug 31, 2026
# [1.10.0](v1.9.0...v1.10.0) (2026-08-31) ### Features * guard bootstrap against an existing App of Apps and announce the target context ([#77](#77)) ([28ea0be](28ea0be))
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
bootstrapran regardless of whether the cluster had already been bootstrapped, silently overwriting theapp-of-appsroot Application. It also never named the cluster it was about to modify unless--contextwas passed explicitly — so a wrongkubectl config use-contextwas enough to bootstrap the wrong cluster with no warning.Changes
Two checks run after the Kubernetes client connects but before anything is written, so aborting leaves the cluster untouched.
Existing App of Apps safeguard —
Client.GetAppOfAppsreads theapp-of-appsApplication. When one exists, bootstrap reports what it found and stops:--forceoverwrites it instead, still reporting what was found. A missing ArgoCD CRD is treated as "absent" rather than an error, so first-time bootstraps are unaffected.Target context countdown — the resolved context is announced with a 10 second grace period:
Skipped by
--yes/-y, and automatically when stdout is not a terminal, so CI is not delayed. The context line still prints in that case.k8s.ResolveContextresolves the context through client-go rather than shelling out tokubectl, so the Configuration stage and the JSON report now name the real target context even when--contextis omitted (previously it printed nothing).New flags
--forcefalse--yes,-yfalseDrive-by fixes the early abort exposed
Application: (updated)on a run that aborted before touching anything — reading as if the App of Apps had been overwritten. Resource lines are now printed only for resources bootstrap actually reached.SilenceUsageis now set insiderunBootstrap(so flag-parse errors still show usage) andSilenceErrorson the root command, sinceExecuteprints errors itself.Re-running
bootstrapon an already bootstrapped cluster now requires--force. The docs previously advertised bootstrap as "fully idempotent, safe to re-run" — the underlying operations remain idempotent, but the default run now stops first. Any CI that re-runs bootstrap needs--force --yes. Marked as afeat(minor) rather than a breaking change; say the word if you'd rather this land as a major.Docs updated accordingly: new Safeguards section and flags in
docs/cli/bootstrap.md, a troubleshooting entry for the new error, the README idempotency note, and the one place insubfolder-setup.mdthat told users to re-run bootstrap.Testing
go build,go vet,gofmtclean; 288 tests pass--force/ lookup error), countdown (interactive vs not),GetAppOfAppsvia a fake dynamic client, andResolveContext(override, kubeconfig fallback, no current context)app-of-apps: bootstrap aborted at the guard with the correct context name and no cluster mutation--forceon a real cluster (installs ArgoCD, overwrites the root Application). The logic is unit-tested and the TTY gate uses the sameterm.IsTerminalidiom asvault_token.go.golangci-lint/gosecnot installed locally — left to CI. The one integer conversion carries the same#nosec G115annotation as the existing one invault_token.go.🤖 Generated with Claude Code