2017-01-19 01:59:38 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
import os
|
2017-01-20 01:01:29 +01:00
|
|
|
import sys
|
2017-01-19 01:59:38 +01:00
|
|
|
import yaml
|
|
|
|
import argparse
|
2017-04-17 21:52:45 +02:00
|
|
|
from itertools import chain
|
2017-01-20 01:01:29 +01:00
|
|
|
from urlparse import urlparse
|
2017-01-19 01:59:38 +01:00
|
|
|
import psycopg2
|
|
|
|
import subprocess
|
2017-01-20 01:01:29 +01:00
|
|
|
import ConfigParser
|
2017-01-19 01:59:38 +01:00
|
|
|
|
|
|
|
from blessings import Terminal
|
|
|
|
|
2017-01-21 01:30:25 +01:00
|
|
|
import trytond
|
|
|
|
|
|
|
|
trytond_version = '.'.join(trytond.__version__.split('.')[:2])
|
|
|
|
|
|
|
|
|
2017-01-19 01:59:38 +01:00
|
|
|
t = Terminal()
|
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
def get_url():
|
|
|
|
config = ConfigParser.ConfigParser()
|
2017-02-13 00:51:15 +01:00
|
|
|
config.read(config_file)
|
2017-01-20 01:01:29 +01:00
|
|
|
url = urlparse(config.get('database', 'uri'))
|
|
|
|
return url
|
2017-01-19 01:59:38 +01:00
|
|
|
|
|
|
|
def run(*args):
|
2017-01-20 01:01:29 +01:00
|
|
|
print 'RUNING:', ' '.join(args)
|
2017-01-21 00:48:30 +01:00
|
|
|
summary = set()
|
2017-01-20 01:01:29 +01:00
|
|
|
process = subprocess.Popen(args, stdout=subprocess.PIPE,
|
2017-04-19 09:54:53 +02:00
|
|
|
stderr=subprocess.PIPE, bufsize=1, shell=False)
|
2017-03-22 18:58:49 +01:00
|
|
|
|
|
|
|
out, err = process.communicate()
|
|
|
|
|
2017-04-17 21:52:45 +02:00
|
|
|
in_traceback = False
|
|
|
|
for line in chain(out.split('\n'), err.split('\n')):
|
2017-01-20 01:01:29 +01:00
|
|
|
line = line.strip()
|
|
|
|
if 'ERROR' in line:
|
2017-01-21 02:17:09 +01:00
|
|
|
s = line[line.index('ERROR'):]
|
|
|
|
summary.add(t.yellow(s))
|
2017-01-20 01:01:29 +01:00
|
|
|
line = t.red(line)
|
|
|
|
elif 'WARNING' in line:
|
2017-01-21 02:17:09 +01:00
|
|
|
s = line[line.index('WARNING'):]
|
|
|
|
summary.add(t.yellow(s))
|
2017-01-20 01:01:29 +01:00
|
|
|
line = t.yellow(line)
|
2017-04-17 21:52:45 +02:00
|
|
|
elif 'Traceback' in line or in_traceback:
|
2017-12-06 10:14:44 +01:00
|
|
|
#s = line[line.index('Traceback'):]
|
2017-04-17 21:52:45 +02:00
|
|
|
in_traceback = True
|
2017-12-06 10:14:44 +01:00
|
|
|
#summary.add(t.red(s))
|
2017-04-17 21:52:45 +02:00
|
|
|
line = t.red(line)
|
|
|
|
if line.startswith('Exception'):
|
|
|
|
in_traceback = False
|
2017-01-20 01:01:29 +01:00
|
|
|
print line
|
2017-03-22 18:58:49 +01:00
|
|
|
|
2017-01-19 01:59:38 +01:00
|
|
|
process.stdout.close()
|
2017-04-19 09:54:53 +02:00
|
|
|
process.stderr.close()
|
2017-01-19 01:59:38 +01:00
|
|
|
process.wait()
|
2017-03-22 18:58:49 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
if summary:
|
|
|
|
print t.bold('\nWARNING AND ERROR SUMMARY:')
|
2017-01-21 00:48:30 +01:00
|
|
|
print '\n'.join(sorted(list(summary)))
|
2017-01-20 01:01:29 +01:00
|
|
|
return process.returncode
|
2017-01-19 01:59:38 +01:00
|
|
|
|
|
|
|
def execute(query, *args, **kwargs):
|
|
|
|
if not args:
|
|
|
|
args = kwargs
|
|
|
|
cursor.execute(query, args)
|
|
|
|
|
2017-01-24 09:54:14 +01:00
|
|
|
def run_trytond(to_install=None):
|
2017-01-19 01:59:38 +01:00
|
|
|
to_run = ['trytond/bin/trytond-admin', '-v', '-c', config_file]
|
|
|
|
if to_install:
|
|
|
|
to_run += ['-u']
|
|
|
|
to_run += to_install
|
|
|
|
to_run.append('--all')
|
|
|
|
to_run.append('-d')
|
|
|
|
to_run.append(database_name)
|
2017-01-20 01:01:29 +01:00
|
|
|
returncode = run(*to_run)
|
|
|
|
if returncode:
|
|
|
|
print t.red('Trytond update failed. Upgrade aborted.')
|
|
|
|
sys.exit(1)
|
|
|
|
return returncode
|
2017-01-19 01:59:38 +01:00
|
|
|
|
|
|
|
def table_exists(table):
|
|
|
|
execute('SELECT count(*) FROM information_schema.tables '
|
|
|
|
'WHERE table_name=%s', table)
|
|
|
|
return bool(cursor.fetchone()[0])
|
|
|
|
|
|
|
|
def field_exists(field):
|
|
|
|
table, field = field.split('.')
|
|
|
|
execute('SELECT count(*) FROM information_schema.columns '
|
|
|
|
'WHERE table_name=%s AND column_name=%s', table, field)
|
|
|
|
return bool(cursor.fetchone()[0])
|
|
|
|
|
2018-06-05 08:15:46 +02:00
|
|
|
def where_exists(query):
|
|
|
|
execute(query)
|
|
|
|
return bool(cursor.fetchone()[0])
|
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
def uninstall_modules():
|
|
|
|
to_uninstall = config.get('to_uninstall')
|
2017-01-19 01:59:38 +01:00
|
|
|
module_table = None
|
|
|
|
for table in ('ir_module_module', 'ir_module'):
|
|
|
|
if table_exists(table):
|
|
|
|
module_table = table
|
|
|
|
break
|
|
|
|
for module in to_uninstall:
|
|
|
|
print 'Module:', module
|
|
|
|
execute('DELETE FROM ' + module_table + '_dependency WHERE '
|
|
|
|
'module IN (SELECT id FROM ' + module_table + ' WHERE name=%s)',
|
|
|
|
module)
|
|
|
|
execute('DELETE FROM ' + module_table + ' WHERE name=%s', module)
|
|
|
|
|
|
|
|
execute('SELECT model, db_id FROM ir_model_data WHERE module=%s',
|
|
|
|
module)
|
|
|
|
for model, db_id in cursor.fetchall():
|
2018-02-27 12:34:09 +01:00
|
|
|
print 'DELETING', model, db_id
|
2018-05-30 12:52:10 +02:00
|
|
|
if model == 'res.user':
|
|
|
|
continue
|
2017-01-19 01:59:38 +01:00
|
|
|
execute('DELETE FROM "' + model.replace('.', '_')
|
|
|
|
+ '" WHERE id=%s', db_id)
|
2018-02-14 16:12:30 +01:00
|
|
|
|
2017-01-19 01:59:38 +01:00
|
|
|
execute('DELETE FROM ir_model_data WHERE module=%s', module)
|
|
|
|
|
2018-06-05 08:15:08 +02:00
|
|
|
if table_exists('babi_report'):
|
|
|
|
execute('DELETE from babi_filter_parameter where filter in'
|
|
|
|
' (SELECT id FROM babi_filter WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s)))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE FROM babi_filter WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE from babi_order where report in'
|
|
|
|
' (SELECT id FROM babi_report WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s)))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE from babi_measure where report in'
|
|
|
|
' (SELECT id FROM babi_report WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s)))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE from babi_dimension where expression in'
|
|
|
|
' (SELECT id FROM babi_expression WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s)))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE FROM babi_expression WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE FROM babi_report WHERE model IN (SELECT '
|
2018-02-27 12:34:09 +01:00
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s))' %
|
|
|
|
module_table)
|
|
|
|
|
2018-05-29 00:55:46 +02:00
|
|
|
if table_exists('mass_editing'):
|
|
|
|
execute('DELETE FROM mass_editing WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s))' %
|
|
|
|
module_table)
|
2018-02-14 16:12:30 +01:00
|
|
|
execute('DELETE FROM ir_trigger WHERE model IN (SELECT '
|
|
|
|
'id FROM ir_model WHERE module NOT IN (SELECT name FROM %s))' %
|
|
|
|
module_table)
|
2017-01-19 01:59:38 +01:00
|
|
|
execute('DELETE FROM ir_action_act_window WHERE res_model IN (SELECT '
|
|
|
|
'model FROM ir_model WHERE module NOT IN (SELECT name FROM %s))' %
|
|
|
|
module_table)
|
|
|
|
execute('DELETE FROM ir_action_wizard WHERE model in (SELECT model FROM '
|
|
|
|
'ir_model WHERE module NOT IN (SELECT name FROM %s))' % module_table)
|
|
|
|
execute('DELETE FROM ir_model WHERE module NOT IN (SELECT name FROM '
|
|
|
|
'%s)' % module_table)
|
|
|
|
execute('DELETE FROM ir_model_field WHERE module NOT IN (SELECT name FROM '
|
|
|
|
'%s)' % module_table)
|
|
|
|
execute('DELETE FROM ir_ui_view WHERE module NOT IN (SELECT name FROM '
|
|
|
|
'%s)' % module_table)
|
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
def process_actions(actions):
|
|
|
|
if not actions:
|
2017-01-19 01:59:38 +01:00
|
|
|
return
|
2017-01-20 01:01:29 +01:00
|
|
|
for action in actions:
|
|
|
|
if isinstance(action, dict):
|
|
|
|
tables = action.get('tables', '')
|
|
|
|
fields = action.get('fields', '')
|
2017-01-21 01:30:25 +01:00
|
|
|
version = action.get('version', '')
|
2017-01-20 01:01:29 +01:00
|
|
|
query = action.get('query')
|
|
|
|
script = action.get('script')
|
2018-06-05 08:15:46 +02:00
|
|
|
where = action.get('where')
|
2017-01-21 01:30:25 +01:00
|
|
|
|
|
|
|
# Check version
|
2017-03-13 17:44:31 +01:00
|
|
|
if version <= trytond_version and version > from_version:
|
2017-01-21 01:30:25 +01:00
|
|
|
continue
|
|
|
|
|
2018-05-30 12:52:10 +02:00
|
|
|
|
2017-01-21 01:30:25 +01:00
|
|
|
# Check tables
|
2017-01-19 01:59:38 +01:00
|
|
|
found = True
|
|
|
|
tables = tables.split()
|
|
|
|
for table in tables:
|
|
|
|
if not table_exists(table):
|
2017-01-20 01:01:29 +01:00
|
|
|
print "TABLE '%s' NOT FOUND" % table
|
2017-01-19 01:59:38 +01:00
|
|
|
found = False
|
|
|
|
break
|
|
|
|
if not found:
|
|
|
|
continue
|
|
|
|
|
2017-01-21 01:30:25 +01:00
|
|
|
# Check fields
|
2017-01-19 01:59:38 +01:00
|
|
|
found = True
|
|
|
|
fields = fields.split()
|
|
|
|
for field in fields:
|
|
|
|
if not field_exists(field):
|
2017-01-20 01:01:29 +01:00
|
|
|
print "FIELD '%s' NOT FOUND" % field
|
2017-01-19 01:59:38 +01:00
|
|
|
found = False
|
|
|
|
break
|
|
|
|
if not found:
|
|
|
|
continue
|
2018-06-05 08:15:46 +02:00
|
|
|
|
|
|
|
# Check where
|
|
|
|
if where and not where_exists(where):
|
|
|
|
print "WHERE '%s' NOT FOUND" % where
|
|
|
|
continue
|
2017-01-20 01:01:29 +01:00
|
|
|
else:
|
|
|
|
query = action
|
|
|
|
script = None
|
2017-03-15 09:10:53 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
if query:
|
|
|
|
query = query.replace('%', '%%')
|
|
|
|
print query
|
|
|
|
execute(query)
|
2017-03-15 09:10:53 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
if script:
|
2017-03-15 09:10:53 +01:00
|
|
|
if os.path.isfile(script):
|
|
|
|
run(script, database_name, args.config)
|
|
|
|
else:
|
2017-03-15 09:17:06 +01:00
|
|
|
print t.red("Not found script: %s" % script)
|
2017-01-19 01:59:38 +01:00
|
|
|
|
|
|
|
|
2017-01-23 22:57:04 +01:00
|
|
|
parser = argparse.ArgumentParser(description='Upgrade a Tryton database to the '
|
|
|
|
'version of the trytond library available.')
|
2017-01-20 01:01:29 +01:00
|
|
|
parser.add_argument('database', nargs=1, help='PostgreSQL database to upgrade')
|
2017-01-23 22:57:04 +01:00
|
|
|
parser.add_argument('from_version', nargs=1, help='Tryton version of the '
|
|
|
|
'database to be migrated')
|
2017-02-13 00:37:34 +01:00
|
|
|
parser.add_argument('-c', '--config', default=None,
|
2017-01-20 01:01:29 +01:00
|
|
|
help='path to the trytond configuration file')
|
2017-01-19 01:59:38 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
args = parser.parse_args()
|
2017-01-19 01:59:38 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
database_name, = args.database
|
2017-01-23 22:50:28 +01:00
|
|
|
from_version, = args.from_version
|
2017-02-13 00:37:34 +01:00
|
|
|
if not args.config:
|
|
|
|
instance = os.path.basename(os.path.realpath(os.path.join(
|
|
|
|
os.path.dirname(os.path.realpath(__file__)), '..')))
|
|
|
|
paths = (
|
|
|
|
'/etc/trytond/%s.conf' % instance,
|
|
|
|
os.environ.get('TRYTOND_CONFIG'),
|
|
|
|
)
|
|
|
|
for config_file in paths:
|
2017-02-13 00:46:25 +01:00
|
|
|
print 'Checking %s...' % config_file
|
2017-02-13 00:37:34 +01:00
|
|
|
if os.path.exists(config_file):
|
|
|
|
break
|
|
|
|
print "Configuration file: %s" % config_file
|
|
|
|
else:
|
|
|
|
config_file = args.config
|
2018-02-06 17:21:21 +01:00
|
|
|
if not os.path.isfile(config_file):
|
|
|
|
print 'Not found file %s' % config_file
|
|
|
|
sys.exit(1)
|
2017-01-19 01:59:38 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
url = get_url()
|
2017-01-19 01:59:38 +01:00
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
config = yaml.load(open('upgrades/config.yml', 'r').read())
|
|
|
|
config.setdefault('to_uninstall', [])
|
|
|
|
config.setdefault('to_install', [])
|
|
|
|
if os.path.exists('upgrade.yml'):
|
|
|
|
override = yaml.load(open('upgrade.yml', 'r').read())
|
|
|
|
config['to_install'] += override.get('to_install', [])
|
2018-07-05 20:18:14 +02:00
|
|
|
config['to_uninstall'] += override.get('to_uninstall', [])
|
2017-03-14 16:13:01 +01:00
|
|
|
config['before'] += override.get('before', [])
|
|
|
|
config['after'] += override.get('after', [])
|
2017-01-20 01:01:29 +01:00
|
|
|
|
|
|
|
if url.username:
|
|
|
|
connection = psycopg2.connect(dbname=database_name, host=url.hostname,
|
|
|
|
port=url.port, user=url.username, password=url.password)
|
|
|
|
else:
|
|
|
|
connection = psycopg2.connect(dbname=database_name)
|
|
|
|
|
|
|
|
cursor = connection.cursor()
|
2017-01-19 01:59:38 +01:00
|
|
|
|
2017-01-23 22:55:01 +01:00
|
|
|
print t.green('\nExecuting actions before update...')
|
|
|
|
process_actions(config.get('before'))
|
|
|
|
|
2017-01-20 01:01:29 +01:00
|
|
|
print t.green('\nUninstalling modules...')
|
|
|
|
uninstall_modules()
|
|
|
|
|
|
|
|
connection.commit()
|
|
|
|
|
|
|
|
print t.green('\nUpdating trytond...')
|
2017-01-24 09:54:14 +01:00
|
|
|
run_trytond(config.get('to_install'))
|
2017-01-20 01:01:29 +01:00
|
|
|
|
|
|
|
print t.green('\nExecuting actions after update...')
|
|
|
|
process_actions(config.get('after'))
|
2017-01-19 01:59:38 +01:00
|
|
|
connection.commit()
|
2017-01-23 23:33:16 +01:00
|
|
|
|
|
|
|
print t.green('\nUpdating trytond again...')
|
|
|
|
run_trytond()
|