2014-03-14 16:11:48 +00:00
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
|
|
|
|
from flask import request, url_for
|
|
|
|
from urllib import quote
|
|
|
|
from urlparse import urlunparse
|
|
|
|
|
|
|
|
from app import app
|
|
|
|
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
|
2014-03-19 16:09:07 +00:00
|
|
|
log_action, request_error, query_param, parse_args, internal_only,
|
2014-03-17 20:57:35 +00:00
|
|
|
validate_json_request, api, Unauthorized, NotFound, InvalidRequest)
|
2014-03-19 19:39:44 +00:00
|
|
|
from endpoints.api.build import (build_status_view, trigger_view, RepositoryBuildStatus,
|
|
|
|
get_trigger_config)
|
2014-03-14 16:11:48 +00:00
|
|
|
from endpoints.common import start_build
|
2014-03-19 00:32:37 +00:00
|
|
|
from endpoints.trigger import (BuildTrigger as BuildTriggerBase, TriggerDeactivationException,
|
2014-03-28 19:32:56 +00:00
|
|
|
TriggerActivationException, EmptyRepositoryException,
|
|
|
|
RepositoryReadException)
|
2014-03-14 16:11:48 +00:00
|
|
|
from data import model
|
2014-03-27 22:33:13 +00:00
|
|
|
from auth.permissions import UserAdminPermission, AdministerOrganizationPermission
|
|
|
|
from util.names import parse_robot_username
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
def _prepare_webhook_url(scheme, username, password, hostname, path):
|
|
|
|
auth_hostname = '%s:%s@%s' % (quote(username), quote(password), hostname)
|
|
|
|
return urlunparse((scheme, auth_hostname, path, '', '', ''))
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/')
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTriggerList(RepositoryParamResource):
|
|
|
|
""" Resource for listing repository build triggers. """
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('listBuildTriggers')
|
|
|
|
def get(self, namespace, repository):
|
|
|
|
""" List the triggers for the specified repository. """
|
|
|
|
triggers = model.list_build_triggers(namespace, repository)
|
|
|
|
return {
|
|
|
|
'triggers': [trigger_view(trigger) for trigger in triggers]
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>')
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTrigger(RepositoryParamResource):
|
|
|
|
""" Resource for managing specific build triggers. """
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('getBuildTrigger')
|
|
|
|
def get(self, namespace, repository, trigger_uuid):
|
|
|
|
""" Get information for the specified build trigger. """
|
|
|
|
try:
|
|
|
|
trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
return trigger_view(trigger)
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('deleteBuildTrigger')
|
|
|
|
def delete(self, namespace, repository, trigger_uuid):
|
|
|
|
""" Delete the specified build trigger. """
|
|
|
|
try:
|
|
|
|
trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 00:32:37 +00:00
|
|
|
handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name)
|
2014-03-19 19:39:44 +00:00
|
|
|
config_dict = get_trigger_config(trigger)
|
2014-03-14 16:11:48 +00:00
|
|
|
if handler.is_active(config_dict):
|
|
|
|
try:
|
|
|
|
handler.deactivate(trigger.auth_token, config_dict)
|
|
|
|
except TriggerDeactivationException as ex:
|
|
|
|
# We are just going to eat this error
|
|
|
|
logger.warning('Trigger deactivation problem: %s', ex)
|
|
|
|
|
|
|
|
log_action('delete_repo_trigger', namespace,
|
|
|
|
{'repo': repository, 'trigger_id': trigger_uuid,
|
|
|
|
'service': trigger.service.name, 'config': config_dict},
|
|
|
|
repo=model.get_repository(namespace, repository))
|
|
|
|
|
|
|
|
trigger.delete_instance(recursive=True)
|
|
|
|
return 'No Content', 204
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>/subdir')
|
2014-03-19 16:09:07 +00:00
|
|
|
@internal_only
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTriggerSubdirs(RepositoryParamResource):
|
|
|
|
""" Custom verb for fetching the subdirs which are buildable for a trigger. """
|
|
|
|
schemas = {
|
|
|
|
'BuildTriggerSubdirRequest': {
|
|
|
|
'id': 'BuildTriggerSubdirRequest',
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Arbitrary json.',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('listBuildTriggerSubdirs')
|
|
|
|
@validate_json_request('BuildTriggerSubdirRequest')
|
|
|
|
def post(self, namespace, repository, trigger_uuid):
|
|
|
|
""" List the subdirectories available for the specified build trigger and source. """
|
|
|
|
try:
|
|
|
|
trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 00:32:37 +00:00
|
|
|
handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name)
|
2014-03-18 23:21:27 +00:00
|
|
|
user_permission = UserAdminPermission(trigger.connected_user.username)
|
2014-03-14 16:11:48 +00:00
|
|
|
if user_permission.can():
|
|
|
|
new_config_dict = request.get_json()
|
|
|
|
|
|
|
|
try:
|
|
|
|
subdirs = handler.list_build_subdirs(trigger.auth_token, new_config_dict)
|
|
|
|
return {
|
|
|
|
'subdir': subdirs,
|
|
|
|
'status': 'success'
|
|
|
|
}
|
|
|
|
except EmptyRepositoryException as exc:
|
2014-03-28 19:32:56 +00:00
|
|
|
return {
|
|
|
|
'status': 'success',
|
|
|
|
'subdir': []
|
|
|
|
}
|
|
|
|
except RepositoryReadException as exc:
|
2014-03-14 16:11:48 +00:00
|
|
|
return {
|
|
|
|
'status': 'error',
|
2014-03-28 19:32:56 +00:00
|
|
|
'message': exc.message
|
2014-03-14 16:11:48 +00:00
|
|
|
}
|
|
|
|
else:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise Unauthorized()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>/activate')
|
2014-03-19 16:09:07 +00:00
|
|
|
@internal_only
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTriggerActivate(RepositoryParamResource):
|
|
|
|
""" Custom verb for activating a build trigger once all required information has been collected.
|
|
|
|
"""
|
|
|
|
schemas = {
|
|
|
|
'BuildTriggerActivateRequest': {
|
|
|
|
'id': 'BuildTriggerActivateRequest',
|
|
|
|
'type': 'object',
|
2014-03-27 22:33:13 +00:00
|
|
|
'required': [
|
|
|
|
'config'
|
|
|
|
],
|
|
|
|
'properties': {
|
|
|
|
'config': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Arbitrary json.',
|
|
|
|
},
|
|
|
|
'pull_robot': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The name of the robot that will be used to pull images.'
|
|
|
|
}
|
|
|
|
}
|
2014-03-14 16:11:48 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('activateBuildTrigger')
|
|
|
|
@validate_json_request('BuildTriggerActivateRequest')
|
|
|
|
def post(self, namespace, repository, trigger_uuid):
|
|
|
|
""" Activate the specified build trigger. """
|
|
|
|
try:
|
|
|
|
trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 00:32:37 +00:00
|
|
|
handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name)
|
2014-03-19 19:39:44 +00:00
|
|
|
existing_config_dict = get_trigger_config(trigger)
|
2014-03-14 16:11:48 +00:00
|
|
|
if handler.is_active(existing_config_dict):
|
2014-03-17 20:57:35 +00:00
|
|
|
raise InvalidRequest('Trigger config is not sufficient for activation.')
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 16:09:07 +00:00
|
|
|
user_permission = UserAdminPermission(trigger.connected_user.username)
|
2014-03-14 16:11:48 +00:00
|
|
|
if user_permission.can():
|
2014-03-27 22:33:13 +00:00
|
|
|
# Update the pull robot (if any).
|
|
|
|
pull_robot_name = request.get_json().get('pull_robot', None)
|
|
|
|
if pull_robot_name:
|
|
|
|
pull_robot = model.lookup_robot(pull_robot_name)
|
|
|
|
if not pull_robot:
|
|
|
|
raise NotFound()
|
|
|
|
|
|
|
|
# Make sure the user has administer permissions for the robot's namespace.
|
|
|
|
(robot_namespace, shortname) = parse_robot_username(pull_robot_name)
|
|
|
|
if not AdministerOrganizationPermission(robot_namespace).can():
|
|
|
|
raise Unauthorized()
|
|
|
|
|
|
|
|
# Make sure the namespace matches that of the trigger.
|
|
|
|
if robot_namespace != namespace:
|
|
|
|
raise Unauthorized()
|
|
|
|
|
|
|
|
# Set the pull robot.
|
|
|
|
trigger.pull_user = pull_robot
|
|
|
|
|
|
|
|
# Update the config.
|
|
|
|
new_config_dict = request.get_json()['config']
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
token_name = 'Build Trigger: %s' % trigger.service.name
|
|
|
|
token = model.create_delegate_token(namespace, repository, token_name,
|
|
|
|
'write')
|
|
|
|
|
|
|
|
try:
|
|
|
|
repository_path = '%s/%s' % (trigger.repository.namespace,
|
|
|
|
trigger.repository.name)
|
|
|
|
path = url_for('webhooks.build_trigger_webhook',
|
|
|
|
repository=repository_path, trigger_uuid=trigger.uuid)
|
|
|
|
authed_url = _prepare_webhook_url(app.config['URL_SCHEME'], '$token',
|
|
|
|
token.code, app.config['URL_HOST'],
|
|
|
|
path)
|
|
|
|
|
|
|
|
final_config = handler.activate(trigger.uuid, authed_url,
|
|
|
|
trigger.auth_token, new_config_dict)
|
|
|
|
except TriggerActivationException as exc:
|
|
|
|
token.delete_instance()
|
2014-03-17 20:57:35 +00:00
|
|
|
raise request_error(message=exc.message)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
# Save the updated config.
|
|
|
|
trigger.config = json.dumps(final_config)
|
|
|
|
trigger.write_token = token
|
|
|
|
trigger.save()
|
|
|
|
|
|
|
|
# Log the trigger setup.
|
|
|
|
repo = model.get_repository(namespace, repository)
|
|
|
|
log_action('setup_repo_trigger', namespace,
|
|
|
|
{'repo': repository, 'namespace': namespace,
|
|
|
|
'trigger_id': trigger.uuid, 'service': trigger.service.name,
|
2014-03-27 22:33:13 +00:00
|
|
|
'pull_user': trigger.pull_user.username if trigger.pull_user else None,
|
2014-03-14 16:11:48 +00:00
|
|
|
'config': final_config}, repo=repo)
|
|
|
|
|
|
|
|
return trigger_view(trigger)
|
|
|
|
else:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise Unauthorized()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>/start')
|
2014-03-14 16:11:48 +00:00
|
|
|
class ActivateBuildTrigger(RepositoryParamResource):
|
|
|
|
""" Custom verb to manually activate a build trigger. """
|
|
|
|
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('manuallyStartBuildTrigger')
|
|
|
|
def post(self, namespace, repository, trigger_uuid):
|
|
|
|
""" Manually start a build from the specified trigger. """
|
|
|
|
try:
|
|
|
|
trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 00:32:37 +00:00
|
|
|
handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name)
|
2014-03-19 19:39:44 +00:00
|
|
|
config_dict = get_trigger_config(trigger)
|
|
|
|
if not handler.is_active(config_dict):
|
2014-03-17 20:57:35 +00:00
|
|
|
raise InvalidRequest('Trigger is not active.')
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
specs = handler.manual_start(trigger.auth_token, config_dict)
|
2014-03-14 16:11:48 +00:00
|
|
|
dockerfile_id, tags, name, subdir = specs
|
|
|
|
|
|
|
|
repo = model.get_repository(namespace, repository)
|
2014-03-27 22:33:13 +00:00
|
|
|
pull_credentials = model.get_pull_credentials(trigger)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-27 22:33:13 +00:00
|
|
|
build_request = start_build(repo, dockerfile_id, tags, name, subdir, True,
|
|
|
|
pull_credentials=pull_credentials)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
resp = build_status_view(build_request, True)
|
|
|
|
repo_string = '%s/%s' % (namespace, repository)
|
|
|
|
headers = {
|
2014-03-17 19:23:49 +00:00
|
|
|
'Location': api.url_for(RepositoryBuildStatus, repository=repo_string,
|
|
|
|
build_uuid=build_request.uuid),
|
2014-03-14 16:11:48 +00:00
|
|
|
}
|
|
|
|
return resp, 201, headers
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>/builds')
|
2014-03-14 16:11:48 +00:00
|
|
|
class TriggerBuildList(RepositoryParamResource):
|
|
|
|
""" Resource to represent builds that were activated from the specified trigger. """
|
2014-03-18 18:45:14 +00:00
|
|
|
@require_repo_admin
|
2014-03-14 16:11:48 +00:00
|
|
|
@parse_args
|
|
|
|
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
|
|
|
|
@nickname('listTriggerRecentBuilds')
|
|
|
|
def get(self, args, namespace, repository, trigger_uuid):
|
|
|
|
""" List the builds started by the specified trigger. """
|
|
|
|
limit = args['limit']
|
|
|
|
builds = list(model.list_trigger_builds(namespace, repository,
|
|
|
|
trigger_uuid, limit))
|
|
|
|
return {
|
|
|
|
'builds': [build_status_view(build, True) for build in builds]
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-03-19 19:39:44 +00:00
|
|
|
@resource('/v1/repository/<repopath:repository>/trigger/<trigger_uuid>/sources')
|
2014-03-19 16:09:07 +00:00
|
|
|
@internal_only
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTriggerSources(RepositoryParamResource):
|
|
|
|
""" Custom verb to fetch the list of build sources for the trigger config. """
|
|
|
|
@require_repo_admin
|
|
|
|
@nickname('listTriggerBuildSources')
|
|
|
|
def get(self, namespace, repository, trigger_uuid):
|
|
|
|
""" List the build sources for the trigger configuration thus far. """
|
|
|
|
try:
|
|
|
|
trigger = model.get_build_trigger(namespace, repository, trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-03-19 16:09:07 +00:00
|
|
|
user_permission = UserAdminPermission(trigger.connected_user.username)
|
2014-03-14 16:11:48 +00:00
|
|
|
if user_permission.can():
|
2014-03-19 00:32:37 +00:00
|
|
|
trigger_handler = BuildTriggerBase.get_trigger_for_service(trigger.service.name)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
'sources': trigger_handler.list_build_sources(trigger.auth_token)
|
|
|
|
}
|
|
|
|
else:
|
2014-03-19 00:32:37 +00:00
|
|
|
raise Unauthorized()
|