Repository navigation
🐛 fix(portal): drop sign-in-page:app disable + repin image sha-4e… #113
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Chainsaw CI (enabled after E3) — L2 declarative cluster e2e on kind. | |
| # | |
| # CI SUBSTRATE PARITY (2026-07-16): the CI cluster is built with the SAME recipe | |
| # as the live kaddy-dev substrate — hack/cluster/kind-up.sh (kind config with | |
| # disableDefaultCNI:true + kubeProxyMode:none, Cilium + Gateway API CRDs + | |
| # LB-IPAM, cert-manager + kaddy-local-ca, all pinned via | |
| # hack/cluster/versions.env). That un-skips the Cilium/Gateway/Rollouts/local-CA | |
| # dependent suites (gateway, security netpol, rollouts, tls certificate-renewal) | |
| # so the platform's headline behaviors are enforced regression here, not | |
| # live-only proofs. | |
| name: chainsaw | |
| on: | |
| # Runs on push to main (local-merge workflow — no PR required; L2 gate lands | |
| # in parallel and we fix-forward if a later commit trips it) AND on PRs. | |
| push: | |
| branches: [main] | |
| paths: | |
| - 'deploy/**' | |
| - 'tests/chainsaw/**' | |
| - 'hack/cluster/**' | |
| - 'Taskfile.yml' | |
| - '.github/workflows/chainsaw.yaml' | |
| pull_request: | |
| paths: | |
| - 'deploy/**' | |
| - 'tests/chainsaw/**' | |
| - 'hack/cluster/**' | |
| - 'Taskfile.yml' | |
| - '.github/workflows/chainsaw.yaml' | |
| permissions: | |
| contents: read | |
| jobs: | |
| chainsaw: | |
| runs-on: ubuntu-latest | |
| # Substrate bring-up (Cilium + observability + rollouts + workloads) plus the | |
| # full suite run budgets ~20 min; hard-stop well under the 6h default so a | |
| # wedged install fails fast. | |
| timeout-minutes: 35 | |
| steps: | |
| # SEC-5: all action refs pinned to full commit SHAs (version as comment), | |
| # Renovate-compatible (github-actions manager updates sha + comment). | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 | |
| - name: Install Task | |
| uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2.0.0 | |
| with: | |
| version: 3.x | |
| repo-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Set up Go | |
| uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5.6.0 | |
| with: | |
| go-version: stable | |
| - name: Install Chainsaw | |
| # SEC-4: pin to an exact tag (was @latest) so builds are reproducible | |
| # and Renovate can track the bump. | |
| run: go install github.com/kyverno/chainsaw@v0.2.15 | |
| - name: Install kind (pinned) | |
| # The cluster itself is created by hack/cluster/kind-up.sh (parity with | |
| # the live substrate) — helm/kind-action is gone. Only the kind BINARY | |
| # is provisioned here; kubectl, helm and jq are runner-preinstalled. | |
| # SEC-4: exact version, paired with KIND_NODE_IMAGE v1.33.1 | |
| # (hack/cluster/versions.env). | |
| run: | | |
| KIND_VERSION="v0.29.0" | |
| sudo curl -sSL -o /usr/local/bin/kind \ | |
| "https://kind.sigs.k8s.io/dl/${KIND_VERSION}/kind-linux-amd64" | |
| sudo chmod +x /usr/local/bin/kind | |
| - name: Raise inotify limits (kind on a shared runner) | |
| # The parity substrate runs many watch-heavy pods (Cilium, kps, Loki, | |
| # Alloy, Kyverno, Rollouts) on one kind node; the runner's default | |
| # inotify limits cause spurious "too many open files" crash-loops. | |
| run: | | |
| sudo sysctl -w fs.inotify.max_user_watches=524288 | |
| sudo sysctl -w fs.inotify.max_user_instances=512 | |
| - name: Create kind cluster (live substrate recipe — Cilium + Gateway API + LB-IPAM + cert-manager + kaddy-local-ca) | |
| # REUSES hack/cluster/kind-up.sh — the exact scripts + versions.env pins | |
| # the live kaddy-dev cluster is built from (that's the parity point). | |
| # On the runner: docker is rootful (podman guards no-op), the loopback | |
| # 30080/30443 extraPortMappings are free, and the LB-IPAM pool is carved | |
| # from the real docker "kind" bridge subnet by install-cilium.sh. | |
| run: | | |
| bash hack/cluster/kind-up.sh | |
| # hack/cluster isolates its kubeconfig at .state/kubeconfig (never the | |
| # runner default) — export it for every following step. Taskfile's | |
| # KUBECONFIG default points at the same file. | |
| echo "KUBECONFIG=${GITHUB_WORKSPACE}/.state/kubeconfig" >> "$GITHUB_ENV" | |
| # Suites still skipped here are gated by `skip: true` until their epic | |
| # lands (identity→E1d, portal→E10, caddy-mvp→e-caddy-mvp, crossplane→needs | |
| # Crossplane in CI, unsigned-image→cosign Enforce flip). Each epic's | |
| # implementation installs its deps here and flips its suite's skip off. | |
| - name: Install Kyverno (labeling/security suites) | |
| run: | | |
| if ls tests/chainsaw/labeling/*.yaml tests/chainsaw/security/*.yaml >/dev/null 2>&1; then | |
| # E1c: the labeling suite applies its own ClusterPolicies, so this | |
| # install must actually succeed — no failure-swallowing || true | |
| # (TEST-8), and the same vendored+pinned manifest the live cluster | |
| # syncs (deploy/kyverno/, v1.18.2). Server-side apply: the CRDs | |
| # exceed client-side annotation limits. | |
| kubectl apply --server-side -f deploy/kyverno/install.yaml | |
| kubectl -n kyverno rollout status deploy/kyverno-admission-controller --timeout=300s | |
| fi | |
| # E3: cert-manager + the kaddy-local-ca ClusterIssuer are already installed | |
| # by kind-up.sh (hack/cluster/install-cert-manager.sh, pinned | |
| # CERT_MANAGER_VERSION) — only the ACME issuer is applied on top. | |
| - name: Apply ACME staging issuer (tls suites) | |
| if: ${{ hashFiles('tests/chainsaw/tls/*.yaml') != '' }} | |
| run: | | |
| # Only the STAGING issuer is exercised by the tls suite. Applying the | |
| # prod issuer too would register an account against the Let's Encrypt | |
| # PRODUCTION ACME API on every PR run — unnecessary prod-API traffic — | |
| # so CI installs staging only. (Live/kind bootstrap applies both.) | |
| kubectl apply -f deploy/cert-manager/cluster-issuer-staging.yaml | |
| # E3: install the observability stack (monitoring suites) directly with Helm, | |
| # reusing the SAME pinned chart versions + values embedded in the Argo CD | |
| # Application manifests (extracted with yq) so CI mirrors the GitOps source of | |
| # truth without standing up Argo CD in the ephemeral cluster. | |
| - name: Install observability stack (monitoring suites) | |
| if: ${{ hashFiles('tests/chainsaw/monitoring/*.yaml') != '' }} | |
| run: | | |
| set -euo pipefail | |
| if ! command -v yq >/dev/null 2>&1; then | |
| # SEC-4: pin to an exact release (was releases/latest) so the install | |
| # is reproducible and Renovate can track the bump. | |
| YQ_VERSION="v4.44.3" | |
| sudo wget -qO /usr/local/bin/yq \ | |
| "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" | |
| sudo chmod +x /usr/local/bin/yq | |
| fi | |
| helm repo add prometheus-community https://prometheus-community.github.io/helm-charts | |
| helm repo add grafana https://grafana.github.io/helm-charts | |
| helm repo update | |
| kubectl create namespace monitoring --dry-run=client -o yaml | kubectl apply -f - | |
| # E1c: kps values point Grafana at grafana.admin.existingSecret | |
| # (monitoring/grafana-admin). The live cluster gets it from | |
| # `task bootstrap:e1c`; the ephemeral CI cluster must create it too or | |
| # Grafana never turns Ready and `helm --wait` times out. | |
| kubectl -n monitoring create secret generic grafana-admin \ | |
| --from-literal=admin-user=admin \ | |
| --from-literal=admin-password="$(openssl rand -base64 24)" \ | |
| --dry-run=client -o yaml | kubectl apply -f - | |
| install_app() { | |
| local file="$1" | |
| local repo chart ver | |
| repo="$(yq e '.spec.source.repoURL' "$file")" | |
| chart="$(yq e '.spec.source.chart' "$file")" | |
| ver="$(yq e '.spec.source.targetRevision' "$file")" | |
| yq e '.spec.source.helm.valuesObject' "$file" > /tmp/vals.yaml | |
| helm upgrade --install "$chart" "$chart" --repo "$repo" \ | |
| --version "$ver" -n monitoring -f /tmp/vals.yaml --wait --timeout 8m | |
| } | |
| install_app deploy/observability/kube-prometheus-stack.yaml | |
| install_app deploy/observability/loki.yaml | |
| install_app deploy/observability/alloy.yaml | |
| # The Grafana Loki datasource ConfigMap is a plain manifest (not Helm). | |
| kubectl apply -f deploy/observability/grafana-loki-datasource.yaml | |
| # E7: Argo Rollouts controller + the Gateway API trafficRouting plugin — | |
| # same vendored+pinned manifests the live cluster syncs (deploy/rollouts/). | |
| - name: Install Argo Rollouts + Gateway API plugin (rollouts suites) | |
| if: ${{ hashFiles('tests/chainsaw/rollouts/*.yaml') != '' }} | |
| run: | | |
| set -euo pipefail | |
| kubectl apply -f deploy/rollouts/namespace.yaml | |
| # ARCH NOTE: deploy/rollouts/config.yaml pins the plugin binary for the | |
| # LIVE kind node arch (linux-arm64 — Apple-silicon podman). GitHub | |
| # runners are linux-amd64, so rewrite ONLY the arch suffix of the same | |
| # pinned v0.16.0 release asset at apply time. The live manifest stays | |
| # byte-identical; no floating tag is introduced (SEC-4). | |
| sed 's#gatewayapi-plugin-linux-arm64#gatewayapi-plugin-linux-amd64#' \ | |
| deploy/rollouts/config.yaml | kubectl apply -f - | |
| # Server-side apply: the Rollout CRD exceeds client-side annotation | |
| # limits. -n argo-rollouts: the vendored upstream install.yaml carries | |
| # NO namespace fields (live, the Argo CD Application destination | |
| # supplies it — deploy/apps/rollouts.yaml). The plugin ConfigMap is | |
| # applied BEFORE the controller starts, so it is loaded on first boot — | |
| # no `rollout restart` needed (live needs one because Argo CD may sync | |
| # the CM after the controller). | |
| kubectl apply --server-side -n argo-rollouts -f deploy/rollouts/install.yaml | |
| kubectl -n argo-rollouts rollout status deploy/argo-rollouts --timeout=300s | |
| # E4/E7: the platform edge + demo workloads the un-skipped suites assert | |
| # against — applied from the SAME GitOps manifests the live cluster syncs. | |
| - name: Apply platform edge + demo workloads (gateway/tls/security/rollouts suites) | |
| if: ${{ hashFiles('tests/chainsaw/gateway/*.yaml', 'tests/chainsaw/security/*.yaml', 'tests/chainsaw/tls/*.yaml', 'tests/chainsaw/rollouts/*.yaml') != '' }} | |
| run: | | |
| set -euo pipefail | |
| # E4 edge: gateway ns + Gateway + HTTPRoutes + clubhouse app + the | |
| # kaddy-local-ca-issued Certificate (gateway + tls + security suites). | |
| # Namespace FIRST: `kubectl apply -f <dir>` walks files alphabetically, | |
| # so namespace.yaml would otherwise land after the resources it holds. | |
| kubectl apply -f deploy/gateway/namespace.yaml | |
| kubectl apply -f deploy/gateway/ | |
| kubectl apply -f deploy/workloads/clubhouse/ | |
| kubectl apply -f deploy/cert-manager/clubhouse-certificate.yaml | |
| kubectl -n gateway rollout status deploy/clubhouse --timeout=300s | |
| kubectl -n gateway wait --for=condition=Ready certificate/clubhouse-tls --timeout=180s | |
| kubectl -n gateway wait --for=condition=Programmed gateway/clubhouse --timeout=180s | |
| # E7 mulligan progressive-delivery demo (needs the Rollouts CRDs above). | |
| if ls tests/chainsaw/rollouts/*.yaml >/dev/null 2>&1; then | |
| kubectl apply -f deploy/workloads/mulligan/namespace.yaml | |
| kubectl apply -f deploy/workloads/mulligan/ | |
| kubectl -n mulligan wait --for=condition=Programmed gateway/mulligan --timeout=180s | |
| # Both Rollouts must be Healthy before the suites patch/abort them. | |
| kubectl -n mulligan wait --for=jsonpath='{.status.phase}'=Healthy \ | |
| rollout/mulligan rollout/mulligan-bg --timeout=300s | |
| fi | |
| # E1c: the default-deny NetworkPolicy baseline (security suites) — applied | |
| # AFTER the stacks are up, mirroring the live cutover order. Cilium (the CI | |
| # CNI since parity) actually enforces these. | |
| - name: Apply default-deny network baseline (security suites) | |
| if: ${{ hashFiles('tests/chainsaw/security/*.yaml') != '' }} | |
| run: | | |
| set -euo pipefail | |
| # No Argo CD runs in the ephemeral CI cluster; create the bare argocd | |
| # namespace so its namespace-wide deny floor applies and the | |
| # default-deny suite asserts the same namespace set as live. | |
| kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f - | |
| kubectl apply -f deploy/policies/network/gateway.yaml | |
| kubectl apply -f deploy/policies/network/monitoring.yaml | |
| kubectl apply -f deploy/policies/network/argocd.yaml | |
| # websites.yaml is NOT applied: the websites namespace belongs to the | |
| # Crossplane suite, which stays live-only (no Crossplane in CI). | |
| - name: Run Chainsaw suites (default discovery — chainsaw-test.yaml) | |
| run: task test:chainsaw | |
| - name: Run per-scenario Chainsaw suites (non-default file names) | |
| # LATENT-GAP FIX (found by this lane): `chainsaw test <dir>` discovers | |
| # ONLY files named chainsaw-test.yaml, so the per-scenario suites | |
| # (gateway/security/monitoring/rollouts/tls/crossplane — one file per | |
| # scenario, the tests/chainsaw/README authoring style) were NEVER | |
| # executed by `task test:chainsaw`, in CI or anywhere. Run every | |
| # Test-bearing file explicitly — the same invocation each suite's | |
| # live-verification annotation documents. Sequential by construction | |
| # (the rollouts suites mutate shared mulligan objects). skip:true | |
| # suites still skip. Follow-up: fold this into the Taskfile target. | |
| run: | | |
| set -euo pipefail | |
| grep -l '^kind: Test$' \ | |
| tests/chainsaw/{gateway,security,monitoring,rollouts,tls,crossplane}/*.yaml \ | |
| | sort | while read -r f; do | |
| echo "::group::chainsaw ${f}" | |
| chainsaw test --test-file "$(basename "$f")" "$(dirname "$f")" | |
| echo "::endgroup::" | |
| done |