106 lines
3.8 KiB
Python
106 lines
3.8 KiB
Python
from data import model
|
|
|
|
import json
|
|
|
|
class BuildJobLoadException(Exception):
|
|
""" Exception raised if a build job could not be instantiated for some reason. """
|
|
pass
|
|
|
|
class BuildJob(object):
|
|
""" Represents a single in-progress build job. """
|
|
def __init__(self, job_item):
|
|
self._job_item = job_item
|
|
|
|
try:
|
|
self._job_details = json.loads(job_item.body)
|
|
except ValueError:
|
|
raise BuildJobLoadException(
|
|
'Could not parse build queue item config with ID %s' % self._job_details['build_uuid']
|
|
)
|
|
|
|
try:
|
|
self._repo_build = model.get_repository_build(self._job_details['build_uuid'])
|
|
except model.InvalidRepositoryBuildException:
|
|
raise BuildJobLoadException(
|
|
'Could not load repository build with ID %s' % self._job_details['build_uuid'])
|
|
|
|
try:
|
|
self._build_config = json.loads(self._repo_build.job_config)
|
|
except ValueError:
|
|
raise BuildJobLoadException(
|
|
'Could not parse repository build job config with ID %s' % self._job_details['build_uuid']
|
|
)
|
|
|
|
def determine_cached_tag(self, base_image_id=None, cache_comments=None):
|
|
""" Returns the tag to pull to prime the cache or None if none. """
|
|
cached_tag = None
|
|
if base_image_id and cache_comments:
|
|
cached_tag = self._determine_cached_tag_by_comments(base_image_id, cache_comments)
|
|
|
|
if not cached_tag:
|
|
cached_tag = self._determine_cached_tag_by_tag()
|
|
|
|
return cached_tag
|
|
|
|
def _determine_cached_tag_by_comments(self, base_image_id, cache_commands):
|
|
""" Determines the tag to use for priming the cache for this build job, by matching commands
|
|
starting at the given base_image_id. This mimics the Docker cache checking, so it should,
|
|
in theory, provide "perfect" caching.
|
|
"""
|
|
# Lookup the base image in the repository. If it doesn't exist, nothing more to do.
|
|
repo_namespace = self._repo_build.repository.namespace_user.username
|
|
repo_name = self._repo_build.repository.name
|
|
|
|
repository = model.get_repository(repo_namespace, repo_name)
|
|
if repository is None:
|
|
# Should never happen, but just to be sure.
|
|
return None
|
|
|
|
current_image = model.get_image(repository, base_image_id)
|
|
if current_image is None:
|
|
return None
|
|
|
|
# For each cache comment, find a child image that matches the command.
|
|
for cache_command in cache_commands:
|
|
print current_image.docker_image_id
|
|
|
|
current_image = model.find_child_image(repository, current_image, cache_command)
|
|
if current_image is None:
|
|
return None
|
|
|
|
# Find a tag associated with the image, if any.
|
|
# TODO(jschorr): We should just return the image ID instead of a parent tag, OR we should
|
|
# make this more efficient.
|
|
for tag in model.list_repository_tags(repo_namespace, repo_name):
|
|
tag_image = tag.image
|
|
ancestor_index = '/%s/' % current_image.id
|
|
if ancestor_index in tag_image.ancestors:
|
|
return tag.name
|
|
|
|
return None
|
|
|
|
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.
|
|
"""
|
|
tags = self._build_config.get('docker_tags', ['latest'])
|
|
existing_tags = model.list_repository_tags(self._repo_build.repository.namespace_user.username,
|
|
self._repo_build.repository.name)
|
|
|
|
cached_tags = set(tags) & set([tag.name for tag in existing_tags])
|
|
if cached_tags:
|
|
return list(cached_tags)[0]
|
|
|
|
return None
|
|
|
|
def job_item(self):
|
|
""" Returns the job's queue item. """
|
|
return self._job_item
|
|
|
|
def repo_build(self):
|
|
""" Returns the repository build DB row for the job. """
|
|
return self._repo_build
|
|
|
|
def build_config(self):
|
|
""" Returns the parsed repository build config for the job. """
|
|
return self._build_config
|