mirror of
https://github.com/docker/compose.git
synced 2026-05-13 13:58:02 +00:00
build relies on types.ContainerSpec
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
24b2a3ae79
commit
21b093e10f
5 changed files with 94 additions and 64 deletions
|
|
@ -51,7 +51,7 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
|
|||
|
||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions, localImages map[string]api.ImageSummary) (map[string]string, error) {
|
||||
imageIDs := map[string]string{}
|
||||
serviceToBuild := types.Services{}
|
||||
imagesToBuild := map[string]types.ContainerSpec{}
|
||||
|
||||
var policy types.DependencyOption = types.IgnoreDependencies
|
||||
if options.Deps {
|
||||
|
|
@ -63,7 +63,7 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
|||
}
|
||||
|
||||
// Separate job names from service names and collect buildable jobs
|
||||
serviceNames := collectBuildableJobs(options.Services, project, localImages, serviceToBuild)
|
||||
serviceNames := collectBuildableJobs(options.Services, project, localImages, imagesToBuild)
|
||||
|
||||
// also include services used as additional_contexts with service: prefix
|
||||
serviceNames = addBuildDependencies(serviceNames, project)
|
||||
|
|
@ -89,14 +89,14 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
|||
if localImagePresent && service.PullPolicy != types.PullPolicyBuild {
|
||||
return nil
|
||||
}
|
||||
serviceToBuild[serviceName] = *service
|
||||
imagesToBuild[serviceName] = service.ContainerSpec
|
||||
return nil
|
||||
}, policy)
|
||||
if err != nil {
|
||||
return imageIDs, err
|
||||
}
|
||||
|
||||
if len(serviceToBuild) == 0 {
|
||||
if len(imagesToBuild) == 0 {
|
||||
return imageIDs, nil
|
||||
}
|
||||
|
||||
|
|
@ -105,9 +105,9 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
|||
return nil, err
|
||||
}
|
||||
if bake {
|
||||
return s.doBuildBake(ctx, project, serviceToBuild, options)
|
||||
return s.doBuildBake(ctx, project, imagesToBuild, options)
|
||||
}
|
||||
return s.doBuildClassic(ctx, project, serviceToBuild, options)
|
||||
return s.doBuildClassic(ctx, project, imagesToBuild, options)
|
||||
}
|
||||
|
||||
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.
|
|||
|
||||
// collectBuildableJobs separates job names from service names in the given list.
|
||||
// Jobs that need building are added to serviceToBuild. Returns only the service names.
|
||||
func collectBuildableJobs(names []string, project *types.Project, localImages map[string]api.ImageSummary, serviceToBuild map[string]types.ServiceConfig) []string {
|
||||
func collectBuildableJobs(names []string, project *types.Project, localImages map[string]api.ImageSummary, imagesToBuild map[string]types.ContainerSpec) []string {
|
||||
var serviceNames []string
|
||||
for _, name := range names {
|
||||
job, ok := project.Jobs[name]
|
||||
|
|
@ -189,10 +189,7 @@ func collectBuildableJobs(names []string, project *types.Project, localImages ma
|
|||
}
|
||||
image := api.ImageNameOrDefault(job.Image, name, project.Name)
|
||||
if _, present := localImages[image]; !present || job.PullPolicy == types.PullPolicyBuild {
|
||||
serviceToBuild[name] = types.ServiceConfig{
|
||||
Name: name,
|
||||
ContainerSpec: job.ContainerSpec,
|
||||
}
|
||||
imagesToBuild[name] = job.ContainerSpec
|
||||
}
|
||||
}
|
||||
return serviceNames
|
||||
|
|
@ -284,9 +281,9 @@ func (s *composeService) getLocalImagesDigests(ctx context.Context, project *typ
|
|||
//
|
||||
// Finally, standard proxy variables based on the Docker client configuration are added, but will not overwrite
|
||||
// any values if already present.
|
||||
func resolveAndMergeBuildArgs(proxyConfig map[string]string, project *types.Project, service types.ServiceConfig, opts api.BuildOptions) types.MappingWithEquals {
|
||||
func resolveAndMergeBuildArgs(proxyConfig map[string]string, project *types.Project, spec types.ContainerSpec, opts api.BuildOptions) types.MappingWithEquals {
|
||||
result := make(types.MappingWithEquals).
|
||||
OverrideBy(service.Build.Args).
|
||||
OverrideBy(spec.Build.Args).
|
||||
OverrideBy(opts.Args).
|
||||
Resolve(envResolver(project.Environment))
|
||||
|
||||
|
|
@ -301,17 +298,17 @@ func resolveAndMergeBuildArgs(proxyConfig map[string]string, project *types.Proj
|
|||
return result
|
||||
}
|
||||
|
||||
func getImageBuildLabels(project *types.Project, service types.ServiceConfig) types.Labels {
|
||||
func getImageBuildLabels(project *types.Project, name string, spec types.ContainerSpec) types.Labels {
|
||||
ret := make(types.Labels)
|
||||
if service.Build != nil {
|
||||
for k, v := range service.Build.Labels {
|
||||
if spec.Build != nil {
|
||||
for k, v := range spec.Build.Labels {
|
||||
ret.Add(k, v)
|
||||
}
|
||||
}
|
||||
|
||||
ret.Add(api.VersionLabel, api.ComposeVersion)
|
||||
ret.Add(api.ProjectLabel, project.Name)
|
||||
ret.Add(api.ServiceLabel, service.Name)
|
||||
ret.Add(api.ServiceLabel, name)
|
||||
return ret
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ type buildStatus struct {
|
|||
Image string `json:"image.name"`
|
||||
}
|
||||
|
||||
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, serviceToBeBuild types.Services, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
|
||||
func (s *composeService) doBuildBake(ctx context.Context, project *types.Project, imagesToBuild map[string]types.ContainerSpec, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
|
||||
eg := errgroup.Group{}
|
||||
ch := make(chan *client.SolveStatus)
|
||||
displayMode := progressui.DisplayMode(options.Progress)
|
||||
|
|
@ -143,31 +143,39 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||
group bakeGroup
|
||||
privileged bool
|
||||
read []string
|
||||
expectedImages = make(map[string]string, len(serviceToBeBuild)) // service name -> expected image
|
||||
targets = make(map[string]string, len(serviceToBeBuild)) // service name -> build target
|
||||
expectedImages = make(map[string]string, len(imagesToBuild)) // service name -> expected image
|
||||
targets = make(map[string]string, len(imagesToBuild)) // service name -> build target
|
||||
)
|
||||
|
||||
// produce a unique ID for service used as bake target
|
||||
for serviceName := range project.Services {
|
||||
t := strings.ReplaceAll(serviceName, ".", "_")
|
||||
// produce a unique ID for each build target (services + jobs)
|
||||
assignTarget := func(name string) {
|
||||
t := strings.ReplaceAll(name, ".", "_")
|
||||
for {
|
||||
if _, ok := targets[serviceName]; !ok {
|
||||
targets[serviceName] = t
|
||||
break
|
||||
if _, ok := targets[name]; !ok {
|
||||
targets[name] = t
|
||||
return
|
||||
}
|
||||
t += "_"
|
||||
}
|
||||
}
|
||||
for serviceName := range project.Services {
|
||||
assignTarget(serviceName)
|
||||
}
|
||||
for name := range imagesToBuild {
|
||||
if _, ok := targets[name]; !ok {
|
||||
assignTarget(name)
|
||||
}
|
||||
}
|
||||
|
||||
var secretsEnv []string
|
||||
for serviceName, service := range project.Services {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
addBakeTarget := func(name string, spec types.ContainerSpec) {
|
||||
if spec.Build == nil {
|
||||
return
|
||||
}
|
||||
buildConfig := *service.Build
|
||||
labels := getImageBuildLabels(project, service)
|
||||
buildConfig := *spec.Build
|
||||
labels := getImageBuildLabels(project, name, spec)
|
||||
|
||||
args := resolveAndMergeBuildArgs(s.getProxyConfig(), project, service, options).ToMapping()
|
||||
args := resolveAndMergeBuildArgs(s.getProxyConfig(), project, spec, options).ToMapping()
|
||||
for k, v := range args {
|
||||
args[k] = strings.ReplaceAll(v, "${", "$${")
|
||||
}
|
||||
|
|
@ -183,11 +191,11 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||
|
||||
var outputs []string
|
||||
var call string
|
||||
push := options.Push && service.Image != ""
|
||||
push := options.Push && spec.Image != ""
|
||||
switch {
|
||||
case options.Check:
|
||||
call = "lint"
|
||||
case len(service.Build.Platforms) > 1:
|
||||
case len(spec.Build.Platforms) > 1:
|
||||
outputs = []string{fmt.Sprintf("type=image,push=%t", push)}
|
||||
default:
|
||||
if push {
|
||||
|
|
@ -205,15 +213,15 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||
}
|
||||
}
|
||||
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
image := api.ImageNameOrDefault(spec.Image, name, project.Name)
|
||||
s.events.On(buildingEvent(image))
|
||||
|
||||
expectedImages[serviceName] = image
|
||||
expectedImages[name] = image
|
||||
|
||||
pull := service.Build.Pull || options.Pull
|
||||
noCache := service.Build.NoCache || options.NoCache
|
||||
pull := spec.Build.Pull || options.Pull
|
||||
noCache := spec.Build.NoCache || options.NoCache
|
||||
|
||||
target := targets[serviceName]
|
||||
target := targets[name]
|
||||
|
||||
secrets, env := toBakeSecrets(project, buildConfig.Secrets)
|
||||
secretsEnv = append(secretsEnv, env...)
|
||||
|
|
@ -248,12 +256,22 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||
}
|
||||
}
|
||||
|
||||
// create a bake group with targets for services to build
|
||||
for serviceName, service := range serviceToBeBuild {
|
||||
if service.Build == nil {
|
||||
for serviceName, service := range project.Services {
|
||||
addBakeTarget(serviceName, service.ContainerSpec)
|
||||
}
|
||||
for name, spec := range imagesToBuild {
|
||||
if _, ok := project.Services[name]; ok {
|
||||
continue // already processed as a service
|
||||
}
|
||||
addBakeTarget(name, spec)
|
||||
}
|
||||
|
||||
// create a bake group with targets to build
|
||||
for name, spec := range imagesToBuild {
|
||||
if spec.Build == nil {
|
||||
continue
|
||||
}
|
||||
group.Targets = append(group.Targets, targets[serviceName])
|
||||
group.Targets = append(group.Targets, targets[name])
|
||||
}
|
||||
|
||||
cfg.Groups["default"] = group
|
||||
|
|
@ -397,7 +415,7 @@ func (s *composeService) doBuildBake(ctx context.Context, project *types.Project
|
|||
}
|
||||
|
||||
results := map[string]string{}
|
||||
for name := range serviceToBeBuild {
|
||||
for name := range imagesToBuild {
|
||||
image := expectedImages[name]
|
||||
target := targets[name]
|
||||
built, ok := md[target]
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import (
|
|||
"github.com/docker/compose/v5/pkg/api"
|
||||
)
|
||||
|
||||
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, serviceToBuild types.Services, options api.BuildOptions) (map[string]string, error) {
|
||||
func (s *composeService) doBuildClassic(ctx context.Context, project *types.Project, imagesToBuild map[string]types.ContainerSpec, options api.BuildOptions) (map[string]string, error) {
|
||||
imageIDs := map[string]string{}
|
||||
|
||||
// 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
|
|||
|
||||
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
|
||||
trace.SpanFromContext(ctx).SetAttributes(attribute.String("builder", "classic"))
|
||||
service, ok := serviceToBuild[name]
|
||||
spec, ok := imagesToBuild[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
image := api.GetImageNameOrDefault(service, project.Name)
|
||||
image := api.ImageNameOrDefault(spec.Image, name, project.Name)
|
||||
s.events.On(buildingEvent(image))
|
||||
id, err := s.doBuildImage(ctx, project, service, options)
|
||||
id, err := s.doBuildImage(ctx, project, name, spec, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ func (s *composeService) doBuildClassic(ctx context.Context, project *types.Proj
|
|||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func (s *composeService) doBuildImage(ctx context.Context, project *types.Project, service types.ServiceConfig, options api.BuildOptions) (string, error) {
|
||||
func (s *composeService) doBuildImage(ctx context.Context, project *types.Project, name string, spec types.ContainerSpec, options api.BuildOptions) (string, error) {
|
||||
var (
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
|
|
@ -126,29 +126,29 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
|
|||
relDockerfile string
|
||||
)
|
||||
|
||||
if len(service.Build.Platforms) > 1 {
|
||||
if len(spec.Build.Platforms) > 1 {
|
||||
return "", fmt.Errorf("the classic builder doesn't support multi-arch build, set DOCKER_BUILDKIT=1 to use BuildKit")
|
||||
}
|
||||
if service.Build.Privileged {
|
||||
if spec.Build.Privileged {
|
||||
return "", fmt.Errorf("the classic builder doesn't support privileged mode, set DOCKER_BUILDKIT=1 to use BuildKit")
|
||||
}
|
||||
if len(service.Build.AdditionalContexts) > 0 {
|
||||
if len(spec.Build.AdditionalContexts) > 0 {
|
||||
return "", fmt.Errorf("the classic builder doesn't support additional contexts, set DOCKER_BUILDKIT=1 to use BuildKit")
|
||||
}
|
||||
if len(service.Build.SSH) > 0 {
|
||||
if len(spec.Build.SSH) > 0 {
|
||||
return "", fmt.Errorf("the classic builder doesn't support SSH keys, set DOCKER_BUILDKIT=1 to use BuildKit")
|
||||
}
|
||||
if len(service.Build.Secrets) > 0 {
|
||||
if len(spec.Build.Secrets) > 0 {
|
||||
return "", fmt.Errorf("the classic builder doesn't support secrets, set DOCKER_BUILDKIT=1 to use BuildKit")
|
||||
}
|
||||
|
||||
if service.Build.Labels == nil {
|
||||
service.Build.Labels = make(map[string]string)
|
||||
if spec.Build.Labels == nil {
|
||||
spec.Build.Labels = make(map[string]string)
|
||||
}
|
||||
service.Build.Labels[api.ImageBuilderLabel] = "classic"
|
||||
spec.Build.Labels[api.ImageBuilderLabel] = "classic"
|
||||
|
||||
dockerfileName := dockerFilePath(service.Build.Context, service.Build.Dockerfile)
|
||||
specifiedContext := service.Build.Context
|
||||
dockerfileName := dockerFilePath(spec.Build.Context, spec.Build.Dockerfile)
|
||||
specifiedContext := spec.Build.Context
|
||||
progBuff := s.stdout()
|
||||
buildBuff := s.stdout()
|
||||
|
||||
|
|
@ -251,8 +251,8 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
|
|||
RegistryToken: authConfig.RegistryToken,
|
||||
}
|
||||
}
|
||||
buildOpts := imageBuildOptions(s.getProxyConfig(), project, service, options)
|
||||
imageName := api.GetImageNameOrDefault(service, project.Name)
|
||||
buildOpts := imageBuildOptions(s.getProxyConfig(), project, spec, options)
|
||||
imageName := api.ImageNameOrDefault(spec.Image, name, project.Name)
|
||||
buildOpts.Tags = append(buildOpts.Tags, imageName)
|
||||
buildOpts.Dockerfile = relDockerfile
|
||||
buildOpts.AuthConfigs = authConfigs
|
||||
|
|
@ -293,15 +293,15 @@ func (s *composeService) doBuildImage(ctx context.Context, project *types.Projec
|
|||
return imageID, nil
|
||||
}
|
||||
|
||||
func imageBuildOptions(proxyConfigs map[string]string, project *types.Project, service types.ServiceConfig, options api.BuildOptions) client.ImageBuildOptions {
|
||||
config := service.Build
|
||||
func imageBuildOptions(proxyConfigs map[string]string, project *types.Project, spec types.ContainerSpec, options api.BuildOptions) client.ImageBuildOptions {
|
||||
config := spec.Build
|
||||
return client.ImageBuildOptions{
|
||||
Version: buildtypes.BuilderV1,
|
||||
Tags: config.Tags,
|
||||
NoCache: config.NoCache,
|
||||
Remove: true,
|
||||
PullParent: config.Pull,
|
||||
BuildArgs: resolveAndMergeBuildArgs(proxyConfigs, project, service, options),
|
||||
BuildArgs: resolveAndMergeBuildArgs(proxyConfigs, project, spec, options),
|
||||
Labels: config.Labels,
|
||||
NetworkMode: config.Network,
|
||||
ExtraHosts: config.ExtraHosts.AsList(":"),
|
||||
|
|
|
|||
|
|
@ -315,6 +315,14 @@ func TestLocalComposeRun(t *testing.T) {
|
|||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/jobs.yaml", "down", "--remove-orphans")
|
||||
})
|
||||
|
||||
t.Run("compose run job with build", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/jobs.yaml", "run", "--rm", "--build", "with-build")
|
||||
lines := Lines(res.Stdout())
|
||||
assert.Equal(t, lines[len(lines)-1], "built-job-marker", res.Stdout())
|
||||
|
||||
c.RunDockerComposeCmd(t, "-f", "./fixtures/run-test/jobs.yaml", "down", "--remove-orphans", "--rmi=local")
|
||||
})
|
||||
|
||||
t.Run("compose run unknown job or service", func(t *testing.T) {
|
||||
res := c.RunDockerComposeCmdNoCheck(t, "-f", "./fixtures/run-test/jobs.yaml", "run", "nonexistent")
|
||||
res.Assert(t, icmd.Expected{
|
||||
|
|
|
|||
|
|
@ -18,3 +18,10 @@ jobs:
|
|||
with-command:
|
||||
image: alpine
|
||||
command: echo "default command"
|
||||
|
||||
with-build:
|
||||
build:
|
||||
dockerfile_inline: |
|
||||
FROM alpine
|
||||
RUN echo "built-job-marker" > /marker.txt
|
||||
command: cat /marker.txt
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue