188 lines
6.5 KiB
Python
188 lines
6.5 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2014, Craig Tracey <craigtracey@gmail.com>
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
|
|
import docker
|
|
import jinja2
|
|
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import tempfile
|
|
|
|
from giftwrap.builders import Builder
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
DEFAULT_TEMPLATE_FILE = os.path.join(os.path.dirname(
|
|
os.path.dirname(__file__)),
|
|
'templates/Dockerfile.jinja2')
|
|
APT_REQUIRED_PACKAGES = [
|
|
'libffi-dev',
|
|
'libxml2-dev',
|
|
'libxslt1-dev',
|
|
'git',
|
|
'wget',
|
|
'curl',
|
|
'libldap2-dev',
|
|
'libsasl2-dev',
|
|
'libssl-dev',
|
|
'python-dev',
|
|
'libmysqlclient-dev',
|
|
'python-pip',
|
|
'build-essential'
|
|
]
|
|
DEFAULT_SRC_PATH = '/opt/openstack'
|
|
|
|
|
|
class DockerBuilder(Builder):
|
|
|
|
def __init__(self, spec):
|
|
self.base_image = 'ubuntu:14.04'
|
|
self.maintainer = 'maintainer@example.com'
|
|
self.envvars = {'DEBIAN_FRONTEND': 'noninteractive'}
|
|
self._commands = []
|
|
super(DockerBuilder, self).__init__(spec)
|
|
|
|
def _execute(self, command, cwd=None, exit=0):
|
|
if cwd:
|
|
self._commands.append("cd %s" % (cwd))
|
|
self._commands.append(command)
|
|
if cwd:
|
|
self._commands.append("cd -")
|
|
|
|
def _make_temp_dir(self, prefix='giftwrap'):
|
|
return "/tmp/giftwrap"
|
|
self._commands.append("mktemp -d -t %s.XXXXXXXXXX" % prefix)
|
|
|
|
def _make_dir(self, path, mode=0o777):
|
|
self._commands.append("mkdir -p -m %o %s" % (mode, path))
|
|
|
|
def _prepare_project_build(self, project):
|
|
self.image_name = "giftwrap/openstack:%s" % (project.version)
|
|
return
|
|
|
|
def _clone_project(self, giturl, name, gitref, depth, path):
|
|
cmd = "git clone %s -b %s --depth=%d %s" % (giturl, gitref,
|
|
depth, path)
|
|
self._commands.append(cmd)
|
|
|
|
def _create_virtualenv(self, venv_command, path):
|
|
self._execute(venv_command, path)
|
|
|
|
def _install_pip_dependencies(self, venv_path, dependencies,
|
|
use_constraints=True):
|
|
pip_path = self._get_venv_pip_path(venv_path)
|
|
install = "install"
|
|
if use_constraints:
|
|
for constraint in self._constraints:
|
|
install = "%s -c %s" % (install, constraint)
|
|
|
|
for dependency in dependencies:
|
|
self._execute("%s %s %s" % (pip_path, install, dependency))
|
|
|
|
def _copy_sample_config(self, src_clone_dir, project):
|
|
src_config = os.path.join(src_clone_dir, 'etc')
|
|
dest_config = os.path.join(project.install_path, 'etc')
|
|
|
|
self._commands.append("if [ -d %s ]; then cp -R %s %s; fi" % (
|
|
src_config, src_config, dest_config))
|
|
|
|
def _install_project(self, venv_path, src_clone_dir):
|
|
pip_path = self._get_venv_pip_path(venv_path)
|
|
install = "install"
|
|
for constraint in self._constraints:
|
|
install = "%s -c %s" % (install, constraint)
|
|
self._execute("%s %s %s" % (pip_path, install, src_clone_dir))
|
|
|
|
def _finalize_project_build(self, project):
|
|
self._commands.append("rm -rf %s" % self._temp_dir)
|
|
for command in self._commands:
|
|
print(command)
|
|
|
|
def _finalize_build(self):
|
|
template_vars = {
|
|
'commands': self._commands
|
|
}
|
|
print(self._render_dockerfile(template_vars))
|
|
self._build_image()
|
|
|
|
def _cleanup_build(self):
|
|
return
|
|
|
|
def _prepare_build(self):
|
|
self._commands.append('apt-get update && apt-get install -y %s' %
|
|
' '.join(APT_REQUIRED_PACKAGES))
|
|
self._commands.append("pip install -U pip virtualenv")
|
|
|
|
def _set_path(self):
|
|
path = ":".join(self._paths)
|
|
self.envvars['PATH'] = "%s:$PATH" % path
|
|
|
|
def _render_dockerfile(self, extra_vars):
|
|
template_vars = self.__dict__
|
|
template_vars.update(extra_vars)
|
|
template_loader = jinja2.FileSystemLoader(searchpath='/')
|
|
template_env = jinja2.Environment(loader=template_loader)
|
|
template = template_env.get_template(DEFAULT_TEMPLATE_FILE)
|
|
return template.render(template_vars)
|
|
|
|
def _build_image(self):
|
|
template_vars = {
|
|
'commands': self._commands
|
|
}
|
|
dockerfile_contents = self._render_dockerfile(template_vars)
|
|
|
|
tempdir = tempfile.mkdtemp()
|
|
dockerfile = os.path.join(tempdir, 'Dockerfile')
|
|
with open(dockerfile, "w") as w:
|
|
w.write(dockerfile_contents)
|
|
docker_client = docker.Client(base_url='unix://var/run/docker.sock',
|
|
timeout=10)
|
|
build_result = docker_client.build(path=tempdir, stream=True,
|
|
tag=self.image_name)
|
|
for line in build_result:
|
|
LOG.info(line.strip())
|
|
|
|
# I borrowed this from docker/stackbrew, should cull it down
|
|
# to be more sane.
|
|
def _parse_result(self, build_result):
|
|
build_success_re = r'^Successfully built ([a-f0-9]+)\n$'
|
|
if isinstance(build_result, tuple):
|
|
img_id, logs = build_result
|
|
return img_id, logs
|
|
else:
|
|
lines = [line for line in build_result]
|
|
try:
|
|
parsed_lines = [json.loads(e).get('stream', '') for e in lines]
|
|
except ValueError:
|
|
# sometimes all the data is sent on a single line ????
|
|
#
|
|
# ValueError: Extra data: line 1 column 87 - line 1 column
|
|
# 33268 (char 86 - 33267)
|
|
line = lines[0]
|
|
# This ONLY works because every line is formatted as
|
|
# {"stream": STRING}
|
|
parsed_lines = [
|
|
json.loads(obj).get('stream', '') for obj in
|
|
re.findall('{\s*"stream"\s*:\s*"[^"]*"\s*}', line)
|
|
]
|
|
|
|
for line in parsed_lines:
|
|
match = re.match(build_success_re, line)
|
|
if match:
|
|
return match.group(1), parsed_lines
|
|
return None, lines
|