forked from gitea/gitea
		
	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 <wxiaoguang@gmail.com>
This commit is contained in:
		
							parent
							
								
									d94f15c2fd
								
							
						
					
					
						commit
						69fc510d6d
					
				| @ -316,8 +316,10 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route { | ||||
| 			r.Group("/blobs/uploads", func() { | ||||
| 				r.Post("", container.InitiateUploadBlob) | ||||
| 				r.Group("/{uuid}", func() { | ||||
| 					r.Get("", container.GetUploadBlob) | ||||
| 					r.Patch("", container.UploadBlob) | ||||
| 					r.Put("", container.EndUploadBlob) | ||||
| 					r.Delete("", container.CancelUploadBlob) | ||||
| 				}) | ||||
| 			}, reqPackageAccess(perm.AccessModeWrite)) | ||||
| 			r.Group("/blobs/{digest}", func() { | ||||
| @ -377,7 +379,7 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route { | ||||
| 			} | ||||
| 
 | ||||
| 			m := blobsUploadsPattern.FindStringSubmatch(path) | ||||
| 			if len(m) == 3 && (isPut || isPatch) { | ||||
| 			if len(m) == 3 && (isGet || isPut || isPatch || isDelete) { | ||||
| 				reqPackageAccess(perm.AccessModeWrite)(ctx) | ||||
| 				if ctx.Written() { | ||||
| 					return | ||||
| @ -391,10 +393,14 @@ func ContainerRoutes(ctx gocontext.Context) *web.Route { | ||||
| 
 | ||||
| 				ctx.SetParams("uuid", m[2]) | ||||
| 
 | ||||
| 				if isPatch { | ||||
| 				if isGet { | ||||
| 					container.GetUploadBlob(ctx) | ||||
| 				} else if isPatch { | ||||
| 					container.UploadBlob(ctx) | ||||
| 				} else { | ||||
| 				} else if isPut { | ||||
| 					container.EndUploadBlob(ctx) | ||||
| 				} else { | ||||
| 					container.CancelUploadBlob(ctx) | ||||
| 				} | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| @ -248,6 +248,27 @@ func InitiateUploadBlob(ctx *context.Context) { | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // https://docs.docker.com/registry/spec/api/#get-blob-upload | ||||
| func GetUploadBlob(ctx *context.Context) { | ||||
| 	uuid := ctx.Params("uuid") | ||||
| 
 | ||||
| 	upload, err := packages_model.GetBlobUploadByID(ctx, uuid) | ||||
| 	if err != nil { | ||||
| 		if err == packages_model.ErrPackageBlobUploadNotExist { | ||||
| 			apiErrorDefined(ctx, errBlobUploadUnknown) | ||||
| 		} else { | ||||
| 			apiError(ctx, http.StatusInternalServerError, err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	setResponseHeaders(ctx.Resp, &containerHeaders{ | ||||
| 		Range:      fmt.Sprintf("0-%d", upload.BytesReceived), | ||||
| 		UploadUUID: upload.ID, | ||||
| 		Status:     http.StatusNoContent, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // https://github.com/opencontainers/distribution-spec/blob/main/spec.md#pushing-a-blob-in-chunks | ||||
| func UploadBlob(ctx *context.Context) { | ||||
| 	image := ctx.Params("image") | ||||
| @ -354,6 +375,30 @@ func EndUploadBlob(ctx *context.Context) { | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| // https://docs.docker.com/registry/spec/api/#delete-blob-upload | ||||
| func CancelUploadBlob(ctx *context.Context) { | ||||
| 	uuid := ctx.Params("uuid") | ||||
| 
 | ||||
| 	_, err := packages_model.GetBlobUploadByID(ctx, uuid) | ||||
| 	if err != nil { | ||||
| 		if err == packages_model.ErrPackageBlobUploadNotExist { | ||||
| 			apiErrorDefined(ctx, errBlobUploadUnknown) | ||||
| 		} else { | ||||
| 			apiError(ctx, http.StatusInternalServerError, err) | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	if err := container_service.RemoveBlobUploadByID(ctx, uuid); err != nil { | ||||
| 		apiError(ctx, http.StatusInternalServerError, err) | ||||
| 		return | ||||
| 	} | ||||
| 
 | ||||
| 	setResponseHeaders(ctx.Resp, &containerHeaders{ | ||||
| 		Status: http.StatusNoContent, | ||||
| 	}) | ||||
| } | ||||
| 
 | ||||
| func getBlobFromContext(ctx *context.Context) (*packages_model.PackageFileDescriptor, error) { | ||||
| 	digest := ctx.Params("digest") | ||||
| 
 | ||||
|  | ||||
| @ -205,18 +205,54 @@ func TestPackageContainer(t *testing.T) { | ||||
| 				assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) | ||||
| 				assert.Equal(t, contentRange, resp.Header().Get("Range")) | ||||
| 
 | ||||
| 				uploadURL = resp.Header().Get("Location") | ||||
| 
 | ||||
| 				req = NewRequest(t, "GET", setting.AppURL+uploadURL[1:]) | ||||
| 				addTokenAuthHeader(req, userToken) | ||||
| 				resp = MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 				assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) | ||||
| 				assert.Equal(t, fmt.Sprintf("0-%d", len(blobContent)), resp.Header().Get("Range")) | ||||
| 
 | ||||
| 				pbu, err = packages_model.GetBlobUploadByID(db.DefaultContext, uuid) | ||||
| 				assert.NoError(t, err) | ||||
| 				assert.EqualValues(t, len(blobContent), pbu.BytesReceived) | ||||
| 
 | ||||
| 				uploadURL = resp.Header().Get("Location") | ||||
| 
 | ||||
| 				req = NewRequest(t, "PUT", fmt.Sprintf("%s?digest=%s", setting.AppURL+uploadURL[1:], blobDigest)) | ||||
| 				addTokenAuthHeader(req, userToken) | ||||
| 				resp = MakeRequest(t, req, http.StatusCreated) | ||||
| 
 | ||||
| 				assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) | ||||
| 				assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) | ||||
| 
 | ||||
| 				t.Run("Cancel", func(t *testing.T) { | ||||
| 					defer tests.PrintCurrentTest(t)() | ||||
| 
 | ||||
| 					req := NewRequest(t, "POST", fmt.Sprintf("%s/blobs/uploads", url)) | ||||
| 					addTokenAuthHeader(req, userToken) | ||||
| 					resp := MakeRequest(t, req, http.StatusAccepted) | ||||
| 
 | ||||
| 					uuid := resp.Header().Get("Docker-Upload-Uuid") | ||||
| 					assert.NotEmpty(t, uuid) | ||||
| 
 | ||||
| 					uploadURL := resp.Header().Get("Location") | ||||
| 					assert.NotEmpty(t, uploadURL) | ||||
| 
 | ||||
| 					req = NewRequest(t, "GET", setting.AppURL+uploadURL[1:]) | ||||
| 					addTokenAuthHeader(req, userToken) | ||||
| 					resp = MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 					assert.Equal(t, uuid, resp.Header().Get("Docker-Upload-Uuid")) | ||||
| 					assert.Equal(t, "0-0", resp.Header().Get("Range")) | ||||
| 
 | ||||
| 					req = NewRequest(t, "DELETE", setting.AppURL+uploadURL[1:]) | ||||
| 					addTokenAuthHeader(req, userToken) | ||||
| 					MakeRequest(t, req, http.StatusNoContent) | ||||
| 
 | ||||
| 					req = NewRequest(t, "GET", setting.AppURL+uploadURL[1:]) | ||||
| 					addTokenAuthHeader(req, userToken) | ||||
| 					MakeRequest(t, req, http.StatusNotFound) | ||||
| 				}) | ||||
| 			}) | ||||
| 
 | ||||
| 			for _, tag := range tags { | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 KN4CK3R
						KN4CK3R