Complete E2E example
One file that exercises every expect: subcommand — resources, commands,
and every kubectl: subcommand. Use it as the canonical reference for what a
fully-featured E2E looks like.
Source: pkg/e2e/fixture/e2e.yaml
apiVersion: orkestra.orkspace.io/v1
kind: E2E
metadata:
name: e2e-probe
description: >
Living integration fixture for the kubectl DSL.
Every subcommand supported by kubectl: must have at least one checkpoint here.
When you add a new subcommand, add a checkpoint that exercises it.
Two CRs run in parallel from cr.yaml:
my-probe-server — orkestra-dev-server on port 9999 (port-forward, JSON assertions)
my-probe-exec — nginx:alpine on port 80 (exec via sh)
spec:
katalog: ./katalog.yaml
crd: ./crd.yaml
cr: ./cr.yaml
cluster:
provider: kind
name: ork-e2e-probe
reuse: false
expect:
# ── resources: ──────────────────────────────────────────────────────────
- name: All resources created and ready
after: cr-applied
timeout: 120s
resources:
- kind: E2EProbe
name: my-probe-server
namespace: default
- kind: Deployment
name: my-probe-server
namespace: default
ready: true
- kind: Service
name: my-probe-server-svc
namespace: default
- kind: E2EProbe
name: my-probe-exec
namespace: default
- kind: Deployment
name: my-probe-exec
namespace: default
ready: true
- kind: Service
name: my-probe-exec-svc
namespace: default
# ── kubectl.get ─────────────────────────────────────────────────────────
- name: Both probes reach Ready status
after: cr-applied
timeout: 120s
kubectl:
get:
- kind: E2EProbe
name: my-probe-server
namespace: default
field: .status.phase
equals: Ready
- kind: E2EProbe
name: my-probe-exec
namespace: default
field: .status.phase
equals: Ready
- name: Field assertions on Deployments and Services
after: cr-applied
timeout: 60s
kubectl:
get:
- kind: Deployment
name: my-probe-server
namespace: default
field: .spec.replicas
equals: "1"
- kind: Deployment
name: my-probe-exec
namespace: default
field: .spec.replicas
equals: "1"
- kind: Service
name: my-probe-server-svc
namespace: default
field: .spec.type
equals: ClusterIP
- kind: E2EProbe
name: my-probe-server
namespace: default
field: .spec.message
equals: e2e-ready
# ── kubectl.logs ─────────────────────────────────────────────────────────
- name: Pod logs show expected startup output
after: cr-applied
timeout: 60s
kubectl:
logs:
- labelSelector: orkestra-owner=my-probe-server
namespace: default
outputContains: "autoscale-metrics"
- labelSelector: orkestra-owner=my-probe-exec
namespace: default
outputContains: "Configuration complete"
# ── kubectl.describe ─────────────────────────────────────────────────────
- name: Describe shows expected resource state
after: cr-applied
timeout: 30s
kubectl:
describe:
- kind: Deployment
name: my-probe-exec
namespace: default
outputContains: RollingUpdate
- kind: Service
name: my-probe-server-svc
namespace: default
outputContains: ClusterIP
# ── kubectl.exec ─────────────────────────────────────────────────────────
- name: Exec into nginx pod (has sh)
after: cr-applied
timeout: 60s
kubectl:
exec:
- labelSelector: orkestra-owner=my-probe-exec
namespace: default
command: [sh, -c, "echo e2e-ready"]
equals: e2e-ready
# ── kubectl.port-forward ─────────────────────────────────────────────────
- name: Port-forward to devserver health endpoints
after: cr-applied
timeout: 60s
kubectl:
port-forward:
- service: my-probe-server-svc
namespace: default
port: 9999
path: /health
outputContains: ok
- service: my-probe-server-svc
namespace: default
port: 9999
path: /started
outputContains: started
- service: my-probe-server-svc
namespace: default
port: 9999
path: /ready
outputContains: ready
# ── kubectl.apply ────────────────────────────────────────────────────────
- name: Apply a ConfigMap inline and assert it landed
after: cr-applied
timeout: 30s
kubectl:
apply:
- inline: |
apiVersion: v1
kind: ConfigMap
metadata:
name: e2e-probe-extra
namespace: default
data:
applied-by: kubectl-dsl
get:
- kind: ConfigMap
name: e2e-probe-extra
namespace: default
field: .data.applied-by
equals: kubectl-dsl
# ── kubectl.patch ────────────────────────────────────────────────────────
- name: Patch server probe and assert the CR field updated
after: cr-applied
timeout: 30s
kubectl:
patch:
- kind: E2EProbe
name: my-probe-server
namespace: default
patch: '{"spec":{"message":"patched"}}'
get:
- kind: E2EProbe
name: my-probe-server
namespace: default
field: .spec.message
equals: patched
# ── kubectl.events ───────────────────────────────────────────────────────
- name: Kubernetes events recorded for both Deployments
after: cr-applied
timeout: 60s
kubectl:
events:
- kind: Deployment
name: my-probe-server
namespace: default
outputContains: ScalingReplicaSet
- kind: Deployment
name: my-probe-exec
namespace: default
outputContains: ScalingReplicaSet
# ── kubectl.auth ─────────────────────────────────────────────────────────
- name: Current user can query cluster resources
after: cr-applied
timeout: 10s
kubectl:
auth:
- verb: get
resource: pods
namespace: default
equals: "yes"
- verb: list
resource: deployments
namespace: default
equals: "yes"
# ── kubectl.cp ───────────────────────────────────────────────────────────
- name: Copy file out of nginx container and assert content
after: cr-applied
timeout: 30s
kubectl:
cp:
- labelSelector: orkestra-owner=my-probe-exec
namespace: default
src: /etc/nginx/nginx.conf
outputContains: worker_processes
# ── kubectl.top ──────────────────────────────────────────────────────────
- name: Live resource usage visible via kubectl top
after: cr-applied
timeout: 60s
kubectl:
top:
- kind: pod
namespace: default
labelSelector: orkestra-owner=my-probe-server
outputContains: my-probe-server
- kind: pod
namespace: default
labelSelector: orkestra-owner=my-probe-exec
outputContains: my-probe-exec
# ── commands: (arbitrary shell) ──────────────────────────────────────────
- name: Arbitrary commands still work alongside the DSL
after: cr-applied
timeout: 30s
commands:
- run: kubectl get e2eprobe -n default -o name
outputContains: my-probe-server
# ── cleanup ──────────────────────────────────────────────────────────────
- name: Cleanup verified
after: cr-deleted
timeout: 60s
resources:
- kind: E2EProbe
name: my-probe-server
namespace: default
count: 0
- kind: E2EProbe
name: my-probe-exec
namespace: default
count: 0
- kind: Deployment
name: my-probe-server
namespace: default
count: 0
- kind: Deployment
name: my-probe-exec
namespace: default
count: 0
What each checkpoint covers
| Checkpoint | Subcommand(s) |
|---|---|
| All resources created and ready | resources: |
| Both probes reach Ready status | kubectl.get — jsonpath field extraction |
| Field assertions on Deployments and Services | kubectl.get — multiple entries per block |
| Pod logs show expected startup output | kubectl.logs — labelSelector, outputContains |
| Describe shows expected resource state | kubectl.describe — kind + name |
| Exec into nginx pod (has sh) | kubectl.exec — labelSelector, inline command |
| Port-forward to devserver health endpoints | kubectl.port-forward — multiple paths per block |
| Apply a ConfigMap inline and assert it landed | kubectl.apply — inline manifest + follow-up kubectl.get |
| Patch server probe and assert the CR field updated | kubectl.patch — merge patch on spec field |
| Arbitrary commands still work alongside the DSL | commands: — raw shell alongside kubectl: |
| Cleanup verified | resources: with count: 0 |
Two CRs run in parallel from cr.yaml (multi-document):
| CR | Image | Port | Purpose |
|---|---|---|---|
my-probe-server | ghcr.io/orkspace/orkestra-dev-server:latest | 9999 | Port-forward and JSON endpoint assertions |
my-probe-exec | nginx:alpine | 80 | Exec assertions — nginx has sh, the devserver is distroless |
See pkg/e2e/fixture/README.md for
instructions on running this fixture and the rule for adding new subcommands.
→ Back: 07-kubectl | Schema index