示例#1
0
文件: dcompose.py 项目: triole/dq-dev
 def __init__(self, conf, prof):
     self.col = Colours()
     self.conf = conf
     self.prof = prof
     self.dcyaml = {}
     self.profconf = {}
     self.names = {}
     self.volumes = []
示例#2
0
def copy_custom_scripts(cs_conf, basedir, active_app):
    col = Colours()
    for typ in cs_conf:
        for con in cs_conf[typ]:
            dockdir = pj(basedir, 'docker', con)
            if exists(dockdir) is True:
                target_folder = pj(dockdir, 'rootfs', 'tmp', 'custom_scripts',
                                   typ)
                source_folder = expand(cs_conf[typ][con], active_app)
                if isdir(source_folder) is True:
                    files = listfiles_only(source_folder)
                    if len(files) > 0:
                        print('\nAdd custom scripts to container ' +
                              col.gre(shortname(dockdir)))
                    for fil in files:
                        copy_file(fil, target_folder)
    print('')
示例#3
0
文件: dcompose.py 项目: triole/dq-dev
class DCompose():
    def __init__(self, conf, prof):
        self.col = Colours()
        self.conf = conf
        self.prof = prof
        self.dcyaml = {}
        self.profconf = {}
        self.names = {}
        self.volumes = []

    def expand_vars_arr(self, arr, container_name=None):
        for i, el in enumerate(arr):
            arr[i] = self.expand_vars(arr[i], container_name)
        return arr

    def expand_vars(self, string, container_name=None):
        if '<' not in string and '>' not in string:
            return string
        # expand variables set in config
        string = string\
            .replace('<HOME>', os.environ['HOME'])\
            .replace('<ACTIVE_APP>', self.conf['conf']['active_app'])\
            .replace('<CONTAINER_PGAPP>', self.nam_con('pgapp'))\
            .replace('<CONTAINER_PGDATA>', self.nam_con('pgdata'))\
            .replace('<CONTAINER_WPDB>', self.nam_con('wpdb'))\
            .replace('<CONTAINER_RABBITMQ>', self.nam_con('rabbitmq'))\
            .replace('<UID>', self.conf['user']['idstr'])\
            .replace('<GID>', self.conf['user']['groupstr'])
        # expand env var placeholders set in other env vars
        if container_name is not None:
            try:
                env_vars = self.conf['conf']['env'][container_name]
            except KeyError:
                pass
            else:
                for k in env_vars:
                    key = str(k)
                    val = str(env_vars[key])
                    if key != '' and val != '':
                        string = string.replace('<' + key.upper() + '>', val)
        # add additional packages
        if container_name is not None:
            try:
                p = ' '.join(
                    self.conf['conf']['additional_packages'][container_name])
            except KeyError:
                pass
            except TypeError:
                print(self.col.err() + self.col.yel(
                    'Please check additional packages config entry for ') +
                      container_name +
                      self.col.yel(' and make sure it is a not empty list.'))
                print('If you do not wish to use additional packages remove ' +
                      ' or comment the whole list entry.')
                sys.exit()
            else:
                var = '<ADDITIONAL_PACKAGES>'
                if var in string:
                    string = string.replace('<ADDITIONAL_PACKAGES>', p)
                    string = uncomment_line(string)
        return string

    def iter_services(self):
        return self.conf['conf']['env']

    # service and container names
    def make_names(self):
        for service in self.iter_services():
            self.names[service] = {}
            self.names[service]['con'] =\
                'dqdev' + '-' + service + '-' + self.conf['prof']['name']
            self.names[service]['img'] =\
                'dqdev' + '_' + service + '_' + self.conf['prof']['name']

    def nam_img(self, service):
        return self.names[service]['img']

    def nam_con(self, service):
        return self.names[service]['con']

    def nam_daiq(self):
        for service in self.names:
            img = self.nam_img(service)
            if 'daiquiri' in img:
                return img
        return None

    def container_enabled(self, container_name):
        try:
            return self.conf['conf']['enable_containers'][container_name]
        except KeyError:
            return False

    # template
    def make_template(self):
        self.dcyaml['version'] = '3.7'
        self.dcyaml['services'] = {}
        self.dcyaml['volumes'] = {}

        for service in self.iter_services():
            if self.container_enabled(service) is True:
                c = self.nam_img(service)
                self.dcyaml['services'][c] = {}
                self.dcyaml['services'][c]['build'] = {}
                self.dcyaml['services'][c]['build']['context'] =\
                    '../../../docker/' + service
                self.dcyaml['services'][c]['container_name'] =\
                    self.nam_con(service)
                self.dcyaml['services'][c]['restart'] = 'always'

    # depends on
    def add_depends_on(self):
        self.dcyaml['services'][self.nam_daiq()]['depends_on'] = []
        for service in self.dcyaml['services']:
            if 'daiquiri' not in service:
                self.dcyaml['services'][self.nam_daiq()]['depends_on']\
                    .append(service)

    # env
    def add_env(self):
        for service in self.iter_services():
            try:
                env_arr = []
                for k in self.conf['conf']['env'][service]:
                    key = str(k)
                    val = str(self.conf['conf']['env'][service][key])
                    env_arr.append(key.upper() + '=' + val)
                env = self.expand_vars_arr(env_arr, service)
            except KeyError:
                pass
            else:
                try:
                    p = self.conf['conf']['portmap'][service]
                except KeyError:
                    pass
                else:
                    env.append('EXPOSED_PORT=' + str(p['exposed']))

                for mp in self.conf['conf']['docker_volume_mountpoints']:
                    key = ''.join(re.findall('[A-Z0-9]', mp.upper()))
                    val = self.conf['conf']['docker_volume_mountpoints'][mp]
                    env.append(key + '=' + val)
                # try because exception occurs when a container is disabled
                try:
                    self.dcyaml['services'][self.nam_img(
                        service)]['environment'] = env
                except KeyError:
                    pass

    # network
    def add_networks(self):
        nn = self.prof.conf['prof']['network_name']
        self.dcyaml['networks'] = {}
        self.dcyaml['networks'][nn] = {}
        self.dcyaml['networks'][nn]['external'] = {}
        self.dcyaml['networks'][nn]['external']['name'] = nn
        for service in self.dcyaml['services']:
            self.dcyaml['services'][service]['networks'] = [nn]

    # ports
    def add_ports(self):
        for service in self.conf['conf']['portmap']:
            try:
                self.dcyaml['services'][self.nam_img(service)]['ports'] =\
                    [self.conf['conf']['portmap'][service]['envstr']]
            except KeyError:
                pass

    # volumes
    def add_volumes(self):
        for vol in self.volumes:
            self.dcyaml['volumes'][vol['name']] = {}
            self.dcyaml['volumes'][vol['name']]['driver_opts'] =\
                vol['driver_opts']

        for service in self.dcyaml['services']:
            self.dcyaml['services'][service]['volumes'] = []

            for vol in self.volumes:
                if rxbool(vol['mount_inside'], service) is True:
                    self.dcyaml['services'][service]['volumes'].append(
                        vol['name'] + ':' + self.expand_vars(vol['mp']))

    def make_volumes(self):
        vols = []
        for volname in self.conf['conf']['docker_volume_mountpoints']:
            try:
                fol = self.conf['conf']['folders_on_host'][volname]
            except KeyError:
                fol = self.conf['conf']['folders_on_host'][self.conf['conf']
                                                           ['active_app']]
            v = self.make_volume(
                volname + '_' + self.profconf['name'],
                self.conf['conf']['docker_volume_mountpoints']
                [volname],  # noqa: E501
                fol,
                volname.startswith('dq_'))
            if self.valid_volume(v) is True:
                vols.append(v)

        for volname in self.conf['conf']['enable_database_volumes']:
            if self.conf['conf']['enable_database_volumes'][
                    volname] is True:  # noqa: E501
                volfolder = pj(
                    self.prof.get_profile_folder_by_name(
                        self.profconf['name']), volname)
                mkdir(volfolder)
                mp = '/var/lib/mysql'
                if volname.startswith('pg'):
                    mp = '/var/lib/postgresql/data'
                vols.append(
                    self.make_volume(volname + '_' + self.profconf['name'],
                                     mp,
                                     volfolder,
                                     mount_inside=volname))
        self.volumes = vols

    def make_volume(self,
                    volname,
                    mp,
                    folder_on_host,
                    required_git=False,
                    mount_inside='.*'):
        vol = {}
        vol['name'] = volname
        vol['mp'] = mp
        vol['required_git'] = required_git
        vol['mount_inside'] = mount_inside
        vol['driver_opts'] = {}
        vol['driver_opts']['o'] = 'bind'
        vol['driver_opts']['type'] = 'none'
        vol['driver_opts']['device'] = self.expand_vars(folder_on_host)
        return vol

    def valid_volume(self, vol):
        r = False
        dev = vol['driver_opts']['device']
        is_dir = os.path.isdir(dev)

        if is_dir is False and vol['required_git'] is False:
            print('Run without volume ' + self.col.yel(vol['name']) +
                  '. Path does not exist on host ' + self.col.yel(dev))

        if is_dir is True:
            r = True

        if vol['required_git'] is True:
            ig = is_git(dev)
            if ig[0] is False:
                print('\n' + self.col.err() + 'Folder ' + self.col.yel(dev) +
                      ' does not look like a git repo. ' +
                      '\nPlease make sure that it contains the source of ' +
                      self.col.yel(vol['name']) + '\n')
                x(1)
            else:
                r = True
        return r

    def write_yaml(self):
        if self.conf['dry_run'] is True:
            print(self.col.yel('\nDry run, dc yaml would look like this:'))
            pprint(self.dcyaml)
        else:
            print('Write dc yaml to    ' +
                  self.col.yel(self.conf['files']['dc_yaml']) + '\n')
            write_yaml(self.dcyaml, self.conf['files']['dc_yaml'])

    def render_dockerfile_templates(self):
        arr = find(self.conf['basedir'], '.*/dockerfile.tpl', 'f')
        for fn in arr:
            print('Render dockerfile template ' + self.col.yel(fn))
            self.render_template_file(fn)

    def render_template_file(self, filename):
        container_name = rxsearch(r'[a-z0-9A-Z-]+(?=/dockerfile.tpl$)',
                                  filename)
        new_filename = rxsearch(r'.*(?=\.)', filename)
        arr = []
        try:
            filecontent = open(filename, 'r')
        except Exception as e:
            raise (e)
        else:
            for line in filecontent.read().splitlines():
                arr.append(self.expand_vars(line, container_name))
        write_array_to_file(arr, new_filename)

    # main
    def render_dc_yaml(self, profname=None):
        self.profconf = self.prof.read_profile_config(profname)

        self.make_names()
        self.make_template()
        self.make_volumes()

        self.add_depends_on()
        self.add_env()
        self.add_ports()
        self.add_networks()
        self.add_volumes()

        self.write_yaml()
