Skip to content

Commit 6c1f06e

Browse files
committed
Run classic builder with BuildConfig, not buildx.Options
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 88b0d17 commit 6c1f06e

File tree

5 files changed

+111
-132
lines changed

5 files changed

+111
-132
lines changed

pkg/api/api.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"time"
2424

2525
"github.com/compose-spec/compose-go/types"
26+
"github.com/docker/compose/v2/pkg/utils"
2627
)
2728

2829
// Service manages a compose project
@@ -107,6 +108,38 @@ type BuildOptions struct {
107108
SSHs []types.SSHKey
108109
}
109110

111+
// Apply mutates project according to build options
112+
func (o BuildOptions) Apply(project *types.Project) error {
113+
platform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
114+
for i, service := range project.Services {
115+
if service.Image == "" && service.Build == nil {
116+
return fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
117+
}
118+
119+
if service.Build == nil {
120+
continue
121+
}
122+
service.Image = GetImageNameOrDefault(service, project.Name)
123+
if platform != "" {
124+
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, platform) {
125+
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", service.Name, platform)
126+
}
127+
service.Platform = platform
128+
}
129+
if service.Platform != "" {
130+
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, service.Platform) {
131+
return fmt.Errorf("service %q build configuration does not support platform: %s", service.Name, service.Platform)
132+
}
133+
}
134+
135+
service.Build.Pull = service.Build.Pull || o.Pull
136+
service.Build.NoCache = service.Build.NoCache || o.NoCache
137+
138+
project.Services[i] = service
139+
}
140+
return nil
141+
}
142+
110143
// CreateOptions group options of the Create API
111144
type CreateOptions struct {
112145
// Services defines the services user interacts with

pkg/compose/build.go

Lines changed: 41 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -43,43 +43,63 @@ import (
4343
)
4444

4545
func (s *composeService) Build(ctx context.Context, project *types.Project, options api.BuildOptions) error {
46+
err := options.Apply(project)
47+
if err != nil {
48+
return err
49+
}
4650
return progress.Run(ctx, func(ctx context.Context) error {
4751
_, err := s.build(ctx, project, options)
4852
return err
4953
}, s.stderr())
5054
}
5155

5256
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) {
53-
args := flatten(options.Args.Resolve(envResolver(project.Environment)))
57+
args := options.Args.Resolve(envResolver(project.Environment))
5458

59+
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
60+
if err != nil {
61+
return nil, err
62+
}
5563
builtIDs := make([]string, len(project.Services))
56-
err := InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
64+
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
5765
if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
5866
return nil
5967
}
6068
for i, service := range project.Services {
6169
if service.Name != name {
6270
continue
6371
}
64-
service, err := project.GetService(name)
65-
if err != nil {
66-
return err
67-
}
72+
6873
if service.Build == nil {
6974
return nil
7075
}
71-
imageName := api.GetImageNameOrDefault(service, project.Name)
72-
buildOptions, err := s.toBuildOptions(project, service, imageName, options)
76+
77+
if !buildkitEnabled {
78+
service.Build.Args = service.Build.Args.OverrideBy(args)
79+
id, err := s.doBuildClassic(ctx, service)
80+
if err != nil {
81+
return err
82+
}
83+
builtIDs[i] = id
84+
85+
if options.Push {
86+
return s.push(ctx, project, api.PushOptions{})
87+
}
88+
return nil
89+
}
90+
buildOptions, err := s.toBuildOptions(project, service, options)
7391
if err != nil {
7492
return err
7593
}
76-
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, args)
77-
opts := map[string]build.Options{imageName: buildOptions}
78-
ids, err := s.doBuild(ctx, project, opts, options.Progress)
94+
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, flatten(args))
95+
opts := map[string]build.Options{service.Name: buildOptions}
96+
97+
ids, err := s.doBuildBuildkit(ctx, opts, options.Progress)
7998
if err != nil {
8099
return err
81100
}
82-
builtIDs[i] = ids[imageName]
101+
builtIDs[i] = ids[service.Name]
102+
return nil
83103
}
84104
return nil
85105
}, func(traversal *graphTraversal) {
@@ -146,39 +166,26 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
146166
}
147167

