Browse code

Use vendored v2 registry api

Update registry package to use the v2 registry api from distribution. Update interfaces to directly take in digests.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)

Derek McGowan authored on 2015/04/01 07:02:27
Showing 13 changed files
... ...
@@ -499,7 +499,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
499 499
 					return err
500 500
 				}
501 501
 
502
-				r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest.Algorithm(), di.digest.Hex(), auth)
502
+				r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest, auth)
503 503
 				if err != nil {
504 504
 					return err
505 505
 				}
... ...
@@ -8,10 +8,10 @@ import (
8 8
 	"io/ioutil"
9 9
 	"os"
10 10
 	"path"
11
-	"strings"
12 11
 	"sync"
13 12
 
14 13
 	"github.com/Sirupsen/logrus"
14
+	"github.com/docker/distribution/digest"
15 15
 	"github.com/docker/docker/engine"
16 16
 	"github.com/docker/docker/image"
17 17
 	"github.com/docker/docker/pkg/progressreader"
... ...
@@ -376,13 +376,13 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o
376 376
 
377 377
 			var exists bool
378 378
 			if len(checksum) > 0 {
379
-				sumParts := strings.SplitN(checksum, ":", 2)
380
-				if len(sumParts) < 2 {
381
-					return fmt.Errorf("Invalid checksum: %s", checksum)
379
+				dgst, err := digest.ParseDigest(checksum)
380
+				if err != nil {
381
+					return fmt.Errorf("Invalid checksum %s: %s", checksum, err)
382 382
 				}
383 383
 
384 384
 				// Call mount blob
385
-				exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], sumParts[1], auth)
385
+				exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, dgst, auth)
386 386
 				if err != nil {
387 387
 					out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil))
388 388
 					return err
... ...
@@ -468,7 +468,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
468 468
 	// Send the layer
469 469
 	logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size)
470 470
 
471
-	if err := r.PutV2ImageBlob(endpoint, imageName, dgst.Algorithm(), dgst.Hex(),
471
+	if err := r.PutV2ImageBlob(endpoint, imageName, dgst,
472 472
 		progressreader.New(progressreader.Config{
473 473
 			In:        tf,
474 474
 			Out:       out,
... ...
@@ -11,8 +11,8 @@ import (
11 11
 	"strings"
12 12
 
13 13
 	"github.com/Sirupsen/logrus"
14
+	"github.com/docker/distribution/registry/api/v2"
14 15
 	"github.com/docker/docker/pkg/requestdecorator"
15
-	"github.com/docker/docker/registry/v2"
16 16
 )
17 17
 
18 18
 // for mocking in unit tests
... ...
@@ -11,7 +11,7 @@ import (
11 11
 
12 12
 	"github.com/Sirupsen/logrus"
13 13
 	"github.com/docker/distribution/digest"
14
-	"github.com/docker/docker/registry/v2"
14
+	"github.com/docker/distribution/registry/api/v2"
15 15
 	"github.com/docker/docker/utils"
16 16
 )
17 17
 
... ...
@@ -109,8 +109,8 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au
109 109
 // - Succeeded to head image blob (already exists)
110 110
 // - Failed with no error (continue to Push the Blob)
111 111
 // - Failed with error
112
-func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (bool, error) {
113
-	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum)
112
+func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) {
113
+	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
114 114
 	if err != nil {
115 115
 		return false, err
116 116
 	}
... ...
@@ -141,11 +141,11 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string,
141 141
 		return false, nil
142 142
 	}
143 143
 
144
-	return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s:%s", res.StatusCode, imageName, sumType, sum), res)
144
+	return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res)
145 145
 }
146 146
 
147
-func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, blobWrtr io.Writer, auth *RequestAuthorization) error {
148
-	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum)
147
+func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error {
148
+	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
149 149
 	if err != nil {
150 150
 		return err
151 151
 	}
... ...
@@ -175,8 +175,8 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, b
175 175
 	return err
176 176
 }
177 177
 
178
-func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (io.ReadCloser, int64, error) {
179
-	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum)
178
+func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) {
179
+	routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst)
180 180
 	if err != nil {
181 181
 		return nil, 0, err
182 182
 	}
... ...
@@ -198,7 +198,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str
198 198
 		if res.StatusCode == 401 {
199 199
 			return nil, 0, errLoginRequired
200 200
 		}
201
-		return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s:%s", res.StatusCode, imageName, sumType, sum), res)
201
+		return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res)
202 202
 	}
203 203
 	lenStr := res.Header.Get("Content-Length")
204 204
 	l, err := strconv.ParseInt(lenStr, 10, 64)
... ...
@@ -212,7 +212,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str
212 212
 // Push the image to the server for storage.
213 213
 // 'layer' is an uncompressed reader of the blob to be pushed.
214 214
 // The server will generate it's own checksum calculation.
215
-func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string, blobRdr io.Reader, auth *RequestAuthorization) error {
215
+func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error {
216 216
 	location, err := r.initiateBlobUpload(ep, imageName, auth)
217 217
 	if err != nil {
218 218
 		return err
... ...
@@ -225,7 +225,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string
225 225
 		return err
226 226
 	}
227 227
 	queryParams := req.URL.Query()
228
-	queryParams.Add("digest", sumType+":"+sumStr)
228
+	queryParams.Add("digest", dgst.String())
229 229
 	req.URL.RawQuery = queryParams.Encode()
230 230
 	if err := auth.Authorize(req); err != nil {
231 231
 		return err
... ...
@@ -245,7 +245,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string
245 245
 			return err
246 246
 		}
247 247
 		logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
248
-		return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res)
248
+		return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res)
249 249
 	}
