diff --git a/conformance/02_push_test.go b/conformance/02_push_test.go index 18321557..22e29823 100644 --- a/conformance/02_push_test.go +++ b/conformance/02_push_test.go @@ -12,7 +12,7 @@ import ( var test02Push = func() { g.Context(titlePush, func() { - var lastResponse *reggie.Response + var lastResponse, prevResponse *reggie.Response g.Context("Setup", func() { // No setup required at this time for push tests @@ -175,6 +175,7 @@ var test02Push = func() { Expect(err).To(BeNil()) location := resp.Header().Get("Location") Expect(location).ToNot(BeEmpty()) + prevResponse = resp req = client.NewRequest(reggie.PATCH, resp.GetRelativeLocation()). SetHeader("Content-Type", "application/octet-stream"). @@ -184,6 +185,30 @@ var test02Push = func() { resp, err = client.Do(req) Expect(err).To(BeNil()) Expect(resp.StatusCode()).To(Equal(http.StatusAccepted)) + Expect(resp.Header().Get("Range")).To(Equal(testBlobBChunk1Range)) + lastResponse = resp + }) + + g.Specify("Retry previous blob chunk should return 416", func() { + SkipIfDisabled(push) + req := client.NewRequest(reggie.PATCH, prevResponse.GetRelativeLocation()). + SetHeader("Content-Type", "application/octet-stream"). + SetHeader("Content-Length", testBlobBChunk1Length). + SetHeader("Content-Range", testBlobBChunk1Range). + SetBody(testBlobBChunk1) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(Equal(http.StatusRequestedRangeNotSatisfiable)) + }) + + g.Specify("Get on stale blob upload should return 204 with a range and location", func() { + SkipIfDisabled(push) + req := client.NewRequest(reggie.GET, prevResponse.GetRelativeLocation()) + resp, err := client.Do(req) + Expect(err).To(BeNil()) + Expect(resp.StatusCode()).To(Equal(http.StatusNoContent)) + Expect(resp.Header().Get("Location")).ToNot(BeEmpty()) + Expect(resp.Header().Get("Range")).To(Equal(fmt.Sprintf("bytes=%s", testBlobBChunk1Range))) lastResponse = resp }) diff --git a/spec.md b/spec.md index 3c4f3a74..82503cbe 100644 --- a/spec.md +++ b/spec.md @@ -348,16 +348,20 @@ It MUST match the following regular expression: The `` is the content-length, in bytes, of the current chunk. -Each successful chunk upload MUST have a `202 Accepted` response code, and MUST have the following header: +Each successful chunk upload MUST have a `202 Accepted` response code, and MUST have the following headers: ``` -Location +Location: +Range: 0- ``` Each consecutive chunk upload SHOULD use the `` provided in the response to the previous chunk upload. +The `` value is the position of the last uploaded byte. + Chunks MUST be uploaded in order, with the first byte of a chunk being the last chunk's `` plus one. If a chunk is uploaded out of order, the registry MUST respond with a `416 Requested Range Not Satisfiable` code. +A GET request may be used to retrieve the current valid offset and upload location. The final chunk MAY be uploaded using a `PATCH` request or it MAY be uploaded in the closing `PUT` request. Regardless of how the final chunk is uploaded, the session MUST be closed with a `PUT` request. @@ -385,6 +389,22 @@ Location: Here, `` is a pullable blob URL. +--- + +To get the current status after a 416 error, issue a `GET` request to a URL `` [end-13](#endpoints). + +The `` refers to the URL obtained from any preceding `POST` or `PATCH` request. + +The response to an active upload `` MUST be a `204 No Content` response code, and MUST have the following headers: + +``` +Location: +Range: 0- +``` + +The following chunk upload SHOULD use the `` provided in the response. + +The `` value is the position of the last uploaded byte. ##### Mounting a blob from another repository @@ -716,6 +736,7 @@ This endpoint MAY be used for authentication/authorization purposes, but this is | end-11 | `POST` | `/v2//blobs/uploads/?mount=&from=` | `201` | `404` | | end-12a | `GET` | `/v2//referrers/` | `200` | `404`/`400` | | end-12b | `GET` | `/v2//referrers/?artifactType=` | `200` | `404`/`400` | +| end-13 | `GET` | `/v2//blobs/uploads/` | `204` | `404` | #### Error Codes