Merge pull request #1926 from coreos-inc/tag-api-pagination
Fix tags API pagination and add a test
This commit is contained in:
commit
c5bd2a5002
2 changed files with 74 additions and 33 deletions
|
@ -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})
|
||||
|
|
|
@ -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)
|
||||
|
|
Reference in a new issue