diff --git a/api_test.go b/api_test.go index a650a102..6cf56344 100644 --- a/api_test.go +++ b/api_test.go @@ -460,7 +460,10 @@ func pushLayer(t *testing.T, ub *v2.URLBuilder, name string, dgst digest.Digest, u.RawQuery = url.Values{ "digest": []string{dgst.String()}, - "size": []string{fmt.Sprint(rsLength)}, + + // TODO(stevvooe): Layer upload can be completed with and without size + // argument. We'll need to add a test that checks the latter path. + "size": []string{fmt.Sprint(rsLength)}, }.Encode() uploadURL := u.String() diff --git a/layerupload.go b/layerupload.go index b65c8ef2..91c3b5cc 100644 --- a/layerupload.go +++ b/layerupload.go @@ -119,9 +119,20 @@ func (luh *layerUploadHandler) PutLayerChunk(w http.ResponseWriter, r *http.Requ if err := luh.maybeCompleteUpload(w, r); err != nil { if err != errNotReadyToComplete { - w.WriteHeader(http.StatusInternalServerError) - luh.Errors.Push(v2.ErrorCodeUnknown, err) - return + switch err := err.(type) { + case storage.ErrLayerInvalidSize: + w.WriteHeader(http.StatusBadRequest) + luh.Errors.Push(v2.ErrorCodeSizeInvalid, err) + return + case storage.ErrLayerInvalidDigest: + w.WriteHeader(http.StatusBadRequest) + luh.Errors.Push(v2.ErrorCodeDigestInvalid, err) + return + default: + w.WriteHeader(http.StatusInternalServerError) + luh.Errors.Push(v2.ErrorCodeUnknown, err) + return + } } } @@ -173,7 +184,7 @@ func (luh *layerUploadHandler) maybeCompleteUpload(w http.ResponseWriter, r *htt dgstStr := r.FormValue("digest") // TODO(stevvooe): Support multiple digest parameters! sizeStr := r.FormValue("size") - if dgstStr == "" || sizeStr == "" { + if dgstStr == "" { return errNotReadyToComplete } @@ -182,9 +193,14 @@ func (luh *layerUploadHandler) maybeCompleteUpload(w http.ResponseWriter, r *htt return err } - size, err := strconv.ParseInt(sizeStr, 10, 64) - if err != nil { - return err + var size int64 + if sizeStr != "" { + size, err = strconv.ParseInt(sizeStr, 10, 64) + if err != nil { + return err + } + } else { + size = -1 } luh.completeUpload(w, r, size, dgst) diff --git a/storage/layer.go b/storage/layer.go index 2ad91314..84f83d2d 100644 --- a/storage/layer.go +++ b/storage/layer.go @@ -43,9 +43,14 @@ type LayerUpload interface { // Offset returns the position of the last byte written to this layer. Offset() int64 + // TODO(stevvooe): Consider completely removing the size check from this + // interface. The digest check may be adequate and we are making it + // optional in the HTTP API. + // Finish marks the upload as completed, returning a valid handle to the // uploaded layer. The final size and digest are validated against the - // contents of the uploaded layer. + // contents of the uploaded layer. If the size is negative, only the + // digest will be checked. Finish(size int64, digest digest.Digest) (Layer, error) // Cancel the layer upload process. @@ -62,9 +67,6 @@ var ( // ErrLayerUploadUnknown returned when upload is not found. ErrLayerUploadUnknown = fmt.Errorf("layer upload unknown") - // ErrLayerInvalidLength returned when length check fails. - ErrLayerInvalidLength = fmt.Errorf("invalid layer length") - // ErrLayerClosed returned when an operation is attempted on a closed // Layer or LayerUpload. ErrLayerClosed = fmt.Errorf("layer closed") @@ -87,3 +89,12 @@ type ErrLayerInvalidDigest struct { func (err ErrLayerInvalidDigest) Error() string { return fmt.Sprintf("invalid digest for referenced layer: %v", err.FSLayer.BlobSum) } + +// ErrLayerInvalidSize returned when length check fails. +type ErrLayerInvalidSize struct { + Size int64 +} + +func (err ErrLayerInvalidSize) Error() string { + return fmt.Sprintf("invalid layer size: %d", err.Size) +} diff --git a/storage/layerupload.go b/storage/layerupload.go index 3ee593b9..63ae5d6f 100644 --- a/storage/layerupload.go +++ b/storage/layerupload.go @@ -110,7 +110,7 @@ func (luc *layerUploadController) Finish(size int64, digest digest.Digest) (Laye if nn, err := luc.writeLayer(fp, digest); err != nil { // Cleanup? return nil, err - } else if nn != size { + } else if size >= 0 && nn != size { // TODO(stevvooe): Short write. Will have to delete the location and // report an error. This error needs to be reported to the client. return nil, fmt.Errorf("short write writing layer") @@ -252,9 +252,10 @@ func (luc *layerUploadController) validateLayer(fp layerFile, size int64, dgst d return "", err } - if end != size { + // Only check size if it is greater than + if size >= 0 && end != size { // Fast path length check. - return "", ErrLayerInvalidLength + return "", ErrLayerInvalidSize{Size: size} } // Now seek back to start and take care of the digest. @@ -262,8 +263,12 @@ func (luc *layerUploadController) validateLayer(fp layerFile, size int64, dgst d return "", err } - tr := io.TeeReader(fp, lengthVerifier) - tr = io.TeeReader(tr, digestVerifier) + tr := io.TeeReader(fp, digestVerifier) + + // Only verify the size if a positive size argument has been passed. + if size >= 0 { + tr = io.TeeReader(tr, lengthVerifier) + } // TODO(stevvooe): This is one of the places we need a Digester write // sink. Instead, its read driven. This migth be okay. @@ -274,8 +279,8 @@ func (luc *layerUploadController) validateLayer(fp layerFile, size int64, dgst d return "", err } - if !lengthVerifier.Verified() { - return "", ErrLayerInvalidLength + if size >= 0 && !lengthVerifier.Verified() { + return "", ErrLayerInvalidSize{Size: size} } if !digestVerifier.Verified() {