mirror of
https://github.com/docker/compose.git
synced 2025-07-25 22:54:54 +02:00
fix builkit progressui integration (#10535)
Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
parent
5fdcaa0fe1
commit
67455e9f33
@ -19,6 +19,7 @@ package compose
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/compose-spec/compose-go/types"
|
"github.com/compose-spec/compose-go/types"
|
||||||
@ -53,68 +54,83 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti
|
|||||||
}, s.stderr(), "Building")
|
}, s.stderr(), "Building")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) {
|
func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) { //nolint:gocyclo
|
||||||
args := options.Args.Resolve(envResolver(project.Environment))
|
args := options.Args.Resolve(envResolver(project.Environment))
|
||||||
|
|
||||||
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
|
buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Progress needs its own context that lives longer than the
|
||||||
|
// build one otherwise it won't read all the messages from
|
||||||
|
// build and will lock
|
||||||
|
progressCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
w, err := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, options.Progress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
builtIDs := make([]string, len(project.Services))
|
builtIDs := make([]string, len(project.Services))
|
||||||
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
|
err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
|
||||||
if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
|
if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
for i, service := range project.Services {
|
service, idx := getServiceIndex(project, name)
|
||||||
if service.Name != name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if service.Build == nil {
|
if service.Build == nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if !buildkitEnabled {
|
|
||||||
if service.Build.Args == nil {
|
|
||||||
service.Build.Args = args
|
|
||||||
} else {
|
|
||||||
service.Build.Args = service.Build.Args.OverrideBy(args)
|
|
||||||
}
|
|
||||||
id, err := s.doBuildClassic(ctx, service, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
builtIDs[i] = id
|
|
||||||
|
|
||||||
if options.Push {
|
|
||||||
return s.push(ctx, project, api.PushOptions{})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.Memory != 0 {
|
|
||||||
fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored.")
|
|
||||||
}
|
|
||||||
|
|
||||||
buildOptions, err := s.toBuildOptions(project, service, options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, flatten(args))
|
|
||||||
opts := map[string]build.Options{service.Name: buildOptions}
|
|
||||||
|
|
||||||
ids, err := s.doBuildBuildkit(ctx, opts, options.Progress)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
builtIDs[i] = ids[service.Name]
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !buildkitEnabled {
|
||||||
|
if service.Build.Args == nil {
|
||||||
|
service.Build.Args = args
|
||||||
|
} else {
|
||||||
|
service.Build.Args = service.Build.Args.OverrideBy(args)
|
||||||
|
}
|
||||||
|
id, err := s.doBuildClassic(ctx, service, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
builtIDs[idx] = id
|
||||||
|
|
||||||
|
if options.Push {
|
||||||
|
return s.push(ctx, project, api.PushOptions{})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Memory != 0 {
|
||||||
|
fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored.")
|
||||||
|
}
|
||||||
|
|
||||||
|
buildOptions, err := s.toBuildOptions(project, service, options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, flatten(args))
|
||||||
|
|
||||||
|
ids, err := s.doBuildBuildkit(ctx, service.Name, buildOptions, w)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
builtIDs[idx] = ids[service.Name]
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}, func(traversal *graphTraversal) {
|
}, func(traversal *graphTraversal) {
|
||||||
traversal.maxConcurrency = s.maxConcurrency
|
traversal.maxConcurrency = s.maxConcurrency
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// enforce all build event get consumed
|
||||||
|
if errw := w.Wait(); errw != nil {
|
||||||
|
return nil, errw
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
imageIDs := map[string]string{}
|
imageIDs := map[string]string{}
|
||||||
for i, d := range builtIDs {
|
for i, d := range builtIDs {
|
||||||
if d != "" {
|
if d != "" {
|
||||||
@ -124,6 +140,18 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
|
|||||||
return imageIDs, err
|
return imageIDs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getServiceIndex(project *types.Project, name string) (types.ServiceConfig, int) {
|
||||||
|
var service types.ServiceConfig
|
||||||
|
var idx int
|
||||||
|
for i, s := range project.Services {
|
||||||
|
if s.Name == name {
|
||||||
|
idx, service = i, s
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return service, idx
|
||||||
|
}
|
||||||
|
|
||||||
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, quietPull bool) error {
|
func (s *composeService) ensureImagesExists(ctx context.Context, project *types.Project, quietPull bool) error {
|
||||||
for _, service := range project.Services {
|
for _, service := range project.Services {
|
||||||
if service.Image == "" && service.Build == nil {
|
if service.Image == "" && service.Build == nil {
|
||||||
|
@ -20,23 +20,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
_ "github.com/docker/buildx/driver/docker" //nolint:blank-imports
|
_ "github.com/docker/buildx/driver/docker" //nolint:blank-imports
|
||||||
_ "github.com/docker/buildx/driver/docker-container" //nolint:blank-imports
|
_ "github.com/docker/buildx/driver/docker-container" //nolint:blank-imports
|
||||||
_ "github.com/docker/buildx/driver/kubernetes" //nolint:blank-imports
|
_ "github.com/docker/buildx/driver/kubernetes" //nolint:blank-imports
|
||||||
_ "github.com/docker/buildx/driver/remote" //nolint:blank-imports
|
_ "github.com/docker/buildx/driver/remote" //nolint:blank-imports
|
||||||
|
buildx "github.com/docker/buildx/util/progress"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/builder"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/util/dockerutil"
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
xprogress "github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/compose/v2/pkg/progress"
|
"github.com/docker/compose/v2/pkg/progress"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]build.Options, mode string) (map[string]string, error) {
|
func (s *composeService) doBuildBuildkit(ctx context.Context, service string, opts build.Options, p *buildx.Printer) (map[string]string, error) {
|
||||||
b, err := builder.New(s.dockerCli)
|
b, err := builder.New(s.dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -49,22 +48,9 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]bu
|
|||||||
|
|
||||||
var response map[string]*client.SolveResponse
|
var response map[string]*client.SolveResponse
|
||||||
if s.dryRun {
|
if s.dryRun {
|
||||||
response = s.dryRunBuildResponse(ctx, opts)
|
response = s.dryRunBuildResponse(ctx, service, opts)
|
||||||
} else {
|
} else {
|
||||||
// Progress needs its own context that lives longer than the
|
response, err = build.Build(ctx, nodes, map[string]build.Options{service: opts}, dockerutil.NewClient(s.dockerCli), filepath.Dir(s.configFile().Filename), buildx.WithPrefix(p, service, true))
|
||||||
// build one otherwise it won't read all the messages from
|
|
||||||
// build and will lock
|
|
||||||
progressCtx, cancel := context.WithCancel(context.Background())
|
|
||||||
defer cancel()
|
|
||||||
w, err := xprogress.NewPrinter(progressCtx, s.stdout(), os.Stdout, mode)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
response, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(s.dockerCli), filepath.Dir(s.configFile().Filename), w)
|
|
||||||
errW := w.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = errW
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, WrapCategorisedComposeError(err, BuildFailure)
|
return nil, WrapCategorisedComposeError(err, BuildFailure)
|
||||||
}
|
}
|
||||||
@ -85,29 +71,27 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]bu
|
|||||||
return imagesBuilt, err
|
return imagesBuilt, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s composeService) dryRunBuildResponse(ctx context.Context, options map[string]build.Options) map[string]*client.SolveResponse {
|
func (s composeService) dryRunBuildResponse(ctx context.Context, name string, options build.Options) map[string]*client.SolveResponse {
|
||||||
w := progress.ContextWriter(ctx)
|
w := progress.ContextWriter(ctx)
|
||||||
buildResponse := map[string]*client.SolveResponse{}
|
buildResponse := map[string]*client.SolveResponse{}
|
||||||
for name, option := range options {
|
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
|
||||||
dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
|
w.Event(progress.Event{
|
||||||
w.Event(progress.Event{
|
ID: " ",
|
||||||
ID: " ",
|
Status: progress.Done,
|
||||||
Status: progress.Done,
|
Text: fmt.Sprintf("build service %s", name),
|
||||||
Text: fmt.Sprintf("build service %s", name),
|
})
|
||||||
})
|
w.Event(progress.Event{
|
||||||
w.Event(progress.Event{
|
ID: "==>",
|
||||||
ID: "==>",
|
Status: progress.Done,
|
||||||
Status: progress.Done,
|
Text: fmt.Sprintf("==> writing image %s", dryRunUUID),
|
||||||
Text: fmt.Sprintf("==> writing image %s", dryRunUUID),
|
})
|
||||||
})
|
w.Event(progress.Event{
|
||||||
w.Event(progress.Event{
|
ID: "==> ==>",
|
||||||
ID: "==> ==>",
|
Status: progress.Done,
|
||||||
Status: progress.Done,
|
Text: fmt.Sprintf(`naming to %s`, options.Tags[0]),
|
||||||
Text: fmt.Sprintf(`naming to %s`, option.Tags[0]),
|
})
|
||||||
})
|
buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{
|
||||||
buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{
|
"containerimage.digest": dryRunUUID,
|
||||||
"containerimage.digest": dryRunUUID,
|
}}
|
||||||
}}
|
|
||||||
}
|
|
||||||
return buildResponse
|
return buildResponse
|
||||||
}
|
}
|
||||||
|
@ -59,8 +59,8 @@ func TestComposeCancel(t *testing.T) {
|
|||||||
}, 30*time.Second, 1*time.Second)
|
}, 30*time.Second, 1*time.Second)
|
||||||
|
|
||||||
err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default
|
err = syscall.Kill(-cmd.Process.Pid, syscall.SIGINT) // simulate Ctrl-C : send signal to processGroup, children will have same groupId by default
|
||||||
|
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
c.WaitForCondition(t, func() (bool, string) {
|
c.WaitForCondition(t, func() (bool, string) {
|
||||||
out := stdout.String()
|
out := stdout.String()
|
||||||
errors := stderr.String()
|
errors := stderr.String()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user