Golang – Docker API – 解析 ImagePull 的结果

huangapple go评论74阅读模式
英文:

Golang - Docker API - parse result of ImagePull

问题

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

io.Copy(os.Stdout, pullResp)

很酷的是我可以看到响应,但我找不到一个合适的方法来解析它并根据响应实现逻辑,如果下载了镜像的新版本,将执行一些操作,如果镜像是最新的,则执行其他操作。
如果你曾经遇到过这个问题,我会很高兴听听你的经验。

英文:

I'm developing a Go script that uses the Docker API for the purposes of my project. After I login to my repository, I pull the Docker image I want, but the problem is that the ImagePull function returns an instance of io.ReadCloser, which I'm only able to pass to the system output via:

io.Copy(os.Stdout, pullResp)

It's cool that I can see the response, but I can't find a decent way to parse it and implement a logic depending on it, which will do some things if a new version of the image have been downloaded, and other things if the image was up to date.<br />
I'll be glad if you share your experience, if you have ever faced this problem.

答案1

得分: 8

你可以导入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命令一样),它会更新行,但如果输出被重定向到文件,则不会更新。

英文:

You can import github.com/docker/docker/pkg/jsonmessage and use both JSONMessage and JSONProgress to decode the stream but it's easier to call
DisplayJSONMessagesToStream: it both parses the stream and displays the messages as text. Here's how you can display the messages using 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)

The nice thing is that it adapts to the output: it updates the lines if this a TTY (the way docker pull does) but it doesn't if the output is redirected to a file.

答案2

得分: 3

@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

希望这能帮助你解决问题,谢谢。

英文:

@radoslav-stoyanov before use my example do

# docker rmi busybox

then run code

<!-- language: go -->

package main
import (
&quot;encoding/json&quot;
&quot;fmt&quot;
&quot;github.com/docker/distribution/context&quot;
docker &quot;github.com/docker/engine-api/client&quot;
&quot;github.com/docker/engine-api/types&quot;
&quot;io&quot;
&quot;strings&quot;
)
func main() {
// DOCKER
cli, err := docker.NewClient(&quot;unix:///var/run/docker.sock&quot;, &quot;v1.28&quot;, nil, map[string]string{&quot;User-Agent&quot;: &quot;engine-api-cli-1.0&quot;})
if err != nil {
panic(err)
}
imageName := &quot;busybox:latest&quot;
events, err := cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{})
if err != nil {
panic(err)
}
d := json.NewDecoder(events)
type Event struct {
Status         string `json:&quot;status&quot;`
Error          string `json:&quot;error&quot;`
Progress       string `json:&quot;progress&quot;`
ProgressDetail struct {
Current int `json:&quot;current&quot;`
Total   int `json:&quot;total&quot;`
} `json:&quot;progressDetail&quot;`
}
var event *Event
for {
if err := d.Decode(&amp;event); err != nil {
if err == io.EOF {
break
}
panic(err)
}
fmt.Printf(&quot;EVENT: %+v\n&quot;, event)
}
// Latest event for new image
// EVENT: {Status:Status: Downloaded newer image for busybox:latest Error: Progress:[==================================================&gt;]  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(&quot;Downloaded newer image for %s&quot;, imageName)) {
// new
fmt.Println(&quot;new&quot;)
}
if strings.Contains(event.Status, fmt.Sprintf(&quot;Image is up to date for %s&quot;, imageName)) {
// up-to-date
fmt.Println(&quot;up-to-date&quot;)
}
}
}

You can see API formats to create your structures (like my Event) to read them here https://docs.docker.com/engine/api/v1.27/#operation/ImageCreate

I hope it helps you solve your problem, thanks.

答案3

得分: 1

我已经使用类似的方法来实现我的目的(不是一个moby客户端)。通常,读取流式响应的思路是相同的。你可以尝试一下并实现自己的代码。

读取任何响应类型的流式响应:

reader := bufio.NewReader(pullResp)
defer pullResp.Close()  // pullResp是io.ReadCloser类型
var resp bytes.Buffer
for {
    line, err := reader.ReadBytes('\n')
    if err != nil {
        // 可能是EOF或读取错误
        // 处理错误
        break
    }
    resp.Write(line)
    resp.WriteByte('\n')
}

// 打印结果
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 {
        // 处理错误
        break
    }
    fmt.Println(pullResult)
}
英文:

I have used similar approach for my purpose (not a moby client). Typically idea is same for reading stream response. Give it a try and implement yours.

Reading stream response of any response type:

reader := bufio.NewReader(pullResp)
defer pullResp.Close()  // pullResp is io.ReadCloser
var resp bytes.Buffer
for {
line, err := reader.ReadBytes(&#39;\n&#39;)
if err != nil {
// it could be EOF or read error
// handle it
break
}
resp.Write(line)
resp.WriteByte(&#39;\n&#39;)
}
// print it
fmt.Println(resp.String())

However your sample response in the comment seems valid JSON structure. The json.Decoder is best way to read JSON stream. This is just an idea-

type ImagePullResponse struct {
ID             string `json&quot;id&quot;`
Status         string `json:&quot;status&quot;`
ProgressDetail struct {
Current int64 `json:&quot;current&quot;`
Total   int64 `json:&quot;total&quot;`
} `json:&quot;progressDetail&quot;`
Progress string `json:&quot;progress&quot;`
}

And do

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

huangapple
  • 本文由 发表于 2017年6月9日 16:14:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/44452679.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定