This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/endpoints/api/build.py

246 lines
7.7 KiB
Python

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):
def user_view(user):
return {
'name': user.username,
'kind': 'user',
'is_robot': user.robot,
}
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),
'pull_user': user_view(trigger.pull_user) if trigger.pull_user else None
}
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 if status else 'cannot_load',
'started': format_date(build_obj.started),
'display_name': build_obj.display_name,
'status': status or {},
'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/<repopath: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',
},
'pull_credentials': {
'type': 'object',
'description': 'Credentials used by the builder when pulling images',
'required': [
'username',
'password',
'registry'
],
'properties': {
'username': {
'type': 'string',
'description': 'The username for the pull'
},
'password': {
'type': 'string',
'description': 'The password for the pull'
},
'registry': {
'type': 'string',
'description': 'The registry hostname for the pull'
},
}
}
},
},
}
@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 ''
pull_credentials = request_json.get('pull_credentials', None)
# 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,
pull_credentials=pull_credentials)
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/<repopath:repository>/build/<build_uuid>/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/<repopath:repository>/build/<build_uuid>/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),
}