Создание образа Docker из кода Go

Я пытаюсь создать образ Docker, используя библиотеки Docker API и Docker Go (https://github.com/docker/engine-api/). Пример кода:

package main
import (
    "fmt"
    "github.com/docker/engine-api/client"
    "github.com/docker/engine-api/types"
    "golang.org/x/net/context"
)
func main() {
    defaultHeaders := map[string]string{"User-Agent": "engine-api-cli-1.0"}
    cli, err := client.NewClient("unix:///var/run/docker.sock", "v1.22", nil, defaultHeaders)
    if err != nil {
        panic(err)
    }
    fmt.Print(cli.ClientVersion())
    opt := types.ImageBuildOptions{
        CPUSetCPUs:   "2",
        CPUSetMems:   "12",
        CPUShares:    20,
        CPUQuota:     10,
        CPUPeriod:    30,
        Memory:       256,
        MemorySwap:   512,
        ShmSize:      10,
        CgroupParent: "cgroup_parent",
        Dockerfile:   "dockerSrc/docker-debug-container/Dockerfile",
    }
    _, err = cli.ImageBuild(context.Background(), nil, opt)
    if err == nil || err.Error() != "Error response from daemon: Server error" {
        fmt.Printf("expected a Server Error, got %v", err)
    }
}

Ошибка всегда одна и та же:

Ответ об ошибке от демона: не удается найти указанный файл Docker: dockerSrc / docker-debug-container / Dockerfile

or

Ответ об ошибке от демона: не удается найти указанный файл Dockerfile: файл Dockerfile

Что я проверил:

  1. Папка существует в пути сборки
  2. Я пробовал как относительный, так и абсолютный путь
  3. В пути нет софтлинков
  4. Я пробовал ту же папку для двоичных файлов и Dockerfile
  5. docker build <path> работает
  6. и куча прочего

Другой вариант заключался в использовании RemoteContext, который выглядит так, как будто он работает, но только для полностью автономных файлов докеров, а не для файлов с «присутствием локального файла».


Обновление. Пытался передать tar в качестве буфера, но получил тот же результат со следующим:

  dockerBuildContext, err := os.Open("<path to>/docker-debug-    container/docker-debug-container.tar")
  defer dockerBuildContext.Close()

    opt := types.ImageBuildOptions{
        Context:      dockerBuildContext,
        CPUSetCPUs:   "2",
        CPUSetMems:   "12",
        CPUShares:    20,
        CPUQuota:     10,
        CPUPeriod:    30,
        Memory:       256,
        MemorySwap:   512,
        ShmSize:      10,
        CgroupParent: "cgroup_parent",
        //  Dockerfile:   "Dockerfile",
    }

    _, err = cli.ImageBuild(context.Background(), nil, opt)

person Mangirdas    schedule 06.08.2016    source источник


Ответы (7)


Для меня работает следующее:

package main

import (
    "archive/tar"
    "bytes"
    "context"
    "io"
    "io/ioutil"
    "log"
    "os"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
)

func main() {
    ctx := context.Background()
    cli, err := client.NewEnvClient()
    if err != nil {
        log.Fatal(err, " :unable to init client")
    }

    buf := new(bytes.Buffer)
    tw := tar.NewWriter(buf)
    defer tw.Close()

    dockerFile := "myDockerfile"
    dockerFileReader, err := os.Open("/path/to/dockerfile")
    if err != nil {
        log.Fatal(err, " :unable to open Dockerfile")
    }
    readDockerFile, err := ioutil.ReadAll(dockerFileReader)
    if err != nil {
        log.Fatal(err, " :unable to read dockerfile")
    }

    tarHeader := &tar.Header{
        Name: dockerFile,
        Size: int64(len(readDockerFile)),
    }
    err = tw.WriteHeader(tarHeader)
    if err != nil {
        log.Fatal(err, " :unable to write tar header")
    }
    _, err = tw.Write(readDockerFile)
    if err != nil {
        log.Fatal(err, " :unable to write tar body")
    }
    dockerFileTarReader := bytes.NewReader(buf.Bytes())

    imageBuildResponse, err := cli.ImageBuild(
        ctx,
        dockerFileTarReader,
        types.ImageBuildOptions{
            Context:    dockerFileTarReader,
            Dockerfile: dockerFile,
            Remove:     true})
    if err != nil {
        log.Fatal(err, " :unable to build docker image")
    }
    defer imageBuildResponse.Body.Close()
    _, err = io.Copy(os.Stdout, imageBuildResponse.Body)
    if err != nil {
        log.Fatal(err, " :unable to read image build response")
    }
}
person Komu    schedule 02.10.2017
comment
мне понравилось, что вы использовали родную библиотеку archive / tar для генерации tar, этот код можно будет поддерживать еще много лет. - person Daniel Andrei Mincă; 07.02.2021

@Mangirdas: достаточно долго смотреть в экран ДЕЙСТВИТЕЛЬНО помогает - по крайней мере, в моем случае. Я уже некоторое время сталкиваюсь с той же проблемой. Вы были правы, применив tar-файл (ваш второй пример). Если вы посмотрите здесь документ API, https://docs.docker.com/engine/reference/api/docker_remote_api_v1.24/#/build-image-from-a-dockerfile вы можете видеть, что он ожидает tar. Что действительно помогло мне, так это поиск других реализаций клиента, perl и ruby ​​в моем случае. Оба создают tar на лету, когда их просят создать образ из каталога. В любом случае вам нужно только поместить свой dockerBuildContext в другое место (см. Cli.ImageBuild ())

dockerBuildContext, err := os.Open("/Path/to/your/docker/tarfile.tar")
defer dockerBuildContext.Close()

buildOptions := types.ImageBuildOptions{
    Dockerfile:   "Dockerfile", // optional, is the default
}

buildResponse, err := cli.ImageBuild(context.Background(), dockerBuildContext, buildOptions)
if err != nil {
    log.Fatal(err)
}
defer buildResponse.Body.Close()

Я пока не умею правильно называть изображения, но, по крайней мере, я могу их создать ... Надеюсь, это поможет. Ваше здоровье

person Marcus Havranek    schedule 30.08.2016
comment
Я закончил делать это: / Так что, похоже, сейчас это неплохой способ сделать это. - person Mangirdas; 15.09.2016
comment
Вам не нужно вызывать close для dockerBuildContext; defer dockerBuildContext.Close() Это потому, что cli.ImageBuild вызывает клиента net / http; github.com/moby/moby/blob/master/client/ image_build.go # L34 И клиент go всегда закрывает тело запроса по умолчанию; github.com/golbang/go/go/go/go/go/ru/ - person Komu; 02.10.2017

В пакете Docker есть функция для создания TAR из пути к файлу. Это то, что используется CLI. Его нет в клиентском пакете, поэтому его нужно устанавливать отдельно:

import (
    "github.com/mitchellh/go-homedir"
    "github.com/docker/docker/pkg/archive"
)

func GetContext(filePath string) io.Reader {
    // Use homedir.Expand to resolve paths like '~/repos/myrepo'
    filePath, _ := homedir.Expand(filePath)
    ctx, _ := archive.TarWithOptions(filePath, &archive.TarOptions{})
    return ctx
}

cli.ImageBuild(context.Background(), GetContext("~/repos/myrepo"), types.ImageBuildOptions{...})
person kylieCatt    schedule 02.10.2018

Объединив несколько ответов и добавив, как правильно анализировать возвращенный JSON, используя DisplayJSONMessagesToStream.

package main

import (
    "os"
    "log"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/pkg/archive"
    "github.com/docker/docker/pkg/jsonmessage"
    "github.com/docker/docker/pkg/term"
    "golang.org/x/net/context"
)

// Build a dockerfile if it exists
func Build(dockerFilePath, buildContextPath string, tags []string) {

    ctx := context.Background()
    cli := getCLI()

    buildOpts := types.ImageBuildOptions{
        Dockerfile: dockerFilePath,
        Tags:       tags,
    }

    buildCtx, _ := archive.TarWithOptions(buildContextPath, &archive.TarOptions{})

    resp, err := cli.ImageBuild(ctx, buildCtx, buildOpts)
    if err != nil {
        log.Fatalf("build error - %s", err)
    }
    defer resp.Body.Close()

    termFd, isTerm := term.GetFdInfo(os.Stderr)
    jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stderr, termFd, isTerm, nil)
}

