All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 35s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 28s
- admin-compliance: .dockerignore + Dockerfile bereinigt - dsfa-corpus/route.ts + legal-corpus/route.ts entfernt (obsolet) - webhooks/woodpecker/route.ts: minor fix - dsfa/[id]/page.tsx: Refactoring - service_modules.py + README.md: aktualisiert - Migration 028 → 032 umbenannt (legal_documents_extend) - docs: index.md + DEVELOPER.md aktualisiert Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
274 lines
8.2 KiB
TypeScript
274 lines
8.2 KiB
TypeScript
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:8002'
|
|
|
|
// =============================================================================
|
|
// 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,
|
|
},
|
|
})
|
|
}
|