Golang – Docker API – 解析 ImagePull 的结果

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

Golang - Docker API - parse result of ImagePull

问题

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

  1. 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:

  1. 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显示消息的示例代码:

  1. reader, err := cli.ImagePull(ctx, myImageRef, types.ImagePullOptions{})
  2. if err != nil {
  3. return err
  4. }
  5. defer reader.Close()
  6. termFd, isTerm := term.GetFdInfo(os.Stderr)
  7. 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:

  1. reader, err := cli.ImagePull(ctx, myImageRef, types.ImagePullOptions{})
  2. if err != nil {
  3. return err
  4. }
  5. defer reader.Close()
  6. termFd, isTerm := term.GetFdInfo(os.Stderr)
  7. 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

然后运行以下代码:

  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "github.com/docker/distribution/context"
  6. docker "github.com/docker/engine-api/client"
  7. "github.com/docker/engine-api/types"
  8. "io"
  9. "strings"
  10. )
  11. func main() {
  12. // DOCKER
  13. cli, err := docker.NewClient("unix:///var/run/docker.sock", "v1.28", nil, map[string]string{"User-Agent": "engine-api-cli-1.0"})
  14. if err != nil {
  15. panic(err)
  16. }
  17. imageName := "busybox:latest"
  18. events, err := cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{})
  19. if err != nil {
  20. panic(err)
  21. }
  22. d := json.NewDecoder(events)
  23. type Event struct {
  24. Status string `json:"status"`
  25. Error string `json:"error"`
  26. Progress string `json:"progress"`
  27. ProgressDetail struct {
  28. Current int `json:"current"`
  29. Total int `json:"total"`
  30. } `json:"progressDetail"`
  31. }
  32. var event *Event
  33. for {
  34. if err := d.Decode(&event); err != nil {
  35. if err == io.EOF {
  36. break
  37. }
  38. panic(err)
  39. }
  40. fmt.Printf("EVENT: %+v\n", event)
  41. }
  42. // Latest event for new image
  43. // EVENT: {Status:Status: Downloaded newer image for busybox:latest Error: Progress:[==================================================>] 699.2kB/699.2kB ProgressDetail:{Current:699243 Total:699243}}
  44. // Latest event for up-to-date image
  45. // EVENT: {Status:Status: Image is up to date for busybox:latest Error: Progress: ProgressDetail:{Current:0 Total:0}}
  46. if event != nil {
  47. if strings.Contains(event.Status, fmt.Sprintf("Downloaded newer image for %s", imageName)) {
  48. // new
  49. fmt.Println("new")
  50. }
  51. if strings.Contains(event.Status, fmt.Sprintf("Image is up to date for %s", imageName)) {
  52. // up-to-date
  53. fmt.Println("up-to-date")
  54. }
  55. }
  56. }

