#!/usr/bin/env python # vim: set shiftwidth=4 softtabstop=4: """Script to locally build a docker image isort:skip_file """ # Version 2.22 import argparse import datetime import json import logging import os import signal from subprocess import call, check_output import sys from six.moves 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__) __version__ = '0.1.3' __date__ = '2018-04-04' __updated__ = '2018-11-19' 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', ) parser.add_argument( '--build-arg', help="build arg for the image, formated like FOO=BAR " "[default: %(default)s]", default=None, nargs='*', ) parser.add_argument( '--no-pull', help="indicate to docker to not pull the base image " "[default: %(default)s]", action='store_true', ) # TODO (maybe) add argument for other build arg # TODO detect that user is member of docker group nmspc = parser.parse_args(argv) 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 'registry.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 = dict() if os.path.exists('.hg'): buildargs['REVISION'] = check_output( 'hg identify -i'.split()).split()[0].decode('utf-8') buildargs['CREATED'] = datetime.datetime.now().isoformat() if nmspc.build_arg: for arg in nmspc.build_arg: a = arg.split('=') buildargs[a[0]] = a[1] logging.debug("Build args: %s", 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 python3-pyinotify\n') elif odoo_type in ('odoo10', ): myfile.write( 'python-ipdb\n') myfile.write( 'RUN pip install watchdog --disable-pip-version-check ' '--system --no-cache-dir --only-binary wheel') 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') pull = not nmspc.no_pull logging.debug("Docker Pull %s", pull) builder = docker_client.build( path='.', rm=True, pull=pull, 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.decode('utf-8')) 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__": return_code = main(sys.argv[1:]) if return_code: exit(return_code)