import logging import json from flask import request from app import app from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource, require_repo_read, require_repo_write, validate_json_request, ApiResource, internal_only, format_date, api, Unauthorized, NotFound) from endpoints.common import start_build from endpoints.trigger import BuildTrigger from data import model from auth.permissions import ModifyRepositoryPermission logger = logging.getLogger(__name__) user_files = app.config['USERFILES'] build_logs = app.config['BUILDLOGS'] def get_trigger_config(trigger): try: return json.loads(trigger.config) except: return {} def get_job_config(build_obj): try: return json.loads(build_obj.job_config) except: return None def trigger_view(trigger): if trigger and trigger.uuid: config_dict = get_trigger_config(trigger) build_trigger = BuildTrigger.get_trigger_for_service(trigger.service.name) return { 'service': trigger.service.name, 'config': config_dict, 'id': trigger.uuid, 'connected_user': trigger.connected_user.username, 'is_active': build_trigger.is_active(config_dict) } return None def build_status_view(build_obj, can_write=False): status = build_logs.get_status(build_obj.uuid) logger.debug('Can write: %s job_config: %s', can_write, build_obj.job_config) resp = { 'id': build_obj.uuid, 'phase': build_obj.phase, 'started': format_date(build_obj.started), 'display_name': build_obj.display_name, 'status': status, 'job_config': get_job_config(build_obj) if can_write else None, 'is_writer': can_write, 'trigger': trigger_view(build_obj.trigger), 'resource_key': build_obj.resource_key, } if can_write: resp['archive_url'] = user_files.get_file_url(build_obj.resource_key) return resp @resource('/v1/repository//build/') class RepositoryBuildList(RepositoryParamResource): """ Resource related to creating and listing repository builds. """ schemas = { 'RepositoryBuildRequest': { 'id': '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', }, 'subdirectory': { 'type': 'string', 'description': 'Subdirectory in which the Dockerfile can be found', }, }, }, } @require_repo_read @parse_args @query_param('limit', 'The maximum number of builds to return', type=int, default=5) @nickname('getRepoBuilds') def get(self, args, namespace, repository): """ Get the list of repository builds. """ limit = args['limit'] builds = list(model.list_repository_builds(namespace, repository, limit)) can_write = ModifyRepositoryPermission(namespace, repository).can() return { 'builds': [build_status_view(build, can_write) for build in builds] } @require_repo_write @nickname('requestRepoBuild') @validate_json_request('RepositoryBuildRequest') def post(self, namespace, repository): """ Request that a repository be built and pushed from the specified input. """ logger.debug('User requested repository initialization.') request_json = request.get_json() dockerfile_id = request_json['file_id'] subdir = request_json['subdirectory'] if 'subdirectory' in request_json else '' # 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 for which it # was used. associated_repository = model.get_repository_for_resource(dockerfile_id) if associated_repository: if not ModifyRepositoryPermission(associated_repository.namespace, associated_repository.name): raise Unauthorized() # Start the build. repo = model.get_repository(namespace, repository) display_name = user_files.get_file_checksum(dockerfile_id) build_request = start_build(repo, dockerfile_id, ['latest'], display_name, subdir, True) resp = build_status_view(build_request, True) repo_string = '%s/%s' % (namespace, repository) headers = { 'Location': api.url_for(RepositoryBuildStatus, repository=repo_string, build_uuid=build_request.uuid), } return resp, 201, headers @resource('/v1/repository//build//status') class RepositoryBuildStatus(RepositoryParamResource): """ Resource for dealing with repository build status. """ @require_repo_read @nickname('getRepoBuildStatus') def get(self, namespace, repository, build_uuid): """ Return the status for the builds specified by the build uuids. """ build = model.get_repository_build(namespace, repository, build_uuid) if not build: raise NotFound() can_write = ModifyRepositoryPermission(namespace, repository).can() return build_status_view(build, can_write) @resource('/v1/repository//build//logs') class RepositoryBuildLogs(RepositoryParamResource): """ Resource for loading repository build logs. """ @require_repo_write @nickname('getRepoBuildLogs') def get(self, namespace, repository, build_uuid): """ Return the build logs for the build specified by the build uuid. """ response_obj = {} build = model.get_repository_build(namespace, repository, build_uuid) start = int(request.args.get('start', 0)) count, logs = build_logs.get_log_entries(build.uuid, start) response_obj.update({ 'start': start, 'total': count, 'logs': [log for log in logs], }) return response_obj @resource('/v1/filedrop/') @internal_only class FileDropResource(ApiResource): """ Custom verb for setting up a client side file transfer. """ schemas = { 'FileDropRequest': { 'id': 'FileDropRequest', 'type': 'object', 'description': 'Description of the file that the user wishes to upload.', 'required': [ 'mimeType', ], 'properties': { 'mimeType': { 'type': 'string', 'description': 'Type of the file which is about to be uploaded', }, }, }, } @nickname('getFiledropUrl') @validate_json_request('FileDropRequest') def post(self): """ Request a URL to which a file may be uploaded. """ mime_type = request.get_json()['mimeType'] (url, file_id) = user_files.prepare_for_drop(mime_type) return { 'url': url, 'file_id': str(file_id), }