你可以在这里查看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 -->

  1. package main
  2. import (
  3. &quot;encoding/json&quot;
  4. &quot;fmt&quot;
  5. &quot;github.com/docker/distribution/context&quot;
  6. docker &quot;github.com/docker/engine-api/client&quot;
  7. &quot;github.com/docker/engine-api/types&quot;
  8. &quot;io&quot;
  9. &quot;strings&quot;
  10. )
  11. func main() {
  12. // DOCKER
  13. 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;})
  14. if err != nil {
  15. panic(err)
  16. }
  17. imageName := &quot;busybox:latest&quot;
  18. events, err := cli.ImagePull(context.Background(), imageName, types.ImagePullOptions{})
  19. if err != nil {
  20. panic(err)
  21. }
  22. d := json.NewDecoder(events)
  23. type Event struct {
  24. Status string `json:&quot;status&quot;`
  25. Error string `json:&quot;error&quot;`
  26. Progress string `json:&quot;progress&quot;`
  27. ProgressDetail struct {
  28. Current int `json:&quot;current&quot;`
  29. Total int `json:&quot;total&quot;`
  30. } `json:&quot;progressDetail&quot;`
  31. }
  32. var event *Event
  33. for {
  34. if err := d.Decode(&amp;event); err != nil {
  35. if err == io.EOF {
  36. break
  37. }
  38. panic(err)
  39. }
  40. fmt.Printf(&quot;EVENT: %+v\n&quot;, event)
  41. }
  42. // Latest event for new image
  43. // EVENT: {Status:Status: Downloaded newer image for busybox:latest Error: Progress:[==================================================&gt;] 699.2kB/699.2kB ProgressDetail:{Current:699243 Total:699243}}
  44. // Latest event for up-to-date image
  45. // EVENT: {Status:Status: Image is up to date for busybox:latest Error: Progress: ProgressDetail:{Current:0 Total:0}}
  46. if event != nil {
  47. if strings.Contains(event.Status, fmt.Sprintf(&quot;Downloaded newer image for %s&quot;, imageName)) {
  48. // new
  49. fmt.Println(&quot;new&quot;)
  50. }
  51. if strings.Contains(event.Status, fmt.Sprintf(&quot;Image is up to date for %s&quot;, imageName)) {
  52. // up-to-date
  53. fmt.Println(&quot;up-to-date&quot;)
  54. }
  55. }
  56. }

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客户端)。通常,读取流式响应的思路是相同的。你可以尝试一下并实现自己的代码。

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

  1. reader := bufio.NewReader(pullResp)
  2. defer pullResp.Close() // pullResp是io.ReadCloser类型
  3. var resp bytes.Buffer
  4. for {
  5. line, err := reader.ReadBytes('\n')
  6. if err != nil {
  7. // 可能是EOF或读取错误
  8. // 处理错误
  9. break
  10. }
  11. resp.Write(line)
  12. resp.WriteByte('\n')
  13. }
  14. // 打印结果
  15. fmt.Println(resp.String())

然而,你在评论中提供的示例响应似乎是有效的JSON结构。使用json.Decoder是读取JSON流的最佳方式。这只是一个想法:

  1. type ImagePullResponse struct {
  2. ID string `json:"id"`
  3. Status string `json:"status"`
  4. ProgressDetail struct {
  5. Current int64 `json:"current"`
  6. Total int64 `json:"total"`
  7. } `json:"progressDetail"`
  8. Progress string `json:"progress"`
  9. }
  10. d := json.NewDecoder(pullResp)
  11. for {
  12. var pullResult ImagePullResponse
  13. if err := d.Decode(&pullResult); err != nil {
  14. // 处理错误
  15. break
  16. }
  17. fmt.Println(pullResult)
  18. }
英文:

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:

  1. reader := bufio.NewReader(pullResp)
  2. defer pullResp.Close() // pullResp is io.ReadCloser
  3. var resp bytes.Buffer
  4. for {
  5. line, err := reader.ReadBytes(&#39;\n&#39;)
  6. if err != nil {
  7. // it could be EOF or read error
  8. // handle it
  9. break
  10. }
  11. resp.Write(line)
  12. resp.WriteByte(&#39;\n&#39;)
  13. }
  14. // print it
  15. 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-

  1. type ImagePullResponse struct {
  2. ID string `json&quot;id&quot;`
  3. Status string `json:&quot;status&quot;`
  4. ProgressDetail struct {
  5. Current int64 `json:&quot;current&quot;`
  6. Total int64 `json:&quot;total&quot;`
  7. } `json:&quot;progressDetail&quot;`
  8. Progress string `json:&quot;progress&quot;`
  9. }

And do

  1. d := json.NewDecoder(pullResp)
  2. for {
  3. var pullResult ImagePullResponse
  4. if err := d.Decode(&amp;pullResult); err != nil {
  5. // handle the error
  6. break
  7. }
  8. fmt.Println(pullResult)
  9. }

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:

确定