Files
kmsvc-cli/PLAN.md
T

71 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# kmsvc-cli — CLI wrapper implementation plan
## Context
Fourth sibling repo, alongside `kafaka_management_service` (server), `kmsvc-proto` (wire contract), and `kmsvc-sdk` (Go client SDK). Per design.md §11a, this is a thin CLI over `kmsvc-sdk`. Used by operators to send/receive/delete messages and inspect/redrive DLQ contents from the terminal.
Queue lifecycle (create/delete/configure) is managed via the Queue CRD on the cluster (`kubectl apply`/`kubectl get queues`), not exposed over gRPC by `kmsvc-proto` — so this CLI has no `queue` subcommand, only `message` and `dlq`.
**Dependency**: `kmsvc-cli` imports `kmsvc-sdk` as a normal Go module dependency — `go get forgejo.riotpiao.homelab.com/rock/[email protected]` (module path matches the self-hosted Forgejo host, same pattern as `kmsvc-proto`). While `kmsvc-sdk` isn't pushed/tagged yet, this repo uses a local `replace` directive in `go.mod` pointing at the sibling `../kmsvc-sdk` checkout — remove it once a real tag exists.
**CLI framework**: the user's existing `talos-cli` (homelab cluster control) is a Rust/`clap` binary, not Go — there's no existing Go CLI convention in this homelab to literally reuse. We use `cobra` (the de facto standard for Go CLIs, same family `kubectl`/`helm` use) and mirror `talos-cli`'s *behavioral* conventions instead: simple subcommand tree, env-var-driven config with sane defaults, no mandatory config file.
## Repo layout
```
kmsvc-cli/
go.mod # module forgejo.riotpiao.homelab.com/rock/kmsvc-cli
cmd/kmsvc/main.go # entrypoint
internal/cli/root.go # root command, global flags (--server, --token, --output)
internal/cli/config.go # ~/.kmsvc/config.yaml (optional) + env var overrides, flag overrides env
internal/cli/client.go # MessageClient interface + buildClient (lazy kmsvc-sdk.Client construction)
internal/cli/messages.go # kmsvc message send|receive|delete|change-visibility
internal/cli/dlq.go # kmsvc dlq peek|redrive
internal/cli/output.go # table/json rendering, shared across subcommands
internal/cli/version.go # kmsvc version (ldflags-injected at release build time)
internal/cli/*_test.go
.forgejo/workflows/ci.yaml
.forgejo/workflows/release.yaml
README.md
```
## Implementation steps
### Step 1 — Command skeleton + config loading ✅ done
- `cobra` root command `kmsvc` with persistent flags `--server`/`--token`/`--output` (table|json), each falling back to `KMSVC_SERVER`/`KMSVC_TOKEN`/`KMSVC_OUTPUT`, falling back to an optional `~/.kmsvc/config.yaml` (`server:`, `output:`), falling back to defaults (`output: table`). Precedence: flag > env > file > default.
- **Verify**: `kmsvc --help` lists `message`/`dlq`/`version`; a table-driven test asserts file+env+flag precedence resolves correctly.
### Step 2 — Auth wiring + client construction ✅ done (revised)
- Commands use the concrete `*kmsvc.Client` directly — no `MessageClient` interface or `clientFactory` abstraction (course-corrected: re-wrapping the SDK's own methods behind an interface purely for test substitution was unnecessary duplication).
- `buildClient` constructs a real `*kmsvc.Client` from resolved config (`kmsvc.WithTokenSource(kmsvc.StaticToken(token))` when `--token`/`KMSVC_TOKEN` is set), errors if `--server`/`KMSVC_SERVER` is unset.
- Tests spin up a real loopback-TCP `grpc.Server` backed by a hand-rolled fake of `kafkamgmtv1.QueueServiceServer` (`testserver_test.go`), exercised through the unmodified SDK `New`/`Client` — fully offline, no interface needed on the CLI side.
### Step 3 — Message commands ✅ done
- `kmsvc message send --queue --body [--group-id] [--dedup-id] [--delay]`, `receive --queue [--wait] [--max-messages] [--visibility-timeout]`, `delete --queue --receipt-handle`, `change-visibility --queue --receipt-handle --timeout`.
- **Verify**: per-command tests against a hand-rolled fake `MessageClient`; `--output json` produces machine-parseable output for `receive`.
### Step 4 — DLQ inspection commands ✅ done
- `kmsvc dlq peek --queue <dlq-name> [--max-messages] [--visibility-timeout]`: receives without deleting (a short visibility timeout lets it become re-visible) — non-destructive inspect.
- `kmsvc dlq redrive --queue <dlq-name> --to <source-queue> [--max-messages]`: for each received message — `SendMessage` to `--to`, then `DeleteMessage` from the DLQ. Not atomic (3+ SDK calls): if send succeeds but delete fails, the command reports that message as "sent but not removed from DLQ — may be redelivered" rather than silently continuing, and the command exits non-zero if any entry had a partial failure.
- **Verify**: fake-backed test asserting call order (send before delete) and that a forced delete failure surfaces the duplicate-risk warning and a non-zero exit.
### Step 5 — Output formatting ✅ done
- `internal/cli/output.go`: shared `table`/`json` renderer for messages and batch results.
- **Verify**: tests for both formats against a fixed sample response.
### Step 6 — Packaging + CI ✅ done (workflows written, release untested — no tag pushed yet)
- `.forgejo/workflows/ci.yaml`: `go build ./...`, `go vet ./...`, `go test ./...` on every push/PR.
- `.forgejo/workflows/release.yaml`: on a tag push, cross-compile (darwin/linux × amd64/arm64) with `-ldflags -X .../internal/cli.version=<tag>`, attach to a Forgejo release.
- **Verify**: a real tag push produces downloadable binaries; `kmsvc version` in the built binary reports the tag. — deferred until first tag.
## Acceptance criteria (overall)
- [x] All `message`/`dlq` subcommands work against a fake gRPC server in CI — no real network dependency.
- [x] Config file + env var + flag precedence is flag > env > file > default, tested explicitly.
- [x] `--output json` is machine-parseable for every read command (`receive`, `dlq peek`).
- [x] `dlq redrive` partial-failure (send ok, delete fails) is surfaced, not silently swallowed, and causes non-zero exit.
- [ ] A tagged release produces real downloadable binaries via Forgejo Actions.
- [ ] Manual end-to-end smoke test (send → receive → delete → dlq redrive) passes against a real running server, once one exists.
## Sequencing note
Once `kmsvc-sdk` is pushed and tagged on Forgejo, swap the `go.mod` `replace` directive for a real `go get forgejo.riotpiao.homelab.com/rock/kmsvc-sdk@<tag>`.