Skip to content

Commit 21b093e

Browse files
committed
build relies on types.ContainerSpec
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 24b2a3a commit 21b093e

5 files changed

Lines changed: 94 additions & 64 deletions

File tree

pkg/compose/build.go

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
5151

5252
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]api.ImageSummary) (map[string]string, error) {
5353
imageIDs := map[string]string{}
54-
serviceToBuild := types.Services{}
54+
imagesToBuild := map[string]types.ContainerSpec{}
5555

5656
var policy types.DependencyOption = types.IgnoreDependencies
5757
if options.Deps {
@@ -63,7 +63,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
6363
}
6464

6565
// Separate job names from service names and collect buildable jobs
66-
serviceNames := collectBuildableJobs(options.Services, project, localImages, serviceToBuild)
66+
serviceNames := collectBuildableJobs(options.Services, project, localImages, imagesToBuild)
6767

6868
// also include services used as additional_contexts with service: prefix
6969
serviceNames = addBuildDependencies(serviceNames, project)
@@ -89,14 +89,14 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
8989
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
9090
return nil
9191
}
92-
serviceToBuild[serviceName] = *service
92+
imagesToBuild[serviceName] = service.ContainerSpec
9393
return nil
9494
}, policy)
9595
if err != nil {
9696
return imageIDs, err
9797
}
9898

99-
if len(serviceToBuild) == 0 {
99+
if len(imagesToBuild) == 0 {
100100
return imageIDs, nil
101101
}
102102

@@ -105,9 +105,9 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
105105
return nil, err
106106
}
107107
if bake {
108-
return s.doBuildBake(ctx, project, serviceToBuild, options)
108+
return s.doBuildBake(ctx, project, imagesToBuild, options)
109109
}
110-
return s.doBuildClassic(ctx, project, serviceToBuild, options)
110+
return s.doBuildClassic(ctx, project, imagesToBuild, options)
111111
}
112112

