diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..5480d0b --- /dev/null +++ b/PLAN.md @@ -0,0 +1,80 @@ +# PLAN.md — Implementation Roadmap + +This document tracks how kctl-tui is being built, phase by phase, and what +is still open. For the full requirements, see [SPEC.md](SPEC.md). + +## Phase 0 — Repository setup (done) + +- [x] Go module (`github.com/skoelle/kctl-tui`), `.gitignore`, MIT `LICENSE`. +- [x] GitHub Actions workflow: `go vet` + `go test` on every push/PR, plus a + cross-platform build matrix (linux/darwin/windows x amd64/arm64) that + attaches binaries to GitHub Releases on version tags. +- [x] `install.sh` for Linux/macOS/WSL, downloading the latest release + asset. +- [x] `README.md`, `config.example.yaml`. + +## Phase 1 — Core logic + navigation (done, initial version) + +- [x] `internal/kctl`: pure, unit-tested logic — + context-pair matching (`FindNextContext`) and namespace/label + filtering (`DistinctLabelValues`, `NamespacesForLabelValue`). +- [x] `internal/config`: YAML config loading (`context_pairs`, + `team_label_key`), with safe defaults when no config file exists yet. +- [x] `internal/kubeexec`: thin wrappers around `kubectl`/`aws` CLI calls + (contexts, namespaces, deployments, rollout restart/status, secret + read, ExternalSecret annotation). +- [x] `cmd/kctl-tui` "full" mode: Bubble Tea navigation for + context -> team -> namespace, with `Esc` correctly popping back one + level at a time, defaults pre-selected from the currently active + context/namespace. +- [x] On confirming a namespace, "full" mode launches the 3-pane `tmux` + session (control pane + two `k9s` panes) via `tea.ExecProcess` and + resumes at the namespace screen once the session ends. +- [x] `cmd/kctl-tui` "panel" mode: menu for Redeploy and the AWS/Kubernetes + secrets diff + force-sync wizard, with `Esc` closing the whole tmux + session (`tmux kill-session`). + +## Phase 2 — Hardening (open) + +- [ ] Replace the hand-rolled AWS secret JSON parsing/`fmt.Sprintf` value + formatting with a proper typed decode, and handle secrets that are + plain strings rather than JSON. +- [ ] Add integration-style tests against a local `kind`/`k3d` cluster in + CI for the `kubeexec` wrappers currently excluded from automated + testing. +- [ ] Input validation for the free-text steps in "panel" mode (empty + secret ID/region/name, invalid characters). +- [ ] Graceful handling when `tmux`, `k9s`, or `aws` are not installed + (currently surfaces the raw exec error). +- [ ] Structured logging / `--verbose` flag for troubleshooting failed + `kubectl` calls. + +## Phase 3 — Windows-native support (open, secondary priority) + +- [ ] Detect OS at runtime; on native Windows (no WSL), fall back to + `wt.exe split-pane` instead of `tmux` for the status panes. +- [ ] Document/implement that `Tab`-based context switching and + `Esc`-triggered session close are **not** available in the native + Windows fallback, per SPEC.md 3.6 — the panes must be closed + manually there. + +## Phase 4 — Nice-to-haves (open, not committed) + +- [ ] Optional direct use of `client-go` instead of shelling out to + `kubectl`, for faster context/namespace/label queries. +- [ ] Config validation command (`kctl-tui config check`) that reports + unknown label keys or context names not present in the current + kubeconfig. +- [ ] Homebrew tap / `scoop` manifest as additional install options + alongside `install.sh`. + +## Notes for contributors + +- Keep any real organization-specific context names, namespace names, + label keys, or secret names out of the repository. Use the generic + placeholders already established in `SPEC.md` and + `config.example.yaml`. +- Pure/testable logic belongs in `internal/kctl` and `internal/config`; + anything that shells out to `kubectl`/`aws`/`tmux` belongs in + `internal/kubeexec` or directly in `cmd/kctl-tui`, and should stay thin + enough that it does not need its own test suite. diff --git a/README.md b/README.md index e69de29..d039513 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,152 @@ +# kctl-tui + +A small terminal entry point for everyday Kubernetes work: pick a context, +a team, and a namespace once, then drive status (via k9s), rollout +restarts, and an AWS Secrets Manager <-> Kubernetes Secret diff/force-sync +workflow from one place instead of retyping long `kubectl` commands. + +## Why + +Working with several clusters, many namespaces per team, and paired +environments (e.g. staging/production) quickly turns into a lot of repeated +typing with plain `kubectl`/`k9s`. kctl-tui adds: + +- A guided **context -> team -> namespace** selection with sensible + defaults (the currently active context/namespace is pre-selected). +- Namespace grouping by an arbitrary, configurable **label** instead of + scrolling through every namespace in the cluster. +- A **3-pane view** (via `tmux`): one control pane for actions, two status + panes running `k9s` for the current namespace across two related + contexts. +- A guided **rollout restart** that lists deployments instead of requiring + you to know/type the exact deployment name. +- A guided **AWS Secrets Manager vs. Kubernetes Secret** comparison, + including an optional ExternalSecret force-sync annotation. + +See [SPEC.md](SPEC.md) for the full requirements and design rationale, and +[PLAN.md](PLAN.md) for the implementation roadmap and current status. + +## How it works + +``` ++--------------------------------------------------+ +| Control pane: kctl-tui panel | +| -> Redeploy, Secrets diff/force-sync | ++--------------------------------------------------+ +| k9s --context -n | ++--------------------------------------------------+ +| k9s --context -n | ++--------------------------------------------------+ +``` + +1. Run `kctl-tui`. It walks you through context, team, and namespace + selection. +2. Once a namespace is confirmed, it opens a `tmux` session with the layout + above and attaches to it. +3. Inside the control pane you can trigger a rollout restart or compare/ + force-sync a secret. The two status panes keep showing live pod state + via `k9s`, so there is no separate "status" menu entry. +4. Pressing `Esc` in the control pane closes the whole `tmux` session + (including both `k9s` panes) and returns you to the namespace + selection. +5. Pressing `Tab` in the control pane switches both status panes to the + paired context configured in `context_pairs` (see Configuration), + keeping the same namespace. + +## Requirements + +- `kubectl`, configured with access to your cluster(s). +- `k9s` (used for the two status panes). +- `tmux` (used for the 3-pane layout). On Windows, this means running + kctl-tui inside **WSL** — `tmux` has no native Windows port. Native + Windows Terminal has its own split-pane feature, but it cannot be + scripted from inside a pane the way `tmux` can, so the automated 3-pane + layout and the `Tab`/`Esc` session handling described above are only + fully supported under Linux/WSL. See SPEC.md section 3.6 for details. +- `aws` CLI, configured with credentials, only needed for the secrets + workflow. + +## Installation + +### Quick install (Linux/macOS/WSL) + +```bash +curl -fsSL https://raw.githubusercontent.com/skoelle/kctl-tui/main/install.sh | bash +``` + +This downloads the latest release binary for your OS/architecture from +GitHub Releases and installs it to `/usr/local/bin/kctl-tui`. + +### From source + +```bash +git clone https://github.com/skoelle/kctl-tui.git +cd kctl-tui +go build -o kctl-tui ./cmd/kctl-tui +sudo mv kctl-tui /usr/local/bin/ +``` + +Requires Go 1.22+. + +### Prebuilt binaries + +Every tagged release (`vX.Y.Z`) is built for `linux`, `darwin`, and +`windows`, each for `amd64` and `arm64`, via the GitHub Actions workflow in +[.github/workflows/build.yml](.github/workflows/build.yml). Download the +matching asset from the [Releases page](https://github.com/skoelle/kctl-tui/releases). + +## Configuration + +Copy [config.example.yaml](config.example.yaml) to `~/.kctl-tui/config.yaml` +and adjust it to your own cluster setup: + +```yaml +context_pairs: + - name: "example-environment-pair" + contexts: + - "example-context-a" + - "example-context-b" + +team_label_key: "example.org/team" +``` + +- `context_pairs`: groups of related `kubectl` contexts. `Tab` in the + control pane cycles through the contexts of whichever group the current + context belongs to. +- `team_label_key`: the Kubernetes namespace label used to group + namespaces by team/ownership in the team-selection screen. This is + entirely up to your organization's labeling convention; kctl-tui ships + with no default team label of its own. + +`~/.kctl-tui/config.yaml` is not part of this repository and should stay +that way — it typically contains your organization's internal context and +label names. + +## WSL setup notes + +If `kubectx`/`kubens` or `kctl-tui` report a missing kubeconfig inside WSL, +your kubeconfig most likely only exists on the Windows side. Symlink it +into WSL: + +```bash +mkdir -p ~/.kube +ln -s /mnt/c/Users//.kube/config ~/.kube/config +``` + +## Development + +```bash +go test ./... +go vet ./... +go build ./cmd/kctl-tui +``` + +Pure logic (context-pair matching, label filtering, config parsing) lives +in `internal/kctl` and `internal/config` and is covered by unit tests. Code +that shells out to `kubectl`/`aws`/`tmux` lives in `internal/kubeexec` and +in `cmd/kctl-tui` and is intentionally kept thin and untested, since it has +no meaningful behavior without a live cluster. + +## License + +[MIT](LICENSE) diff --git a/SPEC.md b/SPEC.md new file mode 100644 index 0000000..e2f75db --- /dev/null +++ b/SPEC.md @@ -0,0 +1,248 @@ +# SPEC: kctl-tui — Kubernetes Entry-Point TUI + +## 1. Goal + +A single terminal tool as the central entry point for everyday Kubernetes +work, bundling the most common workflows currently done via long +`kubectl`/`k9s`/`aws-cli` commands, operable through a text UI (arrow keys, +Esc, Tab) instead of long typed commands. + +Target platform: **Linux / WSL** (primary usage scenario, since split +panes require a real terminal multiplexer). Native Windows (without WSL) +is possible but with reduced split-view functionality (see 3.6). + +**Technology decision: Go + Bubble Tea** (see section 5). + +## 2. Terminology + +| Term | Meaning | +|---|---| +| Context | Kubernetes cluster connection (`kubectl config get-contexts`) | +| Namespace | Logical subdivision within a cluster/context | +| Deployment | Controls the pods of a service; its name does not have to match the namespace name | +| Pod | Running instance, automatically generated name, not needed manually for the restart workflow | +| Rollout Restart | Rolling restart of all pods of a deployment according to the RollingUpdate strategy — **not** a simultaneous hard-kill of all pods, except with 1 replica or the `Recreate` strategy | +| Control pane | The top tmux pane running the Go tool itself (menu for redeploy/secrets) | +| Status panes | The two lower tmux panes, each running a `k9s` instance | +| Team label | Any freely configurable namespace label used to group namespaces by team/ownership (key and values are project-specific, kept generic here) | + +## 3. Functional Requirements + +### 3.1 Start navigation (hierarchical, with defaults) + +Order at program start (outside tmux, "full" mode): + +1. **Context selection** — list of all available contexts, currently + active context pre-selected at the top as "(current)". +2. **Team selection** — list of all distinct values of a configurable + namespace label (key freely configurable, e.g. + `/`), current team value pre-selected. A + "no filter" option is available. +3. **Namespace selection** — filtered by the chosen team label value, + active namespace pre-selected. +4. **Main view** — directly starts the tmux session with the three panes + (see 3.3). + +Navigation: + +- **Esc** in the control pane first ends the whole tmux session + (`tmux kill-session`, closing both k9s panes as well) and then moves the + Go tool's screen stack one level up: namespace selection -> team + selection -> context selection. +- **Tab** in the control pane switches the context pair according to the + context-pair pattern (see 3.6) for both status panes simultaneously; the + namespace stays the same. + +### 3.2 Namespace grouping via labels + +- Display all currently assigned labels per namespace: + `kubectl get ns --show-labels`. +- Query all distinct values of an arbitrary label key (including keys + with a dot/slash) via bracket notation in JSONPath. Example with a + generic placeholder key ``: + ``` + kubectl get ns -o jsonpath='{range .items[*]}{.metadata.labels[""]}{"\n"}{end}' | sort -u + ``` +- The actual label key is project-specific and set via the configuration + file (see 3.6), not hardcoded. +- Namespace labeling is a prerequisite (one-time setup outside the tool). + +### 3.3 Layout: 3-panel view (core design change vs. earlier drafts) + +Once start navigation is complete, the tool opens a tmux session with +**three panes**, started with a single command: + +``` ++--------------------------------------------------+ +| Pane 0 (top): Control pane | +| -> runs the kctl-tui binary in "panel" mode | +| -> menu: Redeploy, secrets diff | ++--------------------------------------------------+ +| Pane 1 (middle): k9s --context -n | ++--------------------------------------------------+ +| Pane 2 (bottom): k9s --context -n | ++--------------------------------------------------+ +``` + +Important: a separate "status" menu item in the control pane is not +needed — status is continuously visible via the two k9s panes for as long +as the session runs. The control pane only contains the actions k9s does +not cover: **redeploy** and **secrets diff**. + +Example startup command (generic placeholders): + +``` +tmux new-session -d -s kctl \ + "kctl-tui panel --ctx=$CTX_A --ns=$NS --team=$TEAM" \; \ + split-window -v "k9s --context $CTX_A -n $NS" \; \ + split-window -v "k9s --context $CTX_B -n $NS" \; \ + select-layout main-horizontal \; \ + attach -t kctl +``` + +Switching between panes: `Ctrl-b` + arrow key, or `Ctrl-b` `o`. + +### 3.4 Redeploy (rollout restart) — in the control pane + +- List all deployments in the currently selected namespace for selection + (`kubectl -n get deploy`). +- Confirmation step before execution. +- Execute `kubectl -n rollout restart deploy/` followed by + `kubectl -n rollout status deploy/`. +- The result is immediately visible in the status panes below (pods get + recreated) — no separate status feedback channel needed in the tool + itself. + +### 3.5 AWS Secrets Manager <-> Kubernetes Secret diff — in the control pane + +1. Load the secret from AWS Secrets Manager: + `aws secretsmanager get-secret-value --secret-id --region --query SecretString --output text`. +2. Show the contained keys for selection. +3. Load the matching Kubernetes secret field: + `kubectl -n get secret -o jsonpath='{.data.}'`, + base64-decode it. +4. Compare the values (identical / different). +5. On mismatch, optionally request a force-sync: + `kubectl -n annotate externalsecret force-sync= --overwrite`. + +All names (secret ID, secret name, field name, ExternalSecret name) are +asked for interactively at runtime, never hardcoded in the tool. + +### 3.6 Context-pair pattern (configurable) — drives both status panes at once + +Requirement: the Tab switch in the control pane must switch **both** +status panes below it, not just an internal state. + +Configuration format (e.g. `~/.kctl-tui/config.yaml`), purely illustrative +with generic placeholders: + +```yaml +context_pairs: + - name: "environment-pair-1" + contexts: ["", ""] + - name: "environment-pair-2" + contexts: ["", ""] + +team_label_key: "/" +``` + +Behavior on Tab in the control pane: + +1. Determine the current context pair from the configuration. +2. Restart both k9s panes via + `tmux respawn-pane -k -t kctl:0.1 "k9s --context -n "` and + `... kctl:0.2 ...` (namespace stays the same). +3. If the current context is in no configured list: show a hint in the + control pane instead of an error. +4. No action outside this configuration — no error, only a hint. + +**Platform limitation on Windows without WSL:** `respawn-pane`/ +`kill-session` are tmux-specific. Windows Terminal (`wt.exe`) offers no +equivalent scripting to replace panes or end the session from inside a +pane. On plain Windows (without WSL), only a simplified flow is possible: +k9s panes are closed manually (`q`, then `Ctrl+Shift+W`); Tab switching and +automatic session termination are unavailable there. This limitation is +the main reason the primary target system is set to Linux/WSL. + +## 4. Non-functional requirements + +- **Primary platform Linux/WSL**, secondary native Windows with reduced + functionality. +- **Single-binary distribution** without external runtime dependency (Go + provides this natively). +- **External dependencies**: `kubectl` mandatory; `tmux`, `k9s`, `aws-cli` + depending on the action used. +- **Low startup time**, noticeably faster than the current `kubens` + experience. +- **No destructive actions without confirmation** (redeploy, force-sync). +- **No project- or company-specific names (contexts, namespaces, label + keys, secret names) in code or example files** — everything is sourced + via configuration or interactive input at runtime. + +## 5. Technology decision: Go + Bubble Tea + +Rationale: + +- The Kubernetes tooling ecosystem (`kubectl`, `k9s`, `client-go`) is + itself written in Go — enabling direct API access instead of pure shell + calls in the future. +- Bubble Tea (Charm) is specifically built for hierarchical, + keyboard-driven TUIs with a screen stack, same look/feel as k9s. +- Static, small binary without runtime dependency; fast cross-compilation + (`GOOS=linux/windows`). +- Both Go and .NET are fundamentally cross-platform; since the real target + is Linux/WSL anyway (due to the split-view requirement), .NET's main + advantage (one runtime for both worlds in a single step) no longer + applies, so the choice is free to fall on the technically closest + ecosystem. + +Rejected options (see discussion history): + +- Python + Textual: good prototype, but requires an additional runtime, no + single binary without extra effort (PyInstaller). +- .NET + Terminal.Gui: technically equivalent, but larger binary, no + compelling advantage anymore with a Linux-only target. +- Rust + Ratatui: excellent TUI library, but a steeper learning curve + without a clear extra benefit over Go for this project. + +## 6. Already solved by standard tooling (no custom build needed) + +- Generic fuzzy selection of context/namespace: `kubectx` + `kubens` + + `fzf`. +- Ad-hoc actions directly from k9s with the current context/namespace/ + resource name: k9s plugins (`~/.k9s/plugin.yml`). +- Split panes themselves: tmux (Linux/WSL) or native Windows Terminal + panes (`wt.exe split-pane`) — no custom multiplexer code needed, the + tool only orchestrates existing tools. + +## 7. Environment prerequisites (WSL-specific, from troubleshooting) + +- **kubectx/kubens incompletely installed:** Some Linux distro packages + for `kubectx` only ship the `kubectx` binary without `kubens`. Fix: + install via the distribution's package manager properly, or manually + symlink both scripts from the official project repository. +- **"kubeconfig file not found" in WSL:** The kubeconfig often only exists + in the Windows user profile; WSL has its own, separate home directory. + Fix via symlink: + ``` + mkdir -p ~/.kube + ln -s /mnt/c/Users//.kube/config ~/.kube/config + ``` + or via the `KUBECONFIG` environment variable pointing to the same + Windows path. +- These fixes are a prerequisite before the tool can be meaningfully + tested, since it builds directly on `kubectl config`. + +## 8. Open items / out of scope (v1) + +- No automatic label setup for namespaces (migration is a separate, + one-time task). +- Split view v1 fixed at 2 status panes + 1 control pane (3 panes total). +- No RBAC/permission checks before executing sensitive actions — the tool + assumes existing kubectl permissions. +- Configuration file format (`config.yaml`) is a proposal, not finally + agreed; concrete label keys, context names, and namespace names are + project-specific and belong exclusively in the user's local, unversioned + configuration, not in this document or the source code. +- Native Windows (without WSL) remains a secondary platform with manual + pane handling instead of an automated tmux lifecycle.