cancan-microstack 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (440) hide show
  1. cancan_microstack/__init__.py +14 -0
  2. cancan_microstack/__version__.py +10 -0
  3. cancan_microstack/assets/__init__.py +6 -0
  4. cancan_microstack/assets/builds/caddy/Caddyfile +187 -0
  5. cancan_microstack/assets/builds/caddy/DEPLOYMENT.md +303 -0
  6. cancan_microstack/assets/builds/caddy/Dockerfile +46 -0
  7. cancan_microstack/assets/builds/caddy/README.md +343 -0
  8. cancan_microstack/assets/builds/caddy/geoip/README.md +5 -0
  9. cancan_microstack/assets/builds/caddy/start.sh +78 -0
  10. cancan_microstack/assets/builds/caddy/waf/coraza.conf +179 -0
  11. cancan_microstack/assets/builds/service/Dockerfile +59 -0
  12. cancan_microstack/assets/builds/service/README.md +13 -0
  13. cancan_microstack/assets/ddl/create_db.sql +22 -0
  14. cancan_microstack/assets/ddl/infra/execution_log_tbl.sql +46 -0
  15. cancan_microstack/assets/ddl/infra/node_instance_tbl.sql +56 -0
  16. cancan_microstack/assets/ddl/infra/service_action_log_tbl.sql +36 -0
  17. cancan_microstack/assets/ddl/infra/service_config_tbl.sql +26 -0
  18. cancan_microstack/assets/ddl/infra/service_info_tbl.sql +45 -0
  19. cancan_microstack/assets/ddl/infra/service_instance_tbl.sql +54 -0
  20. cancan_microstack/assets/ddl/infra/service_operation_tbl.sql +47 -0
  21. cancan_microstack/assets/ddl/infra/workflow_definition_tbl.sql +60 -0
  22. cancan_microstack/assets/ddl/infra/workflow_definition_version_tbl.sql +35 -0
  23. cancan_microstack/assets/ddl/infra/workflow_engine_alert_tbl.sql +34 -0
  24. cancan_microstack/assets/ddl/infra/workflow_run_tbl.sql +52 -0
  25. cancan_microstack/assets/ddl/ops/admin_user_tbl.sql +34 -0
  26. cancan_microstack/assets/ddl/ops/caddy_access_log_tbl.sql +91 -0
  27. cancan_microstack/assets/ddl/ops/caddy_certificate_tbl.sql +59 -0
  28. cancan_microstack/assets/ddl/ops/caddy_rate_limit_tbl.sql +64 -0
  29. cancan_microstack/assets/ddl/ops/caddy_route_tbl.sql +63 -0
  30. cancan_microstack/assets/ddl/ops/caddy_stats_tbl.sql +77 -0
  31. cancan_microstack/assets/ddl/trigger.sql +21 -0
  32. cancan_microstack/assets/docker/docker-compose.infra.yml +401 -0
  33. cancan_microstack/assets/scripts/README.md +195 -0
  34. cancan_microstack/assets/scripts/docker/build_images.sh +44 -0
  35. cancan_microstack/assets/scripts/docker/force_rebuild_images.sh +38 -0
  36. cancan_microstack/assets/scripts/docker/rebuild_all.sh +34 -0
  37. cancan_microstack/assets/scripts/docker/rebuild_compose.sh +61 -0
  38. cancan_microstack/assets/scripts/docker/restart.sh +35 -0
  39. cancan_microstack/assets/scripts/docker/restart_compose.sh +35 -0
  40. cancan_microstack/assets/scripts/docker/start.sh +78 -0
  41. cancan_microstack/assets/scripts/docker/start_all.sh +46 -0
  42. cancan_microstack/assets/scripts/docker/start_compose.sh +66 -0
  43. cancan_microstack/assets/scripts/docker/stop.sh +67 -0
  44. cancan_microstack/assets/scripts/docker/stop_all.sh +38 -0
  45. cancan_microstack/assets/scripts/docker/stop_compose.sh +38 -0
  46. cancan_microstack/assets/scripts/podman/build_images_podman.sh +59 -0
  47. cancan_microstack/assets/scripts/podman/cleanup_podman.sh +25 -0
  48. cancan_microstack/assets/scripts/podman/force_rebuild_images_podman.sh +56 -0
  49. cancan_microstack/assets/scripts/podman/rebuild_all_podman.sh +37 -0
  50. cancan_microstack/assets/scripts/podman/rebuild_compose_podman.sh +60 -0
  51. cancan_microstack/assets/scripts/podman/restart_compose_podman.sh +73 -0
  52. cancan_microstack/assets/scripts/podman/start_all_podman.sh +66 -0
  53. cancan_microstack/assets/scripts/podman/start_compose_podman.sh +80 -0
  54. cancan_microstack/assets/scripts/podman/start_podman.sh +91 -0
  55. cancan_microstack/assets/scripts/podman/stop.sh +73 -0
  56. cancan_microstack/assets/scripts/podman/stop_all_podman.sh +34 -0
  57. cancan_microstack/assets/scripts/podman/stop_compose_podman.sh +58 -0
  58. cancan_microstack/assets/scripts/start_controllersrv.sh +9 -0
  59. cancan_microstack/assets/scripts/utils/check_all_db_tables.sh +104 -0
  60. cancan_microstack/assets/scripts/utils/check_env.sh +177 -0
  61. cancan_microstack/assets/scripts/utils/check_service_management_deployment.sh +225 -0
  62. cancan_microstack/assets/scripts/utils/deploy_service_management.sh +176 -0
  63. cancan_microstack/assets/scripts/utils/force_reload_infrasrv.sh +52 -0
  64. cancan_microstack/assets/scripts/utils/monitor_service_management.sh +187 -0
  65. cancan_microstack/assets/scripts/utils/reset_postgres_volume.sh +68 -0
  66. cancan_microstack/assets/scripts/utils/test_async_operations.sh +141 -0
  67. cancan_microstack/assets/scripts/utils/verify_real_operations.sh +76 -0
  68. cancan_microstack/assets/service/Dockerfile +65 -0
  69. cancan_microstack/assets/www/adminops/assets/AppEmpty.vue_vue_type_script_setup_true_lang-BOKUurnM.js +1 -0
  70. cancan_microstack/assets/www/adminops/assets/ConfigManage-DKV5YOUz.js +1 -0
  71. cancan_microstack/assets/www/adminops/assets/ConfigManage-Y5bhy7wG.css +1 -0
  72. cancan_microstack/assets/www/adminops/assets/ConsoleManage-8ljYvCW2.js +1 -0
  73. cancan_microstack/assets/www/adminops/assets/ConsoleManage-BWpyqbuQ.css +1 -0
  74. cancan_microstack/assets/www/adminops/assets/DashboardNew-B9Nf1OPl.js +1 -0
  75. cancan_microstack/assets/www/adminops/assets/DashboardNew-DYWZKQ1V.css +1 -0
  76. cancan_microstack/assets/www/adminops/assets/LogSearch-CA0Jhe78.js +1 -0
  77. cancan_microstack/assets/www/adminops/assets/LogSearch-CCZfTNPF.css +1 -0
  78. cancan_microstack/assets/www/adminops/assets/LoginView-BId3kP3M.css +1 -0
  79. cancan_microstack/assets/www/adminops/assets/LoginView-BQZTV_Qy.js +1 -0
  80. cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-BdEYwqFq.js +1 -0
  81. cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-D-pASR8G.css +1 -0
  82. cancan_microstack/assets/www/adminops/assets/PageContainer-Byss-yUC.js +1 -0
  83. cancan_microstack/assets/www/adminops/assets/PageContainer-C3nSZwM7.css +1 -0
  84. cancan_microstack/assets/www/adminops/assets/RateLimitManage-BDI8jLpC.css +1 -0
  85. cancan_microstack/assets/www/adminops/assets/RateLimitManage-DJY4NiF-.js +1 -0
  86. cancan_microstack/assets/www/adminops/assets/RouteManage-DaUQ4QLw.css +1 -0
  87. cancan_microstack/assets/www/adminops/assets/RouteManage-w9XCU0UA.js +1 -0
  88. cancan_microstack/assets/www/adminops/assets/ServiceCard-BFzHe6Tw.css +1 -0
  89. cancan_microstack/assets/www/adminops/assets/ServiceCard-BJUhWnA-.js +1 -0
  90. cancan_microstack/assets/www/adminops/assets/ServiceDetail-Cw24WuKp.js +1 -0
  91. cancan_microstack/assets/www/adminops/assets/ServiceDetail-Yum47zdB.css +1 -0
  92. cancan_microstack/assets/www/adminops/assets/ServiceList-C7ryvbhE.js +1 -0
  93. cancan_microstack/assets/www/adminops/assets/ServiceList-Cgd01fUx.css +1 -0
  94. cancan_microstack/assets/www/adminops/assets/ServiceLogs-COpG9H0h.js +1 -0
  95. cancan_microstack/assets/www/adminops/assets/ServiceLogs-H_Alq0cf.css +1 -0
  96. cancan_microstack/assets/www/adminops/assets/StatsOverview-D0TwMQkA.js +39 -0
  97. cancan_microstack/assets/www/adminops/assets/StatsOverview-lqAN6pqM.css +1 -0
  98. cancan_microstack/assets/www/adminops/assets/TotpBindView-CWlAmzFt.js +1 -0
  99. cancan_microstack/assets/www/adminops/assets/TotpBindView-HoQC1lhx.css +1 -0
  100. cancan_microstack/assets/www/adminops/assets/TotpVerifyView-BHN1VtX1.css +1 -0
  101. cancan_microstack/assets/www/adminops/assets/TotpVerifyView-D3w_lZk8.js +1 -0
  102. cancan_microstack/assets/www/adminops/assets/WorkflowCenter-DU_mpIA0.css +1 -0
  103. cancan_microstack/assets/www/adminops/assets/WorkflowCenter-i50rZyxN.js +1 -0
  104. cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-CnHokPL9.js +1 -0
  105. cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-DaZaZpLd.css +1 -0
  106. cancan_microstack/assets/www/adminops/assets/WorkflowRuns-B09hK48c.js +1 -0
  107. cancan_microstack/assets/www/adminops/assets/WorkflowRuns-wGutKIIU.css +1 -0
  108. cancan_microstack/assets/www/adminops/assets/caddy-nnCKf8fG.js +1 -0
  109. cancan_microstack/assets/www/adminops/assets/format-Cuzxgna9.js +1 -0
  110. cancan_microstack/assets/www/adminops/assets/index-CiFlm8oc.js +64 -0
  111. cancan_microstack/assets/www/adminops/assets/index-UW0T1Dkc.css +1 -0
  112. cancan_microstack/assets/www/adminops/assets/service-BYlgGPs_.js +1 -0
  113. cancan_microstack/assets/www/adminops/assets/service-operation-6GzLw2Z1.js +1 -0
  114. cancan_microstack/assets/www/adminops/assets/style-CcIXnQ5y.css +1 -0
  115. cancan_microstack/assets/www/adminops/assets/style-lRnStdGu.js +39 -0
  116. cancan_microstack/assets/www/adminops/assets/useDebounce-BRlqfXqf.js +1 -0
  117. cancan_microstack/assets/www/adminops/assets/workflow-CUXs39Ac.js +1 -0
  118. cancan_microstack/assets/www/adminops/index.html +16 -0
  119. cancan_microstack/assets/www/adminops/vite.svg +1 -0
  120. cancan_microstack/cli/__init__.py +14 -0
  121. cancan_microstack/cli/__main__.py +9 -0
  122. cancan_microstack/cli/main.py +552 -0
  123. cancan_microstack/cmd/__init__.py +54 -0
  124. cancan_microstack/cmd/cancan/__init__.py +12 -0
  125. cancan_microstack/cmd/cancan/run.py +395 -0
  126. cancan_microstack/cmd/controllersrv/__init__.py +0 -0
  127. cancan_microstack/cmd/controllersrv/run.py +131 -0
  128. cancan_microstack/cmd/infrasrv/__init__.py +5 -0
  129. cancan_microstack/cmd/infrasrv/run.py +100 -0
  130. cancan_microstack/cmd/opsbffsrv/__init__.py +5 -0
  131. cancan_microstack/cmd/opsbffsrv/run.py +96 -0
  132. cancan_microstack/core/__init__.py +5 -0
  133. cancan_microstack/core/assets.py +123 -0
  134. cancan_microstack/core/compose_builder.py +102 -0
  135. cancan_microstack/core/doctor.py +152 -0
  136. cancan_microstack/core/microstack.py +71 -0
  137. cancan_microstack/core/runner.py +56 -0
  138. cancan_microstack/core/stack_manager.py +186 -0
  139. cancan_microstack/public/__init__.py +7 -0
  140. cancan_microstack/public/api/__init__.py +1 -0
  141. cancan_microstack/public/api/controllersrv_client.py +277 -0
  142. cancan_microstack/public/api/infrasrv_client.py +404 -0
  143. cancan_microstack/public/const/__init__.py +1 -0
  144. cancan_microstack/public/const/action_consts.py +18 -0
  145. cancan_microstack/public/const/app_consts.py +42 -0
  146. cancan_microstack/public/const/caddy_consts.py +22 -0
  147. cancan_microstack/public/const/controllersrv_consts.py +163 -0
  148. cancan_microstack/public/const/docker_consts.py +15 -0
  149. cancan_microstack/public/const/error.py +56 -0
  150. cancan_microstack/public/const/health_consts.py +52 -0
  151. cancan_microstack/public/const/hook_enums.py +56 -0
  152. cancan_microstack/public/const/logging_enums.py +13 -0
  153. cancan_microstack/public/const/metrics_enums.py +36 -0
  154. cancan_microstack/public/const/monitor_enums.py +26 -0
  155. cancan_microstack/public/const/operation_consts.py +53 -0
  156. cancan_microstack/public/const/opsbffsrv_error.py +92 -0
  157. cancan_microstack/public/const/overrides_consts.py +13 -0
  158. cancan_microstack/public/const/redis.py +17 -0
  159. cancan_microstack/public/const/service_consts.py +15 -0
  160. cancan_microstack/public/const/workflow_consts.py +65 -0
  161. cancan_microstack/public/error.py +41 -0
  162. cancan_microstack/public/logging/__init__.py +0 -0
  163. cancan_microstack/public/logging/initializer.py +109 -0
  164. cancan_microstack/public/logging/mq_handler.py +279 -0
  165. cancan_microstack/public/schemas/__init__.py +1 -0
  166. cancan_microstack/public/schemas/caddy/__init__.py +381 -0
  167. cancan_microstack/public/schemas/caddy/analysis.py +90 -0
  168. cancan_microstack/public/schemas/caddy/route.py +18 -0
  169. cancan_microstack/public/schemas/common.py +79 -0
  170. cancan_microstack/public/schemas/controllersrv/__init__.py +3 -0
  171. cancan_microstack/public/schemas/controllersrv/async_requests.py +30 -0
  172. cancan_microstack/public/schemas/controllersrv/compose_models.py +47 -0
  173. cancan_microstack/public/schemas/controllersrv/const.py +24 -0
  174. cancan_microstack/public/schemas/controllersrv/docker_models.py +45 -0
  175. cancan_microstack/public/schemas/controllersrv/docker_responses.py +104 -0
  176. cancan_microstack/public/schemas/controllersrv/requests.py +54 -0
  177. cancan_microstack/public/schemas/controllersrv/responses.py +124 -0
  178. cancan_microstack/public/schemas/controllersrv/task_models.py +102 -0
  179. cancan_microstack/public/schemas/controllersrv/validation.py +23 -0
  180. cancan_microstack/public/schemas/hook_metrics.py +124 -0
  181. cancan_microstack/public/schemas/hooks.py +39 -0
  182. cancan_microstack/public/schemas/infra/__init__.py +0 -0
  183. cancan_microstack/public/schemas/infra/cleanup.py +25 -0
  184. cancan_microstack/public/schemas/infra/container.py +74 -0
  185. cancan_microstack/public/schemas/infra/enums.py +135 -0
  186. cancan_microstack/public/schemas/infra/health_check.py +42 -0
  187. cancan_microstack/public/schemas/infra/hook_log.py +42 -0
  188. cancan_microstack/public/schemas/infra/operation.py +90 -0
  189. cancan_microstack/public/schemas/infra/overview.py +25 -0
  190. cancan_microstack/public/schemas/infra/push.py +33 -0
  191. cancan_microstack/public/schemas/infra/service_action_log.py +47 -0
  192. cancan_microstack/public/schemas/infra/service_config.py +10 -0
  193. cancan_microstack/public/schemas/infra/service_info.py +69 -0
  194. cancan_microstack/public/schemas/infra/service_instance.py +93 -0
  195. cancan_microstack/public/schemas/infra/service_management.py +152 -0
  196. cancan_microstack/public/schemas/infra/service_operation.py +79 -0
  197. cancan_microstack/public/schemas/infra/service_registry.py +158 -0
  198. cancan_microstack/public/schemas/infra/status_types.py +19 -0
  199. cancan_microstack/public/schemas/infra/workflow.py +566 -0
  200. cancan_microstack/public/schemas/logging/__init__.py +1 -0
  201. cancan_microstack/public/schemas/logging/log_event.py +121 -0
  202. cancan_microstack/public/schemas/opsbffsrv/__init__.py +1 -0
  203. cancan_microstack/public/schemas/opsbffsrv/async_ops.py +17 -0
  204. cancan_microstack/public/schemas/opsbffsrv/db_admin.py +147 -0
  205. cancan_microstack/public/schemas/opsbffsrv/db_init.py +48 -0
  206. cancan_microstack/public/schemas/opsbffsrv/service_config.py +89 -0
  207. cancan_microstack/public/schemas/opsbffsrv/service_logs.py +54 -0
  208. cancan_microstack/public/schemas/service_operation.py +24 -0
  209. cancan_microstack/public/schemas/service_registry.py +40 -0
  210. cancan_microstack/public/types/__init__.py +7 -0
  211. cancan_microstack/public/web/__init__.py +0 -0
  212. cancan_microstack/public/web/config_value.py +105 -0
  213. cancan_microstack/public/web/server.py +385 -0
  214. cancan_microstack/py.typed +0 -0
  215. cancan_microstack/runtime/__init__.py +0 -0
  216. cancan_microstack/runtime/compose_cmd.py +228 -0
  217. cancan_microstack/runtime/host_daemon.py +318 -0
  218. cancan_microstack/runtime/overrides.py +103 -0
  219. cancan_microstack/runtime/resources.py +25 -0
  220. cancan_microstack/runtime/workspace.py +94 -0
  221. cancan_microstack/services/__init__.py +0 -0
  222. cancan_microstack/services/controllersrv/__init__.py +8 -0
  223. cancan_microstack/services/controllersrv/application/__init__.py +0 -0
  224. cancan_microstack/services/controllersrv/application/docker_compose_app.py +427 -0
  225. cancan_microstack/services/controllersrv/conf/__init__.py +0 -0
  226. cancan_microstack/services/controllersrv/conf/config.py +76 -0
  227. cancan_microstack/services/controllersrv/conf/settings.py +54 -0
  228. cancan_microstack/services/controllersrv/domain/__init__.py +0 -0
  229. cancan_microstack/services/controllersrv/domain/docker_compose/__init__.py +0 -0
  230. cancan_microstack/services/controllersrv/domain/docker_compose/docker_compose_domain.py +278 -0
  231. cancan_microstack/services/controllersrv/domain/service_validator.py +327 -0
  232. cancan_microstack/services/controllersrv/domain/task/__init__.py +17 -0
  233. cancan_microstack/services/controllersrv/domain/task/task_queue.py +286 -0
  234. cancan_microstack/services/controllersrv/domain/task/task_worker.py +495 -0
  235. cancan_microstack/services/controllersrv/infrastructure/__init__.py +0 -0
  236. cancan_microstack/services/controllersrv/interface/__init__.py +0 -0
  237. cancan_microstack/services/controllersrv/interface/api/__init__.py +0 -0
  238. cancan_microstack/services/controllersrv/interface/api/docker_control_api.py +470 -0
  239. cancan_microstack/services/controllersrv/router.py +132 -0
  240. cancan_microstack/services/infrasrv/__init__.py +4 -0
  241. cancan_microstack/services/infrasrv/application/__init__.py +0 -0
  242. cancan_microstack/services/infrasrv/application/health_check_app.py +24 -0
  243. cancan_microstack/services/infrasrv/application/logging/__init__.py +1 -0
  244. cancan_microstack/services/infrasrv/application/logging/log_ingestion_service.py +183 -0
  245. cancan_microstack/services/infrasrv/application/service_config.py +22 -0
  246. cancan_microstack/services/infrasrv/application/service_logs_app.py +53 -0
  247. cancan_microstack/services/infrasrv/application/service_management_app.py +689 -0
  248. cancan_microstack/services/infrasrv/application/service_operation_tracker.py +251 -0
  249. cancan_microstack/services/infrasrv/application/service_registry.py +53 -0
  250. cancan_microstack/services/infrasrv/application/workflow/__init__.py +0 -0
  251. cancan_microstack/services/infrasrv/application/workflow/workflow_app.py +991 -0
  252. cancan_microstack/services/infrasrv/application/workflow/workflow_queue.py +302 -0
  253. cancan_microstack/services/infrasrv/application/workflow/workflow_tasks.py +46 -0
  254. cancan_microstack/services/infrasrv/application/workflow/workflow_worker_runtime.py +122 -0
  255. cancan_microstack/services/infrasrv/conf/__init__.py +0 -0
  256. cancan_microstack/services/infrasrv/conf/config.py +98 -0
  257. cancan_microstack/services/infrasrv/domain/__init__.py +0 -0
  258. cancan_microstack/services/infrasrv/domain/health_check/__init__.py +3 -0
  259. cancan_microstack/services/infrasrv/domain/health_check/health_check_domain.py +576 -0
  260. cancan_microstack/services/infrasrv/domain/hooks/__init__.py +19 -0
  261. cancan_microstack/services/infrasrv/domain/hooks/builtin_hooks.py +308 -0
  262. cancan_microstack/services/infrasrv/domain/hooks/hook_registry.py +43 -0
  263. cancan_microstack/services/infrasrv/domain/hooks/hooks_log_utils.py +275 -0
  264. cancan_microstack/services/infrasrv/domain/hooks/init.py +17 -0
  265. cancan_microstack/services/infrasrv/domain/hooks/metrics.py +205 -0
  266. cancan_microstack/services/infrasrv/domain/hooks/pre_registration_hooks.py +490 -0
  267. cancan_microstack/services/infrasrv/domain/registry/__init__.py +0 -0
  268. cancan_microstack/services/infrasrv/domain/registry/service_registry.py +509 -0
  269. cancan_microstack/services/infrasrv/domain/service_config/__init__.py +0 -0
  270. cancan_microstack/services/infrasrv/domain/service_config/service_config.py +50 -0
  271. cancan_microstack/services/infrasrv/domain/service_logs/__init__.py +0 -0
  272. cancan_microstack/services/infrasrv/domain/service_logs/service_logs_domain.py +51 -0
  273. cancan_microstack/services/infrasrv/domain/workflow/__init__.py +4 -0
  274. cancan_microstack/services/infrasrv/domain/workflow/engine.py +159 -0
  275. cancan_microstack/services/infrasrv/domain/workflow/node_handlers.py +509 -0
  276. cancan_microstack/services/infrasrv/domain/workflow/workflow_domain.py +164 -0
  277. cancan_microstack/services/infrasrv/infrastructure/__init__.py +0 -0
  278. cancan_microstack/services/infrasrv/infrastructure/api/__init__.py +0 -0
  279. cancan_microstack/services/infrasrv/infrastructure/api/controllersrv_api.py +165 -0
  280. cancan_microstack/services/infrasrv/infrastructure/cache/__init__.py +0 -0
  281. cancan_microstack/services/infrasrv/infrastructure/cache/service_registry_cache.py +174 -0
  282. cancan_microstack/services/infrasrv/infrastructure/db/__init__.py +0 -0
  283. cancan_microstack/services/infrasrv/infrastructure/db/model/__init__.py +0 -0
  284. cancan_microstack/services/infrasrv/infrastructure/db/model/execution_log_tbl.py +53 -0
  285. cancan_microstack/services/infrasrv/infrastructure/db/model/node_instance_tbl.py +55 -0
  286. cancan_microstack/services/infrasrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
  287. cancan_microstack/services/infrasrv/infrastructure/db/model/service_config_tbl.py +30 -0
  288. cancan_microstack/services/infrasrv/infrastructure/db/model/service_info_tbl.py +59 -0
  289. cancan_microstack/services/infrasrv/infrastructure/db/model/service_instance_tbl.py +88 -0
  290. cancan_microstack/services/infrasrv/infrastructure/db/model/service_operation_tbl.py +73 -0
  291. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_tbl.py +55 -0
  292. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_version_tbl.py +43 -0
  293. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_engine_alert_tbl.py +57 -0
  294. cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_run_tbl.py +56 -0
  295. cancan_microstack/services/infrasrv/infrastructure/db/operate/__init__.py +0 -0
  296. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_action_log_op.py +239 -0
  297. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config.py +80 -0
  298. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config_manager.py +198 -0
  299. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_info_op.py +297 -0
  300. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_instance_op.py +688 -0
  301. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_operation_op.py +387 -0
  302. cancan_microstack/services/infrasrv/infrastructure/db/operate/service_registry.py +124 -0
  303. cancan_microstack/services/infrasrv/infrastructure/db/operate/workflow_op.py +804 -0
  304. cancan_microstack/services/infrasrv/infrastructure/ddl_manager.py +31 -0
  305. cancan_microstack/services/infrasrv/infrastructure/mongo/__init__.py +1 -0
  306. cancan_microstack/services/infrasrv/infrastructure/mongo/log_repository.py +129 -0
  307. cancan_microstack/services/infrasrv/interface/__init__.py +0 -0
  308. cancan_microstack/services/infrasrv/interface/api/__init__.py +0 -0
  309. cancan_microstack/services/infrasrv/interface/api/health_check_api.py +29 -0
  310. cancan_microstack/services/infrasrv/interface/api/hooks.py +284 -0
  311. cancan_microstack/services/infrasrv/interface/api/internal.py +49 -0
  312. cancan_microstack/services/infrasrv/interface/api/internal_instance_api.py +265 -0
  313. cancan_microstack/services/infrasrv/interface/api/internal_operation_api.py +206 -0
  314. cancan_microstack/services/infrasrv/interface/api/service_config.py +50 -0
  315. cancan_microstack/services/infrasrv/interface/api/service_logs_api.py +49 -0
  316. cancan_microstack/services/infrasrv/interface/api/service_management_api.py +113 -0
  317. cancan_microstack/services/infrasrv/interface/api/service_registry.py +117 -0
  318. cancan_microstack/services/infrasrv/interface/api/workflow_api.py +303 -0
  319. cancan_microstack/services/infrasrv/interface/schedule/__init__.py +0 -0
  320. cancan_microstack/services/infrasrv/interface/schedule/cleanup.py +13 -0
  321. cancan_microstack/services/infrasrv/interface/schedule/health_check.py +27 -0
  322. cancan_microstack/services/infrasrv/interface/schedule/log_cleanup.py +26 -0
  323. cancan_microstack/services/infrasrv/interface/schedule/operation_tracker.py +25 -0
  324. cancan_microstack/services/infrasrv/interface/schedule/scheduler.py +39 -0
  325. cancan_microstack/services/infrasrv/interface/schedule/workflow_scheduler.py +115 -0
  326. cancan_microstack/services/infrasrv/router.py +341 -0
  327. cancan_microstack/services/opsbffsrv/__init__.py +4 -0
  328. cancan_microstack/services/opsbffsrv/application/__init__.py +0 -0
  329. cancan_microstack/services/opsbffsrv/application/async_operation_app.py +150 -0
  330. cancan_microstack/services/opsbffsrv/application/auth_app.py +285 -0
  331. cancan_microstack/services/opsbffsrv/application/caddy/__init__.py +0 -0
  332. cancan_microstack/services/opsbffsrv/application/caddy/access_log_analysis_app.py +344 -0
  333. cancan_microstack/services/opsbffsrv/application/caddy/access_log_ingestion_service.py +169 -0
  334. cancan_microstack/services/opsbffsrv/application/caddy/certificate_management_app.py +355 -0
  335. cancan_microstack/services/opsbffsrv/application/caddy/rate_limit_management_app.py +496 -0
  336. cancan_microstack/services/opsbffsrv/application/caddy/route_management_app.py +401 -0
  337. cancan_microstack/services/opsbffsrv/application/caddy/stats_aggregation_app.py +364 -0
  338. cancan_microstack/services/opsbffsrv/application/db_admin_app.py +103 -0
  339. cancan_microstack/services/opsbffsrv/application/db_init_app.py +283 -0
  340. cancan_microstack/services/opsbffsrv/application/logging/__init__.py +1 -0
  341. cancan_microstack/services/opsbffsrv/application/logging/log_query_app.py +28 -0
  342. cancan_microstack/services/opsbffsrv/application/service_config.py +158 -0
  343. cancan_microstack/services/opsbffsrv/application/service_logs_app.py +74 -0
  344. cancan_microstack/services/opsbffsrv/application/service_registry.py +36 -0
  345. cancan_microstack/services/opsbffsrv/application/workflow_ops_app.py +730 -0
  346. cancan_microstack/services/opsbffsrv/conf/__init__.py +0 -0
  347. cancan_microstack/services/opsbffsrv/conf/config.py +224 -0
  348. cancan_microstack/services/opsbffsrv/domain/__init__.py +0 -0
  349. cancan_microstack/services/opsbffsrv/domain/auth/__init__.py +0 -0
  350. cancan_microstack/services/opsbffsrv/domain/auth/admin_init.py +38 -0
  351. cancan_microstack/services/opsbffsrv/domain/auth/auth_domain.py +108 -0
  352. cancan_microstack/services/opsbffsrv/domain/caddy/__init__.py +0 -0
  353. cancan_microstack/services/opsbffsrv/domain/caddy/access_log_analysis.py +358 -0
  354. cancan_microstack/services/opsbffsrv/domain/caddy/certificate_management.py +325 -0
  355. cancan_microstack/services/opsbffsrv/domain/caddy/default_routes.py +53 -0
  356. cancan_microstack/services/opsbffsrv/domain/caddy/rate_limit_management.py +308 -0
  357. cancan_microstack/services/opsbffsrv/domain/caddy/route_management.py +279 -0
  358. cancan_microstack/services/opsbffsrv/domain/caddy/stats_aggregation.py +654 -0
  359. cancan_microstack/services/opsbffsrv/domain/db_admin/__init__.py +0 -0
  360. cancan_microstack/services/opsbffsrv/domain/db_admin/db_admin_domain.py +118 -0
  361. cancan_microstack/services/opsbffsrv/domain/db_init/__init__.py +3 -0
  362. cancan_microstack/services/opsbffsrv/domain/db_init/db_init_domain.py +358 -0
  363. cancan_microstack/services/opsbffsrv/domain/logging/__init__.py +1 -0
  364. cancan_microstack/services/opsbffsrv/domain/logging/log_query_domain.py +99 -0
  365. cancan_microstack/services/opsbffsrv/domain/service_config/__init__.py +0 -0
  366. cancan_microstack/services/opsbffsrv/domain/service_config/service_config.py +81 -0
  367. cancan_microstack/services/opsbffsrv/domain/service_registry/__init__.py +0 -0
  368. cancan_microstack/services/opsbffsrv/domain/service_registry/service_registry.py +292 -0
  369. cancan_microstack/services/opsbffsrv/infrastructure/__init__.py +0 -0
  370. cancan_microstack/services/opsbffsrv/infrastructure/api/__init__.py +0 -0
  371. cancan_microstack/services/opsbffsrv/infrastructure/api/infrasrv_api.py +242 -0
  372. cancan_microstack/services/opsbffsrv/infrastructure/auth/__init__.py +0 -0
  373. cancan_microstack/services/opsbffsrv/infrastructure/auth/captcha_service.py +67 -0
  374. cancan_microstack/services/opsbffsrv/infrastructure/auth/password_service.py +12 -0
  375. cancan_microstack/services/opsbffsrv/infrastructure/auth/redis_store.py +131 -0
  376. cancan_microstack/services/opsbffsrv/infrastructure/auth/totp_service.py +59 -0
  377. cancan_microstack/services/opsbffsrv/infrastructure/caddy/__init__.py +0 -0
  378. cancan_microstack/services/opsbffsrv/infrastructure/caddy/access_log_parser.py +307 -0
  379. cancan_microstack/services/opsbffsrv/infrastructure/caddy/admin_api_client.py +678 -0
  380. cancan_microstack/services/opsbffsrv/infrastructure/caddy/ip_geo_locator.py +176 -0
  381. cancan_microstack/services/opsbffsrv/infrastructure/db/__init__.py +0 -0
  382. cancan_microstack/services/opsbffsrv/infrastructure/db/model/__init__.py +0 -0
  383. cancan_microstack/services/opsbffsrv/infrastructure/db/model/admin_user_tbl.py +33 -0
  384. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_access_log_tbl.py +90 -0
  385. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_certificate_tbl.py +65 -0
  386. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_rate_limit_tbl.py +69 -0
  387. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_route_tbl.py +66 -0
  388. cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_stats_tbl.py +78 -0
  389. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
  390. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_config_tbl.py +30 -0
  391. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_info_tbl.py +51 -0
  392. cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_instance_tbl.py +68 -0
  393. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/__init__.py +0 -0
  394. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/admin_user_operate.py +59 -0
  395. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_access_log.py +531 -0
  396. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_certificate.py +451 -0
  397. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_rate_limit.py +360 -0
  398. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_route.py +271 -0
  399. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_stats.py +343 -0
  400. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_action_log_op.py +57 -0
  401. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_config.py +86 -0
  402. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_info_op.py +79 -0
  403. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_instance.py +58 -0
  404. cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_registry.py +138 -0
  405. cancan_microstack/services/opsbffsrv/infrastructure/ddl_manager.py +31 -0
  406. cancan_microstack/services/opsbffsrv/infrastructure/mongo/__init__.py +1 -0
  407. cancan_microstack/services/opsbffsrv/infrastructure/mongo/log_query_repository.py +87 -0
  408. cancan_microstack/services/opsbffsrv/interface/__init__.py +0 -0
  409. cancan_microstack/services/opsbffsrv/interface/api/__init__.py +0 -0
  410. cancan_microstack/services/opsbffsrv/interface/api/async_operation_api.py +137 -0
  411. cancan_microstack/services/opsbffsrv/interface/api/auth_api.py +113 -0
  412. cancan_microstack/services/opsbffsrv/interface/api/caddy/__init__.py +3 -0
  413. cancan_microstack/services/opsbffsrv/interface/api/caddy/access_log_api.py +174 -0
  414. cancan_microstack/services/opsbffsrv/interface/api/caddy/certificate_api.py +235 -0
  415. cancan_microstack/services/opsbffsrv/interface/api/caddy/rate_limit_api.py +302 -0
  416. cancan_microstack/services/opsbffsrv/interface/api/caddy/route_api.py +250 -0
  417. cancan_microstack/services/opsbffsrv/interface/api/caddy/stats_api.py +243 -0
  418. cancan_microstack/services/opsbffsrv/interface/api/db_admin_api.py +62 -0
  419. cancan_microstack/services/opsbffsrv/interface/api/db_init_api.py +109 -0
  420. cancan_microstack/services/opsbffsrv/interface/api/instance_management_api.py +165 -0
  421. cancan_microstack/services/opsbffsrv/interface/api/log_query_api.py +41 -0
  422. cancan_microstack/services/opsbffsrv/interface/api/mongo_express_proxy_api.py +181 -0
  423. cancan_microstack/services/opsbffsrv/interface/api/pgweb_proxy_api.py +154 -0
  424. cancan_microstack/services/opsbffsrv/interface/api/rabbitmq_mgmt_proxy_api.py +518 -0
  425. cancan_microstack/services/opsbffsrv/interface/api/redis_commander_proxy_api.py +133 -0
  426. cancan_microstack/services/opsbffsrv/interface/api/service_config.py +146 -0
  427. cancan_microstack/services/opsbffsrv/interface/api/service_logs_api.py +81 -0
  428. cancan_microstack/services/opsbffsrv/interface/api/service_registry.py +66 -0
  429. cancan_microstack/services/opsbffsrv/interface/api/workflow_ops_api.py +413 -0
  430. cancan_microstack/services/opsbffsrv/interface/middleware/__init__.py +0 -0
  431. cancan_microstack/services/opsbffsrv/interface/middleware/auth_middleware.py +52 -0
  432. cancan_microstack/services/opsbffsrv/router.py +901 -0
  433. cancan_microstack/utils/__init__.py +1 -0
  434. cancan_microstack/utils/container_env.py +218 -0
  435. cancan_microstack-0.0.1.dist-info/METADATA +155 -0
  436. cancan_microstack-0.0.1.dist-info/RECORD +440 -0
  437. cancan_microstack-0.0.1.dist-info/WHEEL +5 -0
  438. cancan_microstack-0.0.1.dist-info/entry_points.txt +2 -0
  439. cancan_microstack-0.0.1.dist-info/licenses/LICENSE +21 -0
  440. cancan_microstack-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,159 @@
