feat: 将更多 Docker 命令改为内置实现,统一按名称/ID 解析目标

- exec/rm/rmi/stop/restart 支持名称、ID 或唯一 ID 前缀,目标不存在或
  前缀有歧义时在调用 docker 前报错;exec 默认 -it 并以 -- 分隔命令
- logs 新增 --since/--grep/--save;df 改为按占用排序的可读排行;
  stats 默认单次快照,无参数时统计全部运行中容器
- 破坏性确认:Command.ConfirmBuiltin 提供将删除对象的清单,
  rm/rmi 无 --yes 时列清单并退出码 2,拒绝 -f,运行中容器提示先 stop
- 抽出 executor/dockertargets.go 统一镜像/容器解析与 docker 调用
This commit is contained in:
2026-11-01 08:01:12 +08:00
parent bd6f20d7c5
commit f5ae80c1a8
18 changed files with 2308 additions and 226 deletions
+52 -6
View File
@@ -1,7 +1,9 @@
package executor
import (
"bytes"
"context"
"fmt"
"io"
"gitea.odboy.cn/odboy/kenaito-kopsc/internal/diagnose"
@@ -26,12 +28,22 @@ type BuiltinRequest struct {
type Builtin func(ctx context.Context, req BuiltinRequest) error
var builtins = map[string]Builtin{
"disk.usage": runDiskUsage,
"diagnose": runDiagnose,
"docker.load": runDockerLoad,
"docker.save": runDockerSave,
"net.pids": runNetPIDs,
"proc.nohup": runNohup,
"disk.usage": runDiskUsage,
"diagnose": runDiagnose,
"docker.df": runDockerDF,
"docker.exec": runDockerExec,
"docker.load": runDockerLoad,
"docker.logs": runDockerLogs,
"docker.restart": runDockerRestart,
"docker.rm": runDockerRM,
"docker.rm.preview": runDockerPreviewRM,
"docker.rmi": runDockerRMI,
"docker.rmi.preview": runDockerPreviewRMI,
"docker.save": runDockerSave,
"docker.stats": runDockerStats,
"docker.stop": runDockerStop,
"net.pids": runNetPIDs,
"proc.nohup": runNohup,
}
// runDiagnose bridges the registry builtin to the diagnose package.
@@ -40,6 +52,40 @@ func runDiagnose(ctx context.Context, req BuiltinRequest) error {
return service.Run(ctx, req.Args)
}
// Preview runs a command's ConfirmBuiltin (if any) and writes the list of
// objects the command would act on. It is used for the confirmation prompt and
// for --dry-run, so neither path needs to touch the objects themselves.
//
// The list is built from the container/image references kopsc resolves, which
// means every target is validated first: an unknown target is reported here
// rather than after the user typed --yes. Commands without a ConfirmBuiltin
// print nothing.
func Preview(ctx context.Context, plan Plan, w io.Writer) error {
name := plan.PreviewBuiltin()
if name == "" {
return nil
}
fn, ok := LookupBuiltin(name)
if !ok {
return fmt.Errorf("未注册的确认动作 %q", name)
}
// Build the list in memory first: a target that fails to resolve must not
// leave a dangling "将影响:" heading behind.
var buf bytes.Buffer
if err := fn(ctx, BuiltinRequest{
Args: plan.ExtraArgs,
Stdout: &buf,
Stderr: io.Discard,
Info: plan.Result.Info,
Plan: plan,
}); err != nil {
return err
}
fmt.Fprintln(w, "将影响:")
_, err := w.Write(buf.Bytes())
return err
}
// LookupBuiltin returns the implementation registered under name.
func LookupBuiltin(name string) (Builtin, bool) {
fn, ok := builtins[name]