Skip to content

Commit 330b9eb

Browse files
committed
Fix pull-through failing to check repository when package was not in remote
fixes: #1004
1 parent 001147e commit 330b9eb

File tree

3 files changed

+61
-51
lines changed

3 files changed

+61
-51
lines changed

CHANGES/1004.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed pull-through caching not checking the repository if package was not present on remote.

pulp_python/app/pypi/views.py

Lines changed: 30 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from django.db import transaction
1515
from django.db.utils import DatabaseError
1616
from django.http.response import (
17-
Http404,
17+
HttpResponseNotFound,
1818
HttpResponseForbidden,
1919
HttpResponseBadRequest,
2020
StreamingHttpResponse,
@@ -287,7 +287,7 @@ def list(self, request, path):
287287
kwargs = {"content_type": media_type, "headers": headers}
288288
return StreamingHttpResponse(index_data, **kwargs)
289289

290-
def pull_through_package_simple(self, package, path, remote, media_type):
290+
def pull_through_package_simple(self, package, path, remote):
291291
"""Gets the package's simple page from remote."""
292292

293293
def parse_package(release_package):
@@ -305,35 +305,26 @@ def parse_package(release_package):
305305

306306
rfilter = get_remote_package_filter(remote)
307307
if not rfilter.filter_project(package):
308-
raise Http404(f"{package} does not exist.")
308+
return {}
309309

310310
url = remote.get_remote_artifact_url(f"simple/{package}/")
311311
remote.headers = remote.headers or []
312312
remote.headers.append({"Accept": ACCEPT_JSON_PREFERRED})
313313
downloader = remote.get_downloader(url=url, max_retries=1)
314314
try:
315315
d = downloader.fetch()
316-
except ClientError:
317-
return HttpResponse(f"Failed to fetch {package} from {remote.url}.", status=502)
318-
except TimeoutException:
319-
return HttpResponse(f"{remote.url} timed out while fetching {package}.", status=504)
316+
except (ClientError, TimeoutException):
317+
return {}
320318

321319
if d.headers["content-type"] == PYPI_SIMPLE_V1_JSON:
322320
page = ProjectPage.from_json_data(json.load(open(d.path, "rb")), base_url=url)
323321
else:
324322
page = ProjectPage.from_html(package, open(d.path, "rb").read(), base_url=url)
325-
packages = [
326-
parse_package(p) for p in page.packages if rfilter.filter_release(package, p.version)
327-
]
328-
headers = {"X-PyPI-Last-Serial": str(PYPI_SERIAL_CONSTANT)}
329-
330-
if media_type == PYPI_SIMPLE_V1_JSON:
331-
detail_data = write_simple_detail_json(package, packages)
332-
return Response(detail_data, headers=headers)
333-
else:
334-
detail_data = write_simple_detail(package, packages)
335-
kwargs = {"content_type": media_type, "headers": headers}
336-
return HttpResponse(detail_data, **kwargs)
323+
return {
324+
p.filename: parse_package(p)
325+
for p in page.packages
326+
if rfilter.filter_release(package, p.version)
327+
}
337328

338329
@extend_schema(operation_id="pypi_simple_package_read", summary="Get package simple page")
339330
def retrieve(self, request, path, package):
@@ -343,44 +334,36 @@ def retrieve(self, request, path, package):
343334
repo_ver, content = self.get_rvc()
344335
# Should I redirect if the normalized name is different?
345336
normalized = canonicalize_name(package)
337+
releases = {}
346338
if self.distribution.remote:
347-
return self.pull_through_package_simple(
348-
normalized, path, self.distribution.remote, media_type
349-
)
350-
if self.should_redirect(repo_version=repo_ver):
339+
releases = self.pull_through_package_simple(normalized, path, self.distribution.remote)
340+
elif self.should_redirect(repo_version=repo_ver):
351341
return redirect(urljoin(self.base_content_url, f"{path}/simple/{normalized}/"))
352-
packages = (
353-
content.filter(name__normalize=normalized)
354-
.values_list("filename", "sha256", "name", "metadata_sha256", "requires_python")
355-
.iterator()
356-
)
357-
try:
358-
present = next(packages)
359-
except StopIteration:
360-
raise Http404(f"{normalized} does not exist.")
361-
else:
362-
packages = chain([present], packages)
363-
name = present[2]
364-
releases = (
365-
{
366-
"filename": filename,
367-
"url": urljoin(self.base_content_url, f"{path}/{filename}"),
368-
"sha256": sha256,
369-
"metadata_sha256": metadata_sha256,
370-
"requires_python": requires_python,
342+
if content:
343+
packages = content.filter(name__normalize=normalized).values(
344+
"filename", "sha256", "metadata_sha256", "requires_python"
345+
)
346+
local_releases = {
347+
p["filename"]: {
348+
**p,
349+
"url": urljoin(self.base_content_url, f"{path}/{p['filename']}"),
350+
}
351+
for p in packages
371352
}
372-
for filename, sha256, _, metadata_sha256, requires_python in packages
373-
)
353+
releases.update(local_releases)
354+
if not releases:
355+
return HttpResponseNotFound(f"{normalized} does not exist.")
356+
374357
media_type = request.accepted_renderer.media_type
375358
headers = {"X-PyPI-Last-Serial": str(PYPI_SERIAL_CONSTANT)}
376359

377360
if media_type == PYPI_SIMPLE_V1_JSON:
378-
detail_data = write_simple_detail_json(name, releases)
361+
detail_data = write_simple_detail_json(normalized, releases.values())
379362
return Response(detail_data, headers=headers)
380363
else:
381-
detail_data = write_simple_detail(name, releases, streamed=True)
364+
detail_data = write_simple_detail(normalized, releases.values())
382365
kwargs = {"content_type": media_type, "headers": headers}
383-
return StreamingHttpResponse(detail_data, **kwargs)
366+
return HttpResponse(detail_data, **kwargs)
384367

385368
@extend_schema(
386369
request=PackageUploadSerializer,

pulp_python/tests/functional/api/test_full_mirror.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def test_pull_through_filter(python_remote_factory, python_distribution_factory)
8484

8585
r = requests.get(f"{distro.base_url}simple/pulpcore/")
8686
assert r.status_code == 404
87-
assert r.text == "404 Not Found"
87+
assert r.text == "pulpcore does not exist."
8888

8989
r = requests.get(f"{distro.base_url}simple/shelf-reader/")
9090
assert r.status_code == 200
@@ -104,11 +104,11 @@ def test_pull_through_filter(python_remote_factory, python_distribution_factory)
104104

105105
r = requests.get(f"{distro.base_url}simple/django/")
106106
assert r.status_code == 404
107-
assert r.text == "404 Not Found"
107+
assert r.text == "django does not exist."
108108

109109
r = requests.get(f"{distro.base_url}simple/pulpcore/")
110-
assert r.status_code == 502
111-
assert r.text == f"Failed to fetch pulpcore from {remote.url}."
110+
assert r.status_code == 404
111+
assert r.text == "pulpcore does not exist."
112112

113113
r = requests.get(f"{distro.base_url}simple/shelf-reader/")
114114
assert r.status_code == 200
@@ -156,3 +156,29 @@ def test_pull_through_with_repo(
156156
assert r.status_code == 200
157157
tasks = pulpcore_bindings.TasksApi.list(reserved_resources=repo.prn)
158158
assert tasks.count == 3
159+
160+
161+
@pytest.mark.parallel
162+
def test_pull_through_local_only(
163+
python_remote_factory, python_distribution_factory, python_repo_with_sync
164+
):
165+
"""Tests that pull-through checks the repository if the package is not present on the remote."""
166+
remote = python_remote_factory(url=PYPI_URL, includes=["pulpcore"])
167+
repo = python_repo_with_sync(remote=remote)
168+
remote2 = python_remote_factory(includes=[]) # Fixtures does not have pulpcore
169+
distro = python_distribution_factory(repository=repo.pulp_href, remote=remote2.pulp_href)
170+
171+
url = f"{distro.base_url}simple/pulpcore/"
172+
r = requests.get(url)
173+
assert r.status_code == 200
174+
assert "?redirect=" not in r.text
175+
176+
url = f"{distro.base_url}simple/shelf-reader/"
177+
r = requests.get(url)
178+
assert r.status_code == 200
179+
assert "?redirect=" in r.text
180+
181+
url = f"{distro.base_url}simple/pulp_python/"
182+
r = requests.get(url)
183+
assert r.status_code == 404
184+
assert r.text == "pulp-python does not exist."

0 commit comments

Comments
 (0)