Skip to content

Commit 7b49f10

Browse files
committed
Readd manifest artifact backwards compatibility
Reverts ca2b2a5
1 parent a7b579c commit 7b49f10

5 files changed

Lines changed: 157 additions & 18 deletions

File tree

pulp_container/app/downloaders.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
from pulpcore.plugin.download import DownloaderFactory, HttpDownloader
1212

13+
from pulp_container.constants import V2_ACCEPT_HEADERS
14+
1315
log = getLogger(__name__)
1416

1517
HeadResult = namedtuple(
@@ -51,7 +53,11 @@ async def _run(self, handle_401=True, extra_data=None):
5153
handle_401(bool): If true, catch 401, request a new token and retry.
5254
5355
"""
54-
headers = {}
56+
# manifests are header sensitive, blobs do not care
57+
# these accept headers are going to be sent with every request to ensure downloader
58+
# can download manifests, namely in the repair core task
59+
# FIXME this can be rolledback after https://github.com/pulp/pulp_container/issues/1288
60+
headers = V2_ACCEPT_HEADERS
5561
repo_name = None
5662
if extra_data is not None:
5763
headers = extra_data.get("headers", headers)

pulp_container/app/redirects.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.conf import settings
44
from django.core.exceptions import ObjectDoesNotExist
5+
from django.http import Http404
56
from django.shortcuts import redirect
67

78
from pulp_container.app.exceptions import ManifestNotFound
@@ -101,6 +102,47 @@ def redirect_to_object_storage(self, artifact, return_media_type):
101102
)
102103
return redirect(content_url)
103104

105+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifests
106+
def redirect_to_artifact(self, content_name, manifest, manifest_media_type):
107+
"""
108+
Search for the passed manifest's artifact and issue a redirect.
109+
"""
110+
try:
111+
artifact = manifest._artifacts.get()
112+
except ObjectDoesNotExist:
113+
raise Http404(f"An artifact for '{content_name}' was not found")
114+
115+
return self.redirect_to_object_storage(artifact, manifest_media_type)
116+
117+
def issue_tag_redirect(self, tag):
118+
"""
119+
Issue a redirect if an accepted media type requires it or return not found if manifest
120+
version is not supported.
121+
"""
122+
if tag.tagged_manifest.data:
123+
return super().issue_tag_redirect(tag)
124+
125+
manifest_media_type = tag.tagged_manifest.media_type
126+
if manifest_media_type == MEDIA_TYPE.MANIFEST_V1:
127+
return self.redirect_to_artifact(
128+
tag.name, tag.tagged_manifest, MEDIA_TYPE.MANIFEST_V1_SIGNED
129+
)
130+
elif manifest_media_type in get_accepted_media_types(self.request.headers):
131+
return self.redirect_to_artifact(tag.name, tag.tagged_manifest, manifest_media_type)
132+
else:
133+
raise ManifestNotFound(reference=tag.name)
134+
135+
def issue_manifest_redirect(self, manifest):
136+
"""
137+
Directly redirect to an associated manifest's artifact.
138+
"""
139+
if manifest.data:
140+
return super().issue_manifest_redirect(manifest)
141+
142+
return self.redirect_to_artifact(manifest.digest, manifest, manifest.media_type)
143+
144+
# END OF BACKWARD COMPATIBILITY
145+
104146

105147
class AzureStorageRedirects(S3StorageRedirects):
106148
"""

pulp_container/app/registry.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ async def get_tag(self, request):
197197
"Content-Type": return_media_type,
198198
"Docker-Content-Digest": tag.tagged_manifest.digest,
199199
}
200+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest
201+
if not tag.tagged_manifest.data:
202+
return await self.dispatch_tag(request, tag, response_headers)
203+
# END OF BACKWARD COMPATIBILITY
200204
return web.Response(body=tag.tagged_manifest.data, headers=response_headers)
201205

202206
# return what was found in case media_type is accepted header (docker, oci)
@@ -206,11 +210,41 @@ async def get_tag(self, request):
206210
"Content-Type": return_media_type,
207211
"Docker-Content-Digest": tag.tagged_manifest.digest,
208212
}
213+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest
214+
if not tag.tagged_manifest.data:
215+
return await self.dispatch_tag(request, tag, response_headers)
216+
# END OF BACKWARD COMPATIBILITY
209217
return web.Response(body=tag.tagged_manifest.data, headers=response_headers)
210218

