Files
harmony/docs/guides/application-cli.md

186 lines
8.8 KiB
Markdown

# Harmony Application CLI — Use Cases & Commands
> **Status: partially implemented.** The app lifecycle provides build,
> publish, ship, deploy, status, and logs. Other verbs below remain the design
> target. The *decisions and rationale*
> live in [ADR-026](../adr/026-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.** Selection remains mandatory for local k3d and
binaries with one compiled target so shell history and CI identify the target.
- **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). Targets → compiled contexts in the deploy binary.
Secrets → OpenBao.
### Contexts & profiles
A **context** is `{ cluster access, namespace, publication target, domain }`.
It carries no authorization role; authorization comes from the deploy
identity (Zitadel → OpenBao → RBAC). Contexts are typed Rust values compiled
into the deploy binary. Private targets stay in private deploy repositories.
```
harmony context list # what can I target?
harmony context show myapp-prod # cluster, tenant, profile, identity (+ role, derived from token)
```
The context variant determines the profile: local contexts use `Profile::Local`
and remote contexts use production behavior through `Profile::Prod`. A staging
cluster can have a staging context name, but it currently uses the same
replication, TLS, and credential behavior as production. Scores never infer
behavior from the context name. A separate staging profile should be added only
when it has distinct operational semantics. See ADR-026 §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-026 §10).
For remote contexts, the kubeconfig stored in OpenBao selects the Kubernetes
cluster through its `current-context`. Context resolution logs that context,
the API server, and the deployment namespace. Deploy reports repeat the same
target so CI output records where convergence occurred.
---
## `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 | **yes** | Build a digest-pinned OCI image using target-specific naming. Resolves context metadata, but not credentials or cluster access. |
| `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 --context local` |
| 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 --context myapp-prod``harmony app publish --context myapp-prod``harmony app deploy …` |
There is no rollback command. Recovery deploys a known-good digest as a new
roll-forward operation.
### 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-026 §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-026 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-026 §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-026). Until the first verbs
ship, treat the whole document as `[planned]`.