diff --git a/.woodpecker/main.yml b/.woodpecker/main.yml new file mode 100644 index 0000000..346bb05 --- /dev/null +++ b/.woodpecker/main.yml @@ -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