Golang - Docker API - 解析 ImagePull 的结果

7
我正在开发一个使用Docker API的Go脚本,用于我的项目。登录到我的仓库后,我拉取所需的Docker镜像,但问题是ImagePull函数返回一个io.ReadCloser实例,我只能通过以下方式将其传递给系统输出:
io.Copy(os.Stdout, pullResp)

很酷的是我可以看到响应,但我找不到一个合适的方法来解析它并根据它实现逻辑,如果图像的新版本已经被下载,会做一些事情,而如果图像是最新的,会做其他事情。如果您曾经遇到过这个问题,我会很高兴听取您的经验。

1
响应是否有标准格式?如果是这样,您可以使用 json.Decoder 将其解码为结构体,并根据结构体中的值执行所需操作。 - John S Perayil
1
你能否添加一个带有示例响应输出的编辑,我没有使用过docker,只是有一个高层次的理解。 - John S Perayil
1
我不能直接使用fmt.Print()打印它,因为我得到了&{0xc4202d42c0 {0 0} false <nil> 0x62f8b0 0x62f840},所以唯一的选择是将其传递给stdout,这会给我几行像这样的输出:{"status":"Extracting","progressDetail":{"current":1081344,"total":3160552},"progress":"[=================\u003e ] 1.081 MB/3.161 MB","id":"3c947192b06a"}。正如我之前所说,它就像一个流,而不是静态数据,可以简单地解析为本机JSON对象。 - Radoslav Stoyanov
@Robert 是的,那是我目前使用的解决办法,但是我想使用官方的方式(如果有的话,当然):) - Radoslav Stoyanov
1
你能展示一下代码吗? - Eugene Lisitsky
显示剩余3条评论
3个回答

9

您可以导入github.com/docker/docker/pkg/jsonmessage,并使用JSONMessageJSONProgress来解码流,但更容易的方法是调用DisplayJSONMessagesToStream:它既解析流,又将消息显示为文本。以下是如何使用stderr显示消息的方法:

    reader, err := cli.ImagePull(ctx, myImageRef, types.ImagePullOptions{})
    if err != nil {
            return err
    }
    defer reader.Close()

    termFd, isTerm := term.GetFdInfo(os.Stderr)
    jsonmessage.DisplayJSONMessagesStream(reader, os.Stderr, termFd, isTerm, nil)

很好的一点是它可以适应输出:如果是TTY(像docker pull那样),则会更新行,但如果输出被重定向到文件,则不会更新。

这太棒了,帮助很大!另外别忘了,DisplayJSONMessagesStream 也会解析错误,并在出现错误时返回带有错误消息的错误。 - undefined

4

@radoslav-stoyanov 在使用我的示例之前,请

# docker rmi busybox

然后运行代码。

package main

import (
    "encoding/json"
    "fmt"
    "github.com/docker/distribution/context"
    docker "github.com/docker/engine-api/client"
    "github.com/docker/engine-api/types"
    "io"
    "strings"
)

func main() {
    // DOCKER
    cli, err := docker.NewClient("unix:///var/run/docker.sock", "v1.28", nil, map[string]string{"User-Agent": "engine-api-cli-1.0"})
    if err != nil {
        panic(err)
    }

    imageName := "busybox:latest"

    events, err := cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{})
    if err != nil {
        panic(err)
    }

    d := json.NewDecoder(events)

    type Event struct {
        Status         string `json:"status"`
        Error          string `json:"error"`
        Progress       string `json:"progress"`
        ProgressDetail struct {
            Current int `json:"current"`
            Total   int `json:"total"`
        } `json:"progressDetail"`
    }

    var event *Event
    for {
        if err := d.Decode(&event); err != nil {
            if err == io.EOF {
                break
            }

            panic(err)
        }

        fmt.Printf("EVENT: %+v\n", event)
    }

    // Latest event for new image
    // EVENT: {Status:Status: Downloaded newer image for busybox:latest Error: Progress:[==================================================>]  699.2kB/699.2kB ProgressDetail:{Current:699243 Total:699243}}
    // Latest event for up-to-date image
    // EVENT: {Status:Status: Image is up to date for busybox:latest Error: Progress: ProgressDetail:{Current:0 Total:0}}
    if event != nil {
        if strings.Contains(event.Status, fmt.Sprintf("Downloaded newer image for %s", imageName)) {
            // new
            fmt.Println("new")
        }

        if strings.Contains(event.Status, fmt.Sprintf("Image is up to date for %s", imageName)) {
            // up-to-date
            fmt.Println("up-to-date")
        }
    }
}

你可以在这里查看API格式,以创建你的结构(如我的Event)并读取它们:https://docs.docker.com/engine/api/v1.27/#operation/ImageCreate 希望这能帮助你解决问题,谢谢。

谢谢@Vitaly!你的方法看起来不错,我会尝试一下并告诉你它是否适用于我。 - Radoslav Stoyanov
当我即将奖励您的赏金时,如果您能提供一个示例来说明如何捕获新版本的图像是否已下载或现有版本是否已更新,我会很高兴。 - Radoslav Stoyanov
@RadoslavStoyanov 我更新了代码,我认为你可以从中创建一个函数 :-) - Vitalii Velikodnyi

2
我已经为我的目的使用了类似的方法(不是moby客户端)。通常,阅读流响应的想法是相同的。尝试一下并实现你自己的方法。
读取任何响应类型的流响应:
reader := bufio.NewReader(pullResp)
defer pullResp.Close()  // pullResp is io.ReadCloser
var resp bytes.Buffer
for {
    line, err := reader.ReadBytes('\n')
    if err != nil {
        // it could be EOF or read error
        // handle it
        break
    }
    resp.Write(line)
    resp.WriteByte('\n')
}

// print it
fmt.Println(resp.String())

然而,您在评论中提供的示例响应似乎是有效的JSON结构。json.Decoder是读取JSON流的最佳方法。这只是一个想法-
type ImagePullResponse struct {
   ID             string `json"id"`
   Status         string `json:"status"`
   ProgressDetail struct {
     Current int64 `json:"current"`
     Total   int64 `json:"total"`
   } `json:"progressDetail"`
   Progress string `json:"progress"`
}

并且执行

d := json.NewDecoder(pullResp)
for {
   var pullResult ImagePullResponse
  if err := d.Decode(&pullResult); err != nil {
    // handle the error
    break
  }
  fmt.Println(pullResult)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接