@@ -19,6 +19,33 @@ wait_for_exporter() {
1919 exporters.jumpstarter.dev/test-exporter-legacy
2020}
2121
22+ wait_for_exporter_hooks () {
23+ sleep 2
24+ kubectl -n " ${JS_NAMESPACE} " wait --timeout 20m --for=condition=Online --for=condition=Registered \
25+ exporters.jumpstarter.dev/test-exporter-hooks
26+ }
27+
28+ wait_for_exporter_status () {
29+ local exporter=$1
30+ local expected_status=$2
31+ local timeout=${3:- 60}
32+ local interval=2
33+ local elapsed=0
34+
35+ while [ $elapsed -lt $timeout ]; do
36+ local status=$( kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/$exporter \
37+ -o jsonpath=' {.status.exporterStatus}' 2> /dev/null || echo " Unknown" )
38+ if [ " $status " = " $expected_status " ]; then
39+ return 0
40+ fi
41+ sleep $interval
42+ elapsed=$(( elapsed + interval))
43+ done
44+
45+ echo " Timeout waiting for exporter $exporter to reach status $expected_status (current: $status )"
46+ return 1
47+ }
48+
2249@test " can create clients with admin cli" {
2350 jmp admin create client -n " ${JS_NAMESPACE} " test-client-oidc --unsafe --out /dev/null \
2451 --oidc-username dex:test-client-oidc
@@ -144,6 +171,173 @@ EOF
144171 jmp admin get lease --namespace " ${JS_NAMESPACE} "
145172}
146173
174+ # ============================================================================
175+ # HOOKS TESTS
176+ # ============================================================================
177+
178+ @test " hooks: can create hooks-enabled exporters with admin cli" {
179+ jmp admin create exporter -n " ${JS_NAMESPACE} " test-exporter-hooks --save \
180+ --label example.com/board=hooks
181+ jmp admin create exporter -n " ${JS_NAMESPACE} " test-exporter-hooks-endlease --save \
182+ --label example.com/board=hooks-endlease
183+ jmp admin create exporter -n " ${JS_NAMESPACE} " test-exporter-hooks-exit --save \
184+ --label example.com/board=hooks-exit
185+ }
186+
187+ @test " hooks: can configure exporters with hooks" {
188+ go run github.com/mikefarah/yq/v4@latest -i " . * load(\" $GITHUB_ACTION_PATH /exporter-hooks.yaml\" )" \
189+ /etc/jumpstarter/exporters/test-exporter-hooks.yaml
190+ go run github.com/mikefarah/yq/v4@latest -i " . * load(\" $GITHUB_ACTION_PATH /exporter-hooks-endlease.yaml\" )" \
191+ /etc/jumpstarter/exporters/test-exporter-hooks-endlease.yaml
192+ go run github.com/mikefarah/yq/v4@latest -i " . * load(\" $GITHUB_ACTION_PATH /exporter-hooks-exit.yaml\" )" \
193+ /etc/jumpstarter/exporters/test-exporter-hooks-exit.yaml
194+
195+ # Verify hooks are configured in the file
196+ run go run github.com/mikefarah/yq/v4@latest ' .hooks.beforeLease.script' \
197+ /etc/jumpstarter/exporters/test-exporter-hooks.yaml
198+ assert_success
199+ assert_output --partial " beforeLease starting"
200+ }
201+
202+ @test " hooks: can run hooks-enabled exporter" {
203+ cat << EOF | bash 3>&- &
204+ while true; do
205+ jmp run --exporter test-exporter-hooks
206+ done
207+ EOF
208+
209+ wait_for_exporter_hooks
210+ }
211+
212+ @test " hooks: beforeLease executes on lease acquisition" {
213+ wait_for_exporter_hooks
214+
215+ jmp config client use test-client-legacy
216+
217+ # Create lease - this should trigger beforeLease hook
218+ jmp create lease --selector example.com/board=hooks --duration 5m
219+
220+ # Wait for hook to execute and status to transition
221+ sleep 5
222+
223+ # Status should be LeaseReady after successful beforeLease hook
224+ local status=$( kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks \
225+ -o jsonpath=' {.status.exporterStatus}' )
226+ echo " Exporter status: $status "
227+ [ " $status " = " LeaseReady" ]
228+
229+ # Clean up
230+ jmp delete leases --all
231+ }
232+
233+ @test " hooks: can interact with drivers via j CLI in hook script" {
234+ wait_for_exporter_hooks
235+
236+ # The beforeLease hook runs "j power on" - if it fails, the hook would fail
237+ # We verify successful hook execution by checking the lease was acquired
238+ jmp shell --client test-client-legacy --selector example.com/board=hooks j power on
239+
240+ # If we get here, the hook successfully interacted with the driver
241+ wait_for_exporter_hooks
242+ }
243+
244+ @test " hooks: afterLease executes on lease release" {
245+ wait_for_exporter_hooks
246+
247+ jmp config client use test-client-legacy
248+
249+ # Create and immediately delete a lease to trigger both hooks
250+ jmp create lease --selector example.com/board=hooks --duration 1m
251+ sleep 5 # Wait for beforeLease hook
252+
253+ # Delete the lease to trigger afterLease hook
254+ jmp delete leases --all
255+
256+ # Wait for afterLease to complete and status to return to Available
257+ sleep 5
258+
259+ local status=$( kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks \
260+ -o jsonpath=' {.status.exporterStatus}' )
261+ echo " Exporter status after lease release: $status "
262+
263+ wait_for_exporter_hooks
264+ }
265+
266+ @test " hooks: onFailure=endLease blocks lease acquisition" {
267+ # Start the endLease failure mode exporter
268+ cat << EOF | bash 3>&- &
269+ while true; do
270+ jmp run --exporter test-exporter-hooks-endlease
271+ done
272+ EOF
273+
274+ # Wait for exporter to be online
275+ sleep 5
276+ kubectl -n " ${JS_NAMESPACE} " wait --timeout 2m --for=condition=Online --for=condition=Registered \
277+ exporters.jumpstarter.dev/test-exporter-hooks-endlease || true
278+
279+ jmp config client use test-client-legacy
280+
281+ # Try to create a lease - beforeLease hook will fail with onFailure=endLease
282+ jmp create lease --selector example.com/board=hooks-endlease --duration 1m || true
283+
284+ # Wait for hook to execute and fail
285+ sleep 10
286+
287+ # Status should show BeforeLeaseHookFailed
288+ local status=$( kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks-endlease \
289+ -o jsonpath=' {.status.exporterStatus}' )
290+ echo " Exporter status: $status "
291+
292+ [ " $status " = " BeforeLeaseHookFailed" ]
293+
294+ jmp delete leases --all || true
295+ }
296+
297+ @test " hooks: onFailure=exit shuts down exporter" {
298+ # Start the exit failure mode exporter (NOT in a loop - we want it to stay down)
299+ jmp run --exporter test-exporter-hooks-exit &
300+ EXPORTER_PID=$!
301+
302+ # Wait for exporter to be online
303+ sleep 5
304+ kubectl -n " ${JS_NAMESPACE} " wait --timeout 2m --for=condition=Online --for=condition=Registered \
305+ exporters.jumpstarter.dev/test-exporter-hooks-exit || true
306+
307+ jmp config client use test-client-legacy
308+
309+ # Try to create a lease - beforeLease hook will fail with onFailure=exit
310+ jmp create lease --selector example.com/board=hooks-exit --duration 1m || true
311+
312+ # Wait for exporter to shutdown
313+ sleep 10
314+
315+ # The exporter should have gone offline
316+ local status=$( kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks-exit \
317+ -o jsonpath=' {.status.exporterStatus}' 2> /dev/null || echo " Offline" )
318+ echo " Exporter status: $status "
319+
320+ # Status should be Offline or BeforeLeaseHookFailed
321+ [[ " $status " = " Offline" ]] || [[ " $status " = " BeforeLeaseHookFailed" ]]
322+
323+ jmp delete leases --all || true
324+ kill $EXPORTER_PID 2> /dev/null || true
325+ }
326+
327+ @test " hooks: can delete hooks exporters with admin cli" {
328+ jmp admin delete exporter --namespace " ${JS_NAMESPACE} " test-exporter-hooks --delete || true
329+ jmp admin delete exporter --namespace " ${JS_NAMESPACE} " test-exporter-hooks-endlease --delete || true
330+ jmp admin delete exporter --namespace " ${JS_NAMESPACE} " test-exporter-hooks-exit --delete || true
331+
332+ run ! kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks 2> /dev/null
333+ run ! kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks-endlease 2> /dev/null
334+ run ! kubectl -n " ${JS_NAMESPACE} " get exporters.jumpstarter.dev/test-exporter-hooks-exit 2> /dev/null
335+ }
336+
337+ # ============================================================================
338+ # CLEANUP TESTS
339+ # ============================================================================
340+
147341@test " can delete clients with admin cli" {
148342 kubectl -n " ${JS_NAMESPACE} " get secret test-client-oidc-client
149343 kubectl -n " ${JS_NAMESPACE} " get clients.jumpstarter.dev/test-client-oidc
0 commit comments