diff --git a/cmd/ctr/commands/content/content.go b/cmd/ctr/commands/content/content.go index 47fec9944..277eda342 100644 --- a/cmd/ctr/commands/content/content.go +++ b/cmd/ctr/commands/content/content.go @@ -451,7 +451,12 @@ var ( Usage: "Retrieve blobs from a remote", ArgsUsage: "[flags] [, ...]", Description: `Fetch blobs by digests from a remote.`, - Flags: commands.RegistryFlags, + Flags: append(commands.RegistryFlags, []cli.Flag{ + cli.StringFlag{ + Name: "media-type", + Usage: "Specify target mediatype for request header", + }, + }...), Action: func(context *cli.Context) error { var ( ref = context.Args().First() @@ -486,7 +491,7 @@ var ( if err != nil { return err } - rc, _, err := fetcherByDigest.FetchByDigest(ctx, dgst) + rc, _, err := fetcherByDigest.FetchByDigest(ctx, dgst, remotes.WithMediaType(context.String("media-type"))) if err != nil { return err } diff --git a/remotes/docker/fetcher.go b/remotes/docker/fetcher.go index ecf245933..0c50a086f 100644 --- a/remotes/docker/fetcher.go +++ b/remotes/docker/fetcher.go @@ -29,6 +29,7 @@ import ( "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/log" + "github.com/containerd/containerd/remotes" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -151,12 +152,18 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R }) } -func (r dockerFetcher) createGetReq(ctx context.Context, host RegistryHost, ps ...string) (*request, int64, error) { +func (r dockerFetcher) createGetReq(ctx context.Context, host RegistryHost, mediatype string, ps ...string) (*request, int64, error) { headReq := r.request(host, http.MethodHead, ps...) if err := headReq.addNamespace(r.refspec.Hostname()); err != nil { return nil, 0, err } + if mediatype == "" { + headReq.header.Set("Accept", "*/*") + } else { + headReq.header.Set("Accept", strings.Join([]string{mediatype, `*/*`}, ", ")) + } + headResp, err := headReq.doWithRetries(ctx, nil) if err != nil { return nil, 0, err @@ -175,9 +182,15 @@ func (r dockerFetcher) createGetReq(ctx context.Context, host RegistryHost, ps . return getReq, headResp.ContentLength, nil } -func (r dockerFetcher) FetchByDigest(ctx context.Context, dgst digest.Digest) (io.ReadCloser, ocispec.Descriptor, error) { +func (r dockerFetcher) FetchByDigest(ctx context.Context, dgst digest.Digest, opts ...remotes.FetchByDigestOpts) (io.ReadCloser, ocispec.Descriptor, error) { var desc ocispec.Descriptor ctx = log.WithLogger(ctx, log.G(ctx).WithField("digest", dgst)) + var config remotes.FetchByDigestConfig + for _, o := range opts { + if err := o(ctx, &config); err != nil { + return nil, desc, err + } + } hosts := r.filterHosts(HostCapabilityPull) if len(hosts) == 0 { @@ -196,7 +209,7 @@ func (r dockerFetcher) FetchByDigest(ctx context.Context, dgst digest.Digest) (i ) for _, host := range r.hosts { - getReq, sz, err = r.createGetReq(ctx, host, "blobs", dgst.String()) + getReq, sz, err = r.createGetReq(ctx, host, config.Mediatype, "blobs", dgst.String()) if err == nil { break } @@ -209,7 +222,7 @@ func (r dockerFetcher) FetchByDigest(ctx context.Context, dgst digest.Digest) (i if getReq == nil { // Fall back to the "manifests" endpoint for _, host := range r.hosts { - getReq, sz, err = r.createGetReq(ctx, host, "manifests", dgst.String()) + getReq, sz, err = r.createGetReq(ctx, host, config.Mediatype, "manifests", dgst.String()) if err == nil { break } @@ -231,7 +244,7 @@ func (r dockerFetcher) FetchByDigest(ctx context.Context, dgst digest.Digest) (i } seeker, err := newHTTPReadSeeker(sz, func(offset int64) (io.ReadCloser, error) { - return r.open(ctx, getReq, "", offset) + return r.open(ctx, getReq, config.Mediatype, offset) }) if err != nil { return nil, desc, err diff --git a/remotes/resolver.go b/remotes/resolver.go index f200c84bc..c1b11efae 100644 --- a/remotes/resolver.go +++ b/remotes/resolver.go @@ -65,7 +65,7 @@ type FetcherByDigest interface { // FetcherByDigest usually returns an incomplete descriptor. // Typically, the media type is always set to "application/octet-stream", // and the annotations are unset. - FetchByDigest(ctx context.Context, dgst digest.Digest) (io.ReadCloser, ocispec.Descriptor, error) + FetchByDigest(ctx context.Context, dgst digest.Digest, opts ...FetchByDigestOpts) (io.ReadCloser, ocispec.Descriptor, error) } // Pusher pushes content @@ -92,3 +92,20 @@ type PusherFunc func(ctx context.Context, desc ocispec.Descriptor) (content.Writ func (fn PusherFunc) Push(ctx context.Context, desc ocispec.Descriptor) (content.Writer, error) { return fn(ctx, desc) } + +// FetchByDigestConfig provides configuration for fetching content by digest +type FetchByDigestConfig struct { + //Mediatype specifies mediatype header to append for fetch request + Mediatype string +} + +// FetchByDigestOpts allows callers to set options for fetch object +type FetchByDigestOpts func(context.Context, *FetchByDigestConfig) error + +// WithMediaType sets the media type header for fetch request +func WithMediaType(mediatype string) FetchByDigestOpts { + return func(ctx context.Context, cfg *FetchByDigestConfig) error { + cfg.Mediatype = mediatype + return nil + } +}