Skip to content

Commit 69fc510

Browse files
KN4CK3Rwxiaoguang
andauthored
Add GET and DELETE endpoints for Docker blob uploads (#21367)
This PR adds support for https://docs.docker.com/registry/spec/api/#get-blob-upload https://docs.docker.com/registry/spec/api/#delete-blob-upload Both are not required by the OCI spec but some clients call these endpoints. Co-authored-by: wxiaoguang <[email protected]>
1 parent d94f15c commit 69fc510

File tree

3 files changed

+92
-5
lines changed

3 files changed

+92
-5
lines changed

routers/api/packages/api.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -316,8 +316,10 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
316316
r.Group("/blobs/uploads", func() {
317317
r.Post("", container.InitiateUploadBlob)
318318
r.Group("/{uuid}", func() {
319+
r.Get("", container.GetUploadBlob)
319320
r.Patch("", container.UploadBlob)
320321
r.Put("", container.EndUploadBlob)
322+
r.Delete("", container.CancelUploadBlob)
321323
})
322324
}, reqPackageAccess(perm.AccessModeWrite))
323325
r.Group("/blobs/{digest}", func() {
@@ -377,7 +379,7 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
377379
}
378380

379381
m := blobsUploadsPattern.FindStringSubmatch(path)
380-
if len(m) == 3 && (isPut || isPatch) {
382+
if len(m) == 3 && (isGet || isPut || isPatch || isDelete) {
381383
reqPackageAccess(perm.AccessModeWrite)(ctx)
382384
if ctx.Written() {
383385
return
@@ -391,10 +393,14 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route {
391393

392394
ctx.SetParams("uuid", m[2])
393395

394-
if isPatch {
396+
if isGet {
397+
container.GetUploadBlob(ctx)
398+
} else if isPatch {
395399
container.UploadBlob(ctx)
396-
} else {
400+
} else if isPut {
397401
container.EndUploadBlob(ctx)
402+
} else {
403+
container.CancelUploadBlob(ctx)
398404
}
399405
return
400406
}

routers/api/packages/container/container.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,27 @@ func InitiateUploadBlob(ctx *context.Context) {
248248
})
249249
}
250250

251+
// https://docs.docker.com/registry/spec/api/#get-blob-upload
252+
func GetUploadBlob(ctx *context.Context) {
253+
uuid := ctx.Params("uuid")
254+
255+
upload, err := packages_model.GetBlobUploadByID(ctx, uuid)
256+
if err != nil {
257+
if err == packages_model.ErrPackageBlobUploadNotExist {
258+
apiErrorDefined(ctx, errBlobUploadUnknown)
259+
} else {
260+
apiError(ctx, http.StatusInternalServerError, err)
261+
}
262+
return
263+
}
264+
265+
setResponseHeaders(ctx.Resp, &containerHeaders{
266+
Range: fmt.Sprintf("0-%d", upload.BytesReceived),
267+
UploadUUID: upload.ID,
268+
Status: http.StatusNoContent,
269+
})
270+
}
271+
251272
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks
252273
func UploadBlob(ctx *context.Context) {
253274
image := ctx.Params("image")
@@ -354,6 +375,30 @@ func EndUploadBlob(ctx *context.Context) {
354375
})
355376
}
356377

378+
// https://docs.docker.com/registry/spec/api/#delete-blob-upload
379+
func CancelUploadBlob(ctx *context.Context) {
380+
uuid := ctx.Params("uuid")
381+
382+
_, err := packages_model.GetBlobUploadByID(ctx, uuid)
383+
if err != nil {
384+
if err == packages_model.ErrPackageBlobUploadNotExist {
385+
apiErrorDefined(ctx, errBlobUploadUnknown)
386+
} else {
387+
apiError(ctx, http.StatusInternalServerError, err)
388+
}
389+
return
390+
}
391+
392+
if err := container_service.RemoveBlobUploadByID(ctx, uuid); err != nil {
393+
apiError(ctx, http.StatusInternalServerError, err)
394+
return
395+
}
396+
397+
setResponseHeaders(ctx.Resp, &containerHeaders{
398+
Status: http.StatusNoContent,
399+
})
400+
}
401+
357402
func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescriptor, error) {
358403
digest := ctx.Params("digest")
359404

tests/integration/api_packages_container_test.go

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,18 +205,54 @@ func TestPackageContainer(t *testing.T) {
205205
assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid"))
206206
assert.Equal(t, contentRange, resp.Header().Get("Range"))
207207

208+
uploadURL = resp.Header().Get("Location")
209+
210+
req = NewRequest(t, "GET", setting.AppURL+uploadURL[1:])
211+
addTokenAuthHeader(req, userToken)
212+
resp = MakeRequest(t, req, http.StatusNoContent)
213+
214+
assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid"))
215+
assert.Equal(t, fmt.Sprintf("0-%d", len(blobContent)), resp.Header().Get("Range"))
216+
208217
pbu, err = packages_model.GetBlobUploadByID(db.DefaultContext, uuid)
209218
assert.NoError(t, err)
210219
assert.EqualValues(t, len(blobContent), pbu.BytesReceived)
211220

212-
uploadURL = resp.Header().Get("Location")
213-
214221
req = NewRequest(t, "PUT", fmt.Sprintf("%s?digest=%s", setting.AppURL+uploadURL[1:], blobDigest))
215222
addTokenAuthHeader(req, userToken)
216223
resp = MakeRequest(t, req, http.StatusCreated)
217224

218225
assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location"))
219226
assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest"))
227+
228+
t.Run("Cancel", func(t *testing.T) {
229+
defer tests.PrintCurrentTest(t)()
230+
231+
req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url))
232+
addTokenAuthHeader(req, userToken)
233+
resp := MakeRequest(t, req, http.StatusAccepted)
234+
235+
uuid := resp.Header().Get("Docker-Upload-Uuid")
236+
assert.NotEmpty(t, uuid)
237+
238+
uploadURL := resp.Header().Get("Location")
239+
assert.NotEmpty(t, uploadURL)
240+
241+
req = NewRequest(t, "GET", setting.AppURL+uploadURL[1:])
242+
addTokenAuthHeader(req, userToken)
243+
resp = MakeRequest(t, req, http.StatusNoContent)
244+
245+
assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid"))
246+
assert.Equal(t, "0-0", resp.Header().Get("Range"))
247+
248+
req = NewRequest(t, "DELETE", setting.AppURL+uploadURL[1:])
249+
addTokenAuthHeader(req, userToken)
250+
MakeRequest(t, req, http.StatusNoContent)
251+
252+
req = NewRequest(t, "GET", setting.AppURL+uploadURL[1:])
253+
addTokenAuthHeader(req, userToken)
254+
MakeRequest(t, req, http.StatusNotFound)
255+
})
220256
})
221257

222258
for _, tag := range tags {

0 commit comments

Comments
 (0)