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 git binary
  • sops CLI -> go-sops library: In-process decryption, no external sops binary
  • 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:

This architecture has served well for rapid prototyping, but we’re hitting limitations:

  1. Distribution complexity - Requires Python, uv, and bash on target systems
  2. Testing difficulty - Bash is hard to unit test; we rely on manual testing
  3. Argument parsing - Manual, repetitive, easy to miss edge cases
  4. Data structures - Limited to arrays; complex YAML manipulation is awkward
  5. Error handling - set -e helps but proper error types would be cleaner
  6. 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

Migration Strategy

  1. Build Go version as bosun-go alongside existing bash
  2. Verify output parity using golden file tests
  3. Replace bin/bosun symlink once verified
  4. Remove legacy Python/bash after monitoring period

Actual Implementation (2024-12)

The migration is complete. Additional improvements beyond the original plan:

  1. 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)
  2. Improved security: Secrets never exposed via environment variables or external process calls

  3. Reduced external dependencies: Only SSH (for remote deployment) and Docker remain as external requirements

Consequences

Pros

Cons

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