Merge branch 'newchanges' into python-registry-v2
This commit is contained in:
commit
7efa6265bf
6 changed files with 26 additions and 44 deletions
|
@ -14,7 +14,9 @@ logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_parent_images(namespace_name, repository_name, image_obj):
|
def get_parent_images(namespace_name, repository_name, image_obj):
|
||||||
""" Returns a list of parent Image objects in chronilogical order. """
|
""" Returns a list of parent Image objects starting with the most recent parent
|
||||||
|
and ending with the base layer.
|
||||||
|
"""
|
||||||
parents = image_obj.ancestors
|
parents = image_obj.ancestors
|
||||||
|
|
||||||
# Ancestors are in the format /<root>/<intermediate>/.../<parent>/, with each path section
|
# Ancestors are in the format /<root>/<intermediate>/.../<parent>/, with each path section
|
||||||
|
@ -31,7 +33,7 @@ def get_parent_images(namespace_name, repository_name, image_obj):
|
||||||
|
|
||||||
id_to_image = {unicode(image.id): image for image in parents}
|
id_to_image = {unicode(image.id): image for image in parents}
|
||||||
|
|
||||||
return [id_to_image[parent_id] for parent_id in parent_db_ids]
|
return [id_to_image[parent_id] for parent_id in reversed(parent_db_ids)]
|
||||||
|
|
||||||
|
|
||||||
def get_repo_image(namespace_name, repository_name, docker_image_id):
|
def get_repo_image(namespace_name, repository_name, docker_image_id):
|
||||||
|
|
|
@ -3,8 +3,9 @@ import os.path
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
|
|
||||||
DIGEST_PATTERN = r'(tarsum\.(v[\w]+)\+)?([\w]+):([0-9a-f]+)'
|
DIGEST_PATTERN = r'([A-Za-z0-9_+.-]+):([A-Fa-f0-9]+)'
|
||||||
|
REPLACE_WITH_PATH = re.compile(r'[+.]')
|
||||||
|
REPLACE_DOUBLE_SLASHES = re.compile(r'/+')
|
||||||
|
|
||||||
class InvalidDigestException(RuntimeError):
|
class InvalidDigestException(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
@ -13,15 +14,11 @@ class InvalidDigestException(RuntimeError):
|
||||||
class Digest(object):
|
class Digest(object):
|
||||||
DIGEST_REGEX = re.compile(DIGEST_PATTERN)
|
DIGEST_REGEX = re.compile(DIGEST_PATTERN)
|
||||||
|
|
||||||
def __init__(self, hash_alg, hash_bytes, is_tarsum=False, tarsum_version=None):
|
def __init__(self, hash_alg, hash_bytes):
|
||||||
self._hash_alg = hash_alg
|
self._hash_alg = hash_alg
|
||||||
self._hash_bytes = hash_bytes
|
self._hash_bytes = hash_bytes
|
||||||
self._is_tarsum = is_tarsum
|
|
||||||
self._tarsum_version = tarsum_version
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self._is_tarsum:
|
|
||||||
return 'tarsum.{0}+{1}:{2}'.format(self._tarsum_version, self._hash_alg, self._hash_bytes)
|
|
||||||
return '{0}:{1}'.format(self._hash_alg, self._hash_bytes)
|
return '{0}:{1}'.format(self._hash_alg, self._hash_bytes)
|
||||||
|
|
||||||
def __eq__(self, rhs):
|
def __eq__(self, rhs):
|
||||||
|
@ -34,16 +31,7 @@ class Digest(object):
|
||||||
if match is None or match.end() != len(digest):
|
if match is None or match.end() != len(digest):
|
||||||
raise InvalidDigestException('Not a valid digest: %s', digest)
|
raise InvalidDigestException('Not a valid digest: %s', digest)
|
||||||
|
|
||||||
is_tarsum = match.group(1) is not None
|
return Digest(match.group(1), match.group(2))
|
||||||
return Digest(match.group(3), match.group(4), is_tarsum, match.group(2))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_tarsum(self):
|
|
||||||
return self._is_tarsum
|
|
||||||
|
|
||||||
@property
|
|
||||||
def tarsum_version(self):
|
|
||||||
return self._tarsum_version
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def hash_alg(self):
|
def hash_alg(self):
|
||||||
|
@ -59,14 +47,12 @@ def content_path(digest):
|
||||||
parsed = Digest.parse_digest(digest)
|
parsed = Digest.parse_digest(digest)
|
||||||
components = []
|
components = []
|
||||||
|
|
||||||
if parsed.is_tarsum:
|
|
||||||
components.extend(['tarsum', parsed.tarsum_version])
|
|
||||||
|
|
||||||
# Generate a prefix which is always two characters, and which will be filled with leading zeros
|
# Generate a prefix which is always two characters, and which will be filled with leading zeros
|
||||||
# if the input does not contain at least two characters. e.g. ABC -> AB, A -> 0A
|
# if the input does not contain at least two characters. e.g. ABC -> AB, A -> 0A
|
||||||
prefix = parsed.hash_bytes[0:2].zfill(2)
|
prefix = parsed.hash_bytes[0:2].zfill(2)
|
||||||
components.extend([parsed.hash_alg, prefix, parsed.hash_bytes])
|
pathish = REPLACE_WITH_PATH.sub('/', parsed.hash_alg)
|
||||||
|
normalized = REPLACE_DOUBLE_SLASHES.sub('/', pathish).lstrip('/')
|
||||||
|
components.extend([normalized, prefix, parsed.hash_bytes])
|
||||||
return os.path.join(*components)
|
return os.path.join(*components)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -154,10 +154,7 @@ class RepositoryTagImages(RepositoryParamResource):
|
||||||
image_map[str(image.id)] = image
|
image_map[str(image.id)] = image
|
||||||
|
|
||||||
image_map_all = dict(image_map)
|
image_map_all = dict(image_map)
|
||||||
|
all_images = [tag_image] + list(parent_images)
|
||||||
parents = list(parent_images)
|
|
||||||
parents.reverse()
|
|
||||||
all_images = [tag_image] + parents
|
|
||||||
|
|
||||||
# Filter the images returned to those not found in the ancestry of any of the other tags in
|
# Filter the images returned to those not found in the ancestry of any of the other tags in
|
||||||
# the repository.
|
# the repository.
|
||||||
|
|
|
@ -396,7 +396,7 @@ def get_image_ancestry(namespace, repository, image_id, headers):
|
||||||
parents = model.image.get_parent_images(namespace, repository, image)
|
parents = model.image.get_parent_images(namespace, repository, image)
|
||||||
|
|
||||||
ancestry_docker_ids = [image.docker_image_id]
|
ancestry_docker_ids = [image.docker_image_id]
|
||||||
ancestry_docker_ids.extend([parent.docker_image_id for parent in reversed(parents)])
|
ancestry_docker_ids.extend([parent.docker_image_id for parent in parents])
|
||||||
|
|
||||||
# We can not use jsonify here because we are returning a list not an object
|
# We can not use jsonify here because we are returning a list not an object
|
||||||
response = make_response(json.dumps(ancestry_docker_ids), 200)
|
response = make_response(json.dumps(ancestry_docker_ids), 200)
|
||||||
|
@ -519,7 +519,7 @@ def process_image_changes(namespace, repository, image_id):
|
||||||
parent_trie_path = None
|
parent_trie_path = None
|
||||||
if parents:
|
if parents:
|
||||||
parent_trie_path, parent_locations = process_image_changes(namespace, repository,
|
parent_trie_path, parent_locations = process_image_changes(namespace, repository,
|
||||||
parents[-1].docker_image_id)
|
parents[0].docker_image_id)
|
||||||
|
|
||||||
# Read in the collapsed layer state of the filesystem for the parent
|
# Read in the collapsed layer state of the filesystem for the parent
|
||||||
parent_trie = changes.empty_fs()
|
parent_trie = changes.empty_fs()
|
||||||
|
|
|
@ -29,11 +29,7 @@ def _open_stream(formatter, namespace, repository, tag, synthetic_image_id, imag
|
||||||
# the database.
|
# the database.
|
||||||
with database.UseThenDisconnect(app.config):
|
with database.UseThenDisconnect(app.config):
|
||||||
image_list = list(model.image.get_parent_images(namespace, repository, repo_image))
|
image_list = list(model.image.get_parent_images(namespace, repository, repo_image))
|
||||||
image_list.append(repo_image)
|
image_list.insert(0, repo_image)
|
||||||
|
|
||||||
# Note: The image list ordering must be from top-level image, downward, so we reverse the order
|
|
||||||
# here.
|
|
||||||
image_list.reverse()
|
|
||||||
|
|
||||||
def get_next_image():
|
def get_next_image():
|
||||||
for current_image in image_list:
|
for current_image in image_list:
|
||||||
|
|
|
@ -5,12 +5,13 @@ from digest.digest_tools import Digest, content_path, InvalidDigestException
|
||||||
class TestParseDigest(unittest.TestCase):
|
class TestParseDigest(unittest.TestCase):
|
||||||
def test_parse_good(self):
|
def test_parse_good(self):
|
||||||
examples = [
|
examples = [
|
||||||
('tarsum.v123123+sha1:123deadbeef', ('sha1', '123deadbeef', True, 'v123123')),
|
('tarsum.v123123+sha1:123deadbeef', ('tarsum.v123123+sha1', '123deadbeef')),
|
||||||
('tarsum.v1+sha256:123123', ('sha256', '123123', True, 'v1')),
|
('tarsum.v1+sha256:123123', ('tarsum.v1+sha256', '123123')),
|
||||||
('tarsum.v0+md5:abc', ('md5', 'abc', True, 'v0')),
|
('tarsum.v0+md5:abc', ('tarsum.v0+md5', 'abc')),
|
||||||
('sha1:123deadbeef', ('sha1', '123deadbeef', False, None)),
|
('tarsum+sha1:abc', ('tarsum+sha1', 'abc')),
|
||||||
('sha256:123123', ('sha256', '123123', False, None)),
|
('sha1:123deadbeef', ('sha1', '123deadbeef')),
|
||||||
('md5:abc', ('md5', 'abc', False, None)),
|
('sha256:123123', ('sha256', '123123')),
|
||||||
|
('md5:abc', ('md5', 'abc')),
|
||||||
]
|
]
|
||||||
|
|
||||||
for digest, output_args in examples:
|
for digest, output_args in examples:
|
||||||
|
@ -21,9 +22,7 @@ class TestParseDigest(unittest.TestCase):
|
||||||
|
|
||||||
def test_parse_fail(self):
|
def test_parse_fail(self):
|
||||||
examples = [
|
examples = [
|
||||||
'tarsum.v++sha1:123deadbeef',
|
'tarsum.v+md5:abc:',
|
||||||
'.v1+sha256:123123',
|
|
||||||
'tarsum.v+md5:abc',
|
|
||||||
'sha1:123deadbeefzxczxv',
|
'sha1:123deadbeefzxczxv',
|
||||||
'sha256123123',
|
'sha256123123',
|
||||||
'tarsum.v1+',
|
'tarsum.v1+',
|
||||||
|
@ -45,6 +44,8 @@ class TestDigestPath(unittest.TestCase):
|
||||||
('sha256:123123', 'sha256/12/123123'),
|
('sha256:123123', 'sha256/12/123123'),
|
||||||
('md5:abc', 'md5/ab/abc'),
|
('md5:abc', 'md5/ab/abc'),
|
||||||
('md5:1', 'md5/01/1'),
|
('md5:1', 'md5/01/1'),
|
||||||
|
('md5.....+++:1', 'md5/01/1'),
|
||||||
|
('.md5.:1', 'md5/01/1'),
|
||||||
]
|
]
|
||||||
|
|
||||||
for digest, path in examples:
|
for digest, path in examples:
|
||||||
|
|
Reference in a new issue