diff --git a/internal/backendstatus/container.go b/internal/backendstatus/container.go index 678dee1..d47ba36 100644 --- a/internal/backendstatus/container.go +++ b/internal/backendstatus/container.go @@ -28,6 +28,8 @@ const ( codeDeploymentConfigMismatch = "deployment_record_config_mismatch" ) +const inactiveSlotContainerMessage = "非活动槽位容器 " + // diagnoseContainer 对 container 后端做只读诊断。 // 事实来源是 backend_container_deployment 单例记录;现场状态由容器引擎与宿主 Nginx 提供。 func (d *Diagnoser) diagnoseContainer(ctx context.Context) (daemonapi.Diagnosis, error) { @@ -143,7 +145,7 @@ func (d *Diagnoser) containerFindings(ctx context.Context, deployment transactio findings = append(findings, finding{ level: daemonapi.DiagnosisLevelOK, code: codeInactiveContainerOK, - message: "非活动槽位容器 " + inactiveSlot.ContainerName + " 不存在", + message: inactiveSlotContainerMessage + inactiveSlot.ContainerName + " 不存在", }) case inactiveErr != nil: return nil, fmt.Errorf("inspect inactive backend container %s: %w", inactiveSlot.ContainerName, inactiveErr) @@ -151,13 +153,13 @@ func (d *Diagnoser) containerFindings(ctx context.Context, deployment transactio findings = append(findings, finding{ level: daemonapi.DiagnosisLevelDrift, code: codeInactiveContainerRunning, - message: "非活动槽位容器 " + inactiveSlot.ContainerName + " 意外处于运行状态", + message: inactiveSlotContainerMessage + inactiveSlot.ContainerName + " 意外处于运行状态", }) default: findings = append(findings, finding{ level: daemonapi.DiagnosisLevelFixable, code: codeInactiveContainerResidual, - message: "非活动槽位容器 " + inactiveSlot.ContainerName + " 已停止,属于残留", + message: inactiveSlotContainerMessage + inactiveSlot.ContainerName + " 已停止,属于残留", action: "移除残留的已停止容器 " + inactiveSlot.ContainerName, fix: &fixAction{kind: fixRemoveContainer, container: inactiveSlot.ContainerName}, }) diff --git a/main.go b/main.go index b29da28..2214a33 100644 --- a/main.go +++ b/main.go @@ -67,87 +67,13 @@ func run(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Wr } switch arguments[0] { case "update": - request, err := parseUpdateArgs(arguments[1:], stderr) - if err != nil { - fmt.Fprintln(stderr, err) - return 2 - } - var progress func(daemonapi.Response) - if !request.quite { - progress = func(event daemonapi.Response) { - writeUpdateProgress(stdout, event) - } - } - var response daemonapi.Response - if request.inputType == daemonapi.InputTypeContainerImage { - response, err = daemonclient.UpdateContainerImage(ctx, runtimepaths.Socket, request.service, request.imageReference, request.startLog, progress) - } else { - response, err = daemonclient.Update(ctx, runtimepaths.Socket, request.service, request.inputType, request.file, progress) - } - if err != nil { - if response.TransactionID != "" { - fmt.Fprintf(stderr, "transaction=%s state=%s error=%v\n", response.TransactionID, response.State, err) - } else { - fmt.Fprintln(stderr, "ymsctl update failed:", err) - } - return 1 - } - if !request.quite { - fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State) - } - return 0 + return runUpdate(ctx, arguments[1:], stdout, stderr) case "list": - if err := runList(ctx, arguments[1:], stdout, stderr); err != nil { - fmt.Fprintln(stderr, "ymsctl list failed:", err) - return 1 - } - return 0 + return runListCommand(ctx, arguments[1:], stdout, stderr) case "restart": - request, err := parseRestartArgs(arguments[1:], stderr) - if err != nil { - fmt.Fprintln(stderr, err) - return 2 - } - var progress func(daemonapi.Response) - if !request.quite { - progress = func(event daemonapi.Response) { - writeUpdateProgress(stdout, event) - } - } - response, err := daemonclient.Restart(ctx, runtimepaths.Socket, request.service, progress) - if err != nil { - if response.TransactionID != "" { - fmt.Fprintf(stderr, "transaction=%s state=%s error=%v\n", response.TransactionID, response.State, err) - } else { - fmt.Fprintln(stderr, "ymsctl restart failed:", err) - } - return 1 - } - if !request.quite { - fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State) - } - return 0 + return runRestart(ctx, arguments[1:], stdout, stderr) case "status", "doctor", "reconcile": - request, err := parseDiagnosisArgs(arguments[0], arguments[1:], stderr) - if err != nil { - fmt.Fprintln(stderr, err) - return 2 - } - var diagnosis daemonapi.Diagnosis - switch arguments[0] { - case "status": - diagnosis, err = daemonclient.Status(ctx, runtimepaths.Socket, request.service) - case "doctor": - diagnosis, err = daemonclient.Doctor(ctx, runtimepaths.Socket, request.service) - case "reconcile": - diagnosis, err = daemonclient.Reconcile(ctx, runtimepaths.Socket, request.service, request.apply) - } - if err != nil { - fmt.Fprintf(stderr, "ymsctl %s failed: %v\n", arguments[0], err) - return 1 - } - writeDiagnosis(stdout, arguments[0], diagnosis) - return 0 + return runDiagnosis(ctx, arguments[0], arguments[1:], stdout, stderr) case "help", "-h", "--help": writeUsage(stdout) return 0 @@ -158,6 +84,96 @@ func run(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Wr } } +func runUpdate(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Writer) int { + request, err := parseUpdateArgs(arguments, stderr) + if err != nil { + fmt.Fprintln(stderr, err) + return 2 + } + var progress func(daemonapi.Response) + if !request.quite { + progress = func(event daemonapi.Response) { + writeUpdateProgress(stdout, event) + } + } + var response daemonapi.Response + if request.inputType == daemonapi.InputTypeContainerImage { + response, err = daemonclient.UpdateContainerImage(ctx, runtimepaths.Socket, request.service, request.imageReference, request.startLog, progress) + } else { + response, err = daemonclient.Update(ctx, runtimepaths.Socket, request.service, request.inputType, request.file, progress) + } + if err != nil { + if response.TransactionID != "" { + fmt.Fprintf(stderr, "transaction=%s state=%s error=%v\n", response.TransactionID, response.State, err) + } else { + fmt.Fprintln(stderr, "ymsctl update failed:", err) + } + return 1 + } + if !request.quite { + fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State) + } + return 0 +} + +func runListCommand(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Writer) int { + if err := runList(ctx, arguments, stdout, stderr); err != nil { + fmt.Fprintln(stderr, "ymsctl list failed:", err) + return 1 + } + return 0 +} + +func runRestart(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Writer) int { + request, err := parseRestartArgs(arguments, stderr) + if err != nil { + fmt.Fprintln(stderr, err) + return 2 + } + var progress func(daemonapi.Response) + if !request.quite { + progress = func(event daemonapi.Response) { + writeUpdateProgress(stdout, event) + } + } + response, err := daemonclient.Restart(ctx, runtimepaths.Socket, request.service, progress) + if err != nil { + if response.TransactionID != "" { + fmt.Fprintf(stderr, "transaction=%s state=%s error=%v\n", response.TransactionID, response.State, err) + } else { + fmt.Fprintln(stderr, "ymsctl restart failed:", err) + } + return 1 + } + if !request.quite { + fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State) + } + return 0 +} + +func runDiagnosis(ctx context.Context, command string, arguments []string, stdout io.Writer, stderr io.Writer) int { + request, err := parseDiagnosisArgs(command, arguments, stderr) + if err != nil { + fmt.Fprintln(stderr, err) + return 2 + } + var diagnosis daemonapi.Diagnosis + switch command { + case "status": + diagnosis, err = daemonclient.Status(ctx, runtimepaths.Socket, request.service) + case "doctor": + diagnosis, err = daemonclient.Doctor(ctx, runtimepaths.Socket, request.service) + case "reconcile": + diagnosis, err = daemonclient.Reconcile(ctx, runtimepaths.Socket, request.service, request.apply) + } + if err != nil { + fmt.Fprintf(stderr, "ymsctl %s failed: %v\n", command, err) + return 1 + } + writeDiagnosis(stdout, command, diagnosis) + return 0 +} + // updateArguments 保存 update 子命令解析后的参数。 type updateArguments struct { // service 目标服务名,当前仅接受 backend。 @@ -392,33 +408,15 @@ func writeUpdateProgress(output io.Writer, event daemonapi.Response) { // status 只展示需要关注的漂移与可修复项,doctor 展示全部诊断项, // reconcile 只展示可自动修复项作为修复计划。 func writeDiagnosis(output io.Writer, command string, diagnosis daemonapi.Diagnosis) { - state := "HEALTHY" - if !diagnosis.Healthy { - state = "DRIFT" - } else { - for _, item := range diagnosis.Items { - if item.Level == daemonapi.DiagnosisLevelFixable { - state = "REPAIRABLE" - break - } - } - } - fmt.Fprintf(output, "service=%s type=%s status=%s\n", diagnosis.Service, diagnosis.Type, state) + fmt.Fprintf(output, "service=%s type=%s status=%s\n", diagnosis.Service, diagnosis.Type, diagnosisState(diagnosis)) if diagnosis.RepairApplied { fmt.Fprintf(output, "APPLIED 对账修复已执行 transaction=%s\n", diagnosis.RepairTransactionID) fmt.Fprintln(output, "RESULT 现场已重新核对") } shown := 0 for _, item := range diagnosis.Items { - switch command { - case "status": - if item.Level == daemonapi.DiagnosisLevelOK { - continue - } - case "reconcile": - if item.Level != daemonapi.DiagnosisLevelFixable { - continue - } + if !showDiagnosisItem(command, item.Level) { + continue } shown++ fmt.Fprintf(output, "%-8s %s\n", strings.ToUpper(item.Level), item.Message) @@ -433,6 +431,29 @@ func writeDiagnosis(output io.Writer, command string, diagnosis daemonapi.Diagno } } +func diagnosisState(diagnosis daemonapi.Diagnosis) string { + if !diagnosis.Healthy { + return "DRIFT" + } + for _, item := range diagnosis.Items { + if item.Level == daemonapi.DiagnosisLevelFixable { + return "REPAIRABLE" + } + } + return "HEALTHY" +} + +func showDiagnosisItem(command string, level string) bool { + switch command { + case "status": + return level != daemonapi.DiagnosisLevelOK + case "reconcile": + return level == daemonapi.DiagnosisLevelFixable + default: + return true + } +} + // runList 执行 list 子命令:解析过滤参数,打开事务存储读取最近事务, // 按是否指定 --json 决定以稳定 JSON 或制表符分隔的表格形式输出到 stdout。 // 它支持 --limit、--service、--state、--json 四个选项,返回查询或输出阶段的错误。