Define the dev-facing CLI contract: `harmony <scope> <verb>` grammar, mandatory explicit context (no default), declarative/operational verb split, three config homes, per-env profile tag, and the machine/agent contract. Add the living CLI use-case & command guide and register it in the mdbook nav. Mark ADR-012 superseded by 025. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
183 lines
8.5 KiB
Markdown
183 lines
8.5 KiB
Markdown
# Harmony Application CLI — Use Cases & Commands
|
|
|
|
> **Status: design target (proposed).** Today `harmony_cli` runs Scores
|
|
> via flags; none of the verbs below are implemented yet. This is the
|
|
> living reference we are building toward. The *decisions and rationale*
|
|
> live in [ADR-025](../adr/025-application-lifecycle-cli.md) — read that
|
|
> for the "why"; this doc is the "what" and "how".
|
|
|
|
## Mental model
|
|
|
|
Four ideas carry the whole CLI:
|
|
|
|
- **`harmony <scope> <verb>`.** A small fixed set of scope nouns —
|
|
**`app`** (developer lifecycle), **`tenant`** (tenant-admin),
|
|
**`cluster`** (cluster-admin), **`context`** (everyone). What you *can*
|
|
run is decided server-side by your context's identity, not by the
|
|
command existing.
|
|
- **Implicit app, explicit context.** The app is *this project's app*
|
|
(inferred — you never name it). The **target is always explicit**:
|
|
`--context <name>` or `HARMONY_CONTEXT`. **There is no default context;
|
|
omitting it is a hard error.** Even local k3d is `--context local`.
|
|
- **Declarative vs operational.** Verbs that change desired state go
|
|
through the project's typed Scores and re-converge. Verbs that only
|
|
read or are ephemeral talk to the cluster directly and never mutate
|
|
desired state. You never imperatively edit live state — you edit a
|
|
Score and redeploy.
|
|
- **Config has three homes, none of them a config file.** Behavior →
|
|
typed Scores (in git). Target + credentials → the context. Secrets →
|
|
OpenBao.
|
|
|
|
### Contexts & profiles
|
|
|
|
A **context** is `{ cluster (endpoint + CA), tenant, credential source /
|
|
identity, profile }`. It carries **no role** — authorization is enforced
|
|
server-side from the identity's token (Zitadel → OpenBao → RBAC), never a
|
|
client-side setting. Contexts are defined out-of-band (user/CI config),
|
|
not in the project.
|
|
|
|
```
|
|
harmony context list # what can I target?
|
|
harmony context show myapp-prod # cluster, tenant, profile, identity (+ role, derived from token)
|
|
```
|
|
|
|
The **profile** (`local | staging | prod`) is a **structured field** the
|
|
context carries — *not* its name. A Score branches on `ctx.profile()` to
|
|
compute per-environment values (`prod` → 3 replicas + managed Postgres;
|
|
`local` → 1 replica + sqlite), and **never parses the context name**. The
|
|
name (`myapp-prod`) is a free human handle for picking `--context`; the
|
|
profile field is the authoritative value, so two differently-named contexts
|
|
can share `profile = Prod` (and `myapp-prod` vs `otherapp-prod` are two
|
|
different contexts at the same profile). See ADR-025 §7.
|
|
|
|
Credentials ride on the context and degrade: **local** uses the ambient
|
|
k3d/kubeconfig; **remote** mints a short-lived, namespace-scoped token via
|
|
Zitadel→OpenBao. The CLI holds nothing standing (ADR-025 §10).
|
|
|
|
---
|
|
|
|
## `app` — the developer lifecycle
|
|
|
|
The verbs, by class:
|
|
|
|
| Verb | Class | Context? | What it does |
|
|
|---|---|---|---|
|
|
| `check` | declarative | no | Compile + type-check the Scores. The only pre-deploy validation today (no live diff yet). |
|
|
| `build` | declarative | no | Build a digest-pinned OCI image; prints the digest. Environment-agnostic. |
|
|
| `publish` | declarative | **yes** | Push the image to the registry (remote) **or** `k3d image import` (local). Topology-specific. |
|
|
| `deploy` | declarative | **yes** | Converge the Scores against the context, pinned to `--image <digest>`. **Does not build.** Returns only after smoke-test. |
|
|
| `ship` | declarative | **yes** | `build` + `publish` + `deploy`, threading the digest. The everyday verb. |
|
|
| `run` | declarative | **yes** | Run a one-off Score-driven Job (migration, task). |
|
|
| `logs` | operational | **yes** | Stream/tail logs. Deep search lives in the observability tool. |
|
|
| `status` | operational | **yes** | Health, replicas, last deploy, recent events. |
|
|
| `exec` | operational | **yes** | Shell/exec into a running container. |
|
|
| `forward` | operational | **yes** | Port-forward a service to localhost. |
|
|
| `restart` | operational | **yes** | Rollout-restart a workload (pods return identical; no state change). |
|
|
| `describe` | operational | **yes** | Resource detail + events for diagnosis. |
|
|
| `history` | operational | **yes** | Deploy history — what shipped, when, which digest. |
|
|
|
|
### Develop (inner loop)
|
|
|
|
| I want to… | Command |
|
|
|---|---|
|
|
| Check my Scores compile/type-check | `harmony app check` |
|
|
| Build the image to test it builds | `harmony app build` |
|
|
| Deploy to local k3d and test | `harmony app ship --context local` |
|
|
| See it running locally | `harmony app status --context local` · `harmony app logs --context local -f` |
|
|
|
|
### Ship to production
|
|
|
|
| I want to… | Command |
|
|
|---|---|
|
|
| Build + publish + deploy to prod | `harmony app ship --context myapp-prod` |
|
|
| Deploy an already-built image | `harmony app deploy --context myapp-prod --image <digest>` |
|
|
| Roll **forward** to a prior good build (recovery) | `harmony app deploy --context myapp-prod --image <prior-digest>` |
|
|
| Build / publish as separate CI stages | `harmony app build` → `harmony app publish --context myapp-prod` → `harmony app deploy …` |
|
|
|
|
There is no rollback and no staging (the roll-forward-only model): you roll forward by
|
|
deploying a known-good digest.
|
|
|
|
### Operate (day-2)
|
|
|
|
| I want to… | Command |
|
|
|---|---|
|
|
| Tail logs | `harmony app logs --context myapp-prod -f` |
|
|
| Check health / what's deployed | `harmony app status --context myapp-prod` |
|
|
| Shell into the app | `harmony app exec --context myapp-prod -- /bin/sh` |
|
|
| Reach a service locally | `harmony app forward --context myapp-prod 8080:80` |
|
|
| Restart the app | `harmony app restart --context myapp-prod` |
|
|
| Run a DB migration | `harmony app run --context myapp-prod -- migrate` |
|
|
| Change replicas / env / resources | *edit the Score, then* `harmony app ship --context myapp-prod` |
|
|
|
|
> Changing replicas/env/config is **not** an imperative command — it's a
|
|
> Score edit + redeploy. An out-of-band `kubectl scale` survives only
|
|
> until the next deploy, then is reconciled away (ADR-025 §5, §8).
|
|
|
|
### Debug / diagnose
|
|
|
|
| I want to… | Command |
|
|
|---|---|
|
|
| Understand why a deploy failed | read the `deploy` output (convergence errors, rustc-style) |
|
|
| See why a workload is unhealthy | `harmony app describe --context myapp-prod` · `harmony app logs …` |
|
|
| See what shipped recently | `harmony app history --context myapp-prod` |
|
|
| Deep log search / traces / metrics | open the observability tool (HyperDX/SigNoz/Grafana) — the CLI deep-links |
|
|
|
|
---
|
|
|
|
## `tenant` — tenant administration (tenant-admin)
|
|
|
|
> Mostly **manual today** (ADR-025 Out of scope: step-0 provisioning).
|
|
> Listed as the target surface; authorization is by the context's role.
|
|
|
|
| I want to… | Command (planned) |
|
|
|---|---|
|
|
| List apps / environments in my tenant | `harmony tenant list --context my-tenant` |
|
|
| Create an environment (namespace — billed) | `harmony tenant env create <name> --context my-tenant` |
|
|
| Add / remove a developer | `harmony tenant member add <user> --context my-tenant` |
|
|
| See usage vs quota | `harmony tenant usage --context my-tenant` (or the dashboard) |
|
|
| Rotate an app's deploy key | `harmony tenant key rotate <app> --context my-tenant` |
|
|
|
|
---
|
|
|
|
## `cluster` — cluster administration (platform operator)
|
|
|
|
> **Future / manual today.** Same noun-by-scope, authorization by role
|
|
> (only a cluster-admin context may run these).
|
|
|
|
| I want to… | Command (planned) |
|
|
|---|---|
|
|
| Provision a tenant | `harmony tenant create <name> --context <cluster-admin-ctx>` |
|
|
| Cluster-wide status | `harmony cluster status --context <cluster-admin-ctx>` |
|
|
| Manage platform operators | `harmony cluster operator …` |
|
|
|
|
---
|
|
|
|
## Machine & agent usage
|
|
|
|
Every verb is built for CI and autonomous agents — the same surface,
|
|
strict mode (ADR-025 §9):
|
|
|
|
- `--json` → a frozen, versioned schema on **stdout**; logs/progress on
|
|
**stderr**.
|
|
- Non-interactive when stdin isn't a TTY — no hidden prompts.
|
|
- Idempotent verbs; `Outcome` (SUCCESS/NOOP/FAILURE/RUNNING/BLOCKED)
|
|
mapped to **exit codes** so CI/agents branch without scraping text.
|
|
- A future agent skill / MCP surface is *derived from* this reference,
|
|
not authored separately.
|
|
|
|
Example CI release (digest pinned end-to-end):
|
|
|
|
```sh
|
|
DIGEST=$(harmony app publish --context myapp-prod --json | jq -r .image)
|
|
harmony app deploy --context myapp-prod --image "$DIGEST" --json
|
|
```
|
|
|
|
---
|
|
|
|
## Status legend
|
|
|
|
Nothing here is implemented yet. As verbs land, tag them:
|
|
**`[impl]`** implemented · **`[wip]`** in progress · **`[planned]`** ·
|
|
**`[future]`** out of v1 scope (per ADR-025). Until the first verbs
|
|
ship, treat the whole document as `[planned]`.
|