- docs/guides/application-capabilities.md: the .with(...) menu, by-reference wiring, and a recipe for writing a capability; disambiguates the overloaded 'capability' (topology vs application) flagged in DX review; wired into the book. - Deprecate ApplicationScore, features::Monitoring, features::PackagingDeployment (+ doc-deprecate the ApplicationFeature trait) in favour of harmony_app + ComposeDeploy/.with(...) (ADR-026). Legacy examples now warn; harmony lib is warning-clean (internal self-refs allowed).
4.6 KiB
Application Capabilities — .with(...)
Status: in progress (feature branch). The
harmony_appapplication layer and capabilities described here are landing incrementally. The decisions and rationale live in ADR-026; this is the "how". Companion to Application CLI.
A capability is an add-on you attach to an app deployment — a database, monitoring, auth — by declaring it:
ComposeDeploy::from_dir("timesheet", "./app")?
.expose("frontend", "timesheet.example")
.with(Postgres::managed()) // a managed database
.with(Monitoring::new().alert(discord)); // a downtime alert
The app author declares intent; the capability deploys what it needs and
wires itself to the app. Everything else — ship/deploy/status/logs,
contexts, profiles — comes from the application CLI.
Two different "capabilities" — don't confuse them.
- Topology capabilities (catalog) are what a cluster can do —
K8sClient,DnsServer,HelmCommand— exposed as trait bounds aScorerequires. Infrastructure-facing.- Application capabilities (this page) are add-ons a developer attaches to their app with
.with(...). They live inharmony_app::capabilities.
The menu
| Capability | Declares | Deploys | Wires into the app |
|---|---|---|---|
Postgres::managed() |
a managed PostgreSQL | a CNPG cluster <app>-db |
DATABASE_URL ← secretKeyRef(<app>-db-app, uri) |
Monitoring::new().alert(r) |
a downtime alert | a Prometheus alert rule + routes it to r |
nothing (selector-scoped to the app's namespace) |
(ZitadelAuth for OIDC login is next.)
How wiring works — by reference, never by value
A capability never passes a value through Harmony. Postgres' generated
password lives only in the CNPG-created <app>-db-app Secret; the capability
injects an env var that references it (secretKeyRef), resolved in-cluster
at runtime. This is what keeps the rendered chart publishable (it contains a
reference, not a secret) and is why the app layer needs no typed Score outputs
today (ADR-026). The trade: wiring must be expressible as a stable k8s
reference (service DNS, Secret/ConfigMap key) — true for k8s-native add-ons.
Writing your own capability
A capability implements one small trait
(harmony_app::capabilities::Capability):
pub trait Capability {
/// What this capability deploys (a database, an operator, …).
fn scores(&self, app: &AppRef) -> Vec<Box<dyn Score<K8sAnywhereTopology>>> { vec![] }
/// Env injected into the app's containers, wired by reference.
fn env(&self, app: &AppRef) -> Vec<EnvVar> { vec![] }
}
AppRef { name, namespace, profile } is how the capability learns who it's
augmenting (and so derives names like <app>-db). Implement either method or
both, then app.with(MyThing).
Recipe (e.g. a Redis capability)
- Find the backing Score. Capabilities compose existing Scores — they
do not hand-roll manifests (ADR-023). Look in
harmony::modules::*for one (Postgres composesharmony::modules::postgresql::K8sPostgreSQLScore). If none exists, write the Score first (Writing a Score). - Return it from
scores(), naming resources offapp(e.g. clusterformat!("{}-redis", app.name)). - Wire it by reference in
env(). Determine what stable reference the Score exposes — a Service DNS name, or a key in a Secret/ConfigMap it creates — and inject anEnvVarpointing at it. (Postgres references CNPG's<cluster>-appsecret'surikey.) The convention between a Score's output and a capability'senv()is currently by agreement, not type-checked — read the Score to confirm the names it produces. - Receivers and other inputs (e.g. an alert
DiscordWebhook) come fromharmony::modules::*too —Monitoringtakes aharmony::modules::monitoring::alert_channel::discord_alert_channel::DiscordWebhook.
Postgres and Monitoring in harmony_app/src/capabilities.rs are the two
worked examples to copy.
Deprecated: the old ApplicationFeature model
The previous approach — ApplicationScore { features: vec![Monitoring, …] }
over ApplicationFeature — is deprecated in favor of this one. It coupled
app delivery to a fixed feature menu and the (cancelled) ArgoCD path, and
wasn't composable with plain Scores. Migrate ApplicationScore →
ComposeDeploy (or your own HarmonyApp) + .with(...) capabilities.