ci: add universal test coverage pipeline
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
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
This commit is contained in:
236
.gitea/workflows/ci.yml
Normal file
236
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,236 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user