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)
| ... | ... |
@@ -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,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 |
-} |