From bcc280ed48451a18569a6cb864a8104c00019db0 Mon Sep 17 00:00:00 2001 From: Eric Hibbs Date: Wed, 24 Jun 2026 12:18:12 -0700 Subject: [PATCH] Add chart validation CI + helm-unittest suite Adds .github/workflows/validate.yaml: on PRs touching helm/**, runs helm lint, helm template | kubeconform -strict (default + every example values file), and helm unittest. Gates the release.yaml publish, which previously fired on a Chart.yaml version bump with no pre-publish validation. Adds helm/tests/ unittest suites asserting pass-through plumbing: scheduling fields (topologySpreadConstraints, affinity, tolerations, nodeSelector) and upstream-token env wiring. CE-272. Co-authored-by: Cursor --- .github/workflows/validate.yaml | 67 +++++++++++++++++++++++ helm/tests/scheduling_test.yaml | 79 ++++++++++++++++++++++++++++ helm/tests/upstream_tokens_test.yaml | 44 ++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 .github/workflows/validate.yaml create mode 100644 helm/tests/scheduling_test.yaml create mode 100644 helm/tests/upstream_tokens_test.yaml diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml new file mode 100644 index 0000000..ff61791 --- /dev/null +++ b/.github/workflows/validate.yaml @@ -0,0 +1,67 @@ +name: Validate Chart + +# Pre-publish validation for the Helm chart. Runs on PRs (and manually) that +# touch helm/**, gating the release.yaml publish (which fires on a Chart.yaml +# version bump). Catches render/schema regressions and asserts the chart's +# pass-through plumbing via helm-unittest before anything reaches gh-pages. + +on: + pull_request: + paths: ['helm/**', '.github/workflows/validate.yaml'] + workflow_dispatch: {} + +permissions: + contents: read + +env: + KUBECONFORM_VERSION: v0.8.0 + HELM_UNITTEST_VERSION: v1.1.1 + # Kubernetes API version to validate rendered manifests against. The chart's + # implicit floor is ~1.23 (autoscaling/v2); validate against a current stable. + KUBE_VERSION: "1.30.0" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false + + - name: Install Helm + uses: azure/setup-helm@1a275c3b69536ee54be43f2070a358922e12c8d4 # v4 + + - name: Install kubeconform + run: | + set -euo pipefail + curl -sSfL "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" -o kubeconform.tar.gz + tar -xzf kubeconform.tar.gz kubeconform + sudo install kubeconform /usr/local/bin/kubeconform + kubeconform -v + + - name: Helm lint + run: helm lint helm + + - name: Helm template + kubeconform (default + examples) + run: | + set -euo pipefail + # Validate the default render plus every example values file so we + # exercise the path-routing, DNS-override, and forward-proxy branches. + render_and_validate() { + local name="$1"; shift + echo "::group::kubeconform: ${name}" + helm template release helm --set socket.apiToken=ci-dummy "$@" \ + | kubeconform -strict -summary -kubernetes-version "${KUBE_VERSION}" -schema-location default + echo "::endgroup::" + } + render_and_validate "default" + for f in helm/examples/*.yaml; do + render_and_validate "$(basename "$f")" -f "$f" + done + + - name: Install helm-unittest + run: helm plugin install https://github.com/helm-unittest/helm-unittest --version "${HELM_UNITTEST_VERSION}" --verify=false + + - name: Helm unittest + run: helm unittest helm diff --git a/helm/tests/scheduling_test.yaml b/helm/tests/scheduling_test.yaml new file mode 100644 index 0000000..25b5d93 --- /dev/null +++ b/helm/tests/scheduling_test.yaml @@ -0,0 +1,79 @@ +suite: pod scheduling pass-through +# Verifies the optional scheduling fields are pure pass-throughs: absent when +# unset (no behavior change for existing installs) and rendered verbatim when set. +# configmap.yaml is listed at suite level because deployment.yaml's +# checksum/config annotation includes it; each test scopes its assertions to the +# Deployment via the test-level `template` field. +templates: + - templates/deployment.yaml + - templates/configmap.yaml +release: + name: release +tests: + - it: omits scheduling fields by default + template: templates/deployment.yaml + set: + socket.apiToken: ci-dummy + asserts: + - notExists: + path: spec.template.spec.topologySpreadConstraints + - notExists: + path: spec.template.spec.affinity + - notExists: + path: spec.template.spec.nodeSelector + - notExists: + path: spec.template.spec.tolerations + + - it: renders topologySpreadConstraints when set + template: templates/deployment.yaml + set: + socket.apiToken: ci-dummy + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/name: socket-firewall + asserts: + - exists: + path: spec.template.spec.topologySpreadConstraints + - lengthEqual: + path: spec.template.spec.topologySpreadConstraints + count: 1 + - equal: + path: spec.template.spec.topologySpreadConstraints[0].maxSkew + value: 1 + - equal: + path: spec.template.spec.topologySpreadConstraints[0].topologyKey + value: topology.kubernetes.io/zone + - equal: + path: spec.template.spec.topologySpreadConstraints[0].whenUnsatisfiable + value: ScheduleAnyway + + - it: renders affinity and tolerations when set + template: templates/deployment.yaml + set: + socket.apiToken: ci-dummy + affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: disktype + operator: In + values: [ssd] + tolerations: + - key: dedicated + operator: Equal + value: firewall + effect: NoSchedule + asserts: + - exists: + path: spec.template.spec.affinity + - equal: + path: spec.template.spec.tolerations[0].key + value: dedicated + - equal: + path: spec.template.spec.tolerations[0].effect + value: NoSchedule diff --git a/helm/tests/upstream_tokens_test.yaml b/helm/tests/upstream_tokens_test.yaml new file mode 100644 index 0000000..e2922b3 --- /dev/null +++ b/helm/tests/upstream_tokens_test.yaml @@ -0,0 +1,44 @@ +suite: upstream token env wiring +# Verifies that a path route's upstreamToken produces a container env var backed +# by a secretKeyRef into the chart-provisioned upstream-tokens secret. +# configmap.yaml is listed because deployment.yaml's checksum/config annotation +# includes it; documentSelector scopes the assertions to the Deployment. +templates: + - templates/deployment.yaml + - templates/configmap.yaml +release: + name: release +tests: + - it: does not wire upstream token env by default + template: templates/deployment.yaml + set: + socket.apiToken: ci-dummy + asserts: + - notContains: + path: spec.template.spec.containers[0].env + content: + name: NPM_AUTH_TOKEN + any: true + + - it: wires upstream token env from a path route + template: templates/deployment.yaml + set: + socket.apiToken: ci-dummy + pathRouting.enabled: true + pathRouting.domain: sfw.example.com + pathRouting.routes: + - path: /npm + upstream: https://registry.npmjs.org + registry: npm + upstreamToken: NPM_AUTH_TOKEN + upstreamTokens.values: + NPM_AUTH_TOKEN: "npm_xxx" + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: NPM_AUTH_TOKEN + valueFrom: + secretKeyRef: + name: release-socket-firewall-upstream-tokens + key: NPM_AUTH_TOKEN