-
Notifications
You must be signed in to change notification settings - Fork 6
231 lines (203 loc) · 8.77 KB
/
Copy pathtest.yaml
File metadata and controls
231 lines (203 loc) · 8.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
name: Test Deployment
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
unit:
name: unit
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Set up isolated Python env
run: |
uv venv .venv-unit --python 3.10
# Pinned versions the hub image runs against — tests exercise
# the real oauthenticator + jupyterhub contracts, not a stub.
uv pip install --python .venv-unit/bin/python \
jupyterhub==5.5.0 \
oauthenticator==17.4.0 \
jupyterhub-kubespawner==7.0.0 \
pytest
- name: pytest tests/unit
run: .venv-unit/bin/python -m pytest tests/unit -v --color=yes
# Two roles for prep:
# 1. Cache the multi-GB singleuser image tar so every matrix leg
# restores it from cache instead of paying the docker-pull cost.
# Key by the chart-rendered image ref so values.yaml bumps
# invalidate automatically.
# 2. Collect the list of pytest node IDs from tests/e2e/ and emit
# it as a JSON array the matrix consumes. Adding/removing tests
# flows through the matrix automatically with no workflow edits.
prep:
name: prep (image + test matrix)
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
ref: ${{ steps.img.outputs.ref }}
tar: ${{ steps.img.outputs.tar }}
tests: ${{ steps.collect.outputs.tests }}
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Resolve singleuser image ref
id: img
run: |
REF=$(yq -r '.jupyterhub.singleuser.image | .name + ":" + .tag' values.yaml)
echo "ref=$REF" >> "$GITHUB_OUTPUT"
echo "tar=/tmp/singleuser.tar" >> "$GITHUB_OUTPUT"
- name: Cache singleuser image
id: img-cache
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ steps.img.outputs.tar }}
key: singleuser-${{ steps.img.outputs.ref }}
- name: Pull + save singleuser image (cache miss only)
if: steps.img-cache.outputs.cache-hit != 'true'
run: |
docker pull "${{ steps.img.outputs.ref }}"
docker save "${{ steps.img.outputs.ref }}" -o "${{ steps.img.outputs.tar }}"
- name: Collect e2e test node IDs into a JSON array
id: collect
run: |
# Emit `[{"id":"<short>","path":"<full pytest node id>"}, …]`.
# `id` becomes the matrix label so the GitHub UI groups the
# legs as `e2e / <short>` instead of the full file path
# noise. `path` is what pytest needs to run the test.
# tests/e2e/pytest.ini sets rootdir to tests/e2e, so the
# collected IDs come without the prefix; add it back.
uvx pytest tests/e2e --collect-only -q 2>/dev/null \
| grep -E '^test_.*::' \
| while IFS= read -r node; do
# node = test_FILE.py::test_FN[param]
# short = FN[param] (or FN if not parametrized)
short=$(echo "$node" | sed -E 's|^test_[^.]+\.py::test_||')
jq -n --arg id "$short" --arg path "tests/e2e/$node" \
'{id: $id, path: $path}'
done \
| jq -s -c . > /tmp/tests.json
echo "tests=$(cat /tmp/tests.json)" >> "$GITHUB_OUTPUT"
echo "Collected:"
cat /tmp/tests.json | jq -r '.[] | "\(.id) \t\(.path)"'
# Matrix-per-test e2e: each leg gets its own kind cluster, helm
# install, and runs ONE pytest node id. Wall time = max(leg) instead
# of sum(tests). Adding a test is free — prep job's collection adds
# a matrix leg automatically.
e2e:
name: e2e / ${{ matrix.test.id }}
runs-on: ubuntu-latest
needs: prep
timeout-minutes: 10
strategy:
fail-fast: false
max-parallel: 10
matrix:
test: ${{ fromJSON(needs.prep.outputs.tests) }}
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Install kind
uses: helm/kind-action@ef37e7f390d99f746eb8b610417061a60e82a6cc # v1.14.0
with:
version: v0.27.0
install_only: true
- name: Install Helm
run: curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Restore singleuser image from cache
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ${{ needs.prep.outputs.tar }}
key: singleuser-${{ needs.prep.outputs.ref }}
fail-on-cache-miss: true
# Sanitise the pytest node id into a kind-cluster name: kind
# names are DNS-1123 (lowercase alnum + dashes) and max 38 chars.
# SHA1-truncate so duplicates can't collide.
- name: Derive cluster name
id: cluster
run: |
SAFE=$(echo '${{ matrix.test.path }}' | sha1sum | cut -c1-12)
echo "name=nbe2e-${SAFE}" >> "$GITHUB_OUTPUT"
- name: Create kind cluster
run: kind create cluster --name "${{ steps.cluster.outputs.name }}" --wait 60s
- name: Side-load singleuser image into kind node
run: kind load image-archive "${{ needs.prep.outputs.tar }}" --name "${{ steps.cluster.outputs.name }}"
- name: Run single test
env:
KIND_CLUSTER: ${{ steps.cluster.outputs.name }}
KIND_KEEP: "1"
PYTHONUNBUFFERED: "1"
run: uvx pytest "${{ matrix.test.path }}" -v --color=yes
- name: Hub logs on failure
if: failure()
run: kubectl logs -l component=hub --tail=200 || true
# Integration tests for files/keycloak_rbac_bootstrap.py against a
# real Keycloak. The bootstrap is the chart's only piece of Python
# that talks to Keycloak directly — KC version skew or payload
# changes would break it silently in prod. nebari-dev/action-nebari
# -sandbox spins up NIC's foundational stack (Keycloak + nebari realm
# included) in ~3 min on a vanilla runner; the tests then run against
# that live KC via a port-forward.
integration-rbac-bootstrap:
name: integration / rbac-bootstrap
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Provision Nebari sandbox (kind + NIC platform stack)
id: sandbox
uses: nebari-dev/action-nebari-sandbox@9ac369ebf87ac2ae217504dcbf824c77f70e429a # v3.0.0
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Wait for Keycloak service
env:
KUBECONFIG: ${{ steps.sandbox.outputs.kubeconfig }}
run: |
kubectl -n keycloak rollout status statefulset/keycloak-keycloakx --timeout=300s
kubectl -n keycloak get svc/keycloak-keycloakx-http
- name: Port-forward Keycloak admin
env:
KUBECONFIG: ${{ steps.sandbox.outputs.kubeconfig }}
run: |
# Background port-forward. The runner stops on workflow exit,
# so no explicit cleanup needed; ``timeout-minutes`` is the
# safety net.
nohup kubectl -n keycloak port-forward svc/keycloak-keycloakx-http \
18080:8080 >/tmp/kc-pf.log 2>&1 &
# Poll for the local port — port-forward exits non-zero on
# any error so we'd see it via the log dump on failure.
for i in $(seq 1 30); do
if curl -sf http://localhost:18080/realms/master/.well-known/openid-configuration >/dev/null; then
echo "KC reachable on :18080 (attempt $i)"
exit 0
fi
sleep 1
done
echo "::error::Keycloak port-forward never came up"
cat /tmp/kc-pf.log
exit 1
- name: Run integration tests
env:
KUBECONFIG: ${{ steps.sandbox.outputs.kubeconfig }}
KC_URL: http://localhost:18080
KC_ADMIN_PASSWORD: ${{ steps.sandbox.outputs.keycloak-admin-password }}
PYTHONUNBUFFERED: "1"
run: uvx pytest tests/integration -v --color=yes
- name: Keycloak logs on failure
if: failure()
env:
KUBECONFIG: ${{ steps.sandbox.outputs.kubeconfig }}
run: |
kubectl -n keycloak get pods
kubectl -n keycloak logs statefulset/keycloak-keycloakx --tail=200 || true
echo "--- port-forward log ---"
cat /tmp/kc-pf.log || true