Add Woodpecker CI/CD pipeline

- Lint: golangci-lint (consent-service), ruff (Python), next lint (admin-core)
- Tests: Go tests for consent-service with JSON reporting
- Build: Docker images for consent-service, backend-core, admin-core
- Security: SBOM generation + vulnerability scanning
- Deploy: manual docker compose deployment
This commit is contained in:
Benjamin Boenisch
2026-02-15 10:56:01 +01:00
parent fdfe38b61a
commit 87133798ab

255
.woodpecker/main.yml Normal file
View File

@@ -0,0 +1,255 @@
# Woodpecker CI Main Pipeline
# BreakPilot Core - CI/CD Pipeline
#
# Plattform: ARM64 (Apple Silicon Mac Mini)
#
# Services: consent-service (Go), backend-core (Python), admin-core (Node.js), night-scheduler (Python)
#
# Strategie:
# - Lint bei PRs
# - Tests laufen bei JEDEM Push/PR
# - Test-Ergebnisse werden an Dashboard gesendet
# - Builds/Scans laufen nur bei Tags oder manuell
# - Deployment nur manuell (Sicherheit)
when:
- event: [push, pull_request, manual, tag]
branch: [main, develop]
clone:
git:
image: woodpeckerci/plugin-git
settings:
depth: 1
extra_hosts:
- macmini:192.168.178.100
variables:
- &golang_image golang:1.23-alpine
- &python_image python:3.12-slim
- &nodejs_image node:20-alpine
- &docker_image docker:27-cli
steps:
# ========================================
# STAGE 1: Lint (nur bei PRs)
# ========================================
go-lint:
image: golangci/golangci-lint:v1.55-alpine
commands:
- cd consent-service && golangci-lint run --timeout 5m ./...
when:
event: pull_request
python-lint:
image: *python_image
commands:
- pip install --quiet ruff
- |
if [ -d "backend-core" ]; then
ruff check backend-core/ --output-format=github || true
fi
if [ -d "night-scheduler" ]; then
ruff check night-scheduler/ --output-format=github || true
fi
when:
event: pull_request
nodejs-lint:
image: *nodejs_image
commands:
- |
if [ -d "admin-core" ]; then
cd admin-core
npm ci --silent 2>/dev/null || npm install --silent
npx next lint || true
fi
when:
event: pull_request
# ========================================
# STAGE 2: Unit Tests mit JSON-Ausgabe
# Ergebnisse werden im Workspace gespeichert (.ci-results/)
# ========================================
test-go-consent:
image: *golang_image
environment:
CGO_ENABLED: "0"
commands:
- |
set -euo pipefail
apk add --no-cache jq bash
mkdir -p .ci-results
if [ ! -d "consent-service" ]; then
echo '{"service":"consent-service","framework":"go","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-consent.json
echo "WARNUNG: consent-service Verzeichnis nicht gefunden"
exit 0
fi
cd consent-service
set +e
go test -v -json -coverprofile=coverage.out ./... 2>&1 | tee ../.ci-results/test-consent.json
TEST_EXIT=$?
set -e
JSON_FILE="../.ci-results/test-consent.json"
if grep -q '^{' "$JSON_FILE" 2>/dev/null; then
TOTAL=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="run" and .Test != null)] | length')
PASSED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="pass" and .Test != null)] | length')
FAILED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="fail" and .Test != null)] | length')
SKIPPED=$(grep '^{' "$JSON_FILE" | jq -s '[.[] | select(.Action=="skip" and .Test != null)] | length')
else
echo "WARNUNG: Keine JSON-Zeilen in $JSON_FILE gefunden (Build-Fehler?)"
TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0
fi
COVERAGE=$(go tool cover -func=coverage.out 2>/dev/null | tail -1 | awk '{print $3}' | tr -d '%' || echo "0")
[ -z "$COVERAGE" ] && COVERAGE=0
echo "{\"service\":\"consent-service\",\"framework\":\"go\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":$COVERAGE}" > ../.ci-results/results-consent.json
cat ../.ci-results/results-consent.json
# Backlog-Strategie: Fehler werden gemeldet aber Pipeline laeuft weiter
if [ "$FAILED" -gt "0" ]; then
echo "WARNUNG: $FAILED Tests fehlgeschlagen - werden ins Backlog geschrieben"
fi
# ========================================
# STAGE 3: Test-Ergebnisse an Dashboard senden
# ========================================
report-test-results:
image: curlimages/curl:8.10.1
commands:
- |
set -uo pipefail
echo "=== Sende Test-Ergebnisse an Dashboard ==="
echo "Pipeline Status: ${CI_PIPELINE_STATUS:-unknown}"
ls -la .ci-results/ || echo "Verzeichnis nicht gefunden"
PIPELINE_STATUS="${CI_PIPELINE_STATUS:-unknown}"
for f in .ci-results/results-*.json; do
[ -f "$f" ] || continue
echo "Sending: $f"
curl -f -sS -X POST "http://backend:8000/api/tests/ci-result" \
-H "Content-Type: application/json" \
-d "{
\"pipeline_id\": \"${CI_PIPELINE_NUMBER}\",
\"commit\": \"${CI_COMMIT_SHA}\",
\"branch\": \"${CI_COMMIT_BRANCH}\",
\"repo\": \"breakpilot-core\",
\"status\": \"${PIPELINE_STATUS}\",
\"test_results\": $(cat "$f")
}" || echo "WARNUNG: Konnte $f nicht senden"
done
echo "=== Test-Ergebnisse gesendet ==="
when:
status: [success, failure]
depends_on:
- test-go-consent
# ========================================
# STAGE 4: Build & Security (nur Tags/manuell)
# ========================================
build-consent-service:
image: *docker_image
commands:
- |
if [ -d ./consent-service ]; then
docker build -t breakpilot/consent-service:${CI_COMMIT_SHA:0:8} ./consent-service
docker tag breakpilot/consent-service:${CI_COMMIT_SHA:0:8} breakpilot/consent-service:latest
echo "Built breakpilot/consent-service:${CI_COMMIT_SHA:0:8}"
else
echo "consent-service Verzeichnis nicht gefunden - ueberspringe"
fi
when:
- event: tag
- event: manual
build-backend-core:
image: *docker_image
commands:
- |
if [ -d ./backend-core ]; then
docker build -t breakpilot/backend-core:${CI_COMMIT_SHA:0:8} ./backend-core
docker tag breakpilot/backend-core:${CI_COMMIT_SHA:0:8} breakpilot/backend-core:latest
echo "Built breakpilot/backend-core:${CI_COMMIT_SHA:0:8}"
else
echo "backend-core Verzeichnis nicht gefunden - ueberspringe"
fi
when:
- event: tag
- event: manual
build-admin-core:
image: *docker_image
commands:
- |
if [ -d ./admin-core ]; then
docker build -t breakpilot/admin-core:${CI_COMMIT_SHA:0:8} ./admin-core
docker tag breakpilot/admin-core:${CI_COMMIT_SHA:0:8} breakpilot/admin-core:latest
echo "Built breakpilot/admin-core:${CI_COMMIT_SHA:0:8}"
else
echo "admin-core Verzeichnis nicht gefunden - ueberspringe"
fi
when:
- event: tag
- event: manual
generate-sbom:
image: *golang_image
commands:
- |
echo "Installing syft for ARM64..."
wget -qO- https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
if [ -d ./consent-service ]; then
syft dir:./consent-service -o cyclonedx-json > sbom-consent.json
fi
if [ -d ./backend-core ]; then
syft dir:./backend-core -o cyclonedx-json > sbom-backend-core.json
fi
echo "SBOMs generated successfully"
when:
- event: tag
- event: manual
vulnerability-scan:
image: *golang_image
commands:
- |
echo "Installing grype for ARM64..."
wget -qO- https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin
if [ -f sbom-consent.json ]; then
grype sbom:sbom-consent.json -o table --fail-on critical || true
fi
if [ -f sbom-backend-core.json ]; then
grype sbom:sbom-backend-core.json -o table --fail-on critical || true
fi
when:
- event: tag
- event: manual
depends_on:
- generate-sbom
# ========================================
# STAGE 5: Deploy (nur manuell)
# ========================================
deploy-production:
image: *docker_image
commands:
- echo "Deploying breakpilot-core to production..."
- docker compose -f docker-compose.yml pull || true
- docker compose -f docker-compose.yml up -d --remove-orphans || true
when:
event: manual
depends_on:
- build-consent-service
- build-backend-core
- build-admin-core