ctr: add `ctr content fetch-blob`
e.g., `ctr content fetch-blob docker.io/library/debian:latest sha256:43d28810c1b4c28a1be3bac8e0e40fcc472b2bfcfcda952544ed99cb874d2b1a`
@@ -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] <remote> [<digest>, ...]",
+ 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",
