Repository endpoint tags pagination (#3238)

* endpoint/api/repository: limit the number of tags returned

- Limit the number of tags returned by /api/v1/repository/<ns:repo> to 500.
- Uses the tag history endpoint instead, with an active tag filte.
- Update UI to use tag history endpoint instead.
This commit is contained in:
Kenny Lee Sin Cheong 2018-09-14 15:30:54 -04:00 committed by GitHub
parent 6d5489b254
commit 8e643ce5d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 99 additions and 34 deletions

View file

@ -192,12 +192,16 @@ class Repository(RepositoryParamResource):
@parse_args()
@query_param('includeStats', 'Whether to include action statistics', type=truthy_bool,
default=False)
@query_param('includeTags', 'Whether to include repository tags', type=truthy_bool,
default=True)
@require_repo_read
@nickname('getRepo')
def get(self, namespace, repository, parsed_args):
"""Fetch the specified repository."""
logger.debug('Get repo: %s/%s' % (namespace, repository))
repo = model.get_repo(namespace, repository, get_authenticated_user())
include_tags = parsed_args['includeTags']
max_tags = 500;
repo = model.get_repo(namespace, repository, get_authenticated_user(), include_tags, max_tags)
if repo is None:
raise NotFound()

View file

@ -87,7 +87,7 @@ class ImageRepositoryRepository(
"""
def to_dict(self):
return {
img_repo = {
'namespace': self.repository_base_elements.namespace_name,
'name': self.repository_base_elements.repository_name,
'kind': self.repository_base_elements.kind_name,
@ -95,12 +95,13 @@ class ImageRepositoryRepository(
'is_public': self.repository_base_elements.is_public,
'is_organization': self.repository_base_elements.namespace_user_organization,
'is_starred': self.repository_base_elements.is_starred,
'tags': {tag.name: tag.to_dict()
for tag in self.tags},
'status_token': self.badge_token if not self.repository_base_elements.is_public else '',
'trust_enabled': bool(features.SIGNING) and self.trust_enabled,
'tag_expiration_s': self.repository_base_elements.namespace_user_removed_tag_expiration_s,
}
if self.tags is not None:
img_repo['tags'] = {tag.name: tag.to_dict() for tag in self.tags}
return img_repo
class Repository(namedtuple('Repository', [
@ -203,7 +204,7 @@ class RepositoryDataInterface(object):
"""
@abstractmethod
def get_repo(self, namespace_name, repository_name, user):
def get_repo(self, namespace_name, repository_name, user, include_tags=True, max_tags=500):
"""
Returns a repository
"""

View file

@ -134,7 +134,7 @@ class PreOCIModel(RepositoryDataInterface):
repo_kind=repo_kind, description=description)
return Repository(namespace_name, repository_name)
def get_repo(self, namespace_name, repository_name, user):
def get_repo(self, namespace_name, repository_name, user, include_tags=True, max_tags=500):
repo = model.repository.get_repository(namespace_name, repository_name)
if repo is None:
return None
@ -156,18 +156,22 @@ class PreOCIModel(RepositoryDataInterface):
for release in releases
])
tags = None
repo_ref = RepositoryReference.for_repo_obj(repo)
tags = registry_model.list_repository_tags(repo_ref, include_legacy_images=True)
if include_tags:
tags, _ = registry_model.list_repository_tag_history(repo_ref, page=1, size=max_tags, active_tags_only=True)
tags = [
Tag(tag.name, tag.legacy_image.docker_image_id, tag.legacy_image.aggregate_size,
tag.lifetime_start_ts,
tag.manifest_digest,
tag.lifetime_end_ts) for tag in tags
]
start_date = datetime.now() - timedelta(days=MAX_DAYS_IN_3_MONTHS)
counts = model.log.get_repository_action_counts(repo, start_date)
return ImageRepositoryRepository(base, [
Tag(tag.name, tag.legacy_image.docker_image_id, tag.legacy_image.aggregate_size,
tag.lifetime_start_ts,
tag.manifest_digest,
tag.lifetime_end_ts) for tag in tags
], [Count(count.date, count.count) for count in counts], repo.badge_token, repo.trust_enabled)
return ImageRepositoryRepository(base, tags,
[Count(count.date, count.count) for count in counts], repo.badge_token, repo.trust_enabled)
pre_oci_model = PreOCIModel()

View file

@ -7,7 +7,8 @@ from auth.auth_context import get_authenticated_user
from data.registry_model import registry_model
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
RepositoryParamResource, log_action, validate_json_request, path_param,
parse_args, query_param, truthy_bool, disallow_for_app_repositories)
parse_args, query_param, truthy_bool, disallow_for_app_repositories,
format_date)
from endpoints.api.image import image_dict
from endpoints.exception import NotFound, InvalidRequest
from util.names import TAG_ERROR, TAG_REGEX
@ -30,6 +31,16 @@ def _tag_dict(tag):
if tag.legacy_image:
tag_info['docker_image_id'] = tag.legacy_image.docker_image_id
tag_info['image_id'] = tag.legacy_image.docker_image_id
tag_info['size'] = tag.legacy_image.aggregate_size
if tag.lifetime_start_ts > 0:
last_modified = format_date(datetime.fromtimestamp(tag.lifetime_start_ts))
tag_info['last_modified'] = last_modified
if tag.lifetime_end_ts is not None:
expiration = format_date(datetime.fromtimestamp(tag.lifetime_end_ts))
tag_info['expiration'] = expiration
return tag_info
@ -46,11 +57,13 @@ class ListRepositoryTags(RepositoryParamResource):
@query_param('limit', 'Limit to the number of results to return per page. Max 100.', type=int,
default=50)
@query_param('page', 'Page index for the results. Default 1.', type=int, default=1)
@query_param('onlyActiveTags', 'Filter to only active tags.', type=truthy_bool, default=False)
@nickname('listRepoTags')
def get(self, namespace, repository, parsed_args):
specific_tag = parsed_args.get('specificTag') or None
page = max(1, parsed_args.get('page', 1))
limit = min(100, max(1, parsed_args.get('limit', 50)))
active_tags_only = parsed_args.get('onlyActiveTags')
repo_ref = registry_model.lookup_repository(namespace, repository)
if repo_ref is None:
@ -58,7 +71,8 @@ class ListRepositoryTags(RepositoryParamResource):
history, has_more = registry_model.list_repository_tag_history(repo_ref, page=page,
size=limit,
specific_tag_name=specific_tag)
specific_tag_name=specific_tag,
active_tags_only=active_tags_only)
return {
'tags': [_tag_dict(tag) for tag in history],
'page': page,