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/building.py

240 lines
7.3 KiB
Python
Raw Normal View History

import logging
import json
2016-12-06 18:59:47 +00:00
from datetime import datetime, timedelta
from flask import request
from app import app, dockerfile_build_queue
from data import model
from data.database import db
from auth.auth_context import get_authenticated_user
from endpoints.notificationhelper import spawn_notification
from util.names import escape_tag
from util.morecollections import AttrDict
2016-12-05 21:07:00 +00:00
logger = logging.getLogger(__name__)
MAX_BUILD_QUEUE_RATE_ITEMS = app.config.get('MAX_BUILD_QUEUE_RATE_ITEMS', -1)
MAX_BUILD_QUEUE_RATE_SECS = app.config.get('MAX_BUILD_QUEUE_RATE_SECS', -1)
2016-12-05 21:07:00 +00:00
class MaximumBuildsQueuedException(Exception):
"""
This exception is raised when a build is requested, but the incoming build
would exceed the configured maximum build rate.
"""
2016-12-05 21:07:00 +00:00
pass
def start_build(repository, prepared_build, pull_robot_name=None):
if repository.kind.name != 'image':
raise Exception('Attempt to start a build for application repository %s' % repository.id)
if MAX_BUILD_QUEUE_RATE_ITEMS > 0 and MAX_BUILD_QUEUE_RATE_SECS > 0:
2016-12-07 17:55:22 +00:00
queue_item_canonical_name = [repository.namespace_user.username, repository.name]
2016-12-06 19:47:02 +00:00
now = datetime.utcnow()
available_min = now - timedelta(seconds=MAX_BUILD_QUEUE_RATE_SECS)
2016-12-06 19:47:02 +00:00
available_builds = dockerfile_build_queue.num_available_jobs_between(available_min,
now,
2016-12-07 17:55:22 +00:00
queue_item_canonical_name)
if available_builds >= MAX_BUILD_QUEUE_RATE_ITEMS:
2016-12-06 18:59:47 +00:00
raise MaximumBuildsQueuedException()
2016-12-05 21:07:00 +00:00
host = app.config['SERVER_HOSTNAME']
repo_path = '%s/%s/%s' % (host, repository.namespace_user.username, repository.name)
new_token = model.token.create_access_token(repository, 'write', kind='build-worker',
friendly_name='Repository Build Token')
logger.debug('Creating build %s with repo %s tags %s',
prepared_build.build_name, repo_path, prepared_build.tags)
job_config = {
'docker_tags': prepared_build.tags,
'registry': host,
'build_subdir': prepared_build.subdirectory,
'trigger_metadata': prepared_build.metadata or {},
'is_manual': prepared_build.is_manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None,
'archive_url': prepared_build.archive_url
}
with app.config['DB_TRANSACTION_FACTORY'](db):
build_request = model.build.create_repository_build(repository, new_token, job_config,
prepared_build.dockerfile_id,
prepared_build.build_name,
prepared_build.trigger,
pull_robot_name=pull_robot_name)
pull_creds = model.user.get_pull_credentials(pull_robot_name) if pull_robot_name else None
json_data = json.dumps({
'build_uuid': build_request.uuid,
'pull_credentials': pull_creds
})
queue_id = dockerfile_build_queue.put([repository.namespace_user.username, repository.name],
json_data,
retries_remaining=3)
build_request.queue_id = queue_id
build_request.save()
# Add the build to the repo's log and spawn the build_queued notification.
event_log_metadata = {
2015-07-01 14:48:43 +00:00
'build_id': build_request.uuid,
'docker_tags': prepared_build.tags,
'repo': repository.name,
'namespace': repository.namespace_user.username,
'is_manual': prepared_build.is_manual,
'manual_user': get_authenticated_user().username if get_authenticated_user() else None
}
if prepared_build.trigger:
event_log_metadata['trigger_id'] = prepared_build.trigger.uuid
event_log_metadata['trigger_kind'] = prepared_build.trigger.service.name
event_log_metadata['trigger_metadata'] = prepared_build.metadata or {}
model.log.log_action('build_dockerfile', repository.namespace_user.username,
ip=request.remote_addr, metadata=event_log_metadata, repository=repository)
# TODO(jzelinskie): remove when more endpoints have been converted to using interfaces
repo = AttrDict({
'namespace_name': repository.namespace_user.username,
'name': repository.name,
})
spawn_notification(repo, 'build_queued', event_log_metadata,
subpage='build/%s' % build_request.uuid,
pathargs=['build', build_request.uuid])
return build_request
class PreparedBuild(object):
""" Class which holds all the information about a prepared build. The build queuing service
will use this result to actually invoke the build.
"""
def __init__(self, trigger=None):
self._dockerfile_id = None
self._archive_url = None
self._tags = None
self._build_name = None
self._subdirectory = None
self._metadata = None
self._trigger = trigger
self._is_manual = None
@staticmethod
def get_display_name(sha):
return sha[0:7]
def tags_from_ref(self, ref, default_branch=None):
branch = ref.split('/', 2)[-1]
tags = {branch}
if branch == default_branch:
tags.add('latest')
self.tags = tags
def name_from_sha(self, sha):
self.build_name = PreparedBuild.get_display_name(sha)
@property
def is_manual(self):
if self._is_manual is None:
raise Exception('Property is_manual not set')
return self._is_manual
@is_manual.setter
def is_manual(self, value):
if self._is_manual is not None:
raise Exception('Property is_manual already set')
self._is_manual = value
@property
def trigger(self):
return self._trigger
@property
def archive_url(self):
return self._archive_url
@archive_url.setter
def archive_url(self, value):
if self._archive_url:
raise Exception('Property archive_url already set')
self._archive_url = value
@property
def dockerfile_id(self):
return self._dockerfile_id
@dockerfile_id.setter
def dockerfile_id(self, value):
if self._dockerfile_id:
raise Exception('Property dockerfile_id already set')
self._dockerfile_id = value
@property
def tags(self):
if not self._tags:
raise Exception('Missing property tags')
return self._tags
@tags.setter
def tags(self, value):
if self._tags:
raise Exception('Property tags already set')
self._tags = [escape_tag(tag, default='latest') for tag in value]
@property
def build_name(self):
if not self._build_name:
raise Exception('Missing property build_name')
return self._build_name
@build_name.setter
def build_name(self, value):
if self._build_name:
raise Exception('Property build_name already set')
self._build_name = value
@property
def subdirectory(self):
if self._subdirectory is None:
raise Exception('Missing property subdirectory')
return self._subdirectory
@subdirectory.setter
def subdirectory(self, value):
if self._subdirectory:
raise Exception('Property subdirectory already set')
self._subdirectory = value
@property
def metadata(self):
if self._metadata is None:
raise Exception('Missing property metadata')
return self._metadata
@metadata.setter
def metadata(self, value):
if self._metadata:
raise Exception('Property metadata already set')
self._metadata = value