Я оставил нам несколько удобных функций, таких как getCLI, но я уверен, что у вас есть свои эквиваленты.

person Steve Gore    schedule 07.11.2019
comment
Спасибо за это. Мне интересно, почему вы решили написать в Stderr? Почему не Stdout? - person Steve; 22.08.2020

Я согласен с ответом Маркуса Гавранека, этот метод сработал для меня. Просто хочу добавить, как добавить имя к изображению, поскольку это казалось открытым вопросом:

buildOptions := types.ImageBuildOptions{
    Tags:   []string{"imagename"},
}

Надеюсь это поможет!

person Liz Furlan    schedule 06.03.2017

У меня такая же проблема. Наконец, выясните, что tar-файл должен быть контекстом сборки докера даже с Dockerfile.

Вот мой код,

package main

import (
    "log"
    "os"

    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
    "golang.org/x/net/context"
)

func main() {
    dockerBuildContext, err := os.Open("/Users/elsvent/workspace/Go/src/test/test.tar")
    defer dockerBuildContext.Close()

    buildOptions := types.ImageBuildOptions{
        SuppressOutput: true,
        PullParent:     true,
        Tags:           []string{"xxx"},
        Dockerfile:     "test/Dockerfile",
    }
    defaultHeaders := map[string]string{"Content-Type": "application/tar"}
    cli, _ := client.NewClient("unix:///var/run/docker.sock", "v1.24", nil, defaultHeaders)
    buildResp, err := cli.ImageBuild(context.Background(), dockerBuildContext, buildOptions)
    if err != nil {
    log.Fatal(err)
    }
    defer buildResp.Body.Close()
}
person Elsvent    schedule 05.07.2018

person    schedule
comment
перейти и получить github.com/mitchellh/go-homedir - person user15491526; 27.03.2021