Skip to content

Commit 7fa97f2

Browse files
committed
Second cut
1 parent c36e4ef commit 7fa97f2

2 files changed

Lines changed: 106 additions & 27 deletions

File tree

pulp_rust/app/urls.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
11
from django.conf import settings
22
from django.urls import path, re_path
33

4-
from pulp_rust.app.views import IndexRoot, CargoApiViewSet
4+
from pulp_rust.app.views import IndexRoot, CargoApiViewSet, CargoDownloadApiView
55

66
if settings.DOMAIN_ENABLED:
77
path_re = r"(?P<pulp_domain>[-a-zA-Z0-9_]+)/(?P<name>[\w-]+)/(?P<path>.*)"
8-
CRATES_IO_URL = "<slug:pulp_domain>/<path:path>/"
8+
CRATES_IO_URL = "pulp/cargo/<slug:pulp_domain>/<slug:repo>/"
99
else:
1010
path_re = r"(?P<name>[\w-]+)/(?P<path>.*)"
11-
CRATES_IO_URL = "<path:path>/"
11+
CRATES_IO_URL = "pulp/cargo/<slug:repo>/"
1212

1313

1414
urlpatterns = [
15-
path(CRATES_IO_URL, IndexRoot.as_view({"get": "retrieve"}), name="crates-io-root"),
16-
re_path(rf"^pulp/cargo/{path_re}$", CargoApiViewSet.as_view({"get": "retrieve"})),
15+
path(CRATES_IO_URL + "config.json", IndexRoot.as_view({"get": "retrieve"}), name="index-root"),
16+
path(
17+
CRATES_IO_URL + "api/v1/crates/<str:package>/<str:version>/<path:rest>",
18+
CargoDownloadApiView.as_view(),
19+
name="cargo-download-api",
20+
),
21+
path(CRATES_IO_URL + "/<path:path>", CargoApiViewSet.as_view({"get": "retrieve"}), name="cargo-api"),
22+
23+
24+
25+
# re_path(rf"^pulp/cargo/{path_re}$", CargoApiViewSet.as_view({"get": "retrieve"})),
1726
]

pulp_rust/app/views.py

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import json
22
import logging
33

4+
from rest_framework.views import APIView
45
from rest_framework.viewsets import ViewSet
56
from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer, TemplateHTMLRenderer
67
from rest_framework.response import Response
78
from rest_framework.decorators import api_view
8-
from rest_framework.exceptions import NotAcceptable
9+
from rest_framework.exceptions import NotAcceptable, Throttled
910
from django.core.exceptions import ObjectDoesNotExist
1011
from django.shortcuts import redirect
1112
from datetime import datetime, timezone, timedelta
@@ -32,20 +33,19 @@
3233
from pulpcore.plugin.tasking import dispatch
3334
from pulpcore.plugin.util import get_domain, get_url
3435

35-
from pulp_rust.app.models import RustDistribution, RustContent
36+
from pulp_rust.app.models import RustDistribution, RustRepository, RustContent
3637
from pulp_rust.app.serializers import (
3738
IndexRootSerializer,
3839
RustContentSerializer,
3940
)
40-
4141
from pulp_rust.app import tasks
4242

4343
log = logging.getLogger(__name__)
4444

4545
ORIGIN_HOST = settings.CONTENT_ORIGIN if settings.CONTENT_ORIGIN else settings.PYPI_API_HOSTNAME
4646
BASE_CONTENT_URL = urljoin(ORIGIN_HOST, settings.CONTENT_PATH_PREFIX)
4747
BASE_API_URL = settings.CRATES_IO_API_HOSTNAME
48-
CRATES_IO_API_URL = urljoin(BASE_API_URL, "/api/v1/crates/")
48+
CRATES_IO_API = "/api/v1/crates/"
4949

5050

5151
class ApiMixin:
@@ -55,22 +55,18 @@ class ApiMixin:
5555

5656
@property
5757
def distribution(self):
58-
if self._distro:
59-
return self._distro
60-
61-
path = self.kwargs["path"]
62-
distro = self.get_distribution(path)
63-
self._distro = distro
64-
return distro
58+
if not self._distro:
59+
self._distro = self.get_distribution(self.kwargs["repo"])
60+
return self._distro
6561

6662
@staticmethod
67-
def get_distribution(path):
63+
def get_distribution(repo):
6864
"""Finds the distribution associated with this base_path."""
6965
distro_qs = RustDistribution.objects.select_related(
7066
"repository", "repository_version", "remote"
7167
)
7268
try:
73-
return distro_qs.get(base_path=path, pulp_domain=get_domain())
69+
return distro_qs.get(base_path=repo, pulp_domain=get_domain())
7470
except ObjectDoesNotExist:
7571
raise Http404(f"No RustDistribution found for base_path {path}")
7672

