Browse code

Vendor updated distribution for resumable downloads

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>

Aaron Lehmann authored on 2016/01/27 04:20:14
Showing 15 changed files
... ...
@@ -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 &registry{
... ...
@@ -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
 }