示例#4
0
                    '--display_profile',
                    type=str,
                    nargs='*',
                    default=None,
                    help='display currently active profile')
parser.add_argument(
    '-n',
    '--dry_run',
    action='store_true',
    default=False,
    help=('do not run any docker-compose commands nor ' +
          'save rendered docker-compose.yaml, just print them'))
args = parser.parse_args()

if __name__ == '__main__':
    col = Colours()
    conf = init(args)
    prof = Profile(conf)
    dco = DCompose(conf, prof)

    if conf['args']['list'] is True:
        prof.list()
        x()

    if args.create_profile is not None:
        prof.create(args.create_profile)

    if args.set_profile is not None:
        prof.set(args.set_profile)

    if args.display_profile is not None:
示例#5
0
def init(args):
    col = Colours()
    conf = {}
    n = os.path.realpath(__file__)
    basedir = '/'.join(n.split('/')[:-2])
    conf['basedir'] = basedir

    conf['args'] = {}
    conf['files'] = {}
    conf['prof'] = {}
    conf['prof']['basedir'] = pj(conf['basedir'], 'usr', 'profiles')
    conf['files']['active_conf'] = pj(conf['prof']['basedir'], 'active.toml')
    conf['files']['base_conf'] = pj(basedir, 'conf', 'baseconf.toml')
    conf['files']['base_secrets'] = pj(basedir, 'conf', 'secrets.toml')

    conf['args']['list'] = True
    conf['args']['down'] = parse_nargs(args.down)
    conf['args']['remove_network'] = parse_bool(args.remove_network)
    conf['args']['remove_images'] = parse_bool(args.remove_images)
    conf['args']['render'] = parse_nargs(args.render)
    conf['args']['run'] = parse_nargs(args.run)
    conf['args']['stop'] = parse_nargs(args.stop)
    conf['args']['display_profile'] = parse_nargs(args.display_profile)
    conf['args']['tail_logs'] = parse_nargs(args.tail_logs)
    conf['args']['set'] = args.set_profile
    conf['args']['create'] = args.create_profile

    apc = read_toml(conf['files']['active_conf'])
    conf['prof']['name'] = ''
    if apc is not None:
        conf['prof']['name'] = apc['active_profile_name']

    for arg in conf['args']:
        if arg != 'list':
            val = conf['args'][arg]
            if val is not None:
                conf['args']['list'] = None
                if isinstance(val, str):
                    conf['prof']['name'] = val
                break

    # read base configurations
    print('Read base config    ' + col.yel(conf['files']['base_conf']))
    base_conf = read_toml(conf['files']['base_conf'])
    print('Read base secrets   ' + col.yel(conf['files']['base_secrets']))
    base_secrets = read_toml(conf['files']['base_secrets'])
    base_conf['env'] = merge_dictionaries(base_conf['env'], base_secrets)
    conf['conf'] = base_conf

    # stop when no active profile was detected
    if conf['prof']['name'] == '':
        print(
            col.red('No profile active. ' +
                    'Please set one to be able to continue.'))
        x()

    # read profile configurations
    conf['prof']['folder'] = pj(conf['prof']['basedir'], conf['prof']['name'])
    conf['prof']['network_name'] = 'dqdevnet_' + conf['prof']['name']
    conf['files']['dc_yaml'] = pj(conf['prof']['folder'],
                                  'docker-compose.yaml')
    conf['files']['prof_conf'] = pj(conf['prof']['folder'], 'conf.toml')
    conf['files']['prof_secrets'] = pj(conf['prof']['folder'], 'secrets.toml')

    if conf['args']['set'] is None:
        print('\nUse profile         ' + col.gre(conf['prof']['name']))
    if isfile(conf['files']['prof_conf']) is True:
        if conf['args']['set'] is None:
            print('Read prof config    ' + col.yel(conf['files']['prof_conf']))
        prof_conf = read_toml(conf['files']['prof_conf'])
        # merge the two
        conf['conf'] = merge_dictionaries(conf['conf'], prof_conf)
    else:
        if args.set_profile is None and args.create_profile is None:
            print(
                col.red('\nWarning') +
                '\n    Profile config does not exist: ' +
                col.yel(conf['files']['prof_conf']) +
                '\n    All base settings are going to be applied. ' +
                'It is highly likely your setup ' +
                'will turn out to be unusable.\n')

    if isfile(conf['files']['prof_secrets']) is True:
        print('Read prof secrets   ' + col.yel(conf['files']['prof_conf']))
        prof_secrets = read_toml(conf['files']['prof_secrets'])
        conf['conf']['env'] =\
            merge_dictionaries(conf['conf']['env'], prof_secrets)

    # user settings
    conf['user'] = {}
    conf['user']['id'] = os.getuid()
    conf['user']['idstr'] = str(conf['user']['id'])
    conf['user']['group'] = get_group(conf['user']['id'])
    conf['user']['groupstr'] = str(conf['user']['group'])
    conf['dry_run'] = args.dry_run
    mkdir(conf['prof']['basedir'])

    clean_temp_files(conf['basedir'], conf['conf']['enable_containers'])

    copy_custom_scripts(conf['conf']['custom_scripts'], conf['basedir'],
                        conf['conf']['active_app'])

    create_rootfs_folders(conf['basedir'])

    conf['conf']['portmap'] = parse_ports(conf)
    del conf['conf']['exposed_ports']

    return conf
