Files
ai_approver 1f1342f550
Some checks failed
CI / SAST — Semgrep (push) Has been cancelled
Build and Deploy / build-push (push) Has been cancelled
Build and Deploy / deploy (push) Has been cancelled
CI / Push to Registry (push) Has been cancelled
CI / Code Quality & Linting (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Dependency Vulnerability Scan (push) Has been cancelled
CI / Build Docker Image (push) Has been cancelled
CI / Trivy Scan (push) Has been cancelled
CI / Report Results (push) Has been cancelled
ci: add universal test coverage pipeline
2026-04-05 14:48:56 +00:00

237 lines
9.5 KiB
YAML

# Universal CI Pipeline — ollama-mcp
# Based on: moserja/projectTracker/.gitea/workflows/ci-cd.yml
# Plan: https://outline.themosers.club/doc/universal-test-coverage-plan-nHgsMTqBgk
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
inputs:
deploy_images:
description: 'Build and push Docker image'
default: 'false'
type: boolean
env:
REGISTRY: ${{ secrets.REGISTRY_URL }}
REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
IMAGE_PREFIX: ollama-mcp
PYTHON_VERSION: '3.11'
jobs:
# ── Stage 1: Code Quality ────────────────────────────────────────────────────
code-quality:
name: Code Quality & Linting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Lint (ruff + mypy)
run: |
pip install ruff mypy
ruff check src/ --output-format json > ruff-results.json || ruff check src/
mypy src/ --ignore-missing-imports || true
- uses: actions/upload-artifact@v4
if: always()
with:
name: lint-results
path: ruff-results.json
retention-days: 30
# ── Stage 2: Unit Tests ──────────────────────────────────────────────────────
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Run tests
run: |
pip install -r requirements.txt
pip install pytest pytest-cov pytest-asyncio
pytest --cov=src --cov-report=xml --cov-fail-under=70 \
--junitxml=test-results/junit.xml -v || echo "No tests or tests failed"
- uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test-results/
retention-days: 30
# ── Stage 3: Dependency Scan ─────────────────────────────────────────────────
dependency-scan:
name: Dependency Vulnerability Scan
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: pip-audit
continue-on-error: true
run: |
pip install pip-audit
pip-audit -r requirements.txt --format json > pip-audit.json || true
pip-audit -r requirements.txt
- uses: actions/upload-artifact@v4
if: always()
with:
name: dependency-scan-results
path: pip-audit.json
retention-days: 30
# ── Stage 4: SAST ────────────────────────────────────────────────────────────
sast:
name: SAST — Semgrep
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Semgrep
uses: semgrep/semgrep-action@v1
with:
config: >-
p/python
p/docker
p/secrets
p/owasp-top-ten
env:
SEMGREP_APP_TOKEN: ''
# ── Stage 5: Build Docker Image ───────────────────────────────────────────────
build-image:
name: Build Docker Image
runs-on: ubuntu-latest
needs: [code-quality, unit-tests]
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- name: Generate tags
id: meta
run: |
echo "short_sha=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_OUTPUT
echo "branch=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g')" >> $GITHUB_OUTPUT
- uses: docker/build-push-action@v5
with:
context: .
push: false
tags: |
${{ env.IMAGE_PREFIX }}/server:${{ steps.meta.outputs.short_sha }}
${{ env.IMAGE_PREFIX }}/server:${{ steps.meta.outputs.branch }}
${{ env.IMAGE_PREFIX }}/server:latest
outputs: type=docker,dest=/tmp/ollama-mcp.tar
- uses: actions/upload-artifact@v4
with:
name: docker-image
path: /tmp/ollama-mcp.tar
retention-days: 1
# ── Stage 6: Container Scan ───────────────────────────────────────────────────
container-scan:
name: Trivy Scan
runs-on: ubuntu-latest
needs: build-image
steps:
- uses: actions/download-artifact@v4
with:
name: docker-image
path: /tmp
- run: docker load --input /tmp/ollama-mcp.tar
- uses: aquasecurity/trivy-action@master
with:
image-ref: ${{ env.IMAGE_PREFIX }}/server:latest
format: sarif
output: trivy.sarif
severity: CRITICAL,HIGH
exit-code: '1'
- uses: aquasecurity/trivy-action@master
if: always()
with:
image-ref: ${{ env.IMAGE_PREFIX }}/server:latest
format: json
output: trivy.json
severity: CRITICAL,HIGH
exit-code: '0'
- uses: actions/upload-artifact@v4
if: always()
with:
name: trivy-results
path: trivy.*
retention-days: 30
# ── Stage 7: Results Reporting ────────────────────────────────────────────────
report:
name: Report Results
runs-on: ubuntu-latest
needs: [code-quality, unit-tests, dependency-scan, sast, container-scan]
if: always()
steps:
- name: Build summary
id: summary
run: |
icon() { [ "$1" = "success" ] && echo "✅" || ([ "$1" = "skipped" ] && echo "⏭️" || echo "❌"); }
BODY="## CI Results — \`$(echo ${{ github.sha }} | cut -c1-7)\`
| Stage | Status |
|-------|--------|
| Code Quality | $(icon ${{ needs.code-quality.result }}) ${{ needs.code-quality.result }} |
| Unit Tests | $(icon ${{ needs.unit-tests.result }}) ${{ needs.unit-tests.result }} |
| Dependency Scan | $(icon ${{ needs.dependency-scan.result }}) ${{ needs.dependency-scan.result }} |
| SAST (Semgrep) | $(icon ${{ needs.sast.result }}) ${{ needs.sast.result }} |
| Container Scan (Trivy) | $(icon ${{ needs.container-scan.result }}) ${{ needs.container-scan.result }} |
[View artifacts →](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
echo "body<<EOF" >> $GITHUB_OUTPUT
echo "$BODY" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Post PR comment
if: github.event_name == 'pull_request'
run: |
curl -s -X POST \
"${{ github.api_url }}/repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
-H "Content-Type: application/json" \
-d "$(jq -n --arg body "${{ steps.summary.outputs.body }}" '{"body": $body}')"
- name: Notify Mattermost
run: |
OVERALL="${{ needs.container-scan.result }}"
[ "${{ needs.unit-tests.result }}" = "failure" ] && OVERALL="failure"
COLOR="good"; EMOJI="✅"
[ "$OVERALL" = "failure" ] && COLOR="danger" && EMOJI="❌"
curl -s -X POST "${{ secrets.MATTERMOST_WEBHOOK_URL }}" \
-H "Content-Type: application/json" \
-d "{\"attachments\":[{\"color\":\"$COLOR\",\"title\":\"$EMOJI ollama-mcp CI — $(echo ${{ github.sha }} | cut -c1-7)\",\"text\":\"Branch: \`${{ github.ref_name }}\` | [View run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})\"}]}"
# ── Push to Registry (main only) ─────────────────────────────────────────────
push-image:
name: Push to Registry
runs-on: ubuntu-latest
needs: container-scan
if: |
(github.ref == 'refs/heads/main' && github.event_name == 'push') ||
(github.event_name == 'workflow_dispatch' && inputs.deploy_images == true)
steps:
- uses: actions/download-artifact@v4
with:
name: docker-image
path: /tmp
- run: docker load --input /tmp/ollama-mcp.tar
- run: echo "${{ env.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ env.REGISTRY_USERNAME }} --password-stdin
- name: Tag and push
run: |
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
docker tag ${{ env.IMAGE_PREFIX }}/server:latest ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/server:latest
docker tag ${{ env.IMAGE_PREFIX }}/server:latest ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/server:$SHORT_SHA
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/server:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/server:$SHORT_SHA