Add new build script
This new build script is written entirely in python and supports multithreading to speed up the builds. Partially-Implements: blueprint build-script Change-Id: Ia630e5a83951ec37706a9596427024f3b7c10ba7
This commit is contained in:
parent
1c5988e642
commit
0e0b7e77ab
@ -1 +1,2 @@
|
|||||||
docker-compose==1.3.0
|
docker-compose==1.3.0
|
||||||
|
docker-py>=1.2.0
|
||||||
|
245
tools/build.py
Executable file
245
tools/build.py
Executable file
@ -0,0 +1,245 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
#TODO(SamYaple): Allow image pushing
|
||||||
|
#TODO(SamYaple): Single image building w/ optional parent building
|
||||||
|
#TODO(SamYaple): Build only missing images
|
||||||
|
#TODO(SamYaple): Execute the source install script that will pull down and create tarball
|
||||||
|
#TODO(SamYaple): Improve logging instead of printing to stdout
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
import argparse
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import Queue
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from threading import Thread
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import docker
|
||||||
|
|
||||||
|
class WorkerThread(Thread):
|
||||||
|
def __init__(self, queue, cache, rm):
|
||||||
|
self.queue = queue
|
||||||
|
self.nocache = not cache
|
||||||
|
self.forcerm = rm
|
||||||
|
self.dc = docker.Client()
|
||||||
|
Thread.__init__(self)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
""" Executes tasks until the queue is empty """
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = self.queue.get(block=False)
|
||||||
|
self.builder(data)
|
||||||
|
self.queue.task_done()
|
||||||
|
except Queue.Empty:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
self.queue.task_done()
|
||||||
|
|
||||||
|
def builder(self, image):
|
||||||
|
print('Processing:', image['name'])
|
||||||
|
image['status'] = "building"
|
||||||
|
|
||||||
|
if image['parent'] != None and image['parent']['status'] == "error":
|
||||||
|
image['status'] = "parent_error"
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pull the latest image for the base distro only
|
||||||
|
pull = True if image['parent'] == None else False
|
||||||
|
|
||||||
|
image['logs'] = str()
|
||||||
|
for response in self.dc.build(path=image['path'],
|
||||||
|
tag=image['fullname'],
|
||||||
|
nocache=self.nocache,
|
||||||
|
rm=True,
|
||||||
|
pull=pull,
|
||||||
|
forcerm=self.forcerm):
|
||||||
|
stream = json.loads(response)
|
||||||
|
|
||||||
|
if 'stream' in stream:
|
||||||
|
image['logs'] = image['logs'] + stream['stream']
|
||||||
|
elif 'errorDetail' in stream:
|
||||||
|
image['status'] = "error"
|
||||||
|
raise Exception(stream['errorDetail']['message'])
|
||||||
|
|
||||||
|
image['status'] = "built"
|
||||||
|
print(image['logs'], '\nProcessed:', image['name'])
|
||||||
|
|
||||||
|
def argParser():
|
||||||
|
parser = argparse.ArgumentParser(description='Kolla build script')
|
||||||
|
parser.add_argument('-n','--namespace',
|
||||||
|
help='Set the Docker namespace name',
|
||||||
|
type=str,
|
||||||
|
default='kollaglue')
|
||||||
|
parser.add_argument('--tag',
|
||||||
|
help='Set the Docker tag',
|
||||||
|
type=str,
|
||||||
|
default='latest')
|
||||||
|
parser.add_argument('-b','--base',
|
||||||
|
help='The base distro to use when building',
|
||||||
|
type=str,
|
||||||
|
default='centos')
|
||||||
|
parser.add_argument('-t','--type',
|
||||||
|
help='The method of the Openstack install',
|
||||||
|
type=str,
|
||||||
|
default='binary')
|
||||||
|
parser.add_argument('-c','--cache',
|
||||||
|
help='Use Docker cache when building',
|
||||||
|
type=bool,
|
||||||
|
default=True)
|
||||||
|
parser.add_argument('-r','--rm',
|
||||||
|
help='Remove intermediate containers while building',
|
||||||
|
type=bool,
|
||||||
|
default=True)
|
||||||
|
parser.add_argument('-T','--threads',
|
||||||
|
help='The number of threads to use while building',
|
||||||
|
type=int,
|
||||||
|
default=8)
|
||||||
|
return vars(parser.parse_args())
|
||||||
|
|
||||||
|
class KollaWorker():
|
||||||
|
def __init__(self, args):
|
||||||
|
self.kolla_dir = os.path.join(sys.path[0], '..')
|
||||||
|
self.images_dir = os.path.join(self.kolla_dir, 'docker')
|
||||||
|
|
||||||
|
self.namespace = args['namespace']
|
||||||
|
self.base = args['base']
|
||||||
|
self.type_ = args['type']
|
||||||
|
self.tag = args['tag']
|
||||||
|
self.prefix = self.base + '-' + self.type_ + '-'
|
||||||
|
|
||||||
|
def setupWorkingDir(self):
|
||||||
|
""" Creates a working directory for use while building """
|
||||||
|
ts = time.time()
|
||||||
|
ts = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d_%H-%M-%S_')
|
||||||
|
self.temp_dir = tempfile.mkdtemp(prefix='kolla-' + ts)
|
||||||
|
self.working_dir = os.path.join(self.temp_dir, 'docker')
|
||||||
|
shutil.copytree(self.images_dir, self.working_dir)
|
||||||
|
|
||||||
|
def findDockerfiles(self):
|
||||||
|
""" Recursive search for Dockerfiles in the working directory """
|
||||||
|
self.docker_build_paths = list()
|
||||||
|
path = os.path.join(self.working_dir, self.base, self.type_)
|
||||||
|
|
||||||
|
for root, dirs, names in os.walk(path):
|
||||||
|
if 'Dockerfile' in names:
|
||||||
|
self.docker_build_paths.append(root)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
""" Remove temp files """
|
||||||
|
shutil.rmtree(self.temp_dir)
|
||||||
|
|
||||||
|
def sortImages(self):
|
||||||
|
""" Build images dependency tiers """
|
||||||
|
images_to_process = list(self.images)
|
||||||
|
|
||||||
|
self.tiers = list()
|
||||||
|
while images_to_process:
|
||||||
|
self.tiers.append(list())
|
||||||
|
processed_images = list()
|
||||||
|
|
||||||
|
for image in images_to_process:
|
||||||
|
if image['parent'] == None:
|
||||||
|
self.tiers[-1].append(image)
|
||||||
|
processed_images.append(image)
|
||||||
|
if len(self.tiers) > 1:
|
||||||
|
for parent in self.tiers[-2]:
|
||||||
|
if image['parent'] == parent['fullname']:
|
||||||
|
image['parent'] = parent
|
||||||
|
self.tiers[-1].append(image)
|
||||||
|
processed_images.append(image)
|
||||||
|
|
||||||
|
#TODO(SamYaple): Improve error handling in this section
|
||||||
|
if not processed_images:
|
||||||
|
print('Could not find parent image from some images. Aborting', file=sys.stderr)
|
||||||
|
for image in images_to_process:
|
||||||
|
print(image['name'], image['parent'], file=sys.stderr)
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
# You cannot modify a list while using the list in a for loop as it
|
||||||
|
# will produce unexpected results by messing up the index so we
|
||||||
|
# build a seperate list and remove them here instead
|
||||||
|
for image in processed_images:
|
||||||
|
images_to_process.remove(image)
|
||||||
|
|
||||||
|
def summary(self):
|
||||||
|
""" Walk the list of images and check for errors """
|
||||||
|
print("Successfully built images")
|
||||||
|
print("=========================")
|
||||||
|
for image in self.images:
|
||||||
|
if image['status'] == "built":
|
||||||
|
print(image['name'])
|
||||||
|
|
||||||
|
print("\nImages that failed to build")
|
||||||
|
print("===========================")
|
||||||
|
for image in self.images:
|
||||||
|
if image['status'] != "built":
|
||||||
|
print(image['name'], "\r\t\t\t Failed with status:", image['status'])
|
||||||
|
|
||||||
|
def buildImageList(self):
|
||||||
|
self.images = list()
|
||||||
|
|
||||||
|
# Walk all of the Dockerfiles and replace the %%KOLLA%% variables
|
||||||
|
for path in self.docker_build_paths:
|
||||||
|
with open(os.path.join(path, 'Dockerfile')) as f:
|
||||||
|
content = f.read().replace('%%KOLLA_NAMESPACE%%', self.namespace)
|
||||||
|
content = content.replace('%%KOLLA_PREFIX%%', self.prefix)
|
||||||
|
content = content.replace('%%KOLLA_TAG%%', self.tag)
|
||||||
|
with open(os.path.join(path, 'Dockerfile'), 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
image = dict()
|
||||||
|
image['status'] = "unprocessed"
|
||||||
|
image['name'] = os.path.basename(path)
|
||||||
|
image['fullname'] = self.namespace + '/' + self.prefix + \
|
||||||
|
image['name'] + ':' + self.tag
|
||||||
|
image['path'] = path
|
||||||
|
image['parent'] = content.split(' ')[1].split('\n')[0]
|
||||||
|
if not self.namespace in image['parent']:
|
||||||
|
image['parent'] = None
|
||||||
|
|
||||||
|
self.images.append(image)
|
||||||
|
|
||||||
|
def buildQueues(self):
|
||||||
|
"""
|
||||||
|
Return a list of Queues that have been organized into a hierarchy
|
||||||
|
based on dependencies
|
||||||
|
"""
|
||||||
|
self.buildImageList()
|
||||||
|
self.sortImages()
|
||||||
|
|
||||||
|
pools = list()
|
||||||
|
for tier in self.tiers:
|
||||||
|
pool = Queue.Queue()
|
||||||
|
for image in tier:
|
||||||
|
pool.put(image)
|
||||||
|
|
||||||
|
pools.append(pool)
|
||||||
|
|
||||||
|
return pools
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = argParser()
|
||||||
|
|
||||||
|
kolla = KollaWorker(args)
|
||||||
|
kolla.setupWorkingDir()
|
||||||
|
kolla.findDockerfiles()
|
||||||
|
|
||||||
|
# Returns a list of Queues for us to loop through
|
||||||
|
for pool in kolla.buildQueues():
|
||||||
|
for x in xrange(args['threads']):
|
||||||
|
WorkerThread(pool, args['cache'], args['rm']).start()
|
||||||
|
# block until queue is empty
|
||||||
|
pool.join()
|
||||||
|
|
||||||
|
kolla.summary()
|
||||||
|
kolla.cleanup()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in New Issue
Block a user