@@ -17,6 +17,7 @@ variables:
1717 AV_MODULE_NAME : ${MODULES_MODULE_NAME}
1818 AV_PROD_REGISTRY : ${PROD_REGISTRY}
1919 AV_DEV_REGISTRY : ${DEV_REGISTRY}
20+ AV_N8N_LOOP_NOTIFIER_URL : ${N8N_LOOP_NOTIFIER_URL}
2021
2122Antivirus scan get tags :
2223 stage : antivirus_scan
@@ -75,7 +76,7 @@ Antivirus scan get tags:
7576 \${AV_ANTIVIRUS_NAME}-scan:
7677 stage: antivirus_scan
7778 tags:
78- - antivirus
79+ - docker
7980 parallel:
8081 matrix:
8182 - TAG: [\${TAGS_ARRAY}]
@@ -172,3 +173,86 @@ Antivirus scan trigger:
172173 - artifact : child-pipeline-kaspersky.yml
173174 job : " Antivirus scan get tags"
174175 strategy : depend
176+
177+ notify-failure :
178+ stage : antivirus_scan
179+ tags :
180+ - antivirus
181+ image : debian:bookworm-slim
182+ before_script :
183+ - apt-get update && apt-get install -y curl jq
184+ rules :
185+ - when : on_failure
186+ needs :
187+ - job : Antivirus scan get tags
188+ - job : Antivirus scan trigger
189+ optional : true
190+ script :
191+ - |
192+ set -euo pipefail
193+
194+ API="${CI_API_V4_URL}"
195+ PROJECT_ID="${CI_PROJECT_ID}"
196+ PIPELINE_ID="${CI_PIPELINE_ID}"
197+
198+ # auth: prefer CI_JOB_TOKEN, fallback to personal/project token
199+ AUTH_HEADER=()
200+ if [[ -n "${CI_JOB_TOKEN:-}" ]]; then
201+ AUTH_HEADER=(-H "JOB-TOKEN: ${CI_JOB_TOKEN}")
202+ elif [[ -n "${GITLAB_API_TOKEN:-}" ]]; then
203+ AUTH_HEADER=(-H "PRIVATE-TOKEN: ${GITLAB_API_TOKEN}")
204+ else
205+ echo "No CI_JOB_TOKEN or GITLAB_API_TOKEN available"
206+ exit 1
207+ fi
208+
209+ # 1) Find the bridge job "Antivirus scan trigger" and extract downstream_pipeline.id
210+ BRIDGES_JSON="$(curl -sf "${AUTH_HEADER[@]}" \
211+ "${API}/projects/${PROJECT_ID}/pipelines/${PIPELINE_ID}/bridges?per_page=100")"
212+
213+ DOWNSTREAM_ID="$(echo "${BRIDGES_JSON}" | jq -r '
214+ .[] | select(.name=="Antivirus scan trigger") | .downstream_pipeline.id // empty
215+ ')"
216+
217+ if [[ -z "${DOWNSTREAM_ID}" ]]; then
218+ echo "Downstream pipeline id not found for bridge 'Antivirus scan trigger'"
219+ # Fallback: just send a generic alert
220+ DOWNSTREAM_URL="${CI_PIPELINE_URL}"
221+ else
222+ DOWNSTREAM_URL="${CI_PROJECT_URL}/-/pipelines/${DOWNSTREAM_ID}"
223+ fi
224+
225+ # 2) If downstream exists, fetch job statuses in the child pipeline
226+ FAILED_LIST=""
227+ if [[ -n "${DOWNSTREAM_ID}" ]]; then
228+ JOBS_JSON="$(curl -sf "${AUTH_HEADER[@]}" \
229+ "${API}/projects/${PROJECT_ID}/pipelines/${DOWNSTREAM_ID}/jobs?per_page=200")"
230+
231+ # Only take scan jobs; include failed/canceled
232+ FAILED_LIST="$(echo "${JOBS_JSON}" | jq -r '
233+ [ .[]
234+ | select(.name|test("-scan$"))
235+ | select(.status=="failed" or .status=="canceled")
236+ | "- \(.name) (status: \(.status)) \(.web_url)"
237+ ] | .[]?
238+ ')"
239+ fi
240+
241+ if [[ -z "${FAILED_LIST}" ]]; then
242+ FAILED_LIST="- (could not determine the specific failed job(s); check the pipeline/child pipeline)"
243+ fi
244+
245+ # 3) Render template
246+ MSG=$(jq -n --arg pipeline "${CI_PIPELINE_URL}" \
247+ --arg child "${DOWNSTREAM_URL}" \
248+ --arg failed "${FAILED_LIST}" '
249+ "🛑 GitLab Antivirus Scan Failed\n\n" +
250+ "Failed jobs:\n" + $failed + "\n\n" +
251+ "Pipeline: " + $pipeline + "\n" +
252+ "Child pipeline: " + $child
253+ ' -r)
254+
255+ # 4) Send to n8n
256+ curl -sf -L -X POST "$AV_N8N_LOOP_NOTIFIER_URL" \
257+ -H "Content-Type: application/json" \
258+ --data "$(jq -n --arg type "ci_fail" --arg message "$MSG" '{type:$type,message:$message}')"
0 commit comments