diff --git a/cmd/ctr/commands/content/content.go b/cmd/ctr/commands/content/content.go index f8828e064..02f7285bb 100644 --- a/cmd/ctr/commands/content/content.go +++ b/cmd/ctr/commands/content/content.go @@ -29,6 +29,7 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/log" + "github.com/containerd/containerd/remotes" units "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -47,6 +48,7 @@ var ( editCommand, fetchCommand, fetchObjectCommand, + fetchBlobCommand, getCommand, ingestCommand, listCommand, @@ -442,6 +444,60 @@ var ( }, } + fetchBlobCommand = cli.Command{ + Name: "fetch-blob", + Usage: "retrieve blobs from a remote", + ArgsUsage: "[flags] [, ...]", + Description: `Fetch blobs by digests from a remote.`, + Flags: commands.RegistryFlags, + Action: func(context *cli.Context) error { + var ( + ref = context.Args().First() + digests = context.Args().Tail() + ) + if len(digests) == 0 { + return errors.New("must specify digests") + } + ctx, cancel := commands.AppContext(context) + defer cancel() + + resolver, err := commands.GetResolver(ctx, context) + if err != nil { + return err + } + + ctx = log.WithLogger(ctx, log.G(ctx).WithField("ref", ref)) + + log.G(ctx).Debugf("resolving") + fetcher, err := resolver.Fetcher(ctx, ref) + if err != nil { + return err + } + + fetcherByDigest, ok := fetcher.(remotes.FetcherByDigest) + if !ok { + return fmt.Errorf("fetcher %T does not implement remotes.FetcherByDigest", fetcher) + } + + for _, f := range digests { + dgst, err := digest.Parse(f) + if err != nil { + return err + } + rc, _, err := fetcherByDigest.FetchByDigest(ctx, dgst) + if err != nil { + return err + } + _, err = io.Copy(os.Stdout, rc) + rc.Close() + if err != nil { + return err + } + } + return nil + }, + } + pushObjectCommand = cli.Command{ Name: "push-object", Usage: "push an object to a remote",