Skip to content

Commit adea00a

Browse files
committed
ci: harden test coverage with functional gating, helm test, ct, and multi-version matrix
- Fix install-examples gating (remove continue-on-error) + assert deployment Available - Add helm test connection Pod (tests.enabled) and dunglas/frankenphp smoke job - Adopt chart-testing (ct lint --check-version-increment + ct install) - Matrix kubeconform and kind smoke over k8s 1.27/1.29/1.31 + upgrade regression job - Wire yamllint into CI, normalize example files, bump chart to 0.4.0
1 parent 360b310 commit adea00a

17 files changed

Lines changed: 330 additions & 44 deletions

.github/ct.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# chart-testing (ct) configuration. Docs: https://github.com/helm/chart-testing
2+
target-branch: main
3+
chart-dirs:
4+
- charts
5+
helm-extra-args: --timeout 3m
6+
check-version-increment: true
7+
validate-maintainers: false
8+
validate-chart-schema: true
9+
validate-yaml: true

.github/workflows/ci.yaml

Lines changed: 167 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,21 @@ on:
66
pull_request:
77
branches: [ main ]
88

9+
env:
10+
HELM_VERSION: v3.16.3
11+
KUBECONFORM_VERSION: v0.6.4
12+
913
jobs:
10-
# STAGE 1: Unit Tests & Validation
11-
unit-tests:
14+
# STAGE 1a: Lint + unit tests + yamllint (template-level, no cluster)
15+
lint-unit:
1216
runs-on: ubuntu-latest
1317
steps:
1418
- uses: actions/checkout@v4
15-
19+
1620
- uses: azure/setup-helm@v4
1721
with:
18-
version: v3.16.3
19-
22+
version: ${{ env.HELM_VERSION }}
23+
2024
- name: Lint
2125
run: make lint
2226

@@ -26,61 +30,194 @@ jobs:
2630
- name: Unit Tests
2731
run: make unit-test
2832

33+
- name: Install yamllint
34+
run: pipx install yamllint
35+
36+
- name: Yamllint
37+
run: make yamllint
38+
39+
# STAGE 1b: kubeconform validation across multiple Kubernetes API versions
40+
validate:
41+
runs-on: ubuntu-latest
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
kube_version: [ "1.27.0", "1.29.0", "1.31.0" ]
46+
name: Validate (k8s ${{ matrix.kube_version }})
47+
steps:
48+
- uses: actions/checkout@v4
49+
50+
- uses: azure/setup-helm@v4
51+
with:
52+
version: ${{ env.HELM_VERSION }}
53+
2954
- name: Install Kubeconform
3055
run: |
31-
VERSION=v0.6.4
32-
curl -L https://github.com/yannh/kubeconform/releases/download/$VERSION/kubeconform-linux-amd64.tar.gz | tar xz
56+
curl -L https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz | tar xz
3357
sudo mv kubeconform /usr/local/bin/
3458
3559
- name: Kubeconform Validation
36-
run: make validate
60+
run: make validate KUBE_VERSION=${{ matrix.kube_version }}
3761

