fix: clarify reconciliation and preserve container image tags
This commit is contained in:
@@ -13,9 +13,11 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"yms-daemon/internal/backendstatus"
|
||||
"yms-daemon/internal/backendupdate"
|
||||
"yms-daemon/internal/containerengine"
|
||||
"yms-daemon/internal/daemonapi"
|
||||
@@ -125,6 +127,27 @@ func run(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Wr
|
||||
fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State)
|
||||
}
|
||||
return 0
|
||||
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
|
||||
case "help", "-h", "--help":
|
||||
writeUsage(stdout)
|
||||
return 0
|
||||
@@ -235,6 +258,37 @@ func parseRestartArgs(arguments []string, output io.Writer) (restartArguments, e
|
||||
return restartArguments{service: *service, quite: *quite}, nil
|
||||
}
|
||||
|
||||
// diagnosisArguments 保存 status/doctor/reconcile 子命令解析后的参数。
|
||||
type diagnosisArguments struct {
|
||||
// service 目标服务名,当前仅接受 backend。
|
||||
service string
|
||||
// apply 为真时表示 reconcile 执行自动修复,其余命令不接受该选项。
|
||||
apply bool
|
||||
}
|
||||
|
||||
// parseDiagnosisArgs 解析 status/doctor/reconcile 子命令的 flag 参数并做业务校验。
|
||||
// 它要求 --service 必须为 backend,且不接受任何位置参数;--apply 仅允许 reconcile 使用。
|
||||
// 校验失败时向 output 写入原因并返回 error。
|
||||
func parseDiagnosisArgs(command string, arguments []string, output io.Writer) (diagnosisArguments, error) {
|
||||
flags := flag.NewFlagSet(command, flag.ContinueOnError)
|
||||
flags.SetOutput(output)
|
||||
service := flags.String("service", "", "service to diagnose")
|
||||
apply := flags.Bool("apply", false, "apply automatic fixes (reconcile only)")
|
||||
if err := flags.Parse(arguments); err != nil {
|
||||
return diagnosisArguments{}, err
|
||||
}
|
||||
if flags.NArg() != 0 {
|
||||
return diagnosisArguments{}, errors.New(command + " does not accept positional arguments")
|
||||
}
|
||||
if *service != serviceBackend {
|
||||
return diagnosisArguments{}, errors.New("--service currently accepts only backend")
|
||||
}
|
||||
if *apply && command != "reconcile" {
|
||||
return diagnosisArguments{}, errors.New("--apply requires reconcile")
|
||||
}
|
||||
return diagnosisArguments{service: *service, apply: *apply}, nil
|
||||
}
|
||||
|
||||
// runServe 以守护进程服务端角色运行:初始化日志、获取进程锁、加载部署配置、
|
||||
// 打开事务存储与协调器、构建 Nginx 控制器与后端更新器,最后通过 daemonserver 开始服务。
|
||||
// 它使用具名返回 result 以便在各资源清理阶段合并所有 Close 错误,最终返回服务端的运行错误。
|
||||
@@ -273,6 +327,7 @@ func runServe(ctx context.Context) (result error) {
|
||||
}
|
||||
httpClient := &http.Client{Timeout: 5 * time.Second}
|
||||
var updater *backendupdate.Updater
|
||||
var diagnoser *backendstatus.Diagnoser
|
||||
switch config.Backend.Type {
|
||||
case deploymentconfig.BackendTypeNative:
|
||||
releaseStore, err := filestore.New(config.Backend.ReleaseDir)
|
||||
@@ -284,12 +339,18 @@ func runServe(ctx context.Context) (result error) {
|
||||
return err
|
||||
}
|
||||
updater, err = backendupdate.New(config, runtimepaths.WorkRoot, store, coordinator, releaseStore, units, gateway, httpClient, logger)
|
||||
if err == nil {
|
||||
diagnoser, err = backendstatus.New(config, store, coordinator, nil, units, gateway)
|
||||
}
|
||||
case deploymentconfig.BackendTypeContainer:
|
||||
var engine *containerengine.MobyEngine
|
||||
engine, err = containerengine.NewMobyEngine()
|
||||
if err == nil {
|
||||
defer func() { result = errors.Join(result, engine.Close()) }()
|
||||
updater, err = backendupdate.NewContainer(config, runtimepaths.WorkRoot, store, coordinator, engine, gateway, httpClient, logger)
|
||||
if err == nil {
|
||||
diagnoser, err = backendstatus.New(config, store, coordinator, engine, nil, gateway)
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("unsupported backend.type %q", config.Backend.Type)
|
||||
@@ -297,7 +358,7 @@ func runServe(ctx context.Context) (result error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server, err := daemonserver.New(runtimepaths.Socket, updater, logger)
|
||||
server, err := daemonserver.New(runtimepaths.Socket, updater, diagnoser, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -312,6 +373,9 @@ func writeUsage(output io.Writer) {
|
||||
fmt.Fprintln(output, " ymsctl update --service backend --container-image <image-ref> [--no-start-log] [--quite]")
|
||||
fmt.Fprintln(output, " ymsctl restart --service backend [--quite]")
|
||||
fmt.Fprintln(output, " ymsctl list [--limit <n>] [--service backend] [--state <state>] [--json]")
|
||||
fmt.Fprintln(output, " ymsctl status --service backend")
|
||||
fmt.Fprintln(output, " ymsctl doctor --service backend")
|
||||
fmt.Fprintln(output, " ymsctl reconcile --service backend [--apply]")
|
||||
}
|
||||
|
||||
// writeUpdateProgress 把一条进度事件格式化写入 output:状态字段左对齐占 14 列,
|
||||
@@ -324,6 +388,51 @@ func writeUpdateProgress(output io.Writer, event daemonapi.Response) {
|
||||
fmt.Fprintf(output, "%-14s %s\n", state, event.Message)
|
||||
}
|
||||
|
||||
// writeDiagnosis 把诊断结果格式化写入 output。
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
shown++
|
||||
fmt.Fprintf(output, "%-8s %s\n", strings.ToUpper(item.Level), item.Message)
|
||||
if item.Action != "" {
|
||||
fmt.Fprintf(output, "%-8s %s\n", "action", item.Action)
|
||||
}
|
||||
}
|
||||
if shown == 0 {
|
||||
fmt.Fprintln(output, "(no items)")
|
||||
} else if command == "reconcile" && !diagnosis.RepairApplied {
|
||||
fmt.Fprintln(output, "PLAN 仅生成修复计划,未执行;如需执行请追加 --apply")
|
||||
}
|
||||
}
|
||||
|
||||
// runList 执行 list 子命令:解析过滤参数,打开事务存储读取最近事务,
|
||||
// 按是否指定 --json 决定以稳定 JSON 或制表符分隔的表格形式输出到 stdout。
|
||||
// 它支持 --limit、--service、--state、--json 四个选项,返回查询或输出阶段的错误。
|
||||
|
||||
Reference in New Issue
Block a user