113113
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, buildOpts *api.BuildOptions, quietPull bool) error {
@@ -176,7 +176,7 @@ func (s *composeService) ensureImagesExists(ctx context.Context, project *types.
176176

177177
// collectBuildableJobs separates job names from service names in the given list.
178178
// Jobs that need building are added to serviceToBuild. Returns only the service names.
179-
func collectBuildableJobs(names []string, project *types.Project, localImages map[string]api.ImageSummary, serviceToBuild map[string]types.ServiceConfig) []string {
179+
func collectBuildableJobs(names []string, project *types.Project, localImages map[string]api.ImageSummary, imagesToBuild map[string]types.ContainerSpec) []string {
180180
var serviceNames []string
181181
for _, name := range names {
182182
job, ok := project.Jobs[name]
@@ -189,10 +189,7 @@ func collectBuildableJobs(names []string, project *types.Project, localImages ma
189189
}
190190
image := api.ImageNameOrDefault(job.Image, name, project.Name)
191191
if _, present := localImages[image]; !present || job.PullPolicy == types.PullPolicyBuild {
192-
serviceToBuild[name] = types.ServiceConfig{
193-
Name: name,
194-
ContainerSpec: job.ContainerSpec,
195-
}
192+
imagesToBuild[name] = job.ContainerSpec
196193
}
197194
}
198195
return serviceNames
@@ -284,9 +281,9 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
284281
//
285282
// Finally, standard proxy variables based on the Docker client configuration are added, but will not overwrite
286283
// any values if already present.
287-
func resolveAndMergeBuildArgs(proxyConfig map[string]string, project *types.Project, service types.ServiceConfig, opts api.BuildOptions) types.MappingWithEquals {
284+
func resolveAndMergeBuildArgs(proxyConfig map[string]string, project *types.Project, spec types.ContainerSpec, opts api.BuildOptions) types.MappingWithEquals {
288285
result := make(types.MappingWithEquals).
289-
OverrideBy(service.Build.Args).
286+
OverrideBy(spec.Build.Args).
290287
OverrideBy(opts.Args).
291288
Resolve(envResolver(project.Environment))
292289

@@ -301,17 +298,17 @@ func resolveAndMergeBuildArgs(proxyConfig map[string]string, project *types.Proj
301298
return result
302299
}
303300

304-
func getImageBuildLabels(project *types.Project, service types.ServiceConfig) types.Labels {
301+
func getImageBuildLabels(project *types.Project, name string, spec types.ContainerSpec) types.Labels {
305302
ret := make(types.Labels)
306-
if service.Build != nil {
307-
for k, v := range service.Build.Labels {
303+
if spec.Build != nil {
304+
for k, v := range spec.Build.Labels {
308305
ret.Add(k, v)
309306
}
310307
}
311308

312309
ret.Add(api.VersionLabel, api.ComposeVersion)
313310
ret.Add(api.ProjectLabel, project.Name)
314-
ret.Add(api.ServiceLabel, service.Name)
311+
ret.Add(api.ServiceLabel, name)
315312
return ret
316313
}
317314

pkg/compose/build_bake.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ type buildStatus struct {
115115
Image string `json:"image.name"`
116116
}
117117

118-
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
118+
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, imagesToBuild map[string]types.ContainerSpec, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
119119
eg := errgroup.Group{}
120120
ch := make(chan *client.SolveStatus)
121121
displayMode := progressui.DisplayMode(options.Progress)
@@ -143,31 +143,39 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
143143
group bakeGroup
144144
privileged bool
145145
read []string
146-
expectedImages = make(map[string]string, len(serviceToBeBuild)) // service name -> expected image
147-
targets = make(map[string]string, len(serviceToBeBuild)) // service name -> build target
146+
expectedImages = make(map[string]string, len(imagesToBuild)) // service name -> expected image
147+
targets = make(map[string]string, len(imagesToBuild)) // service name -> build target
148148
)
149149

150-
// produce a unique ID for service used as bake target
151-
for serviceName := range project.Services {
152-
t := strings.ReplaceAll(serviceName, ".", "_")
150+
// produce a unique ID for each build target (services + jobs)
151+
assignTarget := func(name string) {
152+
t := strings.ReplaceAll(name, ".", "_")
153153
for {
154-
if _, ok := targets[serviceName]; !ok {
155-
targets[serviceName] = t
156-
break
154+
if _, ok := targets[name]; !ok {
155+
targets[name] = t
156+
return
157157
}
158158
t += "_"
159159
}
160160
}
161+
for serviceName := range project.Services {
162+
assignTarget(serviceName)
163+
}
164+
for name := range imagesToBuild {
165+
if _, ok := targets[name]; !ok {
166+
assignTarget(name)
167+
}
168+
}
161169

162170
var secretsEnv []string
163-
for serviceName, service := range project.Services {
164-
if service.Build == nil {
165-
continue
171+
addBakeTarget := func(name string, spec types.ContainerSpec) {
172+
if spec.Build == nil {
173+
return
166174
}
167-
buildConfig := *service.Build
168-
labels := getImageBuildLabels(project, service)
175+
buildConfig := *spec.Build
176+
labels := getImageBuildLabels(project, name, spec)
169177

170-
args := resolveAndMergeBuildArgs(s.getProxyConfig(), project, service, options).ToMapping()
178+
args := resolveAndMergeBuildArgs(s.getProxyConfig(), project, spec, options).ToMapping()
171179
for k, v := range args {
172180
args[k] = strings.ReplaceAll(v, "${", "$${")
173181
}
@@ -183,11 +191,11 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
183191

184192
var outputs []string
185193
var call string
186-
push := options.Push && service.Image != ""
194+
push := options.Push && spec.Image != ""
187195
switch {
188196
case options.Check:
189197
call = "lint"
190-
case len(service.Build.Platforms) > 1:
198+
case len(spec.Build.Platforms) > 1:
191199
outputs = []string{fmt.Sprintf("type=image,push=%t", push)}
192200
default:
193201
if push {
@@ -205,15 +213,15 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
205213
}
206214
}
207215

208-
image := api.GetImageNameOrDefault(service, project.Name)
216+
image := api.ImageNameOrDefault(spec.Image, name, project.Name)
209217
s.events.On(buildingEvent(image))
210218

211-
expectedImages[serviceName] = image
219+
expectedImages[name] = image
212220

213-
pull := service.Build.Pull || options.Pull
214-
noCache := service.Build.NoCache || options.NoCache
221+
pull := spec.Build.Pull || options.Pull
222+
noCache := spec.Build.NoCache || options.NoCache
215223

216-
target := targets[serviceName]
224+
target := targets[name]
217225

218226
secrets, env := toBakeSecrets(project, buildConfig.Secrets)
219227
secretsEnv = append(secretsEnv, env...)
@@ -248,12 +256,22 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
248256
}
249257
}
250258

251-
// create a bake group with targets for services to build
252-
for serviceName, service := range serviceToBeBuild {
253-
if service.Build == nil {
259+
for serviceName, service := range project.Services {
260+
addBakeTarget(serviceName, service.ContainerSpec)
261+
}
262+
for name, spec := range imagesToBuild {
263+
if _, ok := project.Services[name]; ok {
264+
continue // already processed as a service
265+
}
266+
addBakeTarget(name, spec)
267+
}
268+
269+
// create a bake group with targets to build
270+
for name, spec := range imagesToBuild {
271+
if spec.Build == nil {
254272
continue
255273
}
256-
group.Targets = append(group.Targets, targets[serviceName])
274+
group.Targets = append(group.Targets, targets[name])
257275
}
258276

259277
cfg.Groups["default"] = group
@@ -397,7 +415,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
397415
}
398416

399417
results := map[string]string{}
400-
for name := range serviceToBeBuild {
418+
for name := range imagesToBuild {
401419
image := expectedImages[name]
402420
target := targets[name]
403421
built, ok := md[target]

pkg/compose/build_classic.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import (
4545
"github.com/docker/compose/v5/pkg/api"
4646
)
4747

48-
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, serviceToBuild types.Services, options api.BuildOptions) (map[string]string, error) {
48+
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, imagesToBuild map[string]types.ContainerSpec, options api.BuildOptions) (map[string]string, error) {
4949
imageIDs := map[string]string{}
5050

5151
// Not using bake, additional_context: service:xx is implemented by building images in dependency order
@@ -82,14 +82,14 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
8282

8383
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
8484
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "classic"))
85-
service, ok := serviceToBuild[name]
85+
spec, ok := imagesToBuild[name]
8686
if !ok {
8787
return nil
8888
}
8989

90-
image := api.GetImageNameOrDefault(service, project.Name)
90+
image := api.ImageNameOrDefault(spec.Image, name, project.Name)
9191
s.events.On(buildingEvent(image))
92-
id, err := s.doBuildImage(ctx, project, service, options)
92+
id, err := s.doBuildImage(ctx, project, name, spec, options)
9393
if err != nil {
9494
return err
9595
}
@@ -118,37 +118,37 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
118118
}
119119

120120
//nolint:gocyclo
121-
func (s *composeService) doBuildImage(ctx context.Context, project *types.Project, service types.ServiceConfig, options api.BuildOptions) (string, error) {
121+
func (s *composeService) doBuildImage(ctx context.Context, project *types.Project, name string, spec types.ContainerSpec, options api.BuildOptions) (string, error) {
122122
var (
123123
buildCtx io.ReadCloser
124124
dockerfileCtx io.ReadCloser
125125
contextDir string
126126
relDockerfile string
127127
)
128128

129-
if len(service.Build.Platforms) > 1 {
129+
if len(spec.Build.Platforms) > 1 {
130130
return "", fmt.Errorf("the classic builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use BuildKit")
131131
}
132-
if service.Build.Privileged {
132+
if spec.Build.Privileged {
133133
return "", fmt.Errorf("the classic builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use BuildKit")
134134
}
135-
if len(service.Build.AdditionalContexts) > 0 {
135+
if len(spec.Build.AdditionalContexts) > 0 {
136136
return "", fmt.Errorf("the classic builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit")
137137
}
138-
if len(service.Build.SSH) > 0 {
138+
if len(spec.Build.SSH) > 0 {
139139
return "", fmt.Errorf("the classic builder doesn't support SSH keys, set DOCKER_BUILDKIT=1 to use BuildKit")
140140
}
141-
if len(service.Build.Secrets) > 0 {
141+
if len(spec.Build.Secrets) > 0 {
142142
return "", fmt.Errorf("the classic builder doesn't support secrets, set DOCKER_BUILDKIT=1 to use BuildKit")
143143
}
144144

145-
if service.Build.Labels == nil {
146-
service.Build.Labels = make(map[string]string)
145+
if spec.Build.Labels == nil {
146+
spec.Build.Labels = make(map[string]string)
147147
}
148-
service.Build.Labels[api.ImageBuilderLabel] = "classic"
148+
spec.Build.Labels[api.ImageBuilderLabel] = "classic"
149149

150-
dockerfileName := dockerFilePath(service.Build.Context, service.Build.Dockerfile)
151-
specifiedContext := service.Build.Context
150+
dockerfileName := dockerFilePath(spec.Build.Context, spec.Build.Dockerfile)
151+
specifiedContext := spec.Build.Context
152152
progBuff := s.stdout()
153153
buildBuff := s.stdout()
154154

@@ -251,8 +251,8 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
251251
RegistryToken: authConfig.RegistryToken,
252252
}
253253
}
254-
buildOpts := imageBuildOptions(s.getProxyConfig(), project, service, options)
255-
imageName := api.GetImageNameOrDefault(service, project.Name)
254+
buildOpts := imageBuildOptions(s.getProxyConfig(), project, spec, options)
255+
imageName := api.ImageNameOrDefault(spec.Image, name, project.Name)
256256
buildOpts.Tags = append(buildOpts.Tags, imageName)
257257
buildOpts.Dockerfile = relDockerfile
258258
buildOpts.AuthConfigs = authConfigs
@@ -293,15 +293,15 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
293293
return imageID, nil
294294
}
295295

296-
func imageBuildOptions(proxyConfigs map[string]string, project *types.Project, service types.ServiceConfig, options api.BuildOptions) client.ImageBuildOptions {
297-
config := service.Build
296+
func imageBuildOptions(proxyConfigs map[string]string, project *types.Project, spec types.ContainerSpec, options api.BuildOptions) client.ImageBuildOptions {
297+
config := spec.Build
298298
return client.ImageBuildOptions{
299299
Version: buildtypes.BuilderV1,
300300
Tags: config.Tags,
301301
NoCache: config.NoCache,
302302
Remove: true,
303303
PullParent: config.Pull,
304-
BuildArgs: resolveAndMergeBuildArgs(proxyConfigs, project, service, options),
304+
BuildArgs: resolveAndMergeBuildArgs(proxyConfigs, project, spec, options),
305305
Labels: config.Labels,
306306
NetworkMode: config.Network,
307307
ExtraHosts: config.ExtraHosts.AsList(":"),

pkg/e2e/compose_run_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,14 @@ func TestLocalComposeRun(t *testing.T) {
315315
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/jobs.yaml", "down", "--remove-orphans")
316316
})
317317

318+
t.Run("compose run job with build", func(t *testing.T) {
319+
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/jobs.yaml", "run", "--rm", "--build", "with-build")
320+
lines := Lines(res.Stdout())
321+
assert.Equal(t, lines[len(lines)-1], "built-job-marker", res.Stdout())
322+
323+
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/jobs.yaml", "down", "--remove-orphans", "--rmi=local")
324+
})
325+
318326
t.Run("compose run unknown job or service", func(t *testing.T) {
319327
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/run-test/jobs.yaml", "run", "nonexistent")
320328
res.Assert(t, icmd.Expected{

pkg/e2e/fixtures/run-test/jobs.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,10 @@ jobs:
1818
with-command:
1919
image: alpine
2020
command: echo "default command"
21+
22+
with-build:
23+
build:
24+
dockerfile_inline: |
25+
FROM alpine
26+
RUN echo "built-job-marker" > /marker.txt
27+
command: cat /marker.txt

0 commit comments

Comments
 (0)