v0.7.10 — E2E DSL extensions, cluster improvements, endpoint control, children forEach fixes, ork proxy

8 min read

ork proxy — port-forward for Helm-deployed Orkestra

Inspired by kubectl proxy, ork proxy replaces manual kubectl port-forward calls when working with a deployed Orkestra. It discovers components by the orkestra.orkspace.io/komponent label, resolves the Runtime leader via the orkestra-konductor Lease, and reconnects automatically on pod replacement (rollouts, leader failover).

ork proxy                        # Forward Runtime, Control Center, and Gateway
ork proxy --for cc               # Control Center only
ork proxy --for runtime,cc       # Runtime and Control Center
ork proxy -n my-platform-ns      # Custom namespace
ork proxy --runtime-port 9090    # Remap a port to avoid conflicts

The Runtime forward always targets the leader pod — not a random replica — so the forwarded connection reaches the replica with authoritative reconciler state. Port conflicts are caught before any tunnel opens. The --for flag uses the same vocabulary as ork generate bundle --for.

E2E: kubectl subprocess calls migrated to Go client

Three operations in the e2e runner previously shelled out to kubectl. They now use the Go client directly, removing the kubectl install requirement for these paths:

  • Leader Lease resolution (leaderElection: on port-forward, logs, exec, delete) — CoordinationV1().Leases().Get() instead of kubectl get lease -o jsonpath
  • Auth checks (kubectl.auth) — SelfSubjectAccessReview when no --as is set (matching kubectl auth can-i default behaviour); SubjectAccessReview with User when --as is specified. The previous SubjectAccessReview with an empty user field was rejected by the API server.
  • Port-forward assertions (kubectl.port-forward) — SPDY portforward via k8s.io/client-go/tools/portforward + net/http. Uses readyChan for an exact ready signal instead of the old curl-retry polling loop.

Fix: kind node readiness spinner

The spinner during ork e2e cluster setup was interleaving kubectl output on the same terminal line. Node readiness output is now captured via os.Pipe and never written to the terminal. The spinner updates with (N/total) progress as each node becomes ready and marks success once all nodes are up.

kubectl.restart and kubectl.scale — new E2E DSL subcommands

Two new mutation subcommands under kubectl: for steps that need to trigger a rollout or change replica count as part of a test sequence.

kubectl.restart calls kubectl rollout restart and waits for the rollout to complete (ready: true by default):

kubectl:
  restart:
    - kind: Deployment
      name: my-app
      namespace: default

kubectl.scale calls kubectl scale --replicas=N and waits for the rollout to settle:

kubectl:
  scale:
    - kind: Deployment
      name: my-app
      namespace: default
      replicas: 3

Both support ready: false to skip the rollout status wait. Neither appears in onFailure diagnostics — mutations do not belong in the diagnostic path.

Typical use: disabling deletion protection for e2e cleanup (patch ConfigMap → kubectl.restart gateway so housekeeper starts with enabled: false and removes the ValidatingWebhookConfiguration).

leaderElection: on kubectl.delete and kubectl.exec

leaderElection: was previously supported only on port-forward and logs. It now works on delete and exec as well, completing all four kubectl subcommands.

kubectl.delete with leaderElection: resolves the current Lease holder at test time and deletes the pod by name — without hardcoding it. The leader-failover example now uses this for its “Kill the konductor pod” step, replacing a raw shell command embedded in YAML.

kubectl.exec with leaderElection: runs a command inside the leader pod.

Type rename: E2EKubectlPortForwardLeaderElectionE2EKubectlLeaderElection

The type is now used by all four kubectl subcommands. The old name was misleading. YAML schema keys (leaderElection:) are unchanged.

ork create cluster--workers and --version

--workers <n> provisions n worker nodes in addition to the control-plane. --version <v> selects which kind release to download and cache at ~/.orkestra/bin/kind-<version>; previously hardcoded to v0.27.0. An explicit --version skips any kind binary already in PATH and uses the versioned cache entry. The same --workers flag is accepted on ork e2e and ork push.

ork delete cluster

New command — the complement to ork create cluster. Deletes a kind cluster by name:

ork delete cluster
ork delete cluster --name ork-e2e

e2e.New Options struct

New(e2eFile string, opts Options) replaces a long positional parameter list. Workers and KindVersion propagate into sub-runner New calls for imports.

ork run <name>:<version> — OCI positional argument

ork run postgres:v1.0.0 pulls the pattern from the registry (if not cached) and starts the runtime directly. No -f flag, no local files required.

ork run postgres:v1.0.0 --dev           # pull + cluster + runtime
ork run postgres:v1.0.0 --dev --apply-cr  # + apply example crd.yaml and cr.yaml
ork run postgres:v1.0.0 --dev --use-komposer  # run via the pattern's komposer.yaml
ork run postgres:v1.0.0 --dev --refresh  # re-pull before running

--apply-cr applies crd.yaml from the pattern, waits for the CRD to establish, then applies cr.yaml. The operator starts with a CR already in the cluster.

--use-komposer uses komposer.yaml from the cached pattern instead of katalog.yaml. Only applies to the OCI positional arg path.

komposer.yaml now included in pushed artifacts

komposer.yaml was missing from OptionalFiles for Katalog patterns — it was never included as an OCI layer when pushing. Added FileKomposer constant and added it to the optional files list. Patterns must be re-pushed to include it.

Endpoint control — endpoints: and crossAccess: surfaced in the Control Center

