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 © 179 }