1
+ """
2
+ 工作流执行引擎领域服务
3
+ Workflow Execution Engine Domain Service
4
+ """
5
+ from typing import (
6
+ Any,
7
+ List,
8
+ Tuple,
9
+ )
10
+
11
+ from cancan_microstack.public.schemas.infra.enums import (
12
+ NodeStatus,
13
+ NodeType,
14
+ )
15
+ from cancan_microstack.public.schemas.infra.workflow import (
16
+ NodeConfig,
17
+ WorkflowExecutionContext,
18
+ WorkflowDefinition,
19
+ LogicNodeConfig,
20
+ ForkNodeConfig,
21
+ LoopNodeConfig,
22
+ )
23
+ from .node_handlers import (
24
+ StartNodeHandler,
25
+ ActionNodeHandler,
26
+ TransformNodeHandler,
27
+ LogicNodeHandler,
28
+ ForkNodeHandler,
29
+ JoinNodeHandler,
30
+ LoopNodeHandler,
31
+ EndNodeHandler,
32
+ )
33
+ from linglong_web.utils import logger
34
+
35
+
36
+ class WorkflowEngine:
37
+ """
38
+ 工作流引擎,负责编排和执行节点逻辑。
39
+ The workflow engine, responsible for orchestrating and executing node logic.
40
+ """
41
+
42
+ def __init__(self):
43
+ # 注册可用的节点处理器,便于根据类型动态分发
44
+ # Register available node handlers so we can dispatch by type at runtime
45
+ self._handlers = {
46
+ NodeType.START: StartNodeHandler,
47
+ NodeType.ACTION: ActionNodeHandler,
48
+ NodeType.TRANSFORM: TransformNodeHandler,
49
+ NodeType.LOGIC: LogicNodeHandler,
50
+ NodeType.FORK: ForkNodeHandler,
51
+ NodeType.JOIN: JoinNodeHandler,
52
+ NodeType.LOOP: LoopNodeHandler,
53
+ NodeType.END: EndNodeHandler,
54
+ }
55
+
56
+ async def process_node(
57
+ self,
58
+ workflow_def: WorkflowDefinition,
59
+ node_config: NodeConfig,
60
+ context: WorkflowExecutionContext,
61
+ ) -> Tuple[Any, NodeStatus, List[str], int]:
62
+ """
63
+ 处理单个节点并返回结果、状态、下一个节点ID和新的循环索引。
64
+ Process a single node and return the result, status, next node IDs, and new loop index.
65
+ """
66
+ # 1. 通过节点类型选择处理器 / Lookup handler class by node type
67
+ handler_class = self._handlers.get(node_config.type)
68
+ if not handler_class:
69
+ logger.warning(f"No handler found for node type: {node_config.type}")
70
+ return None, NodeStatus.FAILURE, [], context.loop_index
71
+
72
+ handler = handler_class(node_config)
73
+
74
+ # 2. 特定处理器需要额外依赖 / Some handlers (e.g., JOIN) require extra dependencies
75
+ if node_config.type == NodeType.JOIN:
76
+ output, status = await handler.process(context, workflow_def=workflow_def)
77
+ else:
78
+ output, status = await handler.process(context)
79
+
80
+ # 3. 根据节点输出决定后续节点与循环索引 / Compute downstream topology and loop index
81
+ next_node_ids, new_loop_index = self._determine_next_nodes(
82
+ node_config, output, context.loop_index
83
+ )
84
+
85
+ return output, status, next_node_ids, new_loop_index
86
+
87
+ def _determine_next_nodes(
88
+ self, node_config: NodeConfig, output: Any, current_loop_index: int
89
+ ) -> Tuple[List[str], int]:
90
+ """
91
+ 根据节点结果确定下一个节点和循环索引。
92
+ Determine next nodes and loop index based on node result.
93
+ """
94
+ next_ids = []
95
+ new_loop_index = current_loop_index
96
+
97
+ if node_config.type == NodeType.LOGIC:
98
+ config: LogicNodeConfig = node_config.config
99
+ condition_result = False
100
+ if isinstance(output, dict):
101
+ condition_result = bool(output.get("result"))
102
+ else:
103
+ condition_result = bool(output)
104
+
105
+ target = config.true_next_node_id if condition_result else config.false_next_node_id
106
+ if target:
107
+ next_ids.append(target)
108
+ elif node_config.type == NodeType.FORK:
109
+ # 并行分支节点:返回所有分支节点 ID
110
+ # Fork node: return all branch node IDs for parallel execution
111
+ config: ForkNodeConfig = node_config.config
112
+ if config.branch_node_ids:
113
+ next_ids.extend(config.branch_node_ids)
114
+ elif node_config.type == NodeType.LOOP:
115
+ config: LoopNodeConfig = node_config.config
116
+ body_entry_id = config.body_entry_id or config.jump_target_id
117
+ exit_candidates = []
118
+ if config.exit_node_id:
119
+ exit_candidates.append(config.exit_node_id)
120
+ elif node_config.next_node_ids:
121
+ exit_candidates.extend(node_config.next_node_ids)
122
+
123
+ should_exit = False
124
+ if isinstance(output, dict):
125
+ should_exit = bool(output.get("exit_condition_met"))
126
+ else:
127
+ should_exit = bool(output)
128
+
129
+ if not should_exit:
130
+ if body_entry_id:
131
+ next_ids.append(body_entry_id)
132
+ else:
133
+ # 缺少循环体配置时记录警告并走退出路径
134
+ # Warn and fallback to exit path when loop body is missing
135
+ logger.warning(
136
+ "Loop node %s lacks body_entry_id, falling back to exit path",
137
+ node_config.id,
138
+ )
139
+ next_ids.extend(exit_candidates)
140
+ else:
141
+ next_ids.extend(exit_candidates)
142
+ else:
143
+ if node_config.next_node_ids:
144
+ next_ids.extend(node_config.next_node_ids)
145
+
146
+ # 去重以避免重复派发,但保持结果顺序稳定
147
+ # Deduplicate next nodes to avoid double dispatch while keeping ordering stable
148
+ seen = set()
149
+ ordered_next_ids = []
150
+ for nid in next_ids:
151
+ if nid and nid not in seen:
152
+ seen.add(nid)
153
+ ordered_next_ids.append(nid)
154
+
155
+ return ordered_next_ids, new_loop_index
156
+
157
+
158
+ # Singleton instance
159
+ workflow_engine = WorkflowEngine()
@@ -0,0 +1,509 @@
1
+ """
2
+ 工作流节点处理器
3
+ Workflow Node Handlers
4
+
5
+ 此文件包含处理不同类型工作流节点的领域逻辑。
6
+ This file contains the domain logic for processing different types of workflow nodes.
7
+ """
8
+ from typing import (
9
+ Dict,
10
+ Any,
11
+ Optional,
12
+ Tuple,
13
+ Type,
14
+ )
15
+
16
+ import aiohttp
17
+ import jinja2
18
+ import jmespath
19
+ from pydantic import BaseModel
20
+
21
+ from cancan_microstack.public.schemas.infra.workflow import (
22
+ NodeConfig,
23
+ ActionNodeConfig,
24
+ RequestConfig,
25
+ LogicNodeConfig,
26
+ TransformNodeConfig,
27
+ ForkNodeConfig,
28
+ JoinNodeConfig,
29
+ LoopNodeConfig,
30
+ EndNodeConfig,
31
+ WorkflowExecutionContext,
32
+ WorkflowDefinition,
33
+ HttpResponseOutput,
34
+ )
35
+ from cancan_microstack.public.schemas.infra.enums import (
36
+ NodeStatus,
37
+ JoinMode,
38
+ EndStatus,
39
+ TransformEngine,
40
+ ConditionTruth,
41
+ )
42
+ from linglong_web import http_client
43
+ from linglong_web.utils import logger
44
+
45
+
46
+ def _evaluate_truthy_flag(raw_value: str) -> bool:
47
+ """将条件表达式渲染后的字符串转换为布尔值 / Convert rendered condition string into boolean."""
48
+
49
+ normalized = (raw_value or "").strip().lower()
50
+ try:
51
+ truth = ConditionTruth(normalized)
52
+ except ValueError:
53
+ truth = ConditionTruth.FALSE if normalized in {"", "0"} else ConditionTruth.TRUE
54
+ return truth is ConditionTruth.TRUE
55
+
56
+
57
+ class NodeHandler:
58
+ """所有节点的基类处理器 / TableBase handler for all nodes."""
59
+
60
+ def __init__(self, node_config: NodeConfig):
61
+ self.node_config = node_config
62
+
63
+ def _ensure_config_model(self, model_cls: Type[BaseModel]) -> Any:
64
+ """
65
+ 确保节点配置被解析为期望的 Pydantic 模型
66
+ Ensure the node config is parsed into the expected Pydantic model
67
+ """
68
+
69
+ config = self.node_config.config
70
+ if config is None or isinstance(config, model_cls):
71
+ return config
72
+ if isinstance(config, dict):
73
+ try:
74
+ typed_config = model_cls.model_validate(config)
75
+ self.node_config.config = typed_config
76
+ return typed_config
77
+ except Exception as exc: # noqa: BLE001
78
+ logger.warning(
79
+ "Failed to coerce config for node %s into %s: %s",
80
+ self.node_config.id,
81
+ model_cls.__name__,
82
+ exc,
83
+ )
84
+ return config
85
+
86
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Any, NodeStatus]:
87
+ """
88
+ 处理节点的核心方法。
89
+ Core method for processing the node.
90
+ """
91
+ raise NotImplementedError
92
+
93
+ @staticmethod
94
+ def _build_template_context(context: WorkflowExecutionContext) -> Dict[str, Any]:
95
+ """构建模板上下文,支持工作流官方模板写法。
96
+ Build template context for official workflow template syntax.
97
+
98
+ 约定(官方)/ Official conventions:
99
+ - `context.xxx` 访问执行上下文顶层字段
100
+ - `context.global_context.xxx` 访问业务上下文
101
+ - `context.__runtime__.xxx` 访问运行时命名空间
102
+ """
103
+
104
+ payload = context.model_dump()
105
+ global_context = payload.get("global_context")
106
+ runtime_context: Dict[str, Any] = {}
107
+ if isinstance(global_context, dict):
108
+ runtime_context = dict(global_context.get("__runtime__") or {})
109
+ global_context.setdefault("__runtime__", runtime_context)
110
+ for key, value in global_context.items():
111
+ if key not in payload:
112
+ payload[key] = value
113
+ payload["runtime"] = runtime_context
114
+ payload["context"] = payload
115
+ return payload
116
+
117
+ @staticmethod
118
+ def _prepare_template_expression(template_expr: str) -> str:
119
+ """将官方 runtime 点语法转换为 Jinja 可解析形式。
120
+ Convert official runtime dot syntax into Jinja-parseable form.
121
+
122
+ 官方写法 / Official syntax:
123
+ - `context.__runtime__.last_status`
124
+
125
+ Jinja 对双下划线键的点访问会触发属性解析限制,
126
+ 因此仅对 `context.__runtime__` 做确定性转换。
127
+ """
128
+
129
+ normalized = template_expr
130
+ normalized = normalized.replace("context.__runtime__", "context['__runtime__']")
131
+ return normalized
132
+
133
+
134
+ class StartNodeHandler(NodeHandler):
135
+ """处理开始节点 / Handles Start Nodes."""
136
+
137
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
138
+ return {
139
+ "run_id": str(context.run_id),
140
+ "loop_index": context.loop_index,
141
+ "message": "START node accepted trigger context",
142
+ }, NodeStatus.SUCCESS
143
+
144
+
145
+ class ActionNodeHandler(NodeHandler):
146
+ """处理动作节点,负责执行 HTTP 请求 / Handles Action Nodes for HTTP requests."""
147
+
148
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[HttpResponseOutput, NodeStatus]:
149
+ config = self._ensure_config_model(ActionNodeConfig)
150
+ if not isinstance(config, ActionNodeConfig):
151
+ raise ValueError("Invalid action node config")
152
+ req_conf = config.request
153
+ env = jinja2.Environment()
154
+
155
+ input_dict = self._build_template_context(context)
156
+ rendered_url = env.from_string(req_conf.url).render(**input_dict)
157
+ rendered_headers = {k: env.from_string(v).render(**input_dict) for k, v in (req_conf.headers or {}).items()}
158
+ rendered_params = {k: env.from_string(v).render(**input_dict) for k, v in (req_conf.params or {}).items()}
159
+
160
+ json_payload, data_payload = self._resolve_request_body(req_conf, rendered_headers, env, input_dict)
161
+
162
+ if config.async_mode and context.callback_url:
163
+ rendered_headers["X-Callback-Url"] = context.callback_url
164
+
165
+ resp = await http_client.fetch(
166
+ method=req_conf.method,
167
+ url=rendered_url,
168
+ params=rendered_params,
169
+ headers=rendered_headers,
170
+ json=json_payload,
171
+ data=data_payload,
172
+ timeout=req_conf.timeout_seconds,
173
+ passthrough_errors=True
174
+ )
175
+
176
+ text_content = await resp.text()
177
+ json_content = None
178
+ if "application/json" in resp.headers.get("Content-Type", ""):
179
+ try:
180
+ json_content = await resp.json(content_type=None)
181
+ except Exception:
182
+ logger.warning(f"Failed to decode JSON from response: {rendered_url}")
183
+
184
+ output = HttpResponseOutput(
185
+ status_code=resp.status,
186
+ headers=dict(resp.headers),
187
+ text=text_content,
188
+ json_body=json_content,
189
+ ok=resp.ok,
190
+ )
191
+ status = NodeStatus.SUSPENDED if config.async_mode else NodeStatus.SUCCESS
192
+ return output, status
193
+
194
+ def _resolve_request_body(
195
+ self,
196
+ req_conf: RequestConfig,
197
+ headers: Dict[str, str],
198
+ env: jinja2.Environment,
199
+ context: Dict[str, Any],
200
+ ) -> Tuple[Optional[Any], Optional[Any]]:
201
+ """解析请求体并自动适配 json/data 及 Content-Type。"""
202
+
203
+ rendered_legacy_body = self._render_recursive(req_conf.body, env, context)
204
+ rendered_json_body = self._render_recursive(req_conf.json_body, env, context)
205
+ rendered_form_body = self._render_recursive(req_conf.form_body, env, context)
206
+ rendered_raw_body = self._render_recursive(req_conf.raw_body, env, context)
207
+
208
+ content_type = ""
209
+ for key, value in headers.items():
210
+ if key.lower() == "content-type":
211
+ content_type = str(value).lower()
212
+ break
213
+
214
+ body_type = req_conf.body_type
215
+ if body_type == "auto":
216
+ if rendered_json_body is not None:
217
+ body_type = "json"
218
+ elif rendered_form_body is not None:
219
+ if "multipart/form-data" in content_type:
220
+ body_type = "multipart"
221
+ else:
222
+ body_type = "form-urlencoded"
223
+ elif rendered_raw_body is not None:
224
+ body_type = "raw"
225
+ else:
226
+ if "multipart/form-data" in content_type:
227
+ body_type = "multipart"
228
+ elif "application/x-www-form-urlencoded" in content_type:
229
+ body_type = "form-urlencoded"
230
+ elif isinstance(rendered_legacy_body, (dict, list)):
231
+ body_type = "json"
232
+ else:
233
+ body_type = "raw"
234
+
235
+ if body_type == "json":
236
+ json_payload = rendered_json_body if rendered_json_body is not None else rendered_legacy_body
237
+ if json_payload is not None and not content_type:
238
+ headers["Content-Type"] = "application/json"
239
+ return json_payload, None
240
+
241
+ if body_type == "form-urlencoded":
242
+ form_payload = rendered_form_body if rendered_form_body is not None else rendered_legacy_body
243
+ if form_payload is not None and not content_type:
244
+ headers["Content-Type"] = "application/x-www-form-urlencoded"
245
+ return None, form_payload
246
+
247
+ if body_type == "multipart":
248
+ multipart_payload = rendered_form_body if rendered_form_body is not None else rendered_legacy_body
249
+ normalized = self._to_form_data(multipart_payload)
250
+
251
+ content_type_keys = [key for key in headers if key.lower() == "content-type"]
252
+ for key in content_type_keys:
253
+ headers.pop(key, None)
254
+ return None, normalized
255
+
256
+ raw_payload = rendered_raw_body if rendered_raw_body is not None else rendered_legacy_body
257
+ return None, raw_payload
258
+
259
+ @staticmethod
260
+ def _to_form_data(payload: Any) -> aiohttp.FormData:
261
+ """将 payload 规范化为 multipart FormData。"""
262
+
263
+ form = aiohttp.FormData()
264
+ if isinstance(payload, dict):
265
+ for key, value in payload.items():
266
+ form.add_field(str(key), "" if value is None else str(value))
267
+ return form
268
+
269
+ if isinstance(payload, list):
270
+ for item in payload:
271
+ if isinstance(item, (tuple, list)) and len(item) == 2:
272
+ key, value = item
273
+ form.add_field(str(key), "" if value is None else str(value))
274
+ return form
275
+
276
+ if payload is not None:
277
+ form.add_field("payload", str(payload))
278
+ return form
279
+
280
+ def _render_recursive(self, data: Any, env: jinja2.Environment, context: Dict[str, Any]) -> Any:
281
+ if isinstance(data, str):
282
+ normalized = self._prepare_template_expression(data)
283
+ return env.from_string(normalized).render(**context)
284
+ elif isinstance(data, dict):
285
+ return {k: self._render_recursive(v, env, context) for k, v in data.items()}
286
+ elif isinstance(data, list):
287
+ return [self._render_recursive(v, env, context) for v in data]
288
+ return data
289
+
290
+
291
+ class LogicNodeHandler(NodeHandler):
292
+ """处理逻辑节点,用于评估条件表达式 / Handles Logic Nodes for conditions."""
293
+
294
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
295
+ config = self._ensure_config_model(LogicNodeConfig)
296
+ if not isinstance(config, LogicNodeConfig):
297
+ raise ValueError("Invalid logic node config")
298
+ env = jinja2.Environment()
299
+ expr = config.condition
300
+ if not expr.startswith("{{"):
301
+ expr = f"{{{{ {expr} }}}}"
302
+ expr = self._prepare_template_expression(expr)
303
+
304
+ rendered = env.from_string(expr).render(**self._build_template_context(context))
305
+ result = _evaluate_truthy_flag(rendered)
306
+ return {
307
+ "condition": config.condition,
308
+ "rendered": rendered,
309
+ "result": result,
310
+ "selected_branch": "TRUE" if result else "FALSE",
311
+ "true_next_node_id": config.true_next_node_id,
312
+ "false_next_node_id": config.false_next_node_id,
313
+ }, NodeStatus.SUCCESS
314
+
315
+
316
+ class TransformNodeHandler(NodeHandler):
317
+ """处理转换节点,用于重塑上下文数据 / Handles Transform Nodes for reshaping data."""
318
+
319
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
320
+ config = self._ensure_config_model(TransformNodeConfig)
321
+ if not isinstance(config, TransformNodeConfig):
322
+ raise ValueError("Invalid transform node config")
323
+ result = {}
324
+ input_dict = self._build_template_context(context)
325
+
326
+ for tgt_key, expr in config.output_schema.items():
327
+ try:
328
+ if config.engine == TransformEngine.JMESPATH:
329
+ val = jmespath.search(expr, input_dict)
330
+ result[tgt_key] = val
331
+ else:
332
+ template = jinja2.Template(self._prepare_template_expression(expr))
333
+ val = template.render(**input_dict)
334
+ result[tgt_key] = val
335
+ except Exception as e:
336
+ logger.error(f"Failed to transform key '{tgt_key}' with expression '{expr}': {e}")
337
+ result[tgt_key] = None
338
+ return result, NodeStatus.SUCCESS
339
+
340
+
341
+ class ForkNodeHandler(NodeHandler):
342
+ """处理并行分支节点 / Handles Fork Nodes for parallel execution."""
343
+
344
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
345
+ config = self._ensure_config_model(ForkNodeConfig)
346
+ if not isinstance(config, ForkNodeConfig):
347
+ raise ValueError("Invalid fork node config")
348
+ """
349
+ 处理并行分支节点,直接返回成功状态。
350
+ Process fork node and return success status directly.
351
+
352
+ 并行逻辑由 WorkflowEngine._determine_next_nodes 实现,
353
+ 该方法会将 config.branch_node_ids 返回为 next_node_ids,
354
+ Celery 任务编排层会为每个分支创建独立的执行任务。
355
+
356
+ The parallel logic is implemented by WorkflowEngine._determine_next_nodes,
357
+ which returns config.branch_node_ids as next_node_ids.
358
+ The Celery orchestration layer will create independent execution tasks for each branch.
359
+ """
360
+ # Fork 节点本身不执行任何操作,只是语义标记
361
+ # Fork node itself performs no operation, it's just a semantic marker
362
+ return {
363
+ "branch_node_ids": config.branch_node_ids,
364
+ "branch_count": len(config.branch_node_ids or []),
365
+ }, NodeStatus.SUCCESS
366
+
367
+
368
+ class JoinNodeHandler(NodeHandler):
369
+ """处理汇合节点 / Handles Join Nodes."""
370
+
371
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
372
+ workflow_def: WorkflowDefinition = kwargs.get("workflow_def")
373
+ if not workflow_def:
374
+ raise ValueError("workflow_def is required for JoinNodeHandler")
375
+
376
+ join_config = self._ensure_config_model(JoinNodeConfig)
377
+ if not isinstance(join_config, JoinNodeConfig):
378
+ raise ValueError("Invalid join node config")
379
+
380
+ upstream_node_ids = [
381
+ nid for nid, n_cfg_dict in workflow_def.nodes_config.items()
382
+ if self.node_config.id in (n_cfg_dict.get('next_node_ids') or [])
383
+ ]
384
+
385
+ if not upstream_node_ids:
386
+ return {
387
+ "mode": join_config.mode.value,
388
+ "upstream_node_ids": [],
389
+ "upstream_statuses": {},
390
+ "is_ready": True,
391
+ "reason": "No upstream nodes found",
392
+ }, NodeStatus.SUCCESS
393
+
394
+ upstream_status_map = {
395
+ uid: (context.nodes.get(uid).status.value if context.nodes.get(uid) else "MISSING")
396
+ for uid in upstream_node_ids
397
+ }
398
+ upstream_statuses = [context.nodes.get(uid).status for uid in upstream_node_ids if context.nodes.get(uid)]
399
+
400
+ if join_config.mode == JoinMode.ALL:
401
+ is_ready = all(s in [NodeStatus.SUCCESS, NodeStatus.SKIPPED] for s in upstream_statuses)
402
+ else:
403
+ is_ready = any(s == NodeStatus.SUCCESS for s in upstream_statuses)
404
+
405
+ return {
406
+ "mode": join_config.mode.value,
407
+ "upstream_node_ids": upstream_node_ids,
408
+ "upstream_statuses": upstream_status_map,
409
+ "is_ready": is_ready,
410
+ }, NodeStatus.SUCCESS if is_ready else NodeStatus.PENDING
411
+
412
+
413
+ class LoopNodeHandler(NodeHandler):
414
+ """处理循环节点 / Handles Loop Nodes."""
415
+
416
+ def _evaluate_exit_condition(
417
+ self,
418
+ config: LoopNodeConfig,
419
+ context: WorkflowExecutionContext,
420
+ loop_index_override: Optional[int] = None,
421
+ ) -> Tuple[str, bool]:
422
+ """渲染并评估循环退出条件。
423
+ Render and evaluate loop exit condition.
424
+
425
+ Args:
426
+ config: 循环配置 / Loop config.
427
+ context: 当前执行上下文 / Current execution context.
428
+ loop_index_override: 可选覆盖 loop_index(用于边界轮次对比)/
429
+ Optional override of loop_index (used for boundary comparison).
430
+ """
431
+
432
+ env = jinja2.Environment()
433
+ expr = config.condition
434
+ if not expr.startswith("{{"):
435
+ expr = f"{{{{ {expr} }}}}"
436
+ expr = self._prepare_template_expression(expr)
437
+
438
+ template_context = self._build_template_context(context)
439
+ if loop_index_override is not None:
440
+ template_context["loop_index"] = loop_index_override
441
+ nested_context = template_context.get("context")
442
+ if isinstance(nested_context, dict):
443
+ nested_context["loop_index"] = loop_index_override
444
+
445
+ rendered = env.from_string(expr).render(**template_context)
446
+ return rendered, _evaluate_truthy_flag(rendered)
447
+
448
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
449
+ config = self._ensure_config_model(LoopNodeConfig)
450
+ if not isinstance(config, LoopNodeConfig):
451
+ raise ValueError("Invalid loop node config")
452
+
453
+ if context.loop_index > config.max_iterations:
454
+ return {
455
+ "condition": config.condition,
456
+ "rendered": None,
457
+ "exit_condition_met": True,
458
+ "decision": "EXIT",
459
+ "forced_exit_by_max_iterations": True,
460
+ "max_iterations": config.max_iterations,
461
+ "current_loop_index": context.loop_index,
462
+ }, NodeStatus.SUCCESS
463
+
464
+ rendered, exit_condition_met = self._evaluate_exit_condition(config, context)
465
+
466
+ # 边界轮次防误判:当条件仅在 loop_index == max_iterations 才变为 true 时,
467
+ # 应允许本轮循环体执行,避免出现“max=3 但 body 仅 2 次”的 off-by-one。
468
+ # Boundary guard: when condition flips true only at loop_index == max_iterations,
469
+ # allow current body execution to avoid off-by-one (max=3 but body runs only twice).
470
+ deferred_by_boundary = False
471
+ boundary_probe_rendered = None
472
+ if exit_condition_met and context.loop_index == config.max_iterations:
473
+ boundary_probe_rendered, previous_iteration_exit = self._evaluate_exit_condition(
474
+ config,
475
+ context,
476
+ loop_index_override=max(context.loop_index - 1, 0),
477
+ )
478
+ if not previous_iteration_exit:
479
+ deferred_by_boundary = True
480
+ exit_condition_met = False
481
+
482
+ return {
483
+ "condition": config.condition,
484
+ "rendered": rendered,
485
+ "boundary_probe_rendered": boundary_probe_rendered,
486
+ "exit_condition_met": exit_condition_met,
487
+ "decision": "EXIT" if exit_condition_met else "CONTINUE",
488
+ "deferred_exit_by_max_boundary": deferred_by_boundary,
489
+ "forced_exit_by_max_iterations": False,
490
+ "max_iterations": config.max_iterations,
491
+ "current_loop_index": context.loop_index,
492
+ }, NodeStatus.SUCCESS
493
+
494
+
495
+ class EndNodeHandler(NodeHandler):
496
+ """处理结束节点 / Handles End Nodes."""
497
+
498
+ async def process(self, context: WorkflowExecutionContext, **kwargs) -> Tuple[Dict[str, Any], NodeStatus]:
499
+ config = self._ensure_config_model(EndNodeConfig)
500
+ if config is None:
501
+ config = EndNodeConfig()
502
+ self.node_config.config = config
503
+ if not isinstance(config, EndNodeConfig):
504
+ raise ValueError("Invalid end node config")
505
+ status = NodeStatus.SUCCESS if config.status == EndStatus.SUCCESS else NodeStatus.FAILURE
506
+ return {
507
+ "end_status": config.status.value,
508
+ "loop_index": context.loop_index,
509
+ }, status