From 4e2d95fcf62987ed3e7df306d5e99c8e343dc290 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=B4=BA?= Date: Sun, 19 Oct 2025 18:09:27 +0800 Subject: [PATCH] first commit --- .vscode/launch.json | 16 ++++ config.go | 193 ++++++++++++++++++++++++++++++++++++++++++++ go.mod | 3 + main.go | 91 +++++++++++++++++++++ 4 files changed, 303 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 config.go create mode 100644 go.mod create mode 100644 main.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a154529 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}" + } + + ] +} \ No newline at end of file diff --git a/config.go b/config.go new file mode 100644 index 0000000..48e69af --- /dev/null +++ b/config.go @@ -0,0 +1,193 @@ +package main + +import ( + "encoding/json" + "fmt" + "os/exec" + "strings" +) + +// 从 demo.json 中需要提取的字段: +type ContainerInfo struct { + Name string `json:"Name"` + Config struct { + Hostname string `json:"Hostname"` + Image string `json:"Image"` + Cmd []string `json:"Cmd"` + Labels map[string]string `json:"Labels"` + } `json:"Config"` + Args []string `json:"Args"` + HostConfig struct { + NetworkMode string `json:"NetworkMode"` + RestartPolicy struct { + Name string `json:"Name"` + } `json:"RestartPolicy"` + Runtime string `json:"Runtime"` + } `json:"HostConfig"` + Mounts []struct { + Type string + Source string + Destination string + Mode string + } `json:"Mounts"` +} + +// 解析 JSON 文件并返回 ContainerInfo +func parseJSONString(data map[string]any) (ContainerInfo, error) { + var c ContainerInfo + + // 1. 顶层简单字段 + if v, ok := data["Name"].(string); ok { + c.Name = v[1:] // 去掉前导斜杠 + } + if v, ok := data["Args"].([]any); ok { + c.Args = make([]string, len(v)) + for i, a := range v { + c.Args[i], _ = a.(string) + } + } + + // 2. Config 子对象 + if cfg, ok := data["Config"].(map[string]any); ok { + if v, ok := cfg["Hostname"].(string); ok { + c.Config.Hostname = v + } + if v, ok := cfg["Image"].(string); ok { + c.Config.Image = v + } + if arr, ok := cfg["Cmd"].([]any); ok { + c.Config.Cmd = make([]string, len(arr)) + for i, v := range arr { + c.Config.Cmd[i], _ = v.(string) + } + } + if labels, ok := cfg["Labels"].(map[string]any); ok { + c.Config.Labels = make(map[string]string, len(labels)) + for k, v := range labels { + c.Config.Labels[k], _ = v.(string) + } + } + } + + // 3. HostConfig 子对象 + if hc, ok := data["HostConfig"].(map[string]any); ok { + if v, ok := hc["NetworkMode"].(string); ok { + c.HostConfig.NetworkMode = v + } + if v, ok := hc["Runtime"].(string); ok { + c.HostConfig.Runtime = v + } + if rp, ok := hc["RestartPolicy"].(map[string]any); ok { + if v, ok := rp["Name"].(string); ok { + c.HostConfig.RestartPolicy.Name = v + } + } + } + + // 4. Mounts 数组 + if mounts, ok := data["Mounts"].([]any); ok { + for _, m := range mounts { + mm, _ := m.(map[string]any) + var mount struct { + Type, Source, Destination, Mode string + } + mount.Type, _ = mm["Type"].(string) + mount.Source, _ = mm["Source"].(string) + mount.Destination, _ = mm["Destination"].(string) + mount.Mode, _ = mm["Mode"].(string) + c.Mounts = append(c.Mounts, mount) + } + } + + return c, nil +} + +// 构建 docker run 命令 +func buildDockerRunCommand(container ContainerInfo) string { + var cmd strings.Builder + + // 基础命令 + cmd.WriteString("docker run") + + // 容器名称 + if container.Name != "" { + name := strings.TrimPrefix(container.Name, "/") + cmd.WriteString(fmt.Sprintf(" --name=%s", name)) + } + + // 主机名 + if container.Config.Hostname != "" { + cmd.WriteString(fmt.Sprintf(" --hostname=%s", container.Config.Hostname)) + } + + // 卷挂载 + for _, mount := range container.Mounts { + if mount.Type == "bind" { + cmd.WriteString(fmt.Sprintf(" --volume %s:%s", mount.Source, mount.Destination)) + } + } + + // 网络模式 + if container.HostConfig.NetworkMode != "" { + cmd.WriteString(fmt.Sprintf(" --network=%s", container.HostConfig.NetworkMode)) + } + + // 重启策略 + if container.HostConfig.RestartPolicy.Name != "" { + cmd.WriteString(fmt.Sprintf(" --restart=%s", container.HostConfig.RestartPolicy.Name)) + } + + // 标签 + for key, value := range container.Config.Labels { + cmd.WriteString(fmt.Sprintf(" --label='%s=%s'", key, value)) + } + + // 运行时 + if container.HostConfig.Runtime != "" { + cmd.WriteString(fmt.Sprintf(" --runtime=%s", container.HostConfig.Runtime)) + } + + // 镜像 + if container.Config.Image != "" { + cmd.WriteString(fmt.Sprintf(" %s", container.Config.Image)) + } + + // 命令参数 + if len(container.Args) > 0 { + cmd.WriteString(fmt.Sprintf(" %s", strings.Join(container.Args, " "))) + } else if len(container.Config.Cmd) > 0 { + cmd.WriteString(fmt.Sprintf(" %s", strings.Join(container.Config.Cmd, " "))) + } + + return cmd.String() +} + +func getContainerCommand(containerName string) (string, error) { + data_js, err := execInspectCommand(containerName) + if err != nil { + return "", err + } + ctInfo, err := parseJSONString(data_js[0]) + if err != nil { + return "", err + } + return buildDockerRunCommand(ctInfo), nil +} + +// 执行shell命令 +func execInspectCommand(name string) ([]map[string]interface{}, error) { + cmd := exec.Command("docker", "inspect", name) + out, err := cmd.CombinedOutput() + if err != nil { + return nil, fmt.Errorf("执行命令失败: %v", err) + } + // docker inspect 返回的是 []map[string]interface{} + var result []map[string]interface{} + if err := json.Unmarshal(out, &result); err != nil { + return nil, fmt.Errorf("解析 JSON 失败: %w", err) + } + if len(result) == 0 { + return nil, fmt.Errorf("未找到容器/镜像: %s", name) + } + return result, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8d8afcc --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module runlike + +go 1.25 diff --git a/main.go b/main.go new file mode 100644 index 0000000..a762068 --- /dev/null +++ b/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "fmt" + "os" + "strings" +) + +// 颜色常量 +const ( + ColorReset = "\033[0m" + ColorRed = "\033[31m" + ColorGreen = "\033[32m" + ColorYellow = "\033[33m" + ColorBlue = "\033[34m" + ColorPurple = "\033[35m" + ColorCyan = "\033[36m" + ColorWhite = "\033[37m" +) + +// 应用信息 +const ( + AppName = "runlike" + Version = "1.0.0" + Author = "zfxt" + Description = "Shows command line necessary to run copy of existing Docker container." +) + +// 命令行应用结构 +type CLIApp struct { +} + +// 打印命令行使用说明 +func (app *CLIApp) printUsage() { + fmt.Printf("Usage: %s [options] [containerName]\n", AppName) + fmt.Println() + fmt.Printf("%sDescription:%s\n", ColorGreen, ColorReset) + fmt.Println() + fmt.Printf(" %s - %s\n", AppName, Description) + fmt.Println() + fmt.Printf("%sOptions:%s\n", ColorGreen, ColorReset) + fmt.Println() + fmt.Printf(" %s-h, --help%s 显示此帮助信息\n", ColorYellow, ColorReset) + fmt.Printf(" %s-v, --version%s 显示版本信息\n", ColorYellow, ColorReset) + fmt.Println() + +} + +// 执行子命令 +func (app *CLIApp) executeCommand(command string) { + switch command { + case "help", "h": + app.printUsage() + } +} + +func main() { + // 创建CLI应用实例 + app := &CLIApp{} + + // 检查命令行参数 + if len(os.Args) > 1 { + arg := strings.ToLower(os.Args[1]) + + // 处理全局选项 + switch arg { + case "--version", "-v": + fmt.Printf("%s v%s\n", AppName, Version) + return + case "--help", "-h": + app.printUsage() + return + default: + //该参数是容器名称,需要根据容器名称获取容器信息 + containerInfo, err := getContainerCommand(arg) + if err != nil { + fmt.Printf("获取容器信息失败: %v\n", err) + return + } + //TODO + fmt.Println(containerInfo) + } + + // 处理子命令 + app.executeCommand(arg) + return + } + + // 无参数时打印帮助信息 + app.printUsage() +}