Build & Push - 构建并推送镜像 / 构建后端镜像 (push) Failing after 1s
Build & Push - 构建并推送镜像 / 构建前端镜像 (push) Failing after 1s
Build & Push - 构建并推送镜像 / 构建通知 (push) Failing after 1s
CI - 代码检查和测试 / Python 代码检查 (push) Failing after 1s
CI - 代码检查和测试 / Python 单元测试 (push) Failing after 1s
CI - 代码检查和测试 / Frontend 代码检查 (push) Failing after 1s
CI - 代码检查和测试 / Frontend 构建测试 (push) Failing after 1s
Plane Integration - 项目管理集成 / 同步到 Plane (push) Failing after 1s
✨ 新功能: - 添加 Plane CI/CD 集成 workflow - 自动创建构建记录 Issue - 同步 CI/CD 状态到 Plane - 生成详细构建报告 🔧 配置更新: - 更新 Registry 地址: 192.168.1.83:15000 - 更新 Registry 缓存: 192.168.1.83:15001 - 更新项目名称: bigscreen - 修正 Plane API header 格式 (X-API-Key) - 修正项目标识符: BIGSCREEN 📚 新增文档: - PLANE_INTEGRATION.md - Plane 集成详细指南 - DEVOPS_INTEGRATION.md - 完整 DevOps 方案 - QUICKSTART_DEVOPS.md - 5分钟快速上手指南 - PLANE_API_NOTES.md - API 使用说明和常见问题 - TEST_INTEGRATION.md - 集成测试指南 🧪 已测试: - Plane API 调用成功 - Issue 创建成功 - 完整工作流验证通过
217 lines
7.3 KiB
Python
Executable File
217 lines
7.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Plane 项目管理集成脚本
|
|
同步 Gitea CI/CD 状态到 Plane 项目管理系统
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import requests
|
|
from datetime import datetime
|
|
from typing import Dict, Any, Optional
|
|
|
|
# Plane API 配置
|
|
PLANE_API_URL = os.getenv("PLANE_API_URL", "http://192.168.1.83:8088/api")
|
|
PLANE_API_KEY = os.getenv("PLANE_API_KEY", "")
|
|
PLANE_WORKSPACE = os.getenv("PLANE_WORKSPACE", "ljwx")
|
|
PLANE_PROJECT_ID = os.getenv("PLANE_PROJECT_ID", "BIGSCREEN")
|
|
|
|
# Gitea 事件信息
|
|
GITHUB_EVENT_NAME = os.getenv("GITHUB_EVENT_NAME", "")
|
|
GITHUB_REF = os.getenv("GITHUB_REF", "")
|
|
GITHUB_SHA = os.getenv("GITHUB_SHA", "")
|
|
GITHUB_ACTOR = os.getenv("GITHUB_ACTOR", "")
|
|
WORKFLOW_STATUS = os.getenv("WORKFLOW_STATUS", "success")
|
|
WORKFLOW_NAME = os.getenv("WORKFLOW_NAME", "")
|
|
|
|
|
|
class PlaneClient:
|
|
"""Plane API 客户端"""
|
|
|
|
def __init__(self, api_url: str, api_key: str, workspace: str):
|
|
self.api_url = api_url.rstrip("/")
|
|
self.api_key = api_key
|
|
self.workspace = workspace
|
|
self.headers = {
|
|
"X-API-Key": api_key,
|
|
"Content-Type": "application/json",
|
|
}
|
|
|
|
def get_project(self, project_identifier: str) -> Optional[Dict[str, Any]]:
|
|
"""获取项目信息 - 通过identifier查找项目"""
|
|
# Plane API 不支持直接通过 identifier 获取项目,需要先获取列表
|
|
url = f"{self.api_url}/v1/workspaces/{self.workspace}/projects/"
|
|
try:
|
|
response = requests.get(url, headers=self.headers, timeout=10)
|
|
if response.status_code == 200:
|
|
data = response.json()
|
|
# 在结果中查找匹配的项目
|
|
for project in data.get("results", []):
|
|
if project.get("identifier") == project_identifier:
|
|
return project
|
|
print(f"⚠️ 未找到标识符为 {project_identifier} 的项目")
|
|
return None
|
|
else:
|
|
print(f"⚠️ 获取项目列表失败: {response.status_code} - {response.text}")
|
|
return None
|
|
except Exception as e:
|
|
print(f"❌ API 请求失败: {e}")
|
|
return None
|
|
|
|
def create_issue(
|
|
self, project_id: str, title: str, description: str, **kwargs
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""创建 Issue"""
|
|
url = f"{self.api_url}/v1/workspaces/{self.workspace}/projects/{project_id}/issues/"
|
|
|
|
data = {
|
|
"name": title,
|
|
"description_html": description,
|
|
"priority": kwargs.get("priority", "medium"),
|
|
}
|
|
|
|
# 只有当提供了有效的 state 和 labels 时才添加
|
|
if kwargs.get("state"):
|
|
data["state"] = kwargs.get("state")
|
|
|
|
# labels 需要是 UUID 数组,暂时不使用
|
|
# if kwargs.get("labels"):
|
|
# data["labels"] = kwargs.get("labels")
|
|
|
|
# 移除 None 值
|
|
data = {k: v for k, v in data.items() if v is not None}
|
|
|
|
try:
|
|
response = requests.post(url, headers=self.headers, json=data, timeout=10)
|
|
if response.status_code in [200, 201]:
|
|
return response.json()
|
|
else:
|
|
print(f"⚠️ 创建 Issue 失败: {response.status_code} - {response.text}")
|
|
return None
|
|
except Exception as e:
|
|
print(f"❌ 创建 Issue 失败: {e}")
|
|
return None
|
|
|
|
def add_comment(
|
|
self, project_id: str, issue_id: str, comment: str
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""添加评论"""
|
|
url = f"{self.api_url}/v1/workspaces/{self.workspace}/projects/{project_id}/issues/{issue_id}/comments/"
|
|
|
|
data = {"comment_html": comment}
|
|
|
|
try:
|
|
response = requests.post(url, headers=self.headers, json=data, timeout=10)
|
|
if response.status_code in [200, 201]:
|
|
return response.json()
|
|
else:
|
|
print(f"⚠️ 添加评论失败: {response.status_code} - {response.text}")
|
|
return None
|
|
except Exception as e:
|
|
print(f"❌ 添加评论失败: {e}")
|
|
return None
|
|
|
|
|
|
def generate_build_report() -> str:
|
|
"""生成构建报告"""
|
|
status_emoji = "✅" if WORKFLOW_STATUS == "success" else "❌"
|
|
|
|
report = f"""
|
|
<h3>{status_emoji} CI/CD 构建报告</h3>
|
|
|
|
<p><strong>工作流:</strong> {WORKFLOW_NAME}</p>
|
|
<p><strong>状态:</strong> {WORKFLOW_STATUS}</p>
|
|
<p><strong>分支:</strong> {GITHUB_REF}</p>
|
|
<p><strong>提交:</strong> <code>{GITHUB_SHA[:7]}</code></p>
|
|
<p><strong>触发者:</strong> {GITHUB_ACTOR}</p>
|
|
<p><strong>时间:</strong> {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
|
|
|
|
<h4>🔗 相关链接</h4>
|
|
<ul>
|
|
<li><a href="http://192.168.1.83:33000/gao/bigscreen">Gitea 仓库</a></li>
|
|
<li><a href="http://192.168.1.83:33000/gao/bigscreen/actions">CI/CD 记录</a></li>
|
|
<li><a href="http://192.168.1.83:15000/v2/bigscreen/tags/list">镜像仓库</a></li>
|
|
</ul>
|
|
"""
|
|
return report
|
|
|
|
|
|
def main():
|
|
"""主函数"""
|
|
print("=" * 60)
|
|
print("🚀 Plane 项目管理集成")
|
|
print("=" * 60)
|
|
|
|
# 检查 API Key
|
|
if not PLANE_API_KEY:
|
|
print("⚠️ 未设置 PLANE_API_KEY,跳过同步")
|
|
print("💡 请在 Gitea 仓库设置中添加 PLANE_API_KEY secret")
|
|
sys.exit(0)
|
|
|
|
# 初始化 Plane 客户端
|
|
client = PlaneClient(PLANE_API_URL, PLANE_API_KEY, PLANE_WORKSPACE)
|
|
|
|
# 检查项目是否存在
|
|
print(f"\n📋 检查项目: {PLANE_PROJECT_ID}")
|
|
project = client.get_project(PLANE_PROJECT_ID)
|
|
|
|
if not project:
|
|
print(f"❌ 项目 {PLANE_PROJECT_ID} 不存在,请先在 Plane 中创建")
|
|
print(f"🔗 访问: http://192.168.1.83:8088/{PLANE_WORKSPACE}")
|
|
sys.exit(1)
|
|
|
|
print(f"✅ 找到项目: {project.get('name', PLANE_PROJECT_ID)}")
|
|
|
|
# 生成构建报告
|
|
build_report = generate_build_report()
|
|
|
|
# 根据事件类型处理
|
|
if GITHUB_EVENT_NAME == "push":
|
|
# Push 事件:创建构建记录 Issue
|
|
title = f"🏗️ 构建 {GITHUB_REF.split('/')[-1]} - {GITHUB_SHA[:7]}"
|
|
|
|
print(f"\n📝 创建构建记录 Issue: {title}")
|
|
issue = client.create_issue(
|
|
project_id=project.get("id"),
|
|
title=title,
|
|
description=build_report,
|
|
priority="low",
|
|
)
|
|
|
|
if issue:
|
|
print(f"✅ Issue 创建成功: {issue.get('name')}")
|
|
print(f"🔗 查看: http://192.168.1.83:8088/{PLANE_WORKSPACE}/projects/{PLANE_PROJECT_ID}/issues/{issue.get('id')}")
|
|
else:
|
|
print("❌ Issue 创建失败")
|
|
|
|
elif GITHUB_EVENT_NAME == "pull_request":
|
|
# PR 事件:创建 PR 跟踪 Issue
|
|
title = f"🔀 Pull Request - {GITHUB_SHA[:7]}"
|
|
|
|
print(f"\n📝 创建 PR 跟踪 Issue: {title}")
|
|
issue = client.create_issue(
|
|
project_id=project.get("id"),
|
|
title=title,
|
|
description=build_report,
|
|
priority="medium",
|
|
)
|
|
|
|
if issue:
|
|
print(f"✅ Issue 创建成功")
|
|
else:
|
|
print("❌ Issue 创建失败")
|
|
|
|
elif GITHUB_EVENT_NAME == "workflow_run":
|
|
# Workflow 完成事件
|
|
print(f"\n📊 工作流完成: {WORKFLOW_NAME}")
|
|
print(f"📌 状态: {WORKFLOW_STATUS}")
|
|
|
|
print("\n" + "=" * 60)
|
|
print("✅ Plane 同步完成")
|
|
print("=" * 60)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|