38-
# STAGE 2: Examples (Pre-requisite: Unit Tests MUST pass)
62+
# STAGE 2: chart-testing — enforces Chart.yaml version bump + installs ci values
63+
chart-testing:
64+
needs: [ lint-unit, validate ]
65+
runs-on: ubuntu-latest
66+
steps:
67+
- uses: actions/checkout@v4
68+
with:
69+
fetch-depth: 0
70+
71+
- uses: azure/setup-helm@v4
72+
with:
73+
version: ${{ env.HELM_VERSION }}
74+
75+
- uses: actions/setup-python@v5
76+
with:
77+
python-version: "3.x"
78+
79+
- name: Set up chart-testing
80+
uses: helm/chart-testing-action@v2.6.1
81+
82+
- name: Run chart-testing (lint)
83+
run: ct lint --config .github/ct.yaml
84+
85+
- name: Create kind cluster
86+
uses: helm/kind-action@v1.10.0
87+
88+
- name: Run chart-testing (install)
89+
run: ct install --config .github/ct.yaml
90+
91+
# STAGE 3: Build the matrix of example values files
3992
list-examples:
40-
needs: unit-tests
93+
needs: [ lint-unit, validate ]
4194
runs-on: ubuntu-latest
4295
outputs:
4396
matrix: ${{ steps.set-matrix.outputs.matrix }}
4497
steps:
4598
- uses: actions/checkout@v4
4699
- id: set-matrix
47100
run: |
48-
# List all yaml files in examples, get basename, and format as JSON array
49101
EXAMPLES=$(ls examples/*.yaml | xargs -n 1 basename | jq -R -s -c 'split("\n")[:-1]')
50102
echo "matrix=$EXAMPLES" >> $GITHUB_OUTPUT
51103
104+
# STAGE 3 (cont.): install every example on kind and assert it becomes ready.
105+
# NOTE: gating is now real — a failed install or unready workload fails the job.
52106
install-examples:
53-
needs: list-examples
107+
needs: [ list-examples ]
54108
runs-on: ubuntu-latest
55109
strategy:
56110
fail-fast: false
57111
matrix:
58112
example: ${{ fromJson(needs.list-examples.outputs.matrix) }}
59-
name: Test ${{ matrix.example }}
113+
name: Install ${{ matrix.example }}
60114
steps:
61115
- uses: actions/checkout@v4
62-
116+
63117
- uses: azure/setup-helm@v4
64118
with:
65-
version: v3.16.3
66-
119+
version: ${{ env.HELM_VERSION }}
120+
67121
- name: Create kind cluster
68-
uses: helm/kind-action@v1.9.0
69-
70-
- name: Test Example
71-
continue-on-error: true
122+
uses: helm/kind-action@v1.10.0
123+
124+
- name: Install and verify example
72125
run: |
126+
set -euo pipefail
73127
file="examples/${{ matrix.example }}"
74-
echo "Testing installation with $file..."
75-
76128
RELEASE_NAME="test-$(echo ${{ matrix.example }} | sed 's/\.yaml//' | tr '[:upper:]' '[:lower:]' | tr -cd '[:alnum:]-')"
77129
NS="$RELEASE_NAME-ns"
78-
79-
if helm install "$RELEASE_NAME" ./charts/frankenphp -f "$file" --create-namespace --namespace "$NS" --wait --timeout 3m; then
80-
echo "✅ Installation successful for $file"
81-
else
82-
echo "❌ Installation failed for $file"
83-
kubectl describe pods -n "$NS"
84-
kubectl logs -l app.kubernetes.io/name=frankenphp -n "$NS" --all-containers --tail=20
85-
exit 1
130+
echo "Installing $file as $RELEASE_NAME in $NS"
131+
132+
if ! helm install "$RELEASE_NAME" ./charts/frankenphp -f "$file" \
133+
--create-namespace --namespace "$NS" --wait --timeout 3m; then
134+
echo "::error::helm install failed for $file"
135+
kubectl describe pods -n "$NS" || true
136+
kubectl logs -l app.kubernetes.io/instance="$RELEASE_NAME" -n "$NS" --all-containers --tail=50 || true
137+
exit 1
86138
fi
139+
140+
echo "Asserting main Deployment is Available"
141+
if ! kubectl wait --for=condition=Available deployment \
142+
-l app.kubernetes.io/instance="$RELEASE_NAME" -n "$NS" --timeout=120s; then
143+
echo "::error::deployment not Available for $file"
144+
kubectl describe pods -n "$NS" || true
145+
kubectl logs -l app.kubernetes.io/instance="$RELEASE_NAME" -n "$NS" --all-containers --tail=50 || true
146+
exit 1
147+
fi
148+
echo "✅ $file installed and ready"
149+
150+
# STAGE 4: live HTTP smoke via `helm test`, across multiple Kubernetes versions
151+
smoke:
152+
needs: [ lint-unit, validate ]
153+
runs-on: ubuntu-latest
154+
strategy:
155+
fail-fast: false
156+
matrix:
157+
node_image:
158+
- kindest/node:v1.27.13
159+
- kindest/node:v1.29.8
160+
- kindest/node:v1.31.0
161+
name: Smoke (${{ matrix.node_image }})
162+
steps:
163+
- uses: actions/checkout@v4
164+
165+
- uses: azure/setup-helm@v4
166+
with:
167+
version: ${{ env.HELM_VERSION }}
168+
169+
- name: Create kind cluster
170+
uses: helm/kind-action@v1.10.0
171+
with:
172+
node_image: ${{ matrix.node_image }}
173+
174+
- name: Install smoke release
175+
run: |
176+
helm install smoke ./charts/frankenphp -f charts/frankenphp/ci/smoke-values.yaml \
177+
--create-namespace --namespace smoke --wait --timeout 3m
178+
179+
- name: helm test (HTTP smoke)
180+
run: helm test smoke --namespace smoke --logs
181+
182+
- name: Diagnostics on failure
183+
if: failure()
184+
run: |
185+
kubectl describe pods -n smoke || true
186+
kubectl logs -l app.kubernetes.io/instance=smoke -n smoke --all-containers --tail=50 || true
187+
188+
# STAGE 5: upgrade regression — install the latest released chart, then upgrade to this PR
189+
upgrade:
190+
needs: [ lint-unit, validate ]
191+
runs-on: ubuntu-latest
192+
steps:
193+
- uses: actions/checkout@v4
194+
195+
- uses: azure/setup-helm@v4
196+
with:
197+
version: ${{ env.HELM_VERSION }}
198+
199+
- name: Create kind cluster
200+
uses: helm/kind-action@v1.10.0
201+
202+
- name: Install latest released chart
203+
id: install-old
204+
continue-on-error: true
205+
run: |
206+
helm repo add frankenphp https://fabienpapet.github.io/helm-frankenphp/
207+
helm repo update
208+
helm install upg frankenphp/frankenphp \
209+
--set image.repository=dunglas/frankenphp --set image.tag=1-php8.3 \
210+
--create-namespace --namespace upg --wait --timeout 3m
211+
212+
- name: Upgrade to PR chart
213+
if: steps.install-old.outcome == 'success'
214+
run: |
215+
helm upgrade upg ./charts/frankenphp \
216+
--set image.repository=dunglas/frankenphp --set image.tag=1-php8.3 \
217+
--namespace upg --wait --timeout 3m
218+
kubectl wait --for=condition=Available deployment \
219+
-l app.kubernetes.io/instance=upg -n upg --timeout=120s
220+
221+
- name: Skip note
222+
if: steps.install-old.outcome != 'success'
223+
run: echo "No released chart available yet — skipping upgrade regression."

.yamllint

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
extends: default
2+
3+
rules:
4+
line-length:
5+
max: 160
6+
level: warning
7+
comments:
8+
min-spaces-from-content: 1
9+
truthy:
10+
allowed-values: ['true', 'false']
11+
check-keys: false
12+
indentation:
13+
spaces: 2
14+
indent-sequences: consistent
15+
brackets:
16+
min-spaces-inside: 0
17+
max-spaces-inside: 1
18+
braces:
19+
min-spaces-inside: 0
20+
max-spaces-inside: 1
21+
22+
ignore: |
23+
charts/frankenphp/templates/
24+
charts/frankenphp/tests/

Makefile

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
.PHONY: unit-test lint validate install-kubeconform
1+
.PHONY: unit-test lint yamllint validate helm-test install-kubeconform
2+
3+
# Kubernetes API version kubeconform validates examples against. Override on the CLI:
4+
# make validate KUBE_VERSION=1.31.0
5+
KUBE_VERSION ?= 1.29.0
26

37
unit-test:
48
cd charts/frankenphp; helm unittest .
59

610
lint:
711
helm lint charts/frankenphp
812

13+
yamllint:
14+
yamllint examples/ docs/ -c .yamllint
15+
916
install-kubeconform:
1017
@VERSION=v0.6.4; \
1118
OS=$$(uname | tr '[:upper:]' '[:lower:]'); \
@@ -21,5 +28,12 @@ install-kubeconform:
2128
validate:
2229
@for file in examples/*.yaml; do \
2330
echo "Validating $$file with kubeconform..."; \
24-
helm template frankenphp ./charts/frankenphp -f $$file | kubeconform -summary -strict -ignore-missing-schemas -kubernetes-version 1.29.0 || exit 1; \
31+
helm template frankenphp ./charts/frankenphp -f $$file | kubeconform -summary -strict -ignore-missing-schemas -kubernetes-version $(KUBE_VERSION) || exit 1; \
2532
done
33+
34+
# Install the chart with the CI smoke values and run `helm test` against a running cluster.
35+
helm-test:
36+
helm install smoke ./charts/frankenphp -f charts/frankenphp/ci/smoke-values.yaml \
37+
--create-namespace --namespace smoke --wait --timeout 3m
38+
helm test smoke --namespace smoke --logs
39+
helm uninstall smoke --namespace smoke

charts/frankenphp/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ type: application
2929
# This is the chart version. This version number should be incremented each time you make changes
3030
# to the chart and its templates, including the app version.
3131
# Versions are expected to follow Semantic Versioning (https://semver.org/)
32-
version: 0.3.0
32+
version: 0.4.0
3333

3434
# This is the version number of the application being deployed. This version number should be
3535
# incremented each time you make changes to the application. Versions are not expected to
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# CI smoke values: minimal runnable config used by the `smoke` job and chart-testing.
2+
# Uses the upstream FrankenPHP base image so `helm test` can prove the app serves.
3+
image:
4+
repository: dunglas/frankenphp
5+
tag: "1-php8.3"
6+
pullPolicy: IfNotPresent
7+
8+
deployment:
9+
replicas: 1
10+
11+
resources:
12+
requests:
13+
cpu: "100m"
14+
memory: "128Mi"
15+
limits:
16+
memory: "256Mi"
17+
18+
readinessProbe:
19+
tcpSocket:
20+
port: http
21+
initialDelaySeconds: 5
22+
periodSeconds: 5
23+
failureThreshold: 6
24+
25+
tests:
26+
enabled: true
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{{- if (.Values.tests).enabled }}
2+
apiVersion: v1
3+
kind: Pod
4+
metadata:
5+
name: "{{ include "helm-frankenphp.fullname" . }}-test-connection"
6+
labels:
7+
{{- include "helm-frankenphp.labels" . | nindent 4 }}
8+
annotations:
9+
"helm.sh/hook": test
10+
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
11+
spec:
12+
restartPolicy: Never
13+
containers:
14+
- name: test-connection
15+
image: curlimages/curl:8.11.0
16+
command:
17+
- /bin/sh
18+
- -c
19+
- |
20+
set -e
21+
# A connection refusal/timeout fails the test. Any HTTP response
22+
# (including 404) means FrankenPHP/Caddy is serving, which is success.
23+
curl -sf -o /dev/null --retry 10 --retry-all-errors --retry-delay 3 \
24+
"http://{{ include "helm-frankenphp.fullname" . }}:{{ .Values.service.port }}/" \
25+
|| curl -s -o /dev/null --retry 10 --retry-all-errors --retry-delay 3 \
26+
"http://{{ include "helm-frankenphp.fullname" . }}:{{ .Values.service.port }}/"
27+
echo "OK: {{ include "helm-frankenphp.fullname" . }} is serving on port {{ .Values.service.port }}"
28+
{{- end }}

0 commit comments

Comments
 (0)