feat(plugin): 接入标准运维开放插件运行时与治理能力#698
Conversation
There was a problem hiding this comment.
Code Review Summary
大体量 PR,整体架构合理——uniform_api v4.0.0 协议扩展、开放插件目录索引 + 空间治理、回调鉴权与快照体系都方向正确,文档与 APIGW 资源同步也已覆盖。以下几点值得关注:
需要修复
- ⚡
create_task中collect_plugin_references被重复调用 3 次(validate → build_reference → build_schema),每次内部都有 N+1 查询。建议抽取为一次调用,传递结果给后续方法。 - ⚡
collect_plugin_references存在 N+1 查询——循环内逐条查OpenPluginCatalogIndex和SpaceOpenPluginAvailability,在任务创建/模板保存等 API 路径上会放大。建议批量预取。 - 📄
backfill_open_plugin_snapshots.py缺少开源协议头,项目规范要求每个新增 Python 文件顶部包含 MIT 许可声明。
建议优化
- ⚡
enable_all_visible_plugins循环调用update_or_create,可改为bulk_create+update提升批量开启效率。 - ✨
_refresh_catalog_index的循环update_or_create在低频同步场景可接受,若后续插件量增长可考虑批量化。 - ✨
SpaceViewSet新增 4 个 open_plugin action,当前继承的AdminPermission | SpaceSuperuserPermission | SpaceExemptionPermission足够(空间管理功能),确认符合预期。
| username=request.user.username, | ||
| scope_type=template.scope_type, | ||
| scope_id=template.scope_value, | ||
| ) |
There was a problem hiding this comment.
⚡ validate_pipeline_tree、build_reference_snapshot、build_schema_snapshot 各自内部都调用了 collect_plugin_references,等于同一棵 pipeline_tree 被扫描了 3 遍且每次都有 N+1 查询。建议改为调用一次 collect_plugin_references,将结果传给后续方法复用。
| if not plugin_id: | ||
| continue | ||
|
|
||
| catalog = cls._get_catalog_entry(space_id=space_id, plugin_id=plugin_id, source_key=source_key) |
There was a problem hiding this comment.
⚡ 循环内逐条调用 _get_catalog_entry(ORM query)和 SpaceOpenPluginAvailability.objects.filter().exists(),对于含多个开放插件节点的 pipeline 会产生 2N 次查询。建议在循环前批量 filter(plugin_id__in=...) 预取 catalog 和 availability map。
| @@ -0,0 +1,89 @@ | |||
| from django.core.management.base import BaseCommand | |||
There was a problem hiding this comment.
📄 缺少项目要求的开源协议头(MIT License),其他新增 .py 文件如 open_plugin_callback.py、open_plugin_catalog.py 等都已包含。请补齐。
| catalog_qs = catalog_qs.filter(source_key=source_key) | ||
|
|
||
| updated = [] | ||
| for item in catalog_qs.only("source_key", "plugin_id"): |
There was a problem hiding this comment.
⚡ 循环内调用 toggle_plugin(即 update_or_create),若空间内可用插件较多(如 100+)会产生 200+ 次查询。建议改为先 filter().update(enabled=True) 批量更新存在的记录,再 bulk_create 缺失记录。
| @return_json_response | ||
| def operate_task_node(request, space_id, task_id, node_id, operation): | ||
| data = json.loads(request.body) | ||
| if _is_open_plugin_callback_request(operation, data): |
There was a problem hiding this comment.
🔒 开放插件回调分支在 Serializer 校验之前拦截,绕过了 OperateTaskNodeSerializer 的参数校验。当前 _handle_open_plugin_callback 内部有完整的 token 验签 + 字段提取逻辑,但 data["open_plugin_run_id"] 在第 81 行直接作为查询条件使用而未做格式校验——建议至少校验其非空且为合法字符串。
| @classmethod | ||
| def get_snapshot_node_statuses(cls, space_id, extra_info): | ||
| statuses = {} | ||
| for ref in cls.get_reference_snapshot(extra_info): |
There was a problem hiding this comment.
⚡ 与 collect_plugin_references 类似,get_snapshot_node_statuses 也在循环内逐条查 catalog + availability。若此方法会在高频路径上被调用,同样建议批量预取。
|
dannydeng seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
There was a problem hiding this comment.
Incremental Review
新增 5 个提交引入了「开放插件来源准入」(OpenPluginSpaceGrant)两层校验,覆盖了查询、目录、模板保存、建任务、启动任务等路径,测试覆盖充分。
先前问题状态
先前审查报告的 6 个行内问题(collect_plugin_references 3x 调用、N+1 查询、缺少协议头、enable_all_visible_plugins 循环 update_or_create、open_plugin_run_id 格式校验、get_snapshot_node_statuses N+1)均未在本次增量提交中修改,维持原状。
新发现
⚠️ 数据迁移 0005 直接 import Service 类操作模型,未使用apps.get_model(),有潜在迁移稳定性风险- 📄
grant_open_plugin_source.py缺少开源协议头 - ⚡
validate_pipeline_tree中新增的is_granted逐条查询可优化为批量预取 ⚠️ _raise_uniform_api_catalog_access_error可能静默 fallthrough,调用点紧跟另一个 raise,两条路径错误信息粒度不一致
|
|
||
|
|
||
| def forwards(apps, schema_editor): | ||
| from bkflow.plugin.services.open_plugin_grant import OpenPluginGrantService |
There was a problem hiding this comment.
OpenPluginGrantService(及其内部依赖 OpenPluginCatalogService / SpaceConfig / UniformAPIConfigHandler),绕过了 Django migration state。如果后续迁移修改了 OpenPluginSpaceGrant 或 SpaceConfig 的字段,这个迁移可能会在全新环境跑 migrate 时报错。建议改用 apps.get_model() 操作,或至少将 import 包在 try/except 中并跳过失败。
| @@ -0,0 +1,30 @@ | |||
| from django.core.management.base import BaseCommand | |||
There was a problem hiding this comment.
📄 新增文件缺少项目要求的 MIT 开源协议头,与同目录 backfill_open_plugin_snapshots.py 存在相同问题。
| ): | ||
| if ref["catalog"] is None: | ||
| raise serializers.ValidationError("开放插件 [{}] 不存在或已下线".format(ref["plugin_id"])) | ||
| if not OpenPluginGrantService.is_granted(space_id, ref["catalog"].source_key): |
There was a problem hiding this comment.
⚡ is_granted 在 collect_plugin_references 返回的每个 ref 循环内逐条调用,产生 N 次 DB exists() 查询。因为一个 pipeline 中不同 source_key 数量通常很少,可以在循环前一次性 granted_source_keys = set(OpenPluginGrantService.granted_source_keys(space_id)),循环内用 in 判断。
| api_item["_meta_url"] = self._build_uniform_api_meta_url(api_item, version) | ||
|
|
||
| if not api_item or not api_item.get("_meta_url"): | ||
| self._raise_uniform_api_catalog_access_error(code) |
There was a problem hiding this comment.
_raise_uniform_api_catalog_access_error 仅在 catalog 记录存在且匹配特定状态时才 raise,否则静默返回 None,然后执行到下一行的 raise ValueError。两个 raise 路径给出不同粒度的错误信息,建议统一:要么让 helper 总是 raise(找不到 catalog 时也抛带上下文的异常),要么将其返回错误信息字符串由调用方 raise。
There was a problem hiding this comment.
Incremental Review
新增 2 个提交:080c649 引入开放插件业务版本强校验,3545dec 合并 master 解决冲突。
新增代码评估
版本校验实现质量良好:
OpenPluginCatalogIndex.is_plugin_version_available逻辑清晰,空列表兜底合理PluginSchemaService._validate_uniform_api_plugin_version在两处入口(_get_uniform_api_schema、_get_single_by_type)均正确调用,拦截时机在远程请求之前,避免无效网络开销_get_uniform_api_schema中条件从if version改为if api_item and version,修复了api_item为None时的潜在 AttributeError- 测试覆盖了 snapshot 层和 schema 层的拒绝路径,且验证了远程 client 未被调用(
mock_client_cls.assert_not_called())
先前问题状态
前两轮审查报告的 10 个行内问题(collect_plugin_references 3x 调用、N+1 查询、缺少协议头、循环 update_or_create、open_plugin_run_id 格式校验、迁移直接 import Service、is_granted 逐条查询、_raise_uniform_api_catalog_access_error 双 raise 路径)均未在本次增量提交中修改,维持原状。
结论
本次增量无新问题,代码实现简洁、测试充分。
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #698 +/- ##
==========================================
- Coverage 83.10% 82.81% -0.30%
==========================================
Files 307 314 +7
Lines 18167 19325 +1158
==========================================
+ Hits 15098 16004 +906
- Misses 3069 3321 +252 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Incremental Review
最新提交 6874eac 仅修复 lint 格式问题(__init__.py 尾空行、migration 文件行宽、测试文件 import 排列),无逻辑变更,无新问题。
先前问题状态
前三轮审查报告的 10 个行内问题(collect_plugin_references 3x 调用、N+1 查询、缺少协议头、循环 update_or_create、open_plugin_run_id 格式校验、迁移直接 import Service、is_granted 逐条查询、_raise_uniform_api_catalog_access_error 双 raise 路径)均未在本次增量提交中修改,维持原状。
结论
本次增量无新问题。纯代码风格修正,符合项目 lint 规范。
There was a problem hiding this comment.
Incremental Review
最新提交 91e603cb 新增 open plugin v4 协议演示接口(api_plugin_demo),包含 list_meta / detail_meta / execute / status / cancel 五个 demo endpoint 及对应测试。代码整体结构清晰,demo 数据使用常量 + copy.deepcopy 防止污染,v4 协议字段覆盖完整。
先前问题状态
前四轮审查报告的行内问题(collect_plugin_references 3x 调用、N+1 查询、缺少协议头、循环 update_or_create、迁移 import 风险等)均未在本次提交中修复,仍处于待处理状态。
新增代码问题
本次新增 2 个 Minor 级别问题(见行内评论)。
| @@ -0,0 +1,99 @@ | |||
| """ | |||
There was a problem hiding this comment.
📄 新增测试目录 tests/interface/api_plugin_demo/ 缺少 __init__.py(同目录其他包如 apigw/、plugin/、space/ 均有),可能导致 pytest 无法正确发现测试。同时本文件缺少项目要求的 MIT 开源协议头。
| """ | ||
| Open Plugin V4 List Meta API - 获取 stage 联调用开放插件目录。 | ||
| """ | ||
| limit = int(request.query_params.get("limit", 50)) |
There was a problem hiding this comment.
int(request.query_params.get("limit", 50)) 对非数字字符串(如 ?limit=abc)会直接抛 ValueError 导致 500。虽然是 demo 接口,建议加 try/except 或使用 Serializer 做参数校验以保持一致的错误响应格式。
There was a problem hiding this comment.
Incremental Review
最新提交 28eb0958 将开放插件回调验证逻辑从 interface 层 (operate_task_node.py) 下沉到 engine 层 (TaskNodeOperation._handle_open_plugin_callback),同时将 OpenPluginRunCallbackRef 模型从 plugin app 迁移至 task app。重构方向正确——回调鉴权与状态判定移到引擎侧更贴近执行语义,interface 只做透传。
先前问题状态
- 🔒
operate_task_node.py:136绕过 Serializer 校验:✅ 此问题已通过将校验下沉至 engine 解决,engine 层有完整的 token 验签 + callback_ref 校验链路 - 其余先前报告的问题(
collect_plugin_references3x 调用、N+1 查询等)不在本次增量范围内,状态未变
新增代码小结
- 迁移策略合理:
plugin.0006删除旧表 +task.0016创建新表,因模型是本 PR 新增、尚无生产数据,不需要 data migration - 测试覆盖良好:4 个新增测试用例覆盖了 valid/invalid token、幂等、terminal state 等核心路径
- 回调 token 验签流程在 engine 侧实现完整且安全(token 解密 → 过期检查 → DB ref 匹配 → 消费幂等 → 状态判定 → bamboo callback)
There was a problem hiding this comment.
Incremental Review
最新 2 个提交:28eb0958 将 OpenPluginRunCallbackRef 模型和回调验证逻辑从 interface 层 (plugin app) 下沉到 engine 层 (task app),同时将 cancel 逻辑集成到 revoke 和 forced_fail 操作中;b9440b34 纯文档新增,无代码变更。
架构评估
模型下沉方向正确——OpenPluginRunCallbackRef 的生命周期完全在 engine 内(组件执行时创建、回调时消费、取消时清理),放在 task app 下消除了跨模块 ORM 操作问题。plugin/services/open_plugin_callback.py 保留 re-export 做向后兼容也是合理的。
先前问题状态
operate_task_node.py:136回调绕过 Serializer:✅ 此问题已通过架构变更解决——回调数据现在直接透传到 engine,由_handle_open_plugin_callback做完整的 token 验签和字段提取。
新发现问题(见行内评论)
⚠️ 迁移缺数据迁移步骤(部署风险)- ⚡
cancel_open_plugin_runs_for_task同步串行 HTTP 调用(潜在阻塞)
| @@ -0,0 +1,59 @@ | |||
| # Generated by Django 3.2.25 on 2026-07-03 08:31 | |||
There was a problem hiding this comment.
| return | ||
|
|
||
| callback_refs = model.objects.filter(task_id=task_instance.id, consumed_at__isnull=True) | ||
| for callback_ref in callback_refs.iterator(): |
There was a problem hiding this comment.
⚡ cancel_open_plugin_runs_for_task 在 revoke 时同步串行发起 HTTP 请求取消每个未消费的 callback_ref。若任务包含多个并行的开放插件节点(每个有独立 cancel_url),revoke 操作会被阻塞 N × timeout。建议考虑异步取消(如 dispatch Celery task)或并发请求(ThreadPoolExecutor),避免用户 revoke 操作超时。
There was a problem hiding this comment.
Incremental Review
最新 3 个提交(bfd6b27f、b3b9cc0c、28eb0958)完成了两项重构:
- 将 v4 demo 从
views.py/plugins.py拆分到独立的v4.py,URL 路径从open_plugin_v4/简化为v4/ - 将回调验证逻辑和
OpenPluginRunCallbackRef模型下沉到 engine 层(taskapp),interface 层只做透传
整体方向正确:engine 层统一管理回调状态与鉴权,网关资源收敛为 GET/POST 两个 matchSubpath 入口减少维护成本。
先前问题状态
operate_task_node.py:136回调绕过 Serializer:✅ 此问题已通过架构变更解决——验签逻辑完整下沉到 enginetask/migrations/0016缺数据迁移:已在上轮说明新功能无生产数据,维持原状cancel_open_plugin_runs_for_task同步串行 HTTP:不在本次增量范围,维持原状tests/interface/api_plugin_demo/__init__.py缺失:仍未修复
新发现
见行内评论(2 条)。
|
|
||
| def _build_open_plugin_callback_payload(data): | ||
| callback_payload = { | ||
| "open_plugin_run_id": data["open_plugin_run_id"], |
There was a problem hiding this comment.
_build_open_plugin_callback_payload 直接访问 data["status"]。若请求体只包含 open_plugin_run_id 而缺少 status,会触发未处理的 KeyError 导致 500。建议加 data.get("status", "") 或提前检查必填字段。
|
|
||
| @staticmethod | ||
| def _resolve_open_plugin_source_key(space_id, plugin_id, explicit_source_key=""): | ||
| return explicit_source_key or "" |
There was a problem hiding this comment.
✨ _resolve_open_plugin_source_key 现在移除了 DB fallback,若调用方未传 uniform_api_plugin_source_key,执行将直接失败(line 181-185)。确认上游(模板保存/建任务时)已保证 source_key 必填即可。
There was a problem hiding this comment.
Incremental Review
最新提交 d656b88 补充了 v4 协议验证的 category 分类接口,包含视图、URL 路由、APIGW 文档和测试用例。实现简洁清晰,DEMO_CATEGORIES 常量 + copy.deepcopy 防污染模式与同文件其他接口一致,网关利用 matchSubpath: true 无需额外资源定义。
新增代码评估
category_api实现简洁,GET 只读无副作用- 测试覆盖了 URL 路由解析和响应数据断言
- 文档
api_plugin_demo_v4_get.md同步更新了子路径表格和请求示例
先前问题状态
前几轮审查报告的行内问题状态不变:
- ⚡
collect_plugin_references3x 调用:未修复 - ⚡ N+1 查询(
_get_catalog_entry/is_granted):未修复 - 📄
backfill_open_plugin_snapshots.py/grant_open_plugin_source.py缺开源协议头:未修复 - 📄
tests/interface/api_plugin_demo/__init__.py缺失:未修复 - ⚡
cancel_open_plugin_runs_for_task同步串行 HTTP:未修复 ⚠️ operate_task_node.py回调分支data["status"]未校验:未修复
结论
本次增量无新问题。代码风格与同文件既有模式一致,测试和文档同步到位。
There was a problem hiding this comment.
Incremental Review
最新 2 个提交:
d656b889补充 v4 协议验证category分类接口5951b88c纯文档/前端原型补充(sops_open_plugin_frontend_contract.md、交互设计、HTML 原型)
新增代码评估
category_api实现简洁,与同文件既有 demo 接口风格一致,测试和 APIGW 文档已同步- 前端契约文档补充了 v4 节点保存字段清单和版本选择器交互规则,对前后端协作有正面价值
- 无新引入的逻辑问题
先前问题状态
前几轮审查报告的行内问题均未在本次增量中修复,状态不变:
| 问题 | 状态 |
|---|---|
⚡ create_task 中 collect_plugin_references 3x 调用 |
未修复 |
⚡ collect_plugin_references 循环内 N+1(_get_catalog_entry + exists()) |
未修复 |
⚡ cancel_open_plugin_runs_for_task 同步串行 HTTP |
未修复 |
📄 backfill_open_plugin_snapshots.py / grant_open_plugin_source.py 缺开源协议头 |
未修复 |
📄 tests/interface/api_plugin_demo/__init__.py 缺失 |
未修复 |
operate_task_node.py 回调分支 data["status"] 未校验(KeyError 风险) |
未修复 |
结论
本次增量(docs + category demo endpoint)无新问题,代码风格与既有模式一致。建议在合入前优先处理上表中 ⚡ 性能相关的 Important 级问题(尤其是 collect_plugin_references 重复调用和 N+1 查询),其余 Minor 问题可后续跟进。
There was a problem hiding this comment.
Incremental Review
最新 5 个提交(bfd6b27f ~ e0221f4ced)完成了 v4 demo 代码重构和前端原型文档补充:
- 重构 v4 demo 模块:将 v4 协议验证代码从
views.py/plugins.py拆分到独立v4.py,URL 路径从open_plugin_v4/简化为v4/ - 补充 category 接口:新增
v4/category/分类查询端点 - APIGW 资源收敛:使用
matchSubpath: true将 6 个独立资源合并为 GET/POST 两个入口,文档与 zip 同步更新 - 前端原型补充:按 wiremd 规范新增 V4.0.0 节点 API 插件的正常配置、版本切换、失效版本三态线框图,同步更新交互设计文档与前端对接协议
代码质量
重构质量良好:
v4.py包含完整 MIT 协议头,常量与工具函数组织清晰- 旧 URL 引用已全部清理,测试用例同步验证了新路由结构
- APIGW docs 与 api-resources.yml 同步完整,zip 已重新生成
- 文档变更遵循
docs-management.mdc目录规范
先前问题状态
⚠️ int(request.query_params.get("limit", 50))未做异常处理的问��已从views.py:374迁移到v4.py:232,仍未修复(已在先前轮次报告)- 📄
tests/interface/api_plugin_demo/__init__.py仍缺失(已在先前轮次报告) - 其余先前报告的 10 个行内问题未在本批次提交中涉及,状态不变
结论
本批次提交为纯重构+文档补充,无新引入问题,代码组织更清晰。先前报告的 2 个未解决问题(int() 异常处理、测试目录 __init__.py)建议后续修复。
e0221f4 to
00e27e7
Compare
There was a problem hiding this comment.
Incremental Review
最新提交 00e27e72 纯文档/原型补充——新增 v4 节点 wireframe 设计文档(docs/guide/、docs/specs/、docs/plans/)和 4 张 wireframe 截图(prototypes/output/sops-open-plugin-v4-node-api-plugin/),覆盖正常配置、版本切换、不可用版本三种交互状态。无代码逻辑变更。
新增文档评估
- 文档目录归属正确:guide → 前端协议约定,specs → 交互设计,plans → 实现计划增量
- wireframe markdown 结构清晰,包含 ASCII art + 注释说明,便于前端实现对照
- PNG 截图与 markdown 描述一一对应
先前问题状态
前几轮审查共报告 16 个行内问题,本次提交为纯文档变更,未涉及代码修改,先前报告的问题(collect_plugin_references 3x 调用、N+1 查询、缺少协议头、循环 update_or_create、数据迁移风险等)均未在此提交中解决,仍然 open。
无新问题。
There was a problem hiding this comment.
Incremental Review
最新提交 c19afb52 对前端原型和设计文档做了大幅精简——将 3 态完整 wireframe(正常配置/版本切换/失效版本)收敛为单一"节点配置增加版本选择"最小原型,同步简化了交互设计文档、全能力设计文档、前端对接协议和实现计划。
变更评估
- 文档范围收窄合理:从过度设计的版本差异对比、调度模式展示、失效版本治理等复杂 UI 回归到最小可行产品(只增加版本选择项),与"服务端负责校验,前端不做复杂治理 UI"的设计原则一致
- 删除冗余原型:
02-version-switch.md、03-unavailable-version.md及对应 PNG 已清除,新增01-node-config-version.md表达极简 - 文档目录正确:specs → 设计规范,plans → 实施计划增量,guide → 前端协议约定,prototypes → wireframe 源文件
- 无代码逻辑变更
先前问题状态
前几轮审查共报告 16 个行内问题,本次提交为纯文档变更,未涉及代码修改。以下 2 个低优先级问题仍待处理:
⚠️ v4.py:231int(request.query_params.get("limit", 50))未做异常保护(已在先前轮次报告,Minor 级别)- 📄
tests/interface/api_plugin_demo/__init__.py仍缺失(已在先前轮次报告,Minor 级别)
其余先前报告的问题未在本次提交中涉及,状态不变。无新问题发现。
There was a problem hiding this comment.
Incremental Review
最新提交 a5f39d3b 仅调整 v4 节点版本原型的 wireframe 文档,将 wireframe 从独立的"版本选择"卡片扩展为完整的"画布 + 800px 侧滑"节点配置模拟,更贴近现有界面布局。无 Python 代码变更,无新问题引入。
先前问题状态
前序审查报告的行内问题(collect_plugin_references 3x 调用、N+1 查询、缺少协议头、循环 update_or_create、回调跳过 Serializer 校验、迁移数据丢失风险等)在本次提交中未涉及修复,仍待处理。
总结
本次增量无代码逻辑变更,仅为文档/原型优化,无新增审查意见。
Summary
Test Plan
pytest -o addopts='' tests/plugins/uniform_api/test_uniform_api_client.py tests/plugins/components/collections/uniform_api_test tests/interface/plugin/services/test_plugin_schema_service.py tests/interface/space/test_space_views.py tests/interface/apigw/test_list_plugins.py tests/interface/apigw/test_get_plugin_schema.py tests/interface/apigw/test_operate_task.py tests/interface/apigw/test_operate_task_node.py -qpytest -o addopts='' tests/interface/statistics tests/engine/statistics -qgit diff --check -- bkflow tests scripts