v0.7.12 — Gateway API, IDP, and codebase clarity
Gateway API
Three new endpoints served by the gateway process when gateway.api.enabled: true:
POST /api/v1/apply— server-side apply a CR body; returnsaccepted,name,namespace,resourceVersionGET /api/v1/resources/{kind}/{ns}[/{name}]— read or list CRs without kubeconfigDELETE /api/v1/resources/{kind}/{ns}/{name}— delete a CRGET /api/v1/schema/{kind}— return the CRD’s spec properties withserve.fieldshints merged in; only available whenserve.enabled: true
All routes require a bearer token. Tokens are declared in gateway.api.auth.tokens and can reference a secretRef (self-bootstrapped by the gateway on first start if the Secret does not exist) or an environment variable.
Serve — developer self-service in the Control Center
serve.enabled: true on a CRD entry surfaces a [+ Create] button in the Control Center. The button links to a full-page form that reads field labels, hints, placeholders, and order from serve.fields via the gateway’s /api/v1/schema/{kind} endpoint. The gateway token is injected server-side — the browser never sees it.
gateway:
api:
enabled: true
auth:
tokens:
- name: control-center
secretRef:
name: ork-apply-token
key: token
spec:
crds:
apprequest:
serve:
enabled: true
fields:
team:
label: "Team"
placeholder: "team-payments"
order: 1
environment:
label: "Environment"
hint: "staging or production"
order: 2
Set controlCenter.gatewayToken.secretRef.name in the Helm values to inject the token into the Control Center.
CR list and detail are now leader-pinned
The runtime’s /katalog/{crd}/cr and /katalog/{crd}/cr/{ns}/{name} responses now include isKonductor: true when served by the leader pod. The Control Center retries these requests up to three times until it gets a leader response, then falls back to the last available result. Previously, multi-replica deployments could show an empty CR list or “CR not found” when the Service routed to a follower pod.
Gateway RBAC — least-privilege customresourcedefinitions get
When serve.enabled: true on a CRD, the generated RBAC bundle now includes a get rule on apiextensions.k8s.io/customresourcedefinitions scoped to that CRD’s full resource name ({plural}.{group}). This allows the gateway to read the OpenAPI schema for the /api/v1/schema/{kind} endpoint without cluster-wide CRD read access.
Notes in validation and mutation rules
User-defined notes can now be referenced directly as template expressions inside validation.rules and mutation.rules. Both field:, comparison values (equals:, prefix:, min:, …), and message: are resolved against the full note FuncMap before evaluation. This applies at reconcile time and at admission webhook time.
notes:
functions:
- name: allowedRegistry
expression: "myorg/"
- name: inBusinessHours
expression: '{{ and weekday (timeInWindow "09:00" "18:00") }}'
- name: defaultReplicas
expression: "2"
spec:
crds:
deploymentrequest:
mutation:
rules:
- field: spec.replicas
default: "{{ defaultReplicas }}"
valueType: int
validation:
rules:
- field: "{{ inBusinessHours }}"
equals: "true"
action: deny
message: "deployments are only allowed during business hours"
- field: spec.image
prefix: "{{ allowedRegistry }}"
action: deny
message: "image must be from {{ allowedRegistry }}"
When field: is a template expression, the resolved value is used directly in the comparison — not treated as a path into the CR. The original expression is preserved in violation messages (field "{{ inBusinessHours }}": …).
IsTemplate is now a single exported helper in pkg/types (orktypes.IsTemplate), replacing scattered strings.Contains(s, "{{") checks across the katalog, reconciler, and webhook packages.
Conditional validation and mutation rules
when: and or: conditions are now supported on validation.rules and mutation.rules entries. A rule whose conditions do not match is skipped entirely — no violation is recorded, no log entry is emitted.
validation:
rules:
- field: spec.domain
operator: exists
message: "spec.domain is required for cert workloads"
action: deny
when:
- field: spec.workloadType
equals: cert
- field: spec.repoURL
operator: exists
message: "spec.repoURL is required for app and monitoring workloads"
action: deny
or:
- field: spec.workloadType
equals: app
- field: spec.workloadType
equals: monitoring
Conditions are evaluated using the same EvaluateConditions engine as template when: blocks. Works for both typed and unstructured CRDs — the typed CRD limitation (previously documented as “use Go hooks”) is removed. Both applyReconcileTimeValidation and applyReconcileTimeMutation now use resolver.Data() which handles typed CRDs via JSON round-trip.
Admission webhook rules honour when: and or: in the same way as reconcile-time rules.
serve.fields.<name>.required — browser-native form field enforcement
Setting required: true on an serve field marks it as mandatory in the Control Center form. The browser enforces it natively — the label shows an asterisk and the form cannot be submitted while the field is empty. Fields hidden by a when: or or: condition are automatically excluded from browser constraint validation.
serve:
fields:
productionApproval:
label: "Production Approval Ticket"
required: true
when:
- field: environment
equals: production
required in serve is a form UX annotation only — it does not affect the Gateway API or the admission webhook. For server-side enforcement use validation.rules with action: deny.
include: strict YAML parsing
All include: expansion functions now use StrictUnmarshal (unknown fields produce errors). Previously, a typo in an included YAML file caused the field to be silently dropped; ork validate now surfaces these as clear errors before any cluster is involved.
include: extended to all major Katalog blocks
Previously include: was only supported in e2e expect:. It now works across validation.rules, mutation.rules, conversion.paths, status.fields, notes.functions, profiles, serve.fields, and simulate ops:.
Each include file uses the same root key as the inline block and is expanded in place before the runtime sees the declaration. See documentation/concepts/composition/02-include.md for the full reference and domain layout pattern.
Server-Side Apply for all resource packages
Every reconcilable resource package now uses Kubernetes Server-Side Apply (ApplyPatchType, fieldManager: orkestra-runtime, force: true) instead of Get → compare → Update. Update delegates to the new Apply function; callers are unchanged.
This eliminates false-positive drift from k8s-injected defaults (SA token volumes, imagePullPolicy, Protocol: TCP, default security contexts) and resolves KI-002 resource-version conflicts caused by the status-patch → immediate-reconcile race. Jobs, ServiceAccounts, and PVCs keep Create semantics — their specs are immutable or managed externally.
Hook and constructor args — per-CRD configuration with template support
args: is a free-form key/value map declared under reconciler.hooks or reconciler.constructor in katalog.yaml. Orkestra attaches it to the KubeClient before calling the hook or constructor function; the author reads it via kube.Args().
operatorBox:
reconciler:
hooks:
function: DatabaseHooks
args:
readReplicaCount: 2
backupEnabled: true
region: '{{ default "us-east-1" .spec.region }}'
database:
engine: '{{ default "postgres" .spec.engine }}'
String values support Go template expressions. The GenericReconciler evaluates them against the current CR before the hook runs — the full note FuncMap (default, upper, lower, etc.) is available. Nested maps are walked recursively so any string inside them is also evaluated. Integers and booleans have no template syntax and pass through as their native types.
Constructor authors call kube.ScopedFor(resolver.TemplateEvaluator()) themselves at reconcile time for the same capability with full control over timing.
The primary use case is configuration reusability: one binary, one constructor, many Katalog deployments — each with different args. Tier routing (starter / standard / enterprise), region configuration, feature flags, and per-tenant dynamic values all live in YAML with no code changes. See documentation/concepts/typed-operators/06-reusability.md.
external: under hooks: — hooks can now declare HTTP calls alongside args: in the Katalog. The runtime executes them before invoking the hook, injects the results into the resolver, and makes them available to args: template expressions as .external.<name>.*. The hook reads the result via kube.Args() with no HTTP client or flag-service SDK in hook code.
hooks:
external:
- name: flags
url: "{{ .spec.serviceUrl }}/flags/{{ .metadata.name }}/v2Enabled"
method: GET
continueOnError: true
timeout: 5s
args:
featureEnabled: '{{ .external.flags.body }}'
All external: capabilities available to declarative operators — when:, or:, continueOnError, timeout — apply to hook external calls without additional plumbing. Constructor authors who need external data make the call themselves and pass the URL via args:.
Cluster-scoped GC and always-on finalizer
runners.DeleteOwnedClusterScopedResources now covers Namespaces, ClusterRoles, ClusterRoleBindings, PersistentVolumes, and cluster-scoped custom resources. Previously only Namespaces were explicitly cleaned up on CR deletion.
Every operator now receives the orkestra.orkspace.io/cleanup finalizer unconditionally. Previously the finalizer was only injected when the operator declared Namespace resources. Without it, a CR with cluster-scoped custom: children (e.g. a namespaced CR spawning a cluster-scoped child via custom:) was deleted by Kubernetes before the explicit GC runner could fire, leaving orphaned cluster-scoped resources.
Codebase restructure — packages organised by component
Orkestra’s packages are now organised by component. No API or behaviour changes — import paths only.
Gateway — all gateway concerns under pkg/gateway/:
→ pkg/webhook → pkg/gateway/webhook
→ pkg/notification → pkg/gateway/notification
→ pkg/certmanager → pkg/gateway/certmanager
Runtime — all runtime-only packages under pkg/runtime/:
→ pkg/reconciler → pkg/runtime/reconciler
→ pkg/kordinator → pkg/runtime/kordinator
→ pkg/konductor → pkg/runtime/konductor
→ pkg/informer → pkg/runtime/informer
→ pkg/runners → pkg/runtime/runners
→ pkg/autoscaler → pkg/runtime/autoscaler
→ pkg/queue → pkg/runtime/queue
Registry — simulate, e2e, and motif join pkg/registry/:
→ pkg/simulate → pkg/registry/simulate
→ pkg/e2e → pkg/registry/e2e
→ pkg/motif → pkg/registry/motif
Tools — CLI-support packages under pkg/tools/:
→ pkg/ork → pkg/tools/cluster (package renamed to cluster)
→ pkg/generate → pkg/tools/generate
→ pkg/migrate → pkg/tools/migrate
→ pkg/plan → pkg/tools/plan
→ pkg/devserver → pkg/tools/devserver
→ pkg/proxy → pkg/tools/proxy
New shared package pkg/secrets extracts secret lifecycle helpers used by both the runtime and the gateway Gateway API.