211219
# return 404 in case the client is requesting docker manifest v2 schema 1
212220
raise PathNotResolved(tag_name)
213221

222+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest
223+
async def dispatch_tag(self, request, tag, response_headers):
224+
"""
225+
Finds an artifact associated with a Tag and sends it to the client, otherwise tries
226+
to stream it.
227+
228+
Args:
229+
request(:class:`~aiohttp.web.Request`): The request to prepare a response for.
230+
tag: Tag
231+
response_headers (dict): dictionary that contains the 'Content-Type' header to send
232+
with the response
233+
234+
Returns:
235+
:class:`aiohttp.web.StreamResponse` or :class:`aiohttp.web.FileResponse`: The response
236+
streamed back to the client.
237+
238+
"""
239+
try:
240+
artifact = await tag.tagged_manifest._artifacts.aget()
241+
except ObjectDoesNotExist:
242+
ca = await sync_to_async(lambda x: x[0])(tag.tagged_manifest.contentartifact_set.all())
243+
return await self._stream_content_artifact(request, web.StreamResponse(), ca)
244+
else:
245+
return await Registry._dispatch(artifact, response_headers)
246+
# END OF BACKWARD COMPATIBILITY
247+
214248
@RegistryContentCache(
215249
base_key=lambda req, cac: Registry.find_base_path_cached(req, cac),
216250
auth=lambda req, cac, bk: Registry.auth_cached(req, cac, bk),
@@ -247,6 +281,16 @@ async def get_by_digest(self, request):
247281
"Content-Type": manifest.media_type,
248282
"Docker-Content-Digest": manifest.digest,
249283
}
284+
# TODO: BACKWARD COMPATIBILITY - remove after migrating to artifactless manifest
285+
if not manifest.data:
286+
if saved_artifact := await manifest._artifacts.afirst():
287+
return await Registry._dispatch(saved_artifact, headers)
288+
else:
289+
ca = await sync_to_async(lambda x: x[0])(manifest.contentartifact_set.all())
290+
return await self._stream_content_artifact(
291+
request, web.StreamResponse(), ca
292+
)
293+
# END OF BACKWARD COMPATIBILITY
250294
return web.Response(body=manifest.data, headers=headers)
251295
elif content_type == "blobs":
252296
ca = await ContentArtifact.objects.select_related("artifact", "content").aget(

pulp_container/app/tasks/sign.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import hashlib
44

55
from aiofiles import tempfile
6+
from asgiref.sync import sync_to_async
67
from django.conf import settings
78
from django.db.models import Q
89

@@ -101,10 +102,23 @@ async def create_signature(manifest, reference, signing_service):
101102
"""
102103
async with semaphore:
103104
# download and write file for object storage
104-
async with tempfile.NamedTemporaryFile(dir=".", mode="wb", delete=False) as tf:
105-
await tf.write(manifest.data.encode("utf-8"))
106-
await tf.flush()
107-
manifest_path = tf.name
105+
if not manifest.data:
106+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest
107+
artifact = await manifest._artifacts.aget()
108+
if settings.STORAGES["default"]["BACKEND"] != "pulpcore.app.models.storage.FileSystem":
109+
async with tempfile.NamedTemporaryFile(dir=".", mode="wb", delete=False) as tf:
110+
await tf.write(await sync_to_async(artifact.file.read)())
111+
await tf.flush()
112+
artifact.file.close()
113+
manifest_path = tf.name
114+
else:
115+
manifest_path = artifact.file.path
116+
# END OF BACKWARD COMPATIBILITY
117+
else:
118+
async with tempfile.NamedTemporaryFile(dir=".", mode="wb", delete=False) as tf:
119+
await tf.write(manifest.data.encode("utf-8"))
120+
await tf.flush()
121+
manifest_path = tf.name
108122

109123
async with tempfile.NamedTemporaryFile(dir=".", prefix="signature") as tf:
110124
sig_path = tf.name

pulp_container/app/tasks/sync_stages.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
determine_media_type,
2626
extract_data_from_signature,
2727
filter_resources,
28+
get_content_data,
2829
urlpath_sanitize,
2930
validate_manifest,
3031
)
@@ -83,11 +84,25 @@ async def _check_for_existing_manifest(self, download_tag):
8384

8485
digest = response.headers.get("docker-content-digest")
8586

86-
if manifest := await Manifest.objects.filter(
87-
digest=digest, pulp_domain=get_domain()
88-
).afirst():
89-
raw_text_data = manifest.data
90-
content_data = json.loads(raw_text_data)
87+
if (
88+
manifest := await Manifest.objects.prefetch_related("contentartifact_set")
89+
.filter(digest=digest, pulp_domain=get_domain())
90+
.afirst()
91+
):
92+
if raw_text_data := manifest.data:
93+
content_data = json.loads(raw_text_data)
94+
95+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest
96+
elif saved_artifact := await manifest._artifacts.afirst():
97+
content_data, raw_bytes_data = await sync_to_async(get_content_data)(saved_artifact)
98+
raw_text_data = raw_bytes_data.decode("utf-8")
99+
# if artifact is not available (due to reclaim space) we will download it again
100+
else:
101+
content_data, raw_text_data, response = await self._download_manifest_data(
102+
response.url
103+
)
104+
# END OF BACKWARD COMPATIBILITY
105+
91106
else:
92107
content_data, raw_text_data, response = await self._download_manifest_data(response.url)
93108

@@ -364,7 +379,9 @@ async def get_paginated_tag_list(self, rel_link, repo_name):
364379
while True:
365380
link = urljoin(self.remote.url, rel_link)
366381
list_downloader = self.remote.get_downloader(url=link)
367-
await list_downloader.run(extra_data={"repo_name": repo_name})
382+
# FIXME this can be rolledback after https://github.com/pulp/pulp_container/issues/1288
383+
# tags/list endpoint does not like any unnecessary headers to be sent
384+
await list_downloader.run(extra_data={"repo_name": repo_name, "headers": {}})
368385
with open(list_downloader.path) as tags_raw:
369386
tags_dict = json.loads(tags_raw.read())
370387
tag_list.extend(tags_dict["tags"])
@@ -507,12 +524,26 @@ async def create_listed_manifest(self, manifest_data):
507524
)
508525
manifest_url = urljoin(self.remote.url, relative_url)
509526

510-
if manifest := await Manifest.objects.filter(
511-
digest=digest, pulp_domain=get_domain()
512-
).afirst():
513-
content_data = json.loads(manifest.data)
514-
515-
content_data, manifest = await self._download_and_instantiate_manifest(manifest_url, digest)
527+
if (
528+
manifest := await Manifest.objects.prefetch_related("contentartifact_set")
529+
.filter(digest=digest, pulp_domain=get_domain())
530+
.afirst()
531+
):
532+
if manifest.data:
533+
content_data = json.loads(manifest.data)
534+
# TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest
535+
elif saved_artifact := await manifest._artifacts.afirst():
536+
content_data, _ = await sync_to_async(get_content_data)(saved_artifact)
537+
# if artifact is not available (due to reclaim space) we will download it again
538+
else:
539+
content_data, manifest = await self._download_and_instantiate_manifest(
540+
manifest_url, digest
541+
)
542+
# END OF BACKWARD COMPATIBILITY
543+
else:
544+
content_data, manifest = await self._download_and_instantiate_manifest(
545+
manifest_url, digest
546+
)
516547

517548
# in oci-index spec, platform is an optional field
518549
platform = manifest_data.get("platform", None)
@@ -603,7 +634,9 @@ async def create_signatures(self, man_dc, signature_source):
603634
man_dc.content.digest,
604635
)
605636
signatures_downloader = self.remote.get_downloader(url=signatures_url)
606-
await signatures_downloader.run()
637+
# FIXME this can be rolledback after https://github.com/pulp/pulp_container/issues/1288
638+
# signature extensions endpoint does not like any unnecessary headers to be sent
639+
await signatures_downloader.run(extra_data={"headers": {}})
607640
with open(signatures_downloader.path) as signatures_fd:
608641
api_extension_signatures = json.loads(signatures_fd.read())
609642
for signature in api_extension_signatures.get("signatures", []):

0 commit comments

Comments
 (0)