Allow for caching of previous docker builds for 24 hours.
This commit is contained in:
parent
0bd8a1bcbf
commit
de18236358
1 changed files with 66 additions and 30 deletions
|
@ -14,6 +14,7 @@ from zipfile import ZipFile
|
|||
from functools import partial
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Event
|
||||
from uuid import uuid4
|
||||
|
||||
from data.queue import dockerfile_build_queue
|
||||
from data import model
|
||||
|
@ -34,6 +35,7 @@ user_files = app.config['USERFILES']
|
|||
build_logs = app.config['BUILDLOGS']
|
||||
|
||||
TIMEOUT_PERIOD_MINUTES = 20
|
||||
CACHE_EXPIRATION_PERIOD_HOURS = 24
|
||||
|
||||
|
||||
class StatusWrapper(object):
|
||||
|
@ -95,6 +97,9 @@ class StreamingDockerClient(Client):
|
|||
|
||||
|
||||
class DockerfileBuildContext(object):
|
||||
image_id_to_cache_time = {}
|
||||
public_repos = set()
|
||||
|
||||
def __init__(self, build_context_dir, dockerfile_subdir, repo, tag_names,
|
||||
push_token, build_uuid, pull_credentials=None):
|
||||
self._build_dir = build_context_dir
|
||||
|
@ -120,6 +125,7 @@ class DockerfileBuildContext(object):
|
|||
(self._repo, self._tag_names))
|
||||
|
||||
def __enter__(self):
|
||||
self.__evict_expired_images()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, value, traceback):
|
||||
|
@ -261,7 +267,19 @@ class DockerfileBuildContext(object):
|
|||
|
||||
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
|
||||
for running in self._build_cl.containers(quiet=True):
|
||||
logger.debug('Killing container: %s' % running['Id'])
|
||||
|
@ -272,40 +290,58 @@ class DockerfileBuildContext(object):
|
|||
logger.debug('Removing container: %s' % container['Id'])
|
||||
self._build_cl.remove_container(container['Id'])
|
||||
|
||||
# Iterate all of the images and remove the ones that the public registry
|
||||
# doesn't know about, this should preserve base images.
|
||||
images_to_remove = set()
|
||||
repos = set()
|
||||
def __evict_expired_images(self):
|
||||
self.__cleanup_containers()
|
||||
|
||||
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():
|
||||
images_to_remove.add(image['Id'])
|
||||
image_id = image[u'Id']
|
||||
created = datetime.fromtimestamp(image[u'Created'])
|
||||
|
||||
# If we don't have a cache time, use the created time (e.g. worker reboot)
|
||||
cache_time = self.image_id_to_cache_time.get(image_id, created)
|
||||
expiration = cache_time + timedelta(hours=CACHE_EXPIRATION_PERIOD_HOURS)
|
||||
|
||||
if expiration < now:
|
||||
logger.debug('Removing expired image: %s' % image_id)
|
||||
verify_removed.add(image_id)
|
||||
try:
|
||||
self._build_cl.remove_image(image_id)
|
||||
except APIError:
|
||||
# Sometimes an upstream image removed this one
|
||||
pass
|
||||
|
||||
# Verify that our images were actually removed
|
||||
for image in self._build_cl.images():
|
||||
if image['Id'] in verify_removed:
|
||||
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>':
|
||||
repos.add(tag_repo)
|
||||
|
||||
for repo in repos:
|
||||
repo_url = 'https://index.docker.io/v1/repositories/%s/images' % repo
|
||||
repo_info = requests.get(repo_url)
|
||||
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:
|
||||
self._build_cl.remove_image(to_remove)
|
||||
except APIError:
|
||||
# Sometimes an upstream image removed this one
|
||||
pass
|
||||
|
||||
# Verify that our images were actually removed
|
||||
for image in self._build_cl.images():
|
||||
if image['Id'] in images_to_remove:
|
||||
raise RuntimeError('Image was not removed: %s' % image['Id'])
|
||||
|
||||
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):
|
||||
def __init__(self, *vargs, **kwargs):
|
||||
|
|
Reference in a new issue