11import json
22import logging
33
4+ from rest_framework .views import APIView
45from rest_framework .viewsets import ViewSet
56from rest_framework .renderers import BrowsableAPIRenderer , JSONRenderer , TemplateHTMLRenderer
67from rest_framework .response import Response
78from rest_framework .decorators import api_view
8- from rest_framework .exceptions import NotAcceptable
9+ from rest_framework .exceptions import NotAcceptable , Throttled
910from django .core .exceptions import ObjectDoesNotExist
1011from django .shortcuts import redirect
1112from datetime import datetime , timezone , timedelta
3233from pulpcore .plugin .tasking import dispatch
3334from 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
3637from pulp_rust .app .serializers import (
3738 IndexRootSerializer ,
3839 RustContentSerializer ,
3940)
40-
4141from pulp_rust .app import tasks
4242
4343log = logging .getLogger (__name__ )
4444
4545ORIGIN_HOST = settings .CONTENT_ORIGIN if settings .CONTENT_ORIGIN else settings .PYPI_API_HOSTNAME
4646BASE_CONTENT_URL = urljoin (ORIGIN_HOST , settings .CONTENT_PATH_PREFIX )
4747BASE_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
5151class 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
115122class 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