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 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):
|
||||||
|
|
Reference in a new issue