From 1aa984c7e524b51e5d1951cd5dc89f4befb704a9 Mon Sep 17 00:00:00 2001 From: mac Date: Thu, 30 Jul 2026 16:09:13 +0800 Subject: [PATCH] feat(delivery): sync mysql service ledger and credentials --- ansible/mysql-inspect.yml | 111 ++++ frontend/components.d.ts | 3 - frontend/src/api/delivery.ts | 6 + frontend/src/components/Layout/AppSidebar.vue | 54 +- frontend/src/views/service/Catalog.vue | 4 +- frontend/src/views/service/Management.vue | 321 +++++++---- server/.env.example | 7 + server/go.mod | 1 + server/go.sum | 64 +++ server/internal/config/config.go | 278 +++++----- server/internal/handler/clouddm.go | 169 ++++++ server/internal/handler/delivery.go | 23 + server/internal/router/router.go | 1 + server/internal/service/delivery.go | 506 +++++++++++++++++- server/internal/service/delivery_test.go | 36 ++ 15 files changed, 1322 insertions(+), 262 deletions(-) create mode 100644 ansible/mysql-inspect.yml diff --git a/ansible/mysql-inspect.yml b/ansible/mysql-inspect.yml new file mode 100644 index 0000000..25eac56 --- /dev/null +++ b/ansible/mysql-inspect.yml @@ -0,0 +1,111 @@ +--- +- name: Inspect native MySQL delivery instances + hosts: "{{ target_hosts | default('all') }}" + become: true + gather_facts: false + vars: + mysql_instances: "{{ mysql_instances | default([]) }}" + + tasks: + - name: Initialize host inspection result list + ansible.builtin.set_fact: + mysql_inspect_results: [] + + - name: Inspect instances assigned to this host + ansible.builtin.shell: + cmd: | + set -euo pipefail + instance="{{ item.instance_name }}" + service_name="{{ item.service_name | default('mysql-delivery@' ~ item.instance_name ~ '.service') }}" + config_file="{{ item.config_file | default('/etc/mysql/mysql-delivery/' ~ item.instance_name ~ '.cnf') }}" + expected_data_dir="{{ item.data_dir | default('') }}" + expected_base_dir="{{ item.base_dir | default('') }}" + install_dir="{{ item.install_dir | default('/opt/mysql-delivery/' ~ item.instance_name) }}" + run_dir="{{ item.run_dir | default('/run/mysql-delivery-' ~ item.instance_name) }}" + port="{{ item.port | default(0) }}" + + service_state="$(systemctl is-active "$service_name" 2>/dev/null || true)" + config_exists=false + install_exists=false + base_exists=false + data_exists=false + run_exists=false + port_listening=false + actual_data_dir="" + + [ -e "$config_file" ] && config_exists=true + [ -e "$install_dir" ] && install_exists=true + [ -n "$expected_base_dir" ] && [ -e "$expected_base_dir" ] && base_exists=true + [ -n "$expected_data_dir" ] && [ -e "$expected_data_dir" ] && data_exists=true + [ -e "$run_dir" ] && run_exists=true + if [ "$config_exists" = true ]; then + actual_data_dir="$(awk -F= '/^[[:space:]]*datadir[[:space:]]*=/{gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2); print $2; exit}' "$config_file" || true)" + fi + if [ -n "$port" ] && [ "$port" != "0" ] && ss -lntH "sport = :$port" | grep -q .; then + port_listening=true + fi + + status=unknown + if [ "$service_state" = "active" ] && [ "$port_listening" = true ] && [ "$data_exists" = true ] && [ -e "$expected_data_dir/auto.cnf" ]; then + status=running + elif [ "$config_exists" = true ] && [ -n "$actual_data_dir" ] && [ -n "$expected_data_dir" ] && [ "$actual_data_dir" != "$expected_data_dir" ]; then + status=moved + elif [ "$service_state" != "active" ] && [ "$config_exists" = false ] && [ "$install_exists" = false ] && [ "$base_exists" = false ] && [ "$run_exists" = false ]; then + status=deleted + elif [ "$config_exists" = true ] || [ "$install_exists" = true ] || [ "$base_exists" = true ] || [ "$data_exists" = true ]; then + status=stopped + fi + + export X_INSTANCE="$instance" + export X_STATUS="$status" + export X_SERVICE_STATE="$service_state" + export X_PORT_LISTENING="$port_listening" + export X_CONFIG_EXISTS="$config_exists" + export X_INSTALL_EXISTS="$install_exists" + export X_BASE_EXISTS="$base_exists" + export X_DATA_EXISTS="$data_exists" + export X_RUN_EXISTS="$run_exists" + export X_EXPECTED_DATA_DIR="$expected_data_dir" + export X_ACTUAL_DATA_DIR="$actual_data_dir" + python3 - < + (item.target_host | default('')) == inventory_hostname or + (item.host | default('')) == (ansible_host | default('')) + register: mysql_inspect_shell + changed_when: false + + - name: Merge host inspection results + ansible.builtin.set_fact: + mysql_inspect_results: "{{ mysql_inspect_results + [item.stdout | from_json] }}" + loop: "{{ mysql_inspect_shell.results | default([]) }}" + when: + - item is not skipped + - item.stdout is defined + - item.stdout | length > 0 + + - name: Emit machine-readable inspection results + ansible.builtin.debug: + msg: "XINFRA_MYSQL_INSPECT_RESULT_B64={{ mysql_inspect_results | to_json | b64encode }}" diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 8fd3208..aca2463 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -15,9 +15,6 @@ declare module 'vue' { ElAutocomplete: typeof import('element-plus/es')['ElAutocomplete'] ElButton: typeof import('element-plus/es')['ElButton'] ElDialog: typeof import('element-plus/es')['ElDialog'] - ElDropdown: typeof import('element-plus/es')['ElDropdown'] - ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] - ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElForm: typeof import('element-plus/es')['ElForm'] ElFormItem: typeof import('element-plus/es')['ElFormItem'] ElIcon: typeof import('element-plus/es')['ElIcon'] diff --git a/frontend/src/api/delivery.ts b/frontend/src/api/delivery.ts index 668aecb..f515306 100644 --- a/frontend/src/api/delivery.ts +++ b/frontend/src/api/delivery.ts @@ -149,6 +149,12 @@ export const deliveryApi = { return Array.isArray(data.items) ? data.items : [] }, + async syncMySQLServices(businessLineId: number): Promise { + await authRequest(`/auth/api/v1/delivery/business-lines/${businessLineId}/mysql-services/sync`, { + method: 'POST', + }) + }, + async getTask(taskId: string): Promise<{ task: DeliveryTask; events: TaskEvent[] }> { const data = await authRequest(`/auth/api/v1/delivery/tasks/${encodeURIComponent(taskId)}`) return { diff --git a/frontend/src/components/Layout/AppSidebar.vue b/frontend/src/components/Layout/AppSidebar.vue index a820f0e..0d9cc8c 100644 --- a/frontend/src/components/Layout/AppSidebar.vue +++ b/frontend/src/components/Layout/AppSidebar.vue @@ -12,19 +12,19 @@ ▤基础服务 - ▧任务日志2 + ▧任务日志{{ sidebarBadges.taskLogs }}