-
Notifications
You must be signed in to change notification settings - Fork 50
Readd manifest artifact backwards compatibility #2381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Readded backwards compatibility for manifests that haven't been migrated to new data field. Sync will try to update them to the new format and repair manifests that are missing their artifact, but it is recommended that you run the `pulpcore-manager container-handle-image-data` command to fix all manifests at once. The command has been updated to report broken manifests by repository. Broken manifests in pushed repositories need to be deleted and re-pushed. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ | |
| import hashlib | ||
|
|
||
| from aiofiles import tempfile | ||
| from asgiref.sync import sync_to_async | ||
| from django.conf import settings | ||
| from django.db.models import Q | ||
|
|
||
|
|
@@ -101,10 +102,23 @@ async def create_signature(manifest, reference, signing_service): | |
| """ | ||
| async with semaphore: | ||
| # download and write file for object storage | ||
| async with tempfile.NamedTemporaryFile(dir=".", mode="wb", delete=False) as tf: | ||
| await tf.write(manifest.data.encode("utf-8")) | ||
| await tf.flush() | ||
| manifest_path = tf.name | ||
| if not manifest.data: | ||
| # TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Fully migrating" at this point should include making the manifest.data field not null, so the migration actually fails if it's not yet done.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, and this feels very insufficient https://github.com/pulp/pulp_container/blob/main/pulp_container/app/migrations/0039_manifest_data.py |
||
| artifact = await manifest._artifacts.aget() | ||
| if settings.STORAGES["default"]["BACKEND"] != "pulpcore.app.models.storage.FileSystem": | ||
| async with tempfile.NamedTemporaryFile(dir=".", mode="wb", delete=False) as tf: | ||
| await tf.write(await sync_to_async(artifact.file.read)()) | ||
| await tf.flush() | ||
| artifact.file.close() | ||
| manifest_path = tf.name | ||
| else: | ||
| manifest_path = artifact.file.path | ||
| # END OF BACKWARD COMPATIBILITY | ||
| else: | ||
| async with tempfile.NamedTemporaryFile(dir=".", mode="wb", delete=False) as tf: | ||
| await tf.write(manifest.data.encode("utf-8")) | ||
| await tf.flush() | ||
| manifest_path = tf.name | ||
|
|
||
| async with tempfile.NamedTemporaryFile(dir=".", prefix="signature") as tf: | ||
| sig_path = tf.name | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ | |
| determine_media_type, | ||
| extract_data_from_signature, | ||
| filter_resources, | ||
| get_content_data, | ||
| urlpath_sanitize, | ||
| validate_manifest, | ||
| ) | ||
|
|
@@ -83,11 +84,28 @@ async def _check_for_existing_manifest(self, download_tag): | |
|
|
||
| digest = response.headers.get("docker-content-digest") | ||
|
|
||
| if manifest := await Manifest.objects.filter( | ||
| digest=digest, pulp_domain=get_domain() | ||
| ).afirst(): | ||
| raw_text_data = manifest.data | ||
| content_data = json.loads(raw_text_data) | ||
| if ( | ||
| manifest := await Manifest.objects.prefetch_related("contentartifact_set") | ||
| .filter(digest=digest, pulp_domain=get_domain()) | ||
| .afirst() | ||
| ): | ||
| if raw_text_data := manifest.data: | ||
| content_data = json.loads(raw_text_data) | ||
|
|
||
| # TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest | ||
| elif saved_artifact := await manifest._artifacts.afirst(): | ||
| content_data, raw_bytes_data = await sync_to_async(get_content_data)(saved_artifact) | ||
| raw_text_data = raw_bytes_data.decode("utf-8") | ||
| # if artifact is not available (due to reclaim space) we will download it again | ||
| else: | ||
| content_data, raw_text_data, response = await self._download_manifest_data( | ||
| response.url | ||
| ) | ||
| if manifest.data is None: | ||
| manifest.data = raw_text_data | ||
| await manifest.asave(update_fields=["data"]) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I consider this a low risk compare to the existing issues, but in theory since we are mutating content, it could potentially deadlock between two parallel syncs. Not a blocker, just a note.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well hopefully after running the command, this piece won't be executed often. Also since manifests are per image and each image gets its own repo, it's unlikely to have parallel syncs of the same two images going at the same time.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree |
||
| # END OF BACKWARD COMPATIBILITY | ||
|
|
||
| else: | ||
| content_data, raw_text_data, response = await self._download_manifest_data(response.url) | ||
|
|
||
|
|
@@ -364,7 +382,9 @@ async def get_paginated_tag_list(self, rel_link, repo_name): | |
| while True: | ||
| link = urljoin(self.remote.url, rel_link) | ||
| list_downloader = self.remote.get_downloader(url=link) | ||
| await list_downloader.run(extra_data={"repo_name": repo_name}) | ||
| # FIXME this can be rolledback after https://github.com/pulp/pulp_container/issues/1288 | ||
| # tags/list endpoint does not like any unnecessary headers to be sent | ||
| await list_downloader.run(extra_data={"repo_name": repo_name, "headers": {}}) | ||
| with open(list_downloader.path) as tags_raw: | ||
| tags_dict = json.loads(tags_raw.read()) | ||
| tag_list.extend(tags_dict["tags"]) | ||
|
|
@@ -507,12 +527,30 @@ async def create_listed_manifest(self, manifest_data): | |
| ) | ||
| manifest_url = urljoin(self.remote.url, relative_url) | ||
|
|
||
| if manifest := await Manifest.objects.filter( | ||
| digest=digest, pulp_domain=get_domain() | ||
| ).afirst(): | ||
| content_data = json.loads(manifest.data) | ||
|
|
||
| content_data, manifest = await self._download_and_instantiate_manifest(manifest_url, digest) | ||
| if ( | ||
| manifest := await Manifest.objects.prefetch_related("contentartifact_set") | ||
| .filter(digest=digest, pulp_domain=get_domain()) | ||
| .afirst() | ||
| ): | ||
| if manifest.data: | ||
| content_data = json.loads(manifest.data) | ||
| # TODO: BACKWARD COMPATIBILITY - remove after fully migrating to artifactless manifest | ||
| elif saved_artifact := await manifest._artifacts.afirst(): | ||
| content_data, raw_bytes_data = await sync_to_async(get_content_data)(saved_artifact) | ||
| manifest.data = raw_bytes_data.decode("utf-8") | ||
| await manifest.asave(update_fields=["data"]) | ||
| # if artifact is not available (due to reclaim space) we will download it again | ||
| else: | ||
| content_data, new_manifest = await self._download_and_instantiate_manifest( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So before, we were taking the returned And in the new version, we are taking an existing Right? This is subtle, not sure if I have it correct.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's right, we don't have the artifact so we need to download the data and update the manifest. |
||
| manifest_url, digest | ||
| ) | ||
| manifest.data = new_manifest.data | ||
| await manifest.asave(update_fields=["data"]) | ||
| # END OF BACKWARD COMPATIBILITY | ||
| else: | ||
| content_data, manifest = await self._download_and_instantiate_manifest( | ||
| manifest_url, digest | ||
| ) | ||
|
|
||
| # in oci-index spec, platform is an optional field | ||
| platform = manifest_data.get("platform", None) | ||
|
|
@@ -603,7 +641,9 @@ async def create_signatures(self, man_dc, signature_source): | |
| man_dc.content.digest, | ||
| ) | ||
| signatures_downloader = self.remote.get_downloader(url=signatures_url) | ||
| await signatures_downloader.run() | ||
| # FIXME this can be rolledback after https://github.com/pulp/pulp_container/issues/1288 | ||
| # signature extensions endpoint does not like any unnecessary headers to be sent | ||
| await signatures_downloader.run(extra_data={"headers": {}}) | ||
| with open(signatures_downloader.path) as signatures_fd: | ||
| api_extension_signatures = json.loads(signatures_fd.read()) | ||
| for signature in api_extension_signatures.get("signatures", []): | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.