ADR-0010: Rewrite Bosun CLI in Go
Status
Implemented
Note (2024-12): The Go rewrite is complete. The implementation went beyond the original scope by eliminating additional external dependencies:
- git CLI -> go-git library: Pure Go Git operations, no external
gitbinary- sops CLI -> go-sops library: In-process decryption, no external
sopsbinary- rsync -> native Go file copy: Uses tar-over-SSH for remote, native copy for local
- chezmoi -> native Go templates: text/template + Sprig functions for template rendering
This results in a truly single-binary deployment with minimal external dependencies (only SSH and Docker).
Context
Bosun is currently implemented as:
- bin/bosun - Bash script (~1400 lines) handling CLI, Docker operations, snapshots
- manifest/manifest.py - Python module (~330 lines) for YAML rendering
- bosun/scripts/reconcile.sh - Bash script (~300 lines) for GitOps deployment
This architecture has served well for rapid prototyping, but we’re hitting limitations:
- Distribution complexity - Requires Python, uv, and bash on target systems
- Testing difficulty - Bash is hard to unit test; we rely on manual testing
- Argument parsing - Manual, repetitive, easy to miss edge cases
- Data structures - Limited to arrays; complex YAML manipulation is awkward
- Error handling -
set -ehelps but proper error types would be cleaner - Future features - Filesystem watching, rolling updates, plugin system are painful in bash
The remaining roadmap items (local dev mode, rolling updates, plugin system) would be significantly easier in a compiled language with proper data structures and concurrency primitives.
Decision
Rewrite bosun in Go as a single-binary CLI tool.
Architecture
cmd/bosun/main.go # Entry point
internal/
cmd/ # Cobra commands (yacht, crew, provision, etc.)
manifest/ # YAML rendering engine (port from Python)
docker/ # Docker SDK wrapper
snapshot/ # Rollback system
reconcile/ # GitOps engine (port from bash)
ui/ # Colored console output
Key Libraries
github.com/spf13/cobra- CLI framework (used by kubectl, hugo, gh)github.com/docker/docker- Native Docker SDKgopkg.in/yaml.v3- YAML parsinggithub.com/fatih/color- Colored outputgithub.com/go-git/go-git- Pure Go Git implementation (no git CLI)github.com/getsops/sops/v3- Go SOPS library for in-process decryptiongithub.com/Masterminds/sprig/v3- Template functions (same as chezmoi uses)
Migration Strategy
- Build Go version as
bosun-goalongside existing bash - Verify output parity using golden file tests
- Replace
bin/bosunsymlink once verified - Remove legacy Python/bash after monitoring period
Actual Implementation (2024-12)
The migration is complete. Additional improvements beyond the original plan:
-
Pure Go dependencies: Replaced CLI tool dependencies with Go libraries
- go-git for Git operations (no shell exec to
git) - go-sops for secret decryption (no shell exec to
sops) - Native file operations for deployment (no
rsync) - text/template + Sprig for templating (no
chezmoi)
- go-git for Git operations (no shell exec to
-
Improved security: Secrets never exposed via environment variables or external process calls
-
Reduced external dependencies: Only SSH (for remote deployment) and Docker remain as external requirements
Consequences
Pros
- Single binary - No runtime dependencies (Python, uv, bash)
- Native Docker SDK - Docker is written in Go; first-party SDK
- Testable - Unit tests, golden file tests from day 1
- Type safety - Catch errors at compile time
- Concurrency - Goroutines for watch mode, parallel health checks
- Cross-compilation -
GOOS=linux GOARCH=amd64 go buildproduces Linux binary on Mac - Fast startup - No interpreter overhead
Cons
- Rewrite effort - ~2200 lines to port across ~8 phases
- Learning curve - Go idioms differ from bash/Python
- Build step - Must compile before running (vs editing bash directly)
- Binary size - Go binaries are larger than scripts (~10-20MB)
Alternatives Considered
| Alternative | Why Not |
|---|---|
| Keep Bash | Works now, but painful for P3+ features (watch, plugins) |
| Python (typer+rich) | Already a dependency, but still needs Python runtime |
| Rust | Steeper learning curve, overkill for this use case |
| TypeScript/Deno | Could work, but Go has better Docker ecosystem |
References
- Migration Plan
- Cobra CLI Framework
- Docker SDK for Go