Merge pull request #1926 from coreos-inc/tag-api-pagination

Fix tags API pagination and add a test
This commit is contained in:
josephschorr 2016-10-03 22:09:54 +03:00 committed by GitHub
commit c5bd2a5002
2 changed files with 74 additions and 33 deletions

View file

@ -55,8 +55,9 @@ def paginate(limit_kwarg_name='limit', offset_kwarg_name='offset',
offset = page_info.get('offset', 0)
def callback(num_results, response):
if num_results <= limit:
if num_results < limit:
return
next_page_token = encrypt_page_token({'offset': limit + offset})
link = get_app_url() + url_for(request.endpoint, **request.view_args)
link += '?%s; rel="next"' % urlencode({'n': limit, 'next_page': next_page_token})

View file

@ -526,9 +526,8 @@ class V2RegistryMixin(BaseRegistryMixin):
class V2RegistryPushMixin(V2RegistryMixin):
push_version = 'v2'
def do_push(self, namespace, repository, username, password, images=None, tag_name=None,
cancel=False, invalid=False, expect_failure=None, scopes=None,
munge_shas=False):
def do_push(self, namespace, repository, username, password, images=None, tag_names=None,
cancel=False, invalid=False, expect_failure=None, scopes=None, munge_shas=False):
images = images or self._get_default_images()
repo_name = _get_repo_name(namespace, repository)
@ -546,23 +545,28 @@ class V2RegistryPushMixin(V2RegistryMixin):
if expected_auth_code != 200:
return
# Build a fake manifest.
tag_name = tag_name or 'latest'
builder = DockerSchema1ManifestBuilder(namespace, repository, tag_name)
full_contents = {}
for image_data in reversed(images):
full_contents[image_data['id']] = _get_full_contents(image_data, additional_fields=munge_shas)
checksum = 'sha256:' + hashlib.sha256(full_contents[image_data['id']]).hexdigest()
if invalid:
checksum = 'sha256:' + hashlib.sha256('foobarbaz').hexdigest()
builder.add_layer(checksum, json.dumps(image_data))
expected_code = _get_expected_code(expect_failure, 2, 404)
tag_names = tag_names or ['latest']
# Build the manifest.
manifest = builder.build(_JWK)
manifests = {}
full_contents = {}
for image_data in reversed(images):
full_contents[image_data['id']] = _get_full_contents(image_data,
additional_fields=munge_shas)
# Build a fake manifest.
for tag_name in tag_names:
builder = DockerSchema1ManifestBuilder(namespace, repository, tag_name)
for image_data in reversed(images):
checksum = 'sha256:' + hashlib.sha256(full_contents[image_data['id']]).hexdigest()
if invalid:
checksum = 'sha256:' + hashlib.sha256('foobarbaz').hexdigest()
builder.add_layer(checksum, json.dumps(image_data))
# Build the manifest.
manifests[tag_name] = builder.build(_JWK)
# Push the image's layers.
checksums = {}
@ -636,14 +640,17 @@ class V2RegistryPushMixin(V2RegistryMixin):
self.assertEquals(response.headers['Docker-Content-Digest'], checksum)
self.assertEquals(response.headers['Content-Length'], str(len(layer_bytes)))
# Write the manifest. If we expect it to be invalid, we expect a 404 code. Otherwise, we expect
# a 202 response for success.
put_code = 404 if invalid else 202
self.conduct('PUT', '/v2/%s/manifests/%s' % (repo_name, tag_name),
data=manifest.bytes, expected_code=put_code,
headers={'Content-Type': 'application/json'}, auth='jwt')
for tag_name in tag_names:
manifest = manifests[tag_name]
return checksums, manifest.digest
# Write the manifest. If we expect it to be invalid, we expect a 404 code. Otherwise, we expect
# a 202 response for success.
put_code = 404 if invalid else 202
self.conduct('PUT', '/v2/%s/manifests/%s' % (repo_name, tag_name),
data=manifest.bytes, expected_code=put_code,
headers={'Content-Type': 'application/json'}, auth='jwt')
return checksums, manifests
class V2RegistryPullMixin(V2RegistryMixin):
@ -1147,9 +1154,38 @@ class V1RegistryTests(V1RegistryPullMixin, V1RegistryPushMixin, RegistryTestsMix
class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMixin,
RegistryTestCaseMixin, LiveServerTestCase):
""" Tests for V2 registry. """
def test_tags_pagination(self):
# Push 10 tags.
tag_names = ['tag-%s' % i for i in range(0, 10)]
self.do_push('public', 'new-repo', 'public', 'password', tag_names=tag_names)
encountered = set()
# Ensure tags list is properly paginated.
relative_url = '/v2/public/new-repo/tags/list?n=5'
for i in range(0, 3):
result = self.conduct('GET', relative_url, auth='jwt')
result_json = result.json()
encountered.update(set(result_json['tags']))
if 'Link' not in result.headers:
break
# Check the next page of results.
link = result.headers['Link']
self.assertTrue(link.endswith('; rel="next"'))
url, _ = link.split(';')
relative_url = url[len(self.get_server_url()):]
encountered.update(set(result_json['tags']))
# Ensure we found all the results.
self.assertEquals(encountered, set(tag_names))
def test_numeric_tag(self):
# Push a new repository.
self.do_push('public', 'new-repo', 'public', 'password', tag_name='1234')
self.do_push('public', 'new-repo', 'public', 'password', tag_names=['1234'])
# Pull the repository.
self.do_pull('public', 'new-repo', 'public', 'password', manifest_id='1234')
@ -1172,7 +1208,8 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
'contents': 'somecontent'
}]
(_, digest) = self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
(_, manifests) = self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
digest = manifests['latest'].digest
self.conduct_api_login('devtable', 'password')
labels = self.conduct('GET', '/api/v1/repository/devtable/newrepo/manifest/' + digest + '/labels').json()
@ -1194,7 +1231,8 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
'contents': 'somecontent'
}]
(_, digest) = self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
(_, manifests) = self.do_push('devtable', 'newrepo', 'devtable', 'password', images=images)
digest = manifests['latest'].digest
self.conduct_api_login('devtable', 'password')
labels = self.conduct('GET', '/api/v1/repository/devtable/newrepo/manifest/' + digest + '/labels').json()
@ -1246,7 +1284,8 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
def test_delete_manifest(self):
# Push a new repo with the latest tag.
(_, digest) = self.do_push('devtable', 'newrepo', 'devtable', 'password')
(_, manifests) = self.do_push('devtable', 'newrepo', 'devtable', 'password')
digest = manifests['latest'].digest
# Ensure the pull works.
self.do_pull('devtable', 'newrepo', 'devtable', 'password')
@ -1289,7 +1328,8 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
def test_pull_by_checksum(self):
# Add a new repository under the user, so we have a real repository to pull.
_, digest = self.do_push('devtable', 'newrepo', 'devtable', 'password')
_, manifests = self.do_push('devtable', 'newrepo', 'devtable', 'password')
digest = manifests['latest'].digest
# Attempt to pull by digest.
self.do_pull('devtable', 'newrepo', 'devtable', 'password', manifest_id=digest)
@ -1437,10 +1477,10 @@ class V2RegistryTests(V2RegistryPullMixin, V2RegistryPushMixin, RegistryTestsMix
# Create the repo.
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=latest_images,
tag_name='latest')
tag_names=['latest'])
self.do_push('devtable', 'newrepo', 'devtable', 'password', images=foobar_images,
tag_name='foobar')
tag_names=['foobar'])
# Retrieve the tags.
response = self.conduct('GET', '/v2/devtable/newrepo/tags/list', auth='jwt', expected_code=200)