Skip to content
Snippets Groups Projects
docker_build.py 6.05 KiB
Newer Older
#!/usr/bin/env python
# vim: set shiftwidth=4 softtabstop=4:
"""Script to locally build a docker image
"""
# Version 2.17
import argparse
import json
import logging
import os
import signal
from subprocess import call
import sys

import ConfigParser

import docker  # apt python-docker (1.9) or pip install docker

if docker.__version__ > '2.5.0':
    from docker import APIClient as docker_api
else:
    from docker import Client as docker_api

_logger = logging.getLogger(__name__)

Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
__version__ = '0.1.1'
__date__ = '2018-04-04'
Vincent Hatakeyama's avatar
Vincent Hatakeyama committed
__updated__ = '2018-04-24'


def main(argv=None):  # IGNORE:C0111
    """Parse arguments and docker build
    """
    program_version = __version__
    program_build_date = str(__updated__)
    program_version_message = '%%(prog)s %s (%s)' % (
        program_version, program_build_date)
    program_shortdesc = __doc__.split(".")[0]
    program_license = '''%s

  Created by Vincent Hatakeyama on %s.
  Copyright 2018 XCG Consulting. All rights reserved.

  Licensed under the MIT License

  Distributed on an "AS IS" basis without warranties
  or conditions of any kind, either express or implied.

USAGE
''' % (program_shortdesc, str(__date__))

    # TODO the script assume it is launched from the parent super project
    project_path = os.path.realpath('.')
    project_name = os.path.basename(project_path)
    if project_name == 'odoo_scripts':
        logging.fatal(
            "You must run this script from the super project"
            " (./odoo_scripts/docker_build.py)")
        return

    setup_path = 'setup.cfg'
    # TODO add a way to store configuration options in a project file
    # Argument parsing
    parser = argparse.ArgumentParser(
        description=program_license,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument(
        '-V', '--version', action='version',
        version=program_version_message)
    parser.add_argument(
        '-v', '--verbose', dest='verbose', action='count',
        help="set verbosity level [default: %(default)s]")
    # TODO add tag option, and maybe force tag option
    parser.add_argument(
        '--ensureconf',
        help="ensureconf [default: %(default)s]",
        action='store_true',
    )
    parser.add_argument(
        '--push',
        help="Push image [default: %(default)s]",
        action='store_true',
    )
    parser.add_argument(
        '--dev',
        help="add dev feature to generated image [default: %(default)s]",
        action='store_true',
    )
    # TODO (maybe) add argument for other build arg

    # TODO detect that user is member of docker group

    verbose = nmspc.verbose
    if not verbose:
        logging.basicConfig(level=logging.WARN)
    if verbose == 1:
        logging.basicConfig(level=logging.INFO)
    if verbose and verbose > 1:
        logging.basicConfig(level=logging.DEBUG)
    ensureconf = nmspc.ensureconf
    dev = nmspc.dev
    push = nmspc.push

    c = ConfigParser.ConfigParser()
    if not os.path.exists(setup_path):
        logging.fatal('Missing %s', setup_path)
        return 12
    c.read(setup_path)

    registry = (
        c.has_section('odoo_scripts') and
        c.has_option('odoo_scripts', 'registry') and
        c.get('odoo_scripts', 'registry')) or 'dockerhub.xcg.io'
    project = (
        c.has_section('odoo_scripts') and
        c.has_option('odoo_scripts', 'image') and
        c.get('odoo_scripts', 'image')) or os.path.basename(project_path)
    odoo_type = (
        c.has_section('odoo_scripts') and
        c.has_option('odoo_scripts', 'odoo_type') and
        c.get('odoo_scripts', 'odoo_type')) or 'odoo7'
    image = "%s/%s:latest" % (registry, project)
    logging.debug("Docker image: %s", image)
    # TODO ensureconf
    if ensureconf:
        raise NotImplementedError
    # call build copy
    cmd = ['./odoo_scripts/docker_build_copy']
    logging.debug(' '.join(cmd))
    call(cmd)
    # clean on exit
    def signal_handler(code, frame):
        cmd = ['./odoo_scripts/docker_build_clean']
        logging.debug(' '.join(cmd))
        call(cmd)
        # XXX needed?
    #    sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # TODO docker build
    buildargs = {}
    dockerfile = 'Dockerfile'
    if dev:
        debug_dockerfile = 'Dockerfile.debug'
        call(['cp', dockerfile, debug_dockerfile])

        with open(debug_dockerfile, 'a') as myfile:
            myfile.write('\n# Developer helpers\n'
                         'RUN apt-get update -qq\n')
            myfile.write(
                'RUN DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ')
            if odoo_type == 'odoo11':
                myfile.write(
                    'python3-watchdog python3-ipdb\n')
            elif odoo_type in ('odoo10', ):
                myfile.write(
            elif odoo_type in ('odoo7', 'odoo8'):
                myfile.write(
                    'python-ipdb\n')
                myfile.write('RUN pip install pyinotify')
        dockerfile = debug_dockerfile
        # TODO remove temp image

    docker_client = docker_api(base_url='unix://var/run/docker.sock')
    builder = docker_client.build(
        path='.', rm=True, pull=True, buildargs=buildargs,
        tag=image, dockerfile=dockerfile)
    # this is for python docker 1.8-1.9
    # TODO add compatibility with newer python docker
    for line in builder:
        d = json.loads(line)
        if 'stream' in d:
            logging.info(d['stream'])
        if 'errorDetail' in d:
            logging.fatal(d['errorDetail'])
            return 1
    if dev:
        call(['rm', dockerfile])
    # TODO exit if build failed

    # TODO docker tag with tags/bookmarks (unused so maybe no need)
    # TODO docker push (only when asked for)
    if push:
        raise NotImplementedError
    # XXX call cleanup more intelligently
    signal_handler(0, None)
    return 0


if __name__ == "__main__":
    if return_code:
        exit(return_code)