2015-05-14 20:47:38 +00:00
|
|
|
""" Create, list and manage build triggers. """
|
|
|
|
|
2014-03-14 16:11:48 +00:00
|
|
|
import json
|
|
|
|
import logging
|
2017-03-21 21:24:11 +00:00
|
|
|
from os import path
|
2014-03-14 16:11:48 +00:00
|
|
|
from urllib import quote
|
|
|
|
from urlparse import urlunparse
|
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
from flask import request, url_for
|
|
|
|
|
2014-03-14 16:11:48 +00:00
|
|
|
from app import app
|
2017-03-21 21:24:11 +00:00
|
|
|
from auth.permissions import (UserAdminPermission, AdministerOrganizationPermission,
|
|
|
|
ReadRepositoryPermission, AdministerRepositoryPermission)
|
2015-09-11 21:40:32 +00:00
|
|
|
from buildtrigger.basehandler import BuildTriggerHandler
|
|
|
|
from buildtrigger.triggerutil import (TriggerDeactivationException,
|
|
|
|
TriggerActivationException, EmptyRepositoryException,
|
|
|
|
RepositoryReadException, TriggerStartException)
|
2017-03-21 21:24:11 +00:00
|
|
|
from data import model
|
|
|
|
from data.model.build import update_build_trigger
|
2014-03-14 16:11:48 +00:00
|
|
|
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,
|
2017-03-22 18:30:13 +00:00
|
|
|
validate_json_request, api, path_param, abort,
|
2017-04-17 02:40:59 +00:00
|
|
|
disallow_for_app_repositories, disallow_under_trust)
|
2015-07-15 21:25:41 +00:00
|
|
|
from endpoints.api.build import build_status_view, trigger_view, RepositoryBuildStatus
|
2016-12-05 21:07:00 +00:00
|
|
|
from endpoints.building import start_build, MaximumBuildsQueuedException
|
2017-03-21 21:24:11 +00:00
|
|
|
from endpoints.exception import NotFound, Unauthorized, InvalidRequest
|
2014-04-03 03:33:58 +00:00
|
|
|
from util.dockerfileparse import parse_dockerfile
|
2017-03-21 21:24:11 +00:00
|
|
|
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, '', '', ''))
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTriggerList(RepositoryParamResource):
|
|
|
|
""" Resource for listing repository build triggers. """
|
|
|
|
|
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('listBuildTriggers')
|
2016-03-09 21:20:28 +00:00
|
|
|
def get(self, namespace_name, repo_name):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" List the triggers for the specified repository. """
|
2016-03-09 21:20:28 +00:00
|
|
|
triggers = model.build.list_build_triggers(namespace_name, repo_name)
|
2014-03-14 16:11:48 +00:00
|
|
|
return {
|
2015-04-22 18:30:06 +00:00
|
|
|
'triggers': [trigger_view(trigger, can_admin=True) for trigger in triggers]
|
2014-03-14 16:11:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
2014-03-14 16:11:48 +00:00
|
|
|
class BuildTrigger(RepositoryParamResource):
|
|
|
|
""" Resource for managing specific build triggers. """
|
|
|
|
|
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('getBuildTrigger')
|
2016-03-09 21:20:28 +00:00
|
|
|
def get(self, namespace_name, repo_name, trigger_uuid):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" Get information for the specified build trigger. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-03-14 16:11:48 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-22 18:30:06 +00:00
|
|
|
return trigger_view(trigger, can_admin=True)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('deleteBuildTrigger')
|
2016-03-09 21:20:28 +00:00
|
|
|
def delete(self, namespace_name, repo_name, trigger_uuid):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" Delete the specified build trigger. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-03-14 16:11:48 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger)
|
|
|
|
if handler.is_active():
|
2014-03-14 16:11:48 +00:00
|
|
|
try:
|
2015-04-24 22:36:48 +00:00
|
|
|
handler.deactivate()
|
2014-03-14 16:11:48 +00:00
|
|
|
except TriggerDeactivationException as ex:
|
|
|
|
# We are just going to eat this error
|
|
|
|
logger.warning('Trigger deactivation problem: %s', ex)
|
|
|
|
|
2016-03-09 21:20:28 +00:00
|
|
|
log_action('delete_repo_trigger', namespace_name,
|
|
|
|
{'repo': repo_name, 'trigger_id': trigger_uuid,
|
2015-04-24 22:36:48 +00:00
|
|
|
'service': trigger.service.name},
|
2016-03-09 21:20:28 +00:00
|
|
|
repo=model.repository.get_repository(namespace_name, repo_name))
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-07-25 17:46:22 +00:00
|
|
|
trigger.delete_instance(recursive=True)
|
|
|
|
|
2014-07-18 19:05:39 +00:00
|
|
|
if trigger.write_token is not None:
|
|
|
|
trigger.write_token.delete_instance()
|
|
|
|
|
2014-03-14 16:11:48 +00:00
|
|
|
return 'No Content', 204
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/subdir')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
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': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Arbitrary json.',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('listBuildTriggerSubdirs')
|
|
|
|
@validate_json_request('BuildTriggerSubdirRequest')
|
2016-03-09 21:20:28 +00:00
|
|
|
def post(self, namespace_name, repo_name, trigger_uuid):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" List the subdirectories available for the specified build trigger and source. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-03-14 16:11:48 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
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()
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
try:
|
2015-04-24 22:36:48 +00:00
|
|
|
subdirs = handler.list_build_subdirs()
|
2017-03-21 21:24:11 +00:00
|
|
|
context_map = {}
|
|
|
|
for file in subdirs:
|
|
|
|
context_map = handler.get_parent_directory_mappings(file, context_map)
|
|
|
|
|
2014-03-14 16:11:48 +00:00
|
|
|
return {
|
2017-03-21 21:24:11 +00:00
|
|
|
'dockerfile_paths': ['/' + subdir for subdir in subdirs],
|
|
|
|
'contextMap': context_map,
|
|
|
|
'status': 'success',
|
2014-03-14 16:11:48 +00:00
|
|
|
}
|
|
|
|
except EmptyRepositoryException as exc:
|
2014-03-28 19:32:56 +00:00
|
|
|
return {
|
|
|
|
'status': 'success',
|
2017-03-21 21:24:11 +00:00
|
|
|
'contextMap': {},
|
|
|
|
'dockerfile_paths': [],
|
2014-03-28 19:32:56 +00:00
|
|
|
}
|
|
|
|
except RepositoryReadException as exc:
|
2014-03-14 16:11:48 +00:00
|
|
|
return {
|
|
|
|
'status': 'error',
|
2017-03-21 21:24:11 +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
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/activate')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
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': {
|
|
|
|
'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
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('activateBuildTrigger')
|
|
|
|
@validate_json_request('BuildTriggerActivateRequest')
|
2016-03-09 21:20:28 +00:00
|
|
|
def post(self, namespace_name, repo_name, trigger_uuid):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" Activate the specified build trigger. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-03-14 16:11:48 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger)
|
|
|
|
if handler.is_active():
|
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:
|
2015-07-15 21:25:41 +00:00
|
|
|
try:
|
|
|
|
pull_robot = model.user.lookup_robot(pull_robot_name)
|
|
|
|
except model.InvalidRobotException:
|
2014-03-27 22:33:13 +00:00
|
|
|
raise NotFound()
|
|
|
|
|
|
|
|
# Make sure the user has administer permissions for the robot's namespace.
|
2016-09-09 21:21:14 +00:00
|
|
|
(robot_namespace, _) = parse_robot_username(pull_robot_name)
|
2014-03-27 22:33:13 +00:00
|
|
|
if not AdministerOrganizationPermission(robot_namespace).can():
|
|
|
|
raise Unauthorized()
|
|
|
|
|
|
|
|
# Make sure the namespace matches that of the trigger.
|
2016-03-09 21:20:28 +00:00
|
|
|
if robot_namespace != namespace_name:
|
2014-11-24 21:07:38 +00:00
|
|
|
raise Unauthorized()
|
2014-03-27 22:33:13 +00:00
|
|
|
|
|
|
|
# Set the pull robot.
|
2014-04-02 01:49:06 +00:00
|
|
|
trigger.pull_robot = pull_robot
|
2014-03-27 22:33:13 +00:00
|
|
|
|
|
|
|
# Update the config.
|
|
|
|
new_config_dict = request.get_json()['config']
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-16 16:06:05 +00:00
|
|
|
write_token_name = 'Build Trigger: %s' % trigger.service.name
|
2016-03-09 21:20:28 +00:00
|
|
|
write_token = model.token.create_delegate_token(namespace_name, repo_name, write_token_name,
|
2015-07-15 21:25:41 +00:00
|
|
|
'write')
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
try:
|
2014-11-18 15:29:59 +00:00
|
|
|
path = url_for('webhooks.build_trigger_webhook', trigger_uuid=trigger.uuid)
|
2015-04-16 16:06:05 +00:00
|
|
|
authed_url = _prepare_webhook_url(app.config['PREFERRED_URL_SCHEME'],
|
|
|
|
'$token', write_token.code,
|
2014-04-11 15:17:45 +00:00
|
|
|
app.config['SERVER_HOSTNAME'], path)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)
|
|
|
|
final_config, private_config = handler.activate(authed_url)
|
2015-04-23 22:14:26 +00:00
|
|
|
|
|
|
|
if 'private_key' in private_config:
|
|
|
|
trigger.private_key = private_config['private_key']
|
|
|
|
|
2014-03-14 16:11:48 +00:00
|
|
|
except TriggerActivationException as exc:
|
2015-04-16 16:06:05 +00:00
|
|
|
write_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.
|
2017-03-21 21:24:11 +00:00
|
|
|
update_build_trigger(trigger, final_config, write_token=write_token)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
# Log the trigger setup.
|
2016-03-09 21:20:28 +00:00
|
|
|
repo = model.repository.get_repository(namespace_name, repo_name)
|
|
|
|
log_action('setup_repo_trigger', namespace_name,
|
|
|
|
{'repo': repo_name, 'namespace': namespace_name,
|
|
|
|
'trigger_id': trigger.uuid, 'service': trigger.service.name,
|
|
|
|
'pull_robot': trigger.pull_robot.username if trigger.pull_robot else None,
|
|
|
|
'config': final_config},
|
|
|
|
repo=repo)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-22 18:30:06 +00:00
|
|
|
return trigger_view(trigger, can_admin=True)
|
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
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/analyze')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
2014-04-03 03:33:58 +00:00
|
|
|
@internal_only
|
|
|
|
class BuildTriggerAnalyze(RepositoryParamResource):
|
|
|
|
""" Custom verb for analyzing the config for a build trigger and suggesting various changes
|
|
|
|
(such as a robot account to use for pulling)
|
|
|
|
"""
|
|
|
|
schemas = {
|
|
|
|
'BuildTriggerAnalyzeRequest': {
|
|
|
|
'type': 'object',
|
|
|
|
'required': [
|
|
|
|
'config'
|
|
|
|
],
|
|
|
|
'properties': {
|
|
|
|
'config': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Arbitrary json.',
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-04-03 03:33:58 +00:00
|
|
|
@nickname('analyzeBuildTrigger')
|
|
|
|
@validate_json_request('BuildTriggerAnalyzeRequest')
|
2016-03-09 21:20:28 +00:00
|
|
|
def post(self, namespace_name, repo_name, trigger_uuid):
|
2014-04-03 03:33:58 +00:00
|
|
|
""" Analyze the specified build trigger configuration. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-04-03 03:33:58 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
|
|
|
raise NotFound()
|
|
|
|
|
2017-03-17 18:42:32 +00:00
|
|
|
if trigger.repository.namespace_user.username != namespace_name:
|
|
|
|
raise NotFound()
|
|
|
|
|
|
|
|
if trigger.repository.name != repo_name:
|
|
|
|
raise NotFound()
|
|
|
|
|
2014-04-03 03:33:58 +00:00
|
|
|
new_config_dict = request.get_json()['config']
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger, new_config_dict)
|
2014-04-03 03:33:58 +00:00
|
|
|
|
2017-03-17 18:42:32 +00:00
|
|
|
def analyze_view(image_namespace, image_repository, status, message=None):
|
|
|
|
# Retrieve the list of robots and mark whether they have read access already.
|
|
|
|
robots = []
|
|
|
|
if AdministerOrganizationPermission(image_namespace).can():
|
|
|
|
if image_repository is not None:
|
|
|
|
perm_query = model.user.get_all_repo_users_transitive(image_namespace, image_repository)
|
|
|
|
user_ids_with_permission = set([user.id for user in perm_query])
|
|
|
|
else:
|
|
|
|
user_ids_with_permission = set()
|
|
|
|
|
|
|
|
def robot_view(robot):
|
|
|
|
return {
|
|
|
|
'name': robot.username,
|
|
|
|
'kind': 'user',
|
|
|
|
'is_robot': True,
|
|
|
|
'can_read': robot.id in user_ids_with_permission,
|
|
|
|
}
|
|
|
|
|
|
|
|
robots = [robot_view(robot) for robot in model.user.list_namespace_robots(image_namespace)]
|
|
|
|
|
|
|
|
return {
|
|
|
|
'namespace': image_namespace,
|
|
|
|
'name': image_repository,
|
|
|
|
'robots': robots,
|
|
|
|
'status': status,
|
|
|
|
'message': message,
|
|
|
|
'is_admin': AdministerOrganizationPermission(image_namespace).can(),
|
|
|
|
}
|
|
|
|
|
2014-04-03 03:33:58 +00:00
|
|
|
try:
|
2017-05-08 21:18:03 +00:00
|
|
|
# Default to the current namespace.
|
|
|
|
base_namespace = namespace_name
|
|
|
|
base_repository = None
|
|
|
|
|
2014-04-03 03:33:58 +00:00
|
|
|
# Load the contents of the Dockerfile.
|
2015-04-24 22:36:48 +00:00
|
|
|
contents = handler.load_dockerfile_contents()
|
2014-04-03 03:33:58 +00:00
|
|
|
if not contents:
|
2017-05-08 21:18:03 +00:00
|
|
|
return analyze_view(base_namespace, base_repository, 'warning',
|
|
|
|
message='Specified Dockerfile path for the trigger was not found on the main ' +
|
|
|
|
'branch. This trigger may fail.')
|
2014-04-03 03:33:58 +00:00
|
|
|
|
|
|
|
# Parse the contents of the Dockerfile.
|
|
|
|
parsed = parse_dockerfile(contents)
|
|
|
|
if not parsed:
|
2017-05-08 21:18:03 +00:00
|
|
|
return analyze_view(base_namespace, base_repository, 'error',
|
|
|
|
message='Could not parse the Dockerfile specified')
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2017-03-21 21:24:11 +00:00
|
|
|
# Check whether the dockerfile_path is correct
|
|
|
|
if new_config_dict.get('context'):
|
|
|
|
if not is_parent(new_config_dict.get('context'), new_config_dict.get('dockerfile_path')):
|
2017-05-08 21:18:03 +00:00
|
|
|
return analyze_view(base_namespace, base_repository, 'error',
|
|
|
|
message='Dockerfile, %s, is not child of the context, %s.' %
|
|
|
|
(new_config_dict.get('context'), new_config_dict.get('dockerfile_path')))
|
2017-03-17 18:42:32 +00:00
|
|
|
|
2014-04-03 03:33:58 +00:00
|
|
|
# Determine the base image (i.e. the FROM) for the Dockerfile.
|
|
|
|
base_image = parsed.get_base_image()
|
|
|
|
if not base_image:
|
2017-03-17 18:42:32 +00:00
|
|
|
return analyze_view(base_namespace, base_repository, 'warning',
|
|
|
|
message='No FROM line found in the Dockerfile')
|
2014-04-03 03:33:58 +00:00
|
|
|
|
|
|
|
# Check to see if the base image lives in Quay.
|
2014-04-14 23:37:22 +00:00
|
|
|
quay_registry_prefix = '%s/' % (app.config['SERVER_HOSTNAME'])
|
2014-04-03 03:33:58 +00:00
|
|
|
if not base_image.startswith(quay_registry_prefix):
|
2017-03-17 18:42:32 +00:00
|
|
|
return analyze_view(base_namespace, base_repository, 'publicbase')
|
2014-04-03 03:33:58 +00:00
|
|
|
|
|
|
|
# Lookup the repository in Quay.
|
2017-03-17 18:42:32 +00:00
|
|
|
result = str(base_image)[len(quay_registry_prefix):].split('/', 2)
|
2014-04-03 03:33:58 +00:00
|
|
|
if len(result) != 2:
|
2017-03-17 18:42:32 +00:00
|
|
|
msg = '"%s" is not a valid Quay repository path' % (base_image)
|
|
|
|
return analyze_view(base_namespace, base_repository, 'warning', message=msg)
|
2014-04-03 03:33:58 +00:00
|
|
|
|
|
|
|
(base_namespace, base_repository) = result
|
2015-07-15 21:25:41 +00:00
|
|
|
found_repository = model.repository.get_repository(base_namespace, base_repository)
|
2014-04-03 03:33:58 +00:00
|
|
|
if not found_repository:
|
|
|
|
return {
|
|
|
|
'status': 'error',
|
2014-10-14 19:46:35 +00:00
|
|
|
'message': 'Repository "%s" referenced by the Dockerfile was not found' % (base_image)
|
2014-04-03 03:33:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# If the repository is private and the user cannot see that repo, then
|
|
|
|
# mark it as not found.
|
|
|
|
can_read = ReadRepositoryPermission(base_namespace, base_repository)
|
|
|
|
if found_repository.visibility.name != 'public' and not can_read:
|
|
|
|
return {
|
|
|
|
'status': 'error',
|
2014-10-14 19:46:35 +00:00
|
|
|
'message': 'Repository "%s" referenced by the Dockerfile was not found' % (base_image)
|
2014-04-03 03:33:58 +00:00
|
|
|
}
|
|
|
|
|
2016-09-27 14:52:34 +00:00
|
|
|
if found_repository.visibility.name == 'public':
|
2017-03-17 18:42:32 +00:00
|
|
|
return analyze_view(base_namespace, base_repository, 'publicbase')
|
|
|
|
else:
|
|
|
|
return analyze_view(base_namespace, base_repository, 'requiresrobot')
|
2014-04-03 03:33:58 +00:00
|
|
|
|
|
|
|
except RepositoryReadException as rre:
|
|
|
|
return {
|
|
|
|
'status': 'error',
|
2016-09-27 14:52:34 +00:00
|
|
|
'message': 'Could not analyze the repository: %s' % rre.message,
|
2014-04-03 03:33:58 +00:00
|
|
|
}
|
2015-04-06 18:53:54 +00:00
|
|
|
except NotImplementedError:
|
|
|
|
return {
|
|
|
|
'status': 'notimplemented',
|
|
|
|
}
|
2014-04-03 03:33:58 +00:00
|
|
|
|
|
|
|
raise NotFound()
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2014-04-03 03:33:58 +00:00
|
|
|
|
2017-03-21 21:24:11 +00:00
|
|
|
def is_parent(context, dockerfile_path):
|
|
|
|
""" This checks whether the context is a parent of the dockerfile_path"""
|
|
|
|
if context == "" or dockerfile_path == "":
|
|
|
|
return False
|
|
|
|
|
|
|
|
normalized_context = path.normpath(context)
|
|
|
|
if normalized_context[len(normalized_context) - 1] != path.sep:
|
|
|
|
normalized_context += path.sep
|
|
|
|
|
|
|
|
if normalized_context[0] != path.sep:
|
|
|
|
normalized_context = path.sep + normalized_context
|
|
|
|
|
|
|
|
normalized_subdir = path.normpath(path.dirname(dockerfile_path))
|
|
|
|
if normalized_subdir[0] != path.sep:
|
|
|
|
normalized_subdir = path.sep + normalized_subdir
|
|
|
|
|
|
|
|
if normalized_subdir[len(normalized_subdir) - 1] != path.sep:
|
|
|
|
normalized_subdir += path.sep
|
|
|
|
|
|
|
|
return normalized_subdir.startswith(normalized_context)
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/start')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
2014-03-14 16:11:48 +00:00
|
|
|
class ActivateBuildTrigger(RepositoryParamResource):
|
|
|
|
""" Custom verb to manually activate a build trigger. """
|
2014-09-30 20:29:32 +00:00
|
|
|
schemas = {
|
|
|
|
'RunParameters': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Optional run parameters for activating the build trigger',
|
|
|
|
'properties': {
|
|
|
|
'branch_name': {
|
|
|
|
'type': 'string',
|
2015-07-29 22:25:44 +00:00
|
|
|
'description': '(SCM only) If specified, the name of the branch to build.'
|
2015-04-03 21:10:57 +00:00
|
|
|
},
|
|
|
|
'commit_sha': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': '(Custom Only) If specified, the ref/SHA1 used to checkout a git repository.'
|
2015-08-07 17:01:49 +00:00
|
|
|
},
|
|
|
|
'refs': {
|
2017-03-21 21:24:11 +00:00
|
|
|
'type': ['object', 'null'],
|
2015-08-11 21:17:18 +00:00
|
|
|
'description': '(SCM Only) If specified, the ref to build.'
|
2014-09-30 20:29:32 +00:00
|
|
|
}
|
2015-07-29 22:25:44 +00:00
|
|
|
},
|
|
|
|
'additionalProperties': False
|
2014-09-30 20:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
2014-03-14 16:11:48 +00:00
|
|
|
|
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('manuallyStartBuildTrigger')
|
2014-09-30 20:29:32 +00:00
|
|
|
@validate_json_request('RunParameters')
|
2016-03-09 21:20:28 +00:00
|
|
|
def post(self, namespace_name, repo_name, trigger_uuid):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" Manually start a build from the specified trigger. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-03-14 16:11:48 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
2014-03-17 20:57:35 +00:00
|
|
|
raise NotFound()
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger)
|
|
|
|
if not handler.is_active():
|
2014-03-17 20:57:35 +00:00
|
|
|
raise InvalidRequest('Trigger is not active.')
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2014-09-30 20:29:32 +00:00
|
|
|
try:
|
2016-03-09 21:20:28 +00:00
|
|
|
repo = model.repository.get_repository(namespace_name, repo_name)
|
2015-07-15 21:25:41 +00:00
|
|
|
pull_robot_name = model.build.get_pull_robot_name(trigger)
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-29 21:04:52 +00:00
|
|
|
run_parameters = request.get_json()
|
|
|
|
prepared = handler.manual_start(run_parameters=run_parameters)
|
|
|
|
build_request = start_build(repo, prepared, pull_robot_name=pull_robot_name)
|
2014-09-30 20:29:32 +00:00
|
|
|
except TriggerStartException as tse:
|
|
|
|
raise InvalidRequest(tse.message)
|
2016-12-05 21:07:00 +00:00
|
|
|
except MaximumBuildsQueuedException:
|
|
|
|
abort(429, message='Maximum queued build rate exceeded.')
|
2014-03-14 16:11:48 +00:00
|
|
|
|
2015-04-30 19:33:19 +00:00
|
|
|
resp = build_status_view(build_request)
|
2016-03-09 21:20:28 +00:00
|
|
|
repo_string = '%s/%s' % (namespace_name, repo_name)
|
2014-03-14 16:11:48 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/builds')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
2014-03-14 16:11:48 +00:00
|
|
|
class TriggerBuildList(RepositoryParamResource):
|
|
|
|
""" Resource to represent builds that were activated from the specified trigger. """
|
2017-03-21 21:24:11 +00:00
|
|
|
|
2014-03-18 18:45:14 +00:00
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2016-01-26 21:27:36 +00:00
|
|
|
@parse_args()
|
2014-03-14 16:11:48 +00:00
|
|
|
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
|
|
|
|
@nickname('listTriggerRecentBuilds')
|
2016-03-09 21:20:28 +00:00
|
|
|
def get(self, namespace_name, repo_name, trigger_uuid, parsed_args):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" List the builds started by the specified trigger. """
|
2016-01-26 21:27:36 +00:00
|
|
|
limit = parsed_args['limit']
|
2016-03-09 21:20:28 +00:00
|
|
|
builds = model.build.list_trigger_builds(namespace_name, repo_name, trigger_uuid, limit)
|
2014-03-14 16:11:48 +00:00
|
|
|
return {
|
2015-07-15 21:25:41 +00:00
|
|
|
'builds': [build_status_view(bld) for bld in builds]
|
2014-03-14 16:11:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-08-19 21:49:37 +00:00
|
|
|
FIELD_VALUE_LIMIT = 30
|
2014-09-30 20:29:32 +00:00
|
|
|
|
2017-03-21 21:24:11 +00:00
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/fields/<field_name>')
|
2014-09-30 20:29:32 +00:00
|
|
|
@internal_only
|
|
|
|
class BuildTriggerFieldValues(RepositoryParamResource):
|
|
|
|
""" Custom verb to fetch a values list for a particular field name. """
|
2017-03-21 21:24:11 +00:00
|
|
|
|
2014-09-30 20:29:32 +00:00
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-09-30 20:29:32 +00:00
|
|
|
@nickname('listTriggerFieldValues')
|
2016-03-09 21:20:28 +00:00
|
|
|
def post(self, namespace_name, repo_name, trigger_uuid, field_name):
|
2014-09-30 20:29:32 +00:00
|
|
|
""" List the field values for a custom run field. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-09-30 20:29:32 +00:00
|
|
|
except model.InvalidBuildTriggerException:
|
|
|
|
raise NotFound()
|
|
|
|
|
2015-04-24 22:36:48 +00:00
|
|
|
config = request.get_json() or None
|
2016-09-09 21:21:14 +00:00
|
|
|
if AdministerRepositoryPermission(namespace_name, repo_name).can():
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger, config)
|
2015-08-19 21:49:37 +00:00
|
|
|
values = handler.list_field_values(field_name, limit=FIELD_VALUE_LIMIT)
|
2014-09-30 20:29:32 +00:00
|
|
|
|
|
|
|
if values is None:
|
|
|
|
raise NotFound()
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2014-09-30 20:29:32 +00:00
|
|
|
return {
|
|
|
|
'values': values
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
raise Unauthorized()
|
|
|
|
|
|
|
|
|
2016-01-21 20:40:51 +00:00
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/sources')
|
2014-08-19 23:05:28 +00:00
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
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. """
|
2016-09-27 14:52:34 +00:00
|
|
|
schemas = {
|
|
|
|
'BuildTriggerSourcesRequest': {
|
|
|
|
'type': 'object',
|
|
|
|
'description': 'Specifies the namespace under which to fetch sources',
|
|
|
|
'properties': {
|
|
|
|
'namespace': {
|
|
|
|
'type': 'string',
|
|
|
|
'description': 'The namespace for which to fetch sources'
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-03-14 16:11:48 +00:00
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2014-03-14 16:11:48 +00:00
|
|
|
@nickname('listTriggerBuildSources')
|
2016-09-27 14:52:34 +00:00
|
|
|
@validate_json_request('BuildTriggerSourcesRequest')
|
|
|
|
def post(self, namespace_name, repo_name, trigger_uuid):
|
|
|
|
""" List the build sources for the trigger configuration thus far. """
|
|
|
|
namespace = request.get_json()['namespace']
|
|
|
|
|
|
|
|
try:
|
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
|
|
|
except model.InvalidBuildTriggerException:
|
|
|
|
raise NotFound()
|
|
|
|
|
|
|
|
user_permission = UserAdminPermission(trigger.connected_user.username)
|
|
|
|
if user_permission.can():
|
|
|
|
handler = BuildTriggerHandler.get_handler(trigger)
|
|
|
|
|
|
|
|
try:
|
|
|
|
return {
|
|
|
|
'sources': handler.list_build_sources_for_namespace(namespace)
|
|
|
|
}
|
|
|
|
except RepositoryReadException as rre:
|
|
|
|
raise InvalidRequest(rre.message)
|
|
|
|
else:
|
|
|
|
raise Unauthorized()
|
|
|
|
|
|
|
|
|
|
|
|
@resource('/v1/repository/<apirepopath:repository>/trigger/<trigger_uuid>/namespaces')
|
|
|
|
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
|
|
|
@path_param('trigger_uuid', 'The UUID of the build trigger')
|
|
|
|
@internal_only
|
|
|
|
class BuildTriggerSourceNamespaces(RepositoryParamResource):
|
|
|
|
""" Custom verb to fetch the list of namespaces (orgs, projects, etc) for the trigger config. """
|
2017-03-21 21:24:11 +00:00
|
|
|
|
2016-09-27 14:52:34 +00:00
|
|
|
@require_repo_admin
|
2017-03-22 18:30:13 +00:00
|
|
|
@disallow_for_app_repositories
|
2017-04-17 02:40:59 +00:00
|
|
|
@disallow_under_trust
|
2016-09-27 14:52:34 +00:00
|
|
|
@nickname('listTriggerBuildSourceNamespaces')
|
2016-03-09 21:20:28 +00:00
|
|
|
def get(self, namespace_name, repo_name, trigger_uuid):
|
2014-03-14 16:11:48 +00:00
|
|
|
""" List the build sources for the trigger configuration thus far. """
|
|
|
|
try:
|
2015-07-15 21:25:41 +00:00
|
|
|
trigger = model.build.get_build_trigger(trigger_uuid)
|
2014-03-14 16:11:48 +00:00
|
|
|
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():
|
2015-04-24 22:36:48 +00:00
|
|
|
handler = BuildTriggerHandler.get_handler(trigger)
|
2014-11-24 21:07:38 +00:00
|
|
|
|
2015-04-24 22:36:48 +00:00
|
|
|
try:
|
|
|
|
return {
|
2016-09-27 14:52:34 +00:00
|
|
|
'namespaces': handler.list_build_source_namespaces()
|
2015-04-24 22:36:48 +00:00
|
|
|
}
|
|
|
|
except RepositoryReadException as rre:
|
|
|
|
raise InvalidRequest(rre.message)
|
2014-03-14 16:11:48 +00:00
|
|
|
else:
|
2014-03-19 00:32:37 +00:00
|
|
|
raise Unauthorized()
|