@@ -18,17 +18,18 @@ package docker
1818
1919import (
2020 "context"
21+ "io"
2122 "net/http"
2223 "net/url"
2324 "path"
24- "strconv"
2525 "strings"
2626
2727 "github.com/containerd/containerd/errdefs"
2828 "github.com/containerd/containerd/images"
2929 "github.com/containerd/containerd/log"
3030 "github.com/containerd/containerd/reference"
3131 "github.com/containerd/containerd/remotes"
32+ "github.com/containerd/containerd/remotes/docker/schema1"
3233 "github.com/containerd/containerd/version"
3334 digest "github.com/opencontainers/go-digest"
3435 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -83,6 +84,9 @@ type ResolverOptions struct {
8384 // Host provides the hostname given a namespace.
8485 Host func (string ) (string , error )
8586
87+ // Headers are the HTTP request header fields sent by the resolver
88+ Headers http.Header
89+
8690 // PlainHTTP specifies to use plain http and not https
8791 PlainHTTP bool
8892
@@ -106,6 +110,7 @@ func DefaultHost(ns string) (string, error) {
106110type dockerResolver struct {
107111 auth Authorizer
108112 host func (string ) (string , error )
113+ headers http.Header
109114 uagent string
110115 plainHTTP bool
111116 client * http.Client
@@ -120,21 +125,65 @@ func NewResolver(options ResolverOptions) remotes.Resolver {
120125 if options .Host == nil {
121126 options .Host = DefaultHost
122127 }
123- ua := "containerd/" + version .Version
128+ if options .Headers == nil {
129+ options .Headers = make (http.Header )
130+ }
131+ if _ , ok := options .Headers ["Accept" ]; ! ok {
132+ // set headers for all the types we support for resolution.
133+ options .Headers .Set ("Accept" , strings .Join ([]string {
134+ images .MediaTypeDockerSchema2Manifest ,
135+ images .MediaTypeDockerSchema2ManifestList ,
136+ ocispec .MediaTypeImageManifest ,
137+ ocispec .MediaTypeImageIndex , "*" }, ", " ))
138+ }
139+ ua := options .Headers .Get ("User-Agent" )
140+ if ua != "" {
141+ options .Headers .Del ("User-Agent" )
142+ } else {
143+ ua = "containerd/" + version .Version
144+ }
145+
124146 if options .Authorizer == nil {
125147 options .Authorizer = NewAuthorizer (options .Client , options .Credentials )
126148 options .Authorizer .(* dockerAuthorizer ).ua = ua
127149 }
128150 return & dockerResolver {
129151 auth : options .Authorizer ,
130152 host : options .Host ,
153+ headers : options .Headers ,
131154 uagent : ua ,
132155 plainHTTP : options .PlainHTTP ,
133156 client : options .Client ,
134157 tracker : options .Tracker ,
135158 }
136159}
137160
161+ func getManifestMediaType (resp * http.Response ) string {
162+ // Strip encoding data (manifests should always be ascii JSON)
163+ contentType := resp .Header .Get ("Content-Type" )
164+ if sp := strings .IndexByte (contentType , ';' ); sp != - 1 {
165+ contentType = contentType [0 :sp ]
166+ }
167+
168+ // As of Apr 30 2019 the registry.access.redhat.com registry does not specify
169+ // the content type of any data but uses schema1 manifests.
170+ if contentType == "text/plain" {
171+ contentType = images .MediaTypeDockerSchema1Manifest
172+ }
173+ return contentType
174+ }
175+
176+ type countingReader struct {
177+ reader io.Reader
178+ bytesRead int64
179+ }
180+
181+ func (r * countingReader ) Read (p []byte ) (int , error ) {
182+ n , err := r .reader .Read (p )
183+ r .bytesRead += int64 (n )
184+ return n , err
185+ }
186+
138187var _ remotes.Resolver = & dockerResolver {}
139188
140189func (r * dockerResolver ) Resolve (ctx context.Context , ref string ) (string , ocispec.Descriptor , error ) {
@@ -187,12 +236,7 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
187236 return "" , ocispec.Descriptor {}, err
188237 }
189238
190- // set headers for all the types we support for resolution.
191- req .Header .Set ("Accept" , strings .Join ([]string {
192- images .MediaTypeDockerSchema2Manifest ,
193- images .MediaTypeDockerSchema2ManifestList ,
194- ocispec .MediaTypeImageManifest ,
195- ocispec .MediaTypeImageIndex , "*" }, ", " ))
239+ req .Header = r .headers
196240
197241 log .G (ctx ).Debug ("resolving" )
198242 resp , err := fetcher .doRequestWithRetries (ctx , req , nil )
@@ -210,40 +254,56 @@ func (r *dockerResolver) Resolve(ctx context.Context, ref string) (string, ocisp
210254 }
211255 return "" , ocispec.Descriptor {}, errors .Errorf ("unexpected status code %v: %v" , u , resp .Status )
212256 }
257+ size := resp .ContentLength
213258
214259 // this is the only point at which we trust the registry. we use the
215260 // content headers to assemble a descriptor for the name. when this becomes
216261 // more robust, we mostly get this information from a secure trust store.
217262 dgstHeader := digest .Digest (resp .Header .Get ("Docker-Content-Digest" ))
263+ contentType := getManifestMediaType (resp )
218264
219- if dgstHeader != "" {
265+ if dgstHeader != "" && size != - 1 {
220266 if err := dgstHeader .Validate (); err != nil {
221267 return "" , ocispec.Descriptor {}, errors .Wrapf (err , "%q in header not a valid digest" , dgstHeader )
222268 }
223269 dgst = dgstHeader
224- }
270+ } else {
271+ log .G (ctx ).Debug ("no Docker-Content-Digest header, fetching manifest instead" )
225272
226- if dgst == "" {
227- return "" , ocispec.Descriptor {}, errors .Errorf ("could not resolve digest for %v" , ref )
228- }
229-
230- var (
231- size int64
232- sizeHeader = resp .Header .Get ("Content-Length" )
233- )
234-
235- size , err = strconv .ParseInt (sizeHeader , 10 , 64 )
236- if err != nil {
273+ req , err := http .NewRequest (http .MethodGet , u , nil )
274+ if err != nil {
275+ return "" , ocispec.Descriptor {}, err
276+ }
277+ req .Header = r .headers
237278
238- return "" , ocispec.Descriptor {}, errors .Wrapf (err , "invalid size header: %q" , sizeHeader )
239- }
240- if size < 0 {
241- return "" , ocispec.Descriptor {}, errors .Errorf ("%q in header not a valid size" , sizeHeader )
279+ resp , err := fetcher .doRequestWithRetries (ctx , req , nil )
280+ if err != nil {
281+ return "" , ocispec.Descriptor {}, err
282+ }
283+ defer resp .Body .Close ()
284+
285+ bodyReader := countingReader {reader : resp .Body }
286+
287+ contentType = getManifestMediaType (resp )
288+ if contentType == images .MediaTypeDockerSchema1Manifest {
289+ b , err := schema1 .ReadStripSignature (& bodyReader )
290+ if err != nil {
291+ return "" , ocispec.Descriptor {}, err
292+ }
293+
294+ dgst = digest .FromBytes (b )
295+ } else {
296+ dgst , err = digest .FromReader (& bodyReader )
297+ if err != nil {
298+ return "" , ocispec.Descriptor {}, err
299+ }
300+ }
301+ size = bodyReader .bytesRead
242302 }
243303
244304 desc := ocispec.Descriptor {
245305 Digest : dgst ,
246- MediaType : resp . Header . Get ( "Content-Type" ), // need to strip disposition?
306+ MediaType : contentType ,
247307 Size : size ,
248308 }
249309
0 commit comments