CI:
This commit is contained in:
+118
-1
@@ -2,7 +2,7 @@ name: sampleapp-cicd
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["**"]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
@@ -35,6 +35,58 @@ jobs:
|
||||
set -e
|
||||
docker build -t "${IMAGE}:${GITHUB_SHA}" -t "${IMAGE}:latest" .
|
||||
|
||||
# --- Feature 4: Trivy vulnerability scan (gate) --------------------------
|
||||
# Install the Trivy binary from the official script (the runner reaches the
|
||||
# internet; a pinned binary is more reproducible here than the
|
||||
# trivy-action, which resolves from github.com/master). The scan runs
|
||||
# against the locally-built image over the runner's docker.sock.
|
||||
- name: Install Trivy
|
||||
run: |
|
||||
set -e
|
||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
|
||||
| sh -s -- -b /usr/local/bin v0.58.1
|
||||
trivy --version
|
||||
|
||||
# Gate: fail ci on any fixable CRITICAL/HIGH so a vulnerable image never
|
||||
# gets pushed or deployed. ignore-unfixed keeps the gate real but avoids
|
||||
# failing on distro CVEs with no available fix (nginx:alpine currently has
|
||||
# zero fixable CRITICAL/HIGH — the gate passes clean but is genuinely
|
||||
# enforcing). --scanners vuln keeps it to CVEs (no secret/misconfig noise).
|
||||
- name: Trivy scan (gate CRITICAL,HIGH)
|
||||
run: |
|
||||
set -e
|
||||
trivy image \
|
||||
--exit-code 1 \
|
||||
--severity CRITICAL,HIGH \
|
||||
--ignore-unfixed \
|
||||
--scanners vuln \
|
||||
--no-progress \
|
||||
"${IMAGE}:${GITHUB_SHA}"
|
||||
echo "trivy gate passed (no fixable CRITICAL/HIGH)"
|
||||
|
||||
# --- Feature 4: SBOM (CycloneDX) generation + artifact upload -----------
|
||||
- name: Generate SBOM (CycloneDX)
|
||||
if: always()
|
||||
run: |
|
||||
set -e
|
||||
trivy image \
|
||||
--format cyclonedx \
|
||||
--scanners vuln \
|
||||
--no-progress \
|
||||
--output sbom.cdx.json \
|
||||
"${IMAGE}:${GITHUB_SHA}"
|
||||
echo "SBOM components: $(python3 -c 'import json; print(len(json.load(open("sbom.cdx.json")).get("components",[])))' 2>/dev/null || echo n/a)"
|
||||
ls -l sbom.cdx.json
|
||||
|
||||
- name: Upload SBOM artifact
|
||||
if: always()
|
||||
uses: https://gitea.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: sbom-cyclonedx-${{ github.sha }}
|
||||
path: sbom.cdx.json
|
||||
if-no-files-found: warn
|
||||
|
||||
# --- Push (main only) ---------------------------------------------------
|
||||
- name: Push image (main only)
|
||||
if: github.ref == 'refs/heads/main'
|
||||
run: |
|
||||
@@ -60,3 +112,68 @@ jobs:
|
||||
--data "{\"options\":{\"image_tag\":\"${GITHUB_SHA}\"}}")
|
||||
echo "Rundeck run HTTP ${code}"
|
||||
python3 -c "import json;d=json.load(open('/tmp/rd.json'));print('execution id:',d.get('id'),'status:',d.get('status'),'permalink:',d.get('permalink'))"
|
||||
|
||||
# --- Feature 2: notifications (Mattermost success+failure, GoAlert on failure)
|
||||
notify:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ci, deploy]
|
||||
if: always()
|
||||
env:
|
||||
MM_HOOK: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
|
||||
GA_KEY: ${{ secrets.GOALERT_KEY_URL }}
|
||||
steps:
|
||||
- name: Compute pipeline outcome
|
||||
id: outcome
|
||||
run: |
|
||||
set -e
|
||||
CI="${{ needs.ci.result }}"
|
||||
DEPLOY="${{ needs.deploy.result }}"
|
||||
# deploy is skipped on non-main (PR) runs; treat skipped as non-failing.
|
||||
if [ "$CI" = "success" ] && { [ "$DEPLOY" = "success" ] || [ "$DEPLOY" = "skipped" ]; }; then
|
||||
echo "state=success" >> "$GITHUB_OUTPUT"
|
||||
echo "emoji=:white_check_mark:" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "state=failure" >> "$GITHUB_OUTPUT"
|
||||
echo "emoji=:rotating_light:" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Notify Mattermost (success or failure)
|
||||
env:
|
||||
STATE: ${{ steps.outcome.outputs.state }}
|
||||
EMOJI: ${{ steps.outcome.outputs.emoji }}
|
||||
CI_RESULT: ${{ needs.ci.result }}
|
||||
DEPLOY_RESULT: ${{ needs.deploy.result }}
|
||||
REPO: ${{ github.repository }}
|
||||
REF: ${{ github.ref_name }}
|
||||
SHA: ${{ github.sha }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}
|
||||
run: |
|
||||
set -e
|
||||
# Build the text + JSON payload with python (env in, JSON out) so no
|
||||
# shell quoting of the message can break the webhook body. The webhook
|
||||
# is locked to the deployments channel, so no channel field is sent.
|
||||
# Single-line python keeps every line inside the YAML block scalar.
|
||||
payload="$(python3 -c 'import json,os; e=os.environ; t="%s **sampleapp-cicd %s** on `%s@%s`\nci=`%s` deploy=`%s`\ncommit `%s`\n%s" % (e["EMOJI"], e["STATE"], e["REPO"], e["REF"], e["CI_RESULT"], e["DEPLOY_RESULT"], e["SHA"], e["RUN_URL"]); print(json.dumps({"text": t}))')"
|
||||
code="$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Content-Type: application/json" -d "$payload" "$MM_HOOK")"
|
||||
echo "mattermost webhook HTTP ${code}"
|
||||
[ "$code" = "200" ]
|
||||
|
||||
- name: Notify GoAlert (failure only)
|
||||
if: steps.outcome.outputs.state == 'failure'
|
||||
env:
|
||||
CI_RESULT: ${{ needs.ci.result }}
|
||||
DEPLOY_RESULT: ${{ needs.deploy.result }}
|
||||
REPO: ${{ github.repository }}
|
||||
SHA: ${{ github.sha }}
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_number }}
|
||||
run: |
|
||||
set -e
|
||||
# GoAlert generic API: summary via query param, details via JSON body,
|
||||
# dedup collapses repeated failures onto one alert. Success = HTTP 204.
|
||||
url="$(python3 -c 'import os,urllib.parse; e=os.environ; base=e["GA_KEY"]; sep="&" if "?" in base else "?"; q=urllib.parse.urlencode({"summary":"CI/CD FAILED: %s (ci=%s deploy=%s)" % (e["REPO"], e["CI_RESULT"], e["DEPLOY_RESULT"]), "dedup":"sampleapp-cicd-failure"}); print(base+sep+q)')"
|
||||
body="$(python3 -c 'import json,os; print(json.dumps({"details":"commit %s - %s" % (os.environ["SHA"], os.environ["RUN_URL"]), "action":"trigger"}))')"
|
||||
code="$(curl -sS -o /dev/null -w '%{http_code}' -X POST \
|
||||
-H "Content-Type: application/json" -d "$body" "$url")"
|
||||
echo "goalert generic HTTP ${code}"
|
||||
[ "$code" = "204" ] || [ "$code" = "200" ]
|
||||
Reference in New Issue
Block a user