v0.7.9 — Reconciler configuration, user-defined profiles, kubectl DSL, and resilience examples
Resilience pack — new sub-examples
Three new examples under examples/resilience/, each with ork validate, ork simulate, ork run walkthrough, and a fully passing ork e2e:
| Example | What it teaches |
|---|---|
admission-protection | Runtime validation as a resilience layer. Bad CR → failureThreshold exceeded → operator degrades. Patch to valid image → automatic recovery, no restart. |
crd-missing-recovery | Runtime CRD watch. Delete the CRD at runtime without deletion protection — Orkestra detects the disappearance, degrades, and retries. Re-apply the CRD and CR → full recovery. lastError preserved as audit trail. |
leader-failover | Leader election resilience. Helm-deployed runtime with replicaCount: 2. Kill the konductor pod — a follower is elected within leaseDuration and reconciliation continues. Covers lease inspection and leader election configuration. |
Live API documentation
New concept section: Every CRD is a Live API (documentation/concepts/live-api/). Covers the runtime HTTP API on port 8080 — /katalog, /katalog/{crd}, /katalog/{crd}/health, /katalog/{crd}/cr — with real response shapes, isKonductor signal, hasUnhealthyDependencies, and the gateway API on port 8443. Includes a complete endpoint reference.
E2E concept: testing leader-led deployments
New concept page (documentation/concepts/e2e/06-leader-led-deployments.md) explaining the follower routing problem and how leaderElection solves it. Covers isKonductor, lease namespace defaulting, when to use service vs. leader port-forward, and kubectl.logs with leaderElection. General-purpose — applies to any operator using Kubernetes leader election, not just Orkestra.
Source caching for ork pull -f
ork pull -f komposer.yaml now pre-warms all three local cache namespaces so that subsequent ork template, ork validate, and ork generate calls are served from disk rather than making network requests:
| Cache | Location | Key |
|---|---|---|
| Git Helm sources | ~/.orkestra/helm/git/<sha256>/ | SHA256(repo + ref + subpath) |
| Remote Helm repository charts | ~/.orkestra/helm/repo/<sha256>/ | SHA256(repo + chart + version) |
| Remote HTTPS files | ~/.orkestra/files/<sha256>/ | SHA256(url) |
Caches have no TTL — entries persist until ork pull -f komposer.yaml --refresh is run, which bypasses all three and overwrites the stored copies. --refresh on the OCI registry pull was already supported; it now extends to Helm and file sources.
Bug fixes
- Merger log noise: git clone and Helm pull progress messages in
resolveGitChartandresolveRemoteChartdowngraded fromInfotoDebug. Only shown with--debug. - charts/examples gitlink: dangling gitlink (
mode 160000) in the repository index with no.gitmodulescausedork templateto fail on machines that cloned the repo via the merger. Re-added as regular tracked files. .gitignorebareorkpattern: the entryorkmatchedpkg/ork/and blockedpkg/ork/metrics.gofrom being tracked. Changed to/ork(root-only).- govulncheck in CI:
go run golang.org/x/vuln/cmd/govulncheck@latest ./...added tovalidate-prworkflow.go install+ call-by-name was tried first butGOPATH/binis not onPATHin CI runners. - CVE dependency updates:
x/crypto→v0.52.0,x/net→v0.55.0,containerd→v1.7.33. Three remaining containerd advisories haveFixed in: N/Aupstream and are not actionable.
Breaking: reconciler config moves inside operatorBox
workers, resync, and queue (maxDepth, failureThreshold) move from the CRD root into operatorBox.reconciler. The operatorBox is the complete definition of what makes a CRD an operator — the reconciler runtime config belongs there alongside onCreate, onReconcile, status, and admission.
# before
spec:
crds:
service:
workers: 4
resync: 30s
queue:
maxDepth: 200
failureThreshold: 5
operatorBox:
onCreate: ...
# after
spec:
crds:
service:
operatorBox:
reconciler:
workers: 4
resync: 30s
queue:
maxDepth: 200
failureThreshold: 5
onCreate: ...
ork validate rejects the old layout with a clear error pointing to operatorBox.reconciler.
New profile class: reconciler
Named presets for concurrency and queue tuning. Reference with operatorBox.reconciler.profile:
| Profile | workers | resync | queue.maxDepth |
|---|---|---|---|
high-throughput | 10 | 5m | 1000 |
conservative | 2 | 1m | 100 |
development | 1 | 30s | 50 |
Inline fields override the profile when both are declared. Retry is always exponential backoff — no maxRetries.
User-defined profiles for all profile classes
All profile classes now support user-defined named presets declared in the profiles: block at the root of a Katalog or Motif. Previously only networkPolicies, resourceQuotas, limitRanges, hpa, pdb, and rollingUpdate supported this. Four additional classes are added:
| Class | YAML key | What it tunes |
|---|---|---|
| Resources | profiles.resources | Container CPU and memory requests/limits |
| Probes | profiles.probes | Probe timing — initialDelaySeconds, periodSeconds, failureThreshold, successThreshold, timeoutSeconds |
| Container Security | profiles.containerSecurity | Per-container securityContext — allowPrivilegeEscalation, readOnlyRootFilesystem, runAsNonRoot, capabilities |
| Pod Security | profiles.podSecurity | Pod-level securityContext — runAsNonRoot, runAsUser, runAsGroup, fsGroup |
profiles:
resources:
- name: api-worker
requests: { cpu: "500m", memory: "256Mi" }
limits: { cpu: "2", memory: "1Gi" }
probes:
- name: slow-boot
initialDelaySeconds: 60
periodSeconds: 20
failureThreshold: 5
timeoutSeconds: 10
containerSecurity:
- name: strict-readonly
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop: [ALL]
podSecurity:
- name: ci-runner
runAsNonRoot: true
runAsUser: 2000
runAsGroup: 2000
fsGroup: 2000
User-defined profiles are resolved before Orkestra built-ins. Declaring a profile with the same name as a built-in overrides it for that Katalog. ork validate prints a warning when a built-in is shadowed.
operatorBox.autoscaler does not yet support user-defined profiles — configure autoscaler behavior inline.
kubectl: DSL block for e2e assertions
A kubectl: block can now sit alongside resources: and commands: in any expect: entry. Eleven subcommands:
| Subcommand | Generates |
|---|---|
get | kubectl get <kind> <name> -o jsonpath='{<field>}' or --output json/yaml |
logs | kubectl logs -n <ns> [-l <selector> | <name> | leaderElection] [--since=<since>] |
describe | kubectl describe <kind> <name/selector> -n <ns> |
exec | kubectl exec -n <ns> <pod> -- <command> |
port-forward | Port-forward + curl as one operation; lifecycle managed by the runner |
apply | kubectl apply -f <file> or inline manifest via stdin |
patch | kubectl patch <kind> <name> --type=<merge|strategic|json> -p '<patch>' |
delete | kubectl delete -f <file> or by resource identity; ignoreNotFound: true available |
events | kubectl events --for=<kind>/<name> -n <ns> |
auth | kubectl auth can-i <verb> <resource> [--as <as>] |
cp | kubectl cp <ns>/<pod>:<src> — copies to temp file, asserts content, cleans up |
top | kubectl top <pod|node> — requires metrics-server; installed automatically via Helm |
All subcommands share six assertion fields: equals, notEquals, outputContains, outputNotContains, greaterThan, lessThan. greaterThan and lessThan parse the output as float64 — the check fails if the output is not numeric. jq and yq extraction is supported where applicable. apply and patch run before read subcommands each iteration so mutations take effect before assertions check them.
commands[].run now also supports outputNotContains.
leaderElection on port-forward and logs
Both port-forward and logs entries accept a leaderElection block that resolves the target pod from a Kubernetes coordination.k8s.io/v1 Lease. In multi-replica deployments only the elected leader runs reconcilers — without this, port-forward may land on a follower returning stale state and kubectl logs may target the wrong pod. leaderElection guarantees assertions always run against the pod with authoritative state.
kubectl:
port-forward:
- leaderElection:
lease: orkestra-konductor
namespace: orkestra-system
port: 8080
path: /katalog/myapp/health
jq: state
equals: "healthy"
logs:
- leaderElection:
lease: orkestra-konductor
namespace: orkestra-system
outputContains: "became konductor"
service and pod are optional when leaderElection is set. For logs, name, labelSelector, and leaderElection are mutually exclusive. lease.namespace defaults to the entry’s namespace.
kubectl.delete
Supports deletion by file or by resource identity. ignoreNotFound: true silences errors when the resource is already gone — useful in cleanup steps or after cascade deletes.
kubectl:
delete:
- file: ./crd.yaml
ignoreNotFound: true
- kind: Pod
name: my-pod
namespace: default
ignoreNotFound: true
wait: on checkpoints and port-forward entries
Each expect: checkpoint accepts a wait: field — a duration to sleep before the polling loop starts. Individual port-forward entries accept their own wait: — a duration to sleep after the connection is established but before the curl request is sent.
Curl fix: HTTP 4xx/5xx responses no longer silently fail
Port-forward curl calls previously used curl -sf, which exits non-zero on any HTTP 4xx/5xx response. Health endpoints for degraded operators return HTTP 503, causing assertions against degraded state to time out rather than report the actual body. Changed to curl -s — the body is always returned and assertions check content directly.
Port-forward progress spinner
During the polling loop, a spinner shows the URL being curled and the resolved target (curl http://localhost:PORT/path (→ pod/holder-name)).
Tool pre-flight: ork e2e scans the spec and installs missing tools before assertions run — curl (port-forward), jq, yq (apt-get / apk / brew), and metrics-server (Helm, with --kubelet-insecure-tls on kind clusters).
include: for expect: composition
Large test suites can be split across files. An include: entry is replaced in place by the checkpoints in the referenced file — position in the list determines where the expanded checkpoints appear in the run order. The file uses expect: as its root key.
expect:
- include: ./infra-ready.yaml # expands here — runs first
- name: Operator-specific check
after: cr-applied
timeout: 30s
resources:
- kind: MyOperator
name: my-resource
- include: ./cleanup.yaml # expands here — runs last
ork validate expands all includes before reporting — the checkpoint list and count reflect the fully-expanded run order. Paths are resolved relative to the e2e.yaml that contains the include: entry.
Validator: ork validate checks all kubectl: blocks — required fields, mutual exclusion, at least one assertion per entry, jq/yq format consistency, top kind must be pod or node.
Fixture: pkg/e2e/fixture/ is a living integration test with one checkpoint per subcommand. Rule: add a checkpoint when you add a subcommand.