init
This commit is contained in:
96
app.py
96
app.py
@@ -3,6 +3,7 @@
|
||||
|
||||
import os
|
||||
import logging
|
||||
from typing import Dict, Tuple, Any
|
||||
|
||||
|
||||
os.environ.setdefault('FLASK_RUN_HOST', '0.0.0.0')
|
||||
@@ -79,7 +80,11 @@ def handle_gitea_webhook():
|
||||
event_type = request.headers.get('X-Gitea-Event', 'push')
|
||||
logger.info(f'收到 Gitea Webhook 事件: {event_type}')
|
||||
|
||||
# 只处理 push 事件
|
||||
# 处理 Pull Request 事件
|
||||
if event_type == 'pull_request':
|
||||
return handle_pull_request(payload)
|
||||
|
||||
# 处理 Push 事件
|
||||
if event_type != 'push':
|
||||
return jsonify({'message': 'Event ignored'}), 200
|
||||
|
||||
@@ -161,6 +166,95 @@ def handle_gitea_webhook():
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
def handle_pull_request(payload: Dict[str, Any]) -> Tuple[Dict, int]:
|
||||
"""
|
||||
处理 Pull Request 事件
|
||||
|
||||
Args:
|
||||
payload: Webhook payload
|
||||
|
||||
Returns:
|
||||
JSON 响应和状态码
|
||||
"""
|
||||
try:
|
||||
# 解析 PR 事件
|
||||
pr_info = webhook_handler.parse_pull_request_event(payload)
|
||||
|
||||
if not pr_info:
|
||||
logger.info('PR 事件不需要处理(如关闭、合并等)')
|
||||
return jsonify({'message': 'PR event ignored'}), 200
|
||||
|
||||
repo_name = pr_info['repo_name']
|
||||
source_branch = pr_info['source_branch']
|
||||
source_sha = pr_info['source_sha']
|
||||
pr_number = pr_info['pr_number']
|
||||
pr_title = pr_info['pr_title']
|
||||
pr_url = pr_info['pr_url']
|
||||
target_branch = pr_info['target_branch']
|
||||
author = pr_info['author']
|
||||
|
||||
logger.info(f'处理 PR #{pr_number}: {pr_title} ({source_branch} -> {target_branch})')
|
||||
logger.info(f'扫描 PR 分支: {source_branch}, commit: {source_sha}')
|
||||
|
||||
try:
|
||||
# 获取仓库 URL
|
||||
repo = payload.get('repository', {})
|
||||
clone_url = repo.get('clone_url')
|
||||
if not clone_url:
|
||||
web_url = repo.get('web_url', '')
|
||||
if web_url:
|
||||
clone_url = web_url.rstrip('/') + '.git'
|
||||
|
||||
# 执行代码扫描
|
||||
scan_results = {}
|
||||
|
||||
# Python 扫描
|
||||
if 'python' in config.get('scanner', {}).get('languages', []):
|
||||
scan_results['python'] = python_scanner.scan(
|
||||
clone_url, source_sha, source_branch
|
||||
)
|
||||
|
||||
# JavaScript/TypeScript 扫描
|
||||
if any(lang in config.get('scanner', {}).get('languages', [])
|
||||
for lang in ['javascript', 'typescript']):
|
||||
scan_results['javascript'] = js_scanner.scan(
|
||||
clone_url, source_sha, source_branch
|
||||
)
|
||||
|
||||
# 安全扫描
|
||||
scan_results['security'] = security_scanner.scan(
|
||||
clone_url, source_sha, source_branch
|
||||
)
|
||||
|
||||
# 生成报告
|
||||
commit_message = f'PR #{pr_number}: {pr_title}'
|
||||
report = report_generator.generate(
|
||||
repo_name=repo_name,
|
||||
branch=source_branch,
|
||||
commit_id=source_sha,
|
||||
commit_message=commit_message,
|
||||
author=author,
|
||||
scan_results=scan_results,
|
||||
pr_url=pr_url,
|
||||
target_branch=target_branch
|
||||
)
|
||||
|
||||
# 发送飞书通知
|
||||
feishu_notifier.send_report(report)
|
||||
|
||||
logger.info(f'PR #{pr_number} 扫描完成')
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'扫描 PR #{pr_number} 失败: {str(e)}')
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
return jsonify({'status': 'ok', 'message': 'PR scan completed'}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f'处理 PR Webhook 失败: {str(e)}', exc_info=True)
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/scan/manual', methods=['POST'])
|
||||
def manual_scan():
|
||||
"""手动触发扫描接口"""
|
||||
|
||||
@@ -155,12 +155,33 @@ class FeishuNotifier:
|
||||
|
||||
scan_details.append(detail_text)
|
||||
|
||||
# 检查是否为 PR 扫描
|
||||
pr_url = report.get('pr_url')
|
||||
target_branch = report.get('target_branch')
|
||||
|
||||
# 构建基本信息文本
|
||||
if pr_url and target_branch:
|
||||
# PR 扫描
|
||||
title = f"{status_icon} PR 代码质量扫描报告"
|
||||
basic_info = (f"**仓库:** `{report.get('repo_name', 'unknown')}`\n"
|
||||
f"**源分支:** `{report.get('branch', 'unknown')}` → **目标分支:** `{target_branch}`\n"
|
||||
f"**PR链接:** [查看PR]({pr_url})\n"
|
||||
f"**提交:** `{report.get('commit_id', 'unknown')}`\n"
|
||||
f"**提交者:** {report.get('author', 'unknown')}")
|
||||
else:
|
||||
# Push 扫描
|
||||
title = f"{status_icon} 代码质量扫描报告"
|
||||
basic_info = (f"**仓库:** `{report.get('repo_name', 'unknown')}`\n"
|
||||
f"**分支:** `{report.get('branch', 'unknown')}`\n"
|
||||
f"**提交:** `{report.get('commit_id', 'unknown')}`\n"
|
||||
f"**提交者:** {report.get('author', 'unknown')}")
|
||||
|
||||
# 构建卡片消息
|
||||
card = {
|
||||
"header": {
|
||||
"title": {
|
||||
"tag": "plain_text",
|
||||
"content": f"{status_icon} 代码质量扫描报告"
|
||||
"content": title
|
||||
},
|
||||
"template": theme_color
|
||||
},
|
||||
@@ -169,10 +190,7 @@ class FeishuNotifier:
|
||||
"tag": "div",
|
||||
"text": {
|
||||
"tag": "lark_md",
|
||||
"content": f"**仓库:** `{report.get('repo_name', 'unknown')}`\n"
|
||||
f"**分支:** `{report.get('branch', 'unknown')}`\n"
|
||||
f"**提交:** `{report.get('commit_id', 'unknown')}`\n"
|
||||
f"**提交者:** {report.get('author', 'unknown')}"
|
||||
"content": basic_info
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -37,7 +37,9 @@ class ReportGenerator:
|
||||
commit_id: str,
|
||||
commit_message: str,
|
||||
author: str,
|
||||
scan_results: Dict[str, Any]
|
||||
scan_results: Dict[str, Any],
|
||||
pr_url: str = None,
|
||||
target_branch: str = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
生成扫描报告
|
||||
@@ -49,6 +51,8 @@ class ReportGenerator:
|
||||
commit_message: 提交信息
|
||||
author: 提交者
|
||||
scan_results: 扫描结果
|
||||
pr_url: PR 链接(可选)
|
||||
target_branch: 目标分支(可选,用于 PR 扫描)
|
||||
|
||||
Returns:
|
||||
报告数据
|
||||
@@ -89,8 +93,10 @@ class ReportGenerator:
|
||||
'total_errors': total_errors,
|
||||
'total_warnings': total_warnings,
|
||||
'scan_results': scan_results,
|
||||
'pr_url': pr_url,
|
||||
'target_branch': target_branch,
|
||||
'markdown': self._generate_markdown(
|
||||
repo_name, branch, commit_id, commit_message, author, scan_results, status, status_text
|
||||
repo_name, branch, commit_id, commit_message, author, scan_results, status, status_text, pr_url, target_branch
|
||||
)
|
||||
}
|
||||
|
||||
@@ -109,13 +115,18 @@ class ReportGenerator:
|
||||
author: str,
|
||||
scan_results: Dict[str, Any],
|
||||
status: str,
|
||||
status_text: str
|
||||
status_text: str,
|
||||
pr_url: str = None,
|
||||
target_branch: str = None
|
||||
) -> str:
|
||||
"""生成 Markdown 格式的报告"""
|
||||
lines = []
|
||||
|
||||
# 标题
|
||||
lines.append('# 📊 代码质量扫描报告')
|
||||
# 标题 - 根据是否为 PR 扫描显示不同标题
|
||||
if pr_url:
|
||||
lines.append('# 📊 PR 代码质量扫描报告')
|
||||
else:
|
||||
lines.append('# 📊 代码质量扫描报告')
|
||||
lines.append('')
|
||||
|
||||
# 基本信息
|
||||
@@ -124,7 +135,15 @@ class ReportGenerator:
|
||||
lines.append(f'| 项目 | 内容 |')
|
||||
lines.append(f'|------|------|')
|
||||
lines.append(f'| 仓库 | `{repo_name}` |')
|
||||
lines.append(f'| 分支 | `{branch}` |')
|
||||
|
||||
# 如果是 PR,显示 PR 特有信息
|
||||
if pr_url and target_branch:
|
||||
lines.append(f'| 源分支 | `{branch}` |')
|
||||
lines.append(f'| 目标分支 | `{target_branch}` |')
|
||||
lines.append(f'| PR 链接 | [查看 PR]({pr_url}) |')
|
||||
else:
|
||||
lines.append(f'| 分支 | `{branch}` |')
|
||||
|
||||
lines.append(f'| 提交 | `{commit_id}` |')
|
||||
lines.append(f'| 提交者 | {author} |')
|
||||
lines.append(f'| 提交信息 | {commit_message[:50]}... |' if len(commit_message) > 50 else f'| 提交信息 | {commit_message} |')
|
||||
|
||||
@@ -124,3 +124,66 @@ class GiteaWebhookHandler:
|
||||
f for f in files
|
||||
if any(f.endswith(ext) for ext in extensions)
|
||||
]
|
||||
|
||||
def parse_pull_request_event(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
解析 Pull Request 事件
|
||||
|
||||
Args:
|
||||
payload: Webhook payload
|
||||
|
||||
Returns:
|
||||
解析后的 PR 信息
|
||||
"""
|
||||
action = payload.get('action', '')
|
||||
pr = payload.get('pull_request', {})
|
||||
repo = payload.get('repository', {})
|
||||
|
||||
# 只处理 PR 创建和更新事件
|
||||
if action not in ['opened', 'reopened', 'synchronize', 'ready_for_review']:
|
||||
return None
|
||||
|
||||
# 获取 PR 的源分支和目标分支
|
||||
head = pr.get('head', {})
|
||||
base = pr.get('base', {})
|
||||
|
||||
return {
|
||||
'action': action,
|
||||
'repo_name': repo.get('full_name', ''),
|
||||
'repo_url': repo.get('clone_url', ''),
|
||||
'web_url': repo.get('web_url', ''),
|
||||
'pr_number': pr.get('number', 0),
|
||||
'pr_title': pr.get('title', ''),
|
||||
'pr_body': pr.get('body', ''),
|
||||
'pr_url': pr.get('html_url', ''),
|
||||
'source_branch': head.get('ref', ''),
|
||||
'source_sha': head.get('sha', '')[:8],
|
||||
'target_branch': base.get('ref', ''),
|
||||
'target_sha': base.get('sha', '')[:8],
|
||||
'author': pr.get('user', {}).get('login', ''),
|
||||
'author_email': pr.get('user', {}).get('email', ''),
|
||||
'state': pr.get('state', ''),
|
||||
'merged': pr.get('merged', False),
|
||||
}
|
||||
|
||||
def get_pr_diff_files(self, payload: Dict[str, Any]) -> list:
|
||||
"""
|
||||
从 Pull Request 事件中获取变更的文件列表
|
||||
|
||||
Args:
|
||||
payload: Webhook payload
|
||||
|
||||
Returns:
|
||||
变更的文件列表
|
||||
"""
|
||||
pr = payload.get('pull_request', {})
|
||||
files = pr.get('changed_files', [])
|
||||
|
||||
# 如果没有 changed_files 字段,尝试从 commits 中获取
|
||||
if not files:
|
||||
commits = pr.get('commits', [])
|
||||
files = []
|
||||
for commit in commits:
|
||||
files.extend(self.get_changed_files(commit))
|
||||
|
||||
return files
|
||||
Reference in New Issue
Block a user