示例#6
0
 def __init__(self, conf):
     self.c = Colours()
     self.conf = conf
     self.need_sudo = self.need_sudo()
示例#7
0
class Runner():
    def __init__(self, conf):
        self.c = Colours()
        self.conf = conf
        self.need_sudo = self.need_sudo()

    def run_cmd_fg(self, cmd):
        print(self.c.mag(' '.join(cmd)))
        if self.conf['dry_run'] is False:
            try:
                subprocess.run(cmd)
            except KeyboardInterrupt:
                pass

    def need_sudo(self):
        g = run_cmd(['groups'])
        if bool(re.search('docker', g)) is True:
            return False
        else:
            return True

    def file_arg_compose(self):
        return ['-f', self.conf['files']['dc_yaml']]

    def run_docker(self, args):
        cmd_arr = []
        if self.need_sudo is True:
            cmd_arr.append('sudo')
        cmd_arr.append('docker')
        cmd_arr.extend(args)
        self.run_cmd_fg(cmd_arr)

    def run_compose(self, args):
        cmd_arr = []
        if self.need_sudo is True:
            cmd_arr.append('sudo')
        cmd_arr.append('docker-compose')
        cmd_arr.extend(self.file_arg_compose())
        cmd_arr.extend(args)
        self.run_cmd_fg(cmd_arr)

    # docker compose commands
    def start(self):
        self.run_compose(['up', '--build', '-d'])
        self.tail_logs()

    def stop(self):
        self.run_compose(['stop'])

    def down(self):
        args = ['down', '--volumes', '--remove-orphans']
        if self.conf['args']['remove_images'] is True:
            args.append('--rmi all')
        self.run_compose(args)

    def tail_logs(self):
        self.run_compose(['logs', '-f'])

    def remove_images(self):
        self.run_compose(['down', '--volume'])

    def create_network(self):
        self.run_docker([
            'network', 'create', self.conf['prof']['network_name']
        ])

    def remove_network(self):
        self.run_docker([
            'network', 'remove', self.conf['prof']['network_name']
        ])
