@@ -23,7 +23,7 @@ import (
2323 "io"
2424 "io/ioutil"
2525 "net/http"
26- "path "
26+ "net/url "
2727 "strings"
2828
2929 "github.com/containerd/containerd/errdefs"
@@ -32,34 +32,53 @@ import (
3232 "github.com/docker/distribution/registry/api/errcode"
3333 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3434 "github.com/pkg/errors"
35- "github.com/sirupsen/logrus"
3635)
3736
3837type dockerFetcher struct {
3938 * dockerBase
4039}
4140
4241func (r dockerFetcher ) Fetch (ctx context.Context , desc ocispec.Descriptor ) (io.ReadCloser , error ) {
43- ctx = log .WithLogger (ctx , log .G (ctx ).WithFields (
44- logrus.Fields {
45- "base" : r .base .String (),
46- "digest" : desc .Digest ,
47- },
48- ))
49-
50- urls , err := r .getV2URLPaths (ctx , desc )
51- if err != nil {
52- return nil , err
42+ ctx = log .WithLogger (ctx , log .G (ctx ).WithField ("digest" , desc .Digest ))
43+
44+ hosts := r .filterHosts (HostCapabilityPull )
45+ if len (hosts ) == 0 {
46+ return nil , errors .Wrap (errdefs .ErrNotFound , "no pull hosts" )
5347 }
5448
55- ctx , err = contextWithRepositoryScope (ctx , r .refspec , false )
49+ ctx , err : = contextWithRepositoryScope (ctx , r .refspec , false )
5650 if err != nil {
5751 return nil , err
5852 }
5953
6054 return newHTTPReadSeeker (desc .Size , func (offset int64 ) (io.ReadCloser , error ) {
61- for _ , u := range urls {
62- rc , err := r .open (ctx , u , desc .MediaType , offset )
55+ // firstly try fetch via external urls
56+ for _ , us := range desc .URLs {
57+ ctx = log .WithLogger (ctx , log .G (ctx ).WithField ("url" , us ))
58+
59+ u , err := url .Parse (us )
60+ if err != nil {
61+ log .G (ctx ).WithError (err ).Debug ("failed to parse" )
62+ continue
63+ }
64+ log .G (ctx ).Debug ("trying alternative url" )
65+
66+ // Try this first, parse it
67+ host := RegistryHost {
68+ Client : http .DefaultClient ,
69+ Host : u .Host ,
70+ Scheme : u .Scheme ,
71+ Path : u .Path ,
72+ Capabilities : HostCapabilityPull ,
73+ }
74+ req := r .request (host , http .MethodGet )
75+ // Strip namespace from base
76+ req .path = u .Path
77+ if u .RawQuery != "" {
78+ req .path = req .path + "?" + u .RawQuery
79+ }
80+
81+ rc , err := r .open (ctx , req , desc .MediaType , offset )
6382 if err != nil {
6483 if errdefs .IsNotFound (err ) {
6584 continue // try one of the other urls.
@@ -71,29 +90,62 @@ func (r dockerFetcher) Fetch(ctx context.Context, desc ocispec.Descriptor) (io.R
7190 return rc , nil
7291 }
7392
93+ // Try manifests endpoints for manifests types
94+ switch desc .MediaType {
95+ case images .MediaTypeDockerSchema2Manifest , images .MediaTypeDockerSchema2ManifestList ,
96+ images .MediaTypeDockerSchema1Manifest ,
97+ ocispec .MediaTypeImageManifest , ocispec .MediaTypeImageIndex :
98+
99+ for _ , host := range r .hosts {
100+ req := r .request (host , http .MethodGet , "manifests" , desc .Digest .String ())
101+
102+ rc , err := r .open (ctx , req , desc .MediaType , offset )
103+ if err != nil {
104+ if errdefs .IsNotFound (err ) {
105+ continue // try another host
106+ }
107+
108+ return nil , err
109+ }
110+
111+ return rc , nil
112+ }
113+ }
114+
115+ // Finally use blobs endpoints
116+ for _ , host := range r .hosts {
117+ req := r .request (host , http .MethodGet , "blobs" , desc .Digest .String ())
118+
119+ rc , err := r .open (ctx , req , desc .MediaType , offset )
120+ if err != nil {
121+ if errdefs .IsNotFound (err ) {
122+ continue // try another host
123+ }
124+
125+ return nil , err
126+ }
127+
128+ return rc , nil
129+ }
130+
74131 return nil , errors .Wrapf (errdefs .ErrNotFound ,
75132 "could not fetch content descriptor %v (%v) from remote" ,
76133 desc .Digest , desc .MediaType )
77134
78135 })
79136}
80137
81- func (r dockerFetcher ) open (ctx context.Context , u , mediatype string , offset int64 ) (io.ReadCloser , error ) {
82- req , err := http .NewRequest (http .MethodGet , u , nil )
83- if err != nil {
84- return nil , err
85- }
86-
87- req .Header .Set ("Accept" , strings .Join ([]string {mediatype , `*` }, ", " ))
138+ func (r dockerFetcher ) open (ctx context.Context , req * request , mediatype string , offset int64 ) (io.ReadCloser , error ) {
139+ req .header .Set ("Accept" , strings .Join ([]string {mediatype , `*` }, ", " ))
88140
89141 if offset > 0 {
90142 // Note: "Accept-Ranges: bytes" cannot be trusted as some endpoints
91143 // will return the header without supporting the range. The content
92144 // range must always be checked.
93- req .Header .Set ("Range" , fmt .Sprintf ("bytes=%d-" , offset ))
145+ req .header .Set ("Range" , fmt .Sprintf ("bytes=%d-" , offset ))
94146 }
95147
96- resp , err := r . doRequestWithRetries (ctx , req , nil )
148+ resp , err := req . doWithRetries (ctx , nil )
97149 if err != nil {
98150 return nil , err
99151 }
@@ -106,13 +158,13 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int
106158 defer resp .Body .Close ()
107159
108160 if resp .StatusCode == http .StatusNotFound {
109- return nil , errors .Wrapf (errdefs .ErrNotFound , "content at %v not found" , u )
161+ return nil , errors .Wrapf (errdefs .ErrNotFound , "content at %v not found" , req . String () )
110162 }
111163 var registryErr errcode.Errors
112164 if err := json .NewDecoder (resp .Body ).Decode (& registryErr ); err != nil || registryErr .Len () < 1 {
113- return nil , errors .Errorf ("unexpected status code %v: %v" , u , resp .Status )
165+ return nil , errors .Errorf ("unexpected status code %v: %v" , req . String () , resp .Status )
114166 }
115- return nil , errors .Errorf ("unexpected status code %v: %s - Server message: %s" , u , resp .Status , registryErr .Error ())
167+ return nil , errors .Errorf ("unexpected status code %v: %s - Server message: %s" , req . String () , resp .Status , registryErr .Error ())
116168 }
117169 if offset > 0 {
118170 cr := resp .Header .Get ("content-range" )
@@ -141,30 +193,3 @@ func (r dockerFetcher) open(ctx context.Context, u, mediatype string, offset int
141193
142194 return resp .Body , nil
143195}
144-
145- // getV2URLPaths generates the candidate urls paths for the object based on the
146- // set of hints and the provided object id. URLs are returned in the order of
147- // most to least likely succeed.
148- func (r * dockerFetcher ) getV2URLPaths (ctx context.Context , desc ocispec.Descriptor ) ([]string , error ) {
149- var urls []string
150-
151- if len (desc .URLs ) > 0 {
152- // handle fetch via external urls.
153- for _ , u := range desc .URLs {
154- log .G (ctx ).WithField ("url" , u ).Debug ("adding alternative url" )
155- urls = append (urls , u )
156- }
157- }
158-
159- switch desc .MediaType {
160- case images .MediaTypeDockerSchema2Manifest , images .MediaTypeDockerSchema2ManifestList ,
161- images .MediaTypeDockerSchema1Manifest ,
162- ocispec .MediaTypeImageManifest , ocispec .MediaTypeImageIndex :
163- urls = append (urls , r .url (path .Join ("manifests" , desc .Digest .String ())))
164- }
165-
166- // always fallback to attempting to get the object out of the blobs store.
167- urls = append (urls , r .url (path .Join ("blobs" , desc .Digest .String ())))
168-
169- return urls , nil
170- }
0 commit comments