From eca1365d429973e62987c70fc8ed28c10b366d84 Mon Sep 17 00:00:00 2001 From: Guillaume Lours <705411+glours@users.noreply.github.com> Date: Tue, 2 May 2023 20:23:26 +0200 Subject: [PATCH] cli: dry run support for `build` (#10502) * add dry-run support for classic builder * add dry-run support for buildkit Signed-off-by: Guillaume Lours <705411+glours@users.noreply.github.com> --- pkg/api/dryrunclient.go | 15 +++++++- pkg/compose/build.go | 11 ------ pkg/compose/build_buildkit.go | 69 ++++++++++++++++++++++++++--------- 3 files changed, 66 insertions(+), 29 deletions(-) diff --git a/pkg/api/dryrunclient.go b/pkg/api/dryrunclient.go index cc7f02e54..0fd989320 100644 --- a/pkg/api/dryrunclient.go +++ b/pkg/api/dryrunclient.go @@ -165,7 +165,20 @@ func (d *DryRunClient) CopyToContainer(ctx context.Context, container, path stri } func (d *DryRunClient) ImageBuild(ctx context.Context, reader io.Reader, options moby.ImageBuildOptions) (moby.ImageBuildResponse, error) { - return moby.ImageBuildResponse{}, ErrNotImplemented + jsonMessage, err := json.Marshal(&jsonmessage.JSONMessage{ + Status: fmt.Sprintf("%[1]sSuccessfully built: dryRunID\n%[1]sSuccessfully tagged: %[2]s\n", DRYRUN_PREFIX, options.Tags[0]), + Progress: &jsonmessage.JSONProgress{}, + ID: "", + }) + if err != nil { + return moby.ImageBuildResponse{}, err + } + rc := io.NopCloser(bytes.NewReader(jsonMessage)) + + return moby.ImageBuildResponse{ + Body: rc, + OSType: "", + }, nil } func (d *DryRunClient) ImageInspectWithRaw(ctx context.Context, imageName string) (moby.ImageInspect, []byte, error) { diff --git a/pkg/compose/build.go b/pkg/compose/build.go index c5f1ffaee..694c345c5 100644 --- a/pkg/compose/build.go +++ b/pkg/compose/build.go @@ -53,7 +53,6 @@ func (s *composeService) Build(ctx context.Context, project *types.Project, opti }, s.stderr(), "Building") } -//nolint:gocyclo func (s *composeService) build(ctx context.Context, project *types.Project, options api.BuildOptions) (map[string]string, error) { args := options.Args.Resolve(envResolver(project.Environment)) @@ -75,16 +74,6 @@ func (s *composeService) build(ctx context.Context, project *types.Project, opti return nil } - //TODO:glours - condition to be removed when dry-run support of build will be implemented. - if s.dryRun { - builder := "buildkit" - if !buildkitEnabled { - builder = "legacy builder" - } - fmt.Printf("%sBuilding image %s with %s\n", api.DRYRUN_PREFIX, service.Image, builder) - return nil - } - if !buildkitEnabled { if service.Build.Args == nil { service.Build.Args = args diff --git a/pkg/compose/build_buildkit.go b/pkg/compose/build_buildkit.go index 373d392b3..7ee35b51d 100644 --- a/pkg/compose/build_buildkit.go +++ b/pkg/compose/build_buildkit.go @@ -18,6 +18,8 @@ package compose import ( "context" + "crypto/sha1" + "fmt" "os" "path/filepath" @@ -25,11 +27,13 @@ import ( _ "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 + "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) { @@ -43,23 +47,27 @@ func (s *composeService) doBuildBuildkit(ctx context.Context, opts map[string]bu 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, 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 { - return nil, WrapCategorisedComposeError(err, BuildFailure) + var response map[string]*client.SolveResponse + if s.dryRun { + response = s.dryRunBuildResponse(ctx, 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 + } + if err != nil { + return nil, WrapCategorisedComposeError(err, BuildFailure) + } } imagesBuilt := map[string]string{} @@ -76,3 +84,30 @@ 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 { + 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, + }} + } + return buildResponse +}