Allow for caching of previous docker builds for 24 hours.

This commit is contained in:
jakedt 2014-04-14 15:21:05 -04:00
parent 0bd8a1bcbf
commit de18236358

View file

@ -14,6 +14,7 @@ from zipfile import ZipFile
from functools import partial from functools import partial
from datetime import datetime, timedelta from datetime import datetime, timedelta
from threading import Event from threading import Event
from uuid import uuid4
from data.queue import dockerfile_build_queue from data.queue import dockerfile_build_queue
from data import model from data import model
@ -34,6 +35,7 @@ user_files = app.config['USERFILES']
build_logs = app.config['BUILDLOGS'] build_logs = app.config['BUILDLOGS']
TIMEOUT_PERIOD_MINUTES = 20 TIMEOUT_PERIOD_MINUTES = 20
CACHE_EXPIRATION_PERIOD_HOURS = 24
class StatusWrapper(object): class StatusWrapper(object):
@ -95,6 +97,9 @@ class StreamingDockerClient(Client):
class DockerfileBuildContext(object): class DockerfileBuildContext(object):
image_id_to_cache_time = {}
public_repos = set()
def __init__(self, build_context_dir, dockerfile_subdir, repo, tag_names, def __init__(self, build_context_dir, dockerfile_subdir, repo, tag_names,
push_token, build_uuid, pull_credentials=None): push_token, build_uuid, pull_credentials=None):
self._build_dir = build_context_dir self._build_dir = build_context_dir
@ -120,6 +125,7 @@ class DockerfileBuildContext(object):
(self._repo, self._tag_names)) (self._repo, self._tag_names))
def __enter__(self): def __enter__(self):
self.__evict_expired_images()
return self return self
def __exit__(self, exc_type, value, traceback): def __exit__(self, exc_type, value, traceback):
@ -261,7 +267,19 @@ class DockerfileBuildContext(object):
raise RuntimeError(message) raise RuntimeError(message)
def __cleanup(self): def __is_repo_public(self, repo_name):
if repo_name in self.public_repos:
return True
repo_url = 'https://index.docker.io/v1/repositories/%s/images' % repo_name
repo_info = requests.get(repo_url)
if repo_info.status_code / 100 == 2:
self.public_repos.add(repo_name)
return True
else:
return False
def __cleanup_containers(self):
# First clean up any containers that might be holding the images # First clean up any containers that might be holding the images
for running in self._build_cl.containers(quiet=True): for running in self._build_cl.containers(quiet=True):
logger.debug('Killing container: %s' % running['Id']) logger.debug('Killing container: %s' % running['Id'])
@ -272,40 +290,58 @@ class DockerfileBuildContext(object):
logger.debug('Removing container: %s' % container['Id']) logger.debug('Removing container: %s' % container['Id'])
self._build_cl.remove_container(container['Id']) self._build_cl.remove_container(container['Id'])
# Iterate all of the images and remove the ones that the public registry def __evict_expired_images(self):
# doesn't know about, this should preserve base images. self.__cleanup_containers()
images_to_remove = set()
repos = set() logger.debug('Cleaning images older than %s hours.', CACHE_EXPIRATION_PERIOD_HOURS)
now = datetime.now()
verify_removed = set()
for image in self._build_cl.images(): for image in self._build_cl.images():
images_to_remove.add(image['Id']) image_id = image[u'Id']
created = datetime.fromtimestamp(image[u'Created'])
for tag in image['RepoTags']: # If we don't have a cache time, use the created time (e.g. worker reboot)
tag_repo = tag.split(':')[0] cache_time = self.image_id_to_cache_time.get(image_id, created)
if tag_repo != '<none>': expiration = cache_time + timedelta(hours=CACHE_EXPIRATION_PERIOD_HOURS)
repos.add(tag_repo)
for repo in repos: if expiration < now:
repo_url = 'https://index.docker.io/v1/repositories/%s/images' % repo logger.debug('Removing expired image: %s' % image_id)
repo_info = requests.get(repo_url) verify_removed.add(image_id)
if repo_info.status_code / 100 == 2:
for repo_image in repo_info.json():
if repo_image['id'] in images_to_remove:
logger.debug('Image was deemed public: %s' % repo_image['id'])
images_to_remove.remove(repo_image['id'])
for to_remove in images_to_remove:
logger.debug('Removing private image: %s' % to_remove)
try: try:
self._build_cl.remove_image(to_remove) self._build_cl.remove_image(image_id)
except APIError: except APIError:
# Sometimes an upstream image removed this one # Sometimes an upstream image removed this one
pass pass
# Verify that our images were actually removed # Verify that our images were actually removed
for image in self._build_cl.images(): for image in self._build_cl.images():
if image['Id'] in images_to_remove: if image['Id'] in verify_removed:
raise RuntimeError('Image was not removed: %s' % image['Id']) raise RuntimeError('Image was not removed: %s' % image['Id'])
def __cleanup(self):
self.__cleanup_containers()
# Iterate all of the images and rename the ones that aren't public. This should preserve
# base images and also allow the cache to function.
now = datetime.now()
for image in self._build_cl.images():
image_id = image[u'Id']
if image_id not in self.image_id_to_cache_time:
logger.debug('Setting image %s cache time to %s', image_id, now)
self.image_id_to_cache_time[image_id] = now
for tag in image['RepoTags']:
tag_repo = tag.split(':')[0]
if tag_repo != '<none>':
if self.__is_repo_public(tag_repo):
logger.debug('Repo was deemed public: %s', tag_repo)
else:
new_name = str(uuid4())
logger.debug('Private repo tag being renamed %s -> %s', tag, new_name)
self._build_cl.tag(image_id, new_name)
self._build_cl.remove_image(tag)
class DockerfileBuildWorker(Worker): class DockerfileBuildWorker(Worker):
def __init__(self, *vargs, **kwargs): def __init__(self, *vargs, **kwargs):