示例#8
0
 def __init__(self, conf):
     self.conf = conf
     self.c = Colours()
示例#9
0
class Profile():
    def __init__(self, conf):
        self.conf = conf
        self.c = Colours()

    def create(self, profname):
        if self.profile_exists(profname) is True:
            print('Please check ' + self.c.yel(self.conf['prof']['basedir']) +
                  '\nDid nothing. Profile ' + self.c.yel(profname) +
                  ' seems to exist')
        else:
            mkdir(self.get_profile_folder_by_name(profname))
            conf_yaml = pj(self.get_profile_folder_by_name(profname),
                           'conf.toml')
            secrets_yaml = pj(self.get_profile_folder_by_name(profname),
                              'secrets.toml')
            print('Fresh profile ' + self.c.yel(profname) +
                  ' created inside folder ' +
                  self.c.yel(self.get_profile_folder_by_name(profname)) +
                  '\nPlease add your local settings to ' +
                  self.c.yel(conf_yaml) + '\nAnd don\'t forget your secrets.')
            copyfile(self.conf['files']['base_conf'], conf_yaml)
            copyfile(self.conf['files']['base_secrets'], secrets_yaml)

    def set(self, profname):
        if self.profile_exists(profname) is False:
            print('Unable to set. Profile ' + self.c.yel(profname) +
                  ' does not seem to exist')
        else:
            print('Set active profile ' + self.c.yel(profname))
            p = {}
            p['active_profile_name'] = profname
            write_toml(p, self.conf['files']['active_conf'])

    def read_profile_config(self, profname=None):
        if profname is None or profname is True:
            profname = self.conf['prof']['name']
        if profname is None:
            print('Unable to detect active profile. Either set one or use ' +
                  'the command line arg.')
            x(1)
        r = {}
        f = find(self.conf['prof']['basedir'], profname + r'$', 'd')
        if len(f) < 1:
            print('Please check ' + self.c.yel(self.conf['prof']['basedir']) +
                  '\nProfile ' + self.c.yel(profname) +
                  ' does not seem to exist. ')
            x(1)
        if len(f) > 1:
            print('Please check ' + self.c.yel(self.conf['prof']['basedir']) +
                  '\nMultiple profiles matched: ')
            for el in f:
                print('\t' + el)
            x(1)
        r['name'] = profname
        r['yaml'] = pj(f[0], 'conf.toml')
        r['folder'] = path_up_to_last_slash(f[0])
        r['dc_yaml'] = pj(r['folder'], 'docker-compose.yaml')
        if isfile(r['yaml']) is True:
            r['conf'] = read_toml(r['yaml'])
        return r

    def boolstr(self, bool):
        if bool is True:
            return '*'
        else:
            return ''

    def list(self):
        print(self.c.yel('\nThe following profiles are available\n'))
        arr = find(self.conf['prof']['basedir'],
                   r'.*/profiles/[a-zA-Z0-9-_]+$', 'd')
        head = ['profile', 'has conf', 'active', 'volumes']
        tabledata = []
        for el in arr:
            shortname = rxsearch(r'[^/]+/[^/]+$', el)
            profname = rxsearch(r'[^/]+$', shortname)
            ap = self.conf['prof']['name']
            has_conf = self.boolstr(isfile(pj(el, 'conf.toml')))
            active = self.boolstr(profname == ap)
            listdirs_only(self.get_profile_folder_by_name(el))
            volumes = ' '.join(
                listdirs_only(self.get_profile_folder_by_name(el)))
            tabledata.append([profname, has_conf, active, volumes])
        ptable(head, tabledata)
        print()

    def get_profile_folder_by_name(self, profname=None):
        if profname is None:
            profname = self.conf['prof']['name']
        return pj(self.conf['prof']['basedir'], profname)

    def profile_exists(self, profname):
        return isdir(self.get_profile_folder_by_name(profname))