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,