2014-03-10 22:30:41 +00:00
|
|
|
import logging
|
|
|
|
import json
|
|
|
|
|
2014-03-18 23:21:27 +00:00
|
|
|
from flask import request
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
from data import model
|
2014-03-11 19:20:03 +00:00
|
|
|
from endpoints.api import (truthy_bool, format_date, nickname, log_action, validate_json_request,
|
2014-03-13 00:33:57 +00:00
|
|
|
require_repo_read, require_repo_write, require_repo_admin,
|
|
|
|
RepositoryParamResource, resource, query_param, parse_args, ApiResource,
|
2014-03-18 23:21:27 +00:00
|
|
|
request_error, require_scope, Unauthorized, NotFound, InvalidRequest)
|
|
|
|
from auth.permissions import (ModifyRepositoryPermission, AdministerRepositoryPermission,
|
2014-03-25 21:26:45 +00:00
|
|
|
CreateRepositoryPermission, ReadRepositoryPermission)
|
2014-03-13 00:33:57 +00:00
|
|
|
from auth.auth_context import get_authenticated_user
|
2014-03-14 22:41:14 +00:00
|
|
|
from auth import scopes
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-11 03:54:55 +00:00
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/repository')
|
2014-03-12 20:31:37 +00:00
|
|
|
class RepositoryList(ApiResource):
|
2014-03-11 19:20:03 +00:00
|
|
|
"""Operations for creating and listing repositories."""
|
2014-03-11 03:54:55 +00:00
|
|
|
schemas = {
|
|
|
|
'NewRepo': {
|
|
|
|
'id': 'NewRepo',
|
|
|
|
'type': 'object',
|
2014-03-11 19:20:03 +00:00
|
|
|
'description': 'Description of a new repository',
|
2014-03-17 16:25:41 +00:00
|
|
|
'required': [
|
|
|
|
'repository',
|
|
|
|
'visibility',
|
2014-03-18 19:58:37 +00:00
|
|
|
'description',
|
2014-03-17 16:25:41 +00:00
|
|
|
],
|
2014-03-11 03:54:55 +00:00
|
|
|
'properties': {
|
|
|
|
'repository': {
|
|
|
|
'type': 'string',
|
2014-03-11 19:20:03 +00:00
|
|
|
'description': 'Repository name',
|
2014-03-11 03:54:55 +00:00
|
|
|
},
|
|
|
|
'visibility': {
|
|
|
|
'type': 'string',
|
2014-03-11 19:20:03 +00:00
|
|
|
'description': 'Visibility which the repository will start with',
|
2014-03-11 03:54:55 +00:00
|
|
|
'enum': [
|
|
|
|
'public',
|
|
|
|
'private',
|
2014-03-13 00:33:57 +00:00
|
|
|
],
|
2014-03-11 03:54:55 +00:00
|
|
|
},
|
|
|
|
'namespace': {
|
|
|
|
'type': 'string',
|
2014-03-11 19:20:03 +00:00
|
|
|
'description': ('Namespace in which the repository should be created. If omitted, the '
|
|
|
|
'username of the caller is used'),
|
2014-03-11 03:54:55 +00:00
|
|
|
},
|
|
|
|
'description': {
|
|
|
|
'type': 'string',
|
2014-03-11 19:20:03 +00:00
|
|
|
'description': 'Markdown encoded description for the repository',
|
2014-03-11 03:54:55 +00:00
|
|
|
},
|
2014-03-13 20:31:37 +00:00
|
|
|
},
|
|
|
|
},
|
2014-03-11 03:54:55 +00:00
|
|
|
}
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-13 00:33:57 +00:00
|
|
|
@require_scope(scopes.CREATE_REPO)
|
2014-03-10 22:30:41 +00:00
|
|
|
@nickname('createRepo')
|
2014-03-11 03:54:55 +00:00
|
|
|
@validate_json_request('NewRepo')
|
2014-03-10 22:30:41 +00:00
|
|
|
def post(self):
|
2014-03-11 19:20:03 +00:00
|
|
|
"""Create a new repository."""
|
2014-03-13 00:33:57 +00:00
|
|
|
owner = get_authenticated_user()
|
2014-03-11 03:54:55 +00:00
|
|
|
req = request.get_json()
|
2014-03-18 23:21:27 +00:00
|
|
|
|
|
|
|
if owner is None and 'namespace' not in 'req':
|
|
|
|
raise InvalidRequest('Must provide a namespace or must be logged in.')
|
|
|
|
|
2014-03-11 03:54:55 +00:00
|
|
|
namespace_name = req['namespace'] if 'namespace' in req else owner.username
|
|
|
|
|
|
|
|
permission = CreateRepositoryPermission(namespace_name)
|
|
|
|
if permission.can():
|
|
|
|
repository_name = req['repository']
|
|
|
|
visibility = req['visibility']
|
|
|
|
|
|
|
|
existing = model.get_repository(namespace_name, repository_name)
|
|
|
|
if existing:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise request_error(message='Repository already exists')
|
2014-03-11 03:54:55 +00:00
|
|
|
|
|
|
|
visibility = req['visibility']
|
|
|
|
|
|
|
|
repo = model.create_repository(namespace_name, repository_name, owner,
|
|
|
|
visibility)
|
|
|
|
repo.description = req['description']
|
|
|
|
repo.save()
|
|
|
|
|
2014-03-11 19:20:03 +00:00
|
|
|
log_action('create_repo', namespace_name, {'repo': repository_name,
|
|
|
|
'namespace': namespace_name}, repo=repo)
|
2014-03-13 00:33:57 +00:00
|
|
|
return {
|
2014-03-11 03:54:55 +00:00
|
|
|
'namespace': namespace_name,
|
|
|
|
'name': repository_name
|
2014-03-13 00:33:57 +00:00
|
|
|
}, 201
|
2014-03-11 03:54:55 +00:00
|
|
|
|
2014-03-17 20:57:35 +00:00
|
|
|
raise Unauthorized()
|
2014-03-10 22:30:41 +00:00
|
|
|
|
2014-03-19 16:09:07 +00:00
|
|
|
@require_scope(scopes.READ_REPO)
|
2014-03-10 22:30:41 +00:00
|
|
|
@nickname('listRepos')
|
2014-03-11 16:57:33 +00:00
|
|
|
@parse_args
|
2014-03-11 19:20:03 +00:00
|
|
|
@query_param('page', 'Offset page number. (int)', type=int)
|
|
|
|
@query_param('limit', 'Limit on the number of results (int)', type=int)
|
|
|
|
@query_param('namespace', 'Namespace to use when querying for org repositories.', type=str)
|
|
|
|
@query_param('public', 'Whether to include public repositories.', type=truthy_bool, default=True)
|
|
|
|
@query_param('private', 'Whether to inlcude private repositories.', type=truthy_bool,
|
|
|
|
default=True)
|
|
|
|
@query_param('sort', 'Whether to sort the results.', type=truthy_bool, default=False)
|
|
|
|
@query_param('count', 'Whether to include a count of the total number of results available.',
|
|
|
|
type=truthy_bool, default=False)
|
2014-03-11 16:57:33 +00:00
|
|
|
def get(self, args):
|
2014-03-11 19:20:03 +00:00
|
|
|
"""Fetch the list of repositories under a variety of situations."""
|
2014-03-10 22:30:41 +00:00
|
|
|
def repo_view(repo_obj):
|
|
|
|
return {
|
|
|
|
'namespace': repo_obj.namespace,
|
|
|
|
'name': repo_obj.name,
|
|
|
|
'description': repo_obj.description,
|
|
|
|
'is_public': repo_obj.visibility.name == 'public',
|
|
|
|
}
|
|
|
|
|
|
|
|
username = None
|
2014-03-14 22:41:14 +00:00
|
|
|
if get_authenticated_user() and args['private']:
|
|
|
|
username = get_authenticated_user().username
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
response = {}
|
|
|
|
|
|
|
|
repo_count = None
|
|
|
|
if args['count']:
|
2014-03-11 19:20:03 +00:00
|
|
|
repo_count = model.get_visible_repository_count(username, include_public=args['public'],
|
2014-03-10 22:30:41 +00:00
|
|
|
namespace=args['namespace'])
|
|
|
|
response['count'] = repo_count
|
|
|
|
|
2014-03-11 19:20:03 +00:00
|
|
|
repo_query = model.get_visible_repositories(username, limit=args['limit'], page=args['page'],
|
|
|
|
include_public=args['public'], sort=args['sort'],
|
2014-03-10 22:30:41 +00:00
|
|
|
namespace=args['namespace'])
|
|
|
|
|
2014-03-25 21:26:45 +00:00
|
|
|
response['repositories'] = [repo_view(repo) for repo in repo_query
|
2014-03-26 19:56:51 +00:00
|
|
|
if (repo.visibility.name == 'public' or
|
|
|
|
ReadRepositoryPermission(repo.namespace, repo.name).can())]
|
2014-03-10 22:30:41 +00:00
|
|
|
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>')
|
2014-03-10 22:30:41 +00:00
|
|
|
class Repository(RepositoryParamResource):
|
2014-03-11 19:20:03 +00:00
|
|
|
"""Operations for managing a specific repository."""
|
2014-03-13 00:33:57 +00:00
|
|
|
schemas = {
|
|
|
|
'RepoUpdate': {
|
|
|
|
'id': 'RepoUpdate',
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Fields which can be updated in a repository.',
|
2014-03-17 16:25:41 +00:00
|
|
|
'required': [
|
|
|
|
'description',
|
|
|
|
],
|
2014-03-13 00:33:57 +00:00
|
|
|
'properties': {
|
|
|
|
'description': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'Markdown encoded description for the repository',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-10 22:30:41 +00:00
|
|
|
@require_repo_read
|
|
|
|
@nickname('getRepo')
|
|
|
|
def get(self, namespace, repository):
|
2014-03-11 19:20:03 +00:00
|
|
|
"""Fetch the specified repository."""
|
2014-03-10 22:30:41 +00:00
|
|
|
logger.debug('Get repo: %s/%s' % (namespace, repository))
|
|
|
|
|
|
|
|
def tag_view(tag):
|
|
|
|
return {
|
|
|
|
'name': tag.name,
|
2014-07-17 19:16:22 +00:00
|
|
|
'image_id': tag.image.docker_image_id,
|
|
|
|
'dbid': tag.image.id
|
2014-03-10 22:30:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
organization = None
|
|
|
|
try:
|
|
|
|
organization = model.get_organization(namespace)
|
|
|
|
except model.InvalidOrganizationException:
|
|
|
|
pass
|
|
|
|
|
|
|
|
is_public = model.repository_is_public(namespace, repository)
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if repo:
|
|
|
|
tags = model.list_repository_tags(namespace, repository)
|
|
|
|
tag_dict = {tag.name: tag_view(tag) for tag in tags}
|
|
|
|
can_write = ModifyRepositoryPermission(namespace, repository).can()
|
|
|
|
can_admin = AdministerRepositoryPermission(namespace, repository).can()
|
|
|
|
active_builds = model.list_repository_builds(namespace, repository, 1,
|
|
|
|
include_inactive=False)
|
|
|
|
|
|
|
|
return {
|
|
|
|
'namespace': namespace,
|
|
|
|
'name': repository,
|
|
|
|
'description': repo.description,
|
|
|
|
'tags': tag_dict,
|
|
|
|
'can_write': can_write,
|
|
|
|
'can_admin': can_admin,
|
|
|
|
'is_public': is_public,
|
|
|
|
'is_building': len(list(active_builds)) > 0,
|
|
|
|
'is_organization': bool(organization),
|
|
|
|
'status_token': repo.badge_token if not is_public else ''
|
|
|
|
}
|
|
|
|
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-13 00:33:57 +00:00
|
|
|
|
|
|
|
@require_repo_write
|
|
|
|
@nickname('updateRepo')
|
|
|
|
@validate_json_request('RepoUpdate')
|
|
|
|
def put(self, namespace, repository):
|
|
|
|
""" Update the description in the specified repository. """
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if repo:
|
|
|
|
values = request.get_json()
|
|
|
|
repo.description = values['description']
|
|
|
|
repo.save()
|
|
|
|
|
|
|
|
log_action('set_repo_description', namespace,
|
|
|
|
{'repo': repository, 'description': values['description']},
|
|
|
|
repo=repo)
|
|
|
|
return {
|
|
|
|
'success': True
|
|
|
|
}
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-13 00:33:57 +00:00
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('deleteRepository')
|
|
|
|
def delete(self, namespace, repository):
|
2014-03-13 19:19:49 +00:00
|
|
|
""" Delete a repository. """
|
2014-03-13 00:33:57 +00:00
|
|
|
model.purge_repository(namespace, repository)
|
|
|
|
log_action('delete_repo', namespace,
|
|
|
|
{'repo': repository, 'namespace': namespace})
|
|
|
|
return 'Deleted', 204
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/changevisibility')
|
2014-03-13 00:33:57 +00:00
|
|
|
class RepositoryVisibility(RepositoryParamResource):
|
|
|
|
""" Custom verb for changing the visibility of the repository. """
|
|
|
|
schemas = {
|
|
|
|
'ChangeVisibility': {
|
|
|
|
'id': 'ChangeVisibility',
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Change the visibility for the repository.',
|
2014-03-17 16:25:41 +00:00
|
|
|
'required': [
|
|
|
|
'visibility',
|
|
|
|
],
|
2014-03-13 00:33:57 +00:00
|
|
|
'properties': {
|
|
|
|
'visibility': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'Visibility which the repository will start with',
|
|
|
|
'enum': [
|
|
|
|
'public',
|
|
|
|
'private',
|
|
|
|
],
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('changeRepoVisibility')
|
|
|
|
@validate_json_request('ChangeVisibility')
|
|
|
|
def post(self, namespace, repository):
|
|
|
|
""" Change the visibility of a repository. """
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
if repo:
|
|
|
|
values = request.get_json()
|
|
|
|
model.set_repository_visibility(repo, values['visibility'])
|
|
|
|
log_action('change_repo_visibility', namespace,
|
|
|
|
{'repo': repository, 'visibility': values['visibility']},
|
|
|
|
repo=repo)
|
|
|
|
return {
|
|
|
|
'success': True
|
|
|
|
}
|