diff --git a/data/registry_model/interface.py b/data/registry_model/interface.py index 766ee0fff..917f6763c 100644 --- a/data/registry_model/interface.py +++ b/data/registry_model/interface.py @@ -12,6 +12,12 @@ class RegistryDataInterface(object): """ Returns whether the implementation of the data interface supports schema 2 format manifests. """ + @abstractmethod + def get_legacy_tags_map(self, repository_ref, storage): + """ Returns a map from tag name to its legacy image ID, for all tags with legacy images in + the repository. Note that this can be a *very* heavy operation. + """ + @abstractmethod def find_matching_tag(self, repository_ref, tag_names): """ Finds an alive tag in the repository matching one of the given tag names and returns it diff --git a/data/registry_model/registry_oci_model.py b/data/registry_model/registry_oci_model.py index 1a3d54f93..097d1ca11 100644 --- a/data/registry_model/registry_oci_model.py +++ b/data/registry_model/registry_oci_model.py @@ -13,6 +13,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.schema2 import DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE logger = logging.getLogger(__name__) @@ -28,6 +29,31 @@ class OCIModel(SharedModel, RegistryDataInterface): manifests. """ return True + def get_legacy_tags_map(self, repository_ref, storage): + """ Returns a map from tag name to its legacy image ID, for all tags with legacy images in + the repository. Note that this can be a *very* heavy operation. + """ + tags = oci.tag.list_alive_tags(repository_ref._db_id) + legacy_images_map = oci.tag.get_legacy_images_for_tags(tags) + + tags_map = {} + for tag in tags: + legacy_image = legacy_images_map.get(tag.id) + if legacy_image is not None: + tags_map[tag.name] = legacy_image.docker_image_id + else: + manifest = Manifest.for_manifest(tag.manifest, None) + if legacy_image is None and manifest.media_type == DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE: + # See if we can lookup a schema1 legacy image. + v1_compatible = self.get_schema1_parsed_manifest(manifest, '', '', '', storage) + if v1_compatible is not None: + v1_id = v1_compatible.leaf_layer_v1_image_id + if v1_id is not None: + tags_map[tag.name] = v1_id + + + return tags_map + def find_matching_tag(self, repository_ref, tag_names): """ Finds an alive tag in the repository matching one of the given tag names and returns it or None if none. diff --git a/data/registry_model/registry_pre_oci_model.py b/data/registry_model/registry_pre_oci_model.py index f22eb3a29..a32b37868 100644 --- a/data/registry_model/registry_pre_oci_model.py +++ b/data/registry_model/registry_pre_oci_model.py @@ -32,6 +32,13 @@ class PreOCIModel(SharedModel, RegistryDataInterface): manifests. """ return False + def get_legacy_tags_map(self, repository_ref, storage): + """ Returns a map from tag name to its legacy image, for all tags with legacy images in + the repository. + """ + tags = self.list_repository_tags(repository_ref, include_legacy_images=True) + return {tag.name: tag.legacy_image.docker_image_id for tag in tags} + def find_matching_tag(self, repository_ref, tag_names): """ Finds an alive tag in the repository matching one of the given tag names and returns it or None if none. diff --git a/data/registry_model/test/test_interface.py b/data/registry_model/test/test_interface.py index f6034a448..b171f30b6 100644 --- a/data/registry_model/test/test_interface.py +++ b/data/registry_model/test/test_interface.py @@ -231,6 +231,8 @@ def test_repository_tags(repo_namespace, repo_name, registry_model): tags = registry_model.list_repository_tags(repository_ref, include_legacy_images=True) assert len(tags) + tags_map = registry_model.get_legacy_tags_map(repository_ref, storage) + for tag in tags: found_tag = registry_model.get_repo_tag(repository_ref, tag.name, include_legacy_image=True) assert found_tag == tag @@ -241,6 +243,8 @@ def test_repository_tags(repo_namespace, repo_name, registry_model): found_image = registry_model.get_legacy_image(repository_ref, found_tag.legacy_image.docker_image_id) assert found_image == found_tag.legacy_image + assert tag.name in tags_map + assert tags_map[tag.name] == found_image.docker_image_id def test_repository_tag_history(registry_model): diff --git a/endpoints/v1/tag.py b/endpoints/v1/tag.py index 7b23309df..e098235c8 100644 --- a/endpoints/v1/tag.py +++ b/endpoints/v1/tag.py @@ -27,11 +27,7 @@ def get_tags(namespace_name, repo_name): if repository_ref is None: abort(404) - # TODO(jschorr): Change this to normalize manifest lists back to their legacy image - # (if applicable). - tags = registry_model.list_repository_tags(repository_ref, include_legacy_images=True) - tag_map = {tag.name: tag.legacy_image.docker_image_id - for tag in tags if tag.legacy_image_if_present} + tag_map = registry_model.get_legacy_tags_map(repository_ref, storage) return jsonify(tag_map) abort(403) diff --git a/test/registry/registry_tests.py b/test/registry/registry_tests.py index 41205173e..393d3d308 100644 --- a/test/registry/registry_tests.py +++ b/test/registry/registry_tests.py @@ -1368,7 +1368,7 @@ def test_push_tag_existing_image(v1_protocol, puller, basic_images, liveserver_s True, False ]) -def test_push_pull_manifest_list_back_compat(v22_protocol, v2_protocol, basic_images, +def test_push_pull_manifest_list_back_compat(v22_protocol, legacy_puller, basic_images, different_images, liveserver_session, app_reloader, schema_version, data_model, is_amd): """ Test: Push a new tag with a manifest list containing two manifests, one (possibly) legacy @@ -1405,9 +1405,9 @@ def test_push_pull_manifest_list_back_compat(v22_protocol, v2_protocol, basic_im # Pull the tag and ensure we (don't) get back the basic images, since they are(n't) part of the # amd64+linux manifest. - v2_protocol.pull(liveserver_session, 'devtable', 'newrepo', 'latest', basic_images, - credentials=credentials, - expected_failure=Failures.UNKNOWN_TAG if not is_amd else None) + legacy_puller.pull(liveserver_session, 'devtable', 'newrepo', 'latest', basic_images, + credentials=credentials, + expected_failure=Failures.UNKNOWN_TAG if not is_amd else None) @pytest.mark.parametrize('schema_version', [