2014-12-31 16:33:56 +00:00
|
|
|
import json
|
2015-02-09 18:54:14 +00:00
|
|
|
import logging
|
2014-12-31 16:33:56 +00:00
|
|
|
|
2017-07-26 16:10:48 +00:00
|
|
|
from app import app
|
2014-12-31 16:33:56 +00:00
|
|
|
from cachetools import lru_cache
|
2017-07-14 13:29:59 +00:00
|
|
|
from notifications import spawn_notification
|
2014-11-12 19:03:07 +00:00
|
|
|
from data import model
|
2018-08-13 22:09:05 +00:00
|
|
|
from data.registry_model import registry_model
|
|
|
|
from data.registry_model.datatypes import RepositoryReference
|
2017-07-26 16:10:48 +00:00
|
|
|
from data.database import UseThenDisconnect
|
2016-09-30 22:54:09 +00:00
|
|
|
from util.morecollections import AttrDict
|
2014-11-12 19:03:07 +00:00
|
|
|
|
2015-02-09 18:54:14 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2014-11-12 19:03:07 +00:00
|
|
|
|
|
|
|
class BuildJobLoadException(Exception):
|
|
|
|
""" Exception raised if a build job could not be instantiated for some reason. """
|
|
|
|
pass
|
|
|
|
|
2016-11-29 16:48:08 +00:00
|
|
|
|
2014-11-12 19:03:07 +00:00
|
|
|
class BuildJob(object):
|
|
|
|
""" Represents a single in-progress build job. """
|
|
|
|
def __init__(self, job_item):
|
2014-12-16 18:41:30 +00:00
|
|
|
self.job_item = job_item
|
2014-11-12 19:03:07 +00:00
|
|
|
|
|
|
|
try:
|
2014-12-16 18:41:30 +00:00
|
|
|
self.job_details = json.loads(job_item.body)
|
2016-11-29 16:48:08 +00:00
|
|
|
self.build_notifier = BuildJobNotifier(self.build_uuid)
|
2014-11-12 19:03:07 +00:00
|
|
|
except ValueError:
|
|
|
|
raise BuildJobLoadException(
|
2018-08-13 22:09:05 +00:00
|
|
|
'Could not parse build queue item config with ID %s' % self.job_details['build_uuid']
|
2014-11-18 20:45:56 +00:00
|
|
|
)
|
2014-11-12 19:03:07 +00:00
|
|
|
|
2016-07-08 18:52:14 +00:00
|
|
|
@property
|
|
|
|
def retries_remaining(self):
|
|
|
|
return self.job_item.retries_remaining
|
|
|
|
|
2015-02-12 21:19:44 +00:00
|
|
|
def has_retries_remaining(self):
|
|
|
|
return self.job_item.retries_remaining > 0
|
|
|
|
|
2016-09-12 19:30:15 +00:00
|
|
|
def send_notification(self, kind, error_message=None, image_id=None, manifest_digests=None):
|
2016-11-29 16:48:08 +00:00
|
|
|
self.build_notifier.send_notification(kind, error_message, image_id, manifest_digests)
|
2015-02-03 18:01:42 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@lru_cache(maxsize=1)
|
|
|
|
def _load_repo_build(self):
|
2017-07-26 16:10:48 +00:00
|
|
|
with UseThenDisconnect(app.config):
|
|
|
|
try:
|
|
|
|
return model.build.get_repository_build(self.build_uuid)
|
|
|
|
except model.InvalidRepositoryBuildException:
|
|
|
|
raise BuildJobLoadException(
|
|
|
|
'Could not load repository build with ID %s' % self.build_uuid)
|
2016-07-15 22:28:48 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def build_uuid(self):
|
|
|
|
""" Returns the unique UUID for this build job. """
|
|
|
|
return self.job_details['build_uuid']
|
2014-11-12 19:03:07 +00:00
|
|
|
|
2016-07-08 18:52:14 +00:00
|
|
|
@property
|
|
|
|
def namespace(self):
|
|
|
|
""" Returns the namespace under which this build is running. """
|
|
|
|
return self.repo_build.repository.namespace_user.username
|
|
|
|
|
2016-09-09 19:36:54 +00:00
|
|
|
@property
|
|
|
|
def repo_name(self):
|
|
|
|
""" Returns the name of the repository under which this build is running. """
|
|
|
|
return self.repo_build.repository.name
|
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@property
|
|
|
|
def repo_build(self):
|
|
|
|
return self._load_repo_build()
|
|
|
|
|
2015-08-14 21:22:19 +00:00
|
|
|
def get_build_package_url(self, user_files):
|
|
|
|
""" Returns the URL of the build package for this build, if any or empty string if none. """
|
|
|
|
archive_url = self.build_config.get('archive_url', None)
|
|
|
|
if archive_url:
|
|
|
|
return archive_url
|
|
|
|
|
|
|
|
if not self.repo_build.resource_key:
|
|
|
|
return ''
|
|
|
|
|
2017-10-05 19:20:16 +00:00
|
|
|
return user_files.get_file_url(self.repo_build.resource_key, '127.0.0.1', requires_cors=False)
|
2015-08-14 21:22:19 +00:00
|
|
|
|
2015-01-29 23:01:42 +00:00
|
|
|
@property
|
|
|
|
def pull_credentials(self):
|
|
|
|
""" Returns the pull credentials for this job, or None if none. """
|
|
|
|
return self.job_details.get('pull_credentials')
|
2014-11-12 19:03:07 +00:00
|
|
|
|
2014-12-31 16:33:56 +00:00
|
|
|
@property
|
|
|
|
def build_config(self):
|
2014-11-12 19:03:07 +00:00
|
|
|
try:
|
2014-12-31 16:33:56 +00:00
|
|
|
return json.loads(self.repo_build.job_config)
|
2014-11-12 19:03:07 +00:00
|
|
|
except ValueError:
|
|
|
|
raise BuildJobLoadException(
|
2015-07-15 21:25:41 +00:00
|
|
|
'Could not parse repository build job config with ID %s' % self.job_details['build_uuid']
|
2014-11-18 20:45:56 +00:00
|
|
|
)
|
2014-11-12 19:03:07 +00:00
|
|
|
|
2014-12-11 16:03:40 +00:00
|
|
|
def determine_cached_tag(self, base_image_id=None, cache_comments=None):
|
2014-11-12 19:03:07 +00:00
|
|
|
""" Returns the tag to pull to prime the cache or None if none. """
|
2018-08-13 22:09:05 +00:00
|
|
|
cached_tag = self._determine_cached_tag_by_tag()
|
2015-02-09 18:54:14 +00:00
|
|
|
logger.debug('Determined cached tag %s for %s: %s', cached_tag, base_image_id, cache_comments)
|
2014-12-11 16:03:40 +00:00
|
|
|
return cached_tag
|
|
|
|
|
|
|
|
def _determine_cached_tag_by_tag(self):
|
|
|
|
""" Determines the cached tag by looking for one of the tags being built, and seeing if it
|
|
|
|
exists in the repository. This is a fallback for when no comment information is available.
|
|
|
|
"""
|
2017-07-26 16:10:48 +00:00
|
|
|
with UseThenDisconnect(app.config):
|
|
|
|
tags = self.build_config.get('docker_tags', ['latest'])
|
2018-08-13 22:09:05 +00:00
|
|
|
repository = RepositoryReference.for_repo_obj(self.repo_build.repository)
|
|
|
|
matching_tag = registry_model.find_matching_tag(repository, tags)
|
|
|
|
if matching_tag is not None:
|
|
|
|
return matching_tag.name
|
|
|
|
|
|
|
|
most_recent_tag = registry_model.get_most_recent_tag(repository)
|
|
|
|
if most_recent_tag is not None:
|
|
|
|
return most_recent_tag.name
|
2014-11-12 19:03:07 +00:00
|
|
|
|
2017-07-26 16:10:48 +00:00
|
|
|
return None
|
2016-11-29 16:48:08 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BuildJobNotifier(object):
|
|
|
|
""" A class for sending notifications to a job that only relies on the build_uuid """
|
|
|
|
|
|
|
|
def __init__(self, build_uuid):
|
|
|
|
self.build_uuid = build_uuid
|
|
|
|
|
|
|
|
@property
|
|
|
|
def repo_build(self):
|
|
|
|
return self._load_repo_build()
|
|
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
|
|
def _load_repo_build(self):
|
|
|
|
try:
|
|
|
|
return model.build.get_repository_build(self.build_uuid)
|
|
|
|
except model.InvalidRepositoryBuildException:
|
|
|
|
raise BuildJobLoadException(
|
|
|
|
'Could not load repository build with ID %s' % self.build_uuid)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def build_config(self):
|
|
|
|
try:
|
|
|
|
return json.loads(self.repo_build.job_config)
|
|
|
|
except ValueError:
|
|
|
|
raise BuildJobLoadException(
|
|
|
|
'Could not parse repository build job config with ID %s' % self.repo_build.uuid
|
|
|
|
)
|
|
|
|
|
|
|
|
def send_notification(self, kind, error_message=None, image_id=None, manifest_digests=None):
|
2017-07-26 16:10:48 +00:00
|
|
|
with UseThenDisconnect(app.config):
|
|
|
|
tags = self.build_config.get('docker_tags', ['latest'])
|
2018-03-09 19:19:20 +00:00
|
|
|
trigger = self.repo_build.trigger
|
2018-04-06 17:48:01 +00:00
|
|
|
|
|
|
|
# TODO(bison): This is weird hack. Figure this out.
|
|
|
|
if trigger is not None and trigger.id is not None:
|
|
|
|
trigger_kind = trigger.service.name
|
|
|
|
else:
|
|
|
|
trigger_kind = None
|
|
|
|
|
2017-07-26 16:10:48 +00:00
|
|
|
event_data = {
|
|
|
|
'build_id': self.repo_build.uuid,
|
|
|
|
'build_name': self.repo_build.display_name,
|
|
|
|
'docker_tags': tags,
|
2018-03-09 19:19:20 +00:00
|
|
|
'trigger_id': trigger.uuid if trigger is not None else None,
|
2018-04-06 17:48:01 +00:00
|
|
|
'trigger_kind': trigger_kind,
|
2017-07-26 16:10:48 +00:00
|
|
|
'trigger_metadata': self.build_config.get('trigger_metadata', {})
|
|
|
|
}
|
|
|
|
|
|
|
|
if image_id is not None:
|
|
|
|
event_data['image_id'] = image_id
|
|
|
|
|
|
|
|
if manifest_digests:
|
|
|
|
event_data['manifest_digests'] = manifest_digests
|
|
|
|
|
|
|
|
if error_message is not None:
|
|
|
|
event_data['error_message'] = error_message
|
|
|
|
|
|
|
|
# TODO(jzelinskie): remove when more endpoints have been converted to using
|
|
|
|
# interfaces
|
|
|
|
repo = AttrDict({
|
|
|
|
'namespace_name': self.repo_build.repository.namespace_user.username,
|
|
|
|
'name': self.repo_build.repository.name,
|
|
|
|
})
|
|
|
|
spawn_notification(repo, kind, event_data,
|
|
|
|
subpage='build/%s' % self.repo_build.uuid,
|
|
|
|
pathargs=['build', self.repo_build.uuid])
|