11import logging
22import os
33
4- from aiohttp import web , web_exceptions
5- from aiohttp .client_exceptions import ClientResponseError
4+ from aiohttp import web
65from django .conf import settings
76from django .core .exceptions import ObjectDoesNotExist
8- from django .db import IntegrityError , transaction
9- from gettext import gettext as _
107from multidict import MultiDict
118
12- from pulpcore .plugin .models import Artifact , ContentArtifact , Remote
9+ from pulpcore .plugin .content import Handler , PathNotResolved
10+ from pulpcore .plugin .models import ContentArtifact
1311from pulp_docker .app .models import DockerDistribution , ManifestTag , ManifestListTag , MEDIA_TYPE
1412
1513
1614log = logging .getLogger (__name__ )
1715
1816
19- HOP_BY_HOP_HEADERS = [
20- 'connection' ,
21- 'keep-alive' ,
22- 'public' ,
23- 'proxy-authenticate' ,
24- 'transfer-encoding' ,
25- 'upgrade' ,
26- ]
27-
28-
29- class PathNotResolved (web_exceptions .HTTPNotFound ):
30- """
31- The path could not be resolved to a published file.
32-
33- This could be caused by either the distribution, the publication,
34- or the published file could not be found.
35- """
36-
37- def __init__ (self , path , * args , ** kwargs ):
38- """Initialize the Exception."""
39- self .path = path
40- super ().__init__ (* args , ** kwargs )
41-
42-
4317class ArtifactNotFound (Exception ):
4418 """
4519 The artifact associated with a published-artifact does not exist.
@@ -48,16 +22,12 @@ class ArtifactNotFound(Exception):
4822 pass
4923
5024
51- class Registry :
25+ class Registry ( Handler ) :
5226 """
5327 A set of handlers for the Docker v2 API.
5428 """
5529
56- def __init__ (self ):
57- """
58- Initializes the Registry class.
59- """
60- self .distribution_model = DockerDistribution
30+ distribution_model = DockerDistribution
6131
6232 @staticmethod
6333 async def get_accepted_media_types (request ):
@@ -77,25 +47,19 @@ async def get_accepted_media_types(request):
7747 accepted_media_types .append (value .decode ('UTF-8' ))
7848 return accepted_media_types
7949
80- async def match_distribution (self , path ):
50+ @staticmethod
51+ def _base_paths (path ):
8152 """
82- Match a distribution using a base path .
53+ Get a list of base paths used to match a distribution .
8354
8455 Args:
8556 path (str): The path component of the URL.
8657
8758 Returns:
88- DockerDistribution: The matched docker distribution.
89-
90- Raises:
91- PathNotResolved: when not matched.
59+ list: Of base paths.
9260
9361 """
94- try :
95- return self .distribution_model .objects .get (base_path = path )
96- except ObjectDoesNotExist :
97- log .debug (_ ('DockerDistribution not matched for {path}.' ).format (path = path ))
98- raise PathNotResolved (path )
62+ return [path ]
9963
10064 @staticmethod
10165 async def _dispatch (path , headers ):
@@ -136,7 +100,7 @@ async def tags_list(self, request):
136100 Handler for Docker Registry v2 tags/list API.
137101 """
138102 path = request .match_info ['path' ]
139- distribution = await self .match_distribution (path )
103+ distribution = self ._match_distribution (path )
140104 tags = {'name' : path , 'tags' : set ()}
141105 repository_version = distribution .get_repository_version ()
142106 for c in repository_version .content :
@@ -164,7 +128,7 @@ async def get_tag(self, request):
164128 """
165129 path = request .match_info ['path' ]
166130 tag_name = request .match_info ['tag_name' ]
167- distribution = await self .match_distribution (path )
131+ distribution = self ._match_distribution (path )
168132 repository_version = distribution .get_repository_version ()
169133 accepted_media_types = await Registry .get_accepted_media_types (request )
170134 if MEDIA_TYPE .MANIFEST_LIST in accepted_media_types :
@@ -236,7 +200,7 @@ async def get_by_digest(self, request):
236200 """
237201 path = request .match_info ['path' ]
238202 digest = "sha256:{digest}" .format (digest = request .match_info ['digest' ])
239- distribution = await self .match_distribution (path )
203+ distribution = self ._match_distribution (path )
240204 repository_version = distribution .get_repository_version ()
241205 log .info (digest )
242206 try :
@@ -253,152 +217,3 @@ async def get_by_digest(self, request):
253217 headers )
254218 else :
255219 return await self ._stream_content_artifact (request , web .StreamResponse (), ca )
256-
257- async def _stream_content_artifact (self , request , response , content_artifact ):
258- """
259- Stream and optionally save a ContentArtifact by requesting it using the associated remote.
260-
261- If a fatal download failure occurs while downloading and there are additional
262- :class:`~pulpcore.plugin.models.RemoteArtifact` objects associated with the
263- :class:`~pulpcore.plugin.models.ContentArtifact` they will also be tried. If all
264- :class:`~pulpcore.plugin.models.RemoteArtifact` downloads raise exceptions, an HTTP 502
265- error is returned to the client.
266-
267- Args:
268- request(:class:`~aiohttp.web.Request`): The request to prepare a response for.
269- response (:class:`~aiohttp.web.StreamResponse`): The response to stream data to.
270- content_artifact (:class:`~pulpcore.plugin.models.ContentArtifact`): The ContentArtifact
271- to fetch and then stream back to the client
272-
273- Raises:
274- :class:`~aiohttp.web.HTTPNotFound` when no
275- :class:`~pulpcore.plugin.models.RemoteArtifact` objects associated with the
276- :class:`~pulpcore.plugin.models.ContentArtifact` returned the binary data needed for
277- the client.
278-
279- """
280- for remote_artifact in content_artifact .remoteartifact_set .all ():
281- try :
282- response = await self ._stream_remote_artifact (request , response , remote_artifact )
283-
284- except ClientResponseError :
285- continue
286-
287- raise web_exceptions .HTTPNotFound ()
288-
289- async def _stream_remote_artifact (self , request , response , remote_artifact ):
290- """
291- Stream and save a RemoteArtifact.
292-
293- Args:
294- request(:class:`~aiohttp.web.Request`): The request to prepare a response for.
295- response (:class:`~aiohttp.web.StreamResponse`): The response to stream data to.
296- content_artifact (:class:`~pulpcore.plugin.models.ContentArtifact`): The ContentArtifact
297- to fetch and then stream back to the client
298-
299- Raises:
300- :class:`~aiohttp.web.HTTPNotFound` when no
301- :class:`~pulpcore.plugin.models.RemoteArtifact` objects associated with the
302- :class:`~pulpcore.plugin.models.ContentArtifact` returned the binary data needed for
303- the client.
304-
305- """
306- remote = remote_artifact .remote .cast ()
307-
308- async def handle_headers (headers ):
309- for name , value in headers .items ():
310- if name .lower () in HOP_BY_HOP_HEADERS :
311- continue
312- response .headers [name ] = value
313- await response .prepare (request )
314-
315- async def handle_data (data ):
316- await response .write (data )
317- if remote .policy != Remote .STREAMED :
318- await original_handle_data (data )
319-
320- async def finalize ():
321- if remote .policy != Remote .STREAMED :
322- await original_finalize ()
323-
324- repo_name = remote .namespaced_upstream_name
325- downloader = remote .get_downloader (remote_artifact = remote_artifact ,
326- headers_ready_callback = handle_headers )
327- original_handle_data = downloader .handle_data
328- downloader .handle_data = handle_data
329- original_finalize = downloader .finalize
330- downloader .finalize = finalize
331- download_result = await downloader .run (extra_data = {'repo_name' : repo_name })
332-
333- if remote .policy != Remote .STREAMED :
334- self ._save_artifact (download_result , remote_artifact )
335- await response .write_eof ()
336- return response
337-
338- def _save_artifact (self , download_result , remote_artifact ):
339- """
340- Create/Get an Artifact and associate it to a RemoteArtifact and/or ContentArtifact.
341-
342- Create (or get if already existing) an :class:`~pulpcore.plugin.models.Artifact`
343- based on the `download_result` and associate it to the `content_artifact` of the given
344- `remote_artifact`. Both the created artifact and the updated content_artifact are saved to
345- the DB. The `remote_artifact` is also saved for the pull-through caching use case.
346-
347- Plugin-writers may overide this method if their content module requires
348- additional/different steps for saving.
349-
350- Args:
351- download_result (:class:`~pulpcore.plugin.download.DownloadResult`: The
352- DownloadResult for the downloaded artifact.
353-
354- remote_artifact (:class:`~pulpcore.plugin.models.RemoteArtifact`): The
355- RemoteArtifact to associate the Artifact with.
356-
357- Returns:
358- The associated :class:`~pulpcore.plugin.models.Artifact`.
359-
360- """
361- content_artifact = remote_artifact .content_artifact
362- remote = remote_artifact .remote
363- artifact = Artifact (
364- ** download_result .artifact_attributes ,
365- file = download_result .path
366- )
367- with transaction .atomic ():
368- try :
369- with transaction .atomic ():
370- artifact .save ()
371- except IntegrityError :
372- artifact = Artifact .objects .get (artifact .q ())
373- update_content_artifact = True
374- if content_artifact ._state .adding :
375- # This is the first time pull-through content was requested.
376- rel_path = content_artifact .relative_path
377- c_type = remote .get_remote_artifact_content_type (rel_path )
378- content = c_type .init_from_artifact_and_relative_path (artifact , rel_path )
379- try :
380- with transaction .atomic ():
381- content .save ()
382- content_artifact .content = content
383- content_artifact .save ()
384- except IntegrityError :
385- # There is already content for this Artifact
386- content = c_type .objects .get (content .q ())
387- artifacts = content ._artifacts
388- if artifact .sha256 != artifacts [0 ].sha256 :
389- raise RuntimeError ("The Artifact downloaded during pull-through does not "
390- "match the Artifact already stored for the same "
391- "content." )
392- content_artifact = ContentArtifact .objects .get (content = content )
393- update_content_artifact = False
394- try :
395- with transaction .atomic ():
396- remote_artifact .content_artifact = content_artifact
397- remote_artifact .save ()
398- except IntegrityError :
399- # Remote artifact must have already gotten saved during a parallel request
400- log .info ("RemoteArtifact already exists." )
401- if update_content_artifact :
402- content_artifact .artifact = artifact
403- content_artifact .save ()
404- return artifact
0 commit comments