Merge remote-tracking branch 'upstream/master' into python-registry-v2

This commit is contained in:
Jake Moshenko 2015-09-04 16:32:01 -04:00
commit 210ed7cf02
148 changed files with 1829 additions and 445 deletions

View file

@ -1,7 +1,7 @@
import logging
import datetime
from app import app
from app import app, metric_queue
from flask import Blueprint, request, make_response, jsonify, session
from flask.ext.restful import Resource, abort, Api, reqparse
from flask.ext.restful.utils.cors import crossdomain
@ -20,6 +20,7 @@ from auth.auth_context import get_authenticated_user, get_validated_oauth_token
from auth.auth import process_oauth
from endpoints.csrf import csrf_protect
from endpoints.decorators import check_anon_protection
from util.saas.metricqueue import time_decorator
logger = logging.getLogger(__name__)
@ -28,7 +29,7 @@ api = Api()
api.init_app(api_bp)
api.decorators = [csrf_protect,
crossdomain(origin='*', headers=['Authorization', 'Content-Type']),
process_oauth]
process_oauth, time_decorator(api_bp.name, metric_queue)]
class ApiException(Exception):

View file

@ -3,8 +3,10 @@
import logging
import json
import datetime
import hashlib
from flask import request
from rfc3987 import parse as uri_parse
from app import app, userfiles as user_files, build_logs, log_archive, dockerfile_build_queue
from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
@ -134,8 +136,11 @@ def build_status_view(build_obj):
}
}
if can_write and build_obj.resource_key is not None:
resp['archive_url'] = user_files.get_file_url(build_obj.resource_key, requires_cors=True)
if can_write:
if build_obj.resource_key is not None:
resp['archive_url'] = user_files.get_file_url(build_obj.resource_key, requires_cors=True)
elif job_config.get('archive_url', None):
resp['archive_url'] = job_config['archive_url']
return resp
@ -148,14 +153,15 @@ class RepositoryBuildList(RepositoryParamResource):
'RepositoryBuildRequest': {
'type': 'object',
'description': 'Description of a new repository build.',
'required': [
'file_id',
],
'properties': {
'file_id': {
'type': 'string',
'description': 'The file id that was generated when the build spec was uploaded',
},
'archive_url': {
'type': 'string',
'description': 'The URL of the .tar.gz to build. Must start with "http" or "https".',
},
'subdirectory': {
'type': 'string',
'description': 'Subdirectory in which the Dockerfile can be found',
@ -204,7 +210,26 @@ class RepositoryBuildList(RepositoryParamResource):
logger.debug('User requested repository initialization.')
request_json = request.get_json()
dockerfile_id = request_json['file_id']
dockerfile_id = request_json.get('file_id', None)
archive_url = request_json.get('archive_url', None)
if not dockerfile_id and not archive_url:
raise InvalidRequest('file_id or archive_url required')
if archive_url:
archive_match = None
try:
archive_match = uri_parse(archive_url, 'URI')
except ValueError:
pass
if not archive_match:
raise InvalidRequest('Invalid Archive URL: Must be a valid URI')
scheme = archive_match.get('scheme', None)
if scheme != 'http' and scheme != 'https':
raise InvalidRequest('Invalid Archive URL: Must be http or https')
subdir = request_json['subdirectory'] if 'subdirectory' in request_json else ''
tags = request_json.get('docker_tags', ['latest'])
pull_robot_name = request_json.get('pull_robot', None)
@ -228,18 +253,24 @@ class RepositoryBuildList(RepositoryParamResource):
# Check if the dockerfile resource has already been used. If so, then it
# can only be reused if the user has access to the repository in which the
# dockerfile was previously built.
associated_repository = model.build.get_repository_for_resource(dockerfile_id)
if associated_repository:
if not ModifyRepositoryPermission(associated_repository.namespace_user.username,
associated_repository.name):
raise Unauthorized()
if dockerfile_id:
associated_repository = model.build.get_repository_for_resource(dockerfile_id)
if associated_repository:
if not ModifyRepositoryPermission(associated_repository.namespace_user.username,
associated_repository.name):
raise Unauthorized()
# Start the build.
repo = model.repository.get_repository(namespace, repository)
build_name = (user_files.get_file_checksum(dockerfile_id)
if dockerfile_id
else hashlib.sha224(archive_url).hexdigest()[0:7])
prepared = PreparedBuild()
prepared.build_name = user_files.get_file_checksum(dockerfile_id)
prepared.build_name = build_name
prepared.dockerfile_id = dockerfile_id
prepared.archive_url = archive_url
prepared.tags = tags
prepared.subdirectory = subdir
prepared.is_manual = True

View file

@ -278,6 +278,46 @@ class OrganizationMemberList(ApiResource):
class OrganizationMember(ApiResource):
""" Resource for managing individual organization members. """
@require_scope(scopes.ORG_ADMIN)
@nickname('getOrganizationMember')
def get(self, orgname, membername):
""" Retrieves the details of a member of the organization.
"""
permission = AdministerOrganizationPermission(orgname)
if permission.can():
# Lookup the user.
member = model.user.get_user(membername)
if not member:
raise NotFound()
organization = model.user.get_user_or_org(orgname)
if not organization:
raise NotFound()
# Lookup the user's information in the organization.
teams = list(model.team.get_user_teams_within_org(membername, organization))
if not teams:
raise NotFound()
repo_permissions = model.permission.list_organization_member_permissions(organization, member)
def local_team_view(team):
return {
'name': team.name,
'avatar': avatar.get_data_for_team(team),
}
return {
'name': member.username,
'kind': 'robot' if member.robot else 'user',
'avatar': avatar.get_data_for_user(member),
'teams': [local_team_view(team) for team in teams],
'repositories': [permission.repository.name for permission in repo_permissions]
}
raise Unauthorized()
@require_scope(scopes.ORG_ADMIN)
@nickname('removeOrganizationMember')
def delete(self, orgname, membername):

View file

@ -26,7 +26,8 @@ def notification_view(note):
'uuid': note.uuid,
'event': note.event.name,
'method': note.method.name,
'config': config
'config': config,
'title': note.title,
}
@ -55,7 +56,11 @@ class RepositoryNotificationList(RepositoryParamResource):
'config': {
'type': 'object',
'description': 'JSON config information for the specific method of notification'
}
},
'title': {
'type': 'string',
'description': 'The human-readable title of the notification',
},
}
},
}
@ -78,7 +83,8 @@ class RepositoryNotificationList(RepositoryParamResource):
raise request_error(message=ex.message)
new_notification = model.notification.create_repo_notification(repo, parsed['event'],
parsed['method'], parsed['config'])
parsed['method'], parsed['config'],
parsed.get('title', None))
resp = notification_view(new_notification)
log_action('add_repo_notification', namespace,

View file

@ -461,6 +461,7 @@ class TriggerBuildList(RepositoryParamResource):
}
FIELD_VALUE_LIMIT = 30
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>/fields/<field_name>')
@internal_only
@ -479,7 +480,7 @@ class BuildTriggerFieldValues(RepositoryParamResource):
user_permission = UserAdminPermission(trigger.connected_user.username)
if user_permission.can():
handler = BuildTriggerHandler.get_handler(trigger, config)
values = handler.list_field_values(field_name)
values = handler.list_field_values(field_name, limit=FIELD_VALUE_LIMIT)
if values is None:
raise NotFound()