You need to sign in or sign up before continuing.
Newer
Older
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Launch a docker image but bind the local directory inside the container
and define the module path automatically.
"""
import argparse
import logging

Vincent Hatakeyama
committed
import pwd
from subprocess import call
import sys
import os
import ConfigParser

Vincent Hatakeyama
committed
import docker # apt python-docker (1.9)
from psycopg2 import connect, OperationalError # apt python-psycopg2
# TODO auto create list of module
_logger = logging.getLogger(__name__)
__version__ = '0.1.0'
__date__ = '2017-08-11'
# Check the version of the "docker" package
if docker.__version__.split('.') < [2]:
print("the version of the docker package is {} instead of 1.9"
.format(docker.__version__))
sys.exit(1)
def main(argv=None): # IGNORE:C0111
"""Parse arguments and launch conversion
"""
if argv is None:
argv = sys.argv
else:
sys.argv.extend(argv)
program_version = __version__
program_build_date = str(__updated__)
program_version_message = '%%(prog)s %s (%s)' % (
program_version, program_build_date)

Vincent Hatakeyama
committed
program_shortdesc = __doc__.split(".")[0]
program_license = '''%s
Created by Vincent Hatakeyama on %s.
Copyright 2017 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__),
)

Vincent Hatakeyama
committed
# TODO the script assume it is launched from the parent super project
project_path = os.path.realpath('.')
project_name = os.path.basename(project_path)
setup_path = 'setup.cfg'
# 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]")
parser.add_argument(
'--db_user',
help="Database user [default: %(default)s]",
default=None,
)
parser.add_argument(
'--db_password',
help="Database user password [default: %(default)s]",
default=None,
)
parser.add_argument(
'-d',
'--database',
help="Database [default: %(default)s]",
default=None,
)
group = parser.add_mutually_exclusive_group()
group.add_argument(
'-u',
'--update',
help="Module to update (will also set --i18n-overwrite)"
" [default: %(default)s]\n"
"Options --update, --test and --test-default"
" cannot be used at the same time",
default=None,
)
group.add_argument(
'-t',
'--test',
help="Modules to test (will also set --log-level=test)"
" [default: %(default)s]\n"
"Options --update, --test and --test-default"
" cannot be used at the same time",
action='store_true',
)
group.add_argument(
'--test-default',
help="Test all modules in module_list_test"
" [default: %(default)s]\n"
"Options --update, --test and --test-default"
" cannot be used at the same time",
action='store_true',
)
parser.add_argument(
'-i',
'--install',
help="Module to install [default: %(default)s]",
default=None,
)
parser.add_argument(
'--without-demo',
help="Module to avoid including demo data [default: %(default)s]",
default=None,
)

Vincent Hatakeyama
committed
parser.add_argument(
'--create-user',
help="Create user in database (using your user) [default: %(default)s]",

Vincent Hatakeyama
committed
action='store_true',
)

Vincent Hatakeyama
committed
parser.add_argument(
'--install-default',
help="Append the module_list from setup.cfg to the modules to install [default: %(default)s]",
action='store_true',
)

Vincent Hatakeyama
committed
network_group = parser.add_mutually_exclusive_group()
network_group.add_argument(
'--host-network',
help="Use host network [default: %(default)s]",
action='store_false',
)
network_group.add_argument(
'--docker-network',
help="Use docker network (work better with python-netifaces installed) [default: %(default)s]",

Vincent Hatakeyama
committed
action='store_true',
)
# TODO detect that user is member of docker group
# TODO add a way to add options to docker
# TODO add a way to add arg to odoo
nmspc = parser.parse_args()
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)
db_user = nmspc.db_user
db_password = nmspc.db_password

Vincent Hatakeyama
committed
use_host_network = nmspc.host_network
if project_name == 'odoo_scripts':
logging.fatal(
"You must run this script from the super project"
" (./odoo_scripts/docker_dev_start.py)")
return
c = ConfigParser.ConfigParser()
logging.debug('setup file path %s', setup_path)
# TODO test that setup file exists
c.read(setup_path)
addon_dirs = c.get('odoo_scripts', 'addon_dirs', '').split()
logging.debug("addon directories: %s", addon_dirs)
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)

Vincent Hatakeyama
committed
odoo_type = (
c.has_section('odoo_scripts') and
c.has_option('odoo_scripts', 'odoo_type') and
c.get('odoo_scripts', 'odoo_type')) or 'odoo7'

Vincent Hatakeyama
committed
image = "%s/%s:latest" % (registry, project)
logging.debug("Docker image: %s", image)

Vincent Hatakeyama
committed
# detect if docker image already exists
docker_client = docker.Client(base_url='unix://var/run/docker.sock')
image_list = docker_client.images(name=image, quiet=True)
if not image_list:
logging.warn("Image %s does not exist", image)
else:
logging.info("Test image found")
options = [
'--name',
project_name,
'--rm',
'--publish',
'8069:8069',
'--tty',
'--interactive',
]
arg = [
'start',
]
if db_user:
arg.append('--db_user %s' % db_user)
if db_password:
arg.append('--db_password %s' % db_user)
if nmspc.update:
arg.append('-u %s' % nmspc.update)
arg.append('--i18n-overwrite')
if nmspc.test:
arg.append('-u %s' % nmspc.test)
arg.append('--test-enable')
arg.append('--log-level=test')
if nmspc.test_default:
test_modules = c.get('odoo_scripts', 'module_list_tests').split() or []
arg.append('-u %s' % test_modules)
arg.append('--test-enable')
arg.append('--log-level=test')
if nmspc.database:
arg.append('-d %s' % nmspc.database)

Vincent Hatakeyama
committed
if nmspc.install or nmspc.install_default:
modules_to_install = []
if nmspc.install:
modules_to_install.extend(nmspc.install)
if nmspc.install_default:
modules_to_install.extend((
c.has_section('odoo_scripts') and
c.has_option('odoo_scripts', 'module_list') and
c.get('odoo_scripts', 'module_list').split()) or [])
if modules_to_install:
arg.append('-i')
arg.append(','.join(modules_to_install))
if nmspc.without_demo:
arg.append('--without-demo %s' % nmspc.database)
# auto detect local ip

Vincent Hatakeyama
committed
if use_host_network:
local_ip = '127.0.0.1'
options.append('--network')
options.append('host')
else:
local_ip = None
try:
from netifaces import ifaddresses, AF_INET # apt python-netifaces
ifaceName = 'docker0'
addresses = [i['addr'] for i in ifaddresses(ifaceName).setdefault(AF_INET, [{'addr':'No IP addr'}])]
if addresses:
local_ip = addresses[0]
except ImportError:
logging.warn('Consider installing python netifaces to ease local IP detection')
if not local_ip:

Vincent Hatakeyama
committed
import socket
logging.info('Contacting Google Public DNS to find our IP')

Vincent Hatakeyama
committed
local_ip = [(s.connect(('8.8.8.8', 53)), s.getsockname()[0], s.close()) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1]
logging.debug('IP found %s', local_ip)
arg.append('--db_host')
arg.append(local_ip)
# auto detect local conf
local_conf_path = 'conf/dev/odoo.conf'

Vincent Hatakeyama
committed
password = db_password
if os.path.isfile(local_conf_path):
logging.info('Local configuration file found: %s' % local_conf_path)
options.append('--volume')
options.append('%s:/opt/odoo/etc/odoo.conf' % os.path.join(
project_path, local_conf_path))

Vincent Hatakeyama
committed
cp_local = ConfigParser.ConfigParser()
cp_local.read(local_conf_path)
if not user:
user = cp_local.get('options', 'db_user', 'pg')
if not password:
password = cp_local.get('options', 'db_password', 'THIS-IS-NOT-USED-DONOT-CHANGE')

Vincent Hatakeyama
committed
# data volume handling
if odoo_type != 'odoo7':
options.append('--mount')
data_volume_name = '{}_data'.format(project_name)
logging.debug('Using data volume %s', data_volume_name)
volumes = docker_client.volumes(filters={'name': data_volume_name})
if volumes['Volumes']:
logging.debug('Volume %s already exist', data_volume_name)
volume = volumes['Volumes'][0]
else:
logging.debug('Creating volume %s', data_volume_name)
volume = docker_client.create_volume(name=data_volume_name)
mount_opts = 'source={},target=/mnt/data'.format(data_volume_name)
# make sure the permission in the volume are correct
# TODO replace by something cleaner if possible
call(['docker', 'run', '--rm', '--mount', mount_opts, 'busybox', 'chmod', '777', '/mnt/data'])
options.append(mount_opts)

Vincent Hatakeyama
committed
arg.append('--data-dir /mnt/data')
else:
logging.debug('No data volume for this odoo version')

Vincent Hatakeyama
committed
# Check that connection can be done, try to create user if asked to
try:
test_connection = connect(user=user, password=password, database='postgres', host=local_ip)
except OperationalError as exception:

Vincent Hatakeyama
committed
logging.info('Cannot connect to database with user %s', user)
logging.info(exception)
logging.info('Creating user %s', user)
loginname = pwd.getpwuid(os.getuid())[0]
connection = connect(user=loginname, database='postgres')
with connection.cursor() as cursor:
# not injection safe but you are on your own machine
# with already full access to db
cursor.execute(
'CREATE ROLE %s LOGIN CREATEDB PASSWORD %%s' % user,
(password,))

Vincent Hatakeyama
committed
connection.commit()
connection.close()
else:
logging.warn('Cannot connect to database with user %s', user)
logging.warn(exception)
# volume magic
for addons_dir in addon_dirs:
binds.append(
'%s:/mnt/addons/%s' % (os.path.join(project_path, addons_dir),
addons_dir))
all_addons_dir = [
'/opt/odoo/sources/odoo/addons',
]
all_addons_dir.extend(
'/mnt/addons/%s' % addons_dir for addons_dir in addon_dirs)
arg.append('--addons-path')
arg.append(','.join(all_addons_dir))
cmd = ['docker', 'run']
cmd.extend(options)
cmd.append(image)
cmd.extend(arg)
logging.debug(cmd)

Vincent Hatakeyama
committed
# TODO directly use docker-py instead
call(cmd)
# TODO add handling of signal to reload
if __name__ == "__main__":

Vincent Hatakeyama
committed
return_code = main()
if return_code:
exit(return_code)