Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
| ... | ... |
@@ -250,7 +250,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat |
| 250 | 250 |
p.confirmedV2 = true |
| 251 | 251 |
|
| 252 | 252 |
logrus.Debugf("Pulling ref from V2 registry: %s", ref.String())
|
| 253 |
- progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Name()) |
|
| 253 |
+ progress.Message(p.config.ProgressOutput, tagOrDigest, "Pulling from "+p.repo.Named().Name()) |
|
| 254 | 254 |
|
| 255 | 255 |
var ( |
| 256 | 256 |
imageID image.ID |
| ... | ... |
@@ -166,7 +166,11 @@ func (p *v2Pusher) pushV2Tag(ctx context.Context, ref reference.NamedTagged, ima |
| 166 | 166 |
if _, err = manSvc.Put(ctx, manifest, putOptions...); err != nil {
|
| 167 | 167 |
logrus.Warnf("failed to upload schema2 manifest: %v - falling back to schema1", err)
|
| 168 | 168 |
|
| 169 |
- builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, p.repo.Name(), ref.Tag(), img.RawJSON()) |
|
| 169 |
+ manifestRef, err := distreference.WithTag(p.repo.Named(), ref.Tag()) |
|
| 170 |
+ if err != nil {
|
|
| 171 |
+ return err |
|
| 172 |
+ } |
|
| 173 |
+ builder = schema1.NewConfigManifestBuilder(p.repo.Blobs(ctx), p.config.TrustKey, manifestRef, img.RawJSON()) |
|
| 170 | 174 |
manifest, err = manifestFromBuilder(ctx, builder, descriptors) |
| 171 | 175 |
if err != nil {
|
| 172 | 176 |
return err |
| ... | ... |
@@ -219,7 +223,7 @@ type v2PushDescriptor struct {
|
| 219 | 219 |
} |
| 220 | 220 |
|
| 221 | 221 |
func (pd *v2PushDescriptor) Key() string {
|
| 222 |
- return "v2push:" + pd.repo.Name() + " " + pd.layer.DiffID().String() |
|
| 222 |
+ return "v2push:" + pd.repo.Named().Name() + " " + pd.layer.DiffID().String() |
|
| 223 | 223 |
} |
| 224 | 224 |
|
| 225 | 225 |
func (pd *v2PushDescriptor) ID() string {
|
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
"time" |
| 11 | 11 |
|
| 12 | 12 |
"github.com/docker/distribution" |
| 13 |
+ distreference "github.com/docker/distribution/reference" |
|
| 13 | 14 |
"github.com/docker/distribution/registry/api/errcode" |
| 14 | 15 |
"github.com/docker/distribution/registry/client" |
| 15 | 16 |
"github.com/docker/distribution/registry/client/auth" |
| ... | ... |
@@ -154,7 +155,12 @@ func NewV2Repository(ctx context.Context, repoInfo *registry.RepositoryInfo, end |
| 154 | 154 |
} |
| 155 | 155 |
tr := transport.NewTransport(base, modifiers...) |
| 156 | 156 |
|
| 157 |
- repo, err = client.NewRepository(ctx, repoName, endpoint.URL, tr) |
|
| 157 |
+ repoNameRef, err := distreference.ParseNamed(repoName) |
|
| 158 |
+ if err != nil {
|
|
| 159 |
+ return nil, foundVersion, err |
|
| 160 |
+ } |
|
| 161 |
+ |
|
| 162 |
+ repo, err = client.NewRepository(ctx, repoNameRef, endpoint.URL, tr) |
|
| 158 | 163 |
return repo, foundVersion, err |
| 159 | 164 |
} |
| 160 | 165 |
|
| ... | ... |
@@ -46,7 +46,7 @@ clone git github.com/boltdb/bolt v1.1.0 |
| 46 | 46 |
clone git github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 |
| 47 | 47 |
|
| 48 | 48 |
# get graph and distribution packages |
| 49 |
-clone git github.com/docker/distribution c301f8ab27f4913c968b8d73a38e5dda79b9d3d7 |
|
| 49 |
+clone git github.com/docker/distribution ab9b433fcaf7c8319562a8b80f2720f5faca712f |
|
| 50 | 50 |
clone git github.com/vbatts/tar-split v0.9.11 |
| 51 | 51 |
|
| 52 | 52 |
# get desired notary commit, might also need to be updated in Dockerfile |
| ... | ... |
@@ -2,6 +2,7 @@ Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@users.noreply.gith |
| 2 | 2 |
Stephen J Day <stephen.day@docker.com> Stephen Day <stevvooe@gmail.com> |
| 3 | 3 |
Olivier Gambier <olivier@docker.com> Olivier Gambier <dmp42@users.noreply.github.com> |
| 4 | 4 |
Brian Bland <brian.bland@docker.com> Brian Bland <r4nd0m1n4t0r@gmail.com> |
| 5 |
+Brian Bland <brian.bland@docker.com> Brian Bland <brian.t.bland@gmail.com> |
|
| 5 | 6 |
Josh Hawn <josh.hawn@docker.com> Josh Hawn <jlhawn@berkeley.edu> |
| 6 | 7 |
Richard Scothern <richard.scothern@docker.com> Richard <richard.scothern@gmail.com> |
| 7 | 8 |
Richard Scothern <richard.scothern@docker.com> Richard Scothern <richard.scothern@gmail.com> |
| ... | ... |
@@ -11,4 +12,4 @@ Jessie Frazelle <jessie@docker.com> <jfrazelle@users.noreply.github.com> |
| 11 | 11 |
Sharif Nassar <sharif@mrwacky.com> Sharif Nassar <mrwacky42@users.noreply.github.com> |
| 12 | 12 |
Sven Dowideit <SvenDowideit@home.org.au> Sven Dowideit <SvenDowideit@users.noreply.github.com> |
| 13 | 13 |
Vincent Giersch <vincent.giersch@ovh.net> Vincent Giersch <vincent@giersch.fr> |
| 14 |
-davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com> |
|
| 15 | 14 |
\ No newline at end of file |
| 15 |
+davidli <wenquan.li@hp.com> davidli <wenquan.li@hpe.com> |
| ... | ... |
@@ -21,6 +21,7 @@ Ben Firshman <ben@firshman.co.uk> |
| 21 | 21 |
bin liu <liubin0329@gmail.com> |
| 22 | 22 |
Brian Bland <brian.bland@docker.com> |
| 23 | 23 |
burnettk <burnettk@gmail.com> |
| 24 |
+Carson A <ca@carsonoid.net> |
|
| 24 | 25 |
Chris Dillon <squarism@gmail.com> |
| 25 | 26 |
Daisuke Fujita <dtanshi45@gmail.com> |
| 26 | 27 |
Darren Shepherd <darren@rancher.com> |
| ... | ... |
@@ -33,11 +34,13 @@ davidli <wenquan.li@hp.com> |
| 33 | 33 |
Dejan Golja <dejan@golja.org> |
| 34 | 34 |
Derek McGowan <derek@mcgstyle.net> |
| 35 | 35 |
Diogo Mónica <diogo.monica@gmail.com> |
| 36 |
+DJ Enriquez <dj.enriquez@infospace.com> |
|
| 36 | 37 |
Donald Huang <don.hcd@gmail.com> |
| 37 | 38 |
Doug Davis <dug@us.ibm.com> |
| 38 | 39 |
farmerworking <farmerworking@gmail.com> |
| 39 | 40 |
Florentin Raud <florentin.raud@gmail.com> |
| 40 | 41 |
Frederick F. Kautz IV <fkautz@alumni.cmu.edu> |
| 42 |
+gabriell nascimento <gabriell@bluesoft.com.br> |
|
| 41 | 43 |
harche <p.harshal@gmail.com> |
| 42 | 44 |
Henri Gomez <henri.gomez@gmail.com> |
| 43 | 45 |
Hu Keping <hukeping@huawei.com> |
| ... | ... |
@@ -55,7 +58,9 @@ Josh Hawn <josh.hawn@docker.com> |
| 55 | 55 |
Julien Fernandez <julien.fernandez@gmail.com> |
| 56 | 56 |
Kelsey Hightower <kelsey.hightower@gmail.com> |
| 57 | 57 |
Kenneth Lim <kennethlimcp@gmail.com> |
| 58 |
+Kenny Leung <kleung@google.com> |
|
| 58 | 59 |
Li Yi <denverdino@gmail.com> |
| 60 |
+Liu Hua <sdu.liu@huawei.com> |
|
| 59 | 61 |
Louis Kottmann <louis.kottmann@gmail.com> |
| 60 | 62 |
Luke Carpenter <x@rubynerd.net> |
| 61 | 63 |
Mary Anthony <mary@docker.com> |
| ... | ... |
@@ -76,7 +81,9 @@ Olivier Jacques <olivier.jacques@hp.com> |
| 76 | 76 |
Patrick Devine <patrick.devine@docker.com> |
| 77 | 77 |
Philip Misiowiec <philip@atlashealth.com> |
| 78 | 78 |
Richard Scothern <richard.scothern@docker.com> |
| 79 |
+Rodolfo Carvalho <rhcarvalho@gmail.com> |
|
| 79 | 80 |
Rusty Conover <rusty@luckydinosaur.com> |
| 81 |
+Sean Boran <Boran@users.noreply.github.com> |
|
| 80 | 82 |
Sebastiaan van Stijn <github@gone.nl> |
| 81 | 83 |
Sharif Nassar <sharif@mrwacky.com> |
| 82 | 84 |
Shawn Falkner-Horine <dreadpirateshawn@gmail.com> |
| ... | ... |
@@ -93,11 +100,13 @@ Thomas Sjögren <konstruktoid@users.noreply.github.com> |
| 93 | 93 |
Tianon Gravi <admwiggin@gmail.com> |
| 94 | 94 |
Tibor Vass <teabee89@gmail.com> |
| 95 | 95 |
Tonis Tiigi <tonistiigi@gmail.com> |
| 96 |
+Trevor Pounds <trevor.pounds@gmail.com> |
|
| 96 | 97 |
Troels Thomsen <troels@thomsen.io> |
| 97 | 98 |
Vincent Batts <vbatts@redhat.com> |
| 98 | 99 |
Vincent Demeester <vincent@sbr.pm> |
| 99 | 100 |
Vincent Giersch <vincent.giersch@ovh.net> |
| 100 | 101 |
W. Trevor King <wking@tremily.us> |
| 102 |
+weiyuan.yl <weiyuan.yl@alibaba-inc.com> |
|
| 101 | 103 |
xg.song <xg.song@venusource.com> |
| 102 | 104 |
xiekeyang <xiekeyang@huawei.com> |
| 103 | 105 |
Yann ROBERT <yann.robert@anantaplex.fr> |
| ... | ... |
@@ -32,6 +32,11 @@ |
| 32 | 32 |
Email = "aaron.lehmann@docker.com" |
| 33 | 33 |
GitHub = "aaronlehmann" |
| 34 | 34 |
|
| 35 |
+ [people.brianbland] |
|
| 36 |
+ Name = "Brian Bland" |
|
| 37 |
+ Email = "brian.bland@docker.com" |
|
| 38 |
+ GitHub = "BrianBland" |
|
| 39 |
+ |
|
| 35 | 40 |
[people.dmcgowan] |
| 36 | 41 |
Name = "Derek McGowan" |
| 37 | 42 |
Email = "derek@mcgstyle.net" |
| ... | ... |
@@ -9,6 +9,7 @@ import ( |
| 9 | 9 |
|
| 10 | 10 |
"github.com/docker/distribution" |
| 11 | 11 |
"github.com/docker/distribution/context" |
| 12 |
+ "github.com/docker/distribution/reference" |
|
| 12 | 13 |
"github.com/docker/libtrust" |
| 13 | 14 |
|
| 14 | 15 |
"github.com/docker/distribution/digest" |
| ... | ... |
@@ -39,10 +40,8 @@ type configManifestBuilder struct {
|
| 39 | 39 |
// configJSON is configuration supplied when the ManifestBuilder was |
| 40 | 40 |
// created. |
| 41 | 41 |
configJSON []byte |
| 42 |
- // name is the name provided to NewConfigManifestBuilder |
|
| 43 |
- name string |
|
| 44 |
- // tag is the tag provided to NewConfigManifestBuilder |
|
| 45 |
- tag string |
|
| 42 |
+ // ref contains the name and optional tag provided to NewConfigManifestBuilder. |
|
| 43 |
+ ref reference.Named |
|
| 46 | 44 |
// descriptors is the set of descriptors referencing the layers. |
| 47 | 45 |
descriptors []distribution.Descriptor |
| 48 | 46 |
// emptyTarDigest is set to a valid digest if an empty tar has been |
| ... | ... |
@@ -54,13 +53,12 @@ type configManifestBuilder struct {
|
| 54 | 54 |
// schema version from an image configuration and a set of descriptors. |
| 55 | 55 |
// It takes a BlobService so that it can add an empty tar to the blob store |
| 56 | 56 |
// if the resulting manifest needs empty layers. |
| 57 |
-func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, name, tag string, configJSON []byte) distribution.ManifestBuilder {
|
|
| 57 |
+func NewConfigManifestBuilder(bs distribution.BlobService, pk libtrust.PrivateKey, ref reference.Named, configJSON []byte) distribution.ManifestBuilder {
|
|
| 58 | 58 |
return &configManifestBuilder{
|
| 59 | 59 |
bs: bs, |
| 60 | 60 |
pk: pk, |
| 61 | 61 |
configJSON: configJSON, |
| 62 |
- name: name, |
|
| 63 |
- tag: tag, |
|
| 62 |
+ ref: ref, |
|
| 64 | 63 |
} |
| 65 | 64 |
} |
| 66 | 65 |
|
| ... | ... |
@@ -190,12 +188,17 @@ func (mb *configManifestBuilder) Build(ctx context.Context) (m distribution.Mani |
| 190 | 190 |
|
| 191 | 191 |
history[0].V1Compatibility = string(transformedConfig) |
| 192 | 192 |
|
| 193 |
+ tag := "" |
|
| 194 |
+ if tagged, isTagged := mb.ref.(reference.Tagged); isTagged {
|
|
| 195 |
+ tag = tagged.Tag() |
|
| 196 |
+ } |
|
| 197 |
+ |
|
| 193 | 198 |
mfst := Manifest{
|
| 194 | 199 |
Versioned: manifest.Versioned{
|
| 195 | 200 |
SchemaVersion: 1, |
| 196 | 201 |
}, |
| 197 |
- Name: mb.name, |
|
| 198 |
- Tag: mb.tag, |
|
| 202 |
+ Name: mb.ref.Name(), |
|
| 203 |
+ Tag: tag, |
|
| 199 | 204 |
Architecture: img.Architecture, |
| 200 | 205 |
FSLayers: fsLayerList, |
| 201 | 206 |
History: history, |
| ... | ... |
@@ -8,6 +8,7 @@ import ( |
| 8 | 8 |
"github.com/docker/distribution/context" |
| 9 | 9 |
"github.com/docker/distribution/digest" |
| 10 | 10 |
"github.com/docker/distribution/manifest" |
| 11 |
+ "github.com/docker/distribution/reference" |
|
| 11 | 12 |
"github.com/docker/libtrust" |
| 12 | 13 |
) |
| 13 | 14 |
|
| ... | ... |
@@ -20,13 +21,18 @@ type referenceManifestBuilder struct {
|
| 20 | 20 |
|
| 21 | 21 |
// NewReferenceManifestBuilder is used to build new manifests for the current |
| 22 | 22 |
// schema version using schema1 dependencies. |
| 23 |
-func NewReferenceManifestBuilder(pk libtrust.PrivateKey, name, tag, architecture string) distribution.ManifestBuilder {
|
|
| 23 |
+func NewReferenceManifestBuilder(pk libtrust.PrivateKey, ref reference.Named, architecture string) distribution.ManifestBuilder {
|
|
| 24 |
+ tag := "" |
|
| 25 |
+ if tagged, isTagged := ref.(reference.Tagged); isTagged {
|
|
| 26 |
+ tag = tagged.Tag() |
|
| 27 |
+ } |
|
| 28 |
+ |
|
| 24 | 29 |
return &referenceManifestBuilder{
|
| 25 | 30 |
Manifest: Manifest{
|
| 26 | 31 |
Versioned: manifest.Versioned{
|
| 27 | 32 |
SchemaVersion: 1, |
| 28 | 33 |
}, |
| 29 |
- Name: name, |
|
| 34 |
+ Name: ref.Name(), |
|
| 30 | 35 |
Tag: tag, |
| 31 | 36 |
Architecture: architecture, |
| 32 | 37 |
}, |
| ... | ... |
@@ -81,7 +81,7 @@ type UnmarshalFunc func([]byte) (Manifest, Descriptor, error) |
| 81 | 81 |
|
| 82 | 82 |
var mappings = make(map[string]UnmarshalFunc, 0) |
| 83 | 83 |
|
| 84 |
-// UnmarshalManifest looks up manifest unmarshall functions based on |
|
| 84 |
+// UnmarshalManifest looks up manifest unmarshal functions based on |
|
| 85 | 85 |
// MediaType |
| 86 | 86 |
func UnmarshalManifest(ctHeader string, p []byte) (Manifest, Descriptor, error) {
|
| 87 | 87 |
// Need to look up by the actual media type, not the raw contents of |
| ... | ... |
@@ -2,6 +2,7 @@ package distribution |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 | 4 |
"github.com/docker/distribution/context" |
| 5 |
+ "github.com/docker/distribution/reference" |
|
| 5 | 6 |
) |
| 6 | 7 |
|
| 7 | 8 |
// Scope defines the set of items that match a namespace. |
| ... | ... |
@@ -32,7 +33,7 @@ type Namespace interface {
|
| 32 | 32 |
// Repository should return a reference to the named repository. The |
| 33 | 33 |
// registry may or may not have the repository but should always return a |
| 34 | 34 |
// reference. |
| 35 |
- Repository(ctx context.Context, name string) (Repository, error) |
|
| 35 |
+ Repository(ctx context.Context, name reference.Named) (Repository, error) |
|
| 36 | 36 |
|
| 37 | 37 |
// Repositories fills 'repos' with a lexigraphically sorted catalog of repositories |
| 38 | 38 |
// up to the size of 'repos' and returns the value 'n' for the number of entries |
| ... | ... |
@@ -48,8 +49,8 @@ type ManifestServiceOption interface {
|
| 48 | 48 |
|
| 49 | 49 |
// Repository is a named collection of manifests and layers. |
| 50 | 50 |
type Repository interface {
|
| 51 |
- // Name returns the name of the repository. |
|
| 52 |
- Name() string |
|
| 51 |
+ // Named returns the name of the repository. |
|
| 52 |
+ Named() reference.Named |
|
| 53 | 53 |
|
| 54 | 54 |
// Manifests returns a reference to this repository's manifest service. |
| 55 | 55 |
// with the supplied options applied. |
| ... | ... |
@@ -5,7 +5,7 @@ import ( |
| 5 | 5 |
"net/url" |
| 6 | 6 |
"strings" |
| 7 | 7 |
|
| 8 |
- "github.com/docker/distribution/digest" |
|
| 8 |
+ "github.com/docker/distribution/reference" |
|
| 9 | 9 |
"github.com/gorilla/mux" |
| 10 | 10 |
) |
| 11 | 11 |
|
| ... | ... |
@@ -113,10 +113,10 @@ func (ub *URLBuilder) BuildCatalogURL(values ...url.Values) (string, error) {
|
| 113 | 113 |
} |
| 114 | 114 |
|
| 115 | 115 |
// BuildTagsURL constructs a url to list the tags in the named repository. |
| 116 |
-func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
|
|
| 116 |
+func (ub *URLBuilder) BuildTagsURL(name reference.Named) (string, error) {
|
|
| 117 | 117 |
route := ub.cloneRoute(RouteNameTags) |
| 118 | 118 |
|
| 119 |
- tagsURL, err := route.URL("name", name)
|
|
| 119 |
+ tagsURL, err := route.URL("name", name.Name())
|
|
| 120 | 120 |
if err != nil {
|
| 121 | 121 |
return "", err |
| 122 | 122 |
} |
| ... | ... |
@@ -126,10 +126,18 @@ func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
|
| 126 | 126 |
|
| 127 | 127 |
// BuildManifestURL constructs a url for the manifest identified by name and |
| 128 | 128 |
// reference. The argument reference may be either a tag or digest. |
| 129 |
-func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
|
|
| 129 |
+func (ub *URLBuilder) BuildManifestURL(ref reference.Named) (string, error) {
|
|
| 130 | 130 |
route := ub.cloneRoute(RouteNameManifest) |
| 131 | 131 |
|
| 132 |
- manifestURL, err := route.URL("name", name, "reference", reference)
|
|
| 132 |
+ tagOrDigest := "" |
|
| 133 |
+ switch v := ref.(type) {
|
|
| 134 |
+ case reference.Tagged: |
|
| 135 |
+ tagOrDigest = v.Tag() |
|
| 136 |
+ case reference.Digested: |
|
| 137 |
+ tagOrDigest = v.Digest().String() |
|
| 138 |
+ } |
|
| 139 |
+ |
|
| 140 |
+ manifestURL, err := route.URL("name", ref.Name(), "reference", tagOrDigest)
|
|
| 133 | 141 |
if err != nil {
|
| 134 | 142 |
return "", err |
| 135 | 143 |
} |
| ... | ... |
@@ -138,10 +146,10 @@ func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
|
| 138 | 138 |
} |
| 139 | 139 |
|
| 140 | 140 |
// BuildBlobURL constructs the url for the blob identified by name and dgst. |
| 141 |
-func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) {
|
|
| 141 |
+func (ub *URLBuilder) BuildBlobURL(ref reference.Canonical) (string, error) {
|
|
| 142 | 142 |
route := ub.cloneRoute(RouteNameBlob) |
| 143 | 143 |
|
| 144 |
- layerURL, err := route.URL("name", name, "digest", dgst.String())
|
|
| 144 |
+ layerURL, err := route.URL("name", ref.Name(), "digest", ref.Digest().String())
|
|
| 145 | 145 |
if err != nil {
|
| 146 | 146 |
return "", err |
| 147 | 147 |
} |
| ... | ... |
@@ -151,10 +159,10 @@ func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, err |
| 151 | 151 |
|
| 152 | 152 |
// BuildBlobUploadURL constructs a url to begin a blob upload in the |
| 153 | 153 |
// repository identified by name. |
| 154 |
-func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) {
|
|
| 154 |
+func (ub *URLBuilder) BuildBlobUploadURL(name reference.Named, values ...url.Values) (string, error) {
|
|
| 155 | 155 |
route := ub.cloneRoute(RouteNameBlobUpload) |
| 156 | 156 |
|
| 157 |
- uploadURL, err := route.URL("name", name)
|
|
| 157 |
+ uploadURL, err := route.URL("name", name.Name())
|
|
| 158 | 158 |
if err != nil {
|
| 159 | 159 |
return "", err |
| 160 | 160 |
} |
| ... | ... |
@@ -166,10 +174,10 @@ func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (str |
| 166 | 166 |
// including any url values. This should generally not be used by clients, as |
| 167 | 167 |
// this url is provided by server implementations during the blob upload |
| 168 | 168 |
// process. |
| 169 |
-func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) {
|
|
| 169 |
+func (ub *URLBuilder) BuildBlobUploadChunkURL(name reference.Named, uuid string, values ...url.Values) (string, error) {
|
|
| 170 | 170 |
route := ub.cloneRoute(RouteNameBlobUploadChunk) |
| 171 | 171 |
|
| 172 |
- uploadURL, err := route.URL("name", name, "uuid", uuid)
|
|
| 172 |
+ uploadURL, err := route.URL("name", name.Name(), "uuid", uuid)
|
|
| 173 | 173 |
if err != nil {
|
| 174 | 174 |
return "", err |
| 175 | 175 |
} |
| ... | ... |
@@ -285,9 +285,9 @@ func (th *tokenHandler) fetchToken(params map[string]string) (token *tokenRespon |
| 285 | 285 |
} |
| 286 | 286 |
|
| 287 | 287 |
if tr.ExpiresIn < minimumTokenLifetimeSeconds {
|
| 288 |
- logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
|
| 289 | 288 |
// The default/minimum lifetime. |
| 290 | 289 |
tr.ExpiresIn = minimumTokenLifetimeSeconds |
| 290 |
+ logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
|
|
| 291 | 291 |
} |
| 292 | 292 |
|
| 293 | 293 |
if tr.IssuedAt.IsZero() {
|
| ... | ... |
@@ -27,6 +27,26 @@ type Registry interface {
|
| 27 | 27 |
Repositories(ctx context.Context, repos []string, last string) (n int, err error) |
| 28 | 28 |
} |
| 29 | 29 |
|
| 30 |
+// checkHTTPRedirect is a callback that can manipulate redirected HTTP |
|
| 31 |
+// requests. It is used to preserve Accept and Range headers. |
|
| 32 |
+func checkHTTPRedirect(req *http.Request, via []*http.Request) error {
|
|
| 33 |
+ if len(via) >= 10 {
|
|
| 34 |
+ return errors.New("stopped after 10 redirects")
|
|
| 35 |
+ } |
|
| 36 |
+ |
|
| 37 |
+ if len(via) > 0 {
|
|
| 38 |
+ for headerName, headerVals := range via[0].Header {
|
|
| 39 |
+ if headerName == "Accept" || headerName == "Range" {
|
|
| 40 |
+ for _, val := range headerVals {
|
|
| 41 |
+ req.Header.Add(headerName, val) |
|
| 42 |
+ } |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ } |
|
| 46 |
+ |
|
| 47 |
+ return nil |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 30 | 50 |
// NewRegistry creates a registry namespace which can be used to get a listing of repositories |
| 31 | 51 |
func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTripper) (Registry, error) {
|
| 32 | 52 |
ub, err := v2.NewURLBuilderFromString(baseURL) |
| ... | ... |
@@ -35,8 +55,9 @@ func NewRegistry(ctx context.Context, baseURL string, transport http.RoundTrippe |
| 35 | 35 |
} |
| 36 | 36 |
|
| 37 | 37 |
client := &http.Client{
|
| 38 |
- Transport: transport, |
|
| 39 |
- Timeout: 1 * time.Minute, |
|
| 38 |
+ Transport: transport, |
|
| 39 |
+ Timeout: 1 * time.Minute, |
|
| 40 |
+ CheckRedirect: checkHTTPRedirect, |
|
| 40 | 41 |
} |
| 41 | 42 |
|
| 42 | 43 |
return ®istry{
|
| ... | ... |
@@ -98,18 +119,15 @@ func (r *registry) Repositories(ctx context.Context, entries []string, last stri |
| 98 | 98 |
} |
| 99 | 99 |
|
| 100 | 100 |
// NewRepository creates a new Repository for the given repository name and base URL. |
| 101 |
-func NewRepository(ctx context.Context, name, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
|
| 102 |
- if _, err := reference.ParseNamed(name); err != nil {
|
|
| 103 |
- return nil, err |
|
| 104 |
- } |
|
| 105 |
- |
|
| 101 |
+func NewRepository(ctx context.Context, name reference.Named, baseURL string, transport http.RoundTripper) (distribution.Repository, error) {
|
|
| 106 | 102 |
ub, err := v2.NewURLBuilderFromString(baseURL) |
| 107 | 103 |
if err != nil {
|
| 108 | 104 |
return nil, err |
| 109 | 105 |
} |
| 110 | 106 |
|
| 111 | 107 |
client := &http.Client{
|
| 112 |
- Transport: transport, |
|
| 108 |
+ Transport: transport, |
|
| 109 |
+ CheckRedirect: checkHTTPRedirect, |
|
| 113 | 110 |
// TODO(dmcgowan): create cookie jar |
| 114 | 111 |
} |
| 115 | 112 |
|
| ... | ... |
@@ -125,21 +143,21 @@ type repository struct {
|
| 125 | 125 |
client *http.Client |
| 126 | 126 |
ub *v2.URLBuilder |
| 127 | 127 |
context context.Context |
| 128 |
- name string |
|
| 128 |
+ name reference.Named |
|
| 129 | 129 |
} |
| 130 | 130 |
|
| 131 |
-func (r *repository) Name() string {
|
|
| 131 |
+func (r *repository) Named() reference.Named {
|
|
| 132 | 132 |
return r.name |
| 133 | 133 |
} |
| 134 | 134 |
|
| 135 | 135 |
func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
| 136 | 136 |
statter := &blobStatter{
|
| 137 |
- name: r.Name(), |
|
| 137 |
+ name: r.name, |
|
| 138 | 138 |
ub: r.ub, |
| 139 | 139 |
client: r.client, |
| 140 | 140 |
} |
| 141 | 141 |
return &blobs{
|
| 142 |
- name: r.Name(), |
|
| 142 |
+ name: r.name, |
|
| 143 | 143 |
ub: r.ub, |
| 144 | 144 |
client: r.client, |
| 145 | 145 |
statter: cache.NewCachedBlobStatter(memory.NewInMemoryBlobDescriptorCacheProvider(), statter), |
| ... | ... |
@@ -149,7 +167,7 @@ func (r *repository) Blobs(ctx context.Context) distribution.BlobStore {
|
| 149 | 149 |
func (r *repository) Manifests(ctx context.Context, options ...distribution.ManifestServiceOption) (distribution.ManifestService, error) {
|
| 150 | 150 |
// todo(richardscothern): options should be sent over the wire |
| 151 | 151 |
return &manifests{
|
| 152 |
- name: r.Name(), |
|
| 152 |
+ name: r.name, |
|
| 153 | 153 |
ub: r.ub, |
| 154 | 154 |
client: r.client, |
| 155 | 155 |
etags: make(map[string]string), |
| ... | ... |
@@ -161,7 +179,7 @@ func (r *repository) Tags(ctx context.Context) distribution.TagService {
|
| 161 | 161 |
client: r.client, |
| 162 | 162 |
ub: r.ub, |
| 163 | 163 |
context: r.context, |
| 164 |
- name: r.Name(), |
|
| 164 |
+ name: r.Named(), |
|
| 165 | 165 |
} |
| 166 | 166 |
} |
| 167 | 167 |
|
| ... | ... |
@@ -170,7 +188,7 @@ type tags struct {
|
| 170 | 170 |
client *http.Client |
| 171 | 171 |
ub *v2.URLBuilder |
| 172 | 172 |
context context.Context |
| 173 |
- name string |
|
| 173 |
+ name reference.Named |
|
| 174 | 174 |
} |
| 175 | 175 |
|
| 176 | 176 |
// All returns all tags |
| ... | ... |
@@ -253,7 +271,11 @@ func descriptorFromResponse(response *http.Response) (distribution.Descriptor, e |
| 253 | 253 |
// to construct a descriptor for the tag. If the registry doesn't support HEADing |
| 254 | 254 |
// a manifest, fallback to GET. |
| 255 | 255 |
func (t *tags) Get(ctx context.Context, tag string) (distribution.Descriptor, error) {
|
| 256 |
- u, err := t.ub.BuildManifestURL(t.name, tag) |
|
| 256 |
+ ref, err := reference.WithTag(t.name, tag) |
|
| 257 |
+ if err != nil {
|
|
| 258 |
+ return distribution.Descriptor{}, err
|
|
| 259 |
+ } |
|
| 260 |
+ u, err := t.ub.BuildManifestURL(ref) |
|
| 257 | 261 |
if err != nil {
|
| 258 | 262 |
return distribution.Descriptor{}, err
|
| 259 | 263 |
} |
| ... | ... |
@@ -293,14 +315,18 @@ func (t *tags) Untag(ctx context.Context, tag string) error {
|
| 293 | 293 |
} |
| 294 | 294 |
|
| 295 | 295 |
type manifests struct {
|
| 296 |
- name string |
|
| 296 |
+ name reference.Named |
|
| 297 | 297 |
ub *v2.URLBuilder |
| 298 | 298 |
client *http.Client |
| 299 | 299 |
etags map[string]string |
| 300 | 300 |
} |
| 301 | 301 |
|
| 302 | 302 |
func (ms *manifests) Exists(ctx context.Context, dgst digest.Digest) (bool, error) {
|
| 303 |
- u, err := ms.ub.BuildManifestURL(ms.name, dgst.String()) |
|
| 303 |
+ ref, err := reference.WithDigest(ms.name, dgst) |
|
| 304 |
+ if err != nil {
|
|
| 305 |
+ return false, err |
|
| 306 |
+ } |
|
| 307 |
+ u, err := ms.ub.BuildManifestURL(ref) |
|
| 304 | 308 |
if err != nil {
|
| 305 | 309 |
return false, err |
| 306 | 310 |
} |
| ... | ... |
@@ -337,11 +363,19 @@ func (o etagOption) Apply(ms distribution.ManifestService) error {
|
| 337 | 337 |
} |
| 338 | 338 |
|
| 339 | 339 |
func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...distribution.ManifestServiceOption) (distribution.Manifest, error) {
|
| 340 |
+ var ( |
|
| 341 |
+ digestOrTag string |
|
| 342 |
+ ref reference.Named |
|
| 343 |
+ err error |
|
| 344 |
+ ) |
|
| 340 | 345 |
|
| 341 |
- var tag string |
|
| 342 | 346 |
for _, option := range options {
|
| 343 | 347 |
if opt, ok := option.(withTagOption); ok {
|
| 344 |
- tag = opt.tag |
|
| 348 |
+ digestOrTag = opt.tag |
|
| 349 |
+ ref, err = reference.WithTag(ms.name, opt.tag) |
|
| 350 |
+ if err != nil {
|
|
| 351 |
+ return nil, err |
|
| 352 |
+ } |
|
| 345 | 353 |
} else {
|
| 346 | 354 |
err := option.Apply(ms) |
| 347 | 355 |
if err != nil {
|
| ... | ... |
@@ -350,14 +384,15 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis |
| 350 | 350 |
} |
| 351 | 351 |
} |
| 352 | 352 |
|
| 353 |
- var ref string |
|
| 354 |
- if tag != "" {
|
|
| 355 |
- ref = tag |
|
| 356 |
- } else {
|
|
| 357 |
- ref = dgst.String() |
|
| 353 |
+ if digestOrTag == "" {
|
|
| 354 |
+ digestOrTag = dgst.String() |
|
| 355 |
+ ref, err = reference.WithDigest(ms.name, dgst) |
|
| 356 |
+ if err != nil {
|
|
| 357 |
+ return nil, err |
|
| 358 |
+ } |
|
| 358 | 359 |
} |
| 359 | 360 |
|
| 360 |
- u, err := ms.ub.BuildManifestURL(ms.name, ref) |
|
| 361 |
+ u, err := ms.ub.BuildManifestURL(ref) |
|
| 361 | 362 |
if err != nil {
|
| 362 | 363 |
return nil, err |
| 363 | 364 |
} |
| ... | ... |
@@ -371,8 +406,8 @@ func (ms *manifests) Get(ctx context.Context, dgst digest.Digest, options ...dis |
| 371 | 371 |
req.Header.Add("Accept", t)
|
| 372 | 372 |
} |
| 373 | 373 |
|
| 374 |
- if _, ok := ms.etags[ref]; ok {
|
|
| 375 |
- req.Header.Set("If-None-Match", ms.etags[ref])
|
|
| 374 |
+ if _, ok := ms.etags[digestOrTag]; ok {
|
|
| 375 |
+ req.Header.Set("If-None-Match", ms.etags[digestOrTag])
|
|
| 376 | 376 |
} |
| 377 | 377 |
|
| 378 | 378 |
resp, err := ms.client.Do(req) |
| ... | ... |
@@ -414,13 +449,19 @@ func (o withTagOption) Apply(m distribution.ManifestService) error {
|
| 414 | 414 |
} |
| 415 | 415 |
|
| 416 | 416 |
// Put puts a manifest. A tag can be specified using an options parameter which uses some shared state to hold the |
| 417 |
-// tag name in order to build the correct upload URL. This state is written and read under a lock. |
|
| 417 |
+// tag name in order to build the correct upload URL. |
|
| 418 | 418 |
func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options ...distribution.ManifestServiceOption) (digest.Digest, error) {
|
| 419 |
- var tag string |
|
| 419 |
+ ref := ms.name |
|
| 420 |
+ var tagged bool |
|
| 420 | 421 |
|
| 421 | 422 |
for _, option := range options {
|
| 422 | 423 |
if opt, ok := option.(withTagOption); ok {
|
| 423 |
- tag = opt.tag |
|
| 424 |
+ var err error |
|
| 425 |
+ ref, err = reference.WithTag(ref, opt.tag) |
|
| 426 |
+ if err != nil {
|
|
| 427 |
+ return "", err |
|
| 428 |
+ } |
|
| 429 |
+ tagged = true |
|
| 424 | 430 |
} else {
|
| 425 | 431 |
err := option.Apply(ms) |
| 426 | 432 |
if err != nil {
|
| ... | ... |
@@ -428,13 +469,24 @@ func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options . |
| 428 | 428 |
} |
| 429 | 429 |
} |
| 430 | 430 |
} |
| 431 |
- |
|
| 432 |
- manifestURL, err := ms.ub.BuildManifestURL(ms.name, tag) |
|
| 431 |
+ mediaType, p, err := m.Payload() |
|
| 433 | 432 |
if err != nil {
|
| 434 | 433 |
return "", err |
| 435 | 434 |
} |
| 436 | 435 |
|
| 437 |
- mediaType, p, err := m.Payload() |
|
| 436 |
+ if !tagged {
|
|
| 437 |
+ // generate a canonical digest and Put by digest |
|
| 438 |
+ _, d, err := distribution.UnmarshalManifest(mediaType, p) |
|
| 439 |
+ if err != nil {
|
|
| 440 |
+ return "", err |
|
| 441 |
+ } |
|
| 442 |
+ ref, err = reference.WithDigest(ref, d.Digest) |
|
| 443 |
+ if err != nil {
|
|
| 444 |
+ return "", err |
|
| 445 |
+ } |
|
| 446 |
+ } |
|
| 447 |
+ |
|
| 448 |
+ manifestURL, err := ms.ub.BuildManifestURL(ref) |
|
| 438 | 449 |
if err != nil {
|
| 439 | 450 |
return "", err |
| 440 | 451 |
} |
| ... | ... |
@@ -466,7 +518,11 @@ func (ms *manifests) Put(ctx context.Context, m distribution.Manifest, options . |
| 466 | 466 |
} |
| 467 | 467 |
|
| 468 | 468 |
func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
| 469 |
- u, err := ms.ub.BuildManifestURL(ms.name, dgst.String()) |
|
| 469 |
+ ref, err := reference.WithDigest(ms.name, dgst) |
|
| 470 |
+ if err != nil {
|
|
| 471 |
+ return err |
|
| 472 |
+ } |
|
| 473 |
+ u, err := ms.ub.BuildManifestURL(ref) |
|
| 470 | 474 |
if err != nil {
|
| 471 | 475 |
return err |
| 472 | 476 |
} |
| ... | ... |
@@ -493,7 +549,7 @@ func (ms *manifests) Delete(ctx context.Context, dgst digest.Digest) error {
|
| 493 | 493 |
}*/ |
| 494 | 494 |
|
| 495 | 495 |
type blobs struct {
|
| 496 |
- name string |
|
| 496 |
+ name reference.Named |
|
| 497 | 497 |
ub *v2.URLBuilder |
| 498 | 498 |
client *http.Client |
| 499 | 499 |
|
| ... | ... |
@@ -531,7 +587,11 @@ func (bs *blobs) Get(ctx context.Context, dgst digest.Digest) ([]byte, error) {
|
| 531 | 531 |
} |
| 532 | 532 |
|
| 533 | 533 |
func (bs *blobs) Open(ctx context.Context, dgst digest.Digest) (distribution.ReadSeekCloser, error) {
|
| 534 |
- blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst) |
|
| 534 |
+ ref, err := reference.WithDigest(bs.name, dgst) |
|
| 535 |
+ if err != nil {
|
|
| 536 |
+ return nil, err |
|
| 537 |
+ } |
|
| 538 |
+ blobURL, err := bs.ub.BuildBlobURL(ref) |
|
| 535 | 539 |
if err != nil {
|
| 536 | 540 |
return nil, err |
| 537 | 541 |
} |
| ... | ... |
@@ -666,13 +726,17 @@ func (bs *blobs) Delete(ctx context.Context, dgst digest.Digest) error {
|
| 666 | 666 |
} |
| 667 | 667 |
|
| 668 | 668 |
type blobStatter struct {
|
| 669 |
- name string |
|
| 669 |
+ name reference.Named |
|
| 670 | 670 |
ub *v2.URLBuilder |
| 671 | 671 |
client *http.Client |
| 672 | 672 |
} |
| 673 | 673 |
|
| 674 | 674 |
func (bs *blobStatter) Stat(ctx context.Context, dgst digest.Digest) (distribution.Descriptor, error) {
|
| 675 |
- u, err := bs.ub.BuildBlobURL(bs.name, dgst) |
|
| 675 |
+ ref, err := reference.WithDigest(bs.name, dgst) |
|
| 676 |
+ if err != nil {
|
|
| 677 |
+ return distribution.Descriptor{}, err
|
|
| 678 |
+ } |
|
| 679 |
+ u, err := bs.ub.BuildBlobURL(ref) |
|
| 676 | 680 |
if err != nil {
|
| 677 | 681 |
return distribution.Descriptor{}, err
|
| 678 | 682 |
} |
| ... | ... |
@@ -720,7 +784,11 @@ func buildCatalogValues(maxEntries int, last string) url.Values {
|
| 720 | 720 |
} |
| 721 | 721 |
|
| 722 | 722 |
func (bs *blobStatter) Clear(ctx context.Context, dgst digest.Digest) error {
|
| 723 |
- blobURL, err := bs.ub.BuildBlobURL(bs.name, dgst) |
|
| 723 |
+ ref, err := reference.WithDigest(bs.name, dgst) |
|
| 724 |
+ if err != nil {
|
|
| 725 |
+ return err |
|
| 726 |
+ } |
|
| 727 |
+ blobURL, err := bs.ub.BuildBlobURL(ref) |
|
| 724 | 728 |
if err != nil {
|
| 725 | 729 |
return err |
| 726 | 730 |
} |
| ... | ... |
@@ -1,12 +1,22 @@ |
| 1 | 1 |
package transport |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
- "bufio" |
|
| 5 | 4 |
"errors" |
| 6 | 5 |
"fmt" |
| 7 | 6 |
"io" |
| 8 | 7 |
"net/http" |
| 9 | 8 |
"os" |
| 9 |
+ "regexp" |
|
| 10 |
+ "strconv" |
|
| 11 |
+) |
|
| 12 |
+ |
|
| 13 |
+var ( |
|
| 14 |
+ contentRangeRegexp = regexp.MustCompile(`bytes ([0-9]+)-([0-9]+)/([0-9]+|\\*)`) |
|
| 15 |
+ |
|
| 16 |
+ // ErrWrongCodeForByteRange is returned if the client sends a request |
|
| 17 |
+ // with a Range header but the server returns a 2xx or 3xx code other |
|
| 18 |
+ // than 206 Partial Content. |
|
| 19 |
+ ErrWrongCodeForByteRange = errors.New("expected HTTP 206 from byte range request")
|
|
| 10 | 20 |
) |
| 11 | 21 |
|
| 12 | 22 |
// ReadSeekCloser combines io.ReadSeeker with io.Closer. |
| ... | ... |
@@ -40,8 +50,6 @@ type httpReadSeeker struct {
|
| 40 | 40 |
|
| 41 | 41 |
// rc is the remote read closer. |
| 42 | 42 |
rc io.ReadCloser |
| 43 |
- // brd is a buffer for internal buffered io. |
|
| 44 |
- brd *bufio.Reader |
|
| 45 | 43 |
// readerOffset tracks the offset as of the last read. |
| 46 | 44 |
readerOffset int64 |
| 47 | 45 |
// seekOffset allows Seek to override the offset. Seek changes |
| ... | ... |
@@ -79,11 +87,6 @@ func (hrs *httpReadSeeker) Read(p []byte) (n int, err error) {
|
| 79 | 79 |
hrs.seekOffset += int64(n) |
| 80 | 80 |
hrs.readerOffset += int64(n) |
| 81 | 81 |
|
| 82 |
- // Simulate io.EOF error if we reach filesize. |
|
| 83 |
- if err == nil && hrs.size >= 0 && hrs.readerOffset >= hrs.size {
|
|
| 84 |
- err = io.EOF |
|
| 85 |
- } |
|
| 86 |
- |
|
| 87 | 82 |
return n, err |
| 88 | 83 |
} |
| 89 | 84 |
|
| ... | ... |
@@ -92,8 +95,18 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
| 92 | 92 |
return 0, hrs.err |
| 93 | 93 |
} |
| 94 | 94 |
|
| 95 |
+ lastReaderOffset := hrs.readerOffset |
|
| 96 |
+ |
|
| 97 |
+ if whence == os.SEEK_SET && hrs.rc == nil {
|
|
| 98 |
+ // If no request has been made yet, and we are seeking to an |
|
| 99 |
+ // absolute position, set the read offset as well to avoid an |
|
| 100 |
+ // unnecessary request. |
|
| 101 |
+ hrs.readerOffset = offset |
|
| 102 |
+ } |
|
| 103 |
+ |
|
| 95 | 104 |
_, err := hrs.reader() |
| 96 | 105 |
if err != nil {
|
| 106 |
+ hrs.readerOffset = lastReaderOffset |
|
| 97 | 107 |
return 0, err |
| 98 | 108 |
} |
| 99 | 109 |
|
| ... | ... |
@@ -101,14 +114,14 @@ func (hrs *httpReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
| 101 | 101 |
|
| 102 | 102 |
switch whence {
|
| 103 | 103 |
case os.SEEK_CUR: |
| 104 |
- newOffset += int64(offset) |
|
| 104 |
+ newOffset += offset |
|
| 105 | 105 |
case os.SEEK_END: |
| 106 | 106 |
if hrs.size < 0 {
|
| 107 | 107 |
return 0, errors.New("content length not known")
|
| 108 | 108 |
} |
| 109 |
- newOffset = hrs.size + int64(offset) |
|
| 109 |
+ newOffset = hrs.size + offset |
|
| 110 | 110 |
case os.SEEK_SET: |
| 111 |
- newOffset = int64(offset) |
|
| 111 |
+ newOffset = offset |
|
| 112 | 112 |
} |
| 113 | 113 |
|
| 114 | 114 |
if newOffset < 0 {
|
| ... | ... |
@@ -131,7 +144,6 @@ func (hrs *httpReadSeeker) Close() error {
|
| 131 | 131 |
} |
| 132 | 132 |
|
| 133 | 133 |
hrs.rc = nil |
| 134 |
- hrs.brd = nil |
|
| 135 | 134 |
|
| 136 | 135 |
hrs.err = errors.New("httpLayer: closed")
|
| 137 | 136 |
|
| ... | ... |
@@ -154,7 +166,7 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
| 154 | 154 |
} |
| 155 | 155 |
|
| 156 | 156 |
if hrs.rc != nil {
|
| 157 |
- return hrs.brd, nil |
|
| 157 |
+ return hrs.rc, nil |
|
| 158 | 158 |
} |
| 159 | 159 |
|
| 160 | 160 |
req, err := http.NewRequest("GET", hrs.url, nil)
|
| ... | ... |
@@ -163,10 +175,8 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
| 163 | 163 |
} |
| 164 | 164 |
|
| 165 | 165 |
if hrs.readerOffset > 0 {
|
| 166 |
- // TODO(stevvooe): Get this working correctly. |
|
| 167 |
- |
|
| 168 | 166 |
// If we are at different offset, issue a range request from there. |
| 169 |
- req.Header.Add("Range", "1-")
|
|
| 167 |
+ req.Header.Add("Range", fmt.Sprintf("bytes=%d-", hrs.readerOffset))
|
|
| 170 | 168 |
// TODO: get context in here |
| 171 | 169 |
// context.GetLogger(hrs.context).Infof("Range: %s", req.Header.Get("Range"))
|
| 172 | 170 |
} |
| ... | ... |
@@ -179,12 +189,55 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
| 179 | 179 |
// Normally would use client.SuccessStatus, but that would be a cyclic |
| 180 | 180 |
// import |
| 181 | 181 |
if resp.StatusCode >= 200 && resp.StatusCode <= 399 {
|
| 182 |
- hrs.rc = resp.Body |
|
| 183 |
- if resp.StatusCode == http.StatusOK {
|
|
| 182 |
+ if hrs.readerOffset > 0 {
|
|
| 183 |
+ if resp.StatusCode != http.StatusPartialContent {
|
|
| 184 |
+ return nil, ErrWrongCodeForByteRange |
|
| 185 |
+ } |
|
| 186 |
+ |
|
| 187 |
+ contentRange := resp.Header.Get("Content-Range")
|
|
| 188 |
+ if contentRange == "" {
|
|
| 189 |
+ return nil, errors.New("no Content-Range header found in HTTP 206 response")
|
|
| 190 |
+ } |
|
| 191 |
+ |
|
| 192 |
+ submatches := contentRangeRegexp.FindStringSubmatch(contentRange) |
|
| 193 |
+ if len(submatches) < 4 {
|
|
| 194 |
+ return nil, fmt.Errorf("could not parse Content-Range header: %s", contentRange)
|
|
| 195 |
+ } |
|
| 196 |
+ |
|
| 197 |
+ startByte, err := strconv.ParseUint(submatches[1], 10, 64) |
|
| 198 |
+ if err != nil {
|
|
| 199 |
+ return nil, fmt.Errorf("could not parse start of range in Content-Range header: %s", contentRange)
|
|
| 200 |
+ } |
|
| 201 |
+ |
|
| 202 |
+ if startByte != uint64(hrs.readerOffset) {
|
|
| 203 |
+ return nil, fmt.Errorf("received Content-Range starting at offset %d instead of requested %d", startByte, hrs.readerOffset)
|
|
| 204 |
+ } |
|
| 205 |
+ |
|
| 206 |
+ endByte, err := strconv.ParseUint(submatches[2], 10, 64) |
|
| 207 |
+ if err != nil {
|
|
| 208 |
+ return nil, fmt.Errorf("could not parse end of range in Content-Range header: %s", contentRange)
|
|
| 209 |
+ } |
|
| 210 |
+ |
|
| 211 |
+ if submatches[3] == "*" {
|
|
| 212 |
+ hrs.size = -1 |
|
| 213 |
+ } else {
|
|
| 214 |
+ size, err := strconv.ParseUint(submatches[3], 10, 64) |
|
| 215 |
+ if err != nil {
|
|
| 216 |
+ return nil, fmt.Errorf("could not parse total size in Content-Range header: %s", contentRange)
|
|
| 217 |
+ } |
|
| 218 |
+ |
|
| 219 |
+ if endByte+1 != size {
|
|
| 220 |
+ return nil, fmt.Errorf("range in Content-Range stops before the end of the content: %s", contentRange)
|
|
| 221 |
+ } |
|
| 222 |
+ |
|
| 223 |
+ hrs.size = int64(size) |
|
| 224 |
+ } |
|
| 225 |
+ } else if resp.StatusCode == http.StatusOK {
|
|
| 184 | 226 |
hrs.size = resp.ContentLength |
| 185 | 227 |
} else {
|
| 186 | 228 |
hrs.size = -1 |
| 187 | 229 |
} |
| 230 |
+ hrs.rc = resp.Body |
|
| 188 | 231 |
} else {
|
| 189 | 232 |
defer resp.Body.Close() |
| 190 | 233 |
if hrs.errorHandler != nil {
|
| ... | ... |
@@ -193,11 +246,5 @@ func (hrs *httpReadSeeker) reader() (io.Reader, error) {
|
| 193 | 193 |
return nil, fmt.Errorf("unexpected status resolving reader: %v", resp.Status)
|
| 194 | 194 |
} |
| 195 | 195 |
|
| 196 |
- if hrs.brd == nil {
|
|
| 197 |
- hrs.brd = bufio.NewReader(hrs.rc) |
|
| 198 |
- } else {
|
|
| 199 |
- hrs.brd.Reset(hrs.rc) |
|
| 200 |
- } |
|
| 201 |
- |
|
| 202 |
- return hrs.brd, nil |
|
| 196 |
+ return hrs.rc, nil |
|
| 203 | 197 |
} |