@@ -2,17 +2,17 @@ name: CI/CD
22
33on :
44 push :
5- branches : [ main ]
6- tags : [ ' [0-9]*' ]
5+ branches : [main]
6+ tags : [" [0-9]*" ]
77 pull_request :
8- branches : [ main ]
8+ branches : [main]
99
1010permissions :
1111 contents : read
1212
1313env :
1414 DOCKER_IMAGE : labsai/eddi
15- JAVA_VERSION : ' 25 '
15+ JAVA_VERSION : " 25 "
1616
1717jobs :
1818 # ─── Job 1: Build & Test ────────────────────────────────────────
@@ -22,10 +22,10 @@ jobs:
2222
2323 steps :
2424 - name : Checkout
25- uses : actions/checkout@v6
25+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2626
2727 - name : Set up JDK ${{ env.JAVA_VERSION }}
28- uses : actions/setup-java@v5
28+ uses : actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
2929 with :
3030 distribution : temurin
3131 java-version : ${{ env.JAVA_VERSION }}
@@ -39,21 +39,60 @@ jobs:
3939
4040 - name : Upload test results
4141 if : always()
42- uses : actions/upload-artifact@v7
42+ uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
4343 with :
4444 name : test-results
4545 path : target/surefire-reports/
4646 retention-days : 14
4747
4848 - name : Upload coverage report
4949 if : always()
50- uses : actions/upload-artifact@v7
50+ uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
5151 with :
5252 name : jacoco-report
5353 path : target/site/jacoco/
5454 retention-days : 14
5555
56- # ─── Job 2: Docker Build & Push ─────────────────────────────────
56+ # ─── Job 2: Integration Tests ─────────────────────────────────
57+ integration-test :
58+ name : Integration Tests
59+ runs-on : ubuntu-latest
60+ needs : build-and-test
61+
62+ steps :
63+ - name : Checkout
64+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
65+
66+ - name : Set up JDK ${{ env.JAVA_VERSION }}
67+ uses : actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
68+ with :
69+ distribution : temurin
70+ java-version : ${{ env.JAVA_VERSION }}
71+ cache : maven
72+
73+ - name : Set execute permission for mvnw
74+ run : chmod +x mvnw
75+
76+ - name : Run integration tests
77+ run : ./mvnw verify -DskipITs=false -B
78+
79+ - name : Upload integration test results
80+ if : always()
81+ uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
82+ with :
83+ name : integration-test-results
84+ path : target/failsafe-reports/
85+ retention-days : 14
86+
87+ - name : Upload integration coverage report
88+ if : always()
89+ uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
90+ with :
91+ name : jacoco-it-report
92+ path : target/site/jacoco-it/
93+ retention-days : 14
94+
95+ # ─── Job 3: Docker Build & Push ─────────────────────────────────
5796 docker :
5897 name : Docker Build & Push
5998 runs-on : ubuntu-latest
@@ -70,10 +109,10 @@ jobs:
70109
71110 steps :
72111 - name : Checkout
73- uses : actions/checkout@v6
112+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
74113
75114 - name : Set up JDK ${{ env.JAVA_VERSION }}
76- uses : actions/setup-java@v5
115+ uses : actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
77116 with :
78117 distribution : temurin
79118 java-version : ${{ env.JAVA_VERSION }}
86125 id : meta
87126 run : |
88127 if [[ "$GITHUB_REF" == refs/tags/* ]]; then
89- # Tag push: 6.0.0-RC1
128+ # Tag push: 6.0.0-RC2
90129 PRIMARY_TAG="${GITHUB_REF#refs/tags/}"
91130 IS_RELEASE="true"
92131 else
@@ -126,7 +165,7 @@ jobs:
126165 .
127166
128167 - name : Login to Docker Hub
129- uses : docker/login-action@v4
168+ uses : docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
130169 with :
131170 username : ${{ secrets.DOCKER_USERNAME }}
132171 password : ${{ secrets.DOCKER_PASSWORD }}
@@ -143,7 +182,7 @@ jobs:
143182
144183 echo "### ✅ Pushed to Docker Hub" >> $GITHUB_STEP_SUMMARY
145184
146- # ─── Job 3 : Smoke Test ──────────────────────────────────────────
185+ # ─── Job 4 : Smoke Test ──────────────────────────────────────────
147186 smoke-test :
148187 name : Smoke Test
149188 runs-on : ubuntu-latest
@@ -211,19 +250,19 @@ jobs:
211250 docker rm -f eddi 2>/dev/null || true
212251 docker rm -f mongodb 2>/dev/null || true
213252
214- # ─── Job 4 : Red Hat Preflight (PRs only) ── ──────────────────────
253+ # ─── Job 5 : Red Hat Preflight — PR Dry-Run ──────────────────────
215254 preflight-check :
216- name : Preflight Dry-Run (Red Hat Certification )
255+ name : Preflight Dry-Run (PR )
217256 runs-on : ubuntu-latest
218257 needs : build-and-test
219258 if : github.event_name == 'pull_request'
220259
221260 steps :
222261 - name : Checkout
223- uses : actions/checkout@v6
262+ uses : actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
224263
225264 - name : Set up JDK ${{ env.JAVA_VERSION }}
226- uses : actions/setup-java@v5
265+ uses : actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5
227266 with :
228267 distribution : temurin
229268 java-version : ${{ env.JAVA_VERSION }}
@@ -295,8 +334,190 @@ jobs:
295334
296335 - name : Upload preflight dry-run results
297336 if : always()
298- uses : actions/upload-artifact@v7
337+ uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
299338 with :
300339 name : preflight-dryrun
301340 path : preflight-dryrun.txt
302341 retention-days : 14
342+
343+ # ─── Job 6: Red Hat Preflight — Push Verification ───────────────
344+ preflight-push :
345+ name : Preflight Verify (Pushed Image)
346+ runs-on : ubuntu-latest
347+ needs : docker
348+ if : >
349+ github.event_name == 'push' &&
350+ needs.docker.result == 'success'
351+
352+ steps :
353+ - name : Login to Docker Hub
354+ uses : docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
355+ with :
356+ username : ${{ secrets.DOCKER_USERNAME }}
357+ password : ${{ secrets.DOCKER_PASSWORD }}
358+
359+ - name : Pull pushed image
360+ run : |
361+ PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
362+ echo "Pulling ${DOCKER_IMAGE}:${PRIMARY_TAG}..."
363+ docker pull ${DOCKER_IMAGE}:${PRIMARY_TAG}
364+
365+ - name : Verify Red Hat labels present
366+ run : |
367+ PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
368+ echo "=== Checking required Red Hat labels ==="
369+ LABELS=$(docker inspect ${DOCKER_IMAGE}:${PRIMARY_TAG} --format '{{json .Config.Labels}}')
370+ echo "$LABELS" | python3 -m json.tool
371+ MISSING=0
372+ for label in name vendor version release summary description; do
373+ if echo "$LABELS" | python3 -c "import sys,json; d=json.load(sys.stdin); assert '$label' in d" 2>/dev/null; then
374+ echo "✅ Label '$label' present"
375+ else
376+ echo "❌ Label '$label' MISSING"
377+ MISSING=1
378+ fi
379+ done
380+ if [ "$MISSING" -eq 1 ]; then
381+ echo "::error::Required Red Hat labels are missing on pushed image"
382+ exit 1
383+ fi
384+
385+ - name : Verify /licenses in container
386+ run : |
387+ PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
388+ echo "=== /licenses directory ==="
389+ docker run --rm --entrypoint="" ${DOCKER_IMAGE}:${PRIMARY_TAG} ls -la /licenses/
390+ echo ""
391+ echo "=== THIRD-PARTY.txt exists ==="
392+ docker run --rm --entrypoint="" ${DOCKER_IMAGE}:${PRIMARY_TAG} \
393+ test -f /licenses/THIRD-PARTY.txt && echo "✅ THIRD-PARTY.txt found" || { echo "❌ THIRD-PARTY.txt missing"; exit 1; }
394+
395+ - name : Install preflight
396+ run : |
397+ PREFLIGHT_VERSION=$(curl -s https://api.github.com/repos/redhat-openshift-ecosystem/openshift-preflight/releases/latest | jq -r .tag_name)
398+ echo "Installing preflight ${PREFLIGHT_VERSION}..."
399+ curl -sL "https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/${PREFLIGHT_VERSION}/preflight-linux-amd64" \
400+ -o /usr/local/bin/preflight
401+ chmod +x /usr/local/bin/preflight
402+ preflight --version
403+
404+ - name : Preflight check (pushed image, no submit)
405+ run : |
406+ PRIMARY_TAG="${{ needs.docker.outputs.primary-tag }}"
407+ preflight check container \
408+ docker.io/${DOCKER_IMAGE}:${PRIMARY_TAG} \
409+ --docker-config=${HOME}/.docker/config.json \
410+ 2>&1 | tee preflight-push-results.txt
411+ echo ""
412+ echo "=== Preflight Summary ==="
413+ if grep -q "FAILED" preflight-push-results.txt; then
414+ echo "::warning::Preflight check has failures on pushed image — review output above."
415+ else
416+ echo "✅ All preflight checks passed on pushed image"
417+ fi
418+
419+ - name : Upload preflight results
420+ if : always()
421+ uses : actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
422+ with :
423+ name : preflight-push-results
424+ path : preflight-push-results.txt
425+ retention-days : 14
426+
427+ # ─── Job 7: Slack Notification ─────────────────────────────────
428+ notify-slack :
429+ name : Slack Notification
430+ runs-on : ubuntu-latest
431+ if : always()
432+ needs :
433+ [build-and-test, integration-test, docker, smoke-test, preflight-check, preflight-push]
434+
435+ steps :
436+ - name : Send build notification
437+ env :
438+ SLACK_WEBHOOK : ${{ secrets.SLACK_WEBHOOK_URL }}
439+ run : |
440+ # Skip if no webhook configured
441+ if [ -z "$SLACK_WEBHOOK" ]; then
442+ echo "No SLACK_WEBHOOK_URL configured, skipping"
443+ exit 0
444+ fi
445+
446+ # Determine overall status
447+ BUILD="${{ needs.build-and-test.result }}"
448+ IT="${{ needs.integration-test.result }}"
449+ DOCKER="${{ needs.docker.result }}"
450+ SMOKE="${{ needs.smoke-test.result }}"
451+ PREFLIGHT_PR="${{ needs.preflight-check.result }}"
452+ PREFLIGHT_PUSH="${{ needs.preflight-push.result }}"
453+ # Merge: whichever preflight job actually ran wins (the other is "skipped")
454+ if [ "$PREFLIGHT_PUSH" != "skipped" ]; then
455+ PREFLIGHT="$PREFLIGHT_PUSH"
456+ else
457+ PREFLIGHT="$PREFLIGHT_PR"
458+ fi
459+
460+ # Map result to emoji
461+ status_icon() {
462+ case "$1" in
463+ success) echo "✅" ;;
464+ failure) echo "❌" ;;
465+ skipped) echo "⏭️" ;;
466+ cancelled) echo "🚫" ;;
467+ *) echo "❓" ;;
468+ esac
469+ }
470+
471+ # Overall status
472+ if [ "$BUILD" = "success" ] && [ "$IT" != "failure" ] && [ "$DOCKER" != "failure" ] && [ "$SMOKE" != "failure" ]; then
473+ OVERALL_ICON="✅"
474+ OVERALL_TEXT="Build Passed"
475+ COLOR="#36a64f"
476+ else
477+ OVERALL_ICON="❌"
478+ OVERALL_TEXT="Build Failed"
479+ COLOR="#cc0000"
480+ fi
481+
482+ # Branch or tag name
483+ REF="${GITHUB_REF#refs/heads/}"
484+ REF="${REF#refs/tags/}"
485+ REF="${REF#refs/pull/}"
486+
487+ # Sanitize commit message for JSON embedding
488+ RAW_MSG="${{ github.event.head_commit.message || github.event.pull_request.title || 'Manual run' }}"
489+ COMMIT_MSG=$(echo "$RAW_MSG" | head -1 | cut -c1-80 | sed 's/[\"\\]//g' | sed "s/[']//g")
490+
491+ curl -sf -X POST "$SLACK_WEBHOOK" \
492+ -H 'Content-type: application/json' \
493+ -d "{
494+ \"blocks\": [
495+ {
496+ \"type\": \"header\",
497+ \"text\": { \"type\": \"plain_text\", \"text\": \"${OVERALL_ICON} EDDI CI: ${OVERALL_TEXT}\", \"emoji\": true }
498+ },
499+ {
500+ \"type\": \"section\",
501+ \"text\": {
502+ \"type\": \"mrkdwn\",
503+ \"text\": \"*Branch:* \`${REF}\` · *By:* ${{ github.actor }}\n${COMMIT_MSG}\"
504+ }
505+ },
506+ {
507+ \"type\": \"section\",
508+ \"fields\": [
509+ { \"type\": \"mrkdwn\", \"text\": \"$(status_icon $BUILD) Build & Test\" },
510+ { \"type\": \"mrkdwn\", \"text\": \"$(status_icon $IT) Integration Tests\" },
511+ { \"type\": \"mrkdwn\", \"text\": \"$(status_icon $DOCKER) Docker Push\" },
512+ { \"type\": \"mrkdwn\", \"text\": \"$(status_icon $SMOKE) Smoke Test\" },
513+ { \"type\": \"mrkdwn\", \"text\": \"$(status_icon $PREFLIGHT) Red Hat Preflight\" }
514+ ]
515+ },
516+ {
517+ \"type\": \"context\",
518+ \"elements\": [
519+ { \"type\": \"mrkdwn\", \"text\": \"<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run #${{ github.run_number }}>\" }
520+ ]
521+ }
522+ ]
523+ }"
0 commit comments