Skip to content
This repository was archived by the owner on Dec 7, 2022. It is now read-only.

Commit 99fd573

Browse files
committed
Problem: registry code duplicates code from pulpcore
Solution: use the Handler from pulpcore-plugin [noissue]
1 parent 8dd36da commit 99fd573

2 files changed

Lines changed: 14 additions & 198 deletions

File tree

pulp_docker/app/downloaders.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ async def _run(self, handle_401=True, extra_data=None):
4747
handle_401(bool): If true, catch 401, request a new token and retry.
4848
"""
4949
headers = {}
50+
repo_name = None
5051
if extra_data is not None:
5152
headers = extra_data.get('headers', headers)
5253
repo_name = extra_data.get('repo_name', None)

pulp_docker/app/registry.py

Lines changed: 13 additions & 198 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,19 @@
11
import logging
22
import os
33

4-
from aiohttp import web, web_exceptions
5-
from aiohttp.client_exceptions import ClientResponseError
4+
from aiohttp import web
65
from django.conf import settings
76
from django.core.exceptions import ObjectDoesNotExist
8-
from django.db import IntegrityError, transaction
9-
from gettext import gettext as _
107
from 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
1311
from pulp_docker.app.models import DockerDistribution, ManifestTag, ManifestListTag, MEDIA_TYPE
1412

1513

1614
log = 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-
4317
class 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

Comments
 (0)