|
11 | 11 | {{- $activeDeadlineSeconds := $cronjob.activeDeadlineSeconds | default $global.activeDeadlineSeconds }} |
12 | 12 | {{- $cronjobName := include "charts-cron-job.fullname" (dict "root" $ "cronjob" $cronjob "index" $index) }} |
13 | 13 | {{- $saName := printf "%s-helm-test" $cronjobName | trunc 63 | trimSuffix "-" }} |
14 | | -{{- $jobName := printf "%s-helm-test" $cronjobName | trunc 63 | trimSuffix "-" }} |
| 14 | +{{- $executorName := printf "%s-helm-test-executor" $cronjobName | trunc 63 | trimSuffix "-" }} |
| 15 | +{{- /* Spawned test Job name base: truncated to 46 chars so the runtime suffix |
| 16 | + "-<10-digit-epoch>-<5-digit-random>" (17 chars max) fits within the |
| 17 | + 63-char Kubernetes resource name limit. */ -}} |
| 18 | +{{- $jobNameBase := printf "%s-test" $cronjobName | trunc 46 | trimSuffix "-" }} |
15 | 19 | --- |
16 | | -apiVersion: v1 |
17 | | -kind: Pod |
| 20 | +apiVersion: batch/v1 |
| 21 | +kind: Job |
18 | 22 | metadata: |
19 | | - name: {{ printf "%s-helm-test" $cronjobName | trunc 63 | trimSuffix "-" }} |
| 23 | + name: {{ $executorName }} |
20 | 24 | namespace: {{ $.Release.Namespace }} |
21 | 25 | labels: |
22 | 26 | {{- include "charts-cron-job.labels" (dict "root" $ "cronjob" $cronjob) | nindent 4 }} |
23 | 27 | annotations: |
24 | 28 | helm.sh/hook: test |
25 | 29 | helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded |
26 | 30 | spec: |
27 | | - serviceAccountName: {{ $saName }} |
28 | | - restartPolicy: Never |
29 | | - containers: |
30 | | - - name: helm-test |
31 | | - image: {{ $.Values.global.helmTest.image }} |
32 | | - command: |
33 | | - - /bin/sh |
34 | | - - -c |
35 | | - - | |
36 | | - set -e |
| 31 | + backoffLimit: 1 |
| 32 | + template: |
| 33 | + metadata: |
| 34 | + labels: |
| 35 | + {{- include "charts-cron-job.labels" (dict "root" $ "cronjob" $cronjob) | nindent 8 }} |
| 36 | + spec: |
| 37 | + serviceAccountName: {{ $saName }} |
| 38 | + restartPolicy: Never |
| 39 | + containers: |
| 40 | + - name: helm-test |
| 41 | + image: {{ $.Values.global.image.repository }}/{{ $.Values.global.helmTest.image }} |
| 42 | + command: |
| 43 | + - /bin/sh |
| 44 | + - -c |
| 45 | + - | |
| 46 | + set -e |
37 | 47 |
|
38 | | - CRONJOB="{{ $cronjobName }}" |
39 | | - NAMESPACE="{{ $.Release.Namespace }}" |
40 | | - POLICY="{{ $concurrencyPolicy }}" |
41 | | - JOB_NAME="{{ $jobName }}" |
42 | | - TIMEOUT="{{ $activeDeadlineSeconds }}" |
| 48 | + CRONJOB="{{ $cronjobName }}" |
| 49 | + NAMESPACE="{{ $.Release.Namespace }}" |
| 50 | + POLICY="{{ $concurrencyPolicy }}" |
| 51 | + TIMEOUT="{{ $activeDeadlineSeconds }}" |
43 | 52 |
|
44 | | - echo "==> Helm test for CronJob: ${CRONJOB}" |
45 | | - echo " ConcurrencyPolicy : ${POLICY}" |
46 | | - echo " Test Job name : ${JOB_NAME}" |
47 | | - echo " Timeout : ${TIMEOUT}s" |
| 53 | + # Unique suffix: epoch seconds + shell $RANDOM (0-32767). |
| 54 | + # Base is pre-truncated to 46 chars at render time so the full name |
| 55 | + # stays within the 63-char Kubernetes resource name limit. |
| 56 | + JOB_NAME="{{ $jobNameBase }}-$(date +%s)-${RANDOM}" |
48 | 57 |
|
49 | | - # ── Forbid: wait for any currently-active job to finish ────────── |
50 | | - if [ "$POLICY" = "Forbid" ]; then |
51 | | - echo "==> ConcurrencyPolicy=Forbid: waiting for active jobs to finish before creating test job..." |
52 | | - ELAPSED=0 |
53 | | - while true; do |
54 | | - ACTIVE=$(kubectl get jobs -n "${NAMESPACE}" \ |
55 | | - --selector="batch.kubernetes.io/controller-uid=$(kubectl get cronjob "${CRONJOB}" -n "${NAMESPACE}" -o jsonpath='{.metadata.uid}')" \ |
56 | | - -o jsonpath='{range .items[*]}{.status.active}{"\n"}{end}' 2>/dev/null \ |
57 | | - | grep -c '^[1-9]' || true) |
58 | | - if [ "${ACTIVE}" -eq 0 ]; then |
59 | | - echo " No active jobs found. Proceeding." |
60 | | - break |
61 | | - fi |
62 | | - if [ "${ELAPSED}" -ge "${TIMEOUT}" ]; then |
63 | | - echo "ERROR: Timed out after ${TIMEOUT}s waiting for active jobs to finish." |
64 | | - exit 1 |
65 | | - fi |
66 | | - echo " ${ACTIVE} active job(s) still running. Waiting 10s... (${ELAPSED}s elapsed)" |
67 | | - sleep 10 |
68 | | - ELAPSED=$((ELAPSED + 10)) |
69 | | - done |
| 58 | + echo "==> Helm test for CronJob: ${CRONJOB}" |
| 59 | + echo " ConcurrencyPolicy : ${POLICY}" |
| 60 | + echo " Test Job name : ${JOB_NAME}" |
| 61 | + echo " Timeout : ${TIMEOUT}s" |
| 62 | +
|
| 63 | + # ── Forbid: wait for any currently-active job to finish ────────── |
| 64 | + if [ "$POLICY" = "Forbid" ]; then |
| 65 | + echo "==> ConcurrencyPolicy=Forbid: waiting for active jobs to finish before creating test job..." |
70 | 66 |
|
71 | | - # ── Replace: delete any existing test job first ────────────────── |
72 | | - elif [ "$POLICY" = "Replace" ]; then |
73 | | - echo "==> ConcurrencyPolicy=Replace: deleting existing test job if present..." |
74 | | - kubectl delete job "${JOB_NAME}" -n "${NAMESPACE}" --ignore-not-found |
75 | | - echo " Done." |
76 | | - fi |
| 67 | + # Resolve the CronJob UID at runtime. Jobs spawned by this CronJob |
| 68 | + # carry an ownerReference pointing to this UID — the authoritative |
| 69 | + # way to identify all jobs that belong to a specific CronJob instance |
| 70 | + # (as seen in the job's .metadata.ownerReferences[].uid field). |
| 71 | + CRONJOB_UID=$(kubectl get cronjob "${CRONJOB}" -n "${NAMESPACE}" \ |
| 72 | + -o jsonpath="{.metadata.uid}") |
| 73 | + echo " CronJob UID: ${CRONJOB_UID}" |
| 74 | +
|
| 75 | + ELAPSED=0 |
| 76 | + while true; do |
| 77 | + # Emit "<ownerUID> <activeCount>" for every job, then keep only |
| 78 | + # lines where the ownerUID matches this CronJob and activeCount > 0. |
| 79 | + ACTIVE=$(kubectl get jobs -n "${NAMESPACE}" \ |
| 80 | + -o jsonpath="{range .items[*]}{.metadata.ownerReferences[0].uid}{' '}{.status.active}{'\n'}{end}" \ |
| 81 | + | awk -v uid="${CRONJOB_UID}" \ |
| 82 | + "NF==2 && \$1==uid && \$2+0>0 {count++} END {print count+0}") |
| 83 | + if [ "${ACTIVE}" -eq 0 ]; then |
| 84 | + echo " No active jobs found. Proceeding." |
| 85 | + break |
| 86 | + fi |
| 87 | + if [ "${ELAPSED}" -ge "${TIMEOUT}" ]; then |
| 88 | + echo "ERROR: Timed out after ${TIMEOUT}s waiting for active jobs to finish." |
| 89 | + exit 1 |
| 90 | + fi |
| 91 | + echo " ${ACTIVE} active job(s) still running. Waiting 10s... (${ELAPSED}s elapsed)" |
| 92 | + sleep 10 |
| 93 | + ELAPSED=$((ELAPSED + 10)) |
| 94 | + done |
| 95 | +
|
| 96 | + # ── Replace: delete any active scheduler-spawned jobs first ────── |
| 97 | + elif [ "$POLICY" = "Replace" ]; then |
| 98 | + echo "==> ConcurrencyPolicy=Replace: deleting active jobs owned by this CronJob..." |
| 99 | + CRONJOB_UID=$(kubectl get cronjob "${CRONJOB}" -n "${NAMESPACE}" \ |
| 100 | + -o jsonpath="{.metadata.uid}") |
| 101 | + # Find names of all jobs whose ownerReference points to this CronJob |
| 102 | + # and that currently have active pods, then delete them. |
| 103 | + kubectl get jobs -n "${NAMESPACE}" \ |
| 104 | + -o jsonpath="{range .items[*]}{.metadata.ownerReferences[0].uid}{' '}{.status.active}{' '}{.metadata.name}{'\n'}{end}" \ |
| 105 | + | awk -v uid="${CRONJOB_UID}" "NF==3 && \$1==uid && \$2+0>0 {print \$3}" \ |
| 106 | + | xargs -r kubectl delete job -n "${NAMESPACE}" --ignore-not-found |
| 107 | + echo " Done." |
| 108 | + fi |
77 | 109 |
|
78 | | - # ── Create the test job from the CronJob ───────────────────────── |
79 | | - echo "==> Creating test job '${JOB_NAME}' from CronJob '${CRONJOB}'..." |
80 | | - kubectl create job "${JOB_NAME}" --from=cronjob/"${CRONJOB}" -n "${NAMESPACE}" |
| 110 | + # ── Create the test job from the CronJob ───────────────────────── |
| 111 | + echo "==> Creating test job '${JOB_NAME}' from CronJob '${CRONJOB}'..." |
| 112 | + kubectl create job "${JOB_NAME}" --from=cronjob/"${CRONJOB}" -n "${NAMESPACE}" |
81 | 113 |
|
82 | | - # ── Wait for completion ─────────────────────────────────────────── |
83 | | - echo "==> Waiting for job '${JOB_NAME}' to complete (timeout: ${TIMEOUT}s)..." |
84 | | - kubectl wait job/"${JOB_NAME}" \ |
85 | | - -n "${NAMESPACE}" \ |
86 | | - --for=condition=complete \ |
87 | | - --timeout="${TIMEOUT}s" |
| 114 | + # ── Wait for completion ─────────────────────────────────────────── |
| 115 | + echo "==> Waiting for job '${JOB_NAME}' to complete (timeout: ${TIMEOUT}s)..." |
| 116 | + kubectl wait job/"${JOB_NAME}" \ |
| 117 | + -n "${NAMESPACE}" \ |
| 118 | + --for=condition=complete \ |
| 119 | + --timeout="${TIMEOUT}s" |
88 | 120 |
|
89 | | - echo "==> Test job completed successfully." |
| 121 | + echo "==> Test job completed successfully." |
90 | 122 | {{- end }} |
91 | 123 |
|
92 | 124 | {{- end }} |
0 commit comments