diff --git a/commands/imagetools/create.go b/commands/imagetools/create.go index 7426cd04..1f67b974 100644 --- a/commands/imagetools/create.go +++ b/commands/imagetools/create.go @@ -1,6 +1,7 @@ package commands import ( + "context" "encoding/json" "fmt" "os" @@ -9,6 +10,7 @@ import ( "github.com/docker/buildx/store" "github.com/docker/buildx/store/storeutil" "github.com/docker/buildx/util/imagetools" + "github.com/docker/buildx/util/progress" "github.com/docker/cli/cli/command" "github.com/docker/distribution/reference" "github.com/moby/buildkit/util/appcontext" @@ -25,6 +27,7 @@ type createOptions struct { tags []string dryrun bool actionAppend bool + progress string } func runCreate(dockerCli command.Cli, in createOptions, args []string) error { @@ -177,18 +180,45 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error { // new resolver cause need new auth r = imagetools.New(imageopt) + ctx2, cancel := context.WithCancel(context.TODO()) + defer cancel() + printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress) + + eg, _ := errgroup.WithContext(ctx) + pw := progress.WithPrefix(printer, "internal", true) + for _, t := range tags { - if err := r.Copy(ctx, srcs, t); err != nil { - return err - } + t := t + eg.Go(func() error { + return progress.Wrap(fmt.Sprintf("pushing %s", t.String()), pw.Write, func(sub progress.SubLogger) error { + eg2, _ := errgroup.WithContext(ctx) + for _, s := range srcs { + if reference.Domain(s.Ref) == reference.Domain(t) && reference.Path(s.Ref) == reference.Path(t) { + continue + } + s := s + eg2.Go(func() error { + sub.Log(1, []byte(fmt.Sprintf("copying %s from %s to %s\n", s.Desc.Digest.String(), s.Ref.String(), t.String()))) + return r.Copy(ctx, s, t) + }) + } + + if err := eg2.Wait(); err != nil { + return err + } + sub.Log(1, []byte(fmt.Sprintf("pushing %s to %s\n", desc.Digest.String(), t.String()))) + return r.Push(ctx, t, desc, dt) + }) + }) + } - if err := r.Push(ctx, t, desc, dt); err != nil { - return err - } - fmt.Println(t.String()) + err = eg.Wait() + err1 := printer.Wait() + if err == nil { + err = err1 } - return nil + return err } func parseSources(in []string) ([]*imagetools.Source, error) { @@ -261,6 +291,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command { flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Set reference for new image") flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing") flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest") + flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`) return cmd } diff --git a/docs/reference/buildx_imagetools_create.md b/docs/reference/buildx_imagetools_create.md index 0ecb4eeb..146ac4c7 100644 --- a/docs/reference/buildx_imagetools_create.md +++ b/docs/reference/buildx_imagetools_create.md @@ -15,6 +15,7 @@ Create a new image based on source images | [`--builder`](#builder) | `string` | | Override the configured builder instance | | [`--dry-run`](#dry-run) | | | Show final image instead of pushing | | [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file | +| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output | | [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image | diff --git a/util/imagetools/create.go b/util/imagetools/create.go index 2275636d..22462041 100644 --- a/util/imagetools/create.go +++ b/util/imagetools/create.go @@ -170,30 +170,23 @@ func (r *Resolver) Push(ctx context.Context, ref reference.Named, desc ocispec.D return err } -func (r *Resolver) Copy(ctx context.Context, srcs []*Source, dest reference.Named) error { +func (r *Resolver) Copy(ctx context.Context, src *Source, dest reference.Named) error { dest = reference.TagNameOnly(dest) p, err := r.resolver().Pusher(ctx, dest.String()) if err != nil { return err } - for _, src := range srcs { - if reference.Domain(src.Ref) == reference.Domain(dest) && reference.Path(src.Ref) == reference.Path(dest) { - continue - } - - srcRef := reference.TagNameOnly(src.Ref) - f, err := r.resolver().Fetcher(ctx, srcRef.String()) - if err != nil { - return err - } - - err = contentutil.CopyChain(ctx, contentutil.FromPusher(p), contentutil.FromFetcher(f), src.Desc) - if err != nil { - return err - } + srcRef := reference.TagNameOnly(src.Ref) + f, err := r.resolver().Fetcher(ctx, srcRef.String()) + if err != nil { + return err } + err = contentutil.CopyChain(ctx, contentutil.FromPusher(p), contentutil.FromFetcher(f), src.Desc) + if err != nil { + return err + } return nil }