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.
- cancan_microstack/__init__.py +14 -0
- cancan_microstack/__version__.py +10 -0
- cancan_microstack/assets/__init__.py +6 -0
- cancan_microstack/assets/builds/caddy/Caddyfile +187 -0
- cancan_microstack/assets/builds/caddy/DEPLOYMENT.md +303 -0
- cancan_microstack/assets/builds/caddy/Dockerfile +46 -0
- cancan_microstack/assets/builds/caddy/README.md +343 -0
- cancan_microstack/assets/builds/caddy/geoip/README.md +5 -0
- cancan_microstack/assets/builds/caddy/start.sh +78 -0
- cancan_microstack/assets/builds/caddy/waf/coraza.conf +179 -0
- cancan_microstack/assets/builds/service/Dockerfile +59 -0
- cancan_microstack/assets/builds/service/README.md +13 -0
- cancan_microstack/assets/ddl/create_db.sql +22 -0
- cancan_microstack/assets/ddl/infra/execution_log_tbl.sql +46 -0
- cancan_microstack/assets/ddl/infra/node_instance_tbl.sql +56 -0
- cancan_microstack/assets/ddl/infra/service_action_log_tbl.sql +36 -0
- cancan_microstack/assets/ddl/infra/service_config_tbl.sql +26 -0
- cancan_microstack/assets/ddl/infra/service_info_tbl.sql +45 -0
- cancan_microstack/assets/ddl/infra/service_instance_tbl.sql +54 -0
- cancan_microstack/assets/ddl/infra/service_operation_tbl.sql +47 -0
- cancan_microstack/assets/ddl/infra/workflow_definition_tbl.sql +60 -0
- cancan_microstack/assets/ddl/infra/workflow_definition_version_tbl.sql +35 -0
- cancan_microstack/assets/ddl/infra/workflow_engine_alert_tbl.sql +34 -0
- cancan_microstack/assets/ddl/infra/workflow_run_tbl.sql +52 -0
- cancan_microstack/assets/ddl/ops/admin_user_tbl.sql +34 -0
- cancan_microstack/assets/ddl/ops/caddy_access_log_tbl.sql +91 -0
- cancan_microstack/assets/ddl/ops/caddy_certificate_tbl.sql +59 -0
- cancan_microstack/assets/ddl/ops/caddy_rate_limit_tbl.sql +64 -0
- cancan_microstack/assets/ddl/ops/caddy_route_tbl.sql +63 -0
- cancan_microstack/assets/ddl/ops/caddy_stats_tbl.sql +77 -0
- cancan_microstack/assets/ddl/trigger.sql +21 -0
- cancan_microstack/assets/docker/docker-compose.infra.yml +401 -0
- cancan_microstack/assets/scripts/README.md +195 -0
- cancan_microstack/assets/scripts/docker/build_images.sh +44 -0
- cancan_microstack/assets/scripts/docker/force_rebuild_images.sh +38 -0
- cancan_microstack/assets/scripts/docker/rebuild_all.sh +34 -0
- cancan_microstack/assets/scripts/docker/rebuild_compose.sh +61 -0
- cancan_microstack/assets/scripts/docker/restart.sh +35 -0
- cancan_microstack/assets/scripts/docker/restart_compose.sh +35 -0
- cancan_microstack/assets/scripts/docker/start.sh +78 -0
- cancan_microstack/assets/scripts/docker/start_all.sh +46 -0
- cancan_microstack/assets/scripts/docker/start_compose.sh +66 -0
- cancan_microstack/assets/scripts/docker/stop.sh +67 -0
- cancan_microstack/assets/scripts/docker/stop_all.sh +38 -0
- cancan_microstack/assets/scripts/docker/stop_compose.sh +38 -0
- cancan_microstack/assets/scripts/podman/build_images_podman.sh +59 -0
- cancan_microstack/assets/scripts/podman/cleanup_podman.sh +25 -0
- cancan_microstack/assets/scripts/podman/force_rebuild_images_podman.sh +56 -0
- cancan_microstack/assets/scripts/podman/rebuild_all_podman.sh +37 -0
- cancan_microstack/assets/scripts/podman/rebuild_compose_podman.sh +60 -0
- cancan_microstack/assets/scripts/podman/restart_compose_podman.sh +73 -0
- cancan_microstack/assets/scripts/podman/start_all_podman.sh +66 -0
- cancan_microstack/assets/scripts/podman/start_compose_podman.sh +80 -0
- cancan_microstack/assets/scripts/podman/start_podman.sh +91 -0
- cancan_microstack/assets/scripts/podman/stop.sh +73 -0
- cancan_microstack/assets/scripts/podman/stop_all_podman.sh +34 -0
- cancan_microstack/assets/scripts/podman/stop_compose_podman.sh +58 -0
- cancan_microstack/assets/scripts/start_controllersrv.sh +9 -0
- cancan_microstack/assets/scripts/utils/check_all_db_tables.sh +104 -0
- cancan_microstack/assets/scripts/utils/check_env.sh +177 -0
- cancan_microstack/assets/scripts/utils/check_service_management_deployment.sh +225 -0
- cancan_microstack/assets/scripts/utils/deploy_service_management.sh +176 -0
- cancan_microstack/assets/scripts/utils/force_reload_infrasrv.sh +52 -0
- cancan_microstack/assets/scripts/utils/monitor_service_management.sh +187 -0
- cancan_microstack/assets/scripts/utils/reset_postgres_volume.sh +68 -0
- cancan_microstack/assets/scripts/utils/test_async_operations.sh +141 -0
- cancan_microstack/assets/scripts/utils/verify_real_operations.sh +76 -0
- cancan_microstack/assets/service/Dockerfile +65 -0
- cancan_microstack/assets/www/adminops/assets/AppEmpty.vue_vue_type_script_setup_true_lang-BOKUurnM.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConfigManage-DKV5YOUz.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConfigManage-Y5bhy7wG.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ConsoleManage-8ljYvCW2.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ConsoleManage-BWpyqbuQ.css +1 -0
- cancan_microstack/assets/www/adminops/assets/DashboardNew-B9Nf1OPl.js +1 -0
- cancan_microstack/assets/www/adminops/assets/DashboardNew-DYWZKQ1V.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LogSearch-CA0Jhe78.js +1 -0
- cancan_microstack/assets/www/adminops/assets/LogSearch-CCZfTNPF.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LoginView-BId3kP3M.css +1 -0
- cancan_microstack/assets/www/adminops/assets/LoginView-BQZTV_Qy.js +1 -0
- cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-BdEYwqFq.js +1 -0
- cancan_microstack/assets/www/adminops/assets/OperationProgressDialog-D-pASR8G.css +1 -0
- cancan_microstack/assets/www/adminops/assets/PageContainer-Byss-yUC.js +1 -0
- cancan_microstack/assets/www/adminops/assets/PageContainer-C3nSZwM7.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RateLimitManage-BDI8jLpC.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RateLimitManage-DJY4NiF-.js +1 -0
- cancan_microstack/assets/www/adminops/assets/RouteManage-DaUQ4QLw.css +1 -0
- cancan_microstack/assets/www/adminops/assets/RouteManage-w9XCU0UA.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceCard-BFzHe6Tw.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceCard-BJUhWnA-.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceDetail-Cw24WuKp.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceDetail-Yum47zdB.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceList-C7ryvbhE.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceList-Cgd01fUx.css +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceLogs-COpG9H0h.js +1 -0
- cancan_microstack/assets/www/adminops/assets/ServiceLogs-H_Alq0cf.css +1 -0
- cancan_microstack/assets/www/adminops/assets/StatsOverview-D0TwMQkA.js +39 -0
- cancan_microstack/assets/www/adminops/assets/StatsOverview-lqAN6pqM.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpBindView-CWlAmzFt.js +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpBindView-HoQC1lhx.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpVerifyView-BHN1VtX1.css +1 -0
- cancan_microstack/assets/www/adminops/assets/TotpVerifyView-D3w_lZk8.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowCenter-DU_mpIA0.css +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowCenter-i50rZyxN.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-CnHokPL9.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowDesigner-DaZaZpLd.css +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowRuns-B09hK48c.js +1 -0
- cancan_microstack/assets/www/adminops/assets/WorkflowRuns-wGutKIIU.css +1 -0
- cancan_microstack/assets/www/adminops/assets/caddy-nnCKf8fG.js +1 -0
- cancan_microstack/assets/www/adminops/assets/format-Cuzxgna9.js +1 -0
- cancan_microstack/assets/www/adminops/assets/index-CiFlm8oc.js +64 -0
- cancan_microstack/assets/www/adminops/assets/index-UW0T1Dkc.css +1 -0
- cancan_microstack/assets/www/adminops/assets/service-BYlgGPs_.js +1 -0
- cancan_microstack/assets/www/adminops/assets/service-operation-6GzLw2Z1.js +1 -0
- cancan_microstack/assets/www/adminops/assets/style-CcIXnQ5y.css +1 -0
- cancan_microstack/assets/www/adminops/assets/style-lRnStdGu.js +39 -0
- cancan_microstack/assets/www/adminops/assets/useDebounce-BRlqfXqf.js +1 -0
- cancan_microstack/assets/www/adminops/assets/workflow-CUXs39Ac.js +1 -0
- cancan_microstack/assets/www/adminops/index.html +16 -0
- cancan_microstack/assets/www/adminops/vite.svg +1 -0
- cancan_microstack/cli/__init__.py +14 -0
- cancan_microstack/cli/__main__.py +9 -0
- cancan_microstack/cli/main.py +552 -0
- cancan_microstack/cmd/__init__.py +54 -0
- cancan_microstack/cmd/cancan/__init__.py +12 -0
- cancan_microstack/cmd/cancan/run.py +395 -0
- cancan_microstack/cmd/controllersrv/__init__.py +0 -0
- cancan_microstack/cmd/controllersrv/run.py +131 -0
- cancan_microstack/cmd/infrasrv/__init__.py +5 -0
- cancan_microstack/cmd/infrasrv/run.py +100 -0
- cancan_microstack/cmd/opsbffsrv/__init__.py +5 -0
- cancan_microstack/cmd/opsbffsrv/run.py +96 -0
- cancan_microstack/core/__init__.py +5 -0
- cancan_microstack/core/assets.py +123 -0
- cancan_microstack/core/compose_builder.py +102 -0
- cancan_microstack/core/doctor.py +152 -0
- cancan_microstack/core/microstack.py +71 -0
- cancan_microstack/core/runner.py +56 -0
- cancan_microstack/core/stack_manager.py +186 -0
- cancan_microstack/public/__init__.py +7 -0
- cancan_microstack/public/api/__init__.py +1 -0
- cancan_microstack/public/api/controllersrv_client.py +277 -0
- cancan_microstack/public/api/infrasrv_client.py +404 -0
- cancan_microstack/public/const/__init__.py +1 -0
- cancan_microstack/public/const/action_consts.py +18 -0
- cancan_microstack/public/const/app_consts.py +42 -0
- cancan_microstack/public/const/caddy_consts.py +22 -0
- cancan_microstack/public/const/controllersrv_consts.py +163 -0
- cancan_microstack/public/const/docker_consts.py +15 -0
- cancan_microstack/public/const/error.py +56 -0
- cancan_microstack/public/const/health_consts.py +52 -0
- cancan_microstack/public/const/hook_enums.py +56 -0
- cancan_microstack/public/const/logging_enums.py +13 -0
- cancan_microstack/public/const/metrics_enums.py +36 -0
- cancan_microstack/public/const/monitor_enums.py +26 -0
- cancan_microstack/public/const/operation_consts.py +53 -0
- cancan_microstack/public/const/opsbffsrv_error.py +92 -0
- cancan_microstack/public/const/overrides_consts.py +13 -0
- cancan_microstack/public/const/redis.py +17 -0
- cancan_microstack/public/const/service_consts.py +15 -0
- cancan_microstack/public/const/workflow_consts.py +65 -0
- cancan_microstack/public/error.py +41 -0
- cancan_microstack/public/logging/__init__.py +0 -0
- cancan_microstack/public/logging/initializer.py +109 -0
- cancan_microstack/public/logging/mq_handler.py +279 -0
- cancan_microstack/public/schemas/__init__.py +1 -0
- cancan_microstack/public/schemas/caddy/__init__.py +381 -0
- cancan_microstack/public/schemas/caddy/analysis.py +90 -0
- cancan_microstack/public/schemas/caddy/route.py +18 -0
- cancan_microstack/public/schemas/common.py +79 -0
- cancan_microstack/public/schemas/controllersrv/__init__.py +3 -0
- cancan_microstack/public/schemas/controllersrv/async_requests.py +30 -0
- cancan_microstack/public/schemas/controllersrv/compose_models.py +47 -0
- cancan_microstack/public/schemas/controllersrv/const.py +24 -0
- cancan_microstack/public/schemas/controllersrv/docker_models.py +45 -0
- cancan_microstack/public/schemas/controllersrv/docker_responses.py +104 -0
- cancan_microstack/public/schemas/controllersrv/requests.py +54 -0
- cancan_microstack/public/schemas/controllersrv/responses.py +124 -0
- cancan_microstack/public/schemas/controllersrv/task_models.py +102 -0
- cancan_microstack/public/schemas/controllersrv/validation.py +23 -0
- cancan_microstack/public/schemas/hook_metrics.py +124 -0
- cancan_microstack/public/schemas/hooks.py +39 -0
- cancan_microstack/public/schemas/infra/__init__.py +0 -0
- cancan_microstack/public/schemas/infra/cleanup.py +25 -0
- cancan_microstack/public/schemas/infra/container.py +74 -0
- cancan_microstack/public/schemas/infra/enums.py +135 -0
- cancan_microstack/public/schemas/infra/health_check.py +42 -0
- cancan_microstack/public/schemas/infra/hook_log.py +42 -0
- cancan_microstack/public/schemas/infra/operation.py +90 -0
- cancan_microstack/public/schemas/infra/overview.py +25 -0
- cancan_microstack/public/schemas/infra/push.py +33 -0
- cancan_microstack/public/schemas/infra/service_action_log.py +47 -0
- cancan_microstack/public/schemas/infra/service_config.py +10 -0
- cancan_microstack/public/schemas/infra/service_info.py +69 -0
- cancan_microstack/public/schemas/infra/service_instance.py +93 -0
- cancan_microstack/public/schemas/infra/service_management.py +152 -0
- cancan_microstack/public/schemas/infra/service_operation.py +79 -0
- cancan_microstack/public/schemas/infra/service_registry.py +158 -0
- cancan_microstack/public/schemas/infra/status_types.py +19 -0
- cancan_microstack/public/schemas/infra/workflow.py +566 -0
- cancan_microstack/public/schemas/logging/__init__.py +1 -0
- cancan_microstack/public/schemas/logging/log_event.py +121 -0
- cancan_microstack/public/schemas/opsbffsrv/__init__.py +1 -0
- cancan_microstack/public/schemas/opsbffsrv/async_ops.py +17 -0
- cancan_microstack/public/schemas/opsbffsrv/db_admin.py +147 -0
- cancan_microstack/public/schemas/opsbffsrv/db_init.py +48 -0
- cancan_microstack/public/schemas/opsbffsrv/service_config.py +89 -0
- cancan_microstack/public/schemas/opsbffsrv/service_logs.py +54 -0
- cancan_microstack/public/schemas/service_operation.py +24 -0
- cancan_microstack/public/schemas/service_registry.py +40 -0
- cancan_microstack/public/types/__init__.py +7 -0
- cancan_microstack/public/web/__init__.py +0 -0
- cancan_microstack/public/web/config_value.py +105 -0
- cancan_microstack/public/web/server.py +385 -0
- cancan_microstack/py.typed +0 -0
- cancan_microstack/runtime/__init__.py +0 -0
- cancan_microstack/runtime/compose_cmd.py +228 -0
- cancan_microstack/runtime/host_daemon.py +318 -0
- cancan_microstack/runtime/overrides.py +103 -0
- cancan_microstack/runtime/resources.py +25 -0
- cancan_microstack/runtime/workspace.py +94 -0
- cancan_microstack/services/__init__.py +0 -0
- cancan_microstack/services/controllersrv/__init__.py +8 -0
- cancan_microstack/services/controllersrv/application/__init__.py +0 -0
- cancan_microstack/services/controllersrv/application/docker_compose_app.py +427 -0
- cancan_microstack/services/controllersrv/conf/__init__.py +0 -0
- cancan_microstack/services/controllersrv/conf/config.py +76 -0
- cancan_microstack/services/controllersrv/conf/settings.py +54 -0
- cancan_microstack/services/controllersrv/domain/__init__.py +0 -0
- cancan_microstack/services/controllersrv/domain/docker_compose/__init__.py +0 -0
- cancan_microstack/services/controllersrv/domain/docker_compose/docker_compose_domain.py +278 -0
- cancan_microstack/services/controllersrv/domain/service_validator.py +327 -0
- cancan_microstack/services/controllersrv/domain/task/__init__.py +17 -0
- cancan_microstack/services/controllersrv/domain/task/task_queue.py +286 -0
- cancan_microstack/services/controllersrv/domain/task/task_worker.py +495 -0
- cancan_microstack/services/controllersrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/controllersrv/interface/api/docker_control_api.py +470 -0
- cancan_microstack/services/controllersrv/router.py +132 -0
- cancan_microstack/services/infrasrv/__init__.py +4 -0
- cancan_microstack/services/infrasrv/application/__init__.py +0 -0
- cancan_microstack/services/infrasrv/application/health_check_app.py +24 -0
- cancan_microstack/services/infrasrv/application/logging/__init__.py +1 -0
- cancan_microstack/services/infrasrv/application/logging/log_ingestion_service.py +183 -0
- cancan_microstack/services/infrasrv/application/service_config.py +22 -0
- cancan_microstack/services/infrasrv/application/service_logs_app.py +53 -0
- cancan_microstack/services/infrasrv/application/service_management_app.py +689 -0
- cancan_microstack/services/infrasrv/application/service_operation_tracker.py +251 -0
- cancan_microstack/services/infrasrv/application/service_registry.py +53 -0
- cancan_microstack/services/infrasrv/application/workflow/__init__.py +0 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_app.py +991 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_queue.py +302 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_tasks.py +46 -0
- cancan_microstack/services/infrasrv/application/workflow/workflow_worker_runtime.py +122 -0
- cancan_microstack/services/infrasrv/conf/__init__.py +0 -0
- cancan_microstack/services/infrasrv/conf/config.py +98 -0
- cancan_microstack/services/infrasrv/domain/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/health_check/__init__.py +3 -0
- cancan_microstack/services/infrasrv/domain/health_check/health_check_domain.py +576 -0
- cancan_microstack/services/infrasrv/domain/hooks/__init__.py +19 -0
- cancan_microstack/services/infrasrv/domain/hooks/builtin_hooks.py +308 -0
- cancan_microstack/services/infrasrv/domain/hooks/hook_registry.py +43 -0
- cancan_microstack/services/infrasrv/domain/hooks/hooks_log_utils.py +275 -0
- cancan_microstack/services/infrasrv/domain/hooks/init.py +17 -0
- cancan_microstack/services/infrasrv/domain/hooks/metrics.py +205 -0
- cancan_microstack/services/infrasrv/domain/hooks/pre_registration_hooks.py +490 -0
- cancan_microstack/services/infrasrv/domain/registry/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/registry/service_registry.py +509 -0
- cancan_microstack/services/infrasrv/domain/service_config/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/service_config/service_config.py +50 -0
- cancan_microstack/services/infrasrv/domain/service_logs/__init__.py +0 -0
- cancan_microstack/services/infrasrv/domain/service_logs/service_logs_domain.py +51 -0
- cancan_microstack/services/infrasrv/domain/workflow/__init__.py +4 -0
- cancan_microstack/services/infrasrv/domain/workflow/engine.py +159 -0
- cancan_microstack/services/infrasrv/domain/workflow/node_handlers.py +509 -0
- cancan_microstack/services/infrasrv/domain/workflow/workflow_domain.py +164 -0
- cancan_microstack/services/infrasrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/api/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/api/controllersrv_api.py +165 -0
- cancan_microstack/services/infrasrv/infrastructure/cache/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/cache/service_registry_cache.py +174 -0
- cancan_microstack/services/infrasrv/infrastructure/db/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/execution_log_tbl.py +53 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/node_instance_tbl.py +55 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_config_tbl.py +30 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_info_tbl.py +59 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_instance_tbl.py +88 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/service_operation_tbl.py +73 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_tbl.py +55 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_definition_version_tbl.py +43 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_engine_alert_tbl.py +57 -0
- cancan_microstack/services/infrasrv/infrastructure/db/model/workflow_run_tbl.py +56 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/__init__.py +0 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_action_log_op.py +239 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config.py +80 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_config_manager.py +198 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_info_op.py +297 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_instance_op.py +688 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_operation_op.py +387 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/service_registry.py +124 -0
- cancan_microstack/services/infrasrv/infrastructure/db/operate/workflow_op.py +804 -0
- cancan_microstack/services/infrasrv/infrastructure/ddl_manager.py +31 -0
- cancan_microstack/services/infrasrv/infrastructure/mongo/__init__.py +1 -0
- cancan_microstack/services/infrasrv/infrastructure/mongo/log_repository.py +129 -0
- cancan_microstack/services/infrasrv/interface/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/api/health_check_api.py +29 -0
- cancan_microstack/services/infrasrv/interface/api/hooks.py +284 -0
- cancan_microstack/services/infrasrv/interface/api/internal.py +49 -0
- cancan_microstack/services/infrasrv/interface/api/internal_instance_api.py +265 -0
- cancan_microstack/services/infrasrv/interface/api/internal_operation_api.py +206 -0
- cancan_microstack/services/infrasrv/interface/api/service_config.py +50 -0
- cancan_microstack/services/infrasrv/interface/api/service_logs_api.py +49 -0
- cancan_microstack/services/infrasrv/interface/api/service_management_api.py +113 -0
- cancan_microstack/services/infrasrv/interface/api/service_registry.py +117 -0
- cancan_microstack/services/infrasrv/interface/api/workflow_api.py +303 -0
- cancan_microstack/services/infrasrv/interface/schedule/__init__.py +0 -0
- cancan_microstack/services/infrasrv/interface/schedule/cleanup.py +13 -0
- cancan_microstack/services/infrasrv/interface/schedule/health_check.py +27 -0
- cancan_microstack/services/infrasrv/interface/schedule/log_cleanup.py +26 -0
- cancan_microstack/services/infrasrv/interface/schedule/operation_tracker.py +25 -0
- cancan_microstack/services/infrasrv/interface/schedule/scheduler.py +39 -0
- cancan_microstack/services/infrasrv/interface/schedule/workflow_scheduler.py +115 -0
- cancan_microstack/services/infrasrv/router.py +341 -0
- cancan_microstack/services/opsbffsrv/__init__.py +4 -0
- cancan_microstack/services/opsbffsrv/application/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/application/async_operation_app.py +150 -0
- cancan_microstack/services/opsbffsrv/application/auth_app.py +285 -0
- cancan_microstack/services/opsbffsrv/application/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/application/caddy/access_log_analysis_app.py +344 -0
- cancan_microstack/services/opsbffsrv/application/caddy/access_log_ingestion_service.py +169 -0
- cancan_microstack/services/opsbffsrv/application/caddy/certificate_management_app.py +355 -0
- cancan_microstack/services/opsbffsrv/application/caddy/rate_limit_management_app.py +496 -0
- cancan_microstack/services/opsbffsrv/application/caddy/route_management_app.py +401 -0
- cancan_microstack/services/opsbffsrv/application/caddy/stats_aggregation_app.py +364 -0
- cancan_microstack/services/opsbffsrv/application/db_admin_app.py +103 -0
- cancan_microstack/services/opsbffsrv/application/db_init_app.py +283 -0
- cancan_microstack/services/opsbffsrv/application/logging/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/application/logging/log_query_app.py +28 -0
- cancan_microstack/services/opsbffsrv/application/service_config.py +158 -0
- cancan_microstack/services/opsbffsrv/application/service_logs_app.py +74 -0
- cancan_microstack/services/opsbffsrv/application/service_registry.py +36 -0
- cancan_microstack/services/opsbffsrv/application/workflow_ops_app.py +730 -0
- cancan_microstack/services/opsbffsrv/conf/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/conf/config.py +224 -0
- cancan_microstack/services/opsbffsrv/domain/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/auth/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/auth/admin_init.py +38 -0
- cancan_microstack/services/opsbffsrv/domain/auth/auth_domain.py +108 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/access_log_analysis.py +358 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/certificate_management.py +325 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/default_routes.py +53 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/rate_limit_management.py +308 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/route_management.py +279 -0
- cancan_microstack/services/opsbffsrv/domain/caddy/stats_aggregation.py +654 -0
- cancan_microstack/services/opsbffsrv/domain/db_admin/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/db_admin/db_admin_domain.py +118 -0
- cancan_microstack/services/opsbffsrv/domain/db_init/__init__.py +3 -0
- cancan_microstack/services/opsbffsrv/domain/db_init/db_init_domain.py +358 -0
- cancan_microstack/services/opsbffsrv/domain/logging/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/domain/logging/log_query_domain.py +99 -0
- cancan_microstack/services/opsbffsrv/domain/service_config/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/service_config/service_config.py +81 -0
- cancan_microstack/services/opsbffsrv/domain/service_registry/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/domain/service_registry/service_registry.py +292 -0
- cancan_microstack/services/opsbffsrv/infrastructure/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/api/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/api/infrasrv_api.py +242 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/captcha_service.py +67 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/password_service.py +12 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/redis_store.py +131 -0
- cancan_microstack/services/opsbffsrv/infrastructure/auth/totp_service.py +59 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/access_log_parser.py +307 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/admin_api_client.py +678 -0
- cancan_microstack/services/opsbffsrv/infrastructure/caddy/ip_geo_locator.py +176 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/admin_user_tbl.py +33 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_access_log_tbl.py +90 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_certificate_tbl.py +65 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_rate_limit_tbl.py +69 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_route_tbl.py +66 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/caddy_stats_tbl.py +78 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_action_log_tbl.py +44 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_config_tbl.py +30 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_info_tbl.py +51 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/model/service_instance_tbl.py +68 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/admin_user_operate.py +59 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_access_log.py +531 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_certificate.py +451 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_rate_limit.py +360 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_route.py +271 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/caddy_stats.py +343 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_action_log_op.py +57 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_config.py +86 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_info_op.py +79 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_instance.py +58 -0
- cancan_microstack/services/opsbffsrv/infrastructure/db/operate/service_registry.py +138 -0
- cancan_microstack/services/opsbffsrv/infrastructure/ddl_manager.py +31 -0
- cancan_microstack/services/opsbffsrv/infrastructure/mongo/__init__.py +1 -0
- cancan_microstack/services/opsbffsrv/infrastructure/mongo/log_query_repository.py +87 -0
- cancan_microstack/services/opsbffsrv/interface/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/api/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/api/async_operation_api.py +137 -0
- cancan_microstack/services/opsbffsrv/interface/api/auth_api.py +113 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/__init__.py +3 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/access_log_api.py +174 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/certificate_api.py +235 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/rate_limit_api.py +302 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/route_api.py +250 -0
- cancan_microstack/services/opsbffsrv/interface/api/caddy/stats_api.py +243 -0
- cancan_microstack/services/opsbffsrv/interface/api/db_admin_api.py +62 -0
- cancan_microstack/services/opsbffsrv/interface/api/db_init_api.py +109 -0
- cancan_microstack/services/opsbffsrv/interface/api/instance_management_api.py +165 -0
- cancan_microstack/services/opsbffsrv/interface/api/log_query_api.py +41 -0
- cancan_microstack/services/opsbffsrv/interface/api/mongo_express_proxy_api.py +181 -0
- cancan_microstack/services/opsbffsrv/interface/api/pgweb_proxy_api.py +154 -0
- cancan_microstack/services/opsbffsrv/interface/api/rabbitmq_mgmt_proxy_api.py +518 -0
- cancan_microstack/services/opsbffsrv/interface/api/redis_commander_proxy_api.py +133 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_config.py +146 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_logs_api.py +81 -0
- cancan_microstack/services/opsbffsrv/interface/api/service_registry.py +66 -0
- cancan_microstack/services/opsbffsrv/interface/api/workflow_ops_api.py +413 -0
- cancan_microstack/services/opsbffsrv/interface/middleware/__init__.py +0 -0
- cancan_microstack/services/opsbffsrv/interface/middleware/auth_middleware.py +52 -0
- cancan_microstack/services/opsbffsrv/router.py +901 -0
- cancan_microstack/utils/__init__.py +1 -0
- cancan_microstack/utils/container_env.py +218 -0
- cancan_microstack-0.0.1.dist-info/METADATA +155 -0
- cancan_microstack-0.0.1.dist-info/RECORD +440 -0
- cancan_microstack-0.0.1.dist-info/WHEEL +5 -0
- cancan_microstack-0.0.1.dist-info/entry_points.txt +2 -0
- cancan_microstack-0.0.1.dist-info/licenses/LICENSE +21 -0
- cancan_microstack-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""
|
|
2
|
+
服务注册查询领域层
|
|
3
|
+
负责为ops前端提供服务实例的查询功能
|
|
4
|
+
不包含服务注册/注销/心跳等写操作(由infrasrv负责)
|
|
5
|
+
说明:本服务直接从数据库读取由 infrasrv 维护的服务状态,不执行任何独立的健康检查或陈旧实例过滤。
|
|
6
|
+
"""
|
|
7
|
+
from typing import (
|
|
8
|
+
List,
|
|
9
|
+
Dict,
|
|
10
|
+
)
|
|
11
|
+
from datetime import (
|
|
12
|
+
datetime,
|
|
13
|
+
timezone,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from cancan_microstack.public.const.health_consts import (
|
|
17
|
+
HealthOverallStatus,
|
|
18
|
+
InstanceHealthStatus,
|
|
19
|
+
ServiceExpectedStatusAlias,
|
|
20
|
+
ServiceRuntimeStatus,
|
|
21
|
+
)
|
|
22
|
+
from cancan_microstack.public.schemas.infra.enums import ServiceType
|
|
23
|
+
from cancan_microstack.public.schemas.infra.overview import ServiceOverview
|
|
24
|
+
from cancan_microstack.public.schemas.infra.service_registry import ServiceInstance
|
|
25
|
+
from cancan_microstack.services.opsbffsrv.infrastructure.db.operate.service_info_op import get_all_service_info
|
|
26
|
+
from cancan_microstack.services.opsbffsrv.infrastructure.db.operate.service_registry import (
|
|
27
|
+
get_all_service_names,
|
|
28
|
+
get_all_services,
|
|
29
|
+
get_service_by_instance,
|
|
30
|
+
get_service_instances as db_get_service_instances,
|
|
31
|
+
)
|
|
32
|
+
from linglong_web import LinglongConfig
|
|
33
|
+
from linglong_web.utils import logger
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ServiceRegistryDomain:
|
|
37
|
+
"""
|
|
38
|
+
服务注册查询域
|
|
39
|
+
Service Registry Query Domain
|
|
40
|
+
|
|
41
|
+
职责:直接查询并返回由 infrasrv 清理和维护的服务注册数据。
|
|
42
|
+
Responsibility: Directly query and return service registry data maintained and cleaned by infrasrv.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def _normalize_service_name(service_name: str) -> str:
|
|
47
|
+
"""规范化服务名(兼容 .service 后缀)
|
|
48
|
+
Normalize service name (compatible with .service suffix)
|
|
49
|
+
"""
|
|
50
|
+
value = (service_name or "").strip()
|
|
51
|
+
if value.endswith(".service"):
|
|
52
|
+
return value[:-len(".service")]
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _normalize_expected_status(expected_status: str) -> ServiceRuntimeStatus:
|
|
57
|
+
"""规范化期望状态
|
|
58
|
+
Normalize expected status string
|
|
59
|
+
"""
|
|
60
|
+
value = (expected_status or ServiceRuntimeStatus.RUNNING.value).strip().lower()
|
|
61
|
+
if value in {
|
|
62
|
+
ServiceExpectedStatusAlias.STOP.value,
|
|
63
|
+
ServiceExpectedStatusAlias.STOPPED.value,
|
|
64
|
+
ServiceExpectedStatusAlias.DOWN.value,
|
|
65
|
+
}:
|
|
66
|
+
return ServiceRuntimeStatus.STOPPED
|
|
67
|
+
return ServiceRuntimeStatus.RUNNING
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def _extract_instance_timestamp(instance: ServiceInstance) -> datetime | None:
|
|
71
|
+
"""提取实例最近活动时间
|
|
72
|
+
Extract last seen timestamp for an instance
|
|
73
|
+
"""
|
|
74
|
+
for field_name in ("last_heartbeat", "update_time", "created_time"):
|
|
75
|
+
timestamp = getattr(instance, field_name, None)
|
|
76
|
+
if timestamp is not None:
|
|
77
|
+
if timestamp.tzinfo is None:
|
|
78
|
+
return timestamp.replace(tzinfo=timezone.utc)
|
|
79
|
+
return timestamp
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def _is_stale_instance(self, instance: ServiceInstance, now: datetime) -> bool:
|
|
83
|
+
"""判断实例是否超时陈旧
|
|
84
|
+
Determine whether an instance is stale by heartbeat/update timestamp
|
|
85
|
+
"""
|
|
86
|
+
stale_seconds = int(getattr(LinglongConfig, "SERVICE_INSTANCE_STALE_SECONDS", 180) or 180)
|
|
87
|
+
if stale_seconds <= 0:
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
last_seen = self._extract_instance_timestamp(instance)
|
|
91
|
+
if last_seen is None:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
return (now - last_seen).total_seconds() > stale_seconds
|
|
95
|
+
|
|
96
|
+
def _build_instance_dedupe_key(self, instance: ServiceInstance) -> str:
|
|
97
|
+
"""构建实例去重键
|
|
98
|
+
Build dedupe key for instance consolidation
|
|
99
|
+
"""
|
|
100
|
+
normalized_service_name = self._normalize_service_name(instance.service_name)
|
|
101
|
+
|
|
102
|
+
if instance.container_name:
|
|
103
|
+
return f"{normalized_service_name}|container:{instance.container_name}"
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
f"{normalized_service_name}|endpoint:{instance.host}:{instance.port}:{instance.internal_port}"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def _load_expected_status_map(self) -> Dict[str, ServiceRuntimeStatus]:
|
|
110
|
+
"""加载服务期望状态映射
|
|
111
|
+
Load expected status map from service info table
|
|
112
|
+
"""
|
|
113
|
+
service_info_list = await get_all_service_info()
|
|
114
|
+
status_map: Dict[str, ServiceRuntimeStatus] = {}
|
|
115
|
+
for service_info in service_info_list:
|
|
116
|
+
normalized_service_name = self._normalize_service_name(service_info.service_name)
|
|
117
|
+
status_map[normalized_service_name] = self._normalize_expected_status(service_info.expected_status)
|
|
118
|
+
return status_map
|
|
119
|
+
|
|
120
|
+
def _filter_and_dedupe_instances(
|
|
121
|
+
self,
|
|
122
|
+
instances: List[ServiceInstance],
|
|
123
|
+
expected_status_map: Dict[str, ServiceRuntimeStatus],
|
|
124
|
+
) -> List[ServiceInstance]:
|
|
125
|
+
"""过滤陈旧实例并做同容器去重
|
|
126
|
+
Filter stale instances and dedupe duplicated container rows
|
|
127
|
+
"""
|
|
128
|
+
now = datetime.now(timezone.utc)
|
|
129
|
+
deduped_instances: Dict[str, ServiceInstance] = {}
|
|
130
|
+
|
|
131
|
+
for instance in instances:
|
|
132
|
+
normalized_service_name = self._normalize_service_name(instance.service_name)
|
|
133
|
+
expected_status = expected_status_map.get(normalized_service_name)
|
|
134
|
+
if expected_status == ServiceRuntimeStatus.STOPPED:
|
|
135
|
+
continue
|
|
136
|
+
|
|
137
|
+
if self._is_stale_instance(instance, now):
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
dedupe_key = self._build_instance_dedupe_key(instance)
|
|
141
|
+
existing = deduped_instances.get(dedupe_key)
|
|
142
|
+
if existing is None:
|
|
143
|
+
deduped_instances[dedupe_key] = instance
|
|
144
|
+
continue
|
|
145
|
+
|
|
146
|
+
existing_ts = self._extract_instance_timestamp(existing)
|
|
147
|
+
current_ts = self._extract_instance_timestamp(instance)
|
|
148
|
+
if existing_ts is None and current_ts is not None:
|
|
149
|
+
deduped_instances[dedupe_key] = instance
|
|
150
|
+
elif existing_ts is not None and current_ts is not None and current_ts >= existing_ts:
|
|
151
|
+
deduped_instances[dedupe_key] = instance
|
|
152
|
+
|
|
153
|
+
return list(deduped_instances.values())
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _derive_actual_status(total_instances: int, healthy_instances: int) -> ServiceRuntimeStatus:
|
|
157
|
+
"""根据实例统计推导实际状态
|
|
158
|
+
Derive actual status from instance counters
|
|
159
|
+
"""
|
|
160
|
+
if total_instances <= 0:
|
|
161
|
+
return ServiceRuntimeStatus.STOPPED
|
|
162
|
+
if healthy_instances > 0:
|
|
163
|
+
return ServiceRuntimeStatus.RUNNING
|
|
164
|
+
return ServiceRuntimeStatus.DEGRADED
|
|
165
|
+
|
|
166
|
+
def _to_models(self, instances: List[ServiceInstance]) -> List[ServiceInstance]:
|
|
167
|
+
"""将数据库模型转换为 Pydantic 模型 / Convert DB models to Pydantic models."""
|
|
168
|
+
return [ServiceInstance.model_validate(inst, from_attributes=True) for inst in instances]
|
|
169
|
+
|
|
170
|
+
async def get_service_instances(self, service_name: str | None = None, only_healthy: bool = True) -> List[ServiceInstance]:
|
|
171
|
+
"""
|
|
172
|
+
获取服务实例列表
|
|
173
|
+
Get the list of instances for a service.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
service_name: 服务名称 / Service name
|
|
177
|
+
only_healthy: 是否只返回健康实例 / Whether to return only healthy instances
|
|
178
|
+
"""
|
|
179
|
+
logger.debug(f"Querying instances for service: {service_name}, only_healthy: {only_healthy}")
|
|
180
|
+
|
|
181
|
+
normalized_service_name = self._normalize_service_name(service_name or "")
|
|
182
|
+
health_status_filter = InstanceHealthStatus.HEALTHY if only_healthy else None
|
|
183
|
+
|
|
184
|
+
if normalized_service_name:
|
|
185
|
+
instances = await db_get_service_instances(
|
|
186
|
+
normalized_service_name,
|
|
187
|
+
health_status=health_status_filter,
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
instances = await get_all_services()
|
|
191
|
+
if health_status_filter:
|
|
192
|
+
instances = [instance for instance in instances if instance.health_status == health_status_filter]
|
|
193
|
+
|
|
194
|
+
expected_status_map = await self._load_expected_status_map()
|
|
195
|
+
cleaned_instances = self._filter_and_dedupe_instances(instances, expected_status_map)
|
|
196
|
+
logger.info(
|
|
197
|
+
"Found %s cleaned instances for service=%s (only_healthy=%s)",
|
|
198
|
+
len(cleaned_instances),
|
|
199
|
+
normalized_service_name or "ALL",
|
|
200
|
+
only_healthy,
|
|
201
|
+
)
|
|
202
|
+
return self._to_models(cleaned_instances)
|
|
203
|
+
|
|
204
|
+
async def get_all_instances(self) -> List[ServiceInstance]:
|
|
205
|
+
"""
|
|
206
|
+
获取所有服务实例
|
|
207
|
+
Get all service instances.
|
|
208
|
+
"""
|
|
209
|
+
logger.debug("Querying all service instances.")
|
|
210
|
+
instances = await get_all_services()
|
|
211
|
+
expected_status_map = await self._load_expected_status_map()
|
|
212
|
+
cleaned_instances = self._filter_and_dedupe_instances(instances, expected_status_map)
|
|
213
|
+
return self._to_models(cleaned_instances)
|
|
214
|
+
|
|
215
|
+
async def get_instance(self, service_name: str, instance_id: str) -> ServiceInstance | None:
|
|
216
|
+
"""
|
|
217
|
+
获取指定服务实例
|
|
218
|
+
Get a specific service instance.
|
|
219
|
+
"""
|
|
220
|
+
instance = await get_service_by_instance(service_name, instance_id)
|
|
221
|
+
return ServiceInstance.model_validate(instance, from_attributes=True) if instance else None
|
|
222
|
+
|
|
223
|
+
async def get_all_service_names(self) -> List[str]:
|
|
224
|
+
"""
|
|
225
|
+
获取所有服务名称
|
|
226
|
+
Get all service names.
|
|
227
|
+
"""
|
|
228
|
+
return await get_all_service_names()
|
|
229
|
+
|
|
230
|
+
async def get_services_overview(self) -> List[ServiceOverview]:
|
|
231
|
+
"""
|
|
232
|
+
获取所有服务的概览信息(服务名、状态统计)
|
|
233
|
+
基于 service_info_tbl 的元数据配合实例信息完成统计
|
|
234
|
+
包含没有实例运行的服务
|
|
235
|
+
Get an overview of all services (name, status statistics).
|
|
236
|
+
This is based on metadata from service_info_tbl combined with instance information.
|
|
237
|
+
Includes services that have no running instances.
|
|
238
|
+
"""
|
|
239
|
+
all_service_info = await get_all_service_info()
|
|
240
|
+
all_instances = await get_all_services()
|
|
241
|
+
service_map: Dict[str, ServiceOverview] = {}
|
|
242
|
+
expected_status_map = await self._load_expected_status_map()
|
|
243
|
+
cleaned_instances = self._filter_and_dedupe_instances(all_instances, expected_status_map)
|
|
244
|
+
|
|
245
|
+
for service_info in all_service_info:
|
|
246
|
+
service_name = service_info.service_name
|
|
247
|
+
expected_status = self._normalize_expected_status(service_info.expected_status)
|
|
248
|
+
service_map[service_name] = ServiceOverview(
|
|
249
|
+
service_name=service_name,
|
|
250
|
+
description=service_info.description or "",
|
|
251
|
+
service_type=service_info.service_type or ServiceType.BUSINESS,
|
|
252
|
+
expected_status=expected_status,
|
|
253
|
+
desired_replicas=int(service_info.desired_replicas or 1),
|
|
254
|
+
actual_replicas=int(service_info.actual_replicas or 0),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
for instance in cleaned_instances:
|
|
258
|
+
service_name = self._normalize_service_name(instance.service_name)
|
|
259
|
+
if service_name not in service_map:
|
|
260
|
+
service_map[service_name] = ServiceOverview(
|
|
261
|
+
service_name=service_name,
|
|
262
|
+
service_type=ServiceType.BUSINESS,
|
|
263
|
+
expected_status=ServiceRuntimeStatus.RUNNING,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
service_map[service_name].total_instances += 1
|
|
267
|
+
if instance.status == HealthOverallStatus.UP or instance.status == InstanceHealthStatus.HEALTHY:
|
|
268
|
+
service_map[service_name].healthy_instances += 1
|
|
269
|
+
else:
|
|
270
|
+
service_map[service_name].unhealthy_instances += 1
|
|
271
|
+
|
|
272
|
+
for info in service_map.values():
|
|
273
|
+
info.actual_replicas = info.total_instances
|
|
274
|
+
info.actual_status = self._derive_actual_status(info.total_instances, info.healthy_instances)
|
|
275
|
+
info.status_matches_expected = info.expected_status == info.actual_status
|
|
276
|
+
|
|
277
|
+
if info.expected_status == ServiceRuntimeStatus.STOPPED:
|
|
278
|
+
if info.actual_status == ServiceRuntimeStatus.STOPPED:
|
|
279
|
+
info.overall_status = HealthOverallStatus.UP
|
|
280
|
+
else:
|
|
281
|
+
info.overall_status = HealthOverallStatus.PARTIAL
|
|
282
|
+
elif info.total_instances == 0:
|
|
283
|
+
info.overall_status = HealthOverallStatus.DOWN
|
|
284
|
+
elif info.healthy_instances == 0:
|
|
285
|
+
info.overall_status = HealthOverallStatus.DOWN
|
|
286
|
+
elif info.unhealthy_instances > 0:
|
|
287
|
+
info.overall_status = HealthOverallStatus.PARTIAL
|
|
288
|
+
else:
|
|
289
|
+
info.overall_status = HealthOverallStatus.UP
|
|
290
|
+
|
|
291
|
+
logger.info(f"Retrieved overview for {len(service_map)} services")
|
|
292
|
+
return list(service_map.values())
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
InfraSrv API 客户端 / InfraSrv API Client
|
|
3
|
+
|
|
4
|
+
用于调用 infrasrv 的内部接口和服务管理接口 / For calling infrasrv internal and service management interfaces
|
|
5
|
+
"""
|
|
6
|
+
from typing import (
|
|
7
|
+
Optional,
|
|
8
|
+
)
|
|
9
|
+
import os
|
|
10
|
+
import http
|
|
11
|
+
|
|
12
|
+
from linglong_web.utils import logger
|
|
13
|
+
from linglong_web import LinglongConfig
|
|
14
|
+
from linglong_web import (
|
|
15
|
+
HTTPClientConfig,
|
|
16
|
+
http_client,
|
|
17
|
+
)
|
|
18
|
+
from cancan_microstack.public.api.infrasrv_client import InfraSrvApiClient
|
|
19
|
+
from cancan_microstack.public.error import HTTPException
|
|
20
|
+
from cancan_microstack.public.const.error import ErrorCode
|
|
21
|
+
from cancan_microstack.public.schemas.infra.service_management import (
|
|
22
|
+
ServiceManagementRequest,
|
|
23
|
+
ServiceManagementAPIResponse,
|
|
24
|
+
)
|
|
25
|
+
from cancan_microstack.public.schemas.infra.operation import (
|
|
26
|
+
OperationCreateRequest,
|
|
27
|
+
OperationUpdateRequest,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class InfraSrvApi(InfraSrvApiClient):
|
|
32
|
+
"""
|
|
33
|
+
用于与 InfraSrv 通信的 API 客户端 / API client for communicating with InfraSrv
|
|
34
|
+
继承自 InfraSrvApiClient,提供配置注入 / Inherits from InfraSrvApiClient with configuration injection
|
|
35
|
+
|
|
36
|
+
继承的方法 / Inherited methods:
|
|
37
|
+
- create_operation(request: OperationCreateRequest) -> Dict[str, Any]
|
|
38
|
+
- update_operation(request: OperationUpdateRequest) -> Dict[str, Any]
|
|
39
|
+
- get_operation(operation_id: str) -> Dict[str, Any]
|
|
40
|
+
- list_operations(service_name, status, limit, offset) -> Dict[str, Any]
|
|
41
|
+
|
|
42
|
+
新增方法(服务管理)/ New methods (service management):
|
|
43
|
+
- start_service(request: ServiceManagementRequest) -> Dict[str, Any]
|
|
44
|
+
- stop_service(request: ServiceManagementRequest) -> Dict[str, Any]
|
|
45
|
+
- restart_service(request: ServiceManagementRequest) -> Dict[str, Any]
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
# 注意:该类可能会在 LinglongConfig 初始化前被 import。
|
|
50
|
+
# Note: This class may be imported before LinglongConfig is initialized.
|
|
51
|
+
config_host = getattr(LinglongConfig, "INFRASRV_HOST", None)
|
|
52
|
+
infrasrv_host = os.getenv(
|
|
53
|
+
"INFRASRV_HOST",
|
|
54
|
+
config_host or "http://infrasrv.service:8080",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
base_url = f"{infrasrv_host}"
|
|
58
|
+
super().__init__(base_url)
|
|
59
|
+
self._internal_base_url = f"{infrasrv_host}/v1/infrasrv/internal"
|
|
60
|
+
self.service_mgmt_base_url = f"{infrasrv_host}/v1/infrasrv/service"
|
|
61
|
+
logger.info(f"InfraSrvApi initialized (opsbffsrv) with base_url: {base_url}")
|
|
62
|
+
logger.info(f"Service management base URL: {self.service_mgmt_base_url}")
|
|
63
|
+
logger.info(f"Internal base URL: {self._internal_base_url}")
|
|
64
|
+
|
|
65
|
+
async def create_operation(self, request: OperationCreateRequest):
|
|
66
|
+
"""创建操作记录(内部接口)/ Create operation via internal API."""
|
|
67
|
+
json_data = request.model_dump(mode="json")
|
|
68
|
+
return await self._make_request(
|
|
69
|
+
http.HTTPMethod.POST,
|
|
70
|
+
"/v1/infrasrv/internal/operation/create",
|
|
71
|
+
json_data=json_data,
|
|
72
|
+
response_models=self.OPERATION_RESPONSE_MODELS,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def update_operation(self, request: OperationUpdateRequest):
|
|
76
|
+
"""更新操作记录(内部接口)/ Update operation via internal API."""
|
|
77
|
+
json_data = request.model_dump(mode="json", exclude_none=True)
|
|
78
|
+
if request.started_at:
|
|
79
|
+
json_data["started_at"] = request.started_at.isoformat()
|
|
80
|
+
if request.completed_at:
|
|
81
|
+
json_data["completed_at"] = request.completed_at.isoformat()
|
|
82
|
+
return await self._make_request(
|
|
83
|
+
http.HTTPMethod.POST,
|
|
84
|
+
"/v1/infrasrv/internal/operation/update",
|
|
85
|
+
json_data=json_data,
|
|
86
|
+
response_models=self.OPERATION_RESPONSE_MODELS,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
async def get_operation(self, operation_id: str):
|
|
90
|
+
"""获取操作记录(内部接口)/ Get operation via internal API."""
|
|
91
|
+
return await self._make_request(
|
|
92
|
+
http.HTTPMethod.GET,
|
|
93
|
+
"/v1/infrasrv/internal/operation/get",
|
|
94
|
+
params={"operation_id": operation_id},
|
|
95
|
+
response_models=self.OPERATION_RESPONSE_MODELS,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def list_operations(
|
|
99
|
+
self,
|
|
100
|
+
service_name: Optional[str] = None,
|
|
101
|
+
status: Optional[str] = None,
|
|
102
|
+
limit: int = 50,
|
|
103
|
+
offset: int = 0,
|
|
104
|
+
):
|
|
105
|
+
"""列出操作记录(内部接口)/ List operations via internal API."""
|
|
106
|
+
params: dict[str, object] = {"limit": limit, "offset": offset}
|
|
107
|
+
if service_name:
|
|
108
|
+
params["service_name"] = service_name
|
|
109
|
+
if status:
|
|
110
|
+
params["status"] = status
|
|
111
|
+
return await self._make_request(
|
|
112
|
+
http.HTTPMethod.GET,
|
|
113
|
+
"/v1/infrasrv/internal/operation/list",
|
|
114
|
+
params=params,
|
|
115
|
+
response_models=self.OPERATION_LIST_MODELS,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
async def _make_service_management_request(
|
|
119
|
+
self,
|
|
120
|
+
endpoint: str,
|
|
121
|
+
request: ServiceManagementRequest
|
|
122
|
+
) -> ServiceManagementAPIResponse:
|
|
123
|
+
"""
|
|
124
|
+
调用 infrasrv 服务管理接口的通用方法 / Generic method for calling infrasrv service management interfaces
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
endpoint: API 端点(如 /start, /stop)/ API endpoint (e.g., /start, /stop)
|
|
128
|
+
request: 服务管理请求 / Service management request
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
ServiceManagementAPIResponse
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
HTTPException: 调用失败时抛出异常 / Raise exception on call failure
|
|
135
|
+
"""
|
|
136
|
+
url = f"{self.service_mgmt_base_url}{endpoint}"
|
|
137
|
+
json_data = request.model_dump(mode='json')
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
resp = await http_client.post(
|
|
141
|
+
url=url,
|
|
142
|
+
json=json_data,
|
|
143
|
+
timeout=HTTPClientConfig.INTERNAL_SERVICE_TIMEOUT
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if resp and resp.status == 200:
|
|
147
|
+
response_json = await resp.json()
|
|
148
|
+
logger.debug(f"Successfully called infrasrv service management API [{endpoint}]")
|
|
149
|
+
|
|
150
|
+
# 处理 infrasrv 的标准响应格式 / Handle infrasrv's standard response format
|
|
151
|
+
if isinstance(response_json, dict) and "success" in response_json:
|
|
152
|
+
if response_json.get("success"):
|
|
153
|
+
data = response_json.get("data", {})
|
|
154
|
+
# 将 dict 转换为 model / Convert dict to model
|
|
155
|
+
try:
|
|
156
|
+
return ServiceManagementAPIResponse(**data)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Failed to parse response as ServiceManagementAPIResponse: {e}")
|
|
159
|
+
raise HTTPException(
|
|
160
|
+
status_code=http.HTTPStatus.INTERNAL_SERVER_ERROR.value,
|
|
161
|
+
error_code=ErrorCode.SYSTEM_ERROR,
|
|
162
|
+
msg=f"Failed to parse response from infrasrv: {str(e)}"
|
|
163
|
+
)
|
|
164
|
+
else:
|
|
165
|
+
error_info = response_json.get("error", {})
|
|
166
|
+
error_msg = error_info.get("msg", "Unknown error") if isinstance(error_info, dict) else str(
|
|
167
|
+
error_info)
|
|
168
|
+
error_code = error_info.get("code", ErrorCode.SYSTEM_ERROR) if isinstance(error_info,
|
|
169
|
+
dict) else ErrorCode.SYSTEM_ERROR
|
|
170
|
+
logger.error(f"infrasrv returned error: {error_msg}")
|
|
171
|
+
raise HTTPException(
|
|
172
|
+
status_code=http.HTTPStatus.INTERNAL_SERVER_ERROR.value,
|
|
173
|
+
error_code=error_code,
|
|
174
|
+
msg=f"Infrasrv error: {error_msg}"
|
|
175
|
+
)
|
|
176
|
+
else:
|
|
177
|
+
logger.error(f"Unexpected response format from infrasrv: {response_json}")
|
|
178
|
+
raise HTTPException(
|
|
179
|
+
status_code=http.HTTPStatus.INTERNAL_SERVER_ERROR.value,
|
|
180
|
+
error_code=ErrorCode.SYSTEM_ERROR,
|
|
181
|
+
msg=f"Unexpected response format from infrasrv"
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
status_code = resp.status if resp else 500
|
|
185
|
+
error_msg = f"HTTP error: {status_code}"
|
|
186
|
+
logger.error(f"Failed to call infrasrv service management API [{endpoint}]: {error_msg}")
|
|
187
|
+
raise HTTPException(
|
|
188
|
+
status_code=status_code,
|
|
189
|
+
error_code=ErrorCode.NETWORK_ERROR,
|
|
190
|
+
msg=f"Failed to call infrasrv: {error_msg}"
|
|
191
|
+
)
|
|
192
|
+
except HTTPException:
|
|
193
|
+
raise
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"Error calling infrasrv service management API [{endpoint}]: {e}", exc_info=True)
|
|
196
|
+
raise HTTPException(
|
|
197
|
+
status_code=http.HTTPStatus.INTERNAL_SERVER_ERROR.value,
|
|
198
|
+
error_code=ErrorCode.SYSTEM_ERROR,
|
|
199
|
+
msg=f"Error calling infrasrv: {str(e)}"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
async def start_service(self, request: ServiceManagementRequest) -> ServiceManagementAPIResponse:
|
|
203
|
+
"""
|
|
204
|
+
调用 infrasrv 启动服务 / Call infrasrv to start service
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
request: 服务管理请求 / Service management request
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
ServiceManagementAPIResponse 或 None / ServiceManagementAPIResponse or None
|
|
211
|
+
"""
|
|
212
|
+
logger.info(
|
|
213
|
+
f"[opsbffsrv → infrasrv] Starting service: {request.service_name}, operation_id: {request.operation_id}")
|
|
214
|
+
return await self._make_service_management_request("/start", request)
|
|
215
|
+
|
|
216
|
+
async def stop_service(self, request: ServiceManagementRequest) -> ServiceManagementAPIResponse:
|
|
217
|
+
"""
|
|
218
|
+
调用 infrasrv 停止服务 / Call infrasrv to stop service
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
request: 服务管理请求 / Service management request
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
操作结果 / Operation result
|
|
225
|
+
"""
|
|
226
|
+
logger.info(
|
|
227
|
+
f"[opsbffsrv → infrasrv] Stopping service: {request.service_name}, operation_id: {request.operation_id}")
|
|
228
|
+
return await self._make_service_management_request("/stop", request)
|
|
229
|
+
|
|
230
|
+
async def restart_service(self, request: ServiceManagementRequest) -> ServiceManagementAPIResponse:
|
|
231
|
+
"""
|
|
232
|
+
调用 infrasrv 重启服务 / Call infrasrv to restart service
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
request: 服务管理请求 / Service management request
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
ServiceManagementAPIResponse 或 None / ServiceManagementAPIResponse or None
|
|
239
|
+
"""
|
|
240
|
+
logger.info(
|
|
241
|
+
f"[opsbffsrv → infrasrv] Restarting service: {request.service_name}, operation_id: {request.operation_id}")
|
|
242
|
+
return await self._make_service_management_request("/restart", request)
|
|
File without changes
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""图形验证码生成 / Captcha image generation using Pillow."""
|
|
2
|
+
import base64
|
|
3
|
+
import io
|
|
4
|
+
import random
|
|
5
|
+
import secrets
|
|
6
|
+
import string
|
|
7
|
+
|
|
8
|
+
from nanoid import generate as nanoid_generate
|
|
9
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
10
|
+
|
|
11
|
+
# 排除易混淆字符 0/O/I/1
|
|
12
|
+
_CHARS = "".join(c for c in string.ascii_uppercase + string.digits if c not in "OI01")
|
|
13
|
+
_CAPTCHA_LENGTH = 6
|
|
14
|
+
_IMG_WIDTH = 200
|
|
15
|
+
_IMG_HEIGHT = 60
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def generate_captcha() -> tuple[str, str, str]:
|
|
19
|
+
"""生成图形验证码 / Generate a captcha: (captcha_id, answer, image_base64).
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
captcha_id: 唯一标识符
|
|
23
|
+
answer: 验证码答案(大写)
|
|
24
|
+
image_base64: data URI 格式的 PNG 图片
|
|
25
|
+
"""
|
|
26
|
+
captcha_id = nanoid_generate(size=16)
|
|
27
|
+
# 验证码答案必须用 CSPRNG(不可预测),避免被预测/暴力绕过。
|
|
28
|
+
# Captcha answer must use a CSPRNG so it cannot be predicted.
|
|
29
|
+
# (The visual noise/jitter below stays on `random` — not security-relevant.)
|
|
30
|
+
answer = "".join(secrets.choice(_CHARS) for _ in range(_CAPTCHA_LENGTH))
|
|
31
|
+
|
|
32
|
+
image = Image.new("RGB", (_IMG_WIDTH, _IMG_HEIGHT), color=(255, 255, 255))
|
|
33
|
+
draw = ImageDraw.Draw(image)
|
|
34
|
+
|
|
35
|
+
# 尝试使用系统字体,不可用时使用默认字体
|
|
36
|
+
try:
|
|
37
|
+
font = ImageFont.truetype("Arial", 32)
|
|
38
|
+
except (OSError, IOError):
|
|
39
|
+
font = ImageFont.load_default()
|
|
40
|
+
|
|
41
|
+
# 绘制字符(带随机偏移和颜色)
|
|
42
|
+
x_offset = 15
|
|
43
|
+
for ch in answer:
|
|
44
|
+
color = (random.randint(0, 150), random.randint(0, 150), random.randint(0, 150))
|
|
45
|
+
y_offset = random.randint(5, 15)
|
|
46
|
+
draw.text((x_offset, y_offset), ch, fill=color, font=font)
|
|
47
|
+
x_offset += 28
|
|
48
|
+
|
|
49
|
+
# 绘制干扰线
|
|
50
|
+
for _ in range(5):
|
|
51
|
+
x1, y1 = random.randint(0, _IMG_WIDTH), random.randint(0, _IMG_HEIGHT)
|
|
52
|
+
x2, y2 = random.randint(0, _IMG_WIDTH), random.randint(0, _IMG_HEIGHT)
|
|
53
|
+
color = (random.randint(100, 200), random.randint(100, 200), random.randint(100, 200))
|
|
54
|
+
draw.line([(x1, y1), (x2, y2)], fill=color, width=1)
|
|
55
|
+
|
|
56
|
+
# 绘制噪点
|
|
57
|
+
for _ in range(100):
|
|
58
|
+
x, y = random.randint(0, _IMG_WIDTH - 1), random.randint(0, _IMG_HEIGHT - 1)
|
|
59
|
+
color = (random.randint(100, 200), random.randint(100, 200), random.randint(100, 200))
|
|
60
|
+
draw.point((x, y), fill=color)
|
|
61
|
+
|
|
62
|
+
buf = io.BytesIO()
|
|
63
|
+
image.save(buf, format="PNG")
|
|
64
|
+
b64 = base64.b64encode(buf.getvalue()).decode("utf-8")
|
|
65
|
+
image_base64 = f"data:image/png;base64,{b64}"
|
|
66
|
+
|
|
67
|
+
return captcha_id, answer, image_base64
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""bcrypt 密码哈希与验证 / Password hashing and verification with bcrypt."""
|
|
2
|
+
import bcrypt
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def hash_password(plain: str) -> str:
|
|
6
|
+
"""生成 bcrypt 密码哈希 / Generate bcrypt hash for a plaintext password."""
|
|
7
|
+
return bcrypt.hashpw(plain.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def verify_password(plain: str, hashed: str) -> bool:
|
|
11
|
+
"""校验明文密码与 bcrypt 哈希 / Verify plaintext password against bcrypt hash."""
|
|
12
|
+
return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8"))
|