forked from django-extensions/django-extensions
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added mysql support command mostly for dumping the database.
Added rackspace support for uploading to and listing rackspace containers. Added backup command to integrate mysql dumping and rackspace uploading.
- Loading branch information
Derrick Petzold
committed
Feb 23, 2012
1 parent
1d2af9e
commit 5a9ddea
Showing
3 changed files
with
371 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
from __future__ import print_function | ||
|
||
from optparse import make_option | ||
|
||
import datetime | ||
import re | ||
import socket | ||
import subprocess | ||
import sys | ||
import os | ||
|
||
import django | ||
from django.core.management.base import CommandError, BaseCommand | ||
from django.conf import settings | ||
|
||
from django_extensions.management.commands.mysql import MysqlCommand | ||
from django_extensions.management.commands.rackspace import RackspaceCommand | ||
|
||
class BackupCommand(object): | ||
|
||
def __init__(self, router, container_name, verbose=False): | ||
self.mysql = MysqlCommand(router, verbose) | ||
self.rackspace = RackspaceCommand(container_name, verbose) | ||
self.verbose = verbose | ||
self.start_time = datetime.datetime.now() | ||
|
||
def _run_cmd(self, cmd, filepath, ext=None): | ||
if self.verbose: | ||
print(cmd) | ||
subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) | ||
if ext is not None: | ||
filepath += ext | ||
filesize = os.stat(filepath).st_size | ||
if filesize == 0: | ||
raise baku_exception.BadFileSize('Bad filesize for "%s"' % (filepath)) | ||
return filepath, filesize | ||
|
||
def tar_directory(self, directory, prefix=None): | ||
root, name = os.path.split(directory) | ||
name = '%s.%s-%s.tar.bz2' % \ | ||
(name, self.start_time.strftime('%Y%m%d_%H%M%S'), socket.gethostname()) | ||
if prefix is not None: | ||
backup_dir = '%s/%s' % (settings.BACKUP_DIR, prefix) | ||
else: | ||
backup_dir = settings.BACKUP_DIR | ||
if not os.path.exists(backup_dir): | ||
os.mkdir(backup_dir) | ||
filepath = '%s/%s' % (backup_dir, name) | ||
cmd = '/bin/tar cfj %s %s -C %s' % (filepath, directory, root) | ||
return self._run_cmd(cmd, filepath) | ||
|
||
def cull(self): | ||
culled = [] | ||
files = os.listdir(settings.BACKUP_DIR) | ||
for filename in files: | ||
for date in self.rackspace.culls: | ||
if self.verbose: | ||
print('Checking %s %s' % (date, filename)) | ||
search = re.search('\.%s_' % (date), filename) | ||
if search is not None: | ||
filepath = '%s/%s/' % (settings.BACKUP_DIR, filename) | ||
if self.verbose: | ||
print('Deleting %s' % (filepath)) | ||
if not self.dry_run: | ||
os.unlink(filepath) | ||
culled.append(filepath) | ||
return culled | ||
|
||
def backup_database(self): | ||
dbfile = self.mysql.dump() | ||
uploaded = self.rackspace.upload(dbfile, 'db') | ||
if self.verbose: | ||
print(uploaded.name) | ||
return uploaded | ||
|
||
def backup_site(self): | ||
filepath, filesize = self.tar_directory(settings.SITE_ROOT, 'site') | ||
if self.verbose: | ||
print('%s %s' % (filepath, filesize)) | ||
uploaded = self.rackspace.upload(filepath, 'site') | ||
if self.verbose: | ||
print(uploaded.name) | ||
return uploaded | ||
|
||
def backup_all(self): | ||
db_upload = self.backup_database() | ||
site_upload = self.backup_site() | ||
deletes = self.rackspace.cull() | ||
if self.verbose: | ||
for deleted in deletes: | ||
print('Deleted: %s' % (deleted.name)) | ||
deletes = self.cull() | ||
if self.verbose: | ||
for deleted in deletes: | ||
print('Deleted: %s' % (deleted.name)) | ||
|
||
class Command(BaseCommand): | ||
|
||
option_list = BaseCommand.option_list + ( | ||
make_option('-D', '--db', action='store_true', | ||
dest='db', default=False, | ||
help='Backup the database.'), | ||
make_option('-C', '--contianer', action='store', | ||
dest='container', | ||
default=settings.RACKSPACE_BACKUP_CONTAINER, | ||
help='The container to use.'), | ||
make_option('--cull', action='store_true', | ||
dest='cull', default=False, | ||
help='Cull the backup files.'), | ||
make_option('-R', '--router', action='store', | ||
dest='router', default='default', | ||
help='Use this router and not the default.'), | ||
make_option('-S', '--site', action='store_true', | ||
dest='site', default=False, | ||
help='Backup the site directory.'), | ||
make_option('-U', '--upload', action='store', | ||
dest='upload', help='Upload the file to rackspace.'), | ||
make_option('-V', '--verbose', action='store_true', | ||
dest='verbose', default=False, | ||
help='Be verbose.'), | ||
) | ||
help = """Backups the database and the site directory.""" | ||
|
||
requires_model_validation = False | ||
can_import_settings = True | ||
|
||
def handle(self, *args, **options): | ||
|
||
backup = BackupCommand( | ||
options.get('router'), | ||
options.get('container'), | ||
options.get('verbose')) | ||
|
||
if options.get('cull'): | ||
backup = backup.cull() | ||
elif options.get('db'): | ||
backup = backup.backup_database() | ||
elif options.get('site'): | ||
backup = backup.backup_site() | ||
else: | ||
backup.backup_all() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
from __future__ import print_function | ||
|
||
from optparse import make_option | ||
|
||
import datetime | ||
import socket | ||
import sys | ||
import os | ||
|
||
import django | ||
from django.core.management.base import CommandError, BaseCommand | ||
from django.conf import settings | ||
|
||
class MysqlCommand(object): | ||
|
||
def __init__(self, router, verbose=False): | ||
|
||
self.db = settings.DATABASES[router] | ||
self.verbose = verbose | ||
|
||
cmd = '%s ' | ||
if self.db['HOST'] != '': | ||
cmd += '-h %s' % (self.db['HOST']) | ||
cmd += '-p %s' % (self.db['PORT']) | ||
cmd += '-u {user} --password={password}'.format( | ||
user=self.db['USER'], | ||
password=self.db['PASSWORD']) | ||
self.mysqlbase = cmd | ||
|
||
@property | ||
def mysql(self): | ||
return self.mysqlbase % ('mysql') | ||
|
||
@property | ||
def mysqladmin(self): | ||
return self.mysqlbase % ('mysqladmin') | ||
|
||
@property | ||
def mysqldump(self): | ||
return self.mysqlbase % ('mysqldump') | ||
|
||
def drop(self): | ||
os.system('{mysqladmin} -f DROP {database}'.format( | ||
mysqladmin=self.mysqladmin, | ||
database=self.db['NAME'])) | ||
|
||
def create(self): | ||
os.sytem('{mysqladmin} CREATE {database}'.format( | ||
mysqladmin=self.mysqladmin, | ||
database=self.db['NAME'])) | ||
|
||
def source(self): | ||
os.system('{mysql} {database} < {db_dump}'.format( | ||
mysql=self.mysql, | ||
database=self['NAME'], | ||
db_dump=db_dump)) | ||
|
||
def dump(self, prefix='db'): | ||
|
||
backup_dir = '%s/%s' % (settings.BACKUP_DIR, prefix) | ||
if not os.path.exists(backup_dir): | ||
os.mkdir(backup_dir) | ||
dbfile = '{backup_dir}/{database}.{date}-{hostname}.gz'.format( | ||
backup_dir=backup_dir, | ||
database=self.db['NAME'], | ||
date=datetime.datetime.now().strftime('%Y%m%d_%H%M%S'), | ||
hostname=socket.gethostname()) | ||
output = os.system('{mysqldump} {database} | gzip > {db_file}'.format( | ||
mysqldump=self.mysqldump, | ||
db_file=dbfile, | ||
database=self.db['NAME'])) | ||
return dbfile | ||
|
||
class Command(BaseCommand): | ||
|
||
option_list = BaseCommand.option_list + ( | ||
make_option('-D', '--dump', action='store_true', | ||
dest='dump', default=False, | ||
help='Dump the database to the backup dir.'), | ||
make_option('-R', '--router', action='store', | ||
dest='router', default='default', | ||
help='Use this router-database other then defined in settings.py'), | ||
make_option('-V', '--verbose', action='store_true', | ||
dest='verbose', default=False, | ||
help='Be verbose.'), | ||
) | ||
help = """Backups the database and the site directory.""" | ||
|
||
requires_model_validation = False | ||
can_import_settings = True | ||
|
||
def handle(self, *args, **options): | ||
|
||
mysql = MysqlCommand(options.get('router')) | ||
if options.get('dump'): | ||
dbfile = mysql.dump() | ||
print(dbfile) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
from __future__ import print_function | ||
|
||
from optparse import make_option | ||
|
||
import datetime | ||
import re | ||
import socket | ||
import sys | ||
import os | ||
|
||
import django | ||
from django.core.management.base import CommandError, BaseCommand | ||
from django.conf import settings | ||
|
||
import cloudfiles | ||
|
||
class RackspaceCommand(object): | ||
|
||
def __init__(self, container_name, verbose, dry_run=False, forward=0): | ||
self.container_name = container_name | ||
self.verbose = verbose | ||
self.dry_run = dry_run | ||
self.rp_conn = cloudfiles.get_connection( | ||
settings.RACKSPACE_USER, | ||
settings.RACKSPACE_API_KEY) | ||
self.start_time = datetime.datetime.now() | ||
|
||
if forward == 0: | ||
self.start_time = datetime.datetime.now() | ||
else: | ||
self.start_time = datetime.datetime.now() + datetime.timedelta(days=forward) | ||
|
||
@property | ||
def culls(self): | ||
dates = [] | ||
lastweeks = self.start_time - datetime.timedelta(weeks=1) | ||
|
||
# delete last weeks except mondays | ||
if lastweeks.weekday() != 1 and lastweeks.day != 1: | ||
dates.append(lastweeks.strftime('%Y%m%d')) | ||
|
||
# keep 8 weeks of mondays | ||
lastmonths = self.start_time - datetime.timedelta(weeks=8) | ||
if lastmonths.weekday() == 1 and lastmonths.day != 1: | ||
dates.append(lastmonths.strftime('%Y%m%d')) | ||
return dates | ||
|
||
def cull(self): | ||
culled = [] | ||
container = self.rp_conn.get_container(self.container_name) | ||
for obj in container.get_objects(): | ||
for date in self.culls: | ||
if self.verbose: | ||
print('Checking %s %s' % (date, obj.name)) | ||
search = re.search('\.%s_' % (date), obj.name) | ||
if search is not None: | ||
if self.verbose: | ||
print('Deleting %s' % (obj.name)) | ||
if not self.dry_run: | ||
container.delete_object(obj.name) | ||
culled.append(obj) | ||
return culled | ||
|
||
def list(self): | ||
container = self.rp_conn.get_container(self.container_name) | ||
for obj in container.get_objects(): | ||
print('%s %s' % (obj.name, obj.size)) | ||
|
||
def upload(self, filepath, prefix=None): | ||
root, filename = os.path.split(filepath) | ||
container = self.rp_conn.get_container(self.container_name) | ||
if prefix: | ||
filename = '%s/%s' % (prefix, filename) | ||
obj = container.create_object(filename) | ||
obj.load_from_filename(filepath) | ||
return obj | ||
|
||
def delete(self, filename, prefix=None): | ||
container = self.rp_conn.get_container(self.container_name) | ||
return container.delete_object(filename) | ||
|
||
class Command(BaseCommand): | ||
|
||
option_list = BaseCommand.option_list + ( | ||
make_option('--cull', action='store_true', | ||
dest='cull', default=False, | ||
help='Cull the remote backup files.'), | ||
make_option('-C', '--contianer', action='store', | ||
dest='container', | ||
default=settings.RACKSPACE_BACKUP_CONTAINER, | ||
help='The container to use.'), | ||
make_option('-D', '--delete', action='store', dest='delete', | ||
help='Delete the specified file from container.'), | ||
make_option('-F', '--forward', action='store', | ||
dest='forward', default=0, | ||
help='Pretend to run the specified days forward.'), | ||
make_option('-L', '--list', action='store_true', | ||
dest='list', default=False, | ||
help='List the backup dir.'), | ||
make_option('-N', '--dry-run', action='store_true', | ||
dest='dry_run', default=False, | ||
help='Dry run.'), | ||
make_option('-U', '--upload', action='store', | ||
dest='upload', help='Upload the file to rackspace.'), | ||
make_option('-V', '--verbose', action='store_true', | ||
dest='verbose', default=False, | ||
help='Be verbose.'), | ||
) | ||
help = """Backups the database and the site directory.""" | ||
|
||
requires_model_validation = False | ||
can_import_settings = True | ||
|
||
def handle(self, *args, **options): | ||
|
||
rackspace = RackspaceCommand( | ||
options.get('container'), | ||
options.get('verbose'), | ||
options.get('dry_run'), | ||
int(options.get('forward'))) | ||
|
||
if options.get('cull'): | ||
deletes = rackspace.cull() | ||
for deleted in deletes: | ||
print('Deleted: %s' % (deleted.name)) | ||
elif options.get('delete'): | ||
rackspace.delete(options.get('delete')) | ||
elif options.get('list'): | ||
rackspace.list() | ||
elif options.get('upload'): | ||
backup = rackspace.upload(options.get('upload')) | ||
print(backup) | ||
|