diff --git a/conformance/tests/l1-rendering/container-workload-render/chainsaw-test.yaml b/conformance/tests/l1-rendering/container-workload-render/chainsaw-test.yaml new file mode 100644 index 0000000..05418e5 --- /dev/null +++ b/conformance/tests/l1-rendering/container-workload-render/chainsaw-test.yaml @@ -0,0 +1,36 @@ +# ============================================================================= +# L1 RENDERING — kind: container renders the PaaS workload (R-PLAT-02) +# Catalog: docs/13 `kind: container` (PaaS default). The render analog of the +# vm-lifecycle (R-PLAT-03) test for the container path. +# +# GIVEN a kind: container ServiceDefinition, +# WHEN the renderer produces resources, +# THEN it renders a Deployment (the container workload) + a Service, matching the +# golden, with the egress-class + auth-mode labels admission requires. +# +# STATUS: IMPLEMENTED (T-0.3). render-check.sh is a pure offline check (renderer + +# golden diff + structural asserts, no cluster). conformance/run.sh --layer l1 and +# ci/l1.sh run it directly; this Chainsaw Test wraps the SAME script so an in-cluster +# run executes the identical assertion. Rendering != enforcement (behavior at L2). +# ============================================================================= +apiVersion: chainsaw.kyverno.io/v1alpha1 +kind: Test +metadata: + name: container-workload-render + labels: + conformance.home.k3s/R-PLAT-02: "true" + annotations: + conformance.home.k3s/requirements: "R-PLAT-02" + conformance.home.k3s/layer: "L1" + conformance.home.k3s/safety-critical: "false" + conformance.home.k3s/intent: >- + A kind: container ServiceDefinition renders a Deployment + Service (the PaaS path). +spec: + steps: + - name: render-and-assert-container-workload + description: >- + Render a container ServiceDefinition and assert it produces a Deployment running + the declared image plus a Service, with the required pod labels, matching golden. + try: + - script: + content: bash ./render-check.sh diff --git a/conformance/tests/l1-rendering/container-workload-render/fixtures/svcdef.yaml b/conformance/tests/l1-rendering/container-workload-render/fixtures/svcdef.yaml new file mode 100644 index 0000000..261eef4 --- /dev/null +++ b/conformance/tests/l1-rendering/container-workload-render/fixtures/svcdef.yaml @@ -0,0 +1,14 @@ +# L1 fixture: a minimal kind: container ServiceDefinition — the PaaS path (R-PLAT-02). +# It must render a Deployment (the container workload) + a Service for its exposed port. +# Validates against schemas/servicedefinition.schema.json. +name: paas-demo +kind: container +source: + image: nginx:1.27 +ingressRoutes: + - host: paas-demo.home.keithjackson.dev + port: 8080 +auth: + mode: forward-auth + groups: + - platform-admins diff --git a/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml b/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml new file mode 100644 index 0000000..96f9d06 --- /dev/null +++ b/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml @@ -0,0 +1,152 @@ +--- +# Namespace (trust zone; default-deny + policies apply) +apiVersion: v1 +kind: Namespace +metadata: + name: paas-demo + labels: + homelab.keithjackson.dev/app-namespace: 'true' + gateway-access: shared +--- +# Default-deny baseline (R-NET-01): deny all, allow only DNS +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: default-deny + namespace: paas-demo + labels: + homelab.keithjackson.dev/baseline: default-deny +spec: + podSelector: {} + policyTypes: + - Ingress + - Egress + ingress: [] + egress: + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 +--- +# Deployment (R-PLAT-02) +apiVersion: apps/v1 +kind: Deployment +metadata: + name: paas-demo + namespace: paas-demo + labels: + app.kubernetes.io/name: paas-demo +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: paas-demo + template: + metadata: + labels: + app.kubernetes.io/name: paas-demo + homelab.keithjackson.dev/egress-class: internet + homelab.keithjackson.dev/auth-mode: forward-auth + annotations: + homelab.keithjackson.dev/auth-mode: forward-auth + homelab.keithjackson.dev/auth-groups: platform-admins + spec: + containers: + - name: paas-demo + image: nginx:1.27 + ports: + - containerPort: 8080 +--- +# Service +apiVersion: v1 +kind: Service +metadata: + name: paas-demo + namespace: paas-demo + labels: + app.kubernetes.io/name: paas-demo +spec: + selector: + app.kubernetes.io/name: paas-demo + ports: + - name: port-8080 + port: 8080 + targetPort: 8080 +--- +# HTTPRoute paas-demo-8080 (R-TLS/R-DNS) +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: paas-demo-8080 + namespace: paas-demo + labels: + app.kubernetes.io/name: paas-demo + annotations: + homelab.keithjackson.dev/auth-mode: forward-auth + homelab.keithjackson.dev/auth-groups: platform-admins +spec: + parentRefs: + - name: shared + namespace: gateway + sectionName: https + hostnames: + - paas-demo.home.keithjackson.dev + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: paas-demo + port: 8080 +--- +# ServiceMonitor (R-OBS-01) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: paas-demo + namespace: paas-demo + labels: + release: kube-prometheus-stack + app.kubernetes.io/name: paas-demo +spec: + selector: + matchLabels: + app.kubernetes.io/name: paas-demo + namespaceSelector: + matchNames: + - paas-demo + endpoints: + - port: metrics + interval: 30s +--- +# Flux ImageRepository (R-UPD) +apiVersion: image.toolkit.fluxcd.io/v1beta2 +kind: ImageRepository +metadata: + name: paas-demo + namespace: paas-demo +spec: + image: nginx + interval: 1h0m0s +--- +# Flux ImagePolicy (R-UPD) +apiVersion: image.toolkit.fluxcd.io/v1beta2 +kind: ImagePolicy +metadata: + name: paas-demo + namespace: paas-demo + annotations: + homelab.keithjackson.dev/update-channel: stable + homelab.keithjackson.dev/auto-rollback: 'true' +spec: + imageRepositoryRef: + name: paas-demo + policy: + semver: + range: '>=0.0.0' diff --git a/conformance/tests/l1-rendering/container-workload-render/render-check.sh b/conformance/tests/l1-rendering/container-workload-render/render-check.sh new file mode 100755 index 0000000..bbe4cd4 --- /dev/null +++ b/conformance/tests/l1-rendering/container-workload-render/render-check.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# ============================================================================= +# L1 render check (offline, no cluster) — container-workload-render +# Serves: R-PLAT-02 (PaaS: a kind: container ServiceDefinition renders a container +# workload). The render analog of vm-lifecycle (R-PLAT-03) for the container path. +# Run by conformance/run.sh --layer l1 and ci/l1.sh. Pure bash + catalog/render.py. +# +# GIVEN a kind: container ServiceDefinition with an exposed port, +# WHEN the renderer produces resources, +# THEN (1) the render matches the committed golden byte-for-byte, and +# (2) it includes a Deployment running the SD's image plus a Service for the +# exposed port, and the pod carries the egress-class + auth-mode labels the +# platform's Kyverno policies require (a rendered container that admission +# would accept). +# ============================================================================= +set -euo pipefail +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "${DIR}/../../../.." && pwd)" # repo root +RENDER="${ROOT}/catalog/render.py" +PYTHON="${PYTHON:-python3}" + +tmp="$(mktemp -d)" +trap 'rm -rf "${tmp}"' EXIT + +fail() { echo "RENDER-CHECK FAIL (container-workload-render): $*" >&2; exit 1; } + +"${PYTHON}" "${RENDER}" "${DIR}/fixtures/svcdef.yaml" > "${tmp}/out.yaml" + +# (1) golden match. +diff -u "${DIR}/golden/expected.yaml" "${tmp}/out.yaml" || fail "render drifted from golden" + +# (2) structural: a Deployment (image) + Service (port) + the required pod labels. +"${PYTHON}" - "${tmp}/out.yaml" <<'PY' || fail "container render is missing the PaaS workload shape (R-PLAT-02)" +import sys, yaml +docs = [d for d in yaml.safe_load_all(open(sys.argv[1])) if d] +by = {} +for d in docs: + by.setdefault(d.get("kind"), []).append(d) +assert by.get("Deployment"), "expected a Deployment (the container workload)" +dep = by["Deployment"][0] +c = dep["spec"]["template"]["spec"]["containers"][0] +assert c["image"] == "nginx:1.27", f"unexpected image {c['image']!r}" +labels = dep["spec"]["template"]["metadata"]["labels"] +L = "homelab.keithjackson.dev" +assert labels.get(f"{L}/egress-class") in ("internet", "vpn"), "pod missing egress-class label" +assert labels.get(f"{L}/auth-mode"), "pod missing auth-mode label" +assert by.get("Service"), "expected a Service for the exposed port" +ports = [p["port"] for p in by["Service"][0]["spec"]["ports"]] +assert 8080 in ports, f"Service does not expose port 8080 ({ports})" +PY + +echo "render-check OK: container-workload-render (R-PLAT-02)"