我使用golang,docker client来加载一个.tar格式的对接图像.

func loadImageFromTar(cli *client.Client, tarFilePath string) (string, error) {
    // Read tar file
    tarFile, err := os.Open(tarFilePath)
    if err != nil {
        return "", fmt.Errorf("failed to open tar file: %w", err)
    }
    defer tarFile.Close()

    // Create a pipe to stream data between tar reader and Docker client
    pr, pw := io.Pipe()

    // Set up a WaitGroup for synchronization
    var wg sync.WaitGroup
    wg.Add(2)

    // Load the Docker image in a separate goroutine
    var imageLoadResponse types.ImageLoadResponse
    go func() {
        defer wg.Done()
        imageLoadResponse, err = cli.ImageLoad(context.Background(), pr, false)
        if err != nil {
            err = fmt.Errorf("failed to load Docker image: %w", err)
        }
    }()

    // Read tar file metadata and copy the tar file to the pipe writer in a separate goroutine
    var repoTag string
    go func() {
        defer wg.Done()
        defer pw.Close()

        tarReader := tar.NewReader(tarFile)

        for {
            header, err := tarReader.Next()
            if err == io.EOF {
                break
            }
            if err != nil {
                err = fmt.Errorf("failed to read tar header: %w", err)
                fmt.Printf("Error: %v", err)
                return
            }

            // Extract the repository and tag from the manifest file
            if header.Name == "manifest.json" {
                data, err := io.ReadAll(tarReader)
                if err != nil {
                    err = fmt.Errorf("failed to read manifest file: %w", err)
                    fmt.Printf("Error: %v", err)
                    return
                }

                var manifest []map[string]interface{}
                err = json.Unmarshal(data, &manifest)
                if err != nil {
                    err = fmt.Errorf("failed to unmarshal manifest: %w", err)
                    fmt.Printf("Error: %v", err)
                    return
                }

                repoTag = manifest[0]["RepoTags"].([]interface{})[0].(string)
            }

            // Copy the tar file data to the pipe writer
            _, err = io.Copy(pw, tarReader)
            if err != nil {
                err = fmt.Errorf("failed to copy tar data: %w", err)
                fmt.Printf("Error: %v", err)
                return
            }
        }
    }()

    // Wait for both goroutines to finish
    wg.Wait()

    // Check if any error occurred in the goroutines
    if err != nil {
        return "", err
    }

    // Close the image load response body
    defer imageLoadResponse.Body.Close()

    // Get the image ID
    imageID, err := getImageIDByRepoTag(cli, repoTag)
    if err != nil {
        return "", fmt.Errorf("failed to get image ID: %w", err)
    }

    return imageID, nil
}

//func:getImageIDByRepoTag

func getImageIDByRepoTag(cli *client.Client, repoTag string) (string, error) {
    images, err := cli.ImageList(context.Background(), types.ImageListOptions{})
    if err != nil {
        return "", fmt.Errorf("failed to list images: %w", err)
    }

    for _, image := range images {
        for _, tag := range image.RepoTags {
            if tag == repoTag {
                return image.ID, nil
            }
        }
    }

    return "", fmt.Errorf("image ID not found for repo tag: %s", repoTag)
}

getImageIDByRepoTag总是返回fmt.Errorf("image ID not found for repo tag: %s", repoTag). 同样,当我运行docker images时,我没有看到正在加载的映像.看起来图像加载未完成.

在我的其他代码中,尽管扩展坞客户端cli.ImageLoad立即返回,但扩展坞图像加载通常需要时间.在判断getImageIDByRepoTag之前,我通常会增加大约30秒的等待时间.在这种情况下,增加等待时间也无济于事.

谢谢

推荐答案

有几个问题:

  • the two goroutines share err so some error handling may get lost
    • 您应该在这里 for each Goroutine使用唯一的错误变量&;判断wg.Wait()之后的两个错误
  • 主要问题:您正在从tar阅读器读取内容,以找到 list 文件并提取标签信息--这很好--但一旦找到它,就将字节流的其余部分复制到您的管道中.因此,您将丢失从未到达docker客户端的字节流的一大块

为了避免两次读取tar字节流,您可以使用io.TeeReader. 这允许您读取TAR存档-扫描manifest文件-但也可以将该流完整地写入其他地方(即写入到docker客户端).

创建TeeReader:

tr := io.TeeReader(tarFile, pw)  // reading `tr` will read the tarFile - but simultaneously write to `pw`

图像加载现在将从此处(而不是管道)读取:

//imageLoadResponse, err = cli.ImageLoad(context.Background(), pr, false)
imageLoadResponse, err = cli.ImageLoad(context.Background(), tr, false)

然后将您的archive/tar阅读器更改为从管道读取:

//tarReader := tar.NewReader(tarFile) // direct from file
tarReader := tar.NewReader(pr) // read from pipe (indirectly from the file)

然后,您可以放下您的io.Copy区块:

// no longer needed:
//
// _, err = io.Copy(pw, tarReader)
//

因为TAR判断代码将整个流读取到EOF.

附注:当您判断来自任一Goroutine的任何潜在错误时,您可能希望将io.EOF重置为nil,以避免认为EOF是更严重的错误:

header, err = tarReader.Next()
if err == io.EOF {
    err = nil  //  EOF is a non-fatal error here
    break
}

Go相关问答推荐

gorm插入不支持的数据

../golang/pkg/mod/github.com/wmentor/lemmas@v0.0.6/processor.go:72:9:未定义:令牌.进程

CURL和Postman HTTP POST工作,但Golang请求失败,状态为400

exec的可执行决议.命令+路径

当我使用 CircleCI 构建 Go Image 时,我得到runtime/cgo: pthread_create failed: Operation not allowed

使用Dockertest进行Golang SQL单元测试的基本设置

如何将任何类型的数据值传递到 Golang 中的 GRPC Protobuf struct ?

如何在 Go 中将 int 转换为包含 complex128 的泛型类型?

如何在 Go 服务中导入 monorepo 中的包?

如何使用 GolangCI 删除未使用的导入

如何在 gocql 中设置最大池大小?

动态 SQL 集 Golang

使用图像解码 JPEG 时 colored颜色 不正确.解码并写入 PDF?

无法使用 Golang 扫描文件路径

合并几千万文件最快的方法是什么

即使一个测试用例失败,如何运行所有测试用例

通用函数与外部包中的常见成员一起处理不同的 struct ?

函数的递归调用以 goroutine 和惯用方式开始,以在所有工作 goroutine 完成时继续调用者

如何迭代在泛型函数中传递的片的并集?

Golang 中的无实体函数