Skip to content

Commit bfa5408

Browse files
authored
build: fix missing proxy build args for classic builder (docker#10887)
Refactor to use a consistent code path for determining the build args for a service image regardless of whether BuildKit or the classic builder is being used. After recent changes, these code paths had diverged, so the classic builder was missing the proxy variables from the Docker client config. Signed-off-by: Milas Bowman <[email protected]>
1 parent 0be8e4a commit bfa5408

File tree

3 files changed

+63
-54
lines changed

3 files changed

+63
-54
lines changed

pkg/compose/build.go

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,19 @@ import (
2222
"os"
2323
"path/filepath"
2424

25-
"github.com/docker/buildx/builder"
26-
"github.com/docker/compose/v2/internal/tracing"
27-
28-
"github.com/docker/buildx/controller/pb"
29-
3025
"github.com/compose-spec/compose-go/types"
3126
"github.com/containerd/containerd/platforms"
3227
"github.com/docker/buildx/build"
33-
_ "github.com/docker/buildx/driver/docker" // required to get default driver registered
28+
"github.com/docker/buildx/builder"
29+
"github.com/docker/buildx/controller/pb"
3430
"github.com/docker/buildx/store/storeutil"
3531
"github.com/docker/buildx/util/buildflags"
3632
xprogress "github.com/docker/buildx/util/progress"
33+
"github.com/docker/cli/cli/command"
34+
"github.com/docker/compose/v2/internal/tracing"
35+
"github.com/docker/compose/v2/pkg/api"
36+
"github.com/docker/compose/v2/pkg/progress"
37+
"github.com/docker/compose/v2/pkg/utils"
3738
"github.com/docker/docker/builder/remotecontext/urlutil"
3839
bclient "github.com/moby/buildkit/client"
3940
"github.com/moby/buildkit/session"
@@ -45,9 +46,8 @@ import (
4546
"github.com/pkg/errors"
4647
"github.com/sirupsen/logrus"
4748

48-
"github.com/docker/compose/v2/pkg/api"
49-
"github.com/docker/compose/v2/pkg/progress"
50-
"github.com/docker/compose/v2/pkg/utils"
49+
// required to get default driver registered
50+
_ "github.com/docker/buildx/driver/docker"
5151
)
5252

5353
func (s *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
@@ -61,9 +61,8 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
6161
}, s.stdinfo(), "Building")
6262
}
6363

