From 8dc1b4c67fd7be2673fdd04334f5c8e376b3aff9 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Thu, 5 Mar 2026 23:05:08 +0100 Subject: [PATCH] =?UTF-8?q?chore:=20Woodpecker=20CI=20entfernt=20=E2=80=94?= =?UTF-8?q?=20nur=20noch=20Gitea=20Actions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Woodpecker wird nicht mehr verwendet. Wir migrieren vollstaendig auf Gitea Actions (gitea.meghsakha.com). Entfernt: - woodpecker-server + woodpecker-agent Container (docker-compose.yml) - woodpecker_data Volume - backend-core/woodpecker_proxy_api.py (SQLite-DB Proxy) - admin-core/app/api/admin/infrastructure/woodpecker/route.ts - admin-core/app/api/webhooks/woodpecker/route.ts - .woodpecker/main.yml (alte CI-Pipeline-Konfiguration) Bereinigt: - ci-cd/page.tsx: Woodpecker-Tab + Status-Karte + State entfernt - types/infrastructure-modules.ts: Woodpecker-Typen + API-Endpunkte - DevOpsPipelineSidebar.tsx: Textbeschreibungen auf Gitea Actions - dashboard/page.tsx: Woodpecker aus Service-Health-Liste - sbom/page.tsx: Woodpecker aus SBOM-Liste - navigation.ts: Beschreibung aktualisiert - .env.example: WOODPECKER_* Variablen entfernt Co-Authored-By: Claude Opus 4.6 --- .env.example | 5 - .woodpecker/main.yml | 422 ---------------- admin-core/app/(admin)/dashboard/page.tsx | 1 - .../app/(admin)/infrastructure/ci-cd/page.tsx | 470 +----------------- .../app/(admin)/infrastructure/sbom/page.tsx | 3 +- .../app/(admin)/infrastructure/tests/page.tsx | 2 +- .../admin/infrastructure/woodpecker/route.ts | 271 ---------- .../app/api/webhooks/woodpecker/route.ts | 273 ---------- .../infrastructure/DevOpsPipelineSidebar.tsx | 24 +- admin-core/lib/navigation.ts | 2 +- admin-core/types/infrastructure-modules.ts | 31 +- backend-core/main.py | 3 - backend-core/woodpecker_proxy_api.py | 133 ----- docker-compose.yml | 54 -- 14 files changed, 13 insertions(+), 1681 deletions(-) delete mode 100644 .woodpecker/main.yml delete mode 100644 admin-core/app/api/admin/infrastructure/woodpecker/route.ts delete mode 100644 admin-core/app/api/webhooks/woodpecker/route.ts delete mode 100644 backend-core/woodpecker_proxy_api.py diff --git a/.env.example b/.env.example index cd027b4..ba66286 100644 --- a/.env.example +++ b/.env.example @@ -46,11 +46,6 @@ ERPNEXT_DB_ROOT_PASSWORD=erpnext_root ERPNEXT_DB_PASSWORD=erpnext_secret ERPNEXT_ADMIN_PASSWORD=admin -# Woodpecker CI -WOODPECKER_HOST=http://macmini:8090 -WOODPECKER_ADMIN=pilotadmin -WOODPECKER_AGENT_SECRET=woodpecker-secret - # Gitea Runner GITEA_RUNNER_TOKEN= diff --git a/.woodpecker/main.yml b/.woodpecker/main.yml deleted file mode 100644 index 3e8a8a9..0000000 --- a/.woodpecker/main.yml +++ /dev/null @@ -1,422 +0,0 @@ -# Woodpecker CI Main Pipeline -# BreakPilot Core - CI/CD Pipeline -# -# Plattform: ARM64 (Apple Silicon Mac Mini) -# -# Services: -# Go: consent-service -# Python: backend-core, voice-service (+ BQAS), embedding-service, night-scheduler -# Node.js: admin-core -# -# 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 - - | - for svc in backend-core voice-service night-scheduler embedding-service; do - if [ -d "$svc" ]; then - echo "=== Linting $svc ===" - ruff check "$svc/" --output-format=github || true - fi - done - 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 - - test-python-voice: - image: *python_image - environment: - CI: "true" - commands: - - | - set -uo pipefail - mkdir -p .ci-results - - if [ ! -d "voice-service" ]; then - echo '{"service":"voice-service","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-voice.json - echo "WARNUNG: voice-service Verzeichnis nicht gefunden" - exit 0 - fi - - cd voice-service - export PYTHONPATH="$(pwd):${PYTHONPATH:-}" - pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true - pip install --quiet --no-cache-dir fastapi uvicorn pydantic pytest pytest-json-report - - set +e - python -m pytest tests/ -v --tb=short --ignore=tests/bqas --json-report --json-report-file=../.ci-results/test-voice.json - TEST_EXIT=$? - set -e - - if [ -f ../.ci-results/test-voice.json ]; then - TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") - PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") - FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") - SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-voice.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") - else - TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 - fi - - echo "{\"service\":\"voice-service\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-voice.json - cat ../.ci-results/results-voice.json - - if [ "$TEST_EXIT" -ne "0" ]; then exit 1; fi - - test-bqas-golden: - image: *python_image - commands: - - | - set -uo pipefail - mkdir -p .ci-results - - if [ ! -d "voice-service/tests/bqas" ]; then - echo '{"service":"bqas-golden","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-bqas-golden.json - echo "WARNUNG: voice-service/tests/bqas Verzeichnis nicht gefunden" - exit 0 - fi - - cd voice-service - export PYTHONPATH="$(pwd):${PYTHONPATH:-}" - pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true - pip install --quiet --no-cache-dir fastapi uvicorn pydantic pytest pytest-json-report pytest-asyncio - - set +e - python -m pytest tests/bqas/test_golden.py tests/bqas/test_regression.py tests/bqas/test_synthetic.py -v --tb=short --json-report --json-report-file=../.ci-results/test-bqas-golden.json - TEST_EXIT=$? - set -e - - if [ -f ../.ci-results/test-bqas-golden.json ]; then - TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") - PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") - FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") - SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-golden.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") - else - TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 - fi - - echo "{\"service\":\"bqas-golden\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-bqas-golden.json - cat ../.ci-results/results-bqas-golden.json - - # BQAS tests may skip if Ollama not available - don't fail pipeline - if [ "$FAILED" -gt "0" ]; then exit 1; fi - - test-bqas-rag: - image: *python_image - commands: - - | - set -uo pipefail - mkdir -p .ci-results - - if [ ! -d "voice-service/tests/bqas" ]; then - echo '{"service":"bqas-rag","framework":"pytest","total":0,"passed":0,"failed":0,"skipped":0,"coverage":0}' > .ci-results/results-bqas-rag.json - echo "WARNUNG: voice-service/tests/bqas Verzeichnis nicht gefunden" - exit 0 - fi - - cd voice-service - export PYTHONPATH="$(pwd):${PYTHONPATH:-}" - pip install --quiet --no-cache-dir -r requirements.txt 2>/dev/null || true - pip install --quiet --no-cache-dir fastapi uvicorn pydantic pytest pytest-json-report pytest-asyncio - - set +e - python -m pytest tests/bqas/test_rag.py tests/bqas/test_notifier.py -v --tb=short --json-report --json-report-file=../.ci-results/test-bqas-rag.json - TEST_EXIT=$? - set -e - - if [ -f ../.ci-results/test-bqas-rag.json ]; then - TOTAL=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('total',0))" 2>/dev/null || echo "0") - PASSED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('passed',0))" 2>/dev/null || echo "0") - FAILED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('failed',0))" 2>/dev/null || echo "0") - SKIPPED=$(python3 -c "import json; d=json.load(open('../.ci-results/test-bqas-rag.json')); print(d.get('summary',{}).get('skipped',0))" 2>/dev/null || echo "0") - else - TOTAL=0; PASSED=0; FAILED=0; SKIPPED=0 - fi - - echo "{\"service\":\"bqas-rag\",\"framework\":\"pytest\",\"total\":$TOTAL,\"passed\":$PASSED,\"failed\":$FAILED,\"skipped\":$SKIPPED,\"coverage\":0}" > ../.ci-results/results-bqas-rag.json - cat ../.ci-results/results-bqas-rag.json - - # BQAS tests may skip if Ollama not available - don't fail pipeline - if [ "$FAILED" -gt "0" ]; then exit 1; 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 - - test-python-voice - - test-bqas-golden - - test-bqas-rag - - # ======================================== - # 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 - - build-voice-service: - image: *docker_image - commands: - - | - if [ -d ./voice-service ]; then - docker build -t breakpilot/voice-service:${CI_COMMIT_SHA:0:8} ./voice-service - docker tag breakpilot/voice-service:${CI_COMMIT_SHA:0:8} breakpilot/voice-service:latest - echo "Built breakpilot/voice-service:${CI_COMMIT_SHA:0:8}" - else - echo "voice-service Verzeichnis nicht gefunden - ueberspringe" - fi - when: - - event: tag - - event: manual - - build-embedding-service: - image: *docker_image - commands: - - | - if [ -d ./embedding-service ]; then - docker build -t breakpilot/embedding-service:${CI_COMMIT_SHA:0:8} ./embedding-service - docker tag breakpilot/embedding-service:${CI_COMMIT_SHA:0:8} breakpilot/embedding-service:latest - echo "Built breakpilot/embedding-service:${CI_COMMIT_SHA:0:8}" - else - echo "embedding-service Verzeichnis nicht gefunden - ueberspringe" - fi - when: - - event: tag - - event: manual - - build-night-scheduler: - image: *docker_image - commands: - - | - if [ -d ./night-scheduler ]; then - docker build -t breakpilot/night-scheduler:${CI_COMMIT_SHA:0:8} ./night-scheduler - docker tag breakpilot/night-scheduler:${CI_COMMIT_SHA:0:8} breakpilot/night-scheduler:latest - echo "Built breakpilot/night-scheduler:${CI_COMMIT_SHA:0:8}" - else - echo "night-scheduler 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 - for svc in consent-service backend-core voice-service embedding-service night-scheduler; do - if [ -d "./$svc" ]; then - syft dir:./$svc -o cyclonedx-json > sbom-$svc.json - echo "SBOM generated for $svc" - fi - done - 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 - for f in sbom-*.json; do - [ -f "$f" ] || continue - echo "=== Scanning $f ===" - grype sbom:"$f" -o table --fail-on critical || true - done - 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 - - build-voice-service - - build-embedding-service - - build-night-scheduler diff --git a/admin-core/app/(admin)/dashboard/page.tsx b/admin-core/app/(admin)/dashboard/page.tsx index 7b8ab5c..807edae 100644 --- a/admin-core/app/(admin)/dashboard/page.tsx +++ b/admin-core/app/(admin)/dashboard/page.tsx @@ -27,7 +27,6 @@ export default function DashboardPage() { { name: 'Jitsi Meet', status: 'unknown' }, { name: 'Mailpit', status: 'unknown' }, { name: 'Gitea', status: 'unknown' }, - { name: 'Woodpecker CI', status: 'unknown' }, { name: 'Backend Core', status: 'unknown' }, ] diff --git a/admin-core/app/(admin)/infrastructure/ci-cd/page.tsx b/admin-core/app/(admin)/infrastructure/ci-cd/page.tsx index f72b31b..f30ba01 100644 --- a/admin-core/app/(admin)/infrastructure/ci-cd/page.tsx +++ b/admin-core/app/(admin)/infrastructure/ci-cd/page.tsx @@ -85,38 +85,7 @@ interface DockerStats { stopped_containers: number } -type TabType = 'overview' | 'woodpecker' | 'pipelines' | 'deployments' | 'setup' | 'scheduler' - -// Woodpecker Types -interface WoodpeckerStep { - name: string - state: 'pending' | 'running' | 'success' | 'failure' | 'skipped' - exit_code: number - error?: string -} - -interface WoodpeckerPipeline { - id: number - number: number - status: 'pending' | 'running' | 'success' | 'failure' | 'error' - event: string - branch: string - commit: string - message: string - author: string - created: number - started: number - finished: number - steps: WoodpeckerStep[] - errors?: string[] -} - -interface WoodpeckerStatus { - status: 'online' | 'offline' - pipelines: WoodpeckerPipeline[] - lastUpdate: string - error?: string -} +type TabType = 'overview' | 'pipelines' | 'deployments' | 'setup' | 'scheduler' // ============================================================================ // Helper Components @@ -168,10 +137,6 @@ export default function CICDPage() { const [containerFilter, setContainerFilter] = useState<'all' | 'running' | 'stopped'>('all') const [actionLoading, setActionLoading] = useState(null) - // Woodpecker State - const [woodpeckerStatus, setWoodpeckerStatus] = useState(null) - const [triggeringWoodpecker, setTriggeringWoodpecker] = useState(false) - // General State const [loading, setLoading] = useState(true) const [error, setError] = useState(null) @@ -214,54 +179,12 @@ export default function CICDPage() { } }, []) - const loadWoodpeckerData = useCallback(async () => { - try { - const response = await fetch('/api/admin/infrastructure/woodpecker?limit=10') - if (response.ok) { - const data = await response.json() - setWoodpeckerStatus(data) - } - } catch (err) { - console.error('Failed to load Woodpecker data:', err) - setWoodpeckerStatus({ - status: 'offline', - pipelines: [], - lastUpdate: new Date().toISOString(), - error: 'Verbindung fehlgeschlagen' - }) - } - }, []) - - const triggerWoodpeckerPipeline = async () => { - setTriggeringWoodpecker(true) - setMessage(null) - try { - const response = await fetch('/api/admin/infrastructure/woodpecker', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ branch: 'main' }) - }) - if (response.ok) { - const result = await response.json() - setMessage(`Woodpecker Pipeline #${result.pipeline?.number || '?'} gestartet!`) - setTimeout(loadWoodpeckerData, 2000) - setTimeout(loadWoodpeckerData, 5000) - } else { - setError('Pipeline-Start fehlgeschlagen') - } - } catch (err) { - setError('Pipeline konnte nicht gestartet werden') - } finally { - setTriggeringWoodpecker(false) - } - } - const loadAllData = useCallback(async () => { setLoading(true) setError(null) - await Promise.all([loadPipelineData(), loadContainerData(), loadWoodpeckerData()]) + await Promise.all([loadPipelineData(), loadContainerData()]) setLoading(false) - }, [loadPipelineData, loadContainerData, loadWoodpeckerData]) + }, [loadPipelineData, loadContainerData]) useEffect(() => { loadAllData() @@ -402,11 +325,6 @@ export default function CICDPage() { )}, - { id: 'woodpecker', name: 'Woodpecker CI', icon: ( - - - - )}, { id: 'pipelines', name: 'Gitea Pipelines', icon: ( @@ -458,95 +376,6 @@ export default function CICDPage() { {/* ================================================================ */} {activeTab === 'overview' && (
- {/* Woodpecker CI Status - Prominent */} -
-
-
-
- - - -
-
-
-

