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,385 @@
1
+ """项目 AppServer,基于 Linglong 扩展服务注册、远程配置与内部管理路由。"""
2
+ import asyncio
3
+ import os
4
+ from typing import (
5
+ Any,
6
+ Awaitable,
7
+ Callable,
8
+ Dict,
9
+ Optional,
10
+ Sequence,
11
+ Tuple,
12
+ )
13
+
14
+ from fastapi import (
15
+ APIRouter,
16
+ Request,
17
+ )
18
+ from fastapi.responses import ORJSONResponse
19
+
20
+ from linglong_web import (
21
+ LinglongConfig,
22
+ LinglongConfigBase,
23
+ )
24
+ from linglong_web import (
25
+ HTTPClientConfig,
26
+ http_client,
27
+ )
28
+ from linglong_web import LinglongAppServer
29
+ from linglong_web import BaseServerExtension
30
+ from linglong_web import LinglongConst
31
+ from linglong_web.utils import logger
32
+
33
+ from cancan_microstack.public.const.app_consts import WebServerConst
34
+ from cancan_microstack.public.schemas.service_registry import (
35
+ InstanceMetadata,
36
+ ServiceMetadata,
37
+ ServiceRegistryPayload,
38
+ )
39
+ from cancan_microstack.public.schemas.infra.status_types import InstanceStatus
40
+ from cancan_microstack.public.web.config_value import ConfigValueResolver
41
+
42
+
43
+ class AppServer(LinglongAppServer):
44
+ """项目 AppServer,实现服务注册、配置下发等行为 / Project specific AppServer."""
45
+
46
+ def __init__(self) -> None:
47
+ super().__init__()
48
+ self._registry_keepalive_task: Optional[asyncio.Task] = None
49
+ self._config_value_resolver = ConfigValueResolver()
50
+
51
+ async def initialize(
52
+ self,
53
+ service_name: str,
54
+ router: APIRouter,
55
+ config_dict: Dict[str, type[LinglongConfigBase]],
56
+ scheduler_group=None,
57
+ middleware: Optional[list[Callable[[Request, Callable[[Request], Awaitable[Any]]], Awaitable[Any]]]] = None,
58
+ on_startup: Optional[Sequence[Callable[[], Any]]] = None,
59
+ on_shutdown: Optional[Sequence[Callable[[], Any]]] = None,
60
+ extensions: Sequence[BaseServerExtension] | None = None,
61
+ ) -> "AppServer":
62
+ await super().initialize(
63
+ service_name=service_name,
64
+ router=router,
65
+ config_dict=config_dict,
66
+ scheduler_group=scheduler_group,
67
+ middleware=middleware,
68
+ on_startup=on_startup,
69
+ on_shutdown=on_shutdown,
70
+ extensions=extensions,
71
+ )
72
+
73
+ self._initialize_config_value_resolver()
74
+
75
+ # controllersrv 等“宿主机控制服务”不参与服务注册/远程配置。
76
+ # Host controller services (e.g. controllersrv) do not participate in registry/remote config.
77
+ if not self._should_skip_registry():
78
+ self.register_shutdown_callback(self._deregister_from_infrasrv)
79
+ return self
80
+
81
+ def _initialize_config_value_resolver(self) -> None:
82
+ """初始化配置解析器并展开 ConfigValue 默认值 / Initialize resolver and materialize ConfigValue defaults."""
83
+ snapshot = LinglongConfig.snapshot()
84
+ self._config_value_resolver = ConfigValueResolver.from_snapshot(snapshot)
85
+ defaults = self._config_value_resolver.materialize_defaults()
86
+ if defaults:
87
+ LinglongConfig.apply_updates(defaults)
88
+ logger.info("ConfigValue wrappers initialized: %s", list(sorted(defaults.keys())))
89
+
90
+ async def on_startup(self): # noqa: D401 - lifecycle override
91
+ await super().on_startup()
92
+ if not self._should_skip_registry():
93
+ self._registry_keepalive_task = asyncio.create_task(
94
+ self._registry_keepalive_loop(),
95
+ name=f"{self.service_name}-registry-keepalive",
96
+ )
97
+
98
+ async def on_shutdown(self) -> None: # noqa: D401 - lifecycle override
99
+ if self._registry_keepalive_task:
100
+ self._registry_keepalive_task.cancel()
101
+ try:
102
+ await self._registry_keepalive_task
103
+ except asyncio.CancelledError:
104
+ pass
105
+ self._registry_keepalive_task = None
106
+ await super().on_shutdown()
107
+
108
+ async def _before_resources_initialized(self) -> None:
109
+ await self._fetch_service_config_from_remote(with_retry=True)
110
+
111
+ def _add_internal_routes(self) -> None:
112
+ if not self.app:
113
+ return
114
+ self.app.add_api_route("/internal/health", self._internal_health_check_handler, methods=["GET"])
115
+ self.app.add_api_route("/internal/config/update", self._internal_config_update_handler, methods=["POST"])
116
+
117
+ async def _internal_health_check_handler(self, request: Request): # noqa: D401 - FastAPI handler
118
+ return ORJSONResponse({
119
+ "status": InstanceStatus.UP,
120
+ "service": self.service_name,
121
+ "instance_id": self.instance_id,
122
+ })
123
+
124
+ async def _internal_config_update_handler(self, request: Request):
125
+ try:
126
+ body = await request.json()
127
+ except Exception as exc: # pragma: no cover - FastAPI already validates JSON
128
+ logger.error("Failed to parse config update payload: %s", exc, exc_info=True)
129
+ return ORJSONResponse({"status": "error", "message": str(exc)}, status_code=400)
130
+
131
+ config_payload = body.get("config") if isinstance(body, dict) else None
132
+ if not config_payload:
133
+ return ORJSONResponse({"status": "error", "message": "No config provided"}, status_code=400)
134
+
135
+ self._update_config(config_payload)
136
+ return ORJSONResponse({"status": "success", "message": "Config updated"})
137
+
138
+ async def _fetch_service_config_from_remote(self, with_retry: bool = False) -> None:
139
+ if self._should_skip_registry():
140
+ logger.info("%s is a special service, skip remote config fetch", self.service_name)
141
+ return
142
+
143
+ url = f"{LinglongConfig.INFRASRV_HOST}/v1/infrasrv/service_config"
144
+ params = {"service_name": self.service_name}
145
+ retries = 3 if with_retry else 0
146
+ for attempt in range(retries + 1):
147
+ try:
148
+ resp = await http_client.get(url, params=params, timeout=HTTPClientConfig.INTERNAL_SERVICE_TIMEOUT)
149
+ resp.raise_for_status()
150
+ data = await resp.json()
151
+ remote_config = data.get("data", {}) if isinstance(data, dict) else {}
152
+ if remote_config:
153
+ logger.info("Fetched remote config keys: %s", list(remote_config.keys()))
154
+ self._update_config(remote_config)
155
+ return
156
+ except Exception as exc:
157
+ is_dev = LinglongConfig.DEBUG
158
+ level = logger.warning if attempt < retries else logger.error
159
+ level(
160
+ "Fetch remote config failed (attempt %s/%s): %s",
161
+ attempt + 1,
162
+ retries + 1,
163
+ exc,
164
+ )
165
+ if attempt < retries:
166
+ await asyncio.sleep(3)
167
+ elif is_dev:
168
+ logger.info("Development mode: remote config fetch failure can be ignored")
169
+
170
+ def _update_config(self, remote_config: Dict[str, Any]) -> None:
171
+ if not remote_config:
172
+ return
173
+
174
+ processed: Dict[str, Any] = {}
175
+ for key, value in remote_config.items():
176
+ config_key = key.upper()
177
+ if not hasattr(LinglongConfig, config_key):
178
+ logger.warning("Unknown remote config key '%s', skipping", config_key)
179
+ continue
180
+
181
+ local_value = getattr(LinglongConfig, config_key)
182
+ try:
183
+ new_value = self._config_value_resolver.resolve_update_value(
184
+ config_key=config_key,
185
+ current_value=local_value,
186
+ raw_value=value,
187
+ )
188
+ processed[config_key] = new_value
189
+ except Exception as exc: # noqa: BLE001
190
+ logger.warning(
191
+ "Failed to apply remote config for '%s': %s",
192
+ config_key,
193
+ exc,
194
+ )
195
+
196
+ if processed:
197
+ LinglongConfig.apply_updates(processed)
198
+ logger.info("Applied remote config: %s", list(processed.keys()))
199
+
200
+ async def _register_to_infrasrv(self) -> None:
201
+ register_host, container_hostname, network_alias, host_source = self._resolve_registration_host()
202
+ instance_metadata = InstanceMetadata(
203
+ container_hostname=container_hostname,
204
+ network_alias=network_alias,
205
+ register_host_source=host_source,
206
+ app_host_binding=self.host,
207
+ )
208
+ service_metadata = ServiceMetadata(
209
+ version="1.0.0",
210
+ environment=(
211
+ LinglongConst.ENVIRONMENT.DEVELOPMENT if LinglongConfig.DEBUG else LinglongConst.ENVIRONMENT.PRODUCTION),
212
+ )
213
+ payload = ServiceRegistryPayload(
214
+ service_name=self.service_name or "",
215
+ instance_id=self.instance_id or "",
216
+ host=register_host,
217
+ port=self.port or 0,
218
+ internal_port=self.port or 0,
219
+ health_check_url="/internal/health",
220
+ container_name=container_hostname or network_alias,
221
+ compose_service_name=network_alias,
222
+ service_metadata=service_metadata,
223
+ instance_metadata=instance_metadata,
224
+ )
225
+ payload_dict = payload.model_dump()
226
+ payload_dict["instance_metadata"] = instance_metadata.model_dump(exclude_none=True)
227
+
228
+ url = f"{LinglongConfig.INFRASRV_HOST}/v1/infrasrv/registry/register"
229
+ for attempt in range(4):
230
+ try:
231
+ resp = await http_client.post(
232
+ url,
233
+ json=payload_dict,
234
+ timeout=HTTPClientConfig.INTERNAL_SERVICE_TIMEOUT,
235
+ )
236
+ if resp and resp.status == 200:
237
+ logger.info("Service registered: %s (%s)", self.service_name, self.instance_id)
238
+ return
239
+ logger.warning("Register service failed, status=%s", resp.status if resp else "N/A")
240
+ except Exception as exc:
241
+ logger.warning("register attempt %s failed: %s", attempt + 1, exc)
242
+ await asyncio.sleep(3)
243
+ logger.error("All attempts to register %s failed", self.service_name)
244
+
245
+ async def _register_keepalive_once(self) -> bool:
246
+ """执行一次保活注册 / Execute one keepalive registration tick."""
247
+ register_host, container_hostname, network_alias, host_source = self._resolve_registration_host()
248
+ instance_metadata = InstanceMetadata(
249
+ container_hostname=container_hostname,
250
+ network_alias=network_alias,
251
+ register_host_source=host_source,
252
+ app_host_binding=self.host,
253
+ )
254
+ service_metadata = ServiceMetadata(
255
+ version="1.0.0",
256
+ environment=(
257
+ LinglongConst.ENVIRONMENT.DEVELOPMENT if LinglongConfig.DEBUG else LinglongConst.ENVIRONMENT.PRODUCTION),
258
+ )
259
+ payload = ServiceRegistryPayload(
260
+ service_name=self.service_name or "",
261
+ instance_id=self.instance_id or "",
262
+ host=register_host,
263
+ port=self.port or 0,
264
+ internal_port=self.port or 0,
265
+ health_check_url="/internal/health",
266
+ container_name=container_hostname or network_alias,
267
+ compose_service_name=network_alias,
268
+ service_metadata=service_metadata,
269
+ instance_metadata=instance_metadata,
270
+ )
271
+ payload_dict = payload.model_dump()
272
+ payload_dict["instance_metadata"] = instance_metadata.model_dump(exclude_none=True)
273
+
274
+ url = f"{LinglongConfig.INFRASRV_HOST}/v1/infrasrv/registry/register"
275
+ try:
276
+ resp = await http_client.post(
277
+ url,
278
+ json=payload_dict,
279
+ timeout=HTTPClientConfig.INTERNAL_SERVICE_TIMEOUT,
280
+ )
281
+ if resp and resp.status == 200:
282
+ return True
283
+ logger.warning(
284
+ "Registry keepalive failed, status=%s, service=%s, instance=%s",
285
+ resp.status if resp else "N/A",
286
+ self.service_name,
287
+ self.instance_id,
288
+ )
289
+ return False
290
+ except Exception as exc:
291
+ logger.warning(
292
+ "Registry keepalive request failed for %s (%s): %s",
293
+ self.service_name,
294
+ self.instance_id,
295
+ exc,
296
+ )
297
+ return False
298
+
299
+ async def _registry_keepalive_loop(self) -> None:
300
+ """持续执行服务注册保活 / Keep registry heartbeat alive via periodic upsert."""
301
+ interval_seconds = int(getattr(LinglongConfig, "SERVICE_REGISTRY_KEEPALIVE_SECONDS", 30) or 30)
302
+ interval_seconds = max(interval_seconds, 5)
303
+
304
+ # 启动即执行一次注册 / Register immediately on startup
305
+ first_ok = await self._register_keepalive_once()
306
+ if first_ok:
307
+ logger.info("Service registered: %s (%s)", self.service_name, self.instance_id)
308
+ else:
309
+ logger.warning("Initial registry keepalive failed: %s (%s)", self.service_name, self.instance_id)
310
+
311
+ while True:
312
+ try:
313
+ await asyncio.sleep(interval_seconds)
314
+ await self._register_keepalive_once()
315
+ except asyncio.CancelledError:
316
+ break
317
+ except Exception as exc:
318
+ logger.warning(
319
+ "Registry keepalive loop error for %s (%s): %s",
320
+ self.service_name,
321
+ self.instance_id,
322
+ exc,
323
+ )
324
+
325
+ async def _deregister_from_infrasrv(self) -> None:
326
+ if self._should_skip_registry():
327
+ return
328
+ try:
329
+ url = f"{LinglongConfig.INFRASRV_HOST}/v1/infrasrv/registry/deregister"
330
+ params = {"service_name": self.service_name, "instance_id": self.instance_id}
331
+ resp = await http_client.delete(url, params=params, timeout=HTTPClientConfig.INTERNAL_SERVICE_TIMEOUT)
332
+ if resp and resp.status == 200:
333
+ logger.info("Service deregistered: %s (%s)", self.service_name, self.instance_id)
334
+ else:
335
+ logger.warning("Service deregister failed, status=%s", resp.status if resp else "N/A")
336
+ except Exception as exc:
337
+ logger.error("Deregister error: %s", exc)
338
+
339
+ def _resolve_registration_host(self) -> Tuple[str, Optional[str], str, str]:
340
+ container_hostname = (os.environ.get("HOSTNAME") or "").strip() or None
341
+ network_alias = self._build_network_alias()
342
+ host_source = "container_hostname"
343
+ register_host = container_hostname or network_alias
344
+
345
+ override_host = (os.environ.get("SERVICE_REGISTRY_HOST") or "").strip()
346
+ if override_host:
347
+ register_host = override_host
348
+ host_source = "env:SERVICE_REGISTRY_HOST"
349
+ elif (not container_hostname or
350
+ self._looks_like_container_hostname(container_hostname) or
351
+ container_hostname.lower() in WebServerConst.UNSAFE_HOSTS):
352
+ register_host = network_alias
353
+ host_source = "network_alias"
354
+ return register_host, container_hostname, network_alias, host_source
355
+
356
+ def _build_network_alias(self) -> str:
357
+ explicit_alias = os.environ.get("SERVICE_NETWORK_ALIAS")
358
+ if explicit_alias and explicit_alias.strip():
359
+ return explicit_alias.strip()
360
+ suffix = os.environ.get("SERVICE_NETWORK_SUFFIX", ".service")
361
+ base_name = self.service_name or ""
362
+ if base_name.endswith(suffix):
363
+ return base_name
364
+ return f"{base_name}{suffix}" if base_name else suffix.lstrip('.')
365
+
366
+ @staticmethod
367
+ def _looks_like_container_hostname(hostname: Optional[str]) -> bool:
368
+ if not hostname:
369
+ return False
370
+ candidate = hostname.strip().lower()
371
+ return bool(WebServerConst.CONTAINER_ID_PATTERN.fullmatch(candidate))
372
+
373
+ def _should_skip_registry(self) -> bool:
374
+ # controllersrv 是宿主机控制服务:不注册、不拉取远端配置、不发心跳。
375
+ # controllersrv is a host controller service: skip registry/remote config/heartbeat.
376
+ if bool(getattr(LinglongConfig, "IS_CONTROLLER_SERVICE", False)):
377
+ logger.info("%s is a controller service, skip service registry", self.service_name)
378
+ return True
379
+
380
+ # 可通过配置显式禁用服务注册(例如某些 BFF/工具型服务)。
381
+ # Allow explicit opt-out via config for services that should not register.
382
+ if bool(getattr(LinglongConfig, "SKIP_SERVICE_REGISTRY", False)):
383
+ logger.info("%s is configured to skip service registry", self.service_name)
384
+ return True
385
+ return False
File without changes
File without changes
@@ -0,0 +1,228 @@
1
+ """Compose command detection & execution helpers.
2
+
3
+ 提供对 Docker/Podman Compose 的自动探测与执行。
4
+ Provide auto-detection and execution helpers for Docker/Podman compose commands.
5
+
6
+ Notes:
7
+ - 用户不需要手动运行 `docker compose` / `podman compose`;cancan CLI 会在内部调用。
8
+ Users don't need to run `docker compose` / `podman compose` manually; cancan CLI will call it internally.
9
+ - 本模块只依赖标准库,便于未来抽离成独立包。
10
+ This module uses stdlib only, so it can be extracted into a standalone package later.
11
+ """
12
+ import os
13
+ import shutil
14
+ import subprocess
15
+ from dataclasses import dataclass
16
+ from pathlib import Path
17
+ from typing import Callable
18
+ from typing import IO
19
+ from typing import Iterable
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class ComposeCommand:
24
+ """Compose 命令描述 / Compose command descriptor."""
25
+
26
+ argv_prefix: list[str]
27
+
28
+ def with_file(self, compose_file: Path) -> list[str]:
29
+ return [*self.argv_prefix, "-f", str(compose_file)]
30
+
31
+
32
+ def _can_run(
33
+ cmd: list[str],
34
+ runner: Callable[..., subprocess.CompletedProcess[str]],
35
+ *,
36
+ timeout_seconds: float = 5.0,
37
+ ) -> bool:
38
+ """轻量检测命令是否可运行 / Lightweight check whether a command can run."""
39
+
40
+ try:
41
+ result = runner(
42
+ cmd,
43
+ stdout=subprocess.DEVNULL,
44
+ stderr=subprocess.DEVNULL,
45
+ text=True,
46
+ check=False,
47
+ timeout=timeout_seconds,
48
+ )
49
+ except Exception:
50
+ return False
51
+
52
+ return result.returncode == 0
53
+
54
+
55
+ def _first_existing(which: Callable[[str], str | None], names: Iterable[str]) -> str | None:
56
+ for name in names:
57
+ if which(name):
58
+ return name
59
+ return None
60
+
61
+
62
+ def _daemon_ready_probe(prefix: list[str]) -> list[str] | None:
63
+ """Return a probe command that validates the engine daemon is reachable.
64
+
65
+ 返回用于验证引擎 daemon 可用的 probe 命令。
66
+
67
+ Notes:
68
+ - `docker compose version` 只验证 CLI 存在,不保证 Docker daemon 可用。
69
+ `docker compose version` only checks CLI presence, not Docker daemon readiness.
70
+ - `podman compose version` 同理。
71
+ Same for podman.
72
+ """
73
+
74
+ if prefix[:2] == ["docker", "compose"] or prefix == ["docker-compose"]:
75
+ return ["docker", "info"]
76
+ if prefix[:2] == ["podman", "compose"] or prefix == ["podman-compose"]:
77
+ return ["podman", "info"]
78
+ return None
79
+
80
+
81
+ def detect_compose_command(
82
+ *,
83
+ engine: str | None = None,
84
+ which: Callable[[str], str | None] = shutil.which,
85
+ runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
86
+ ) -> ComposeCommand:
87
+ """自动探测 Compose 命令(Docker 或 Podman)。
88
+
89
+ Auto-detect compose command (Docker or Podman).
90
+
91
+ Args:
92
+ engine: "docker" | "podman" | "auto" | None.
93
+ - None/"auto": try docker first then podman
94
+ - "docker": docker only
95
+ - "podman": podman only
96
+ which: injectable for tests.
97
+ runner: injectable for tests.
98
+
99
+ Returns:
100
+ ComposeCommand: argv prefix, e.g. ["docker", "compose"] or ["podman-compose"].
101
+
102
+ Raises:
103
+ RuntimeError: when no supported compose command is found.
104
+ """
105
+
106
+ normalized = (engine or os.environ.get("CANCAN_CONTAINER_ENGINE") or "auto").strip().lower()
107
+ if normalized not in {"auto", "docker", "podman"}:
108
+ normalized = "auto"
109
+
110
+ candidates: list[list[str]] = []
111
+
112
+ def add_docker_candidates() -> None:
113
+ # Prefer `docker compose` when docker exists.
114
+ # 优先使用 `docker compose`。
115
+ if which("docker"):
116
+ candidates.append(["docker", "compose"])
117
+ if which("docker-compose"):
118
+ candidates.append(["docker-compose"])
119
+
120
+ def add_podman_candidates() -> None:
121
+ # Prefer `podman compose` (Podman v4+ plugin). It avoids podman-compose pod semantics.
122
+ # 优先使用 `podman compose`(Podman v4+ 插件),避免 podman-compose 的 pod 语义与噪声报错。
123
+ if which("podman"):
124
+ candidates.append(["podman", "compose"])
125
+ if which("podman-compose"):
126
+ candidates.append(["podman-compose"])
127
+
128
+ if normalized == "docker":
129
+ add_docker_candidates()
130
+ elif normalized == "podman":
131
+ add_podman_candidates()
132
+ else:
133
+ # Auto mode: try Docker first only if Docker daemon is reachable; otherwise prefer Podman.
134
+ # 自动模式:只有当 Docker daemon 可用时才优先 Docker;否则优先 Podman,避免卡住。
135
+ add_docker_candidates()
136
+ add_podman_candidates()
137
+
138
+ for prefix in candidates:
139
+ # 1) CLI exists?
140
+ # 1) CLI 是否存在?
141
+ if not _can_run([*prefix, "version"], runner, timeout_seconds=5.0):
142
+ continue
143
+
144
+ # 2) Daemon reachable? (best-effort, fast timeout)
145
+ # 2) daemon 是否可用?(尽力而为,短超时)
146
+ probe = _daemon_ready_probe(prefix)
147
+ if probe is not None and _can_run(probe, runner, timeout_seconds=2.0):
148
+ return ComposeCommand(argv_prefix=prefix)
149
+
150
+ # If probe is not available, accept CLI presence.
151
+ # 若没有 probe,退化为“CLI 存在即接受”。
152
+ if probe is None:
153
+ return ComposeCommand(argv_prefix=prefix)
154
+
155
+ # Fallback: if some CLI exists but daemon probe failed (e.g. engine booting), return first CLI.
156
+ # 兜底:若 CLI 存在但 daemon probe 失败(例如引擎刚启动),返回第一个可用 CLI。
157
+ for prefix in candidates:
158
+ if _can_run([*prefix, "version"], runner, timeout_seconds=5.0):
159
+ return ComposeCommand(argv_prefix=prefix)
160
+
161
+ raise RuntimeError(
162
+ "No compose command found. Install Docker Desktop (docker compose) or Podman (podman compose / podman-compose)."
163
+ )
164
+
165
+
166
+ def run_compose(
167
+ *,
168
+ compose_file: Path,
169
+ args: list[str],
170
+ workspace: Path,
171
+ engine: str | None = None,
172
+ runner: Callable[..., subprocess.CompletedProcess[str]] = subprocess.run,
173
+ ) -> None:
174
+ """执行 compose 子命令 / Execute compose subcommand."""
175
+
176
+ cmd = detect_compose_command(engine=engine).with_file(compose_file)
177
+ full_cmd = [*cmd, *args]
178
+
179
+ runner(
180
+ full_cmd,
181
+ cwd=str(workspace),
182
+ check=True,
183
+ text=True,
184
+ )
185
+
186
+
187
+ def run_compose_streaming(
188
+ *,
189
+ compose_file: Path,
190
+ args: list[str],
191
+ workspace: Path,
192
+ engine: str | None = None,
193
+ log_file: Path | None = None,
194
+ ) -> None:
195
+ """Run compose command and stream output to stdout while teeing to a log file.
196
+
197
+ 执行 compose 命令,输出实时显示在终端,同时写入日志文件。
198
+ """
199
+
200
+ cmd = detect_compose_command(engine=engine).with_file(compose_file)
201
+ full_cmd = [*cmd, *args]
202
+
203
+ log_fp: IO[str] | None = None
204
+ if log_file is not None:
205
+ log_file.parent.mkdir(parents=True, exist_ok=True)
206
+ log_fp = open(log_file, "a", encoding="utf-8")
207
+
208
+ try:
209
+ proc = subprocess.Popen(
210
+ full_cmd,
211
+ cwd=str(workspace),
212
+ stdout=subprocess.PIPE,
213
+ stderr=subprocess.STDOUT,
214
+ text=True,
215
+ bufsize=1,
216
+ )
217
+
218
+ assert proc.stdout is not None
219
+ for line in proc.stdout:
220
+ print(line, end="")
221
+ if log_fp is not None:
222
+ log_fp.write(line)
223
+ rc = proc.wait()
224
+ if rc != 0:
225
+ raise subprocess.CalledProcessError(rc, full_cmd)
226
+ finally:
227
+ if log_fp is not None:
228
+ log_fp.close()