Fix support for pulling manifest lists via Docker V1 protocol where applicable

This commit is contained in:
Joseph Schorr 2018-11-14 14:05:06 +02:00
parent 37b20010aa
commit 276d0d571d
6 changed files with 48 additions and 9 deletions

View file

@ -12,6 +12,12 @@ class RegistryDataInterface(object):
""" Returns whether the implementation of the data interface supports schema 2 format """ Returns whether the implementation of the data interface supports schema 2 format
manifests. """ 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 @abstractmethod
def find_matching_tag(self, repository_ref, tag_names): 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 """ Finds an alive tag in the repository matching one of the given tag names and returns it

View file

@ -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.shared import SharedModel
from data.registry_model.label_handlers import apply_label_to_manifest from data.registry_model.label_handlers import apply_label_to_manifest
from image.docker import ManifestException from image.docker import ManifestException
from image.docker.schema2 import DOCKER_SCHEMA2_MANIFESTLIST_CONTENT_TYPE
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -28,6 +29,31 @@ class OCIModel(SharedModel, RegistryDataInterface):
manifests. """ manifests. """
return True 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): 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 """ Finds an alive tag in the repository matching one of the given tag names and returns it
or None if none. or None if none.

View file

@ -32,6 +32,13 @@ class PreOCIModel(SharedModel, RegistryDataInterface):
manifests. """ manifests. """
return False 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): 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 """ Finds an alive tag in the repository matching one of the given tag names and returns it
or None if none. or None if none.

View file

@ -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) tags = registry_model.list_repository_tags(repository_ref, include_legacy_images=True)
assert len(tags) assert len(tags)
tags_map = registry_model.get_legacy_tags_map(repository_ref, storage)
for tag in tags: for tag in tags:
found_tag = registry_model.get_repo_tag(repository_ref, tag.name, include_legacy_image=True) found_tag = registry_model.get_repo_tag(repository_ref, tag.name, include_legacy_image=True)
assert found_tag == tag 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_image = registry_model.get_legacy_image(repository_ref,
found_tag.legacy_image.docker_image_id) found_tag.legacy_image.docker_image_id)
assert found_image == found_tag.legacy_image 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): def test_repository_tag_history(registry_model):

View file

@ -27,11 +27,7 @@ def get_tags(namespace_name, repo_name):
if repository_ref is None: if repository_ref is None:
abort(404) abort(404)
# TODO(jschorr): Change this to normalize manifest lists back to their legacy image tag_map = registry_model.get_legacy_tags_map(repository_ref, storage)
# (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}
return jsonify(tag_map) return jsonify(tag_map)
abort(403) abort(403)

View file

@ -1368,7 +1368,7 @@ def test_push_tag_existing_image(v1_protocol, puller, basic_images, liveserver_s
True, True,
False 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, different_images, liveserver_session, app_reloader,
schema_version, data_model, is_amd): schema_version, data_model, is_amd):
""" Test: Push a new tag with a manifest list containing two manifests, one (possibly) legacy """ 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 # Pull the tag and ensure we (don't) get back the basic images, since they are(n't) part of the
# amd64+linux manifest. # amd64+linux manifest.
v2_protocol.pull(liveserver_session, 'devtable', 'newrepo', 'latest', basic_images, legacy_puller.pull(liveserver_session, 'devtable', 'newrepo', 'latest', basic_images,
credentials=credentials, credentials=credentials,
expected_failure=Failures.UNKNOWN_TAG if not is_amd else None) expected_failure=Failures.UNKNOWN_TAG if not is_amd else None)
@pytest.mark.parametrize('schema_version', [ @pytest.mark.parametrize('schema_version', [