Woodpecker CI

- - {woodpeckerStatus?.status === 'online' ? 'Online' : 'Offline'} - -
- {woodpeckerStatus?.pipelines?.[0] && ( -

- Pipeline #{woodpeckerStatus.pipelines[0].number}: {' '} - - {woodpeckerStatus.pipelines[0].status} - - {' '}auf {woodpeckerStatus.pipelines[0].branch} -

- )} -
-
-
- - -
-
- {/* Failed steps preview */} - {woodpeckerStatus?.pipelines?.[0]?.steps?.some(s => s.state === 'failure') && ( -
-

Fehlgeschlagene Steps:

-
- {woodpeckerStatus.pipelines[0].steps.filter(s => s.state === 'failure').map((step, i) => ( - - {step.name} - - ))} -
-
- )} -
- {/* Status Cards */}
@@ -679,299 +508,6 @@ export default function CICDPage() {
)} - {/* ================================================================ */} - {/* Woodpecker Tab */} - {/* ================================================================ */} - {activeTab === 'woodpecker' && ( -
- {/* Woodpecker Status Header */} -
-
-

Woodpecker CI Pipeline

- - - {woodpeckerStatus?.status === 'online' ? 'Online' : 'Offline'} - -
-
- - - - - Woodpecker UI - - -
-
- - {/* Pipeline Stats */} -
-
-
- - - - Gesamt -
-

{woodpeckerStatus?.pipelines?.length || 0}

-
-
-
- - - - Erfolgreich -
-

- {woodpeckerStatus?.pipelines?.filter(p => p.status === 'success').length || 0} -

-
-
-
- - - - Fehlgeschlagen -
-

- {woodpeckerStatus?.pipelines?.filter(p => p.status === 'failure' || p.status === 'error').length || 0} -

-
-
-
- - - - Laufend -
-

- {woodpeckerStatus?.pipelines?.filter(p => p.status === 'running' || p.status === 'pending').length || 0} -

-
-
- - {/* Pipeline List */} - {woodpeckerStatus?.pipelines && woodpeckerStatus.pipelines.length > 0 ? ( -
-

Pipeline Historie

-
- {woodpeckerStatus.pipelines.map((pipeline) => ( -
-
-
-
- - Pipeline #{pipeline.number} - - {pipeline.status} - -
-
- {pipeline.branch} - - {pipeline.commit} - - {pipeline.event} -
- {pipeline.message && ( -

{pipeline.message}

- )} - - {/* Steps Progress */} - {pipeline.steps && pipeline.steps.length > 0 && ( -
-
- {pipeline.steps.map((step, i) => ( -
- ))} -
-
- {pipeline.steps.map((step, i) => ( - - {step.name} - - ))} -
-
- )} - - {/* Errors */} - {pipeline.errors && pipeline.errors.length > 0 && ( -
-
Fehler:
-
    - {pipeline.errors.map((err, i) => ( -
  • {err}
  • - ))} -
-
- )} -
- -
-

{new Date(pipeline.created * 1000).toLocaleDateString('de-DE')}

-

{new Date(pipeline.created * 1000).toLocaleTimeString('de-DE')}

- {pipeline.started && pipeline.finished && ( -

- Dauer: {Math.round((pipeline.finished - pipeline.started) / 60)}m -

- )} -
-
-
- ))} -
-
- ) : ( -
- - - -

Keine Pipelines gefunden

-

Starte eine neue Pipeline oder pruefe die Woodpecker-Konfiguration

-
- )} - - {/* Pipeline Configuration Info */} -
-

Pipeline Konfiguration

-
-{`Woodpecker CI Pipeline (.woodpecker/main.yml)
-     │
-     ├── 1. go-lint          → Go Linting (PR only)
-     ├── 2. python-lint      → Python Linting (PR only)
-     ├── 3. secrets-scan     → GitLeaks Secrets Scan
-     │
-     ├── 4. test-go-consent  → Go Unit Tests
-     ├── 5. test-go-billing  → Billing Service Tests
-     ├── 6. test-go-school   → School Service Tests
-     ├── 7. test-python      → Python Backend Tests
-     │
-     ├── 8. build-images     → Docker Image Build
-     ├── 9. generate-sbom    → SBOM Generation (Syft)
-     ├── 10. vuln-scan       → Vulnerability Scan (Grype)
-     ├── 11. container-scan  → Container Scan (Trivy)
-     │
-     ├── 12. sign-images     → Cosign Image Signing
-     ├── 13. attest-sbom     → SBOM Attestation
-     ├── 14. provenance      → SLSA Provenance
-     │
-     └── 15. deploy-prod     → Production Deployment`}
-                    
-
- - {/* Workflow Anleitung */} -
-

- - - - Workflow-Anleitung -

-
-
-
🤖 Automatisch (bei jedem Push/PR):
-
    -
  • Linting - Code-Qualitaet pruefen (nur PRs)
  • -
  • Unit Tests - Go & Python Tests
  • -
  • Test-Dashboard - Ergebnisse werden gesendet
  • -
  • Backlog - Fehlgeschlagene Tests werden erfasst
  • -
-
-
-
👆 Manuell (Button oder Tag):
-
    -
  • Docker Builds - Container erstellen
  • -
  • SBOM/Scans - Sicherheitsanalyse
  • -
  • Deployment - In Produktion deployen
  • -
  • Pipeline starten - Diesen Button verwenden
  • -
-
-
-
-
⚙️ Setup: API Token konfigurieren
-

- Um Pipelines ueber das Dashboard zu starten, muss ein WOODPECKER_TOKEN konfiguriert werden: -

-
    -
  1. Woodpecker UI oeffnen: http://macmini:8090
  2. -
  3. Mit Gitea-Account einloggen
  4. -
  5. Klick auf Profil → User SettingsPersonal Access Tokens
  6. -
  7. Neues Token erstellen und in .env eintragen: WOODPECKER_TOKEN=...
  8. -
  9. Container neu starten: docker compose up -d admin-v2
  10. -
-
-
-
- )} - {/* ================================================================ */} {/* Pipelines Tab */} {/* ================================================================ */} diff --git a/admin-core/app/(admin)/infrastructure/sbom/page.tsx b/admin-core/app/(admin)/infrastructure/sbom/page.tsx index 12fa6e1..a35dc10 100644 --- a/admin-core/app/(admin)/infrastructure/sbom/page.tsx +++ b/admin-core/app/(admin)/infrastructure/sbom/page.tsx @@ -110,8 +110,7 @@ const INFRASTRUCTURE_COMPONENTS: Component[] = [ { type: 'service', name: 'ERPNext', version: 'v15', category: 'erp', port: '8090', description: 'Open Source ERP System', license: 'GPL-3.0', sourceUrl: 'https://github.com/frappe/erpnext' }, // ===== CI/CD & VERSION CONTROL ===== - { type: 'service', name: 'Woodpecker CI', version: '2.x', category: 'cicd', port: '8082', description: 'Self-hosted CI/CD Pipeline (Drone Fork)', license: 'Apache-2.0', sourceUrl: 'https://github.com/woodpecker-ci/woodpecker' }, - { type: 'service', name: 'Gitea', version: '1.21', category: 'cicd', port: '3003', description: 'Self-hosted Git Service', license: 'MIT', sourceUrl: 'https://github.com/go-gitea/gitea' }, + { type: 'service', name: 'Gitea', version: '1.21', category: 'cicd', port: '3003', description: 'Self-hosted Git Service with Actions CI/CD', license: 'MIT', sourceUrl: 'https://github.com/go-gitea/gitea' }, { type: 'service', name: 'Dokploy', version: '0.26.7', category: 'cicd', port: '3000', description: 'Self-hosted PaaS (Vercel/Heroku Alternative)', license: 'Apache-2.0', sourceUrl: 'https://github.com/Dokploy/dokploy' }, // ===== DEVELOPMENT ===== diff --git a/admin-core/app/(admin)/infrastructure/tests/page.tsx b/admin-core/app/(admin)/infrastructure/tests/page.tsx index d361dfe..ce4e3dd 100644 --- a/admin-core/app/(admin)/infrastructure/tests/page.tsx +++ b/admin-core/app/(admin)/infrastructure/tests/page.tsx @@ -639,7 +639,7 @@ Tests bleiben wo sie sind:

- Daten-Fluss: Woodpecker CI → POST /api/tests/ci-result → PostgreSQL → Test Dashboard + Daten-Fluss: Gitea Actions → POST /api/tests/ci-result → PostgreSQL → Test Dashboard

diff --git a/admin-core/app/api/admin/infrastructure/woodpecker/route.ts b/admin-core/app/api/admin/infrastructure/woodpecker/route.ts deleted file mode 100644 index 7f9fa3a..0000000 --- a/admin-core/app/api/admin/infrastructure/woodpecker/route.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' - -// Woodpecker API configuration -const WOODPECKER_URL = process.env.WOODPECKER_URL || 'http://woodpecker-server:8000' -const WOODPECKER_TOKEN = process.env.WOODPECKER_TOKEN || '' -const BACKEND_URL = process.env.BACKEND_URL || 'http://backend-core:8000' - -export interface PipelineStep { - name: string - state: 'pending' | 'running' | 'success' | 'failure' | 'skipped' - exit_code: number - error?: string -} - -export interface Pipeline { - id: number - number: number - status: 'pending' | 'running' | 'success' | 'failure' | 'error' - event: string - branch: string - commit: string - message: string - author: string - created: number - started: number - finished: number - steps: PipelineStep[] - errors?: string[] - repo_name?: string -} - -export interface WoodpeckerStatusResponse { - status: 'online' | 'offline' - pipelines: Pipeline[] - lastUpdate: string - error?: string -} - -async function fetchFromBackendProxy(repoId: string, limit: number): Promise { - // Use backend-core proxy that reads Woodpecker sqlite DB directly - const url = `${BACKEND_URL}/api/v1/woodpecker/pipelines?repo=${repoId}&limit=${limit}` - const response = await fetch(url, { cache: 'no-store' }) - - if (!response.ok) { - return { - status: 'offline', - pipelines: [], - lastUpdate: new Date().toISOString(), - error: `Backend Woodpecker Proxy Fehler (${response.status})` - } - } - - const data = await response.json() - return { - status: data.status || 'online', - pipelines: (data.pipelines || []).map((p: any) => ({ - id: p.id, - number: p.number, - status: p.status, - event: p.event, - branch: p.branch || 'main', - commit: p.commit || '', - message: p.message || '', - author: p.author || '', - created: p.created, - started: p.started, - finished: p.finished, - repo_name: p.repo_name, - steps: (p.steps || []).map((s: any) => ({ - name: s.name, - state: s.state, - exit_code: s.exit_code || 0, - error: s.error - })), - })), - lastUpdate: data.lastUpdate || new Date().toISOString(), - } -} - -async function fetchFromWoodpeckerAPI(repoId: string, limit: number): Promise { - const response = await fetch( - `${WOODPECKER_URL}/api/repos/${repoId}/pipelines?per_page=${limit}`, - { - headers: { - 'Authorization': `Bearer ${WOODPECKER_TOKEN}`, - 'Content-Type': 'application/json', - }, - cache: 'no-store', - } - ) - - if (!response.ok) { - return { - status: 'offline', - pipelines: [], - lastUpdate: new Date().toISOString(), - error: `Woodpecker API nicht erreichbar (${response.status})` - } - } - - const rawPipelines = await response.json() - - const pipelines: Pipeline[] = rawPipelines.map((p: any) => { - const errors: string[] = [] - const steps: PipelineStep[] = [] - - if (p.workflows) { - for (const workflow of p.workflows) { - if (workflow.children) { - for (const child of workflow.children) { - steps.push({ - name: child.name, - state: child.state, - exit_code: child.exit_code, - error: child.error - }) - if (child.state === 'failure' && child.error) { - errors.push(`${child.name}: ${child.error}`) - } - } - } - } - } - - return { - id: p.id, - number: p.number, - status: p.status, - event: p.event, - branch: p.branch, - commit: p.commit?.substring(0, 7) || '', - message: p.message || '', - author: p.author, - created: p.created, - started: p.started, - finished: p.finished, - steps, - errors: errors.length > 0 ? errors : undefined - } - }) - - return { - status: 'online', - pipelines, - lastUpdate: new Date().toISOString() - } -} - -export async function GET(request: NextRequest) { - const searchParams = request.nextUrl.searchParams - const repoId = searchParams.get('repo') || '0' - const limit = parseInt(searchParams.get('limit') || '10') - - try { - // If WOODPECKER_TOKEN is set, use the Woodpecker API directly - // Otherwise, use the backend proxy that reads the sqlite DB - if (WOODPECKER_TOKEN) { - return NextResponse.json(await fetchFromWoodpeckerAPI(repoId, limit)) - } else { - return NextResponse.json(await fetchFromBackendProxy(repoId, limit)) - } - } catch (error) { - console.error('Woodpecker API error:', error) - return NextResponse.json({ - status: 'offline', - pipelines: [], - lastUpdate: new Date().toISOString(), - error: 'Fehler beim Abrufen des Woodpecker Status' - } as WoodpeckerStatusResponse) - } -} - -// Trigger a new pipeline -export async function POST(request: NextRequest) { - try { - const body = await request.json() - const { repoId = '1', branch = 'main' } = body - - if (!WOODPECKER_TOKEN) { - return NextResponse.json( - { error: 'WOODPECKER_TOKEN nicht konfiguriert - Pipeline-Start nicht moeglich' }, - { status: 503 } - ) - } - - const response = await fetch( - `${WOODPECKER_URL}/api/repos/${repoId}/pipelines`, - { - method: 'POST', - headers: { - 'Authorization': `Bearer ${WOODPECKER_TOKEN}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ branch }), - } - ) - - if (!response.ok) { - return NextResponse.json( - { error: 'Pipeline konnte nicht gestartet werden' }, - { status: 500 } - ) - } - - const pipeline = await response.json() - return NextResponse.json({ - success: true, - pipeline: { - id: pipeline.id, - number: pipeline.number, - status: pipeline.status - } - }) - - } catch (error) { - console.error('Pipeline trigger error:', error) - return NextResponse.json( - { error: 'Fehler beim Starten der Pipeline' }, - { status: 500 } - ) - } -} - -// Get pipeline logs -export async function PUT(request: NextRequest) { - try { - const body = await request.json() - const { repoId = '1', pipelineNumber, stepId } = body - - if (!pipelineNumber || !stepId) { - return NextResponse.json( - { error: 'pipelineNumber und stepId erforderlich' }, - { status: 400 } - ) - } - - if (!WOODPECKER_TOKEN) { - return NextResponse.json( - { error: 'WOODPECKER_TOKEN nicht konfiguriert' }, - { status: 503 } - ) - } - - const response = await fetch( - `${WOODPECKER_URL}/api/repos/${repoId}/pipelines/${pipelineNumber}/logs/${stepId}`, - { - headers: { - 'Authorization': `Bearer ${WOODPECKER_TOKEN}`, - 'Content-Type': 'application/json', - }, - } - ) - - if (!response.ok) { - return NextResponse.json( - { error: 'Logs nicht verfuegbar' }, - { status: response.status } - ) - } - - const logs = await response.json() - return NextResponse.json({ logs }) - - } catch (error) { - console.error('Pipeline logs error:', error) - return NextResponse.json( - { error: 'Fehler beim Abrufen der Logs' }, - { status: 500 } - ) - } -} diff --git a/admin-core/app/api/webhooks/woodpecker/route.ts b/admin-core/app/api/webhooks/woodpecker/route.ts deleted file mode 100644 index c73a93e..0000000 --- a/admin-core/app/api/webhooks/woodpecker/route.ts +++ /dev/null @@ -1,273 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import type { WoodpeckerWebhookPayload, ExtractedError, BacklogSource } from '@/types/infrastructure-modules' - -// ============================================================================= -// Configuration -// ============================================================================= - -// Webhook secret for verification (optional but recommended) -const WEBHOOK_SECRET = process.env.WOODPECKER_WEBHOOK_SECRET || '' - -// Internal API URL for log extraction -const LOG_EXTRACT_URL = process.env.NEXT_PUBLIC_APP_URL - ? `${process.env.NEXT_PUBLIC_APP_URL}/api/infrastructure/log-extract/extract` - : 'http://localhost:3002/api/infrastructure/log-extract/extract' - -// Test service API URL for backlog insertion -const TEST_SERVICE_URL = process.env.TEST_SERVICE_URL || 'http://localhost:8086' - -// ============================================================================= -// Helper Functions -// ============================================================================= - -/** - * Verify webhook signature (if secret is configured) - */ -function verifySignature(request: NextRequest, body: string): boolean { - if (!WEBHOOK_SECRET) return true // Skip verification if no secret configured - - const signature = request.headers.get('X-Woodpecker-Signature') - if (!signature) return false - - // Simple HMAC verification (Woodpecker uses SHA256) - const crypto = require('crypto') - const expectedSignature = crypto - .createHmac('sha256', WEBHOOK_SECRET) - .update(body) - .digest('hex') - - return signature === `sha256=${expectedSignature}` -} - -/** - * Map error category to backlog priority - */ -function categoryToPriority(category: string): 'critical' | 'high' | 'medium' | 'low' { - switch (category) { - case 'security_warning': - return 'critical' - case 'build_error': - return 'high' - case 'license_violation': - return 'high' - case 'test_failure': - return 'medium' - case 'dependency_issue': - return 'low' - default: - return 'medium' - } -} - -/** - * Map error category to error_type for backlog - */ -function categoryToErrorType(category: string): string { - switch (category) { - case 'security_warning': - return 'security' - case 'build_error': - return 'build' - case 'license_violation': - return 'license' - case 'test_failure': - return 'test' - case 'dependency_issue': - return 'dependency' - default: - return 'unknown' - } -} - -/** - * Insert extracted errors into backlog - */ -async function insertIntoBacklog( - errors: ExtractedError[], - pipelineNumber: number, - source: BacklogSource -): Promise<{ inserted: number; failed: number }> { - let inserted = 0 - let failed = 0 - - for (const error of errors) { - try { - // Create backlog item - const backlogItem = { - test_name: error.message.substring(0, 200), // Truncate long messages - test_file: error.file_path || null, - service: error.service || 'unknown', - framework: `ci_cd_pipeline_${pipelineNumber}`, - error_message: error.message, - error_type: categoryToErrorType(error.category), - status: 'open', - priority: categoryToPriority(error.category), - fix_suggestion: error.suggested_fix || null, - notes: `Auto-generated from pipeline #${pipelineNumber}, step: ${error.step}, line: ${error.line}`, - source, // Custom field to track origin - } - - // Try to insert into test service backlog - const response = await fetch(`${TEST_SERVICE_URL}/api/v1/backlog`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(backlogItem), - }) - - if (response.ok) { - inserted++ - } else { - console.warn(`Failed to insert backlog item: ${response.status}`) - failed++ - } - } catch (insertError) { - console.error('Backlog insertion error:', insertError) - failed++ - } - } - - return { inserted, failed } -} - -// ============================================================================= -// API Handler -// ============================================================================= - -/** - * POST /api/webhooks/woodpecker - * - * Webhook endpoint fuer Woodpecker CI/CD Events. - * - * Bei Pipeline-Failure: - * 1. Extrahiert Logs mit /api/infrastructure/logs/extract - * 2. Parsed Fehler nach Kategorie - * 3. Traegt automatisch in Backlog ein - * - * Request Body (Woodpecker Webhook Format): - * - event: 'pipeline_success' | 'pipeline_failure' | 'pipeline_started' - * - repo_id: number - * - pipeline_number: number - * - branch?: string - * - commit?: string - * - author?: string - * - message?: string - */ -export async function POST(request: NextRequest) { - try { - const bodyText = await request.text() - - // Verify webhook signature - if (!verifySignature(request, bodyText)) { - return NextResponse.json( - { error: 'Invalid webhook signature' }, - { status: 401 } - ) - } - - const payload: WoodpeckerWebhookPayload = JSON.parse(bodyText) - - // Log all events for debugging - console.log(`Woodpecker webhook: ${payload.event} for pipeline #${payload.pipeline_number}`) - - // Only process pipeline_failure events - if (payload.event !== 'pipeline_failure') { - return NextResponse.json({ - status: 'ignored', - message: `Event ${payload.event} wird nicht verarbeitet`, - pipeline_number: payload.pipeline_number, - }) - } - - // 1. Extract logs from failed pipeline - console.log(`Extracting logs for failed pipeline #${payload.pipeline_number}`) - - const extractResponse = await fetch(LOG_EXTRACT_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - pipeline_number: payload.pipeline_number, - repo_id: String(payload.repo_id), - }), - }) - - if (!extractResponse.ok) { - const errorText = await extractResponse.text() - console.error('Log extraction failed:', errorText) - return NextResponse.json({ - status: 'error', - message: 'Log-Extraktion fehlgeschlagen', - pipeline_number: payload.pipeline_number, - }, { status: 500 }) - } - - const extractionResult = await extractResponse.json() - const errors: ExtractedError[] = extractionResult.errors || [] - - console.log(`Extracted ${errors.length} errors from pipeline #${payload.pipeline_number}`) - - // 2. Insert errors into backlog - if (errors.length > 0) { - const backlogResult = await insertIntoBacklog( - errors, - payload.pipeline_number, - 'ci_cd' - ) - - console.log(`Backlog: ${backlogResult.inserted} inserted, ${backlogResult.failed} failed`) - - return NextResponse.json({ - status: 'processed', - pipeline_number: payload.pipeline_number, - branch: payload.branch, - commit: payload.commit, - errors_found: errors.length, - backlog_inserted: backlogResult.inserted, - backlog_failed: backlogResult.failed, - categories: { - test_failure: errors.filter(e => e.category === 'test_failure').length, - build_error: errors.filter(e => e.category === 'build_error').length, - security_warning: errors.filter(e => e.category === 'security_warning').length, - license_violation: errors.filter(e => e.category === 'license_violation').length, - dependency_issue: errors.filter(e => e.category === 'dependency_issue').length, - }, - }) - } - - return NextResponse.json({ - status: 'processed', - pipeline_number: payload.pipeline_number, - message: 'Keine Fehler extrahiert', - errors_found: 0, - }) - - } catch (error) { - console.error('Webhook processing error:', error) - return NextResponse.json( - { error: 'Webhook-Verarbeitung fehlgeschlagen' }, - { status: 500 } - ) - } -} - -/** - * GET /api/webhooks/woodpecker - * - * Health check endpoint - */ -export async function GET() { - return NextResponse.json({ - status: 'ready', - endpoint: '/api/webhooks/woodpecker', - events: ['pipeline_failure'], - description: 'Woodpecker CI/CD Webhook Handler', - configured: { - webhook_secret: WEBHOOK_SECRET ? 'yes' : 'no', - log_extract_url: LOG_EXTRACT_URL, - test_service_url: TEST_SERVICE_URL, - }, - }) -} diff --git a/admin-core/components/infrastructure/DevOpsPipelineSidebar.tsx b/admin-core/components/infrastructure/DevOpsPipelineSidebar.tsx index 14d2908..9dacc22 100644 --- a/admin-core/components/infrastructure/DevOpsPipelineSidebar.tsx +++ b/admin-core/components/infrastructure/DevOpsPipelineSidebar.tsx @@ -92,25 +92,7 @@ function usePipelineLiveStatus(): PipelineLiveStatus | null { const [status, setStatus] = useState(null) useEffect(() => { - // Optional: Fetch live status from API - // For now, return null and display static content - // Uncomment below to enable live status fetching - /* - const fetchStatus = async () => { - try { - const response = await fetch('/api/admin/infrastructure/woodpecker/status') - if (response.ok) { - const data = await response.json() - setStatus(data) - } - } catch (error) { - console.error('Failed to fetch pipeline status:', error) - } - } - fetchStatus() - const interval = setInterval(fetchStatus, 30000) // Poll every 30s - return () => clearInterval(interval) - */ + // Live status fetching not yet implemented }, []) return status @@ -246,7 +228,7 @@ export function DevOpsPipelineSidebar({
{currentTool === 'ci-cd' && ( - Verwalten Sie Woodpecker Pipelines und Deployments + Verwalten Sie Gitea Actions Pipelines und Deployments )} {currentTool === 'tests' && ( Ueberwachen Sie 280+ Tests ueber alle Services @@ -458,7 +440,7 @@ export function DevOpsPipelineSidebarResponsive({
{currentTool === 'ci-cd' && ( <> - Aktuell: Woodpecker Pipelines und Deployments verwalten + Aktuell: Gitea Actions Pipelines und Deployments verwalten )} {currentTool === 'tests' && ( diff --git a/admin-core/lib/navigation.ts b/admin-core/lib/navigation.ts index f69d706..ccb3c5c 100644 --- a/admin-core/lib/navigation.ts +++ b/admin-core/lib/navigation.ts @@ -69,7 +69,7 @@ export const navigation: NavCategory[] = [ id: 'ci-cd', name: 'CI/CD Dashboard', href: '/infrastructure/ci-cd', - description: 'Gitea & Woodpecker Pipelines', + description: 'Gitea Actions Pipelines', purpose: 'CI/CD Dashboard mit Pipelines, Deployment-Status und Container-Management.', audience: ['DevOps', 'Entwickler'], subgroup: 'DevOps Pipeline', diff --git a/admin-core/types/infrastructure-modules.ts b/admin-core/types/infrastructure-modules.ts index 7fbbb65..557b042 100644 --- a/admin-core/types/infrastructure-modules.ts +++ b/admin-core/types/infrastructure-modules.ts @@ -2,7 +2,7 @@ * Shared Types & Constants for Infrastructure/DevOps Modules * * Diese Datei enthaelt gemeinsame Typen und Konstanten fuer die DevOps-Pipeline: - * - CI/CD: Woodpecker Pipelines & Deployments + * - CI/CD: Gitea Actions Pipelines & Deployments * - Tests: Test Dashboard & Backlog * - SBOM: Software Bill of Materials & Lizenz-Checks * - Security: DevSecOps Scans & Vulnerabilities @@ -230,24 +230,6 @@ export interface LogExtractionResponse { // Webhook Types // ============================================================================= -/** - * Woodpecker Webhook Event Types - */ -export type WoodpeckerEventType = 'pipeline_success' | 'pipeline_failure' | 'pipeline_started' - -/** - * Woodpecker Webhook Payload - */ -export interface WoodpeckerWebhookPayload { - event: WoodpeckerEventType - repo_id: number - pipeline_number: number - branch?: string - commit?: string - author?: string - message?: string -} - // ============================================================================= // LLM Integration Types // ============================================================================= @@ -346,18 +328,14 @@ export interface PipelineLiveStatus { export const INFRASTRUCTURE_API_ENDPOINTS = { /** CI/CD Endpoints */ CI_CD: { - PIPELINES: '/api/admin/infrastructure/woodpecker', - TRIGGER: '/api/admin/infrastructure/woodpecker/trigger', - LOGS: '/api/admin/infrastructure/woodpecker/logs', + PIPELINES: '/api/v1/security/sbom/pipeline/history', + STATUS: '/api/v1/security/sbom/pipeline/status', + TRIGGER: '/api/v1/security/sbom/pipeline/trigger', }, /** Log Extraction Endpoints */ LOG_EXTRACT: { EXTRACT: '/api/infrastructure/log-extract/extract', }, - /** Webhook Endpoints */ - WEBHOOKS: { - WOODPECKER: '/api/webhooks/woodpecker', - }, /** LLM Endpoints */ LLM: { ANALYZE: '/api/ai/analyze', @@ -375,7 +353,6 @@ export const INFRASTRUCTURE_API_ENDPOINTS = { */ export const DEVOPS_ARCHITECTURE = { services: [ - { name: 'Woodpecker CI', port: 8000, description: 'CI/CD Pipeline Server' }, { name: 'Gitea', port: 3003, description: 'Git Repository Server' }, { name: 'Syft', type: 'CLI', description: 'SBOM Generator' }, { name: 'Grype', type: 'CLI', description: 'Vulnerability Scanner' }, diff --git a/backend-core/main.py b/backend-core/main.py index 37e5b98..64383bb 100644 --- a/backend-core/main.py +++ b/backend-core/main.py @@ -25,8 +25,6 @@ from email_template_api import ( ) from system_api import router as system_router from security_api import router as security_router -from woodpecker_proxy_api import router as woodpecker_router - # --------------------------------------------------------------------------- # Middleware imports # --------------------------------------------------------------------------- @@ -106,7 +104,6 @@ app.include_router(system_router) # already has paths defined in r # Security / DevSecOps dashboard app.include_router(security_router, prefix="/api") -app.include_router(woodpecker_router, prefix="/api") # --------------------------------------------------------------------------- # Startup / Shutdown events diff --git a/backend-core/woodpecker_proxy_api.py b/backend-core/woodpecker_proxy_api.py deleted file mode 100644 index 1a1ba52..0000000 --- a/backend-core/woodpecker_proxy_api.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Woodpecker CI Proxy API - -Liest Pipeline-Daten direkt aus der Woodpecker SQLite-Datenbank. -Wird als Fallback verwendet, wenn kein WOODPECKER_TOKEN konfiguriert ist. -""" - -import sqlite3 -from pathlib import Path -from datetime import datetime -from fastapi import APIRouter, Query - -router = APIRouter(prefix="/v1/woodpecker", tags=["Woodpecker CI"]) - -WOODPECKER_DB = Path("/woodpecker-data/woodpecker.sqlite") - - -def get_db(): - if not WOODPECKER_DB.exists(): - return None - conn = sqlite3.connect(f"file:{WOODPECKER_DB}?mode=ro", uri=True) - conn.row_factory = sqlite3.Row - return conn - - -@router.get("/status") -async def get_status(): - conn = get_db() - if not conn: - return {"status": "offline", "error": "Woodpecker DB nicht gefunden"} - - try: - repos = [dict(r) for r in conn.execute( - "SELECT id, name, full_name, active FROM repos ORDER BY id" - ).fetchall()] - - total_pipelines = conn.execute("SELECT COUNT(*) FROM pipelines").fetchone()[0] - success = conn.execute("SELECT COUNT(*) FROM pipelines WHERE status='success'").fetchone()[0] - failure = conn.execute("SELECT COUNT(*) FROM pipelines WHERE status='failure'").fetchone()[0] - - latest = conn.execute("SELECT MAX(created) FROM pipelines").fetchone()[0] - - return { - "status": "online", - "repos": repos, - "stats": { - "total_pipelines": total_pipelines, - "success": success, - "failure": failure, - "success_rate": round(success / total_pipelines * 100, 1) if total_pipelines > 0 else 0, - }, - "last_activity": datetime.fromtimestamp(latest).isoformat() if latest else None, - } - finally: - conn.close() - - -@router.get("/pipelines") -async def get_pipelines( - repo: int = Query(default=0, description="Repo ID (0 = alle)"), - limit: int = Query(default=10, ge=1, le=100), -): - conn = get_db() - if not conn: - return {"status": "offline", "pipelines": [], "lastUpdate": datetime.now().isoformat()} - - try: - base_sql = """SELECT p.id, p.repo_id, p.number, p.status, p.event, p.branch, - p."commit", p.message, p.author, p.created, p.started, p.finished, - r.name as repo_name - FROM pipelines p - JOIN repos r ON r.id = p.repo_id""" - - if repo > 0: - rows = conn.execute( - base_sql + " WHERE p.repo_id = ? ORDER BY p.id DESC LIMIT ?", - (repo, limit) - ).fetchall() - else: - rows = conn.execute( - base_sql + " ORDER BY p.id DESC LIMIT ?", - (limit,) - ).fetchall() - - pipelines = [] - for r in rows: - p = dict(r) - - # Get steps directly (steps.pipeline_id links to pipelines.id) - steps = [dict(s) for s in conn.execute( - """SELECT s.name, s.state, s.exit_code, s.error - FROM steps s - WHERE s.pipeline_id = ? - ORDER BY s.pid""", - (p["id"],) - ).fetchall()] - - p["steps"] = steps - p["commit"] = (p.get("commit") or "")[:7] - msg = p.get("message") or "" - p["message"] = msg.split("\n")[0][:100] - pipelines.append(p) - - return { - "status": "online", - "pipelines": pipelines, - "lastUpdate": datetime.now().isoformat(), - } - finally: - conn.close() - - -@router.get("/repos") -async def get_repos(): - conn = get_db() - if not conn: - return [] - - try: - repos = [] - for r in conn.execute("SELECT id, name, full_name, active FROM repos ORDER BY id").fetchall(): - repo = dict(r) - latest = conn.execute( - 'SELECT status, created FROM pipelines WHERE repo_id = ? ORDER BY id DESC LIMIT 1', - (repo["id"],) - ).fetchone() - if latest: - repo["last_status"] = latest["status"] - repo["last_activity"] = datetime.fromtimestamp(latest["created"]).isoformat() - repos.append(repo) - return repos - finally: - conn.close() diff --git a/docker-compose.yml b/docker-compose.yml index 6208432..d530cb9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,6 @@ volumes: gitea_data: gitea_config: gitea_runner_data: - woodpecker_data: # ERP erpnext_db_data: erpnext_redis_queue_data: @@ -238,7 +237,6 @@ services: expose: - "8000" volumes: - - woodpecker_data:/woodpecker-data:ro - /var/run/docker.sock:/var/run/docker.sock:ro environment: DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-breakpilot}:${POSTGRES_PASSWORD:-breakpilot123}@postgres:5432/${POSTGRES_DB:-breakpilot_db}?options=-csearch_path%3Dcore,public @@ -505,56 +503,6 @@ services: networks: - breakpilot-network - woodpecker-server: - image: woodpeckerci/woodpecker-server:v3 - container_name: bp-core-woodpecker-server - ports: - - "8090:8000" - volumes: - - woodpecker_data:/var/lib/woodpecker - environment: - WOODPECKER_OPEN: "true" - WOODPECKER_HOST: ${WOODPECKER_HOST:-http://macmini:8090} - WOODPECKER_ADMIN: ${WOODPECKER_ADMIN:-pilotadmin} - WOODPECKER_GITEA: "true" - WOODPECKER_GITEA_URL: http://macmini:3003 - WOODPECKER_GITEA_CLIENT: ${WOODPECKER_GITEA_CLIENT:-} - WOODPECKER_GITEA_SECRET: ${WOODPECKER_GITEA_SECRET:-} - WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:-woodpecker-secret} - WOODPECKER_DATABASE_DRIVER: sqlite3 - WOODPECKER_DATABASE_DATASOURCE: /var/lib/woodpecker/woodpecker.sqlite - WOODPECKER_LOG_LEVEL: warn - WOODPECKER_PLUGINS_PRIVILEGED: "plugins/docker" - WOODPECKER_PLUGINS_TRUSTED_CLONE: "true" - extra_hosts: - - "macmini:192.168.178.100" - depends_on: - gitea: - condition: service_healthy - restart: unless-stopped - networks: - - breakpilot-network - - woodpecker-agent: - image: woodpeckerci/woodpecker-agent:v3 - container_name: bp-core-woodpecker-agent - volumes: - - /var/run/docker.sock:/var/run/docker.sock - environment: - WOODPECKER_SERVER: woodpecker-server:9000 - WOODPECKER_AGENT_SECRET: ${WOODPECKER_AGENT_SECRET:-woodpecker-secret} - WOODPECKER_MAX_WORKFLOWS: "2" - WOODPECKER_LOG_LEVEL: warn - WOODPECKER_BACKEND: docker - DOCKER_HOST: unix:///var/run/docker.sock - WOODPECKER_BACKEND_DOCKER_EXTRA_HOSTS: "macmini:192.168.178.100" - WOODPECKER_BACKEND_DOCKER_NETWORK: breakpilot-network - depends_on: - - woodpecker-server - restart: unless-stopped - networks: - - breakpilot-network - # ========================================================= # DOCUMENTATION & UTILITIES # ========================================================= @@ -632,8 +580,6 @@ services: environment: NODE_ENV: production BACKEND_URL: http://backend-core:8000 - WOODPECKER_URL: http://bp-core-woodpecker-server:8000 - WOODPECKER_TOKEN: ${WOODPECKER_TOKEN:-} OLLAMA_URL: ${OLLAMA_URL:-http://host.docker.internal:11434} extra_hosts: - "host.docker.internal:host-gateway"