@@ -104,18 +100,29 @@ def initial(self, request, *args, **kwargs):
104100
"""Perform common initialization tasks for API endpoints."""
105101
super().initial(request, *args, **kwargs)
106102
domain_name = get_domain().name
103+
log.warning(self.kwargs)
104+
repo = self.kwargs["repo"]
107105
if settings.DOMAIN_ENABLED:
108-
self.base_content_url = urljoin(BASE_CONTENT_URL, f"{domain_name}/")
109-
self.base_api_url = urljoin(BASE_API_URL, f"{domain_name}/")
106+
self.base_content_url = urljoin(BASE_CONTENT_URL, f"pulp/cargo/{domain_name}/{repo}/")
107+
self.base_api_url = urljoin(BASE_API_URL, f"pulp/cargo/{domain_name}/{repo}/")
108+
self.base_download_url = urljoin(
109+
BASE_API_URL, f"pulp/cargo/{domain_name}/{repo}{CRATES_IO_API}"
110+
)
110111
else:
111-
self.base_content_url = BASE_CONTENT_URL
112-
self.base_api_url = BASE_API_URL
112+
self.base_content_url = urljoin(BASE_CONTENT_URL, f"pulp/cargo/{repo}/")
113+
self.base_api_url = urljoin(BASE_API_URL, f"pulp/cargo/{repo}/")
114+
self.base_download_url = urljoin(BASE_API_URL, f"pulp/cargo/{repo}{CRATES_IO_API}")
115+
116+
@classmethod
117+
def urlpattern(cls):
118+
"""Mocking NamedModelViewSet behavior to get Cargo APIs to support RBAC access polices."""
119+
return f"pulp/cargo/{cls.endpoint_name}"
113120

114121

115122
class CargoApiViewSet(ApiMixin, ViewSet):
116123
"""View for the Cargo JSON metadata endpoint."""
117124

118-
endpoint_name = "api/v1/crates/"
125+
endpoint_name = "api"
119126
DEFAULT_ACCESS_POLICY = {
120127
"statements": [
121128
{
@@ -131,7 +138,7 @@ class CargoApiViewSet(ApiMixin, ViewSet):
131138
responses={200: RustContentSerializer},
132139
summary="Get package metadata",
133140
)
134-
def retrieve(self, request, path, meta):
141+
def retrieve(self, request, path):
135142
"""
136143
Retrieve crate metadata for the sparse protocol.
137144
@@ -149,7 +156,7 @@ def retrieve(self, request, path, meta):
149156
return HttpResponseNotFound("No content available")
150157

151158
# Extract crate name from the path
152-
meta_path = PurePath(meta)
159+
meta_path = PurePath(path)
153160
crate_name = meta_path.name.lower()
154161

155162
# Query for all versions of this crate
@@ -222,8 +229,71 @@ def retrieve(self, request, path):
222229
"""Gets index route."""
223230
return Response(
224231
data={
225-
"dl": CRATES_IO_API_URL,
226-
"api": BASE_API_URL,
232+
"dl": self.base_download_url,
233+
"api": self.base_api_url,
227234
"auth-required": False,
228235
}
229236
)
237+
238+
239+
class CargoDownloadApiView(APIView):
240+
"""
241+
ViewSet for interacting with maven deploy API
242+
"""
243+
244+
model = RustRepository
245+
queryset = RustRepository.objects.all()
246+
247+
lookup_field = "name"
248+
249+
# Authentication disabled for now
250+
authentication_classes = []
251+
permission_classes = []
252+
253+
def redirect_to_content_app(self, distribution, relative_path, request):
254+
scheme = request.META.get("HTTP_X_FORWARDED_PROTO", request.scheme)
255+
hostname = request.META.get("HTTP_X_FORWARDED_HOST", request.get_host())
256+
content_origin = f"{scheme}://{hostname}"
257+
return redirect(
258+
f"{content_origin}{settings.CONTENT_PATH_PREFIX}"
259+
f"{get_full_path(distribution.base_path)}/{relative_path}"
260+
)
261+
262+
def get_repository_and_distributions(self, name):
263+
repository = get_object_or_404(RustRepository, name=name, pulp_domain=get_domain())
264+
distribution = get_object_or_404(
265+
RustDistribution, repository=repository, pulp_domain=get_domain()
266+
)
267+
return repository, distribution
268+
269+
def get(self, request, name, version):
270+
"""
271+
Responds to GET requests about packages by reference
272+
"""
273+
repo, distro = self.get_repository_and_distributions(name)
274+
content = get_object_or_404(RustPackage, name=name, vers=version, pk__in=repo.latest_version().content)
275+
relative_path = content.contentartifact_set.get().relative_path
276+
return self.redirect_to_content_app(distro, relative_path, request)
277+
278+
279+
def has_task_completed(task):
280+
"""
281+
Verify whether an immediate task ran properly.
282+
283+
Returns:
284+
bool: True if the task ended successfully.
285+
286+
Raises:
287+
Exception: If an error occured during the task's runtime.
288+
Throttled: If the task did not run due to resource constraints.
289+
290+
"""
291+
if task.state == "completed":
292+
task.delete()
293+
return True
294+
elif task.state == "canceled":
295+
raise Throttled()
296+
else:
297+
error = task.error
298+
task.delete()
299+
raise Exception(str(error))

0 commit comments

Comments
 (0)