mirror of
https://github.com/linuxkit/linuxkit.git
synced 2025-07-20 09:39:08 +00:00
build directly with buildkit
Signed-off-by: Avi Deitcher <avi@deitcher.net>
This commit is contained in:
parent
fb111d3bbf
commit
0929aabe50
@ -8,7 +8,7 @@ in a LinuxKit-based project, if you know how to build a container,
|
|||||||
you should be able to build a LinuxKit package.
|
you should be able to build a LinuxKit package.
|
||||||
|
|
||||||
All official LinuxKit packages are:
|
All official LinuxKit packages are:
|
||||||
- Enabled with multi-arch manifests to work on multiple architectures.
|
- Enabled with multi-arch indexes to work on multiple architectures.
|
||||||
- Derived from well-known sources for repeatable builds.
|
- Derived from well-known sources for repeatable builds.
|
||||||
- Built with multi-stage builds to minimise their size.
|
- Built with multi-stage builds to minimise their size.
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ A package source consists of a directory containing at least two files:
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
Before you can build packages you need:
|
Before you can build packages you need:
|
||||||
- Docker version 19.03 or newer, which includes [buildx](https://docs.docker.com/buildx/working-with-buildx/)
|
- Docker version 19.03 or newer.
|
||||||
- If you are on a Mac you also need `docker-credential-osxkeychain.bin`, which comes with Docker for Mac.
|
- If you are on a Mac you also need `docker-credential-osxkeychain.bin`, which comes with Docker for Mac.
|
||||||
- `make`, `base64`, `jq`, and `expect`
|
- `make`, `base64`, `jq`, and `expect`
|
||||||
- A *recent* version of `manifest-tool` which you can build with `make
|
- A *recent* version of `manifest-tool` which you can build with `make
|
||||||
@ -147,14 +147,19 @@ a MacBook with Apple Silicon running on `arm64`.
|
|||||||
|
|
||||||
How does linuxkit determine where to build the target images?
|
How does linuxkit determine where to build the target images?
|
||||||
|
|
||||||
linuxkit uses a combination of buildx builders and docker contexts, controlled by your input, to determine where to build.
|
linuxkit uses [buildkit](https://github.com/moby/buildkit) directly to build all images.
|
||||||
|
It uses docker contexts to determine _where_ to run those buildkit containers, based on the target
|
||||||
|
architecture.
|
||||||
|
|
||||||
Upon startup, it looks for a buildx builder named `linuxkit`. If it cannot find that builder, it creates it.
|
When running a package build, linuxkit looks for a container named `linuxkit-builder`, running the appropriate
|
||||||
|
version of buildkit. If it cannot find a container with that name, it creates it.
|
||||||
|
If the container already exists but is not running buildkit, or if the version is incorrect, linuxkit stops and removes
|
||||||
|
the existing `linuxkit-builder` container and creates one running the correct version of buildkit.
|
||||||
|
|
||||||
When linuxkit needs to build a package for a particular architecture:
|
When linuxkit needs to build a package for a particular architecture:
|
||||||
|
|
||||||
1. If a context for that architecture was provided, use that context.
|
1. If a context for that architecture was provided, use that context, looking for and/or starting a buildkit container named `linuxkit-builder`.
|
||||||
1. If no context for that architecture was provided, use the default `linuxkit` context
|
1. If no context for that architecture was provided, use the `default` context.
|
||||||
|
|
||||||
The actual building then will be one of:
|
The actual building then will be one of:
|
||||||
|
|
||||||
@ -183,14 +188,14 @@ linuxkit is capable of using native build nodes to do the build, even remotely.
|
|||||||
1. Create a [docker context](https://docs.docker.com/engine/context/working-with-contexts/) that references the build node
|
1. Create a [docker context](https://docs.docker.com/engine/context/working-with-contexts/) that references the build node
|
||||||
1. Tell linuxkit to use that context for that architecture
|
1. Tell linuxkit to use that context for that architecture
|
||||||
|
|
||||||
linuxkit will then use that provided context to create a buildx builder and use it for that architecture.
|
linuxkit will then use that provided context to look for and/or start a container in which to run buildkit for that architecture.
|
||||||
|
|
||||||
linuxkit looks for contexts in the following descending order of priority:
|
linuxkit looks for contexts in the following descending order of priority:
|
||||||
|
|
||||||
1. CLI option `--builders <platform>=<context>,<platform>=<context>`, e.g. `--builders linux/arm64=linuxkit-arm64,linux/amd64=default`
|
1. CLI option `--builders <platform>=<context>,<platform>=<context>`, e.g. `--builders linux/arm64=linuxkit-arm64,linux/amd64=default`
|
||||||
1. Environment variable `LINUXKIT_BUILDERS=<platform>=<context>,<platform>=<context>`, e.g. `LINUXKIT_BUILDERS=linux/arm64=linuxkit-arm64,linux/amd64=default`
|
1. Environment variable `LINUXKIT_BUILDERS=<platform>=<context>,<platform>=<context>`, e.g. `LINUXKIT_BUILDERS=linux/arm64=linuxkit-arm64,linux/amd64=default`
|
||||||
1. Existing context named `linuxkit-<platform>`, e.g. `linuxkit-linux-arm64` or `linuxkit-linux-s390x`, with "/" replaced by "-", as "/" is an invalid character.
|
1. Existing context named `linuxkit-<platform>`, e.g. `linuxkit-linux-arm64` or `linuxkit-linux-s390x`, with "/" replaced by "-", as "/" is an invalid character.
|
||||||
1. Default builder named `linuxkit`, created by linuxkit, running in the default context
|
1. Default context
|
||||||
|
|
||||||
If a builder name is provided for a specific platform, and it doesn't exist, it will be treated as a fatal error.
|
If a builder name is provided for a specific platform, and it doesn't exist, it will be treated as a fatal error.
|
||||||
|
|
||||||
@ -200,10 +205,9 @@ If a builder name is provided for a specific platform, and it doesn't exist, it
|
|||||||
|
|
||||||
There are no contexts starting with `linuxkit-`, no environment variable `LINUXKIT_BUILDERS`, no command-line argument `--builders`.
|
There are no contexts starting with `linuxkit-`, no environment variable `LINUXKIT_BUILDERS`, no command-line argument `--builders`.
|
||||||
|
|
||||||
linuxkit will build any requested packages using `docker buildx` on the local platform, with a builder (created, if necessary) named `linuxkit`.
|
linuxkit will build any requested packages using `default` context on the local platform, with a container (created, if necessary) named `linuxkit-builder`.
|
||||||
Builds for the same architecture will be native, builds for other platforms will use either qemu or cross-building.
|
Builds for the same architecture will be native, builds for other platforms will use either qemu or cross-building.
|
||||||
|
|
||||||
|
|
||||||
##### Specified target
|
##### Specified target
|
||||||
|
|
||||||
You create a context named `my-remote-arm64` and then run:
|
You create a context named `my-remote-arm64` and then run:
|
||||||
@ -215,10 +219,13 @@ linuxkit pkg build --platforms=linux/arm64,linux/amd64 --builders linux/arm64=my
|
|||||||
linuxkit will build:
|
linuxkit will build:
|
||||||
|
|
||||||
* for arm64 using the context `my-remote-arm64`, since you specified in `--builders` to use `my-remote-arm64` for `linux/arm64`
|
* for arm64 using the context `my-remote-arm64`, since you specified in `--builders` to use `my-remote-arm64` for `linux/arm64`
|
||||||
* for amd64 using the context `default` and the `linuxkit` builder, as that is the default fallback
|
* for amd64 using the context `default`, as that is the default fallback
|
||||||
|
|
||||||
The same would happen if you used `LINUXKIT_BUILDERS=linux/arm64=my-remote-arm64` instead of the `--builders` flag.
|
The same would happen if you used `LINUXKIT_BUILDERS=linux/arm64=my-remote-arm64` instead of the `--builders` flag.
|
||||||
|
|
||||||
|
In both cases - the remote context `my-remote-arm64` and the local `default` context - it will do the build inside
|
||||||
|
a container named `linuxkit-builder`.
|
||||||
|
|
||||||
##### Named context
|
##### Named context
|
||||||
|
|
||||||
You create a context named `linuxkit-linux-arm64` and then run:
|
You create a context named `linuxkit-linux-arm64` and then run:
|
||||||
@ -234,7 +241,7 @@ linuxkit will build:
|
|||||||
|
|
||||||
##### Combination
|
##### Combination
|
||||||
|
|
||||||
You create a context named `linuxkit-arm64`, and another named `my-remote-builder-amd64` and then run:
|
You create a context named `linuxkit-linux-arm64`, and another named `my-remote-builder-amd64` and then run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
linuxkit pkg build --platforms=linux/arm64,linux/amd64 --builders linux/amd64=my-remote-builder-amd64
|
linuxkit pkg build --platforms=linux/arm64,linux/amd64 --builders linux/amd64=my-remote-builder-amd64
|
||||||
@ -242,7 +249,7 @@ linuxkit pkg build --platforms=linux/arm64,linux/amd64 --builders linux/amd64=my
|
|||||||
|
|
||||||
linuxkit will build:
|
linuxkit will build:
|
||||||
|
|
||||||
* for arm64 using the context `linuxkit-arm64`, since there is a context with the name `linuxkit-<arch>`, and you did not override that particular architecture using `--builders` or the environment variable `LINUXKIT_BUILDERS`
|
* for arm64 using the context `linuxkit-linux-arm64`, since there is a context with the name `linuxkit-<platform>`, and you did not override that particular architecture using `--builders` or the environment variable `LINUXKIT_BUILDERS`
|
||||||
* for amd64 using the context `my-remote-builder-amd64`, since you specified for that architecture using `--builders`
|
* for amd64 using the context `my-remote-builder-amd64`, since you specified for that architecture using `--builders`
|
||||||
|
|
||||||
The same would happen if you used `LINUXKIT_BUILDERS=linux/arm64=my-remote-builder-amd64` instead of the `--builders` flag.
|
The same would happen if you used `LINUXKIT_BUILDERS=linux/arm64=my-remote-builder-amd64` instead of the `--builders` flag.
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/moby/term"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
buildersEnvVar = "LINUXKIT_BUILDERS"
|
buildersEnvVar = "LINUXKIT_BUILDERS"
|
||||||
|
defaultBuilderImage = "moby/buildkit:v0.10.3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func pkgBuild(args []string) {
|
func pkgBuild(args []string) {
|
||||||
@ -38,6 +39,8 @@ func pkgBuildPush(args []string, withPush bool) {
|
|||||||
platforms := flags.String("platforms", "", "Which platforms to build for, defaults to all of those for which the package can be built")
|
platforms := flags.String("platforms", "", "Which platforms to build for, defaults to all of those for which the package can be built")
|
||||||
skipPlatforms := flags.String("skip-platforms", "", "Platforms that should be skipped, even if present in build.yml")
|
skipPlatforms := flags.String("skip-platforms", "", "Platforms that should be skipped, even if present in build.yml")
|
||||||
builders := flags.String("builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
|
builders := flags.String("builders", "", "Which builders to use for which platforms, e.g. linux/arm64=docker-context-arm64, overrides defaults and environment variables, see https://github.com/linuxkit/linuxkit/blob/master/docs/packages.md#Providing-native-builder-nodes")
|
||||||
|
builderImage := flags.String("builder-image", defaultBuilderImage, "buildkit builder container image to use")
|
||||||
|
builderRestart := flags.Bool("builder-restart", false, "force restarting builder, even if container with correct name and image exists")
|
||||||
buildCacheDir := flags.String("cache", defaultLinuxkitCache(), "Directory for storing built image, incompatible with --docker")
|
buildCacheDir := flags.String("cache", defaultLinuxkitCache(), "Directory for storing built image, incompatible with --docker")
|
||||||
|
|
||||||
// some logic clarification:
|
// some logic clarification:
|
||||||
@ -140,6 +143,8 @@ func pkgBuildPush(args []string, withPush bool) {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
|
opts = append(opts, pkglib.WithBuildBuilders(buildersMap))
|
||||||
|
opts = append(opts, pkglib.WithBuildBuilderImage(*builderImage))
|
||||||
|
opts = append(opts, pkglib.WithBuildBuilderRestart(*builderRestart))
|
||||||
|
|
||||||
for _, p := range pkgs {
|
for _, p := range pkgs {
|
||||||
// things we need our own copies of
|
// things we need our own copies of
|
||||||
|
@ -2,6 +2,7 @@ package pkglib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
registry "github.com/google/go-containerregistry/pkg/v1"
|
registry "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/cache"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
@ -40,6 +42,8 @@ type buildOpts struct {
|
|||||||
builders map[string]string
|
builders map[string]string
|
||||||
runner dockerRunner
|
runner dockerRunner
|
||||||
writer io.Writer
|
writer io.Writer
|
||||||
|
builderImage string
|
||||||
|
builderRestart bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildOpt allows callers to specify options to Build
|
// BuildOpt allows callers to specify options to Build
|
||||||
@ -141,9 +145,26 @@ func WithBuildOutputWriter(w io.Writer) BuildOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithBuildBuilderImage set the builder container image to use.
|
||||||
|
func WithBuildBuilderImage(image string) BuildOpt {
|
||||||
|
return func(bo *buildOpts) error {
|
||||||
|
bo.builderImage = image
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithBuildBuilderRestart restart the builder container even if it already is running with the correct image version
|
||||||
|
func WithBuildBuilderRestart(restart bool) BuildOpt {
|
||||||
|
return func(bo *buildOpts) error {
|
||||||
|
bo.builderRestart = restart
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Build builds the package
|
// Build builds the package
|
||||||
func (p Pkg) Build(bos ...BuildOpt) error {
|
func (p Pkg) Build(bos ...BuildOpt) error {
|
||||||
var bo buildOpts
|
var bo buildOpts
|
||||||
|
var ctx = context.TODO()
|
||||||
for _, fn := range bos {
|
for _, fn := range bos {
|
||||||
if err := fn(&bo); err != nil {
|
if err := fn(&bo); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -209,8 +230,8 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.buildkitCheck(); err != nil {
|
if err := d.contextSupportCheck(); err != nil {
|
||||||
return fmt.Errorf("buildkit not supported, check docker version: %v", err)
|
return fmt.Errorf("contexts not supported, check docker version: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
skipBuild := bo.skipBuild
|
skipBuild := bo.skipBuild
|
||||||
@ -227,23 +248,31 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
if !skipBuild {
|
if !skipBuild {
|
||||||
fmt.Fprintf(writer, "building %s\n", ref)
|
fmt.Fprintf(writer, "building %s\n", ref)
|
||||||
var (
|
var (
|
||||||
args []string
|
imageBuildOpts = types.ImageBuildOptions{
|
||||||
|
Labels: map[string]string{},
|
||||||
|
BuildArgs: map[string]*string{},
|
||||||
|
}
|
||||||
descs []registry.Descriptor
|
descs []registry.Descriptor
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// args that we use:
|
||||||
|
// labels map[string]string
|
||||||
|
// network string
|
||||||
|
// build-arg []string
|
||||||
|
|
||||||
if p.git != nil && p.gitRepo != "" {
|
if p.git != nil && p.gitRepo != "" {
|
||||||
args = append(args, "--label", "org.opencontainers.image.source="+p.gitRepo)
|
imageBuildOpts.Labels["org.opencontainers.image.source"] = p.gitRepo
|
||||||
}
|
}
|
||||||
if p.git != nil && !p.dirty {
|
if p.git != nil && !p.dirty {
|
||||||
commit, err := p.git.commitHash("HEAD")
|
commit, err := p.git.commitHash("HEAD")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
args = append(args, "--label", "org.opencontainers.image.revision="+commit)
|
imageBuildOpts.Labels["org.opencontainers.image.revision"] = commit
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.network {
|
if !p.network {
|
||||||
args = append(args, "--network=none")
|
imageBuildOpts.NetworkMode = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.config != nil {
|
if p.config != nil {
|
||||||
@ -251,21 +280,25 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
args = append(args, "--label=org.mobyproject.config="+string(b))
|
imageBuildOpts.Labels["org.mobyproject.config"] = string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
args = append(args, "--label=org.mobyproject.linuxkit.version="+version.Version)
|
imageBuildOpts.Labels["org.mobyproject.linuxkit.version"] = version.Version
|
||||||
args = append(args, "--label=org.mobyproject.linuxkit.revision="+version.GitCommit)
|
imageBuildOpts.Labels["org.mobyproject.linuxkit.revision"] = version.GitCommit
|
||||||
|
|
||||||
if p.buildArgs != nil {
|
if p.buildArgs != nil {
|
||||||
for _, buildArg := range *p.buildArgs {
|
for _, buildArg := range *p.buildArgs {
|
||||||
args = append(args, "--build-arg", buildArg)
|
parts := strings.SplitN(buildArg, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("invalid build-arg, must be in format 'arg=value': %s", buildArg)
|
||||||
|
}
|
||||||
|
imageBuildOpts.BuildArgs[parts[0]] = &parts[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build for each arch and save in the linuxkit cache
|
// build for each arch and save in the linuxkit cache
|
||||||
for _, platform := range bo.platforms {
|
for _, platform := range bo.platforms {
|
||||||
desc, err := p.buildArch(d, c, platform.Architecture, args, writer, bo)
|
desc, err := p.buildArch(ctx, d, c, bo.builderImage, platform.Architecture, bo.builderRestart, writer, bo, imageBuildOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error building for arch %s: %v", platform.Architecture, err)
|
return fmt.Errorf("error building for arch %s: %v", platform.Architecture, err)
|
||||||
}
|
}
|
||||||
@ -354,7 +387,7 @@ func (p Pkg) Build(bos ...BuildOpt) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildArch builds the package for a single arch
|
// buildArch builds the package for a single arch
|
||||||
func (p Pkg) buildArch(d dockerRunner, c lktspec.CacheProvider, arch string, args []string, writer io.Writer, bo buildOpts) (*registry.Descriptor, error) {
|
func (p Pkg) buildArch(ctx context.Context, d dockerRunner, c lktspec.CacheProvider, builderImage, arch string, restart bool, writer io.Writer, bo buildOpts, imageBuildOpts types.ImageBuildOptions) (*registry.Descriptor, error) {
|
||||||
var (
|
var (
|
||||||
desc *registry.Descriptor
|
desc *registry.Descriptor
|
||||||
tagArch string
|
tagArch string
|
||||||
@ -388,7 +421,6 @@ func (p Pkg) buildArch(d dockerRunner, c lktspec.CacheProvider, arch string, arg
|
|||||||
|
|
||||||
// set the target
|
// set the target
|
||||||
var (
|
var (
|
||||||
buildxOutput string
|
|
||||||
stdout io.WriteCloser
|
stdout io.WriteCloser
|
||||||
eg errgroup.Group
|
eg errgroup.Group
|
||||||
stdoutCloser = func() {
|
stdoutCloser = func() {
|
||||||
@ -403,7 +435,6 @@ func (p Pkg) buildArch(d dockerRunner, c lktspec.CacheProvider, arch string, arg
|
|||||||
}
|
}
|
||||||
|
|
||||||
// we are writing to local, so we need to catch the tar output stream and place the right files in the right place
|
// we are writing to local, so we need to catch the tar output stream and place the right files in the right place
|
||||||
buildxOutput = "type=oci"
|
|
||||||
piper, pipew := io.Pipe()
|
piper, pipew := io.Pipe()
|
||||||
stdout = pipew
|
stdout = pipew
|
||||||
|
|
||||||
@ -418,13 +449,10 @@ func (p Pkg) buildArch(d dockerRunner, c lktspec.CacheProvider, arch string, arg
|
|||||||
piper.Close()
|
piper.Close()
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
args = append(args, fmt.Sprintf("--output=%s", buildxOutput))
|
|
||||||
|
|
||||||
buildCtx := &buildCtx{sources: p.sources}
|
buildCtx := &buildCtx{sources: p.sources}
|
||||||
platform := fmt.Sprintf("linux/%s", arch)
|
platform := fmt.Sprintf("linux/%s", arch)
|
||||||
archArgs := append(args, "--platform")
|
if err := d.build(ctx, tagArch, p.path, builderName, builderImage, platform, restart, buildCtx.Reader(), stdout, imageBuildOpts); err != nil {
|
||||||
archArgs = append(archArgs, platform)
|
|
||||||
if err := d.build(tagArch, p.path, builderName, platform, buildCtx.Reader(), stdout, archArgs...); err != nil {
|
|
||||||
stdoutCloser()
|
stdoutCloser()
|
||||||
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") {
|
if strings.Contains(err.Error(), "executor failed running [/dev/.buildkit_qemu_emulator") {
|
||||||
return nil, fmt.Errorf("buildkit was unable to emulate %s. check binfmt has been set up and works for this platform: %v", platform, err)
|
return nil, fmt.Errorf("buildkit was unable to emulate %s. check binfmt has been set up and works for this platform: %v", platform, err)
|
||||||
|
@ -2,6 +2,7 @@ package pkglib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -13,6 +14,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/containerd/containerd/reference"
|
"github.com/containerd/containerd/reference"
|
||||||
|
dockertypes "github.com/docker/docker/api/types"
|
||||||
registry "github.com/google/go-containerregistry/pkg/v1"
|
registry "github.com/google/go-containerregistry/pkg/v1"
|
||||||
"github.com/google/go-containerregistry/pkg/v1/types"
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
||||||
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
lktspec "github.com/linuxkit/linuxkit/src/cmd/linuxkit/spec"
|
||||||
@ -20,7 +22,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type dockerMocker struct {
|
type dockerMocker struct {
|
||||||
supportBuildKit bool
|
supportContexts bool
|
||||||
images map[string][]byte
|
images map[string][]byte
|
||||||
enableTag bool
|
enableTag bool
|
||||||
enableBuild bool
|
enableBuild bool
|
||||||
@ -34,15 +36,8 @@ type buildLog struct {
|
|||||||
pkg string
|
pkg string
|
||||||
dockerContext string
|
dockerContext string
|
||||||
platform string
|
platform string
|
||||||
opts []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *dockerMocker) buildkitCheck() error {
|
|
||||||
if d.supportBuildKit {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return errors.New("buildkit unsupported")
|
|
||||||
}
|
|
||||||
func (d *dockerMocker) tag(ref, tag string) error {
|
func (d *dockerMocker) tag(ref, tag string) error {
|
||||||
if !d.enableTag {
|
if !d.enableTag {
|
||||||
return errors.New("tags not allowed")
|
return errors.New("tags not allowed")
|
||||||
@ -50,11 +45,18 @@ func (d *dockerMocker) tag(ref, tag string) error {
|
|||||||
d.images[tag] = d.images[ref]
|
d.images[tag] = d.images[ref]
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (d *dockerMocker) build(tag, pkg, dockerContext, platform string, stdin io.Reader, stdout io.Writer, opts ...string) error {
|
func (d *dockerMocker) contextSupportCheck() error {
|
||||||
|
if d.supportContexts {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("contexts not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dockerMocker) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, builderRestart bool, stdin io.Reader, stdout io.Writer, imageBuildOpts dockertypes.ImageBuildOptions) error {
|
||||||
if !d.enableBuild {
|
if !d.enableBuild {
|
||||||
return errors.New("build disabled")
|
return errors.New("build disabled")
|
||||||
}
|
}
|
||||||
d.builds = append(d.builds, buildLog{tag, pkg, dockerContext, platform, opts})
|
d.builds = append(d.builds, buildLog{tag, pkg, dockerContext, platform})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (d *dockerMocker) save(tgt string, refs ...string) error {
|
func (d *dockerMocker) save(tgt string, refs ...string) error {
|
||||||
@ -297,13 +299,13 @@ func TestBuild(t *testing.T) {
|
|||||||
err string
|
err string
|
||||||
}{
|
}{
|
||||||
{"invalid tag", Pkg{image: "docker.io/foo/bar:abc:def:ghi"}, nil, nil, &dockerMocker{}, &cacheMocker{}, "could not resolve references"},
|
{"invalid tag", Pkg{image: "docker.io/foo/bar:abc:def:ghi"}, nil, nil, &dockerMocker{}, &cacheMocker{}, "could not resolve references"},
|
||||||
{"not at head", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "foo"}, nil, []string{"amd64"}, &dockerMocker{supportBuildKit: false}, &cacheMocker{}, "Cannot build from commit hash != HEAD"},
|
{"not at head", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "foo"}, nil, []string{"amd64"}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "Cannot build from commit hash != HEAD"},
|
||||||
{"no build cache", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "HEAD"}, nil, []string{"amd64"}, &dockerMocker{supportBuildKit: false}, &cacheMocker{}, "must provide linuxkit build cache"},
|
{"no build cache", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "HEAD"}, nil, []string{"amd64"}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "must provide linuxkit build cache"},
|
||||||
{"unsupported buildkit", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64"}, &dockerMocker{supportBuildKit: false}, &cacheMocker{}, "buildkit not supported, check docker version"},
|
{"unsupported contexts", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64"}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "contexts not supported, check docker version"},
|
||||||
{"load docker without local platform", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir), WithBuildTargetDockerCache()}, []string{nonLocal}, &dockerMocker{supportBuildKit: false}, &cacheMocker{}, "must build for local platform"},
|
{"load docker without local platform", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir), WithBuildTargetDockerCache()}, []string{nonLocal}, &dockerMocker{supportContexts: false}, &cacheMocker{}, "must build for local platform"},
|
||||||
{"amd64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64"}, &dockerMocker{supportBuildKit: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
|
{"amd64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64"}, &dockerMocker{supportContexts: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
|
||||||
{"arm64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"arm64"}, &dockerMocker{supportBuildKit: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
|
{"arm64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"arm64"}, &dockerMocker{supportContexts: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
|
||||||
{"amd64 and arm64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64", "arm64"}, &dockerMocker{supportBuildKit: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
|
{"amd64 and arm64", Pkg{org: "foo", image: "bar", hash: "abc", arches: []string{"amd64", "arm64"}, commitHash: "HEAD"}, []BuildOpt{WithBuildCacheDir(cacheDir)}, []string{"amd64", "arm64"}, &dockerMocker{supportContexts: true, enableBuild: true}, &cacheMocker{enableImagePull: false, enableImageLoad: true, enableIndexWrite: true}, ""},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.msg, func(t *testing.T) {
|
t.Run(tt.msg, func(t *testing.T) {
|
||||||
@ -324,7 +326,12 @@ func TestBuild(t *testing.T) {
|
|||||||
// need to make sure that it was called the correct number of times with the correct arguments
|
// need to make sure that it was called the correct number of times with the correct arguments
|
||||||
t.Errorf("mismatched call to runners, should be %d was %d: %#v", len(tt.targets), len(tt.runner.builds), tt.runner.builds)
|
t.Errorf("mismatched call to runners, should be %d was %d: %#v", len(tt.targets), len(tt.runner.builds), tt.runner.builds)
|
||||||
case tt.err == "":
|
case tt.err == "":
|
||||||
// check that all of our platforms were called
|
// check that all of our platforms were called exactly once each
|
||||||
|
// we do that by:
|
||||||
|
// 1- creating a map of all of the target platforms and setting them to `false`
|
||||||
|
// 2- checking with each build for which platform it was called
|
||||||
|
//
|
||||||
|
// each build is assumed to track what platform it built
|
||||||
platformMap := map[string]bool{}
|
platformMap := map[string]bool{}
|
||||||
for _, arch := range tt.targets {
|
for _, arch := range tt.targets {
|
||||||
platformMap[fmt.Sprintf("linux/%s", arch)] = false
|
platformMap[fmt.Sprintf("linux/%s", arch)] = false
|
||||||
@ -334,6 +341,11 @@ func TestBuild(t *testing.T) {
|
|||||||
t.Errorf("mismatch in build: '%v', %#v", err, build)
|
t.Errorf("mismatch in build: '%v', %#v", err, build)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for k, v := range platformMap {
|
||||||
|
if !v {
|
||||||
|
t.Errorf("did not execute build for platform: %s", k)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -341,13 +353,7 @@ func TestBuild(t *testing.T) {
|
|||||||
|
|
||||||
// testCheckBuildRun check the output of a build run
|
// testCheckBuildRun check the output of a build run
|
||||||
func testCheckBuildRun(build buildLog, platforms map[string]bool) error {
|
func testCheckBuildRun(build buildLog, platforms map[string]bool) error {
|
||||||
for i, arg := range build.opts {
|
platform := build.platform
|
||||||
switch {
|
|
||||||
case arg == "--platform", arg == "-platform":
|
|
||||||
if i+1 >= len(build.opts) {
|
|
||||||
return errors.New("provided arg --platform with no next argument")
|
|
||||||
}
|
|
||||||
platform := build.opts[i+1]
|
|
||||||
used, ok := platforms[platform]
|
used, ok := platforms[platform]
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("requested unknown platform: %s", platform)
|
return fmt.Errorf("requested unknown platform: %s", platform)
|
||||||
@ -358,6 +364,3 @@ func testCheckBuildRun(build buildLog, platforms map[string]bool) error {
|
|||||||
platforms[platform] = true
|
platforms[platform] = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return errors.New("missing platform argument")
|
|
||||||
}
|
|
||||||
|
@ -7,6 +7,7 @@ package pkglib
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -16,24 +17,35 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
versioncompare "github.com/hashicorp/go-version"
|
versioncompare "github.com/hashicorp/go-version"
|
||||||
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/registry"
|
"github.com/linuxkit/linuxkit/src/cmd/linuxkit/registry"
|
||||||
|
buildkitClient "github.com/moby/buildkit/client"
|
||||||
|
_ "github.com/moby/buildkit/client/connhelper/dockercontainer"
|
||||||
|
_ "github.com/moby/buildkit/client/connhelper/ssh"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerfile/builder"
|
||||||
|
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
registryServer = "https://index.docker.io/v1/"
|
registryServer = "https://index.docker.io/v1/"
|
||||||
buildkitBuilderName = "linuxkit"
|
buildkitBuilderName = "linuxkit-builder"
|
||||||
|
buildkitSocketPath = "/run/buildkit/buildkitd.sock"
|
||||||
|
buildkitWaitServer = 30 // seconds
|
||||||
|
buildkitCheckInterval = 1 // seconds
|
||||||
)
|
)
|
||||||
|
|
||||||
type dockerRunner interface {
|
type dockerRunner interface {
|
||||||
buildkitCheck() error
|
|
||||||
tag(ref, tag string) error
|
tag(ref, tag string) error
|
||||||
build(tag, pkg, dockerContext, platform string, stdin io.Reader, stdout io.Writer, opts ...string) error
|
build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, stdin io.Reader, stdout io.Writer, imageBuildOpts types.ImageBuildOptions) error
|
||||||
save(tgt string, refs ...string) error
|
save(tgt string, refs ...string) error
|
||||||
load(src io.Reader) error
|
load(src io.Reader) error
|
||||||
pull(img string) (bool, error)
|
pull(img string) (bool, error)
|
||||||
|
contextSupportCheck() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type dockerRunnerImpl struct {
|
type dockerRunnerImpl struct {
|
||||||
@ -171,101 +183,134 @@ func (dr *dockerRunnerImpl) versionCheck(version string) (string, string, error)
|
|||||||
return clientVersionString, serverVersionString, nil
|
return clientVersionString, serverVersionString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// buildkitCheck checks if buildkit is supported. This is necessary because github uses some strange versions
|
// contextCheck checks if contexts are supported. This is necessary because github uses some strange versions
|
||||||
// of docker in Actions, which makes it difficult to tell if buildkit is supported.
|
// of docker in Actions, which makes it difficult to tell if context is supported.
|
||||||
// See https://github.community/t/what-really-is-docker-3-0-6/16171
|
// See https://github.community/t/what-really-is-docker-3-0-6/16171
|
||||||
func (dr *dockerRunnerImpl) buildkitCheck() error {
|
func (dr *dockerRunnerImpl) contextSupportCheck() error {
|
||||||
return dr.command(nil, ioutil.Discard, ioutil.Discard, "buildx", "ls")
|
return dr.command(nil, ioutil.Discard, ioutil.Discard, "context", "ls")
|
||||||
}
|
}
|
||||||
|
|
||||||
// builder ensure that a builder exists. Works as follows.
|
// builder ensure that a builder container exists or return an error.
|
||||||
|
//
|
||||||
|
// Process:
|
||||||
|
//
|
||||||
|
// 1. Get an appropriate docker context.
|
||||||
|
// 2. Using the appropriate context, try to find a docker container named `linuxkit-builder` in that context.
|
||||||
|
// 3. Return a reference to that container.
|
||||||
|
//
|
||||||
|
// To get the appropriate docker context:
|
||||||
|
//
|
||||||
// 1. if dockerContext is provided, try to create a builder with that context; if it succeeds, we are done; if not, return an error.
|
// 1. if dockerContext is provided, try to create a builder with that context; if it succeeds, we are done; if not, return an error.
|
||||||
// 2. try to find an existing named runner with the pattern; if it succeeds, we are done; if not, try next.
|
// 2. try to find an existing named runner with the pattern; if it succeeds, we are done; if not, try next.
|
||||||
// 3. try to create a generic builder using the default context named "linuxkit".
|
// 3. try to create a generic builder using the default context named "linuxkit".
|
||||||
func (dr *dockerRunnerImpl) builder(dockerContext, platform string) (string, error) {
|
func (dr *dockerRunnerImpl) builder(ctx context.Context, dockerContext, builderImage, platform string, restart bool) (*buildkitClient.Client, error) {
|
||||||
var (
|
|
||||||
builderName string
|
|
||||||
args = []string{"buildx", "create", "--driver", "docker-container", "--buildkitd-flags", "--allow-insecure-entitlement network.host"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// if we were given a context, we must find a builder and use it, or create one and use it
|
// if we were given a context, we must find a builder and use it, or create one and use it
|
||||||
if dockerContext != "" {
|
if dockerContext != "" {
|
||||||
// does the context exist?
|
// does the context exist?
|
||||||
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, "context", "inspect", dockerContext); err != nil {
|
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, "context", "inspect", dockerContext); err != nil {
|
||||||
return "", fmt.Errorf("provided docker context '%s' not found", dockerContext)
|
return nil, fmt.Errorf("provided docker context '%s' not found", dockerContext)
|
||||||
}
|
}
|
||||||
builderName = fmt.Sprintf("%s-%s-%s-builder", buildkitBuilderName, dockerContext, strings.ReplaceAll(platform, "/", "-"))
|
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart)
|
||||||
if err := dr.builderEnsureContainer(builderName, platform, dockerContext, args...); err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error preparing builder based on context '%s': %v", dockerContext, err)
|
return nil, fmt.Errorf("error preparing builder based on context '%s': %v", dockerContext, err)
|
||||||
}
|
}
|
||||||
return builderName, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// no provided dockerContext, so look for one based on platform-specific name
|
// no provided dockerContext, so look for one based on platform-specific name
|
||||||
dockerContext = fmt.Sprintf("%s-%s", buildkitBuilderName, strings.ReplaceAll(platform, "/", "-"))
|
dockerContext = fmt.Sprintf("%s-%s", "linuxkit", strings.ReplaceAll(platform, "/", "-"))
|
||||||
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, "context", "inspect", dockerContext); err == nil {
|
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, "context", "inspect", dockerContext); err == nil {
|
||||||
// we found an appropriately named context, so let us try to use it or error out
|
// we found an appropriately named context, so let us try to use it or error out
|
||||||
builderName = fmt.Sprintf("%s-builder", dockerContext)
|
if client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, platform, dockerContext, restart); err == nil {
|
||||||
if err := dr.builderEnsureContainer(builderName, platform, dockerContext, args...); err == nil {
|
return client, nil
|
||||||
return builderName, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a generic builder
|
// create a generic builder
|
||||||
builderName = buildkitBuilderName
|
client, err := dr.builderEnsureContainer(ctx, buildkitBuilderName, builderImage, "", "default", restart)
|
||||||
if err := dr.builderEnsureContainer(builderName, "", "", args...); err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error ensuring default builder '%s': %v", builderName, err)
|
return nil, fmt.Errorf("error ensuring builder container in default context: %v", err)
|
||||||
}
|
}
|
||||||
return builderName, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// builderEnsureContainer provided a name of a builder, ensure that the builder exists, and if not, create it
|
// builderEnsureContainer provided a name of a docker context, ensure that the builder container exists and
|
||||||
// based on the provided docker context, for the target platform.. Assumes the dockerContext already exists.
|
// is running the appropriate version of buildkit. If it does not exist, create it; if it is running
|
||||||
func (dr *dockerRunnerImpl) builderEnsureContainer(name, platform, dockerContext string, args ...string) error {
|
// but has the wrong version of buildkit, or not running buildkit at all, remove it and create an appropriate
|
||||||
|
// one.
|
||||||
|
// Returns a network connection to the buildkit builder in the container.
|
||||||
|
func (dr *dockerRunnerImpl) builderEnsureContainer(ctx context.Context, name, image, platform, dockerContext string, forceRestart bool) (*buildkitClient.Client, error) {
|
||||||
// if no error, then we have a builder already
|
// if no error, then we have a builder already
|
||||||
// inspect it to make sure it is of the right type
|
// inspect it to make sure it is of the right type
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
if err := dr.command(nil, &b, ioutil.Discard, "buildx", "inspect", name); err != nil {
|
if err := dr.command(nil, &b, ioutil.Discard, "--context", dockerContext, "container", "inspect", name); err == nil {
|
||||||
// we did not have the named builder, so create the builder
|
// we already have a container named "linuxkit-builder" in the provided context.
|
||||||
args = append(args, "--name", name)
|
var restart bool
|
||||||
msg := fmt.Sprintf("creating builder '%s'", name)
|
// get its state and config
|
||||||
if platform != "" {
|
var containerJSON []types.ContainerJSON
|
||||||
args = append(args, "--platform", platform)
|
if err := json.Unmarshal(b.Bytes(), &containerJSON); err != nil || len(containerJSON) < 1 {
|
||||||
msg = fmt.Sprintf("%s for platform '%s'", msg, platform)
|
return nil, fmt.Errorf("unable to read results of 'container inspect %s': %v", name, err)
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("%s for all supported platforms", msg)
|
|
||||||
}
|
|
||||||
if dockerContext != "" {
|
|
||||||
args = append(args, dockerContext)
|
|
||||||
msg = fmt.Sprintf("%s based on docker context '%s'", msg, dockerContext)
|
|
||||||
}
|
|
||||||
fmt.Println(msg)
|
|
||||||
return dr.command(nil, ioutil.Discard, ioutil.Discard, args...)
|
|
||||||
}
|
|
||||||
// if we got here, we found a builder already, so let us check its type
|
|
||||||
var (
|
|
||||||
scanner = bufio.NewScanner(&b)
|
|
||||||
driver string
|
|
||||||
)
|
|
||||||
for scanner.Scan() {
|
|
||||||
fields := strings.Fields(scanner.Text())
|
|
||||||
if len(fields) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if fields[0] != "Driver:" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
driver = fields[1]
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switch driver {
|
existingImage := containerJSON[0].Config.Image
|
||||||
case "":
|
|
||||||
return fmt.Errorf("builder '%s' exists but has no driver type", name)
|
switch {
|
||||||
case "docker-container":
|
case forceRestart:
|
||||||
return nil
|
// if restart==true, we always restart, else we check if it matches our requirements
|
||||||
default:
|
fmt.Printf("told to force restart, replacing existing container %s\n", name)
|
||||||
return fmt.Errorf("builder '%s' exists but has wrong driver type '%s'", name, driver)
|
restart = true
|
||||||
|
case existingImage != image:
|
||||||
|
// if image mismatches, restart
|
||||||
|
fmt.Printf("existing container %s is running image %s instead of target %s, replacing\n", name, existingImage, image)
|
||||||
|
restart = true
|
||||||
|
case !containerJSON[0].HostConfig.Privileged:
|
||||||
|
fmt.Printf("existing container %s is unprivileged, replacing\n", name)
|
||||||
|
restart = true
|
||||||
|
}
|
||||||
|
if !restart {
|
||||||
|
fmt.Printf("using existing container %s\n", name)
|
||||||
|
return buildkitClient.New(ctx, fmt.Sprintf("docker-container://%s?context=%s", name, dockerContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we made it here, we need to stop and remove the container, either because of a config mismatch,
|
||||||
|
// or because we received the CLI option
|
||||||
|
if containerJSON[0].State.Status == "running" {
|
||||||
|
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, "--context", dockerContext, "container", "stop", name); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to stop existing container %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, "--context", dockerContext, "container", "rm", name); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to remove existing container %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create the builder
|
||||||
|
args := []string{"container", "run", "-d", "--name", name, "--privileged", image, "--allow-insecure-entitlement", "network.host", "--addr", fmt.Sprintf("unix://%s", buildkitSocketPath), "--debug"}
|
||||||
|
msg := fmt.Sprintf("creating builder container '%s' in context '%s", name, dockerContext)
|
||||||
|
fmt.Println(msg)
|
||||||
|
if err := dr.command(nil, ioutil.Discard, ioutil.Discard, args...); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// wait for buildkit socket to be ready up to the timeout
|
||||||
|
fmt.Printf("waiting for buildkit builder to be ready, up to %d seconds\n", buildkitWaitServer)
|
||||||
|
timeout := time.After(buildkitWaitServer * time.Second)
|
||||||
|
ticker := time.Tick(buildkitCheckInterval * time.Second)
|
||||||
|
// Keep trying until we're timed out or get a success
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
// Got a timeout! fail with a timeout error
|
||||||
|
case <-timeout:
|
||||||
|
return nil, fmt.Errorf("could not communicate with buildkit builder at context/container %s/%s after %d seconds", dockerContext, name, buildkitWaitServer)
|
||||||
|
// Got a tick, we should try again
|
||||||
|
case <-ticker:
|
||||||
|
client, err := buildkitClient.New(ctx, fmt.Sprintf("docker-container://%s?context=%s", name, dockerContext))
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println("buildkit builder ready!")
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// got an error, wait 1 second and try again
|
||||||
|
log.Debugf("buildkitclient error: %v, waiting %d seconds and trying again", err, buildkitCheckInterval)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,37 +364,77 @@ func (dr *dockerRunnerImpl) tag(ref, tag string) error {
|
|||||||
return dr.command(nil, nil, nil, "image", "tag", ref, tag)
|
return dr.command(nil, nil, nil, "image", "tag", ref, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *dockerRunnerImpl) build(tag, pkg, dockerContext, platform string, stdin io.Reader, stdout io.Writer, opts ...string) error {
|
func (dr *dockerRunnerImpl) build(ctx context.Context, tag, pkg, dockerContext, builderImage, platform string, restart bool, stdin io.Reader, stdout io.Writer, imageBuildOpts types.ImageBuildOptions) error {
|
||||||
// ensure we have a builder
|
// ensure we have a builder
|
||||||
builderName, err := dr.builder(dockerContext, platform)
|
client, err := dr.builder(ctx, dockerContext, builderImage, platform, restart)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to ensure proper buildx builder: %v", err)
|
return fmt.Errorf("unable to ensure builder container: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"buildx", "build"}
|
frontendAttrs := map[string]string{}
|
||||||
|
|
||||||
for _, proxyVarName := range proxyEnvVars {
|
for _, proxyVarName := range proxyEnvVars {
|
||||||
if value, ok := os.LookupEnv(proxyVarName); ok {
|
if value, ok := os.LookupEnv(proxyVarName); ok {
|
||||||
args = append(args,
|
frontendAttrs[proxyVarName] = value
|
||||||
[]string{"--build-arg", fmt.Sprintf("%s=%s", proxyVarName, value)}...)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// platform
|
||||||
|
frontendAttrs["platform"] = platform
|
||||||
|
|
||||||
|
// build-args
|
||||||
|
for k, v := range imageBuildOpts.BuildArgs {
|
||||||
|
frontendAttrs[fmt.Sprintf("build-arg:%s", k)] = *v
|
||||||
|
}
|
||||||
|
|
||||||
|
// no-cache option
|
||||||
if !dr.cache {
|
if !dr.cache {
|
||||||
args = append(args, "--no-cache")
|
frontendAttrs["no-cache"] = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// network
|
||||||
|
frontendAttrs["network"] = imageBuildOpts.NetworkMode
|
||||||
|
|
||||||
|
for k, v := range imageBuildOpts.Labels {
|
||||||
|
frontendAttrs[fmt.Sprintf("label:%s", k)] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
solveOpts := buildkitClient.SolveOpt{
|
||||||
|
Frontend: "dockerfile.v0",
|
||||||
|
FrontendAttrs: frontendAttrs,
|
||||||
|
Exports: []buildkitClient.ExportEntry{
|
||||||
|
{
|
||||||
|
Type: buildkitClient.ExporterOCI,
|
||||||
|
Attrs: map[string]string{
|
||||||
|
"name": tag,
|
||||||
|
},
|
||||||
|
Output: fixedWriteCloser(&writeNopCloser{stdout}),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
args = append(args, opts...)
|
|
||||||
args = append(args, fmt.Sprintf("--builder=%s", builderName))
|
|
||||||
args = append(args, "-t", tag)
|
|
||||||
|
|
||||||
// should docker read from the build path or stdin?
|
|
||||||
buildPath := pkg
|
|
||||||
if stdin != nil {
|
if stdin != nil {
|
||||||
buildPath = "-"
|
buf := bufio.NewReader(stdin)
|
||||||
|
up := uploadprovider.New()
|
||||||
|
frontendAttrs["context"] = up.Add(buf)
|
||||||
|
solveOpts.Session = append(solveOpts.Session, up)
|
||||||
|
} else {
|
||||||
|
solveOpts.LocalDirs = map[string]string{
|
||||||
|
builder.DefaultLocalNameDockerfile: pkg,
|
||||||
|
builder.DefaultLocalNameContext: pkg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
args = append(args, buildPath)
|
|
||||||
|
|
||||||
fmt.Printf("building for platform %s using builder %s\n", platform, builderName)
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
return dr.command(stdin, stdout, nil, args...)
|
defer cancel()
|
||||||
|
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, "auto")
|
||||||
|
pw := progress.WithPrefix(printer, "", false)
|
||||||
|
ch, done := progress.NewChannel(pw)
|
||||||
|
defer func() { <-done }()
|
||||||
|
|
||||||
|
fmt.Printf("building for platform %s\n", platform)
|
||||||
|
|
||||||
|
_, err = client.Solve(ctx, nil, solveOpts, ch)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dr *dockerRunnerImpl) save(tgt string, refs ...string) error {
|
func (dr *dockerRunnerImpl) save(tgt string, refs ...string) error {
|
||||||
@ -361,3 +446,20 @@ func (dr *dockerRunnerImpl) load(src io.Reader) error {
|
|||||||
args := []string{"image", "load"}
|
args := []string{"image", "load"}
|
||||||
return dr.command(src, nil, nil, args...)
|
return dr.command(src, nil, nil, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fixedWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
|
||||||
|
return func(map[string]string) (io.WriteCloser, error) {
|
||||||
|
return wc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeNopCloser struct {
|
||||||
|
writer io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writeNopCloser) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (w *writeNopCloser) Write(p []byte) (n int, err error) {
|
||||||
|
return w.writer.Write(p)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user