Disabled endpoints no longer show as degraded in the CC. The /katalog summary now carries healthEnabled, infoEnabled, and crossAccess flags alongside the static CRD fields (GVK, GVR, Mode, Namespaced, Description) that were previously only available from the per-CRD info endpoint.

The CC reads these flags and skips disabled endpoint calls, synthesising health and info from the always-available /katalog summary. A CRD with endpoints: enabled: false returns state: "endpoints-disabled" and renders a clean disabled page instead of zeros or error states.

All three CC templates updated:

  • Katalog card — health badge + icon when health is disabled; metrics rows hidden when info is disabled; ⊘ info endpoint disabled row when fully dark
  • CRD detail — three explicit disabled states: full-page notice, health section notice, operatorBox section notice
  • Generated docs — new Endpoints section (always shown); Cross-read access row in Overview; operatorBox summary table (hooks, constructor, finalizers); Configuration section hidden for disabled CRDs

crossAccess: is now visible in the generated docs page for each CRD — ✓ allowed or ⊘ denied with a plain-English explanation.

Advanced pack — 19-endpoint-control

Three new examples under examples/advanced/19-endpoint-control/, each with ork validate, ork simulate, ork run walkthrough, and a fully passing ork e2e:

ExampleWhat it teaches
01-selective-healthendpoints: health: false — info and CR list active, health endpoint removed
02-full-disableendpoints: enabled: false — all HTTP surfaces removed, operator reconciles normally
03-fully-darkcrossAccess: false + endpoints: enabled: false — dark operator reads its open sibling via cross:; sibling’s cross-read is denied; both outcomes visible in status printer columns

Root simulate.yaml and e2e.yaml use imports: to chain all three sub-examples.

Control Center — generated docs page

New documentation page in documentation/orkestra-core/03-controlcenter/generated-docs.md covering the live-generated per-CRD docs: what sections appear, when, and how disabled endpoints affect the output.

Bug fixes

  • forEach: silently no-op on NetworkPolicy, ResourceQuota, LimitRange, ClusterRole, ClusterRoleBinding: forEach: support for these five resource types was deferred from v0.7.8 when they were first introduced. A declared forEach: block was silently dropped — no expansion, no error. Fixed: ExpandForEach* wrappers added in pkg/children/foreach.go; runner calls in run_template_reconcile.go now pass through the expand step.

  • ForEach field missing on NetworkPolicy, ResourceQuota, LimitRange, ClusterRole, ClusterRoleBinding template sources: forEach: was undeclared on these types — any YAML using it would be silently ignored. Fixed: ForEach *ForEachSpec added to all five structs.

  • NetworkPolicy invisible to child tracker: mergeTemplates in pkg/children/read.go merged all v0.7.8 resources except NetworkPolicies — they never appeared in the CR detail children map. Fixed.

  • ResourceQuota and LimitRange invisible to CR detail view: ResourceQuotaGVR and LimitRangeGVR were absent from pkg/children/gvr.go — these resources could not be read back as children after reconcile. Fixed: GVR vars and ChildGVRs() entries added.

  • NetworkPolicy, ResourceQuota, LimitRange, ClusterRole, ClusterRoleBinding not tracked as children: No *Names helper or children.go read block existed for any of the five types. Fixed: helpers and read blocks added; resources now appear in CR detail and the Control Center Resources tab.

  • Node-ready race: waitForNodesReady checked the condition type (Ready) not its status (True). A node could be type=Ready status=False and still be reported ready. Replaced with kubectl wait --for=condition=Ready node --all.

  • --version silently ignored when kind is in PATH: resolveKind always checked PATH first regardless of the version flag. Fixed: empty version checks PATH then falls back to default; non-empty version skips PATH entirely.

  • Spinner output leaking during setup waits: WaitForResource used .Run() for ready checks, letting kubectl rollout status and kubectl wait write progress to the terminal while the spinner goroutine was running. Replaced with .Output(). helm repo add/update had the same issue during the Installing spinner.

ork e2e --report-file

--report-file <path> writes results as a GFM markdown table after every run. The file contains two tables — passed cases (icon, test, time) and failed cases (icon, test, time, error) — with a summary line. Newlines in error messages are replaced with . so the table renders correctly.

ork e2e --report-file results.md
ork e2e --report-file "$GITHUB_STEP_SUMMARY"   # render directly in GitHub Actions

Terminal output is now also split: passed cases print first, then failed cases with the full error indented line-by-line — consistent with how most language test frameworks present results.

spec.onFailure and per-expectation onFailure

Two levels of diagnostic output when a test fails:

spec.onFailure — runs once after all expectations complete, when at least one failed. Use for a global cluster snapshot.

expect[].onFailure — runs immediately when that specific checkpoint fails, before moving to the next. Use to capture state at the moment of failure.

Both accept the same DSL:

spec:
  onFailure:
    kubectl:
      logs:
        - labelSelector: app=my-operator
          namespace: default
          since: 2m
    commands:
      - kubectl get pods -A -o wide

  expect:
    - name: Operator reaches Ready
      after: cr-applied
      timeout: 120s
      kubectl:
        get:
          - kind: MyResource
            name: my-cr
            namespace: default
            field: .status.phase
            equals: Ready
      onFailure:
        kubectl:
          describe:
            - kind: Deployment
              name: my-operator
              namespace: default

Assertion fields on the kubectl: structs are present but never evaluated — output is always printed. onFailure never blocks teardown.