style(data, endpoints, test): ran yapf against changed files
### Description of Changes Issue: https://coreosdev.atlassian.net/browse/QUAY-633 ## Reviewer Checklist - [ ] It works! - [ ] Comments provide sufficient explanations for the next contributor - [ ] Tests cover changes and corner cases - [ ] Follows Quay syntax patterns and format
This commit is contained in:
parent
9e1106f164
commit
8f1200b00d
7 changed files with 871 additions and 1332 deletions
|
@ -16,8 +16,6 @@ SERVICE_LEVEL_LOG_KINDS = set(['service_key_create', 'service_key_approve', 'ser
|
|||
'service_key_modify', 'service_key_extend', 'service_key_rotate'])
|
||||
|
||||
|
||||
|
||||
|
||||
def _validate_logs_arguments(start_time, end_time):
|
||||
if start_time:
|
||||
try:
|
||||
|
|
|
@ -10,14 +10,13 @@ from datetime import timedelta, datetime
|
|||
from flask import request, abort
|
||||
|
||||
from app import dockerfile_build_queue, tuf_metadata_api
|
||||
from endpoints.api import (format_date, nickname, log_action, validate_json_request,
|
||||
require_repo_read, require_repo_write, require_repo_admin,
|
||||
RepositoryParamResource, resource, parse_args, ApiResource,
|
||||
request_error, require_scope, path_param, page_support,
|
||||
query_param, truthy_bool, show_if)
|
||||
from endpoints.api import (
|
||||
format_date, nickname, log_action, validate_json_request, require_repo_read, require_repo_write,
|
||||
require_repo_admin, RepositoryParamResource, resource, parse_args, ApiResource, request_error,
|
||||
require_scope, path_param, page_support, query_param, truthy_bool, show_if)
|
||||
from endpoints.api.repository_models_pre_oci import pre_oci_model as model
|
||||
from endpoints.exception import (Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException,
|
||||
DownstreamIssue)
|
||||
from endpoints.exception import (
|
||||
Unauthorized, NotFound, InvalidRequest, ExceedsLicenseException, DownstreamIssue)
|
||||
from endpoints.api.billing import lookup_allowed_private_repos, get_namespace_plan
|
||||
from endpoints.api.subscribe import check_repository_usage
|
||||
|
||||
|
@ -71,7 +70,8 @@ class RepositoryList(ApiResource):
|
|||
],
|
||||
},
|
||||
'namespace': {
|
||||
'type': 'string',
|
||||
'type':
|
||||
'string',
|
||||
'description': ('Namespace in which the repository should be created. If omitted, the '
|
||||
'username of the caller is used'),
|
||||
},
|
||||
|
@ -118,16 +118,17 @@ class RepositoryList(ApiResource):
|
|||
raise InvalidRequest('Invalid repository name')
|
||||
|
||||
kind = req.get('repo_kind', 'image') or 'image'
|
||||
model.create_repo(namespace_name, repository_name, owner, req['description'], visibility=visibility,
|
||||
repo_kind=kind)
|
||||
model.create_repo(namespace_name, repository_name, owner, req['description'],
|
||||
visibility=visibility, repo_kind=kind)
|
||||
|
||||
log_action('create_repo', namespace_name, {'repo': repository_name,
|
||||
'namespace': namespace_name}, repo_name=repository_name)
|
||||
log_action('create_repo', namespace_name,
|
||||
{'repo': repository_name,
|
||||
'namespace': namespace_name}, repo_name=repository_name)
|
||||
return {
|
||||
'namespace': namespace_name,
|
||||
'name': repository_name,
|
||||
'kind': kind,
|
||||
}, 201
|
||||
'namespace': namespace_name,
|
||||
'name': repository_name,
|
||||
'kind': kind,
|
||||
}, 201
|
||||
|
||||
raise Unauthorized()
|
||||
|
||||
|
@ -178,9 +179,7 @@ class Repository(RepositoryParamResource):
|
|||
'RepoUpdate': {
|
||||
'type': 'object',
|
||||
'description': 'Fields which can be updated in a repository.',
|
||||
'required': [
|
||||
'description',
|
||||
],
|
||||
'required': ['description',],
|
||||
'properties': {
|
||||
'description': {
|
||||
'type': 'string',
|
||||
|
@ -239,12 +238,10 @@ class Repository(RepositoryParamResource):
|
|||
model.set_description(namespace, repository, values['description'])
|
||||
|
||||
log_action('set_repo_description', namespace,
|
||||
{'repo': repository, 'namespace': namespace, 'description': values['description']},
|
||||
repo_name=repository)
|
||||
return {
|
||||
'success': True
|
||||
}
|
||||
|
||||
{'repo': repository,
|
||||
'namespace': namespace,
|
||||
'description': values['description']}, repo_name=repository)
|
||||
return {'success': True}
|
||||
|
||||
@require_repo_admin
|
||||
@nickname('deleteRepository')
|
||||
|
@ -259,8 +256,7 @@ class Repository(RepositoryParamResource):
|
|||
# Remove any builds from the queue.
|
||||
dockerfile_build_queue.delete_namespaced_items(namespace, repository)
|
||||
|
||||
log_action('delete_repo', namespace,
|
||||
{'repo': repository, 'namespace': namespace})
|
||||
log_action('delete_repo', namespace, {'repo': repository, 'namespace': namespace})
|
||||
return '', 204
|
||||
|
||||
|
||||
|
@ -272,9 +268,7 @@ class RepositoryVisibility(RepositoryParamResource):
|
|||
'ChangeVisibility': {
|
||||
'type': 'object',
|
||||
'description': 'Change the visibility for the repository.',
|
||||
'required': [
|
||||
'visibility',
|
||||
],
|
||||
'required': ['visibility',],
|
||||
'properties': {
|
||||
'visibility': {
|
||||
'type': 'string',
|
||||
|
@ -301,8 +295,9 @@ class RepositoryVisibility(RepositoryParamResource):
|
|||
|
||||
model.set_repository_visibility(namespace, repository, visibility)
|
||||
log_action('change_repo_visibility', namespace,
|
||||
{'repo': repository, 'namespace': namespace, 'visibility': values['visibility']},
|
||||
repo_name=repository)
|
||||
{'repo': repository,
|
||||
'namespace': namespace,
|
||||
'visibility': values['visibility']}, repo_name=repository)
|
||||
return {'success': True}
|
||||
|
||||
|
||||
|
@ -314,9 +309,7 @@ class RepositoryTrust(RepositoryParamResource):
|
|||
'ChangeRepoTrust': {
|
||||
'type': 'object',
|
||||
'description': 'Change the trust settings for the repository.',
|
||||
'required': [
|
||||
'trust_enabled',
|
||||
],
|
||||
'required': ['trust_enabled',],
|
||||
'properties': {
|
||||
'trust_enabled': {
|
||||
'type': 'boolean',
|
||||
|
@ -341,8 +334,10 @@ class RepositoryTrust(RepositoryParamResource):
|
|||
values = request.get_json()
|
||||
model.set_trust(namespace, repository, values['trust_enabled'])
|
||||
|
||||
log_action('change_repo_trust', namespace,
|
||||
{'repo': repository, 'namespace': namespace, 'trust_enabled': values['trust_enabled']},
|
||||
repo_name=repository)
|
||||
log_action(
|
||||
'change_repo_trust', namespace,
|
||||
{'repo': repository,
|
||||
'namespace': namespace,
|
||||
'trust_enabled': values['trust_enabled']}, repo_name=repository)
|
||||
|
||||
return {'success': True}
|
||||
|
|
|
@ -8,10 +8,12 @@ import features
|
|||
from endpoints.api import format_date
|
||||
|
||||
|
||||
class RepositoryBaseElement(namedtuple('RepositoryBaseElement',
|
||||
['namespace_name', 'repository_name', 'is_starred', 'is_public', 'kind_name',
|
||||
'description', 'namespace_user_organization',
|
||||
'namespace_user_removed_tag_expiration_s', 'last_modified', 'action_count'])):
|
||||
class RepositoryBaseElement(
|
||||
namedtuple('RepositoryBaseElement', [
|
||||
'namespace_name', 'repository_name', 'is_starred', 'is_public', 'kind_name', 'description',
|
||||
'namespace_user_organization', 'namespace_user_removed_tag_expiration_s', 'last_modified',
|
||||
'action_count', 'should_last_modified', 'should_popularity', 'should_is_starred'
|
||||
])):
|
||||
"""
|
||||
Repository a single quay repository
|
||||
:type namespace_name: string
|
||||
|
@ -56,31 +58,25 @@ class ApplicationRepository(
|
|||
:type releases: [Release]
|
||||
"""
|
||||
|
||||
def to_dict(self, can_write, can_admin):
|
||||
releases_channels_map = defaultdict(list)
|
||||
for channel in self.channels:
|
||||
releases_channels_map[channel.linked_tag_name].append(channel.name)
|
||||
|
||||
def to_dict(self):
|
||||
repo_data = {
|
||||
'namespace': self.repository_base_elements.namespace_name,
|
||||
'name': self.repository_base_elements.repository_name,
|
||||
'kind': self.repository_base_elements.kind_name,
|
||||
'description': self.repository_base_elements.description,
|
||||
'can_write': can_write,
|
||||
'can_admin': can_admin,
|
||||
'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,
|
||||
'channels': [chan.to_dict() for chan in self.channels],
|
||||
'releases': [release.to_dict(releases_channels_map) for release in self.channels],
|
||||
'releases': [release.to_dict() for release in self.releases],
|
||||
}
|
||||
|
||||
return repo_data
|
||||
|
||||
|
||||
class NonApplicationRepository(namedtuple('NonApplicationRepository',
|
||||
['repository_base_elements', 'tags', 'counts', 'badge_token',
|
||||
'trust_enabled'])):
|
||||
class ImageRepositoryRepository(
|
||||
namedtuple('NonApplicationRepository',
|
||||
['repository_base_elements', 'tags', 'counts', 'badge_token', 'trust_enabled'])):
|
||||
"""
|
||||
Repository a single quay repository
|
||||
:type repository_base_elements: RepositoryBaseElement
|
||||
|
@ -90,25 +86,27 @@ class NonApplicationRepository(namedtuple('NonApplicationRepository',
|
|||
:type trust_enabled: boolean
|
||||
"""
|
||||
|
||||
def to_dict(self, can_write, can_admin):
|
||||
def to_dict(self):
|
||||
return {
|
||||
'namespace': self.repository_base_elements.namespace_name,
|
||||
'name': self.repository_base_elements.repository_name,
|
||||
'kind': self.repository_base_elements.kind_name,
|
||||
'description': self.repository_base_elements.description,
|
||||
'can_write': can_write,
|
||||
'can_admin': can_admin,
|
||||
'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},
|
||||
'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,
|
||||
}
|
||||
|
||||
|
||||
class Repository(namedtuple('Repository', ['namespace_name', 'repository_name', ])):
|
||||
class Repository(namedtuple('Repository', [
|
||||
'namespace_name',
|
||||
'repository_name',
|
||||
])):
|
||||
"""
|
||||
Repository a single quay repository
|
||||
:type namespace_name: string
|
||||
|
@ -132,24 +130,29 @@ class Channel(namedtuple('Channel', ['name', 'linked_tag_name', 'linked_tag_life
|
|||
}
|
||||
|
||||
|
||||
class Release(namedtuple('Channel', ['name', 'released', 'lifetime_start'])):
|
||||
class Release(
|
||||
namedtuple('Channel', ['name', 'released', 'lifetime_start', 'releases_channels_map'])):
|
||||
"""
|
||||
Repository a single quay repository
|
||||
:type name: string
|
||||
:type released: string
|
||||
:type last_modified: string
|
||||
:type releases_channels_map: {string -> string}
|
||||
"""
|
||||
|
||||
def to_dict(self, releases_channels_map):
|
||||
def to_dict(self):
|
||||
return {
|
||||
'name': self.name,
|
||||
'last_modified': format_date(datetime.fromtimestamp(self.lifetime_start / 1000)),
|
||||
'channels': releases_channels_map[self.name],
|
||||
'channels': self.releases_channels_map[self.name],
|
||||
}
|
||||
|
||||
|
||||
class Tag(namedtuple('Tag', ['name', 'image_docker_image_id', 'image_aggregate_size', 'lifetime_start_ts',
|
||||
'tag_manifest_digest'])):
|
||||
class Tag(
|
||||
namedtuple('Tag', [
|
||||
'name', 'image_docker_image_id', 'image_aggregate_size', 'lifetime_start_ts',
|
||||
'tag_manifest_digest'
|
||||
])):
|
||||
"""
|
||||
:type name: string
|
||||
:type image_docker_image_id: string
|
||||
|
|
|
@ -79,11 +79,9 @@ class PreOCIModel(RepositoryDataInterface):
|
|||
# Also note the +1 on the limit, as paginate_query uses the extra result to determine whether
|
||||
# there is a next page.
|
||||
start_id = model.modelutil.pagination_start(page_token)
|
||||
repo_query = model.repository.get_visible_repositories(username=username,
|
||||
include_public=public,
|
||||
start_id=start_id,
|
||||
limit=REPOS_PER_PAGE + 1,
|
||||
kind_filter=repo_kind)
|
||||
repo_query = model.repository.get_visible_repositories(
|
||||
username=username, include_public=public, start_id=start_id, limit=REPOS_PER_PAGE + 1,
|
||||
kind_filter=repo_kind)
|
||||
|
||||
repos, next_page_token = model.modelutil.paginate_query(repo_query, limit=REPOS_PER_PAGE,
|
||||
id_alias='rid')
|
||||
|
@ -139,29 +137,30 @@ class PreOCIModel(RepositoryDataInterface):
|
|||
|
||||
is_starred = model.repository.repository_is_starred(user, repo) if user else False
|
||||
is_public = model.repository.is_repository_public(repo)
|
||||
base = RepositoryBaseElement(namespace_name, repository_name, is_starred, is_public, repo.kind.name,
|
||||
repo.description, repo.namespace_user.organization,
|
||||
repo.namespace_user.removed_tag_expiration_s, None, None)
|
||||
base = RepositoryBaseElement(
|
||||
namespace_name, repository_name, is_starred, is_public, repo.kind.name, repo.description,
|
||||
repo.namespace_user.organization, repo.namespace_user.removed_tag_expiration_s, None, None,
|
||||
False, False, False)
|
||||
|
||||
# Note: This is *temporary* code for the new OCI model stuff.
|
||||
if base.kind_name == 'application':
|
||||
channels = oci_model.channel.get_repo_channels(repo)
|
||||
releases = oci_model.release.get_release_objs(repo)
|
||||
|
||||
return ApplicationRepository(base,
|
||||
[Channel(channel.name, channel.linked_tag.name, channel.linked_tag.lifetime_start)
|
||||
for channel in channels],
|
||||
[Release(release.name, release.released, release.lifetime_start)
|
||||
for release in releases])
|
||||
releases_channels_map = defaultdict(list)
|
||||
return ApplicationRepository(
|
||||
base, [create_channel(channel, releases_channels_map) for channel in channels], [
|
||||
Release(release.name, release.released, release.lifetime_start, releases_channels_map)
|
||||
for release in releases
|
||||
])
|
||||
|
||||
tags = model.tag.list_active_repo_tags(repo)
|
||||
start_date = datetime.now() - timedelta(days=MAX_DAYS_IN_3_MONTHS)
|
||||
counts = model.log.get_repository_action_counts(repo, start_date)
|
||||
return NonApplicationRepository(base,
|
||||
[Tag(tag.name, tag.image.docker_image_id, tag.image.aggregate_size,
|
||||
tag.lifetime_start_ts, tag.tagmanifest.digest) for tag in tags],
|
||||
[Count(count.date, count.count) for count in counts],
|
||||
repo.badge_token, repo.trust_enabled)
|
||||
|
||||
return ImageRepositoryRepository(base, [
|
||||
Tag(tag.name, tag.image.docker_image_id, tag.image.aggregate_size, tag.lifetime_start_ts,
|
||||
tag.tagmanifest.digest) for tag in tags
|
||||
], [Count(count.date, count.count) for count in counts], repo.badge_token, repo.trust_enabled)
|
||||
|
||||
|
||||
pre_oci_model = PreOCIModel()
|
||||
|
|
|
@ -9,7 +9,6 @@ from features import FeatureNameValue
|
|||
|
||||
from test.fixtures import *
|
||||
|
||||
|
||||
INVALID_RESPONSE = {
|
||||
u'detail': u"u'invalid_req' is not of type 'boolean'",
|
||||
u'error_message': u"u'invalid_req' is not of type 'boolean'",
|
||||
|
@ -20,31 +19,44 @@ INVALID_RESPONSE = {
|
|||
}
|
||||
|
||||
NOT_FOUND_RESPONSE = {
|
||||
u'detail': u'Not Found',
|
||||
u'error_message': u'Not Found',
|
||||
u'error_type': u'not_found',
|
||||
u'message': u'You have requested this URI [/api/v1/repository/devtable/repo/changetrust] but did you mean /api/v1/repository/<apirepopath:repository>/changetrust or /api/v1/repository/<apirepopath:repository>/changevisibility or /api/v1/repository/<apirepopath:repository>/tag/<tag>/images ?',
|
||||
u'status': 404,
|
||||
u'title': u'not_found',
|
||||
u'type': u'http://localhost/api/v1/error/not_found'
|
||||
u'detail':
|
||||
u'Not Found',
|
||||
u'error_message':
|
||||
u'Not Found',
|
||||
u'error_type':
|
||||
u'not_found',
|
||||
u'message':
|
||||
u'You have requested this URI [/api/v1/repository/devtable/repo/changetrust] but did you mean /api/v1/repository/<apirepopath:repository>/changetrust or /api/v1/repository/<apirepopath:repository>/changevisibility or /api/v1/repository/<apirepopath:repository>/tag/<tag>/images ?',
|
||||
u'status':
|
||||
404,
|
||||
u'title':
|
||||
u'not_found',
|
||||
u'type':
|
||||
u'http://localhost/api/v1/error/not_found'
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('trust_enabled,repo_found,expected_body,expected_status', [
|
||||
(True, True,{'success': True}, 200),
|
||||
(False, True, {'success': True}, 200),
|
||||
(True, True, {
|
||||
'success': True
|
||||
}, 200),
|
||||
(False, True, {
|
||||
'success': True
|
||||
}, 200),
|
||||
(False, False, NOT_FOUND_RESPONSE, 404),
|
||||
('invalid_req', False, INVALID_RESPONSE , 400),
|
||||
('invalid_req', False, INVALID_RESPONSE, 400),
|
||||
])
|
||||
def test_post_changetrust(trust_enabled, repo_found, expected_body, expected_status, client):
|
||||
with patch('endpoints.api.repository.tuf_metadata_api') as mock_tuf:
|
||||
with patch('endpoints.api.repository_models_pre_oci.model.repository.get_repository') as mock_model:
|
||||
with patch(
|
||||
'endpoints.api.repository_models_pre_oci.model.repository.get_repository') as mock_model:
|
||||
mock_model.return_value = MagicMock() if repo_found else None
|
||||
mock_tuf.get_default_tags_with_expiration.return_value = ['tags', 'expiration']
|
||||
with client_with_identity('devtable', client) as cl:
|
||||
params = {'repository': 'devtable/repo'}
|
||||
request_body = {'trust_enabled': trust_enabled}
|
||||
assert expected_body == conduct_api_call(cl, RepositoryTrust, 'POST', params, request_body, expected_status).json
|
||||
assert expected_body == conduct_api_call(cl, RepositoryTrust, 'POST', params, request_body,
|
||||
expected_status).json
|
||||
|
||||
|
||||
def test_signing_disabled(client):
|
||||
|
|
Reference in a new issue