4.9 KiB
Sunbeam CLI
Kubernetes-based local dev stack manager. Python 3.11+, zero runtime dependencies (only stdlib + subprocess calls to bundled binaries).
Build & Test
pip install -e . # install in editable mode
python -m pytest sunbeam/tests/ # run all tests
Tests use unittest + unittest.mock. Test files live in sunbeam/tests/test_<module>.py.
Critical Rules
- Do NOT add dependencies. This project has zero Python dependencies beyond setuptools. All external tools (kubectl, kustomize, helm, buildctl) are downloaded at runtime by
tools.py. Neverpip installa library to solve a problem — use stdlib. - Do NOT refactor code you weren't asked to change. Don't rename variables, add type hints, rewrite functions "for clarity," or extract helpers. Touch only what the task requires.
- Do NOT add abstractions. No base classes, no factories, no wrapper utilities for one-time operations. Three similar lines is fine.
- Do NOT over-engineer error handling. Only validate at system boundaries (user input, subprocess results). Trust internal code.
- Do NOT create new files unless absolutely necessary. Prefer editing existing files.
- Do NOT add comments or docstrings to code you didn't write or change.
- Never commit secrets (.env, credentials, keys).
Architecture
sunbeam/
__main__.py → entry point, delegates to cli.py
cli.py → argparse verb dispatch (lazy-imports command modules)
config.py → ~/.sunbeam.json load/save (SunbeamConfig class)
output.py → step/ok/warn/die logging + table formatter
tools.py → downloads and caches kubectl/kustomize/helm/buildctl binaries
kube.py → kubectl/kustomize wrappers, SSH tunnel, domain substitution
cluster.py → Lima VM lifecycle (up/down), kubeconfig, core service install
manifests.py → kustomize build + kubectl server-side apply pipeline
services.py → service status/logs/restart, managed namespace list
secrets.py → OpenBao init/unseal/seed, VSO secret sync
images.py → container image build + registry mirror
checks.py → functional health checks (CheckResult dataclass)
users.py → Kratos identity management (create/delete/lockout)
gitea.py → Gitea bootstrap + registry config
tests/ → unittest tests per module
Modules are imported lazily in cli.py — each verb imports its command module only when invoked. Do not add top-level imports in cli.py.
Code Style — Follow Existing Patterns Exactly
Module docstrings: One-line, starts with a capital letter, uses em-dash to separate topic from description:
"""Service management — status, logs, restart."""
Imports: stdlib first, then sunbeam.* imports. Use from sunbeam.output import step, ok, warn, die not import sunbeam.output.
Output/logging: Use output.py functions — never bare print():
from sunbeam.output import step, ok, warn, die
step("Applying manifests") # section header: "\n==> Applying manifests"
ok("Namespace created") # info line: " Namespace created"
warn("Pod not ready") # stderr: " WARN: Pod not ready"
die("Cluster unreachable") # stderr + exit(1)
Subprocess calls: Use subprocess.run() directly or the wrappers in kube.py:
from sunbeam.kube import kube, kube_out, kube_ok
kube("apply", "-f", path) # run kubectl, check=True
output = kube_out("get", "pods") # capture stdout
success = kube_ok("get", "ns/foo") # returns bool, no exception
Command handlers: Named cmd_<verb>(args), take the argparse Namespace. Defined in their respective module, imported lazily in cli.py.
Type hints: Minimal. Use them on function signatures when they add clarity. Don't add them retroactively to existing code.
Constants: Module-level UPPER_SNAKE_CASE. Lists of managed resources are plain Python lists, not configs or enums.
Global state: Module-level private variables (_context, _ssh_host) set once at startup. Don't introduce new global state without good reason.
Error flow: die() for fatal errors (prints + exits). Don't raise exceptions for user-facing errors.
What NOT to Do
- Don't introduce dataclasses, NamedTuples, or TypedDicts unless the task specifically needs a new data structure. The only existing dataclass is
CheckResultinchecks.py. - Don't add logging (the
loggingmodule). Useoutput.pyfunctions. - Don't add
__all__exports to modules. - Don't add
if __name__ == "__main__"blocks (entry point is__main__.py). - Don't wrap subprocess calls in try/except when
check=Truealready handles failure. - Don't convert working procedural code to classes.
- Don't add CLI arguments that weren't requested. The argparse setup in
cli.pyis intentionally minimal. - Don't create utility modules, helper files, or shared abstractions.
- Don't add configuration file formats (YAML, TOML) — config is JSON via
config.py.