#!/usr/bin/python3 import argparse import contextlib import logging import psycopg2 import os import re import shutil import sys from collections import defaultdict from logging.handlers import WatchedFileHandler LOG_FORMAT = '%(asctime)s %(levelname)s %(name)s: %(message)s' logging.basicConfig(level=logging.INFO, format=LOG_FORMAT) logging.getLogger('odoo.addons.runbot').setLevel(logging.DEBUG) logging.addLevelName(25, "!NFO") _logger = logging.getLogger(__name__) DBRE = r'^(?P\d+)-.+-[0-9a-f]{6}-?(?P.*)$' @contextlib.contextmanager def local_pgadmin_cursor(): cnx = None try: cnx = psycopg2.connect("dbname=postgres") cnx.autocommit = True # required for admin commands yield cnx.cursor() finally: if cnx: cnx.close() def list_local_dbs(): with local_pgadmin_cursor() as local_cr: local_cr.execute(""" SELECT datname FROM pg_database WHERE pg_get_userbyid(datdba) = current_user """) return [d[0] for d in local_cr.fetchall()] def _local_pg_rename_db(dbname, new_db_name): with local_pgadmin_cursor() as local_cr: pid_col = 'pid' if local_cr.connection.server_version >= 90200 else 'procpid' query = 'SELECT pg_terminate_backend({}) FROM pg_stat_activity WHERE datname=%s'.format(pid_col) local_cr.execute(query, [dbname]) local_cr.execute("ALTER DATABASE \"%s\" RENAME TO \"%s\";" % (dbname, new_db_name)) class RunbotClient(): def __init__(self, env): self.env = env def rename_build_dirs(self, args): builds_root = os.path.join(self.env['runbot.runbot']._root(), 'build') builds_backup_root = os.path.join(self.env['runbot.runbot']._root(), 'build-backup') if not args.dry_run: try: _logger.info('Backup build dir in "%s"', builds_backup_root) shutil.copytree(builds_root, builds_backup_root, copy_function=os.link) except FileExistsError: _logger.info('Backup path "%s" already exists, skipping', builds_backup_root) build_dirs = {} leftovers = [] for dir_name in os.listdir(builds_root): match = re.match(DBRE, dir_name) if match and match['db_suffix'] == '': build_dirs[match['build_id']] = dir_name else: leftovers.append(dir_name) for build in self.env['runbot.build'].search([('id', 'in', list(build_dirs.keys()))]): origin_dir = build_dirs[str(build.id)] origin_path = os.path.join(builds_root, origin_dir) if origin_dir == build.dest: _logger.info('Skip moving %s, already moved', build.dest) continue _logger.info('Moving "%s" --> "%s"', origin_dir, build.dest) if args.dry_run: continue dest_path = os.path.join(builds_root, build.dest) os.rename(origin_path, dest_path) for leftover in leftovers: _logger.info("leftover: %s", leftover) def rename_databases(self, args): total_db = 0 db_names = defaultdict(dict) leftovers = [] for local_db_name in list_local_dbs(): match = re.match(DBRE, local_db_name) if match and match['db_suffix'] != '': db_names[match['build_id']][match['db_suffix']] = local_db_name else: leftovers.append(local_db_name) total_db += 1 nb_matching = 0 ids = [int(i) for i in db_names.keys()] builds = self.env['runbot.build'].search([('id', 'in', ids)]) for build in builds: for suffix in db_names[str(build.id)].keys(): origin_name = db_names[str(build.id)][suffix] dest_name = "%s-%s" % (build.dest, suffix) nb_matching += 1 _logger.info('Renaming database "%s" --> "%s"', origin_name, dest_name) if args.dry_run: continue _local_pg_rename_db(origin_name, dest_name) _logger.info("Found %s databases", total_db) _logger.info("Found %s matching databases", nb_matching) _logger.info("Leftovers: %s", len(leftovers)) _logger.info("Builds not found : %s", len(set(ids) - set(builds.ids))) def run(): # parse args parser = argparse.ArgumentParser() parser.add_argument('--odoo-path', help='Odoo sources path') parser.add_argument('--db_host', default='127.0.0.1') parser.add_argument('--db_port', default='5432') parser.add_argument('--db_user') parser.add_argument('--db_password') parser.add_argument('-d', '--database', default='runbot_upgrade', help='name of runbot db') parser.add_argument('--logfile', default=False) parser.add_argument('-n', '--dry-run', action='store_true') args = parser.parse_args() if args.logfile: dirname = os.path.dirname(args.logfile) if dirname and not os.path.isdir(dirname): os.makedirs(dirname) handler = WatchedFileHandler(args.logfile) formatter = logging.Formatter(LOG_FORMAT) handler.setFormatter(formatter) _logger.parent.handlers.clear() _logger.parent.addHandler(handler) # configure odoo sys.path.append(args.odoo_path) import odoo _logger.info("Starting upgrade move script using database %s", args.database) odoo.tools.config['db_host'] = args.db_host odoo.tools.config['db_port'] = args.db_port odoo.tools.config['db_user'] = args.db_user odoo.tools.config['db_password'] = args.db_password addon_path = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '..')) config_addons_path = odoo.tools.config['addons_path'] odoo.tools.config['addons_path'] = ','.join([config_addons_path, addon_path]) # create environment registry = odoo.registry(args.database) with odoo.api.Environment.manage(): with registry.cursor() as cr: env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) runbot_client = RunbotClient(env) runbot_client.rename_build_dirs(args) runbot_client.rename_databases(args) if __name__ == '__main__': run() _logger.info("All done")