2 """Main module to execute Mammoth to Odyssey Data Migration. 4 usage: ody_migr_executor.py [-h] [--secret SECRET] [--reset_and_migrate] 5 [--verbose] [--disable_logging] 6 {www4,www3,www5,www6} {clean,migrate,summary} cu 7 {settings,memdata,memhist,admin} username password 9 Argument Parser for Mammoth to Odyssey Data Migration. 13 server code for Mammoth endpoint 14 {clean,migrate,summary} 16 cu target Credit Union <CUCODE> to migrate (upper case 18 {settings,memdata,memhist,admin,loanapp} 19 data category to migrate 20 username mammoth monitor username 21 password mammoth monitor password (base64 encoded) 24 -h, --help show this help message and exit 25 --secret SECRET, -s SECRET 26 pass secret key as optional argument instead 27 (alternative to environment variable) 28 --reset_and_migrate, -reset 29 clean db tables and commit before migration) 30 --verbose, -v output verbosity 31 --disable_logging, -dl 32 disable logging, logs only critical messages 42 All the data migration scripts(app/tools/bin/ody_migr_*.py) have a shelf life. 43 Their usage is limited to one-time data migration of CU and its members from 44 Mammoth to Odyssey. We do not intend to maintain these scripts as-is after 45 migrations are complete. 57 from logging
import FileHandler, StreamHandler, Formatter
58 from ody_migr_settings
import migrate_settings
59 from ody_migr_members
import migrate_members
60 from ody_migr_admin
import migrate_admin
61 from ody_migr_loans
import migrate_loanapps
62 from ody_migr_utils
import file_exc_decorator
63 from ody_migr_mmth_endpoint
import MammothMigration
65 import ody_migr_db_handler
as pg_handler
66 from psycopg2.extras
import RealDictCursor
67 from ody_migr_transaction
import pg_crsr_hndlr_decrtr
69 from ody_migr_config
import (ENV_MIGR_SECRET_KEY,
75 DATA_OPT_SWITCH_ACCOUNTS,
87 LOGGER = logging.getLogger(__name__)
90 MIGRATION_ENTRYPOINTS = {
91 DATA_OPT_SETTINGS: migrate_settings,
92 DATA_OPT_MEMDATA: migrate_members,
93 DATA_OPT_SWITCH_ACCOUNTS: migrate_members,
94 DATA_OPT_MEMHIST: migrate_members,
95 DATA_OPT_ADMIN: migrate_admin,
96 DATA_OPT_LOANAPP: migrate_loanapps
102 """get file/directory user/group ownership detail""" 103 stat_info = os.stat(_directory)
104 user = pwd.getpwuid(stat_info.st_uid)[0]
105 group = grp.getgrgid(stat_info.st_gid)[0]
111 """Return a logging file handler with specified user/group ownership""" 116 uid = pwd.getpwnam(owner[0]).pw_uid
117 gid = grp.getgrnam(owner[1]).gr_gid
119 if not os.path.exists(_file_name):
120 open(_file_name,
'a').close()
121 os.chown(_file_name, *owner)
122 return FileHandler(_file_name, mode)
132 """Setup logging configuration and handlers.""" 133 log_file_dir = os.path.dirname(_log_file)
136 if not os.path.exists(log_file_dir):
137 err_msg = (
"Log directory `{}` does not exist. " 138 "MAKE SURE OF THE FOLLOWING: " 139 "(i) `/home/{}/*` directories are in place (perhaps, `{}` " 140 "is not created in Odyssey?). " 141 "(ii) CUCODE `{}` is a valid Mammoth target for migration " 143 "(iii) CUCODE `{}` is an intended Odyssey source " 144 "for migration.").format(
151 raise Exception(err_msg)
156 fh.setFormatter(formatter)
157 LOGGER.addHandler(fh)
160 if(_disable_logging):
161 logging.basicConfig(level=logging.CRITICAL, handlers=handlers)
165 logging.basicConfig(level=logging.DEBUG, handlers=handlers)
167 logging.basicConfig(level=logging.INFO, handlers=handlers)
171 """Argument parser for the main executor""" 172 parser = argparse.ArgumentParser(
173 description=
"Argument Parser for Mammoth to Odyssey Data Migration.")
178 choices=SERVER_CHOICES,
179 help=
"server code for Mammoth endpoint" 185 choices=ACTION_CHOICES,
186 help=
"migration action" 192 help=
"target Credit Union <CUCODE> to migrate (upper case required)" 198 choices=DATA_CHOICES,
199 help=
"data category to migrate" 205 help=
"mammoth monitor username" 211 help=
"mammoth monitor password (base64 encoded)" 218 help=(
"pass secret key as optional argument instead " 219 "(alternative to environment variable)")
223 "--reset_and_migrate",
225 help=
"clean db tables and commit before migration)",
232 help=
"output verbosity",
239 help=
"disable logging, logs only critical messages",
246 @pg_crsr_hndlr_decrtr
248 """Return a list of valid CUs from cuinfo""" 249 sql = (
"select user_name, www_server, case " 250 " when (coalesce(system_options, 0) & {}) <> 0 then 'Live'" 251 " when (coalesce(system_options, 0) & {}) <> 0 then 'Batch'" 252 " when (coalesce(system_options, 0) & {}) <> 0 then 'Web Only'" 253 " else 'Other' end as cutype from cuinfo" 254 " where (coalesce(system_options, 0) & {}) = 0" 255 " order by user_name".format(SYS_TYPE_LIVE,
260 with pg_handler.PGSession()
as conn:
261 with conn.cursor(cursor_factory=RealDictCursor)
as cur:
263 valid_cu_list = cur.fetchall()
268 """Main entrypoint for all the migration operations.""" 273 if os.getenv(ENV_MIGR_SECRET_KEY)
is None:
274 if args.secret
is None:
275 error_msg = (
"SECRET KEY is required (either supply as --secret" 276 " argument or set `{}` env variable.)".format(
277 ENV_MIGR_SECRET_KEY))
278 LOGGER.error(error_msg)
279 raise SystemExit(error_msg)
280 os.environ[ENV_MIGR_SECRET_KEY] = args.secret.strip()
282 if os.getenv(ENV_MIGR_SECRET_KEY)
is None:
283 error_msg =
"Environment variable: `{}` is not set!".format(
285 LOGGER.error(error_msg)
286 raise SystemExit(error_msg)
288 requested_cu = args.cu.strip()
290 log_formatter = Formatter(
'%(asctime)s ' 291 '%(levelname)-4s [{}-{}-{}-{}] ' 292 '(%(name)s|%(funcName)s:%(lineno)d) :: ' 294 .format(args.server.strip().upper(),
295 args.data_category.strip().upper(),
296 args.action.strip().upper(),
300 log_file = LOG_FILE_NAME.format(
301 requested_cu.lower(),
302 args.action.strip().lower(),
303 args.data_category.strip().lower())
307 args.disable_logging,
315 LOGGER.info(
"MIGRATION STARTED: [Data Category: {}, " 316 "Operation: {}, CU: {}]".format(
317 args.data_category.strip().upper(),
318 args.action.strip().upper(),
322 if args.action.strip() ==
"migrate":
324 if requested_cu
in SUPPORTED_DEV_CU
and args.server.strip() ==
"www4":
325 LOGGER.warning(
"Relaxing validation for DEV CUs. " 326 "Allowed DEV CUs are: {}".format(SUPPORTED_DEV_CU))
328 allow_cu_migration =
False 333 for cu_info
in all_cu_list_monitor:
335 if cu_info[
'cutype'].strip()
in [
"Live"]
and cu_info[
336 'user_name'].strip().upper() == requested_cu
and cu_info[
337 'www_server'].strip() == args.server.strip():
338 allow_cu_migration =
True 339 valid_cu_info = cu_info
344 if(allow_cu_migration):
345 LOGGER.info(
"CUCODE `{}` [{}] is valid for " 346 "server `{}` in production." 348 valid_cu_info[
'user_name'].strip(),
349 valid_cu_info[
'cutype'].strip(),
350 valid_cu_info[
'www_server'].strip())
353 error_msg = (
"CUCODE `{}` is not valid for " 354 "server `{}` in production." 355 .format(requested_cu, args.server.strip()))
356 LOGGER.error(error_msg)
357 raise SystemExit(error_msg)
362 args.password = base64.b64decode(args.password.strip()).decode()
369 args.username.strip(),
375 if args.data_category
in [DATA_OPT_MEMHIST, DATA_OPT_MEMDATA,
376 DATA_OPT_SWITCH_ACCOUNTS]:
377 args_migration.append(args.data_category)
378 args_migration.append(args.reset_and_migrate)
381 elif args.data_category
in [DATA_OPT_ADMIN, DATA_OPT_LOANAPP]:
382 args_migration.append(args.reset_and_migrate)
385 migration_exec_resp = MIGRATION_ENTRYPOINTS[args.data_category](*args_migration)
387 LOGGER.info(
"MIGRATION DONE: [Data Category: {}, " 388 "Operation: {}, CU: {}]".format(
389 args.data_category.strip().upper(),
390 args.action.strip().upper(),
392 return migration_exec_resp
395 if __name__ ==
"__main__":
def setup_logging(_verbose, _disable_logging, formatter, _cu, _server, _log_file)
def owned_file_handler(_file_name, mode='w', owner=None)
def get_ownership_detail(_directory)