OAM Model

OAM Model, Parser & Transformer

Go Reference Go Reference

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

KindTypePurpose
ApplicationApplicationThe app: components[] (each with type + properties) and traits[].
PackagePackageA parameterized, distributable unit: app.yaml + a kurel.yaml parameter schema (ParameterDecl).
ClusterProfileClusterProfilePlatform choices (trait implementations, capabilities) supplied at build time.
CapabilityDefinitionCapabilityDefinitionDeclares a capability’s rendering/property schema for validation.

Pipeline

parse → resolve parameters → transform (component + trait handlers) → manifests
  1. Parse an Application/Package/ClusterProfile from YAML.
  2. Resolve parameters (ResolveParameters) — apply kurel.yaml declarations, values files, and --set overrides via ${var} substitution.
  3. Transform (Transformer) — dispatch each component to its ComponentHandler and each trait to its TraitHandler, merging the ClusterProfile’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-reserved networkPolicy.trafficSources capability rendering. When a routing trait’s backendRef names 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 authored backendSelector (matchLabels only, on the routing trait’s paths[].backend / backendRefs[] — the selector is not inferable from a Service name): that emits a separate {service}-allow-ingress-traffic policy 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-namespace ReferenceGrant is 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) — from TransformContext.EgressPeers, a downstream-supplied, non-authorable synthesis input (graph-derived dependency peers; never set from OAM YAML or capability rendering). K8s NetworkPolicy only. 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, from TransformContext.IngressPeers (a platform-supplied, non-authorable graph-derived input). Each netpol.IngressPeer names an Endpoint (pod selector + ports) and the sources allowed to reach it; launcher emits an Ingress NetworkPolicy selecting the endpoint’s own selector — deliberately not the component-label key — so it protects operator-created pods (e.g. a CloudNativePG cluster’s cnpg.io/cluster instance 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 optional EndpointProvider interface and read through Transformer.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-ingress name, 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 and kustomize build accepts 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) and EgressPeers (egress) are platform inputs, so a caller that injects them must ensure its pods carry the derived label — a downstream platform sets its own Domain and stamps the matching <domain>/component on every rendered workload and helm-rendered pod — or set ComponentLabelKey to a label its pods do carry (e.g. "app"). A caller that injects trafficSources/EgressPeers without either will synthesize a policy that selects nothing.

Parsing

FunctionPurpose
Parse / ParseMulti / MustParseParse one / many Application documents.
ParsePackageParse a Package (app + parameter schema).
ParseClusterProfileParse a ClusterProfile.
LoadCapabilityDefinitionsLoad CapabilityDefinitions for capability validation.
ParseWithExtraTraitTypesParse 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:

InterfaceRole
ComponentHandlerCanHandle(type) + ToApplicationConfig(...) — see components.
TraitHandlerCanHandle(type) + Apply(...) — see traits.
PolicyHandlerEnforce/validate policies (Enforceable, PolicyResult).
CapabilityAwareMark a handler as requiring a ClusterProfile capability.
PropertySchemaProviderDeclare a PropertySchema for the handler’s user-facing properties (see below).
SourceDeduplicatableCollapse duplicate sources (e.g. shared OCI/Helm repos).
ComponentNamedExpose 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.