From 570d97406748b9d15b3cf4e6b9669471c0b61967 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 15 Jan 2019 16:00:06 -0500 Subject: [PATCH] Add support for retargeting a tag to all schema 1 manifests Schema version 1 manifests contain the tag name, and we have a check to ensure we don't point a tag at a manifest with the wrong name embedded. However, this also means that we cannot retarget to that manifest, which will break the UI once we get rid of legacy images. This change means we can retarget to those manifests, and the OCI model does the work of rewriting the manifest when necessary. --- data/registry_model/registry_oci_model.py | 23 +++++++++++++++++++++ data/registry_model/test/test_interface.py | 24 ++++++++++++++++++++++ image/docker/schema1.py | 9 ++++++++ 3 files changed, 56 insertions(+) diff --git a/data/registry_model/registry_oci_model.py b/data/registry_model/registry_oci_model.py index bbdabadc8..c1f2cffd5 100644 --- a/data/registry_model/registry_oci_model.py +++ b/data/registry_model/registry_oci_model.py @@ -14,6 +14,7 @@ from data.registry_model.datatypes import (Tag, Manifest, LegacyImage, Label, Se from data.registry_model.shared import SharedModel from data.registry_model.label_handlers import apply_label_to_manifest from image.docker import ManifestException +from image.docker.schema1 import DOCKER_SCHEMA1_CONTENT_TYPES from image.docker.schema2 import DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE @@ -313,6 +314,28 @@ class OCIModel(SharedModel, RegistryDataInterface): return None manifest_id = created.manifest.id + else: + # If the manifest is a schema 1 manifest and its tag name does not match that + # specified, then we need to create a new manifest, but with that tag name. + if manifest_or_legacy_image.media_type in DOCKER_SCHEMA1_CONTENT_TYPES: + try: + parsed = manifest_or_legacy_image.get_parsed_manifest() + except ManifestException: + logger.exception('Could not parse manifest `%s` in retarget_tag', + manifest_or_legacy_image._db_id) + return None + + if parsed.tag != tag_name: + logger.debug('Rewriting manifest `%s` for tag named `%s`', + manifest_or_legacy_image._db_id, tag_name) + + repository_id = repository_ref._db_id + updated = parsed.with_tag_name(tag_name) + created = oci.manifest.get_or_create_manifest(repository_id, updated, storage) + if created is None: + return None + + manifest_id = created.manifest.id tag = oci.tag.retarget_tag(tag_name, manifest_id, is_reversion=is_reversion) legacy_image = LegacyImage.for_image(oci.shared.get_legacy_image_for_manifest(manifest_id)) diff --git a/data/registry_model/test/test_interface.py b/data/registry_model/test/test_interface.py index e8c6054fb..413081b97 100644 --- a/data/registry_model/test/test_interface.py +++ b/data/registry_model/test/test_interface.py @@ -342,6 +342,30 @@ def test_retarget_tag_history(use_manifest, registry_model): assert len(new_history) == len(history) + 1 +def test_retarget_tag_schema1(oci_model): + repository_ref = oci_model.lookup_repository('devtable', 'simple') + latest_tag = oci_model.get_repo_tag(repository_ref, 'latest') + manifest = oci_model.get_manifest_for_tag(latest_tag) + + existing_parsed = manifest.get_parsed_manifest() + + # Retarget a new tag to the manifest. + updated_tag = oci_model.retarget_tag(repository_ref, 'somenewtag', manifest, storage) + assert updated_tag + assert updated_tag.name == 'somenewtag' + + updated_manifest = oci_model.get_manifest_for_tag(updated_tag) + parsed = updated_manifest.get_parsed_manifest() + assert parsed.namespace == 'devtable' + assert parsed.repo_name == 'simple' + assert parsed.tag == 'somenewtag' + + assert parsed.layers == existing_parsed.layers + + # Ensure the tag has changed targets. + assert oci_model.get_repo_tag(repository_ref, 'somenewtag') == updated_tag + + def test_change_repository_tag_expiration(registry_model): repository_ref = registry_model.lookup_repository('devtable', 'simple') tag = registry_model.get_repo_tag(repository_ref, 'latest') diff --git a/image/docker/schema1.py b/image/docker/schema1.py index 846b9e3aa..84b9f63a1 100644 --- a/image/docker/schema1.py +++ b/image/docker/schema1.py @@ -356,6 +356,15 @@ class DockerSchema1Manifest(ManifestInterface): return builder.build() + def with_tag_name(self, tag_name, json_web_key=None): + """ Returns a copy of this manifest, with the tag changed to the given tag name. """ + builder = DockerSchema1ManifestBuilder(self._namespace, self._repo_name, tag_name, + self._architecture) + for layer in reversed(self.layers): + builder.add_layer(str(layer.digest), layer.raw_v1_metadata) + + return builder.build(json_web_key) + def _generate_layers(self): """ Returns a generator of objects that have the blobSum and v1Compatibility keys in them,