148168
func (s *composeService) prepareProjectForBuild(project *types.Project, images map[string]string) error {
149-
platform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
169+
err := api.BuildOptions{}.Apply(project)
170+
if err != nil {
171+
return err
172+
}
150173
for i, service := range project.Services {
151-
if service.Image == "" && service.Build == nil {
152-
return fmt.Errorf("invalid service %q. Must specify either image or build", service.Name)
153-
}
154174
if service.Build == nil {
155175
continue
156176
}
157177

158-
imageName := api.GetImageNameOrDefault(service, project.Name)
159-
service.Image = imageName
160-
161-
_, localImagePresent := images[imageName]
178+
_, localImagePresent := images[service.Image]
162179
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
163180
service.Build = nil
164181
project.Services[i] = service
165182
continue
166183
}
167184

168-
if platform != "" {
169-
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, platform) {
170-
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", service.Name, platform)
171-
}
172-
service.Platform = platform
173-
}
174-
175185
if service.Platform == "" {
176186
// let builder to build for default platform
177187
service.Build.Platforms = nil
178188
} else {
179-
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, service.Platform) {
180-
return fmt.Errorf("service %q build configuration does not support platform: %s", service.Name, platform)
181-
}
182189
service.Build.Platforms = []string{service.Platform}
183190
}
184191
project.Services[i] = service
@@ -214,19 +221,8 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
214221
return images, nil
215222
}
216223

217-
func (s *composeService) doBuild(ctx context.Context, project *types.Project, opts map[string]build.Options, mode string) (map[string]string, error) {
218-
if len(opts) == 0 {
219-
return nil, nil
220-
}
221-
if buildkitEnabled, err := s.dockerCli.BuildKitEnabled(); err != nil || !buildkitEnabled {
222-
return s.doBuildClassic(ctx, project, opts)
223-
}
224-
return s.doBuildBuildkit(ctx, opts, mode)
225-
}
226-
227-
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, imageTag string, options api.BuildOptions) (build.Options, error) {
228-
var tags []string
229-
tags = append(tags, imageTag)
224+
func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
225+
tags := []string{service.Image}
230226

231227
buildArgs := flatten(service.Build.Args.Resolve(envResolver(project.Environment)))
232228

@@ -304,8 +300,8 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
304300
},
305301
CacheFrom: cacheFrom,
306302
CacheTo: cacheTo,
307-
NoCache: service.Build.NoCache || options.NoCache,
308-
Pull: service.Build.Pull || options.Pull,
303+
NoCache: service.Build.NoCache,
304+
Pull: service.Build.Pull,
309305
BuildArgs: buildArgs,
310306
Tags: tags,
311307
Target: service.Build.Target,

pkg/compose/build_classic.go

Lines changed: 32 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -27,55 +27,23 @@ import (
2727
"strings"
2828

2929
"github.com/compose-spec/compose-go/types"
30-
buildx "github.com/docker/buildx/build"
3130
"github.com/docker/cli/cli"
3231
"github.com/docker/cli/cli/command/image/build"
33-
"github.com/docker/compose/v2/pkg/utils"
3432
dockertypes "github.com/docker/docker/api/types"
33+
"github.com/docker/docker/api/types/container"
3534
"github.com/docker/docker/builder/remotecontext/urlutil"
3635
"github.com/docker/docker/pkg/archive"
3736
"github.com/docker/docker/pkg/idtools"
3837
"github.com/docker/docker/pkg/jsonmessage"
3938
"github.com/docker/docker/pkg/progress"
4039
"github.com/docker/docker/pkg/streamformatter"
41-
"github.com/hashicorp/go-multierror"
42-
"github.com/moby/buildkit/util/entitlements"
4340
"github.com/pkg/errors"
4441

4542
"github.com/docker/compose/v2/pkg/api"
4643
)
4744

