You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

migrate.py 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #! /usr/bin/env python3
  2. import argparse
  3. import sys
  4. import os
  5. import os.path
  6. import re
  7. import distutils.version
  8. import subprocess
  9. def file_runner_pgsql(folder, file):
  10. subprocess.call(['psql', '-f', os.path.join(folder, file)])
  11. def file_runner_pgsql_gz(folder, file):
  12. p1 = subprocess.Popen(['gunzip', '-c', os.path.join(folder, file)], stdout=subprocess.PIPE)
  13. p2 = subprocess.Popen(["psql"], stdin=p1.stdout)
  14. p1.stdout.close()
  15. p2.communicate()
  16. def file_runner_exec(folder, file):
  17. subprocess.call([os.path.join(folder, file)])
  18. def list_files(folder):
  19. files = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]
  20. files.sort()
  21. return files
  22. def sort_versions(versions):
  23. versions.sort(key=distutils.version.StrictVersion)
  24. def list_versions(files):
  25. versions = []
  26. for file in files:
  27. version = file.split('_')[0]
  28. if re.match('^[0-9]+\.[0-9]+\.[0-9]+$', version):
  29. if version not in versions:
  30. versions.append(version)
  31. sort_versions(versions)
  32. return versions
  33. def run_file(folder, file, no_run, file_runners):
  34. can_run = False
  35. for file_runner_ext in file_runners:
  36. if file.endswith(file_runner_ext):
  37. print('Running file %s' % (file))
  38. can_run = True
  39. if not no_run:
  40. try:
  41. sys.stdout.flush()
  42. file_runners[file_runner_ext](folder, file)
  43. except Exception as e:
  44. print('Failed to run file: %s' % (e))
  45. if not can_run:
  46. print('Ignoring file %s' % (file))
  47. def run_migration(folder, files, version, no_run, file_runners):
  48. for file in files:
  49. if file.startswith('%s_' % (version)):
  50. run_file(folder, file, no_run, file_runners)
  51. def run_migrations(folder, version_from, version_to, no_run, file_runners):
  52. files = list_files(folder)
  53. versions = list_versions(files)
  54. if not versions:
  55. print('No migration available. Exiting.')
  56. return 0
  57. if version_to is None:
  58. version_to = versions[-1]
  59. print('Defaulting VERSION_TO to %s' % (version_to))
  60. if version_to not in versions:
  61. print('Could not find VERSION_TO %s' % (version_to))
  62. return 1
  63. if version_from is not None and version_from not in versions:
  64. print('Could not find VERSION_FROM %s' % (version_from))
  65. return 1
  66. if no_run:
  67. print('Not running because --no-run was specified')
  68. if version_from is None:
  69. print('Initializing from %s (inclusive) to %s (inclusive)' % (versions[0], version_to))
  70. else:
  71. print('Migrating from %s (exclusive) to %s (inclusive)' % (version_from, version_to))
  72. version_from_obj = distutils.version.StrictVersion('0.0.0' if version_from is None else version_from)
  73. version_to_obj = distutils.version.StrictVersion(version_to)
  74. last_version = version_from
  75. for version in versions:
  76. version_obj = distutils.version.StrictVersion(version)
  77. if version_from_obj < version_obj and version_obj <= version_to_obj:
  78. if last_version is None:
  79. print('Initialising %s' % (version))
  80. else:
  81. print('Migrating from %s to %s' % (last_version, version))
  82. run_migration(folder, files, version, no_run, file_runners)
  83. last_version = version
  84. if version_from is None:
  85. print('Initialized from %s (inclusive) to %s (inclusive)' % (versions[0], version_to))
  86. else:
  87. print('Migrated from %s (exclusive) to %s (inclusive)' % (version_from, version_to))
  88. return 0
  89. def main():
  90. parser = argparse.ArgumentParser(description='Migrate container configuration/data')
  91. parser.add_argument('--folder', dest='folder', default='/docker-entrypoint-initdb.d/', help='Folder to use for migration')
  92. parser.add_argument('--init', dest='is_init', default=False, action='store_true', help='Run all migrations')
  93. parser.add_argument('--migrate', dest='is_migrate', default=False, action='store_true', help='Run all migrations between VERSION_FROM (exclusive) and VERSION_TO (inclusive)')
  94. parser.add_argument('--no-run', dest='no_run', default=False, action='store_true', help='Do not run migration, just print')
  95. parser.add_argument('--version-from', dest='version_from', default=None, help='Current version, required if using --migrate, can not be used --init')
  96. parser.add_argument('--version-to', dest='version_to', default=None, help='Final version, default to last available version is not given')
  97. args = parser.parse_args()
  98. file_runners = {
  99. 'sql': file_runner_pgsql,
  100. 'sql.gz': file_runner_pgsql_gz,
  101. 'sh': file_runner_exec
  102. }
  103. if args.is_init and args.is_migrate:
  104. print('--init and --migrate can not be used together')
  105. return 64
  106. if args.is_init:
  107. if args.version_from is not None:
  108. print('--version-from can not be used with --init')
  109. return 64
  110. return run_migrations(args.folder, None, args.version_to, args.no_run, file_runners)
  111. elif args.is_migrate:
  112. if args.version_from is None:
  113. print('--version-from is required. Use --init to run all migrations')
  114. return 64
  115. return run_migrations(args.folder, args.version_from, args.version_to, args.no_run, file_runners)
  116. else:
  117. print('Missing --init or --migrate')
  118. return 64
  119. if __name__ == '__main__':
  120. sys.exit(main())