refactor: split CLI diagnosis and status rendering
This commit is contained in:
@@ -28,6 +28,8 @@ const (
|
|||||||
codeDeploymentConfigMismatch = "deployment_record_config_mismatch"
|
codeDeploymentConfigMismatch = "deployment_record_config_mismatch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const inactiveSlotContainerMessage = "非活动槽位容器 "
|
||||||
|
|
||||||
// diagnoseContainer 对 container 后端做只读诊断。
|
// diagnoseContainer 对 container 后端做只读诊断。
|
||||||
// 事实来源是 backend_container_deployment 单例记录;现场状态由容器引擎与宿主 Nginx 提供。
|
// 事实来源是 backend_container_deployment 单例记录;现场状态由容器引擎与宿主 Nginx 提供。
|
||||||
func (d *Diagnoser) diagnoseContainer(ctx context.Context) (daemonapi.Diagnosis, error) {
|
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{
|
findings = append(findings, finding{
|
||||||
level: daemonapi.DiagnosisLevelOK,
|
level: daemonapi.DiagnosisLevelOK,
|
||||||
code: codeInactiveContainerOK,
|
code: codeInactiveContainerOK,
|
||||||
message: "非活动槽位容器 " + inactiveSlot.ContainerName + " 不存在",
|
message: inactiveSlotContainerMessage + inactiveSlot.ContainerName + " 不存在",
|
||||||
})
|
})
|
||||||
case inactiveErr != nil:
|
case inactiveErr != nil:
|
||||||
return nil, fmt.Errorf("inspect inactive backend container %s: %w", inactiveSlot.ContainerName, inactiveErr)
|
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{
|
findings = append(findings, finding{
|
||||||
level: daemonapi.DiagnosisLevelDrift,
|
level: daemonapi.DiagnosisLevelDrift,
|
||||||
code: codeInactiveContainerRunning,
|
code: codeInactiveContainerRunning,
|
||||||
message: "非活动槽位容器 " + inactiveSlot.ContainerName + " 意外处于运行状态",
|
message: inactiveSlotContainerMessage + inactiveSlot.ContainerName + " 意外处于运行状态",
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
findings = append(findings, finding{
|
findings = append(findings, finding{
|
||||||
level: daemonapi.DiagnosisLevelFixable,
|
level: daemonapi.DiagnosisLevelFixable,
|
||||||
code: codeInactiveContainerResidual,
|
code: codeInactiveContainerResidual,
|
||||||
message: "非活动槽位容器 " + inactiveSlot.ContainerName + " 已停止,属于残留",
|
message: inactiveSlotContainerMessage + inactiveSlot.ContainerName + " 已停止,属于残留",
|
||||||
action: "移除残留的已停止容器 " + inactiveSlot.ContainerName,
|
action: "移除残留的已停止容器 " + inactiveSlot.ContainerName,
|
||||||
fix: &fixAction{kind: fixRemoveContainer, container: inactiveSlot.ContainerName},
|
fix: &fixAction{kind: fixRemoveContainer, container: inactiveSlot.ContainerName},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -67,7 +67,25 @@ func run(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Wr
|
|||||||
}
|
}
|
||||||
switch arguments[0] {
|
switch arguments[0] {
|
||||||
case "update":
|
case "update":
|
||||||
request, err := parseUpdateArgs(arguments[1:], stderr)
|
return runUpdate(ctx, arguments[1:], stdout, stderr)
|
||||||
|
case "list":
|
||||||
|
return runListCommand(ctx, arguments[1:], stdout, stderr)
|
||||||
|
case "restart":
|
||||||
|
return runRestart(ctx, arguments[1:], stdout, stderr)
|
||||||
|
case "status", "doctor", "reconcile":
|
||||||
|
return runDiagnosis(ctx, arguments[0], arguments[1:], stdout, stderr)
|
||||||
|
case "help", "-h", "--help":
|
||||||
|
writeUsage(stdout)
|
||||||
|
return 0
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(stderr, "unknown command: %s\n", arguments[0])
|
||||||
|
writeUsage(stderr)
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runUpdate(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Writer) int {
|
||||||
|
request, err := parseUpdateArgs(arguments, stderr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(stderr, err)
|
fmt.Fprintln(stderr, err)
|
||||||
return 2
|
return 2
|
||||||
@@ -96,14 +114,18 @@ 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)
|
fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
case "list":
|
}
|
||||||
if err := runList(ctx, arguments[1:], stdout, stderr); err != nil {
|
|
||||||
|
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)
|
fmt.Fprintln(stderr, "ymsctl list failed:", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
case "restart":
|
}
|
||||||
request, err := parseRestartArgs(arguments[1:], stderr)
|
|
||||||
|
func runRestart(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Writer) int {
|
||||||
|
request, err := parseRestartArgs(arguments, stderr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(stderr, err)
|
fmt.Fprintln(stderr, err)
|
||||||
return 2
|
return 2
|
||||||
@@ -127,14 +149,16 @@ 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)
|
fmt.Fprintf(stdout, "transaction=%s state=%s\n", response.TransactionID, response.State)
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
case "status", "doctor", "reconcile":
|
}
|
||||||
request, err := parseDiagnosisArgs(arguments[0], arguments[1:], stderr)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
fmt.Fprintln(stderr, err)
|
fmt.Fprintln(stderr, err)
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
var diagnosis daemonapi.Diagnosis
|
var diagnosis daemonapi.Diagnosis
|
||||||
switch arguments[0] {
|
switch command {
|
||||||
case "status":
|
case "status":
|
||||||
diagnosis, err = daemonclient.Status(ctx, runtimepaths.Socket, request.service)
|
diagnosis, err = daemonclient.Status(ctx, runtimepaths.Socket, request.service)
|
||||||
case "doctor":
|
case "doctor":
|
||||||
@@ -143,19 +167,11 @@ func run(ctx context.Context, arguments []string, stdout io.Writer, stderr io.Wr
|
|||||||
diagnosis, err = daemonclient.Reconcile(ctx, runtimepaths.Socket, request.service, request.apply)
|
diagnosis, err = daemonclient.Reconcile(ctx, runtimepaths.Socket, request.service, request.apply)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(stderr, "ymsctl %s failed: %v\n", arguments[0], err)
|
fmt.Fprintf(stderr, "ymsctl %s failed: %v\n", command, err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
writeDiagnosis(stdout, arguments[0], diagnosis)
|
writeDiagnosis(stdout, command, diagnosis)
|
||||||
return 0
|
return 0
|
||||||
case "help", "-h", "--help":
|
|
||||||
writeUsage(stdout)
|
|
||||||
return 0
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(stderr, "unknown command: %s\n", arguments[0])
|
|
||||||
writeUsage(stderr)
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateArguments 保存 update 子命令解析后的参数。
|
// updateArguments 保存 update 子命令解析后的参数。
|
||||||
@@ -392,34 +408,16 @@ func writeUpdateProgress(output io.Writer, event daemonapi.Response) {
|
|||||||
// status 只展示需要关注的漂移与可修复项,doctor 展示全部诊断项,
|
// status 只展示需要关注的漂移与可修复项,doctor 展示全部诊断项,
|
||||||
// reconcile 只展示可自动修复项作为修复计划。
|
// reconcile 只展示可自动修复项作为修复计划。
|
||||||
func writeDiagnosis(output io.Writer, command string, diagnosis daemonapi.Diagnosis) {
|
func writeDiagnosis(output io.Writer, command string, diagnosis daemonapi.Diagnosis) {
|
||||||
state := "HEALTHY"
|
fmt.Fprintf(output, "service=%s type=%s status=%s\n", diagnosis.Service, diagnosis.Type, diagnosisState(diagnosis))
|
||||||
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 {
|
if diagnosis.RepairApplied {
|
||||||
fmt.Fprintf(output, "APPLIED 对账修复已执行 transaction=%s\n", diagnosis.RepairTransactionID)
|
fmt.Fprintf(output, "APPLIED 对账修复已执行 transaction=%s\n", diagnosis.RepairTransactionID)
|
||||||
fmt.Fprintln(output, "RESULT 现场已重新核对")
|
fmt.Fprintln(output, "RESULT 现场已重新核对")
|
||||||
}
|
}
|
||||||
shown := 0
|
shown := 0
|
||||||
for _, item := range diagnosis.Items {
|
for _, item := range diagnosis.Items {
|
||||||
switch command {
|
if !showDiagnosisItem(command, item.Level) {
|
||||||
case "status":
|
|
||||||
if item.Level == daemonapi.DiagnosisLevelOK {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case "reconcile":
|
|
||||||
if item.Level != daemonapi.DiagnosisLevelFixable {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shown++
|
shown++
|
||||||
fmt.Fprintf(output, "%-8s %s\n", strings.ToUpper(item.Level), item.Message)
|
fmt.Fprintf(output, "%-8s %s\n", strings.ToUpper(item.Level), item.Message)
|
||||||
if item.Action != "" {
|
if item.Action != "" {
|
||||||
@@ -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 子命令:解析过滤参数,打开事务存储读取最近事务,
|
// runList 执行 list 子命令:解析过滤参数,打开事务存储读取最近事务,
|
||||||
// 按是否指定 --json 决定以稳定 JSON 或制表符分隔的表格形式输出到 stdout。
|
// 按是否指定 --json 决定以稳定 JSON 或制表符分隔的表格形式输出到 stdout。
|
||||||
// 它支持 --limit、--service、--state、--json 四个选项,返回查询或输出阶段的错误。
|
// 它支持 --limit、--service、--state、--json 四个选项,返回查询或输出阶段的错误。
|
||||||
|
|||||||
Reference in New Issue
Block a user