48-
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, opts map[string]buildx.Options) (map[string]string, error) {
49-
nameDigests := make(map[string]string)
50-
var errs error
51-
err := project.WithServices(nil, func(service types.ServiceConfig) error {
52-
imageName := api.GetImageNameOrDefault(service, project.Name)
53-
o, ok := opts[imageName]
54-
if !ok {
55-
return nil
56-
}
57-
digest, err := s.doBuildClassicSimpleImage(ctx, o)
58-
if err != nil {
59-
errs = multierror.Append(errs, err).ErrorOrNil()
60-
}
61-
nameDigests[imageName] = digest
62-
if errs != nil {
63-
return nil
64-
}
65-
if len(o.Exports) != 0 && o.Exports[0].Attrs["push"] == "true" {
66-
return s.push(ctx, project, api.PushOptions{})
67-
}
68-
return nil
69-
})
70-
if err != nil {
71-
return nil, err
72-
}
73-
74-
return nameDigests, errs
75-
}
76-
7745
//nolint:gocyclo
78-
func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options buildx.Options) (string, error) {
46+
func (s *composeService) doBuildClassic(ctx context.Context, service types.ServiceConfig) (string, error) {
7947
var (
8048
buildCtx io.ReadCloser
8149
dockerfileCtx io.ReadCloser
@@ -86,31 +54,31 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
8654
err error
8755
)
8856

89-
dockerfileName := options.Inputs.DockerfilePath
90-
specifiedContext := options.Inputs.ContextPath
57+
dockerfileName := dockerFilePath(service.Build.Context, service.Build.Dockerfile)
58+
specifiedContext := service.Build.Context
9159
progBuff := s.stdout()
9260
buildBuff := s.stdout()
93-
if options.ImageIDFile != "" {
94-
// Avoid leaving a stale file if we eventually fail
95-
if err := os.Remove(options.ImageIDFile); err != nil && !os.IsNotExist(err) {
96-
return "", errors.Wrap(err, "removing image ID file")
97-
}
98-
}
9961

100-
if len(options.Platforms) > 1 {
101-
return "", errors.Errorf("this builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use multi-arch builder")
62+
if len(service.Build.Platforms) > 1 {
63+
return "", errors.Errorf("the classic builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use BuildKit")
64+
}
65+
if service.Build.Privileged {
66+
return "", errors.Errorf("the classic builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use BuildKit")
67+
}
68+
if len(service.Build.AdditionalContexts) > 0 {
69+
return "", errors.Errorf("the classic builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit")
10270
}
103-
if utils.Contains(options.Allow, entitlements.EntitlementSecurityInsecure) {
104-
return "", errors.Errorf("this builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use builder supporting privileged mode")
71+
if len(service.Build.SSH) > 0 {
72+
return "", errors.Errorf("the classic builder doesn't support SSH keys, set DOCKER_BUILDKIT=1 to use BuildKit")
10573
}
106-
if len(options.Inputs.NamedContexts) > 0 {
107-
return "", errors.Errorf("this builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit which does")
74+
if len(service.Build.Secrets) > 0 {
75+
return "", errors.Errorf("the classic builder doesn't support secrets, set DOCKER_BUILDKIT=1 to use BuildKit")
10876
}
10977

110-
if options.Labels == nil {
111-
options.Labels = make(map[string]string)
78+
if service.Build.Labels == nil {
79+
service.Build.Labels = make(map[string]string)
11280
}
113-
options.Labels[api.ImageBuilderLabel] = "classic"
81+
service.Build.Labels[api.ImageBuilderLabel] = "classic"
11482

11583
switch {
11684
case isLocalDir(specifiedContext):
@@ -189,8 +157,8 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
189157
for k, auth := range creds {
190158
authConfigs[k] = dockertypes.AuthConfig(auth)
191159
}
192-
buildOptions := imageBuildOptions(options)
193-
buildOptions.Version = dockertypes.BuilderV1
160+
buildOptions := imageBuildOptions(service.Build)
161+
buildOptions.Tags = append(buildOptions.Tags, service.Image)
194162
buildOptions.Dockerfile = relDockerfile
195163
buildOptions.AuthConfigs = authConfigs
196164

@@ -235,15 +203,6 @@ func (s *composeService) doBuildClassicSimpleImage(ctx context.Context, options
235203
"files and directories.")
236204
}
237205

238-
if options.ImageIDFile != "" {
239-
if imageID == "" {
240-
return "", errors.Errorf("Server did not provide an image ID. Cannot write %s", options.ImageIDFile)
241-
}
242-
if err := os.WriteFile(options.ImageIDFile, []byte(imageID), 0o666); err != nil {
243-
return "", err
244-
}
245-
}
246-
247206
return imageID, nil
248207
}
249208

@@ -252,25 +211,18 @@ func isLocalDir(c string) bool {
252211
return err == nil
253212
}
254213

255-
func imageBuildOptions(options buildx.Options) dockertypes.ImageBuildOptions {
214+
func imageBuildOptions(config *types.BuildConfig) dockertypes.ImageBuildOptions {
256215
return dockertypes.ImageBuildOptions{
257-
Tags: options.Tags,
258-
NoCache: options.NoCache,
216+
Version: dockertypes.BuilderV1,
217+
Tags: config.Tags,
218+
NoCache: config.NoCache,
259219
Remove: true,
260-
PullParent: options.Pull,
261-
BuildArgs: toMapStringStringPtr(options.BuildArgs),
262-
Labels: options.Labels,
263-
NetworkMode: options.NetworkMode,
264-
ExtraHosts: options.ExtraHosts,
265-
Target: options.Target,
266-
}
267-
}
268-
269-
func toMapStringStringPtr(source map[string]string) map[string]*string {
270-
dest := make(map[string]*string)
271-
for k, v := range source {
272-
v := v
273-
dest[k] = &v
220+
PullParent: config.Pull,
221+
BuildArgs: config.Args,
222+
Labels: config.Labels,
223+
NetworkMode: config.Network,
224+
ExtraHosts: config.ExtraHosts.AsList(),
225+
Target: config.Target,
226+
Isolation: container.Isolation(config.Isolation),
274227
}
275-
return dest
276228
}

0 commit comments

Comments
 (0)