OAM Model
OAM Model, Parser & Transformer
Package oam is launcher’s core: the OAM data model, YAML parser, semantic
validator, and the transform pipeline that turns an Application + ClusterProfile
into Kubernetes manifests. All documents use apiVersion: launcher.gokure.dev/v1alpha1.
Document kinds
| Kind | Type | Purpose |
|---|---|---|
Application | Application | The app: components[] (each with type + properties) and traits[]. |
Package | Package | A parameterized, distributable unit: app.yaml + a kurel.yaml parameter schema (ParameterDecl). |
ClusterProfile | ClusterProfile | Platform choices (trait implementations, capabilities) supplied at build time. |
CapabilityDefinition | CapabilityDefinition | Declares a capability’s rendering/property schema for validation. |
Pipeline
- Parse an Application/Package/ClusterProfile from YAML.
- Resolve parameters (
ResolveParameters) — applykurel.yamldeclarations, values files, and--setoverrides via${var}substitution. - Transform (
Transformer) — dispatch each component to itsComponentHandlerand each trait to itsTraitHandler, merging theClusterProfile’s capability choices.
A Phase-4 post-build stage then synthesizes per-component NetworkPolicy resources,
each a separate additive resource (the authored networkpolicy /
cilium-networkpolicy traits are unaffected):
- Inbound (
{comp}-allow-ingress-traffic) — routing-derived, from routing traits' platform-reservednetworkPolicy.trafficSourcescapability rendering. When a routing trait’sbackendRefnames a separate backend Service (not the exposing component’s own), the allow is retargeted onto that backend component’s pods + the backendRef port — resolved by matching the backend Service name to a sibling OAM component cluster-wide, and the retargeted policy is emitted in the backend component’s own leaf bundle. Resolution spans bundles: components of one Application share a namespace but are split across leaf bundles (dependency-aware = one per component, hierarchical = one per tier), so a router in one bundle correctly retargets onto a backend in another. Two components resolving to the same Service name is ambiguous and fails the transform. A backendRef that resolves to no component (a bare external Service) is left authored unless it carries an explicit authoredbackendSelector(matchLabels only, on the routing trait’spaths[].backend/backendRefs[]— the selector is not inferable from a Service name): that emits a separate{service}-allow-ingress-trafficpolicy in the router’s namespace selecting the backend’s pods on the backendRef ports. Same-namespace only (the backend is referenced by bare name; cross-namespaceReferenceGrantis out of scope). External backends are deduplicated cluster-wide (routers in different leaf bundles naming the same Service emit one merged policy). Two routers giving one external Service different selectors, or an external policy name colliding with a component’s emitted inbound policy, fails the transform rather than emitting conflicting or duplicate allows. - Egress (
{comp}-allow-egress-traffic) — fromTransformContext.EgressPeers, a downstream-supplied, non-authorable synthesis input (graph-derived dependency peers; never set from OAM YAML or capability rendering). K8sNetworkPolicyonly. Empty when a caller supplies no peers (e.g. the kurel CLI), so synthesis is then a no-op. Fail-fast (aligned with the endpoint-ingress family): a peer that carries ports but a nil, empty, or expression-bearing pod selector is a producer bug and fails the transform with an error — it would otherwise emit a namespace-wide egress allow. A peer with no ports is the documented escape hatch and is silently skipped (the destination stays authored). - Endpoint ingress (
{comp}-allow-endpoint-ingress) — the target side of a connection, fromTransformContext.IngressPeers(a platform-supplied, non-authorable graph-derived input). Eachnetpol.IngressPeernames anEndpoint(pod selector + ports) and the sources allowed to reach it; launcher emits an IngressNetworkPolicyselecting the endpoint’s own selector — deliberately not the component-label key — so it protects operator-created pods (e.g. a CloudNativePG cluster’scnpg.io/clusterinstance pods) that carry no component-provenance label. Fail-closed: each source must carry a namespace + a non-empty matchLabels pod selector (namespace-wide sources are dropped), and a policy with no valid rule is not emitted. A component’s endpoints are declared by its handler via the optionalEndpointProviderinterface and read throughTransformer.ComponentEndpoints— the producer half a downstream platform uses to learn the real selector (no hardcoding) and build its dependency graph. One policy is emitted per distinct endpoint: a single-endpoint component keeps the bare{comp}-allow-endpoint-ingressname, while a multi-endpoint component (e.g. a CloudNativePG cluster plus its pooler) suffixes each policy with a short content hash of the endpoint, so the names are distinct and stay stable across unrelated endpoint additions. The suffix names the emitted NetworkPolicy resource itself (not just the internal layout entry), so a multi-endpoint component’s resource ids are unique andkustomize buildaccepts them.
The inbound/egress families select the component’s own pods (the ingress recipients / the egress
source pods) via a derived <domain>/component label by default — the domain comes
from TransformContext.Domain (empty ⇒ the library default gokure.dev; the kurel CLI
uses launcher.gokure.dev). The full key is overridable per transform through
TransformContext.ComponentLabelKey (precedence: ComponentLabelKey > <Domain>/component
gokure.dev/component). This is a platform contract:trafficSources(inbound) andEgressPeers(egress) are platform inputs, so a caller that injects them must ensure its pods carry the derived label — a downstream platform sets its ownDomainand stamps the matching<domain>/componenton every rendered workload and helm-rendered pod — or setComponentLabelKeyto a label its pods do carry (e.g."app"). A caller that injectstrafficSources/EgressPeerswithout either will synthesize a policy that selects nothing.
Parsing
| Function | Purpose |
|---|---|
Parse / ParseMulti / MustParse | Parse one / many Application documents. |
ParsePackage | Parse a Package (app + parameter schema). |
ParseClusterProfile | Parse a ClusterProfile. |
LoadCapabilityDefinitions | Load CapabilityDefinitions for capability validation. |
ParseWithExtraTraitTypes | Parse allowing additional (custom) trait types. |
Standalone parsing validates each trait’s type against the built-in handler set
(the security-context trait is included, matching SecurityContextHandler);
ParseWithExtraTraitTypes widens that allowlist with caller-supplied custom types.
Transform & extension
NewTransformer(...) builds a transformer from maps of component/trait handlers;
pkg/cmd/kurel registers the built-ins. Extend the system by implementing:
| Interface | Role |
|---|---|
ComponentHandler | CanHandle(type) + ToApplicationConfig(...) — see components. |
TraitHandler | CanHandle(type) + Apply(...) — see traits. |
PolicyHandler | Enforce/validate policies (Enforceable, PolicyResult). |
CapabilityAware | Mark a handler as requiring a ClusterProfile capability. |
PropertySchemaProvider | Declare a PropertySchema for the handler’s user-facing properties (see below). |
SourceDeduplicatable | Collapse duplicate sources (e.g. shared OCI/Helm repos). |
ComponentNamed | Expose the owning OAM component (ComponentName() string) on a trait/component sub-app config, so consumers can attribute each emitted resource to its component without re-deriving it from sub-app names. |
Property schemas
Handlers may implement PropertySchemaProvider (PropertySchema() map[string]PropertySchema)
to declare a constrained schema for their user-facing properties. PropertySchema is launcher’s
single schema vocabulary — the same type also backs kurel.yaml parameters (ParameterDecl) and
CapabilityDefinition rendering properties. It has Type (string/integer/boolean/number/array/object),
Description, Required, Default, Enum, nested Properties, Items, and AdditionalProperties
(default false; escape-hatch fields set it true). The rich fields (Enum, Properties, Items,
AdditionalProperties) are meaningful only for handler properties: the two flat call sites (kurel
parameters, capability rendering) reject them at decode time, so unifying the type does not widen
their accepted behavior. Transformer.HandlerSchemas() returns a HandlerSchemaSet{ Components, Traits }
of every registered handler that declares one, so the downstream runtime’s validator can check a component/trait’s
properties before the handler is invoked. Built-in examples: the configmap trait and the
passthrough component.
Description is optional (json:"description,omitempty") but every built-in property populates it —
including nested object fields and array item schemas at every depth — so the downstream runtime can surface prose in
its generated Handler API Reference. A completeness test (pkg/cmd/kurel) enforces that no built-in
schema node is left without a description.
Policy defaults & enforcement
Policy is a typed accessor interface (no type assertions in handlers) that carries
per-environment enforced limits (MaxReplicas, MaxCPU, MaxMemory, MaxStorageSize,
AllowedRegistries), defaults (DefaultReplicas, the CPU/memory request/limit defaults,
and the workload-shape defaults DefaultStorageSize, DefaultScalerMinReplicas,
DefaultScalerMaxReplicas), security flags, and capability constraints. Handlers that
implement Enforceable receive it via ApplyPolicy; NoopPolicy supplies zero values when
no policy is set (so ApplyPolicy is always called with a non-nil value at runtime).
Handlers apply values with the precedence authored > policy default > handler default,
then enforce the limits on the resulting effective value. For example the scaler trait fills
minReplicas/maxReplicas from the scaler defaults when omitted (erroring if neither the trait
nor a policy default supplies them), and the pvc/postgresql handlers default the storage
size from DefaultStorageSize. See the Policy Interface design note under the Concepts
section for the full accessor list and rationale.
Capability system
Capability-aware traits (e.g. expose, certificate, external-secret) declare
required platform inputs; the ClusterProfile provides them, and
CapabilityDefinition rendering/property schemas validate custom capabilities
(--strict-capabilities turns warnings into errors).
This is a large internal builder surface; the tables above cover the entry points.
See pkg.go.dev for the full
type reference, the design notes under the Concepts section, and examples/ for
runnable applications.