From 2ed82c7c0ac98b3c981cc4bae958e53d891ff026 Mon Sep 17 00:00:00 2001 From: Benjamin Karran Date: Tue, 29 Nov 2022 14:47:17 +0100 Subject: [PATCH] feat: Add buildx option for daemon-less BuildKit support `docker buildx build` can be configured to execute a remote buildkit instance. No docker daemon is necessary for this to work. This commit removes dependencies on docker daemon if `buildx` is used, so `skaffold build` can be used in a CI without docker daemon. --- pkg/skaffold/build/docker/docker.go | 14 ++++++++++++-- pkg/skaffold/build/docker/docker_test.go | 4 ++-- pkg/skaffold/build/docker/errors_test.go | 2 +- pkg/skaffold/build/docker/types.go | 4 +++- pkg/skaffold/build/local/local.go | 9 +++++++-- pkg/skaffold/build/local/types.go | 2 +- pkg/skaffold/schema/latest/config.go | 2 ++ 7 files changed, 28 insertions(+), 9 deletions(-) diff --git a/pkg/skaffold/build/docker/docker.go b/pkg/skaffold/build/docker/docker.go index 523eb196d0a..94683bb73aa 100644 --- a/pkg/skaffold/build/docker/docker.go +++ b/pkg/skaffold/build/docker/docker.go @@ -72,7 +72,7 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, a *latest.Artifact, // ignore useCLI boolean if buildkit is enabled since buildkit is only implemented for docker CLI at the moment in skaffold. // we might consider a different approach in the future. // use CLI for cross-platform builds - if b.useCLI || (b.useBuildKit != nil && *b.useBuildKit) || len(a.DockerArtifact.CliFlags) > 0 || matcher.IsNotEmpty() { + if b.useCLI || b.buildx || (b.useBuildKit != nil && *b.useBuildKit) || len(a.DockerArtifact.CliFlags) > 0 || matcher.IsNotEmpty() { imageID, err = b.dockerCLIBuild(ctx, output.GetUnderlyingWriter(out), a.ImageName, a.Workspace, dockerfile, a.ArtifactType.DockerArtifact, opts, pl) } else { imageID, err = b.localDocker.Build(ctx, out, a.Workspace, a.ImageName, a.ArtifactType.DockerArtifact, opts) @@ -93,6 +93,9 @@ func (b *Builder) Build(ctx context.Context, out io.Writer, a *latest.Artifact, func (b *Builder) dockerCLIBuild(ctx context.Context, out io.Writer, name string, workspace string, dockerfilePath string, a *latest.DockerArtifact, opts docker.BuildOptions, pl v1.Platform) (string, error) { args := []string{"build", workspace, "--file", dockerfilePath, "-t", opts.Tag} + if b.buildx { + args = append([]string{"buildx"}, args...) + } imgRef, err := docker.ParseReference(opts.Tag) if err != nil { return "", fmt.Errorf("couldn't parse image tag: %w", err) @@ -110,6 +113,9 @@ func (b *Builder) dockerCLIBuild(ctx context.Context, out io.Writer, name string if err != nil { return "", fmt.Errorf("getting docker build args: %w", err) } + if b.buildx && b.pushImages { + cliArgs = append(cliArgs, "--push") + } args = append(args, cliArgs...) if b.cfg.Prune() { @@ -142,7 +148,11 @@ func (b *Builder) dockerCLIBuild(ctx context.Context, out io.Writer, name string return "", tryExecFormatErr(fmt.Errorf("running build: %w", err), errBuffer) } - return b.localDocker.ImageID(ctx, opts.Tag) + if b.buildx { + return "", nil // TODO: return id from CLI + } else { + return b.localDocker.ImageID(ctx, opts.Tag) + } } func (b *Builder) pullCacheFromImages(ctx context.Context, out io.Writer, a *latest.DockerArtifact, pl v1.Platform) error { diff --git a/pkg/skaffold/build/docker/docker_test.go b/pkg/skaffold/build/docker/docker_test.go index 220888a0971..59eee3c806b 100644 --- a/pkg/skaffold/build/docker/docker_test.go +++ b/pkg/skaffold/build/docker/docker_test.go @@ -161,7 +161,7 @@ func TestDockerCLIBuild(t *testing.T) { } t.Override(&util.OSEnviron, func() []string { return []string{"KEY=VALUE"} }) - builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv(test.extraEnv), test.cfg, test.localBuild.UseDockerCLI, test.localBuild.UseBuildkit, false, mockArtifactResolver{make(map[string]string)}, nil) + builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv(test.extraEnv), test.cfg, test.localBuild.UseDockerCLI, false, test.localBuild.UseBuildkit, false, mockArtifactResolver{make(map[string]string)}, nil) artifact := &latest.Artifact{ Workspace: ".", @@ -242,7 +242,7 @@ func TestDockerCLICheckCacheFromArgs(t *testing.T) { ) t.Override(&util.DefaultExecCommand, mockCmd) - builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv([]string{}), mockConfig{}, true, util.Ptr(false), false, mockArtifactResolver{make(map[string]string)}, nil) + builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv([]string{}), mockConfig{}, true, false, util.Ptr(false), false, mockArtifactResolver{make(map[string]string)}, nil) _, err := builder.Build(context.Background(), io.Discard, &a, test.tag, platform.Matcher{}) t.CheckNoError(err) }) diff --git a/pkg/skaffold/build/docker/errors_test.go b/pkg/skaffold/build/docker/errors_test.go index 32686d38117..2341541dc0c 100644 --- a/pkg/skaffold/build/docker/errors_test.go +++ b/pkg/skaffold/build/docker/errors_test.go @@ -60,7 +60,7 @@ Refer https://skaffold.dev/docs/references/yaml/#build-artifacts-docker for deta "docker build . --file "+dockerfilePath+" -t tag", )) t.Override(&docker.DefaultAuthHelper, stubAuth{}) - builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv([]string{}), mockConfig{}, true, nil, false, mockArtifactResolver{make(map[string]string)}, nil) + builder := NewArtifactBuilder(fakeLocalDaemonWithExtraEnv([]string{}), mockConfig{}, true, false, nil, false, mockArtifactResolver{make(map[string]string)}, nil) artifact := &latest.Artifact{ ImageName: "test-image", diff --git a/pkg/skaffold/build/docker/types.go b/pkg/skaffold/build/docker/types.go index 00ad02ff680..9f6f7efc978 100644 --- a/pkg/skaffold/build/docker/types.go +++ b/pkg/skaffold/build/docker/types.go @@ -29,6 +29,7 @@ type Builder struct { cfg docker.Config pushImages bool useCLI bool + buildx bool useBuildKit *bool artifacts ArtifactResolver sourceDependencies TransitiveSourceDependenciesResolver @@ -45,12 +46,13 @@ type TransitiveSourceDependenciesResolver interface { } // NewBuilder returns an new instance of a docker builder -func NewArtifactBuilder(localDocker docker.LocalDaemon, cfg docker.Config, useCLI bool, useBuildKit *bool, pushImages bool, ar ArtifactResolver, dr TransitiveSourceDependenciesResolver) *Builder { +func NewArtifactBuilder(localDocker docker.LocalDaemon, cfg docker.Config, useCLI bool, buildx bool, useBuildKit *bool, pushImages bool, ar ArtifactResolver, dr TransitiveSourceDependenciesResolver) *Builder { return &Builder{ localDocker: localDocker, pushImages: pushImages, cfg: cfg, useCLI: useCLI, + buildx: buildx, useBuildKit: useBuildKit, artifacts: ar, sourceDependencies: dr, diff --git a/pkg/skaffold/build/local/local.go b/pkg/skaffold/build/local/local.go index f1cc930ffc3..10a557b50f4 100644 --- a/pkg/skaffold/build/local/local.go +++ b/pkg/skaffold/build/local/local.go @@ -90,11 +90,16 @@ func (b *Builder) buildArtifact(ctx context.Context, out io.Writer, a *latest.Ar imageID := digestOrImageID b.builtImages = append(b.builtImages, imageID) - return build.TagWithImageID(ctx, tag, imageID, b.localDocker) + + if b.local.Buildx { + return "", nil + } else { + return build.TagWithImageID(ctx, tag, imageID, b.localDocker) + } } func (b *Builder) runBuildForArtifact(ctx context.Context, out io.Writer, a *latest.Artifact, tag string, platforms platform.Matcher) (string, error) { - if !b.pushImages { + if !b.local.Buildx && !b.pushImages { // All of the builders will rely on a local Docker: // + Either to build the image, // + Or to docker load it. diff --git a/pkg/skaffold/build/local/types.go b/pkg/skaffold/build/local/types.go index 94893324846..7123d7d4f3e 100644 --- a/pkg/skaffold/build/local/types.go +++ b/pkg/skaffold/build/local/types.go @@ -148,7 +148,7 @@ type artifactBuilder interface { func newPerArtifactBuilder(b *Builder, a *latest.Artifact) (artifactBuilder, error) { switch { case a.DockerArtifact != nil: - return dockerbuilder.NewArtifactBuilder(b.localDocker, b.cfg, b.local.UseDockerCLI, b.local.UseBuildkit, b.pushImages, b.artifactStore, b.sourceDependencies), nil + return dockerbuilder.NewArtifactBuilder(b.localDocker, b.cfg, b.local.UseDockerCLI, b.local.Buildx, b.local.UseBuildkit, b.pushImages, b.artifactStore, b.sourceDependencies), nil case a.BazelArtifact != nil: return bazel.NewArtifactBuilder(b.localDocker, b.cfg, b.pushImages), nil diff --git a/pkg/skaffold/schema/latest/config.go b/pkg/skaffold/schema/latest/config.go index 10266ccd68d..534d8985f84 100644 --- a/pkg/skaffold/schema/latest/config.go +++ b/pkg/skaffold/schema/latest/config.go @@ -311,6 +311,8 @@ type LocalBuild struct { // UseDockerCLI use `docker` command-line interface instead of Docker Engine APIs. UseDockerCLI bool `yaml:"useDockerCLI,omitempty"` + Buildx bool `yaml:"buildx,omitempty"` + // UseBuildkit use BuildKit to build Docker images. If unspecified, uses the Docker default. UseBuildkit *bool `yaml:"useBuildkit,omitempty"`