From 6fb22ade5cc2f1b792eb98a0444c7e20e1f16c9f Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Mon, 1 Jun 2026 16:01:17 -0400 Subject: [PATCH 1/3] Add GET blob upload status endpoint Implement Docker v2 GET/HEAD on /v2//blobs/uploads/ to report resumable upload progress. Closes #483. Co-authored-by: Cursor --- CHANGES/483.feature | 1 + pulp_container/app/registry_api.py | 12 +++++++ .../tests/functional/api/test_push_content.py | 32 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 CHANGES/483.feature diff --git a/CHANGES/483.feature b/CHANGES/483.feature new file mode 100644 index 000000000..d751ba52a --- /dev/null +++ b/CHANGES/483.feature @@ -0,0 +1 @@ +Added support for retrieving blob upload status via `GET /v2//blobs/uploads/`. diff --git a/pulp_container/app/registry_api.py b/pulp_container/app/registry_api.py index 92adfcbc9..78ae07437 100644 --- a/pulp_container/app/registry_api.py +++ b/pulp_container/app/registry_api.py @@ -1014,6 +1014,18 @@ def partial_update(self, request, path, pk=None): return UploadResponse(upload=upload, path=path, request=request) + def get(self, request, path, pk=None): + """ + Retrieve the status of an upload. + """ + _, repository = self.get_dr_push(request, path) + upload = get_object_or_404(models.Upload, repository=repository, pk=pk) + return UploadResponse(upload=upload, path=path, request=request, status=204) + + def head(self, request, path, pk=None): + """Respond to HEAD requests about blob uploads.""" + return self.get(request, path, pk=pk) + def put(self, request, path, pk=None): """ Create a blob from uploaded chunks. diff --git a/pulp_container/tests/functional/api/test_push_content.py b/pulp_container/tests/functional/api/test_push_content.py index 3c1ce607f..4d7895e70 100644 --- a/pulp_container/tests/functional/api/test_push_content.py +++ b/pulp_container/tests/functional/api/test_push_content.py @@ -605,3 +605,35 @@ def test_push_empty_manifest_list( assert manifest_list.media_type == MEDIA_TYPE.MANIFEST_LIST assert manifest_list.schema_version == 2 assert manifest_list.listed_manifests == [] + + +def test_blob_upload_status(container_bindings, full_path, add_to_cleanup): + """Test GET blob upload status returns current upload progress.""" + repo_name = "test/upload_status" + upload_url = urljoin( + container_bindings.client.configuration.host, + f"/v2/{full_path(repo_name)}/blobs/uploads/", + ) + auth = get_auth_for_url(upload_url) + + response = requests.post(upload_url, auth=auth) + response.raise_for_status() + assert response.status_code == 202 + upload_uuid = response.headers["Docker-Upload-UUID"] + location = response.headers["Location"] + + status_url = urljoin(container_bindings.client.configuration.host, location) + response = requests.get(status_url, auth=auth) + response.raise_for_status() + assert response.status_code == 204 + assert response.headers["Docker-Upload-UUID"] == upload_uuid + assert response.headers["Range"] == "0-0" + assert response.headers["Content-Length"] == "0" + + response = requests.head(status_url, auth=auth) + response.raise_for_status() + assert response.status_code == 204 + assert response.headers["Docker-Upload-UUID"] == upload_uuid + + namespace = container_bindings.PulpContainerNamespacesApi.list(name="test").results[0] + add_to_cleanup(container_bindings.PulpContainerNamespacesApi, namespace.pulp_href) From 91fc364b19d1910b3e5735295873c9174c4eb17f Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Mon, 1 Jun 2026 16:22:26 -0400 Subject: [PATCH 2/3] Fix blob upload status test authentication Use local_registry.get_response with POST to obtain a push-scoped token instead of get_auth_for_url, which probes with GET and gets pull scope. Co-authored-by: Cursor --- .../tests/functional/api/test_push_content.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pulp_container/tests/functional/api/test_push_content.py b/pulp_container/tests/functional/api/test_push_content.py index 4d7895e70..6c2198fc6 100644 --- a/pulp_container/tests/functional/api/test_push_content.py +++ b/pulp_container/tests/functional/api/test_push_content.py @@ -607,30 +607,25 @@ def test_push_empty_manifest_list( assert manifest_list.listed_manifests == [] -def test_blob_upload_status(container_bindings, full_path, add_to_cleanup): +def test_blob_upload_status(local_registry, container_bindings, full_path, add_to_cleanup): """Test GET blob upload status returns current upload progress.""" repo_name = "test/upload_status" - upload_url = urljoin( - container_bindings.client.configuration.host, - f"/v2/{full_path(repo_name)}/blobs/uploads/", - ) - auth = get_auth_for_url(upload_url) + upload_path = f"/v2/{full_path(repo_name)}/blobs/uploads/" - response = requests.post(upload_url, auth=auth) + response, auth = local_registry.get_response("POST", upload_path) response.raise_for_status() assert response.status_code == 202 upload_uuid = response.headers["Docker-Upload-UUID"] location = response.headers["Location"] - status_url = urljoin(container_bindings.client.configuration.host, location) - response = requests.get(status_url, auth=auth) + response, _ = local_registry.get_response("GET", location, auth=auth) response.raise_for_status() assert response.status_code == 204 assert response.headers["Docker-Upload-UUID"] == upload_uuid assert response.headers["Range"] == "0-0" assert response.headers["Content-Length"] == "0" - response = requests.head(status_url, auth=auth) + response, _ = local_registry.get_response("HEAD", location, auth=auth) response.raise_for_status() assert response.status_code == 204 assert response.headers["Docker-Upload-UUID"] == upload_uuid From fd9fb1c6beac9321a85fac667d4272b7de16c5e7 Mon Sep 17 00:00:00 2001 From: Gerrod Ubben Date: Mon, 1 Jun 2026 16:49:10 -0400 Subject: [PATCH 3/3] Fix blob upload status test GET/HEAD requests Reuse the push-scoped bearer token from POST with requests.get/head instead of passing auth into local_registry.get_response. Co-authored-by: Cursor --- pulp_container/tests/functional/api/test_push_content.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pulp_container/tests/functional/api/test_push_content.py b/pulp_container/tests/functional/api/test_push_content.py index 6c2198fc6..74804ce4f 100644 --- a/pulp_container/tests/functional/api/test_push_content.py +++ b/pulp_container/tests/functional/api/test_push_content.py @@ -618,14 +618,15 @@ def test_blob_upload_status(local_registry, container_bindings, full_path, add_t upload_uuid = response.headers["Docker-Upload-UUID"] location = response.headers["Location"] - response, _ = local_registry.get_response("GET", location, auth=auth) + status_url = urljoin(container_bindings.client.configuration.host, location) + response = requests.get(status_url, auth=auth) response.raise_for_status() assert response.status_code == 204 assert response.headers["Docker-Upload-UUID"] == upload_uuid assert response.headers["Range"] == "0-0" assert response.headers["Content-Length"] == "0" - response, _ = local_registry.get_response("HEAD", location, auth=auth) + response = requests.head(status_url, auth=auth) response.raise_for_status() assert response.status_code == 204 assert response.headers["Docker-Upload-UUID"] == upload_uuid