github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/docker/docker.go (about)

     1  /*
     2  Copyright 2020 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package docker
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"os/exec"
    25  
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/instrumentation"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/output/log"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    32  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    33  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util/stringslice"
    34  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings"
    35  )
    36  
    37  func (b *Builder) SupportedPlatforms() platform.Matcher {
    38  	return platform.All
    39  }
    40  
    41  func (b *Builder) Build(ctx context.Context, out io.Writer, a *latest.Artifact, tag string, matcher platform.Matcher) (string, error) {
    42  	a = adjustCacheFrom(a, tag)
    43  	instrumentation.AddAttributesToCurrentSpanFromContext(ctx, map[string]string{
    44  		"BuildType":   "docker",
    45  		"Context":     instrumentation.PII(a.Workspace),
    46  		"Destination": instrumentation.PII(tag),
    47  	})
    48  
    49  	// Fail fast if the Dockerfile can't be found.
    50  	dockerfile, err := docker.NormalizeDockerfilePath(a.Workspace, a.DockerArtifact.DockerfilePath)
    51  	if err != nil {
    52  		return "", dockerfileNotFound(fmt.Errorf("normalizing dockerfile path: %w", err), a.ImageName)
    53  	}
    54  	if _, err := os.Stat(dockerfile); os.IsNotExist(err) {
    55  		return "", dockerfileNotFound(err, a.ImageName)
    56  	}
    57  
    58  	if err := b.pullCacheFromImages(ctx, out, a.ArtifactType.DockerArtifact); err != nil {
    59  		return "", cacheFromPullErr(err, a.ImageName)
    60  	}
    61  	opts := docker.BuildOptions{Tag: tag, Mode: b.cfg.Mode(), ExtraBuildArgs: docker.ResolveDependencyImages(a.Dependencies, b.artifacts, true)}
    62  
    63  	var imageID string
    64  
    65  	// ignore useCLI boolean if buildkit is enabled since buildkit is only implemented for docker CLI at the moment in skaffold.
    66  	// we might consider a different approach in the future.
    67  	// use CLI for cross-platform builds
    68  	if b.useCLI || (b.useBuildKit != nil && *b.useBuildKit) || len(a.DockerArtifact.CliFlags) > 0 || matcher.IsNotEmpty() {
    69  		imageID, err = b.dockerCLIBuild(ctx, output.GetUnderlyingWriter(out), a.ImageName, a.Workspace, dockerfile, a.ArtifactType.DockerArtifact, opts, matcher)
    70  	} else {
    71  		imageID, err = b.localDocker.Build(ctx, out, a.Workspace, a.ImageName, a.ArtifactType.DockerArtifact, opts)
    72  	}
    73  
    74  	if err != nil {
    75  		return "", newBuildError(err, b.cfg)
    76  	}
    77  
    78  	if b.pushImages {
    79  		// TODO (tejaldesai) Remove https://github.com/GoogleContainerTools/skaffold/blob/main/pkg/skaffold/errors/err_map.go#L56
    80  		// and instead define a pushErr() method here.
    81  		return b.localDocker.Push(ctx, out, tag)
    82  	}
    83  
    84  	return imageID, nil
    85  }
    86  
    87  func (b *Builder) dockerCLIBuild(ctx context.Context, out io.Writer, name string, workspace string, dockerfilePath string, a *latest.DockerArtifact, opts docker.BuildOptions, matcher platform.Matcher) (string, error) {
    88  	if matcher.IsMultiPlatform() {
    89  		// TODO: implement multi platform build
    90  		log.Entry(ctx).Warnf("multiple target platforms %q found for artifact %q. Skaffold doesn't yet support multi-platform builds for the docker builder. Consider specifying a single target platform explicitly. See https://skaffold.dev/docs/pipeline-stages/builders/#cross-platform-build-support", matcher.String(), name)
    91  	}
    92  
    93  	args := []string{"build", workspace, "--file", dockerfilePath, "-t", opts.Tag}
    94  	ba, err := docker.EvalBuildArgs(b.cfg.Mode(), workspace, a.DockerfilePath, a.BuildArgs, opts.ExtraBuildArgs)
    95  	if err != nil {
    96  		return "", fmt.Errorf("unable to evaluate build args: %w", err)
    97  	}
    98  	cliArgs, err := docker.ToCLIBuildArgs(a, ba)
    99  	if err != nil {
   100  		return "", fmt.Errorf("getting docker build args: %w", err)
   101  	}
   102  	args = append(args, cliArgs...)
   103  
   104  	if b.cfg.Prune() {
   105  		args = append(args, "--force-rm")
   106  	}
   107  
   108  	if len(matcher.Platforms) == 1 {
   109  		args = append(args, "--platform", platform.Format(matcher.Platforms[0]))
   110  	}
   111  
   112  	cmd := exec.CommandContext(ctx, "docker", args...)
   113  	cmd.Env = append(util.OSEnviron(), b.localDocker.ExtraEnv()...)
   114  	if b.useBuildKit != nil {
   115  		if *b.useBuildKit {
   116  			cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=1")
   117  		} else {
   118  			cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=0")
   119  		}
   120  	} else if len(matcher.Platforms) == 1 { // cross-platform builds require buildkit
   121  		log.Entry(ctx).Debugf("setting DOCKER_BUILDKIT=1 for docker build for artifact %q since it targets platform %q", name, matcher.Platforms[0])
   122  		cmd.Env = append(cmd.Env, "DOCKER_BUILDKIT=1")
   123  	}
   124  	cmd.Stdout = out
   125  	cmd.Stderr = out
   126  
   127  	if err := util.RunCmd(ctx, cmd); err != nil {
   128  		return "", fmt.Errorf("running build: %w", err)
   129  	}
   130  
   131  	return b.localDocker.ImageID(ctx, opts.Tag)
   132  }
   133  
   134  func (b *Builder) pullCacheFromImages(ctx context.Context, out io.Writer, a *latest.DockerArtifact) error {
   135  	if len(a.CacheFrom) == 0 {
   136  		return nil
   137  	}
   138  
   139  	for _, image := range a.CacheFrom {
   140  		imageID, err := b.localDocker.ImageID(ctx, image)
   141  		if err != nil {
   142  			return fmt.Errorf("getting imageID for %q: %w", image, err)
   143  		}
   144  		if imageID != "" {
   145  			// already pulled
   146  			continue
   147  		}
   148  
   149  		if err := b.localDocker.Pull(ctx, out, image); err != nil {
   150  			warnings.Printf("cacheFrom image couldn't be pulled: %s\n", image)
   151  		}
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // adjustCacheFrom returns an artifact where any cache references from the artifactImage is changed to the tagged built image name instead.
   158  func adjustCacheFrom(a *latest.Artifact, artifactTag string) *latest.Artifact {
   159  	if os.Getenv("SKAFFOLD_DISABLE_DOCKER_CACHE_ADJUSTMENT") != "" {
   160  		// allow this behaviour to be disabled
   161  		return a
   162  	}
   163  
   164  	if !stringslice.Contains(a.DockerArtifact.CacheFrom, a.ImageName) {
   165  		return a
   166  	}
   167  
   168  	cf := make([]string, 0, len(a.DockerArtifact.CacheFrom))
   169  	for _, image := range a.DockerArtifact.CacheFrom {
   170  		if image == a.ImageName {
   171  			cf = append(cf, artifactTag)
   172  		} else {
   173  			cf = append(cf, image)
   174  		}
   175  	}
   176  	copy := *a
   177  	copy.DockerArtifact.CacheFrom = cf
   178  	return &copy
   179  }