Patch Handler rewrite
This commit is contained in:
parent
e266c6ba29
commit
d691df8b8e
2 changed files with 294 additions and 122 deletions
|
|
@ -1,136 +1,308 @@
|
|||
"""
|
||||
Execute patch files.
|
||||
|
||||
Patch files must be in the "patches" module specified by "modules_path"
|
||||
To run directly
|
||||
|
||||
python lib/wnf.py patch patch1, patch2 etc
|
||||
python lib/wnf.py patch -f patch1, patch2 etc
|
||||
|
||||
where patch1, patch2 is module name
|
||||
Execute Patch Files
|
||||
|
||||
Patch files usually lie in the "patches" module specified by "modules_path" in defs.py
|
||||
|
||||
"""
|
||||
import unittest
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import webnotes
|
||||
import webnotes.defs
|
||||
import webnotes.utils
|
||||
import webnotes.utils.email_lib
|
||||
from webnotes.db import Database
|
||||
|
||||
|
||||
def run(patch_list, overwrite = 0, log_exception=1, conn = '', db_name = '', db_password = ''):
|
||||
import webnotes, webnotes.defs
|
||||
|
||||
print patch_list
|
||||
class PatchHandler:
|
||||
def __init__(self, **kwargs):
|
||||
"""
|
||||
Gets connection to database
|
||||
|
||||
# db connection
|
||||
if not conn:
|
||||
dbn = db_name or webnotes.defs.default_db_name
|
||||
pwd = db_password or (hasattr(webnotes.defs, 'get_db_password') and webnotes.defs.get_db_password(dbn)) or (hasattr(webnotes.defs, 'db_password') and webnotes.defs.db_password)
|
||||
connect_db(dbn, pwd)
|
||||
else:
|
||||
webnotes.conn = conn
|
||||
|
||||
# session
|
||||
if not (webnotes.session and webnotes.session['user']):
|
||||
webnotes.session = {'user':'Administrator'}
|
||||
|
||||
# no patches on accounts
|
||||
if webnotes.conn.cur_db_name=='accounts':
|
||||
return
|
||||
|
||||
# check if already applied
|
||||
if not overwrite:
|
||||
patch_list = check_already_applied_patch(patch_list)
|
||||
|
||||
block_user("Patches are being executed, please try again in a few minutes")
|
||||
|
||||
try:
|
||||
for p in patch_list:
|
||||
# execute patch
|
||||
execute_patch(p, log_exception)
|
||||
|
||||
except Exception, e:
|
||||
webnotes.conn.rollback()
|
||||
if log_exception:
|
||||
write_log()
|
||||
else:
|
||||
print webnotes.getTraceback()
|
||||
finally:
|
||||
# unblock
|
||||
block_user()
|
||||
|
||||
def block_user(msg=None):
|
||||
"""stop further executions till patch is run"""
|
||||
import webnotes
|
||||
webnotes.conn.begin()
|
||||
webnotes.conn.set_global('__session_status', msg and 'stop' or None)
|
||||
webnotes.conn.set_global('__session_status_message', msg or None)
|
||||
webnotes.conn.commit()
|
||||
|
||||
|
||||
def execute_patch(p, log_exception):
|
||||
import webnotes
|
||||
|
||||
webnotes.conn.begin()
|
||||
|
||||
exec('import ' + p)
|
||||
eval(p).execute()
|
||||
|
||||
# update patch log table
|
||||
webnotes.conn.sql("insert into `__PatchLog` (patch, applied_on) values (%s, now())", p)
|
||||
|
||||
webnotes.conn.commit()
|
||||
|
||||
print "Patch: %s applied successfully..." % p
|
||||
|
||||
def check_already_applied_patch(patch_list):
|
||||
"""
|
||||
Remove if patch already applied
|
||||
"""
|
||||
import webnotes
|
||||
|
||||
try:
|
||||
already_patched = [d[0] for d in webnotes.conn.sql("select distinct patch from `__PatchLog`")]
|
||||
except Exception, e:
|
||||
if e.args[0]==1146:
|
||||
webnotes.conn.sql("create table if not exists `__PatchLog` (patch TEXT, applied_on DATETIME)")
|
||||
check_already_applied_patch(patch_list)
|
||||
return
|
||||
else:
|
||||
Arguments can be:
|
||||
* db_name
|
||||
* print_enabled --> to enabled printing
|
||||
"""
|
||||
self.print_enabled = kwargs.get('print_enabled')
|
||||
try:
|
||||
self.db_name = kwargs.get('db_name')
|
||||
webnotes.conn = Database(user=self.db_name)
|
||||
webnotes.conn.use(self.db_name)
|
||||
if not (webnotes.session and webnotes.session['user']):
|
||||
webnotes.session = {'user': 'Administrator'}
|
||||
except Exception, e:
|
||||
self.log(log_type='error', msg=webnotes.getTraceback())
|
||||
raise e
|
||||
|
||||
|
||||
def run_patch(self, **kwargs):
|
||||
"""
|
||||
Run a patch on a db
|
||||
|
||||
Arguments can be:
|
||||
* patch_module
|
||||
* patch_file
|
||||
* force
|
||||
|
||||
* It gets a connection to the db having name db_name
|
||||
and assigns the connection to webnotes.conn
|
||||
* If force == 0, it checks if the patch is already executed or not
|
||||
* It imports patch_file from patch_module
|
||||
* It runs the execute function of patch_file
|
||||
"""
|
||||
try:
|
||||
patch_module = kwargs.get('patch_module', 'patches')
|
||||
patch_file = kwargs.get('patch_file')
|
||||
module_file = str(patch_module) + "." + str(patch_file)
|
||||
executed_patches = [p['patch'] for p in self.get_executed_patch_list()]
|
||||
|
||||
pending_patch = []
|
||||
if kwargs.get('force') or module_file not in executed_patches:
|
||||
webnotes.conn.begin()
|
||||
|
||||
module = __import__(module_file)
|
||||
patch = getattr(module, patch_file)
|
||||
getattr(patch, 'execute')()
|
||||
|
||||
self.log(log_type='success', patch_module=patch_module, patch_file=patch_file)
|
||||
webnotes.conn.commit()
|
||||
|
||||
except Exception, e:
|
||||
webnotes.conn.rollback()
|
||||
self.log(log_type='error', patch_module=patch_module, patch_file=patch_file)
|
||||
|
||||
|
||||
def reload(self, **kwargs):
|
||||
"""
|
||||
Reloads a particular doc in the system
|
||||
|
||||
Arguments can be:
|
||||
* module
|
||||
* doc_type
|
||||
* doc_name
|
||||
"""
|
||||
try:
|
||||
module = kwargs.get('module')
|
||||
doc_type, doc_name = kwargs.get('doc_type'), kwargs.get('doc_name')
|
||||
reload_string = 'Module: %s, DocType: %s, DocName: %s' % (module, doc_type, doc_name)
|
||||
|
||||
from webnotes.modules.module_manager import reload_doc
|
||||
reload_doc(module, doc_type, doc_name)
|
||||
|
||||
self.log(log_type='info', msg='Reload successful. ' + reload_string)
|
||||
|
||||
except Exception, e:
|
||||
self.log(log_type='error', msg='Reload error. ' + reload_string)
|
||||
|
||||
|
||||
def get_executed_patch_list(self):
|
||||
"""
|
||||
Gets a list of dict of patches already executed in a db
|
||||
The list is in chronological order
|
||||
"""
|
||||
try:
|
||||
return webnotes.conn.sql("""\
|
||||
SELECT * from __PatchLog
|
||||
ORDER BY applied_on ASC""", as_dict=1)
|
||||
except Exception, e:
|
||||
try:
|
||||
if e.args[0]==1146:
|
||||
self.create_patch_log_table()
|
||||
return []
|
||||
else:
|
||||
self.log(log_type='error')
|
||||
except Exception, e:
|
||||
self.log(log_type='error')
|
||||
|
||||
|
||||
def run(self, **kwargs):
|
||||
"""
|
||||
Runs a patch or reload on a db
|
||||
|
||||
Arguments can be:
|
||||
* Either one of
|
||||
+ patch_list --> [{
|
||||
'patch_module': <module>,
|
||||
'patch_file': <file>,
|
||||
'force': True/False
|
||||
}, ...]
|
||||
+ run_latest --> can be any true value
|
||||
|
||||
Before beginning, block user
|
||||
After completion, unblock user
|
||||
"""
|
||||
self.block_user(True, msg="Patches are being executed in the system. Please try again in a few minutes.")
|
||||
|
||||
if kwargs.get('patch_list'):
|
||||
for patch in kwargs.get('patch_list'):
|
||||
self.run_patch(patch_module=patch.get('patch_module', 'patches'), patch_file=patch.get('patch_file'), force=kwargs.get('force'))
|
||||
|
||||
elif kwargs.get('run_latest'):
|
||||
try:
|
||||
from patches.patch_list import patch_list
|
||||
for patch in patch_list:
|
||||
self.run_patch(patch_module=patch.get('patch_module', 'patches'), patch_file=patch.get('patch_file'))
|
||||
except Exception, e:
|
||||
self.log(log_type='error')
|
||||
|
||||
|
||||
self.block_user(False)
|
||||
|
||||
|
||||
for p in patch_list:
|
||||
if p not in already_patched:
|
||||
pending_patch.append(p)
|
||||
def log(self, **kwargs):
|
||||
"""
|
||||
Logs successful patches in the database table __PatchLog
|
||||
|
||||
Logs exceptions in patches.patch.log
|
||||
|
||||
return pending_patch
|
||||
Also, if self.print_enabled is true, then it prints what gets logged
|
||||
|
||||
def connect_db(db_name, pwd):
|
||||
Arguments can be:
|
||||
* log_type = success/error/info
|
||||
* patch_module
|
||||
* patch_file
|
||||
* msg
|
||||
"""
|
||||
log_type = kwargs.get('log_type')
|
||||
patch_module = kwargs.get('patch_module')
|
||||
patch_file = kwargs.get('patch_file')
|
||||
patch = str(patch_module) + "." + str(patch_file)
|
||||
|
||||
if log_type == 'success':
|
||||
webnotes.conn.sql("""\
|
||||
INSERT INTO `__PatchLog`
|
||||
VALUES (%s, now())""", patch)
|
||||
if self.print_enabled: print 'Patch: %s applied successfully' % patch
|
||||
|
||||
elif log_type == 'error' or log_type == 'info':
|
||||
args = {
|
||||
'log_type': log_type,
|
||||
'patch_module': patch_module,
|
||||
'patch_file': patch_file,
|
||||
'msg': kwargs.get('msg')
|
||||
}
|
||||
patch_msg = self.write_log(**args)
|
||||
if self.print_enabled: print patch_msg
|
||||
|
||||
|
||||
|
||||
def write_log(self, **kwargs):
|
||||
"""
|
||||
Write error/info details into patch.log file
|
||||
|
||||
Arguments can be:
|
||||
* log_type = error/info
|
||||
* patch_module
|
||||
* patch_file
|
||||
* msg
|
||||
"""
|
||||
|
||||
log_type = kwargs.get('log_type')
|
||||
patch_module = kwargs.get('patch_module')
|
||||
patch_file = kwargs.get('patch_file')
|
||||
msg = kwargs.get('msg')
|
||||
try:
|
||||
company = str(webnotes.utils.get_defaults().get('company'))
|
||||
except Exception, e:
|
||||
company = None
|
||||
|
||||
patch_log = open(os.path.join(webnotes.defs.modules_path, 'patches', 'patch.log'), 'a')
|
||||
patch_msg = "\n" + (log_type=='error' and "[Error] " or "[Info] ") + \
|
||||
(company and ('Company: ' + company + " | ") or "Company: Unknown | ") + \
|
||||
'DB Name: ' + str(self.db_name) + "\n" + \
|
||||
(patch_module and \
|
||||
("Patch Module: " + patch_module + " | Patch File: " + patch_file + "\n") \
|
||||
or "") + \
|
||||
(msg and ("Message: " + str(msg) + "\n") or "") + \
|
||||
(log_type=='error' and ("\n" + webnotes.getTraceback() + "\n") or "")
|
||||
patch_log.write(patch_msg)
|
||||
patch_log.close()
|
||||
try:
|
||||
if log_type=='error' and getattr(webnotes.defs, 'admin_email_notification', 0):
|
||||
webnotes.utils.email_lib.sendmail(\
|
||||
recipients='developers@erpnext.com', \
|
||||
sender='exception+patches@erpnext.com', \
|
||||
subject='Patch Error' + \
|
||||
(patch_module and (": " + patch_module + "." + patch_file) or ""), \
|
||||
msg=patch_msg,
|
||||
reply_to='support@erpnext.com',
|
||||
from_defs=1
|
||||
)
|
||||
except Exception, e:
|
||||
print e
|
||||
finally:
|
||||
return patch_msg
|
||||
|
||||
|
||||
def create_patch_log_table(self):
|
||||
"""
|
||||
Create __PatchLog table
|
||||
"""
|
||||
webnotes.conn.sql("""\
|
||||
CREATE TABLE IF NOT EXISTS `__PatchLog` (
|
||||
patch TEXT,
|
||||
applied_on DATETIME
|
||||
)""")
|
||||
self.log(log_type='info', msg='Table __PatchLog created successfully')
|
||||
|
||||
|
||||
def block_user(self, block, **kwargs):
|
||||
"""
|
||||
Block user when patches are being applied
|
||||
|
||||
Arguments can be:
|
||||
* block --> True/False (Mandatory!)
|
||||
* msg --> Message to be displayed to user
|
||||
"""
|
||||
webnotes.conn.begin()
|
||||
|
||||
if block:
|
||||
webnotes.conn.set_global('__session_status', 'stop')
|
||||
webnotes.conn.set_global('__session_status_message', kwargs.get('msg'))
|
||||
else:
|
||||
webnotes.conn.set_global('__session_status', None)
|
||||
webnotes.conn.set_global('__session_status_message', None)
|
||||
|
||||
webnotes.conn.commit()
|
||||
|
||||
|
||||
class TestPatchHandler(unittest.TestCase):
|
||||
"""
|
||||
Connect database
|
||||
Tests patch handler functions
|
||||
"""
|
||||
import webnotes
|
||||
import webnotes.db
|
||||
webnotes.conn = webnotes.db.Database(user=db_name, password=pwd)
|
||||
webnotes.conn.use(db_name)
|
||||
def setUp(self):
|
||||
self.patch_module = 'patches'
|
||||
self.patch_file = 'reload_print_format'
|
||||
webnotes.conn = None
|
||||
self.patch_list = [
|
||||
{
|
||||
'patch_module': 'patches',
|
||||
'patch_file': 'reload_project_task'
|
||||
},
|
||||
{
|
||||
'patch_module': 'patches',
|
||||
'patch_file': 'reload_print_format'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def write_log():
|
||||
import os
|
||||
import webnotes.defs
|
||||
import webnotes
|
||||
|
||||
patch_log = open(os.path.join(webnotes.defs.modules_path, 'patches', 'patch.log'), 'a')
|
||||
patch_log.write(('\n\nError in %s:\n' % webnotes.conn.cur_db_name) + webnotes.getTraceback())
|
||||
patch_log.close()
|
||||
|
||||
if getattr(webnotes.defs,'admin_email_notification',0):
|
||||
from webnotes.utils import sendmail
|
||||
subj = 'Patch Error. <br>Account: %s' % webnotes.conn.cur_db_name
|
||||
msg = subj + '<br><br>' + webnotes.getTraceback()
|
||||
print msg
|
||||
#sendmail(['nabin@erpnext.com'], sender='automail@erpnext.com', subject= subj, parts=[['text/plain', msg]])
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
if __name__=='__main__':
|
||||
import sys, os
|
||||
|
||||
# webnotes path
|
||||
sys.path.append('lib/py')
|
||||
|
||||
run(sys.argv[1:])
|
||||
def test_run_patch(self):
|
||||
self.ph = PatchHandler(db_name='frappe', print_enabled=1)
|
||||
#self.ph.run_patch(patch_module=self.patch_module, patch_file=self.patch_file, force=1)
|
||||
|
||||
|
||||
def test_run(self):
|
||||
self.ph = PatchHandler(db_name='frappe', print_enabled=1)
|
||||
self.ph.run(patch_list=self.patch_list)
|
||||
self.ph.run(run_latest=True)
|
||||
|
||||
|
||||
def test_reload(self):
|
||||
self.ph = PatchHandler(db_name='frappe', print_enabled=1)
|
||||
self.ph.reload(module='Core', doc_type='DocType', doc_name='Print Format')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -530,8 +530,8 @@ def clear_recycle_bin():
|
|||
def send_error_report():
|
||||
sql = webnotes.conn.sql
|
||||
m = ''
|
||||
acc_id = webnotes.conn.get_value('Control Panel',None,'account_id') or ''
|
||||
if acc_id: m = 'Account Id : '+acc_id
|
||||
company = webnotes.conn.get_value('Control Panel',None,'company_name') or ''
|
||||
if company: m = 'Company : ' + company
|
||||
form = webnotes.form
|
||||
err_msg = '''
|
||||
%s <br>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue