Cluster Profiles
Design: Platform Profile — ClusterProfile
Status: Final | Issue: go-kure/launcher#37
| Version | Date | Summary |
|---|---|---|
| 1.1 | 2026-05-14 | Remove parameters field; update GVK to launcher.gokure.dev/v1alpha1; add strictness rule; add migration guide |
| 1.0 | 2026-04-19 | Initial draft |
1. Purpose
A ClusterProfile tells the launcher runtime how the platform implements each trait.
It is an environment-level document, written once per cluster by the platform operator and
shared across all applications deployed to that cluster.
The separation it enforces: an Application says “I need ingress” — the ClusterProfile
says “ingress on this cluster means a Gateway API HTTPRoute.” The application spec is
portable; the profile is not.
2. Schema
Field reference
| Field | Type | Description |
|---|---|---|
metadata.name | string | Identifies the cluster; referenced in build tooling |
spec.gitopsEngine | string | GitOps engine whose delivery CRs the built-in components emit. Accepted: "fluxcd" (default, optional). |
spec.capabilities | map | Keys are trait types; values are capability bindings |
capabilities.<type>.rendering | map | Platform values merged into trait properties before handler invocation, and into the listed properties of a component whose handler implements ComponentCapabilityDefaults (the persistentvolumeclaim kind reads pvc’s storageClassName). A value ComponentCapabilityDefaults fills into a component is validated against that component’s own property schema; a component handler with no schema is refused unless the trait handler or rule registered for the capability’s type validates the rendering, by implementing ValidateAndApplyDefaults or, for a type that is not built in, through a loaded CapabilityDefinition (go-kure/launcher#751, go-kure/launcher#772). A lowering rule may read a binding through LoweringContext.Capability: webservice and worker fill pvc’s storageClassName into the claims their pvc volumes describe (go-kure/launcher#746). A component handler may read one through ComponentCapabilityFiller: statefulset fills it into its volumeClaimTemplates entries (go-kure/launcher#761) |
Capability schema
cluster.yaml does not carry capability schema — it only carries rendering values.
The schema of what keys a given capability accepts (types, required fields, descriptions)
is separate from the profile itself.
For built-in handlers, the rendering schema lives in typed Go structs in
pkg/oam/builtin/, one struct per capability type. The handler validates and applies
defaults via a ValidateAndApplyDefaults method at ClusterProfile evaluation time.
Unknown rendering keys for built-in handlers are a build error — consistent with the
strict-by-default principle in design-gvk.md.
For custom capabilities (handlers registered by downstream consumers via library
embedding), the rendering schema is optionally declared in a CapabilityDefinition
document (Phase 2/3). See design-capability-schema.md for the full design.
Strictness
Launcher rejects unknown fields in cluster.yaml. A ClusterProfile with unrecognised
keys is a build error. See design-gvk.md for the parser strictness rationale.
What is NOT in a launcher ClusterProfile
A downstream platform runtime may layer additional fields onto its own cluster profile.
Those fields are downstream-specific and must not appear in a launcher cluster.yaml:
- delivery-layer wiring (FluxCD/ArgoCD engine configuration blocks)
- component-catalog / artifact-registry references
- downstream component-variant selection
Note: spec.gitopsEngine (a single string field) is launcher-specific and IS present in
launcher ClusterProfiles. It names the GitOps engine whose delivery CRs are emitted by
the built-in components. It is not the same as a downstream runtime’s full delivery-wiring
block, which stays in that runtime only.
3. Capability Key Resolution
At build time the runtime looks up a capability for each trait using a two-step key resolution:
- Scoped key —
<type>.<scope>wherescopecomes from the trait’sproperties.scopefield, if set. Allows a cluster to configure multiple implementations of the same trait type (e.g. public and internal ingress). - Bare key —
<type>— used whenscopeis absent or no scoped entry is found.
Both key forms may be present in the same profile:
4. Merge Semantics
Rendering values are platform-provided defaults. Application inline properties always take precedence:
An application property authored as null is absent, so it does not overwrite: the
rendering’s value stands (go-kure/launcher#742).
A rendering value reaches the handler as a copy with its decoded type kept, so a 3
stays an integer. The transform refuses a rendering holding a null below the top
level, NaN or ±Inf, or a Go value that is not a string, boolean, number, list or
string-keyed object, before it builds anything (go-kure/launcher#756).
Example:
If the Application overrides an individual key within a nested map, only that key is
overridden; sibling keys from rendering are preserved (go-kure/launcher#750). The
merge recurses wherever both sides hold a map; a list, or a value of another kind,
replaces the rendered value whole. A null is absent at any depth. Authored
validation checks nested Required on the merged properties, so a partial override
that relies on the rendering for a required sibling is accepted
(go-kure/launcher#765).
5. Example Cluster Profiles
nginx ingress + cert-manager (Let’s Encrypt) + Vault ESO
Gateway API + cert-manager (internal CA) + AWS Secrets Manager
Minimal (ingress only)
6. CapabilityAware handlers
Some trait handlers require a capability to produce correct output — for example, the
expose handler must know controllerType to dispatch to the right implementation.
These handlers implement the CapabilityAware interface (defined in pkg/oam):
If CapabilityRequired() returns true and no capability resolves for the trait, the
runtime returns ErrMissingCapability and the build fails with a message naming the
trait type and the cluster profile in use.
Handlers that do not implement CapabilityAware, or whose CapabilityRequired() returns
false, proceed without a capability and rely solely on Application inline properties.
7. Migrating from a downstream cluster profile
A downstream platform runtime may define its own richer ClusterProfile that a launcher
cluster.yaml is derived from. Launcher consumes only the capability model above:
spec.capabilities and its per-capability rendering values map across unchanged.
Everything else — delivery-layer wiring, catalog/registry references, component-variant
selection, and any per-capability parameters schema — is out of scope for a launcher
cluster.yaml and stays in the downstream runtime. spec.gitopsEngine is a launcher-only
addition with no upstream equivalent.
The concrete field-by-field mapping and migration steps for a specific downstream runtime live in that runtime’s own documentation, not here.