Guest User

Untitled

a guest
Apr 22nd, 2018
115
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.30 KB | None | 0 0
  1. import datetime
  2. import logging
  3. import os.path
  4. import os
  5.  
  6. from django.core.management.base import BaseCommand, CommandError
  7. from django.core.files.storage import get_storage_class
  8. from django.core.files import File
  9. from django.conf import settings
  10. from django.utils import timezone as tz
  11. from django.utils.text import slugify
  12.  
  13.  
  14. class Command(BaseCommand):
  15.  
  16. """
  17. Usage:
  18.  
  19. To generate a database backup of the default database, and send it to the default file storage, run the following command:
  20. > python manage.py backup_databases
  21.  
  22. All available options:
  23. -m Is the max number of days to retain a database backup, defaults to 14 days
  24. -s Is the name of the settings variable that defines which storage backend to use for the backups. Defaults to 'DEFAULT_FILE_STORAGE'
  25. -d Is the name of the database settings to use for the backup, defaults to 'default'.
  26. -f Is an optional filename prefix for the database backup file. If specified, the file format will be: <prefix>_<datetime>.sql.zip If it's not specified, the host and database name will be used:
  27. <host>_<datbasename>_<datetime>.sql.zip
  28. -t Is the temporary directory to store the database backup before it's put in storage. Defaults to /tmp
  29. -p Is the the filename pattern to use to specify the datetime. Defaults to %Y-%m-%d_%H-%M-%S
  30.  
  31. To generate a database backup lasting up to 90 days and send it to a special storage bucket, run the following command:
  32. > python manage.py backup_databases -s=DATABASE_BACKUP_STORAGE -m=90
  33.  
  34. """
  35.  
  36. # Possible Arguments:
  37. max_days = None
  38. storage_backend_setting = None
  39. database_to_backup = None
  40. filename_prefix = None
  41. storage_directory = None
  42. temporary_directory = None
  43. filename_date_pattern = None
  44.  
  45. # Determine at runtime:
  46. database_settings = None
  47. backup_filename = None
  48. temporary_filename = None
  49. temporary_zip_filename = None
  50. storage_backend = None
  51. stored_file_url = None
  52.  
  53. def add_arguments(self, parser):
  54.  
  55. parser.add_argument('-m', '-max_days', action='store', dest='max_days',
  56. help='Max age of backups in days', type=int, default=14)
  57. parser.add_argument('-s', '-storage_backend_setting', action='store', dest='storage_backend_setting',
  58. help='Setting name for storage', type=str, default='DEFAULT_FILE_STORAGE')
  59. parser.add_argument('-d', '-database_to_backup', action='store', dest='database_to_backup',
  60. help='Database setting name to be backed up', type=str, default='default',)
  61. parser.add_argument('-f', '-filename_prefix', action='store', dest='filename_prefix',
  62. help='Prefix backups filename', type=str)
  63. parser.add_argument('-g', '-storage_directory', action='store', dest='storage_directory',
  64. help='Directory to store files within storage', type=str)
  65. parser.add_argument('-t', '-temporary_directory', action='store', dest='temporary_directory',
  66. help='Directory to temporarily store backup', type=str, default='/tmp',)
  67. parser.add_argument('-p', '-filename_date_pattern', action='store', dest='filename_date_pattern',
  68. help='Directory to temporarily store backup', type=str, default='%Y-%m-%d_%H-%M-%S',)
  69.  
  70. def handle(self, *args, **options):
  71.  
  72. self.max_days = int(options['max_days'])
  73. self.storage_backend_setting = str(
  74. options['storage_backend_setting'] or '')
  75. self.database_to_backup = str(options['database_to_backup'] or '')
  76. self.filename_prefix = str(options['filename_prefix'] or '')
  77. self.storage_directory = str(
  78. options['storage_directory'] or '')
  79. self.temporary_directory = str(options['temporary_directory'] or '')
  80. self.filename_date_pattern = str(
  81. options['filename_date_pattern'] or '')
  82.  
  83. self.database_settings = settings.DATABASES[self.database_to_backup]
  84.  
  85. self.backup_filename = self.get_file_backup_name(
  86. self.filename_prefix, self.database_settings, self.filename_date_pattern)
  87.  
  88. self.temporary_filename = os.path.join(
  89. self.temporary_directory, self.backup_filename)
  90. logging.debug(u"Temporary database backup file name is %s" %
  91. (self.temporary_filename))
  92.  
  93. storage_name = getattr(settings, self.storage_backend_setting, None)
  94. self.storage_backend = get_storage_class(storage_name)()
  95.  
  96. # Generate SQL and save to local temp file
  97. response = self.generate_backup(
  98. self.temporary_filename, self.database_settings)
  99.  
  100. # Zip SQL
  101. self.temporary_zip_filename = self.zip_backup(self.temporary_filename)
  102. logging.debug(u"Database backup zipped up to %s" %
  103. (self.temporary_zip_filename))
  104.  
  105. # Store zip file
  106. self.stored_file_url = self.store_backup(
  107. self.storage_backend, self.storage_directory, self.temporary_zip_filename)
  108. logging.info(u"Database backup stored at %s" %
  109. (self.stored_file_url))
  110.  
  111. # Clean up temporary files
  112. self.cleanup(self.temporary_filename, self.temporary_zip_filename)
  113.  
  114. # Delete old files
  115. self.rotate_backups(self.storage_backend, self.storage_directory,
  116. self.max_days)
  117.  
  118. def get_file_backup_name(self, filename_prefix, database_settings, filename_date_pattern):
  119.  
  120. now = datetime.datetime.now()
  121. now_string = now.strftime(filename_date_pattern)
  122.  
  123. if filename_prefix:
  124. filename = slugify(u"%s_%s" % (filename_prefix, now_string))
  125. else:
  126. filename = slugify(u"%s_%s_%s" % (database_settings[
  127. 'HOST'], database_settings['NAME'], now_string))
  128.  
  129. extension = '.sql'
  130. if database_settings['ENGINE'] == 'django.db.backends.postgresql':
  131. extension = '.pgsql'
  132.  
  133. filename_full = u"%s%s" % (filename, extension)
  134.  
  135. return filename_full
  136.  
  137. def generate_backup(self, temporary_filename, database_settings):
  138.  
  139. database_engine = database_settings['ENGINE']
  140. if database_engine == 'django.db.backends.mysql':
  141.  
  142. return self.generate_backup_mysql(temporary_filename, database_settings)
  143.  
  144. elif database_engine == 'django.db.backends.postgresql' or database_engine == 'django.db.backends.postgresql_psycopg2':
  145.  
  146. return self.generate_backup_postgres(temporary_filename, database_settings)
  147.  
  148. elif database_engine == 'django.db.backends.sqlite3':
  149.  
  150. return self.generate_backup_sqlite(temporary_filename, database_settings)
  151.  
  152. raise NotImplementedError(
  153. u'No database backup mechanmism found for database engine of type %s' % (database_engine))
  154.  
  155. def generate_backup_mysql(self, temporary_filename, database_settings):
  156. MYSQL_CMD = 'mysqldump'
  157.  
  158. user = database_settings['USER']
  159. password = database_settings['PASSWORD']
  160. name = database_settings['NAME']
  161. port = database_settings['PORT']
  162. host = database_settings['HOST']
  163.  
  164. # mysqldump -P 3310 -h 127.0.0.1 -u mysql_user -p database_name
  165. # table_name
  166.  
  167. cmd = "%(mysqldump)s -P %(port)s -h %(host)s -u %(user)s --password='%(password)s' %(database)s > %(temporary_filename)s" % {
  168. 'mysqldump': MYSQL_CMD,
  169. 'port': port,
  170. 'host': host,
  171. 'user': user,
  172. 'password': password,
  173. 'database': name,
  174. 'temporary_filename': temporary_filename}
  175. # logging.debug("Backing up with command %s " % cmd)
  176. return os.system(cmd)
  177.  
  178. def generate_backup_postgres(self, temporary_filename, database_settings):
  179. PG_CMD = 'pg_dump'
  180.  
  181. user = database_settings['USER']
  182. password = database_settings['PASSWORD']
  183. name = database_settings['NAME']
  184. port = database_settings['PORT']
  185. host = database_settings['HOST']
  186.  
  187. # export PGPASSWORD=db_password; pg_dump -h 127.0.0.1 -U db_username
  188. # db_name > path/to/file.sql
  189. cmd = "export PGPASSWORD=%(password)s; %(pgdump)s -h %(host)s -U %(user)s %(database)s > %(temporary_filename)s" % {
  190. 'password': password,
  191. 'pgdump': PG_CMD,
  192. 'host': host,
  193. 'user': user,
  194. 'database': name,
  195. 'temporary_filename': temporary_filename}
  196. # logging.debug("Backing up with command %s " % cmd)
  197. return os.system(cmd)
  198.  
  199. def generate_backup_sqlite(self, temporary_filename, database_settings):
  200. user = database_settings['USER']
  201. password = database_settings['PASSWORD']
  202. name = database_settings['NAME']
  203. port = database_settings['PORT']
  204. host = database_settings['HOST']
  205.  
  206. import sqlite3
  207.  
  208. con = sqlite3.connect(name)
  209. with open(temporary_filename, 'w') as f:
  210. for line in con.iterdump():
  211. f.write('%s\n' % line)
  212.  
  213. def zip_backup(self, temporary_filename):
  214. ZIP_CMD = 'zip'
  215.  
  216. zipfile_name = u"%s.zip" % (temporary_filename)
  217.  
  218. if os.path.exists(zipfile_name):
  219. os.remove(zipfile_name)
  220. zip_cmds = {'zip': ZIP_CMD, 'zipfile': zipfile_name,
  221. 'file': temporary_filename}
  222.  
  223. # Create the archive
  224. os.system("%(zip)s -q -9 %(zipfile)s %(file)s" % zip_cmds)
  225.  
  226. # Test the archive
  227. if not os.system("%(zip)s -T -D -q %(zipfile)s" % zip_cmds):
  228. return zipfile_name
  229. else:
  230. return None
  231.  
  232. def store_backup(self, storage_backend, storage_directory, temporary_zip_filename):
  233.  
  234. fn = os.path.join(storage_directory, os.path.split(
  235. temporary_zip_filename)[1])
  236. try:
  237. f = open(temporary_zip_filename, 'r',
  238. encoding="utf8", errors='ignore')
  239. except TypeError:
  240. f = open(temporary_zip_filename, 'r')
  241.  
  242. django_file = File(f, fn)
  243. path = storage_backend.save(fn, django_file)
  244. url = storage_backend.url(path)
  245. return url
  246.  
  247. def cleanup(self, temporary_filename, temporary_zip_filename):
  248. # Remove ZIP file
  249. if os.path.exists(temporary_filename):
  250. os.remove(temporary_filename)
  251.  
  252. # Remove sql file
  253. if os.path.exists(temporary_zip_filename):
  254. os.remove(temporary_zip_filename)
  255.  
  256. def rotate_backups(self, storage_backend, storage_directory, max_days):
  257. dir_to_list = os.path.join(storage_directory, '.')
  258. files_in_backup_dir = storage_backend.listdir(dir_to_list)[1]
  259.  
  260. max_age = datetime.datetime.now() - datetime.timedelta(days=max_days)
  261.  
  262. dt = tz.make_aware(max_age, tz.utc)
  263. max_age_tz_aware = dt if settings.USE_TZ else tz.make_naive(dt)
  264.  
  265. logging.debug(u"If we store backups from the last %s days, then the maximum age for a backup is %s" % (
  266. max_days, max_age))
  267. for filename in files_in_backup_dir:
  268. date = storage_backend.get_modified_time(
  269. os.path.join(storage_directory, filename))
  270. # date = self.get_date_from_filename(
  271. # filename, filename_prefix, database_to_backup,
  272. # filename_date_pattern)
  273. if date < max_age_tz_aware:
  274. logging.info(u"Deleting expired back up from %s (%s)" %
  275. (date, filename))
  276. storage_backend.delete(filename)
Add Comment
Please, Sign In to add comment