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

8.8 KiB

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 — 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-prodharmony app publish --context myapp-prodharmony 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):

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].