250 250
 
251 251
 	return nil
252 252
deleted file mode 100644
... ...
@@ -1,144 +0,0 @@
1
-package v2
2
-
3
-import "net/http"
4
-
5
-// TODO(stevvooe): Add route descriptors for each named route, along with
6
-// accepted methods, parameters, returned status codes and error codes.
7
-
8
-// ErrorDescriptor provides relevant information about a given error code.
9
-type ErrorDescriptor struct {
10
-	// Code is the error code that this descriptor describes.
11
-	Code ErrorCode
12
-
13
-	// Value provides a unique, string key, often captilized with
14
-	// underscores, to identify the error code. This value is used as the
15
-	// keyed value when serializing api errors.
16
-	Value string
17
-
18
-	// Message is a short, human readable decription of the error condition
19
-	// included in API responses.
20
-	Message string
21
-
22
-	// Description provides a complete account of the errors purpose, suitable
23
-	// for use in documentation.
24
-	Description string
25
-
26
-	// HTTPStatusCodes provides a list of status under which this error
27
-	// condition may arise. If it is empty, the error condition may be seen
28
-	// for any status code.
29
-	HTTPStatusCodes []int
30
-}
31
-
32
-// ErrorDescriptors provides a list of HTTP API Error codes that may be
33
-// encountered when interacting with the registry API.
34
-var ErrorDescriptors = []ErrorDescriptor{
35
-	{
36
-		Code:    ErrorCodeUnknown,
37
-		Value:   "UNKNOWN",
38
-		Message: "unknown error",
39
-		Description: `Generic error returned when the error does not have an
40
-		API classification.`,
41
-	},
42
-	{
43
-		Code:    ErrorCodeDigestInvalid,
44
-		Value:   "DIGEST_INVALID",
45
-		Message: "provided digest did not match uploaded content",
46
-		Description: `When a blob is uploaded, the registry will check that
47
-		the content matches the digest provided by the client. The error may
48
-		include a detail structure with the key "digest", including the
49
-		invalid digest string. This error may also be returned when a manifest
50
-		includes an invalid layer digest.`,
51
-		HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
52
-	},
53
-	{
54
-		Code:    ErrorCodeSizeInvalid,
55
-		Value:   "SIZE_INVALID",
56
-		Message: "provided length did not match content length",
57
-		Description: `When a layer is uploaded, the provided size will be
58
-		checked against the uploaded content. If they do not match, this error
59
-		will be returned.`,
60
-		HTTPStatusCodes: []int{http.StatusBadRequest},
61
-	},
62
-	{
63
-		Code:    ErrorCodeNameInvalid,
64
-		Value:   "NAME_INVALID",
65
-		Message: "manifest name did not match URI",
66
-		Description: `During a manifest upload, if the name in the manifest
67
-		does not match the uri name, this error will be returned.`,
68
-		HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
69
-	},
70
-	{
71
-		Code:    ErrorCodeTagInvalid,
72
-		Value:   "TAG_INVALID",
73
-		Message: "manifest tag did not match URI",
74
-		Description: `During a manifest upload, if the tag in the manifest
75
-		does not match the uri tag, this error will be returned.`,
76
-		HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
77
-	},
78
-	{
79
-		Code:    ErrorCodeNameUnknown,
80
-		Value:   "NAME_UNKNOWN",
81
-		Message: "repository name not known to registry",
82
-		Description: `This is returned if the name used during an operation is
83
-		unknown to the registry.`,
84
-		HTTPStatusCodes: []int{http.StatusNotFound},
85
-	},
86
-	{
87
-		Code:    ErrorCodeManifestUnknown,
88
-		Value:   "MANIFEST_UNKNOWN",
89
-		Message: "manifest unknown",
90
-		Description: `This error is returned when the manifest, identified by
91
-		name and tag is unknown to the repository.`,
92
-		HTTPStatusCodes: []int{http.StatusNotFound},
93
-	},
94
-	{
95
-		Code:    ErrorCodeManifestInvalid,
96
-		Value:   "MANIFEST_INVALID",
97
-		Message: "manifest invalid",
98
-		Description: `During upload, manifests undergo several checks ensuring
99
-		validity. If those checks fail, this error may be returned, unless a
100
-		more specific error is included. The detail will contain information
101
-		the failed validation.`,
102
-		HTTPStatusCodes: []int{http.StatusBadRequest},
103
-	},
104
-	{
105
-		Code:    ErrorCodeManifestUnverified,
106
-		Value:   "MANIFEST_UNVERIFIED",
107
-		Message: "manifest failed signature verification",
108
-		Description: `During manifest upload, if the manifest fails signature
109
-		verification, this error will be returned.`,
110
-		HTTPStatusCodes: []int{http.StatusBadRequest},
111
-	},
112
-	{
113
-		Code:    ErrorCodeBlobUnknown,
114
-		Value:   "BLOB_UNKNOWN",
115
-		Message: "blob unknown to registry",
116
-		Description: `This error may be returned when a blob is unknown to the
117
-		registry in a specified repository. This can be returned with a
118
-		standard get or if a manifest references an unknown layer during
119
-		upload.`,
120
-		HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound},
121
-	},
122
-
123
-	{
124
-		Code:    ErrorCodeBlobUploadUnknown,
125
-		Value:   "BLOB_UPLOAD_UNKNOWN",
126
-		Message: "blob upload unknown to registry",
127
-		Description: `If a blob upload has been cancelled or was never
128
-		started, this error code may be returned.`,
129
-		HTTPStatusCodes: []int{http.StatusNotFound},
130
-	},
131
-}
132
-
133
-var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor
134
-var idToDescriptors map[string]ErrorDescriptor
135
-
136
-func init() {
137
-	errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(ErrorDescriptors))
138
-	idToDescriptors = make(map[string]ErrorDescriptor, len(ErrorDescriptors))
139
-
140
-	for _, descriptor := range ErrorDescriptors {
141
-		errorCodeToDescriptors[descriptor.Code] = descriptor
142
-		idToDescriptors[descriptor.Value] = descriptor
143
-	}
144
-}
145 1
deleted file mode 100644
... ...
@@ -1,13 +0,0 @@
1
-// Package v2 describes routes, urls and the error codes used in the Docker
2
-// Registry JSON HTTP API V2. In addition to declarations, descriptors are
3
-// provided for routes and error codes that can be used for implementation and
4
-// automatically generating documentation.
5
-//
6
-// Definitions here are considered to be locked down for the V2 registry api.
7
-// Any changes must be considered carefully and should not proceed without a
8
-// change proposal.
9
-//
10
-// Currently, while the HTTP API definitions are considered stable, the Go API
11
-// exports are considered unstable. Go API consumers should take care when
12
-// relying on these definitions until this message is deleted.
13
-package v2
14 1
deleted file mode 100644
... ...
@@ -1,185 +0,0 @@
1
-package v2
2
-
3
-import (
4
-	"fmt"
5
-	"strings"
6
-)
7
-
8
-// ErrorCode represents the error type. The errors are serialized via strings
9
-// and the integer format may change and should *never* be exported.
10
-type ErrorCode int
11
-
12
-const (
13
-	// ErrorCodeUnknown is a catch-all for errors not defined below.
14
-	ErrorCodeUnknown ErrorCode = iota
15
-
16
-	// ErrorCodeDigestInvalid is returned when uploading a blob if the
17
-	// provided digest does not match the blob contents.
18
-	ErrorCodeDigestInvalid
19
-
20
-	// ErrorCodeSizeInvalid is returned when uploading a blob if the provided
21
-	// size does not match the content length.
22
-	ErrorCodeSizeInvalid
23
-
24
-	// ErrorCodeNameInvalid is returned when the name in the manifest does not
25
-	// match the provided name.
26
-	ErrorCodeNameInvalid
27
-
28
-	// ErrorCodeTagInvalid is returned when the tag in the manifest does not
29
-	// match the provided tag.
30
-	ErrorCodeTagInvalid
31
-
32
-	// ErrorCodeNameUnknown when the repository name is not known.
33
-	ErrorCodeNameUnknown
34
-
35
-	// ErrorCodeManifestUnknown returned when image manifest is unknown.
36
-	ErrorCodeManifestUnknown
37
-
38
-	// ErrorCodeManifestInvalid returned when an image manifest is invalid,
39
-	// typically during a PUT operation. This error encompasses all errors
40
-	// encountered during manifest validation that aren't signature errors.
41
-	ErrorCodeManifestInvalid
42
-
43
-	// ErrorCodeManifestUnverified is returned when the manifest fails
44
-	// signature verfication.
45
-	ErrorCodeManifestUnverified
46
-
47
-	// ErrorCodeBlobUnknown is returned when a blob is unknown to the
48
-	// registry. This can happen when the manifest references a nonexistent
49
-	// layer or the result is not found by a blob fetch.
50
-	ErrorCodeBlobUnknown
51
-
52
-	// ErrorCodeBlobUploadUnknown is returned when an upload is unknown.
53
-	ErrorCodeBlobUploadUnknown
54
-)
55
-
56
-// ParseErrorCode attempts to parse the error code string, returning
57
-// ErrorCodeUnknown if the error is not known.
58
-func ParseErrorCode(s string) ErrorCode {
59
-	desc, ok := idToDescriptors[s]
60
-
61
-	if !ok {
62
-		return ErrorCodeUnknown
63
-	}
64
-
65
-	return desc.Code
66
-}
67
-
68
-// Descriptor returns the descriptor for the error code.
69
-func (ec ErrorCode) Descriptor() ErrorDescriptor {
70
-	d, ok := errorCodeToDescriptors[ec]
71
-
72
-	if !ok {
73
-		return ErrorCodeUnknown.Descriptor()
74
-	}
75
-
76
-	return d
77
-}
78
-
79
-// String returns the canonical identifier for this error code.
80
-func (ec ErrorCode) String() string {
81
-	return ec.Descriptor().Value
82
-}
83
-
84
-// Message returned the human-readable error message for this error code.
85
-func (ec ErrorCode) Message() string {
86
-	return ec.Descriptor().Message
87
-}
88
-
89
-// MarshalText encodes the receiver into UTF-8-encoded text and returns the
90
-// result.
91
-func (ec ErrorCode) MarshalText() (text []byte, err error) {
92
-	return []byte(ec.String()), nil
93
-}
94
-
95
-// UnmarshalText decodes the form generated by MarshalText.
96
-func (ec *ErrorCode) UnmarshalText(text []byte) error {
97
-	desc, ok := idToDescriptors[string(text)]
98
-
99
-	if !ok {
100
-		desc = ErrorCodeUnknown.Descriptor()
101
-	}
102
-
103
-	*ec = desc.Code
104
-
105
-	return nil
106
-}
107
-
108
-// Error provides a wrapper around ErrorCode with extra Details provided.
109
-type Error struct {
110
-	Code    ErrorCode   `json:"code"`
111
-	Message string      `json:"message,omitempty"`
112
-	Detail  interface{} `json:"detail,omitempty"`
113
-}
114
-
115
-// Error returns a human readable representation of the error.
116
-func (e Error) Error() string {
117
-	return fmt.Sprintf("%s: %s",
118
-		strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)),
119
-		e.Message)
120
-}
121
-
122
-// Errors provides the envelope for multiple errors and a few sugar methods
123
-// for use within the application.
124
-type Errors struct {
125
-	Errors []Error `json:"errors,omitempty"`
126
-}
127
-
128
-// Push pushes an error on to the error stack, with the optional detail
129
-// argument. It is a programming error (ie panic) to push more than one
130
-// detail at a time.
131
-func (errs *Errors) Push(code ErrorCode, details ...interface{}) {
132
-	if len(details) > 1 {
133
-		panic("please specify zero or one detail items for this error")
134
-	}
135
-
136
-	var detail interface{}
137
-	if len(details) > 0 {
138
-		detail = details[0]
139
-	}
140
-
141
-	if err, ok := detail.(error); ok {
142
-		detail = err.Error()
143
-	}
144
-
145
-	errs.PushErr(Error{
146
-		Code:    code,
147
-		Message: code.Message(),
148
-		Detail:  detail,
149
-	})
150
-}
151
-
152
-// PushErr pushes an error interface onto the error stack.
153
-func (errs *Errors) PushErr(err error) {
154
-	switch err.(type) {
155
-	case Error:
156
-		errs.Errors = append(errs.Errors, err.(Error))
157
-	default:
158
-		errs.Errors = append(errs.Errors, Error{Message: err.Error()})
159
-	}
160
-}
161
-
162
-func (errs *Errors) Error() string {
163
-	switch errs.Len() {
164
-	case 0:
165
-		return "<nil>"
166
-	case 1:
167
-		return errs.Errors[0].Error()
168
-	default:
169
-		msg := "errors:\n"
170
-		for _, err := range errs.Errors {
171
-			msg += err.Error() + "\n"
172
-		}
173
-		return msg
174
-	}
175
-}
176
-
177
-// Clear clears the errors.
178
-func (errs *Errors) Clear() {
179
-	errs.Errors = errs.Errors[:0]
180
-}
181
-
182
-// Len returns the current number of errors.
183
-func (errs *Errors) Len() int {
184
-	return len(errs.Errors)
185
-}
186 1
deleted file mode 100644
... ...
@@ -1,163 +0,0 @@
1
-package v2
2
-
3
-import (
4
-	"encoding/json"
5
-	"reflect"
6
-	"testing"
7
-)
8
-
9
-// TestErrorCodes ensures that error code format, mappings and
10
-// marshaling/unmarshaling. round trips are stable.
11
-func TestErrorCodes(t *testing.T) {
12
-	for _, desc := range ErrorDescriptors {
13
-		if desc.Code.String() != desc.Value {
14
-			t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value)
15
-		}
16
-
17
-		if desc.Code.Message() != desc.Message {
18
-			t.Fatalf("incorrect message for error code %v: %q != %q", desc.Code, desc.Code.Message(), desc.Message)
19
-		}
20
-
21
-		// Serialize the error code using the json library to ensure that we
22
-		// get a string and it works round trip.
23
-		p, err := json.Marshal(desc.Code)
24
-
25
-		if err != nil {
26
-			t.Fatalf("error marshaling error code %v: %v", desc.Code, err)
27
-		}
28
-
29
-		if len(p) <= 0 {
30
-			t.Fatalf("expected content in marshaled before for error code %v", desc.Code)
31
-		}
32
-
33
-		// First, unmarshal to interface and ensure we have a string.
34
-		var ecUnspecified interface{}
35
-		if err := json.Unmarshal(p, &ecUnspecified); err != nil {
36
-			t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err)
37
-		}
38
-
39
-		if _, ok := ecUnspecified.(string); !ok {
40
-			t.Fatalf("expected a string for error code %v on unmarshal got a %T", desc.Code, ecUnspecified)
41
-		}
42
-
43
-		// Now, unmarshal with the error code type and ensure they are equal
44
-		var ecUnmarshaled ErrorCode
45
-		if err := json.Unmarshal(p, &ecUnmarshaled); err != nil {
46
-			t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err)
47
-		}
48
-
49
-		if ecUnmarshaled != desc.Code {
50
-			t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, desc.Code)
51
-		}
52
-	}
53
-}
54
-
55
-// TestErrorsManagement does a quick check of the Errors type to ensure that
56
-// members are properly pushed and marshaled.
57
-func TestErrorsManagement(t *testing.T) {
58
-	var errs Errors
59
-
60
-	errs.Push(ErrorCodeDigestInvalid)
61
-	errs.Push(ErrorCodeBlobUnknown,
62
-		map[string]string{"digest": "sometestblobsumdoesntmatter"})
63
-
64
-	p, err := json.Marshal(errs)
65
-
66
-	if err != nil {
67
-		t.Fatalf("error marashaling errors: %v", err)
68
-	}
69
-
70
-	expectedJSON := "{\"errors\":[{\"code\":\"DIGEST_INVALID\",\"message\":\"provided digest did not match uploaded content\"},{\"code\":\"BLOB_UNKNOWN\",\"message\":\"blob unknown to registry\",\"detail\":{\"digest\":\"sometestblobsumdoesntmatter\"}}]}"
71
-
72
-	if string(p) != expectedJSON {
73
-		t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON)
74
-	}
75
-
76
-	errs.Clear()
77
-	errs.Push(ErrorCodeUnknown)
78
-	expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}"
79
-	p, err = json.Marshal(errs)
80
-
81
-	if err != nil {
82
-		t.Fatalf("error marashaling errors: %v", err)
83
-	}
84
-
85
-	if string(p) != expectedJSON {
86
-		t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON)
87
-	}
88
-}
89
-
90
-// TestMarshalUnmarshal ensures that api errors can round trip through json
91
-// without losing information.
92
-func TestMarshalUnmarshal(t *testing.T) {
93
-
94
-	var errors Errors
95
-
96
-	for _, testcase := range []struct {
97
-		description string
98
-		err         Error
99
-	}{
100
-		{
101
-			description: "unknown error",
102
-			err: Error{
103
-
104
-				Code:    ErrorCodeUnknown,
105
-				Message: ErrorCodeUnknown.Descriptor().Message,
106
-			},
107
-		},
108
-		{
109
-			description: "unknown manifest",
110
-			err: Error{
111
-				Code:    ErrorCodeManifestUnknown,
112
-				Message: ErrorCodeManifestUnknown.Descriptor().Message,
113
-			},
114
-		},
115
-		{
116
-			description: "unknown manifest",
117
-			err: Error{
118
-				Code:    ErrorCodeBlobUnknown,
119
-				Message: ErrorCodeBlobUnknown.Descriptor().Message,
120
-				Detail:  map[string]interface{}{"digest": "asdfqwerqwerqwerqwer"},
121
-			},
122
-		},
123
-	} {
124
-		fatalf := func(format string, args ...interface{}) {
125
-			t.Fatalf(testcase.description+": "+format, args...)
126
-		}
127
-
128
-		unexpectedErr := func(err error) {
129
-			fatalf("unexpected error: %v", err)
130
-		}
131
-
132
-		p, err := json.Marshal(testcase.err)
133
-		if err != nil {
134
-			unexpectedErr(err)
135
-		}
136
-
137
-		var unmarshaled Error
138
-		if err := json.Unmarshal(p, &unmarshaled); err != nil {
139
-			unexpectedErr(err)
140
-		}
141
-
142
-		if !reflect.DeepEqual(unmarshaled, testcase.err) {
143
-			fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, testcase.err)
144
-		}
145
-
146
-		// Roll everything up into an error response envelope.
147
-		errors.PushErr(testcase.err)
148
-	}
149
-
150
-	p, err := json.Marshal(errors)
151
-	if err != nil {
152
-		t.Fatalf("unexpected error marshaling error envelope: %v", err)
153
-	}
154
-
155
-	var unmarshaled Errors
156
-	if err := json.Unmarshal(p, &unmarshaled); err != nil {
157
-		t.Fatalf("unexpected error unmarshaling error envelope: %v", err)
158
-	}
159
-
160
-	if !reflect.DeepEqual(unmarshaled, errors) {
161
-		t.Fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, errors)
162
-	}
163
-}
164 1
deleted file mode 100644
... ...
@@ -1,22 +0,0 @@
1
-package v2
2
-
3
-import "regexp"
4
-
5
-// This file defines regular expressions for use in route definition. These
6
-// are also defined in the registry code base. Until they are in a common,
7
-// shared location, and exported, they must be repeated here.
8
-
9
-// RepositoryNameComponentRegexp restricts registtry path components names to
10
-// start with at least two letters or numbers, with following parts able to
11
-// separated by one period, dash or underscore.
12
-var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`)
13
-
14
-// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to
15
-// 5 path components, separated by a forward slash.
16
-var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String())
17
-
18
-// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go.
19
-var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`)
20
-
21
-// DigestRegexp matches valid digest types.
22
-var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+`)
23 1
deleted file mode 100644
... ...
@@ -1,66 +0,0 @@
1
-package v2
2
-
3
-import "github.com/gorilla/mux"
4
-
5
-// The following are definitions of the name under which all V2 routes are
6
-// registered. These symbols can be used to look up a route based on the name.
7
-const (
8
-	RouteNameBase            = "base"
9
-	RouteNameManifest        = "manifest"
10
-	RouteNameTags            = "tags"
11
-	RouteNameBlob            = "blob"
12
-	RouteNameBlobUpload      = "blob-upload"
13
-	RouteNameBlobUploadChunk = "blob-upload-chunk"
14
-)
15
-
16
-var allEndpoints = []string{
17
-	RouteNameManifest,
18
-	RouteNameTags,
19
-	RouteNameBlob,
20
-	RouteNameBlobUpload,
21
-	RouteNameBlobUploadChunk,
22
-}
23
-
24
-// Router builds a gorilla router with named routes for the various API
25
-// methods. This can be used directly by both server implementations and
26
-// clients.
27
-func Router() *mux.Router {
28
-	router := mux.NewRouter().
29
-		StrictSlash(true)
30
-
31
-	// GET /v2/	Check	Check that the registry implements API version 2(.1)
32
-	router.
33
-		Path("/v2/").
34
-		Name(RouteNameBase)
35
-
36
-	// GET      /v2/<name>/manifest/<reference>	Image Manifest	Fetch the image manifest identified by name and reference where reference can be a tag or digest.
37
-	// PUT      /v2/<name>/manifest/<reference>	Image Manifest	Upload the image manifest identified by name and reference where reference can be a tag or digest.
38
-	// DELETE   /v2/<name>/manifest/<reference>	Image Manifest	Delete the image identified by name and reference where reference can be a tag or digest.
39
-	router.
40
-		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}").
41
-		Name(RouteNameManifest)
42
-
43
-	// GET	/v2/<name>/tags/list	Tags	Fetch the tags under the repository identified by name.
44
-	router.
45
-		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list").
46
-		Name(RouteNameTags)
47
-
48
-	// GET	/v2/<name>/blob/<digest>	Layer	Fetch the blob identified by digest.
49
-	router.
50
-		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}").
51
-		Name(RouteNameBlob)
52
-
53
-	// POST	/v2/<name>/blob/upload/	Layer Upload	Initiate an upload of the layer identified by tarsum.
54
-	router.
55
-		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/").
56
-		Name(RouteNameBlobUpload)
57
-
58
-	// GET	/v2/<name>/blob/upload/<uuid>	Layer Upload	Get the status of the upload identified by tarsum and uuid.
59
-	// PUT	/v2/<name>/blob/upload/<uuid>	Layer Upload	Upload all or a chunk of the upload identified by tarsum and uuid.
60
-	// DELETE	/v2/<name>/blob/upload/<uuid>	Layer Upload	Cancel the upload identified by layer and uuid
61
-	router.
62
-		Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}").
63
-		Name(RouteNameBlobUploadChunk)
64
-
65
-	return router
66
-}
67 1
deleted file mode 100644
... ...
@@ -1,192 +0,0 @@
1
-package v2
2
-
3
-import (
4
-	"encoding/json"
5
-	"net/http"
6
-	"net/http/httptest"
7
-	"reflect"
8
-	"testing"
9
-
10
-	"github.com/gorilla/mux"
11
-)
12
-
13
-type routeTestCase struct {
14
-	RequestURI string
15
-	Vars       map[string]string
16
-	RouteName  string
17
-	StatusCode int
18
-}
19
-
20
-// TestRouter registers a test handler with all the routes and ensures that
21
-// each route returns the expected path variables. Not method verification is
22
-// present. This not meant to be exhaustive but as check to ensure that the
23
-// expected variables are extracted.
24
-//
25
-// This may go away as the application structure comes together.
26
-func TestRouter(t *testing.T) {
27
-
28
-	router := Router()
29
-
30
-	testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
31
-		testCase := routeTestCase{
32
-			RequestURI: r.RequestURI,
33
-			Vars:       mux.Vars(r),
34
-			RouteName:  mux.CurrentRoute(r).GetName(),
35
-		}
36
-
37
-		enc := json.NewEncoder(w)
38
-
39
-		if err := enc.Encode(testCase); err != nil {
40
-			http.Error(w, err.Error(), http.StatusInternalServerError)
41
-			return
42
-		}
43
-	})
44
-
45
-	// Startup test server
46
-	server := httptest.NewServer(router)
47
-
48
-	for _, testcase := range []routeTestCase{
49
-		{
50
-			RouteName:  RouteNameBase,
51
-			RequestURI: "/v2/",
52
-			Vars:       map[string]string{},
53
-		},
54
-		{
55
-			RouteName:  RouteNameManifest,
56
-			RequestURI: "/v2/foo/manifests/bar",
57
-			Vars: map[string]string{
58
-				"name":      "foo",
59
-				"reference": "bar",
60
-			},
61
-		},
62
-		{
63
-			RouteName:  RouteNameManifest,
64
-			RequestURI: "/v2/foo/bar/manifests/tag",
65
-			Vars: map[string]string{
66
-				"name":      "foo/bar",
67
-				"reference": "tag",
68
-			},
69
-		},
70
-		{
71
-			RouteName:  RouteNameTags,
72
-			RequestURI: "/v2/foo/bar/tags/list",
73
-			Vars: map[string]string{
74
-				"name": "foo/bar",
75
-			},
76
-		},
77
-		{
78
-			RouteName:  RouteNameBlob,
79
-			RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234",
80
-			Vars: map[string]string{
81
-				"name":   "foo/bar",
82
-				"digest": "tarsum.dev+foo:abcdef0919234",
83
-			},
84
-		},
85
-		{
86
-			RouteName:  RouteNameBlob,
87
-			RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234",
88
-			Vars: map[string]string{
89
-				"name":   "foo/bar",
90
-				"digest": "sha256:abcdef0919234",
91
-			},
92
-		},
93
-		{
94
-			RouteName:  RouteNameBlobUpload,
95
-			RequestURI: "/v2/foo/bar/blobs/uploads/",
96
-			Vars: map[string]string{
97
-				"name": "foo/bar",
98
-			},
99
-		},
100
-		{
101
-			RouteName:  RouteNameBlobUploadChunk,
102
-			RequestURI: "/v2/foo/bar/blobs/uploads/uuid",
103
-			Vars: map[string]string{
104
-				"name": "foo/bar",
105
-				"uuid": "uuid",
106
-			},
107
-		},
108
-		{
109
-			RouteName:  RouteNameBlobUploadChunk,
110
-			RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
111
-			Vars: map[string]string{
112
-				"name": "foo/bar",
113
-				"uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
114
-			},
115
-		},
116
-		{
117
-			RouteName:  RouteNameBlobUploadChunk,
118
-			RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
119
-			Vars: map[string]string{
120
-				"name": "foo/bar",
121
-				"uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==",
122
-			},
123
-		},
124
-		{
125
-			// Check ambiguity: ensure we can distinguish between tags for
126
-			// "foo/bar/image/image" and image for "foo/bar/image" with tag
127
-			// "tags"
128
-			RouteName:  RouteNameManifest,
129
-			RequestURI: "/v2/foo/bar/manifests/manifests/tags",
130
-			Vars: map[string]string{
131
-				"name":      "foo/bar/manifests",
132
-				"reference": "tags",
133
-			},
134
-		},
135
-		{
136
-			// This case presents an ambiguity between foo/bar with tag="tags"
137
-			// and list tags for "foo/bar/manifest"
138
-			RouteName:  RouteNameTags,
139
-			RequestURI: "/v2/foo/bar/manifests/tags/list",
140
-			Vars: map[string]string{
141
-				"name": "foo/bar/manifests",
142
-			},
143
-		},
144
-		{
145
-			RouteName:  RouteNameBlobUploadChunk,
146
-			RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286",
147
-			StatusCode: http.StatusNotFound,
148
-		},
149
-	} {
150
-		// Register the endpoint
151
-		router.GetRoute(testcase.RouteName).Handler(testHandler)
152
-		u := server.URL + testcase.RequestURI
153
-
154
-		resp, err := http.Get(u)
155
-
156
-		if err != nil {
157
-			t.Fatalf("error issuing get request: %v", err)
158
-		}
159
-
160
-		if testcase.StatusCode == 0 {
161
-			// Override default, zero-value
162
-			testcase.StatusCode = http.StatusOK
163
-		}
164
-
165
-		if resp.StatusCode != testcase.StatusCode {
166
-			t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode)
167
-		}
168
-
169
-		if testcase.StatusCode != http.StatusOK {
170
-			// We don't care about json response.
171
-			continue
172
-		}
173
-
174
-		dec := json.NewDecoder(resp.Body)
175
-
176
-		var actualRouteInfo routeTestCase
177
-		if err := dec.Decode(&actualRouteInfo); err != nil {
178
-			t.Fatalf("error reading json response: %v", err)
179
-		}
180
-		// Needs to be set out of band
181
-		actualRouteInfo.StatusCode = resp.StatusCode
182
-
183
-		if actualRouteInfo.RouteName != testcase.RouteName {
184
-			t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName)
185
-		}
186
-
187
-		if !reflect.DeepEqual(actualRouteInfo, testcase) {
188
-			t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase)
189
-		}
190
-	}
191
-
192
-}
193 1
deleted file mode 100644
... ...
@@ -1,179 +0,0 @@
1
-package v2
2
-
3
-import (
4
-	"net/http"
5
-	"net/url"
6
-
7
-	"github.com/gorilla/mux"
8
-)
9
-
10
-// URLBuilder creates registry API urls from a single base endpoint. It can be
11
-// used to create urls for use in a registry client or server.
12
-//
13
-// All urls will be created from the given base, including the api version.
14
-// For example, if a root of "/foo/" is provided, urls generated will be fall
15
-// under "/foo/v2/...". Most application will only provide a schema, host and
16
-// port, such as "https://localhost:5000/".
17
-type URLBuilder struct {
18
-	root   *url.URL // url root (ie http://localhost/)
19
-	router *mux.Router
20
-}
21
-
22
-// NewURLBuilder creates a URLBuilder with provided root url object.
23
-func NewURLBuilder(root *url.URL) *URLBuilder {
24
-	return &URLBuilder{
25
-		root:   root,
26
-		router: Router(),
27
-	}
28
-}
29
-
30
-// NewURLBuilderFromString workes identically to NewURLBuilder except it takes
31
-// a string argument for the root, returning an error if it is not a valid
32
-// url.
33
-func NewURLBuilderFromString(root string) (*URLBuilder, error) {
34
-	u, err := url.Parse(root)
35
-	if err != nil {
36
-		return nil, err
37
-	}
38
-
39
-	return NewURLBuilder(u), nil
40
-}
41
-
42
-// NewURLBuilderFromRequest uses information from an *http.Request to
43
-// construct the root url.
44
-func NewURLBuilderFromRequest(r *http.Request) *URLBuilder {
45
-	u := &url.URL{
46
-		Scheme: r.URL.Scheme,
47
-		Host:   r.Host,
48
-	}
49
-
50
-	return NewURLBuilder(u)
51
-}
52
-
53
-// BuildBaseURL constructs a base url for the API, typically just "/v2/".
54
-func (ub *URLBuilder) BuildBaseURL() (string, error) {
55
-	route := ub.cloneRoute(RouteNameBase)
56
-
57
-	baseURL, err := route.URL()
58
-	if err != nil {
59
-		return "", err
60
-	}
61
-
62
-	return baseURL.String(), nil
63
-}
64
-
65
-// BuildTagsURL constructs a url to list the tags in the named repository.
66
-func (ub *URLBuilder) BuildTagsURL(name string) (string, error) {
67
-	route := ub.cloneRoute(RouteNameTags)
68
-
69
-	tagsURL, err := route.URL("name", name)
70
-	if err != nil {
71
-		return "", err
72
-	}
73
-
74
-	return tagsURL.String(), nil
75
-}
76
-
77
-// BuildManifestURL constructs a url for the manifest identified by name and reference.
78
-func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) {
79
-	route := ub.cloneRoute(RouteNameManifest)
80
-
81
-	manifestURL, err := route.URL("name", name, "reference", reference)
82
-	if err != nil {
83
-		return "", err
84
-	}
85
-
86
-	return manifestURL.String(), nil
87
-}
88
-
89
-// BuildBlobURL constructs the url for the blob identified by name and dgst.
90
-func (ub *URLBuilder) BuildBlobURL(name string, dgst string) (string, error) {
91
-	route := ub.cloneRoute(RouteNameBlob)
92
-
93
-	layerURL, err := route.URL("name", name, "digest", dgst)
94
-	if err != nil {
95
-		return "", err
96
-	}
97
-
98
-	return layerURL.String(), nil
99
-}
100
-
101
-// BuildBlobUploadURL constructs a url to begin a blob upload in the
102
-// repository identified by name.
103
-func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) {
104
-	route := ub.cloneRoute(RouteNameBlobUpload)
105
-
106
-	uploadURL, err := route.URL("name", name)
107
-	if err != nil {
108
-		return "", err
109
-	}
110
-
111
-	return appendValuesURL(uploadURL, values...).String(), nil
112
-}
113
-
114
-// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid,
115
-// including any url values. This should generally not be used by clients, as
116
-// this url is provided by server implementations during the blob upload
117
-// process.
118
-func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) {
119
-	route := ub.cloneRoute(RouteNameBlobUploadChunk)
120
-
121
-	uploadURL, err := route.URL("name", name, "uuid", uuid)
122
-	if err != nil {
123
-		return "", err
124
-	}
125
-
126
-	return appendValuesURL(uploadURL, values...).String(), nil
127
-}
128
-
129
-// clondedRoute returns a clone of the named route from the router. Routes
130
-// must be cloned to avoid modifying them during url generation.
131
-func (ub *URLBuilder) cloneRoute(name string) clonedRoute {
132
-	route := new(mux.Route)
133
-	root := new(url.URL)
134
-
135
-	*route = *ub.router.GetRoute(name) // clone the route
136
-	*root = *ub.root
137
-
138
-	return clonedRoute{Route: route, root: root}
139
-}
140
-
141
-type clonedRoute struct {
142
-	*mux.Route
143
-	root *url.URL
144
-}
145
-
146
-func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) {
147
-	routeURL, err := cr.Route.URL(pairs...)
148
-	if err != nil {
149
-		return nil, err
150
-	}
151
-
152
-	return cr.root.ResolveReference(routeURL), nil
153
-}
154
-
155
-// appendValuesURL appends the parameters to the url.
156
-func appendValuesURL(u *url.URL, values ...url.Values) *url.URL {
157
-	merged := u.Query()
158
-
159
-	for _, v := range values {
160
-		for k, vv := range v {
161
-			merged[k] = append(merged[k], vv...)
162
-		}
163
-	}
164
-
165
-	u.RawQuery = merged.Encode()
166
-	return u
167
-}
168
-
169
-// appendValues appends the parameters to the url. Panics if the string is not
170
-// a url.
171
-func appendValues(u string, values ...url.Values) string {
172
-	up, err := url.Parse(u)
173
-
174
-	if err != nil {
175
-		panic(err) // should never happen
176
-	}
177
-
178
-	return appendValuesURL(up, values...).String()
179
-}
180 1
deleted file mode 100644
... ...
@@ -1,113 +0,0 @@
1
-package v2
2
-
3
-import (
4
-	"net/url"
5
-	"testing"
6
-)
7
-
8
-type urlBuilderTestCase struct {
9
-	description  string
10
-	expectedPath string
11
-	build        func() (string, error)
12
-}
13
-
14
-// TestURLBuilder tests the various url building functions, ensuring they are
15
-// returning the expected values.
16
-func TestURLBuilder(t *testing.T) {
17
-	var (
18
-		urlBuilder *URLBuilder
19
-		err        error
20
-	)
21
-
22
-	testCases := []urlBuilderTestCase{
23
-		{
24
-			description:  "test base url",
25
-			expectedPath: "/v2/",
26
-			build: func() (string, error) {
27
-				return urlBuilder.BuildBaseURL()
28
-			},
29
-		},
30
-		{
31
-			description:  "test tags url",
32
-			expectedPath: "/v2/foo/bar/tags/list",
33
-			build: func() (string, error) {
34
-				return urlBuilder.BuildTagsURL("foo/bar")
35
-			},
36
-		},
37
-		{
38
-			description:  "test manifest url",
39
-			expectedPath: "/v2/foo/bar/manifests/tag",
40
-			build: func() (string, error) {
41
-				return urlBuilder.BuildManifestURL("foo/bar", "tag")
42
-			},
43
-		},
44
-		{
45
-			description:  "build blob url",
46
-			expectedPath: "/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789",
47
-			build: func() (string, error) {
48
-				return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789")
49
-			},
50
-		},
51
-		{
52
-			description:  "build blob upload url",
53
-			expectedPath: "/v2/foo/bar/blobs/uploads/",
54
-			build: func() (string, error) {
55
-				return urlBuilder.BuildBlobUploadURL("foo/bar")
56
-			},
57
-		},
58
-		{
59
-			description:  "build blob upload url with digest and size",
60
-			expectedPath: "/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000",
61
-			build: func() (string, error) {
62
-				return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{
63
-					"size":   []string{"10000"},
64
-					"digest": []string{"tarsum.v1+sha256:abcdef0123456789"},
65
-				})
66
-			},
67
-		},
68
-		{
69
-			description:  "build blob upload chunk url",
70
-			expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part",
71
-			build: func() (string, error) {
72
-				return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part")
73
-			},
74
-		},
75
-		{
76
-			description:  "build blob upload chunk url with digest and size",
77
-			expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000",
78
-			build: func() (string, error) {
79
-				return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{
80
-					"size":   []string{"10000"},
81
-					"digest": []string{"tarsum.v1+sha256:abcdef0123456789"},
82
-				})
83
-			},
84
-		},
85
-	}
86
-
87
-	roots := []string{
88
-		"http://example.com",
89
-		"https://example.com",
90
-		"http://localhost:5000",
91
-		"https://localhost:5443",
92
-	}
93
-
94
-	for _, root := range roots {
95
-		urlBuilder, err = NewURLBuilderFromString(root)
96
-		if err != nil {
97
-			t.Fatalf("unexpected error creating urlbuilder: %v", err)
98
-		}
99
-
100
-		for _, testCase := range testCases {
101
-			url, err := testCase.build()
102
-			if err != nil {
103
-				t.Fatalf("%s: error building url: %v", testCase.description, err)
104
-			}
105
-
106
-			expectedURL := root + testCase.expectedPath
107
-
108
-			if url != expectedURL {
109
-				t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL)
110
-			}
111
-		}
112
-	}
113
-}