浏览代码

fix builkit progressui integration (#10535)

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De loof 2 年之前
父节点
当前提交
67455e9f33
共有 3 个文件被更改,包括 89 次插入77 次删除
  1. 64 36
      pkg/compose/build.go
  2. 24 40
      pkg/compose/build_buildkit.go
  3. 1 1
      pkg/e2e/cancel_test.go

+ 64 - 36
pkg/compose/build.go

@@ -19,6 +19,7 @@ package compose
 import (
 	"context"
 	"fmt"
+	"os"
 	"path/filepath"
 
 	"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")
 }
 
-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))
 
 	buildkitEnabled, err := s.dockerCli.BuildKitEnabled()
 	if err != nil {
 		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))
 	err = InDependencyOrder(ctx, project, func(ctx context.Context, name string) error {
 		if len(options.Services) > 0 && !utils.Contains(options.Services, name) {
 			return nil
 		}
-		for i, service := range project.Services {
-			if service.Name != name {
-				continue
-			}
-
-			if service.Build == nil {
-				return nil
-			}
+		service, idx := getServiceIndex(project, name)
 
-			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 service.Build == nil {
+			return nil
+		}
 
-			if options.Memory != 0 {
-				fmt.Fprintln(s.stderr(), "WARNING: --memory is not supported by BuildKit and will be ignored.")
+		if !buildkitEnabled {
+			if service.Build.Args == nil {
+				service.Build.Args = args
+			} else {
+				service.Build.Args = service.Build.Args.OverrideBy(args)
 			}
-
-			buildOptions, err := s.toBuildOptions(project, service, options)
+			id, err := s.doBuildClassic(ctx, service, options)
 			if err != nil {
 				return err
 			}
-			buildOptions.BuildArgs = mergeArgs(buildOptions.BuildArgs, flatten(args))
-			opts := map[string]build.Options{service.Name: buildOptions}
+			builtIDs[idx] = id
 
-			ids, err := s.doBuildBuildkit(ctx, opts, options.Progress)
-			if err != nil {
-				return err
+			if options.Push {
+				return s.push(ctx, project, api.PushOptions{})
 			}
-			builtIDs[i] = ids[service.Name]
 			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
 	}, func(traversal *graphTraversal) {
 		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{}
 	for i, d := range builtIDs {
 		if d != "" {
@@ -124,6 +140,18 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti
 	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 {
 	for _, service := range project.Services {
 		if service.Image == "" && service.Build == nil {

+ 24 - 40
pkg/compose/build_buildkit.go

@@ -20,23 +20,22 @@ import (
 	"context"
 	"crypto/sha1"
 	"fmt"
-	"os"
 	"path/filepath"
 
 	_ "github.com/docker/buildx/driver/docker"           //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/remote"           //nolint:blank-imports
+	buildx "github.com/docker/buildx/util/progress"
 	"github.com/moby/buildkit/client"
 
 	"github.com/docker/buildx/build"
 	"github.com/docker/buildx/builder"
 	"github.com/docker/buildx/util/dockerutil"
-	xprogress "github.com/docker/buildx/util/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)
 	if err != nil {
 		return nil, err
@@ -49,22 +48,9 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]bu
 
 	var response map[string]*client.SolveResponse
 	if s.dryRun {
-		response = s.dryRunBuildResponse(ctx, opts)
+		response = s.dryRunBuildResponse(ctx, service, opts)
 	} else {
-		// 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, 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
-		}
+		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))
 		if err != nil {
 			return nil, WrapCategorisedComposeError(err, BuildFailure)
 		}
@@ -85,29 +71,27 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]bu
 	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)
 	buildResponse := map[string]*client.SolveResponse{}
-	for name, option := range options {
-		dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
-		w.Event(progress.Event{
-			ID:     " ",
-			Status: progress.Done,
-			Text:   fmt.Sprintf("build service %s", name),
-		})
-		w.Event(progress.Event{
-			ID:     "==>",
-			Status: progress.Done,
-			Text:   fmt.Sprintf("==> writing image %s", dryRunUUID),
-		})
-		w.Event(progress.Event{
-			ID:     "==> ==>",
-			Status: progress.Done,
-			Text:   fmt.Sprintf(`naming to %s`, option.Tags[0]),
-		})
-		buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{
-			"containerimage.digest": dryRunUUID,
-		}}
-	}
+	dryRunUUID := fmt.Sprintf("dryRun-%x", sha1.Sum([]byte(name)))
+	w.Event(progress.Event{
+		ID:     " ",
+		Status: progress.Done,
+		Text:   fmt.Sprintf("build service %s", name),
+	})
+	w.Event(progress.Event{
+		ID:     "==>",
+		Status: progress.Done,
+		Text:   fmt.Sprintf("==> writing image %s", dryRunUUID),
+	})
+	w.Event(progress.Event{
+		ID:     "==> ==>",
+		Status: progress.Done,
+		Text:   fmt.Sprintf(`naming to %s`, options.Tags[0]),
+	})
+	buildResponse[name] = &client.SolveResponse{ExporterResponse: map[string]string{
+		"containerimage.digest": dryRunUUID,
+	}}
 	return buildResponse
 }

+ 1 - 1
pkg/e2e/cancel_test.go

@@ -59,8 +59,8 @@ func TestComposeCancel(t *testing.T) {
 		}, 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
-
 		assert.NilError(t, err)
+
 		c.WaitForCondition(t, func() (bool, string) {
 			out := stdout.String()
 			errors := stderr.String()