64-
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
65-
args := options.Args.Resolve(envResolver(project.Environment))
66-
64+
//nolint:gocyclo
65+
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) {
6766
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
6867
if err != nil {
6968
return nil, err
@@ -119,12 +118,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
119118
}
120119

121120
if !buildkitEnabled {
122-
if service.Build.Args == nil {
123-
service.Build.Args = args
124-
} else {
125-
service.Build.Args = service.Build.Args.OverrideBy(args)
126-
}
127-
id, err := s.doBuildClassic(ctx, project.Name, service, options)
121+
id, err := s.doBuildClassic(ctx, project, service, options)
128122
if err != nil {
129123
return err
130124
}
@@ -144,7 +138,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
144138
if err != nil {
145139
return err
146140
}
147-
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, flatten(args))
148141

149142
digest, err := s.doBuildBuildkit(ctx, service.Name, buildOptions, w, nodes)
150143
if err != nil {
@@ -337,15 +330,37 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
337330
return images, nil
338331
}
339332

340-
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
341-
buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
342-
343-
for k, v := range storeutil.GetProxyConfig(s.dockerCli) {
344-
if _, ok := buildArgs[k]; !ok {
345-
buildArgs[k] = v
346-
}
347-
}
333+
// resolveAndMergeBuildArgs returns the final set of build arguments to use for the service image build.
334+
//
335+
// First, args directly defined via `build.args` in YAML are considered.
336+
// Then, any explicitly passed args in opts (e.g. via `--build-arg` on the CLI) are merged, overwriting any
337+
// keys that already exist.
338+
// Next, any keys without a value are resolved using the project environment.
339+
//
340+
// Finally, standard proxy variables based on the Docker client configuration are added, but will not overwrite
341+
// any values if already present.
342+
func resolveAndMergeBuildArgs(
343+
dockerCli command.Cli,
344+
project *types.Project,
345+
service types.ServiceConfig,
346+
opts api.BuildOptions,
347+
) types.MappingWithEquals {
348+
result := make(types.MappingWithEquals).
349+
OverrideBy(service.Build.Args).
350+
OverrideBy(opts.Args).
351+
Resolve(envResolver(project.Environment))
352+
353+
// proxy arguments do NOT override and should NOT have env resolution applied,
354+
// so they're handled last
355+
for k, v := range storeutil.GetProxyConfig(dockerCli) {
356+
if _, ok := result[k]; !ok {
357+
result[k] = &v
358+
}
359+
}
360+
return result
361+
}
348362

363+
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
349364
plats, err := addPlatforms(project, service)
350365
if err != nil {
351366
return build.Options{}, err
@@ -418,7 +433,7 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
418433
CacheTo: pb.CreateCaches(cacheTo),
419434
NoCache: service.Build.NoCache,
420435
Pull: service.Build.Pull,
421-
BuildArgs: buildArgs,
436+
BuildArgs: flatten(resolveAndMergeBuildArgs(s.dockerCli, project, service, options)),
422437
Tags: tags,
423438
Target: service.Build.Target,
424439
Exports: exports,
@@ -445,16 +460,6 @@ func flatten(in types.MappingWithEquals) types.Mapping {
445460
return out
446461
}
447462

448-
func mergeArgs(m ...types.Mapping) types.Mapping {
449-
merged := types.Mapping{}
450-
for _, mapping := range m {
451-
for key, val := range mapping {
452-
merged[key] = val
453-
}
454-
}
455-
return merged
456-
}
457-
458463
func dockerFilePath(ctxName string, dockerfile string) string {
459464
if dockerfile == "" {
460465
return ""

pkg/compose/build_classic.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import (
2626
"runtime"
2727
"strings"
2828

29+
"github.com/docker/cli/cli/command"
30+
2931
"github.com/docker/docker/api/types/registry"
3032

3133
"github.com/compose-spec/compose-go/types"
@@ -45,7 +47,7 @@ import (
4547
)
4648

4749
//nolint:gocyclo
48-
func (s *composeService) doBuildClassic(ctx context.Context, projectName string, service types.ServiceConfig, options api.BuildOptions) (string, error) {
50+
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, service types.ServiceConfig, options api.BuildOptions) (string, error) {
4951
var (
5052
buildCtx io.ReadCloser
5153
dockerfileCtx io.ReadCloser
@@ -159,8 +161,8 @@ func (s *composeService) doBuildClassic(ctx context.Context, projectName string,
159161
for k, auth := range creds {
160162
authConfigs[k] = registry.AuthConfig(auth)
161163
}
162-
buildOptions := imageBuildOptions(service.Build)
163-
imageName := api.GetImageNameOrDefault(service, projectName)
164+
buildOptions := imageBuildOptions(s.dockerCli, project, service, options)
165+
imageName := api.GetImageNameOrDefault(service, project.Name)
164166
buildOptions.Tags = append(buildOptions.Tags, imageName)
165167
buildOptions.Dockerfile = relDockerfile
166168
buildOptions.AuthConfigs = authConfigs
@@ -215,14 +217,15 @@ func isLocalDir(c string) bool {
215217
return err == nil
216218
}
217219

218-
func imageBuildOptions(config *types.BuildConfig) dockertypes.ImageBuildOptions {
220+
func imageBuildOptions(dockerCli command.Cli, project *types.Project, service types.ServiceConfig, options api.BuildOptions) dockertypes.ImageBuildOptions {
221+
config := service.Build
219222
return dockertypes.ImageBuildOptions{
220223
Version: dockertypes.BuilderV1,
221224
Tags: config.Tags,
222225
NoCache: config.NoCache,
223226
Remove: true,
224227
PullParent: config.Pull,
225-
BuildArgs: config.Args,
228+
BuildArgs: resolveAndMergeBuildArgs(dockerCli, project, service, options),
226229
Labels: config.Labels,
227230
NetworkMode: config.Network,
228231
ExtraHosts: config.ExtraHosts.AsList(),

pkg/e2e/build_test.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ func TestLocalComposeBuild(t *testing.T) {
3838

3939
t.Run(env+" build named and unnamed images", func(t *testing.T) {
4040
// ensure local test run does not reuse previously build image
41-
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
42-
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
41+
c.RunDockerOrExitError(t, "rmi", "-f", "build-test-nginx")
42+
c.RunDockerOrExitError(t, "rmi", "-f", "custom-nginx")
4343

4444
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build")
4545

@@ -50,8 +50,8 @@ func TestLocalComposeBuild(t *testing.T) {
5050

5151
t.Run(env+" build with build-arg", func(t *testing.T) {
5252
// ensure local test run does not reuse previously build image
53-
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
54-
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
53+
c.RunDockerOrExitError(t, "rmi", "-f", "build-test-nginx")
54+
c.RunDockerOrExitError(t, "rmi", "-f", "custom-nginx")
5555

5656
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "build", "--build-arg", "FOO=BAR")
5757

@@ -61,8 +61,8 @@ func TestLocalComposeBuild(t *testing.T) {
6161

6262
t.Run(env+" build with build-arg set by env", func(t *testing.T) {
6363
// ensure local test run does not reuse previously build image
64-
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
65-
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
64+
c.RunDockerOrExitError(t, "rmi", "-f", "build-test-nginx")
65+
c.RunDockerOrExitError(t, "rmi", "-f", "custom-nginx")
6666

6767
icmd.RunCmd(c.NewDockerComposeCmd(t,
6868
"--project-directory",
@@ -72,7 +72,7 @@ func TestLocalComposeBuild(t *testing.T) {
7272
"FOO"),
7373
func(cmd *icmd.Cmd) {
7474
cmd.Env = append(cmd.Env, "FOO=BAR")
75-
})
75+
}).Assert(t, icmd.Success)
7676

7777
res := c.RunDockerCmd(t, "image", "inspect", "build-test-nginx")
7878
res.Assert(t, icmd.Expected{Out: `"FOO": "BAR"`})
@@ -92,8 +92,9 @@ func TestLocalComposeBuild(t *testing.T) {
9292
})
9393

9494
t.Run(env+" build as part of up", func(t *testing.T) {
95-
c.RunDockerOrExitError(t, "rmi", "build-test-nginx")
96-
c.RunDockerOrExitError(t, "rmi", "custom-nginx")
95+
// ensure local test run does not reuse previously build image
96+
c.RunDockerOrExitError(t, "rmi", "-f", "build-test-nginx")
97+
c.RunDockerOrExitError(t, "rmi", "-f", "custom-nginx")
9798

9899
res := c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "up", "-d")
99100
t.Cleanup(func() {
@@ -130,8 +131,8 @@ func TestLocalComposeBuild(t *testing.T) {
130131

131132
t.Run(env+" cleanup build project", func(t *testing.T) {
132133
c.RunDockerComposeCmd(t, "--project-directory", "fixtures/build-test", "down")
133-
c.RunDockerCmd(t, "rmi", "build-test-nginx")
134-
c.RunDockerCmd(t, "rmi", "custom-nginx")
134+
c.RunDockerOrExitError(t, "rmi", "-f", "build-test-nginx")
135+
c.RunDockerOrExitError(t, "rmi", "-f", "custom-nginx")
135136
})
136137
}
137138

0 commit comments

Comments
 (0)