Skip to content

Commit

Permalink
Merge branch 'master' into some_clean
Browse files Browse the repository at this point in the history
Conflicts:
	dbbackup/management/commands/mediabackup.py
  • Loading branch information
ZuluPro committed Jul 26, 2015
2 parents ab0f43b + 2561c38 commit a7b0b3e
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 47 deletions.
18 changes: 18 additions & 0 deletions dbbackup/management/commands/_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from optparse import make_option
from django.core.management.base import BaseCommand, LabelCommand


class BaseDbBackupCommand(LabelCommand):
option_list = BaseCommand.option_list + (
make_option("--noinput", action='store_false', dest='interactive', default=True,
help='Tells Django to NOT prompt the user for input of any kind.'),
make_option('-q', "--quiet", action='store_true', default=False,
help='Tells Django to NOT output other text than errors.')
)

verbosity = 1
quiet = False

def log(self, msg, level):
if not self.quiet and self.verbosity >= level:
self.stdout.write(msg)
25 changes: 14 additions & 11 deletions dbbackup/management/commands/dbbackup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Save backup files to Dropbox.
Save database.
"""
from __future__ import (absolute_import, division,
print_function, unicode_literals)
Expand All @@ -9,22 +9,22 @@
import tempfile
import gzip
from shutil import copyfileobj

from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.core.management.base import LabelCommand
from optparse import make_option

from dbbackup.management.commands._base import BaseDbBackupCommand
from dbbackup import utils
from dbbackup.dbcommands import DBCommands
from dbbackup.storage.base import BaseStorage
from dbbackup.storage.base import StorageError
from dbbackup import settings as dbbackup_settings


class Command(LabelCommand):
class Command(BaseDbBackupCommand):
help = "dbbackup [-c] [-d <dbname>] [-s <servername>] [--compress] [--encrypt]"
option_list = BaseCommand.option_list + (
option_list = BaseDbBackupCommand.option_list + (
make_option("-c", "--clean", help="Clean up old backup files", action="store_true", default=False),
make_option("-d", "--database", help="Database to backup (default: everything)"),
make_option("-s", "--servername", help="Specify server name to include in backup filename"),
Expand All @@ -35,6 +35,8 @@ class Command(LabelCommand):
@utils.email_uncaught_exception
def handle(self, **options):
""" Django command handler. """
self.verbosity = options.get('verbosity')
self.quiet = options.get('quiet')
try:
self.clean = options.get('clean')
self.clean_keep = settings.CLEANUP_KEEP
Expand All @@ -55,39 +57,40 @@ def handle(self, **options):
except StorageError as err:
raise CommandError(err)

def save_new_backup(self, database, database_name):
def save_new_backup(self, database):
""" Save a new backup file. """
print("Backing Up Database: %s" % database['NAME'])
self.log("Backing Up Database: %s" % database['NAME'], 1)
filename = self.dbcommands.filename(self.servername)
outputfile = tempfile.SpooledTemporaryFile(
max_size=10 * 1024 * 1024,
dir=dbbackup_settings.TMP_DIR)
self.dbcommands.run_backup_commands(outputfile)
outputfile.name = filename
if self.compress:
compressed_file = self.compress_file(outputfile)
outputfile.close()
outputfile = compressed_file
if self.encrypt:
encrypted_file = utils.encrypt_file(outputfile)
outputfile = encrypted_file
print(" Backup tempfile created: %s" % (utils.handle_size(outputfile)))
print(" Writing file to %s: %s, filename: %s" % (self.storage.name, self.storage.backup_dir, filename))
self.log(" Backup tempfile created: %s" % (utils.handle_size(outputfile)), 1)
self.log(" Writing file to %s: %s, filename: %s" % (self.storage.name, self.storage.backup_dir, filename), 1)
self.storage.write_file(outputfile, filename)

def cleanup_old_backups(self, database):
""" Cleanup old backups, keeping the number of backups specified by
DBBACKUP_CLEANUP_KEEP and any backups that occur on first of the month.
"""
if self.clean:
print("Cleaning Old Backups for: %s" % database['NAME'])
self.log("Cleaning Old Backups for: %s" % database['NAME'], 1)
filepaths = self.storage.list_directory()
filepaths = self.dbcommands.filter_filepaths(filepaths)
for filepath in sorted(filepaths[0:-self.clean_keep]):
regex = r'^%s' % self.dbcommands.filename_match(self.servername, '(.*?)')
datestr = re.findall(regex, os.path.basename(filepath))[0]
dateTime = datetime.datetime.strptime(datestr, dbbackup_settings.DATE_FORMAT)
if int(dateTime.strftime("%d")) != 1:
print(" Deleting: %s" % filepath)
self.log(" Deleting: %s" % filepath, 1)
self.storage.delete_file(filepath)

def compress_file(self, inputfile):
Expand Down
44 changes: 24 additions & 20 deletions dbbackup/management/commands/dbrestore.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,55 @@
"""
Restore pgdump files from Dropbox.
See __init__.py for a list of options.
Restore database.
"""
from __future__ import (absolute_import, division,
print_function, unicode_literals)
import os
import tempfile
import gzip
import sys
from getpass import getpass

from ... import utils
from ...dbcommands import DBCommands
from ...storage.base import BaseStorage
from ...storage.base import StorageError
from dbbackup import settings as dbbackup_settings
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
from django.core.management.base import LabelCommand
from django.utils import six
from django.db import connection

from optparse import make_option

from dbbackup.management.commands._base import BaseDbBackupCommand
from dbbackup import utils
from dbbackup.dbcommands import DBCommands
from dbbackup.storage.base import BaseStorage, StorageError
from dbbackup import settings as dbbackup_settings

input = raw_input if six.PY2 else input # @ReservedAssignment


class Command(LabelCommand):
class Command(BaseDbBackupCommand):
help = "dbrestore [-d <dbname>] [-f <filename>] [-s <servername>]"
option_list = BaseCommand.option_list + (
option_list = BaseDbBackupCommand.option_list + (
make_option("-d", "--database", help="Database to restore"),
make_option("-f", "--filepath", help="Specific file to backup from"),
make_option("-x", "--backup-extension", help="The extension to use when scanning for files to restore from."),
make_option("-s", "--servername", help="Use a different servername backup"),
make_option("-l", "--list", action='store_true', default=False, help="List backups in the backup directory"),
make_option("-c", "--decrypt", help="Decrypt data before restoring", default=False, action='store_true'),
make_option("-p", "--passphrase", help="Passphrase for decrypt file", default=None),
make_option("-z", "--uncompress", help="Uncompress gzip data before restoring", action='store_true'),
)

def handle(self, **options):
""" Django command handler. """
self.verbosity = options.get('verbosity')
self.quiet = options.get('quiet')
try:
connection.close()
self.filepath = options.get('filepath')
self.backup_extension = options.get('backup-extension') or 'backup'
self.backup_extension = options.get('backup_extension') or 'backup'
self.servername = options.get('servername')
self.decrypt = options.get('decrypt')
self.uncompress = options.get('uncompress')
self.passphrase = options.get('passphrase')
self.database = self._get_database(options)
self.storage = BaseStorage.storage_factory()
self.dbcommands = DBCommands(self.database)
Expand All @@ -68,17 +72,17 @@ def _get_database(self, options):

def restore_backup(self):
""" Restore the specified database. """
self.stdout.write("Restoring backup for database: %s" % self.database['NAME'])
self.log("Restoring backup for database: %s" % self.database['NAME'], 1)
# Fetch the latest backup if filepath not specified
if not self.filepath:
self.stdout.write(" Finding latest backup")
self.log(" Finding latest backup", 1)
filepaths = self.storage.list_directory()
filepaths = [f for f in filepaths if f.endswith('.' + self.backup_extension)]
if not filepaths:
raise CommandError("No backup files found in: /%s" % self.storage.backup_dir)
self.filepath = filepaths[-1]
# Restore the specified filepath backup
self.stdout.write(" Restoring: %s" % self.filepath)
self.log(" Restoring: %s" % self.filepath, 1)
input_filename = self.filepath
inputfile = self.storage.read_file(input_filename)
if self.decrypt:
Expand All @@ -90,10 +94,10 @@ def restore_backup(self):
uncompressed_file = self.uncompress_file(inputfile)
inputfile.close()
inputfile = uncompressed_file
self.stdout.write(" Restore tempfile created: %s" % utils.handle_size(inputfile))
self.log(" Restore tempfile created: %s" % utils.handle_size(inputfile), 1)
answer = input("Are you sure you want to continue? [Y/n]")
if answer.lower() not in ('y', 'yes', ''):
self.stdout.write("Quitting")
self.log("Quitting", 1)
sys.exit(0)
inputfile.seek(0)
self.dbcommands.run_restore_commands(inputfile)
Expand All @@ -120,7 +124,7 @@ def unencrypt_file(self, inputfile):
import gnupg

def get_passphrase():
return input('Input Passphrase: ')
return self.passphrase or getpass('Input Passphrase: ') or None

temp_dir = tempfile.mkdtemp(dir=dbbackup_settings.TMP_DIR)
try:
Expand Down Expand Up @@ -151,8 +155,8 @@ def get_passphrase():

def list_backups(self):
""" List backups in the backup directory. """
self.stdout.write("Listing backups on %s in /%s:" % (self.storage.name, self.storage.backup_dir))
self.log("Listing backups on %s in /%s:" % (self.storage.name, self.storage.backup_dir), 1)
for filepath in self.storage.list_directory():
self.stdout.write(" %s" % os.path.basename(filepath))
self.log(" %s" % os.path.basename(filepath), 1)
# TODO: Implement filename_details method
# print(utils.filename_details(filepath))
29 changes: 13 additions & 16 deletions dbbackup/management/commands/mediabackup.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""
Save media files.
"""
from __future__ import (absolute_import, division,
print_function, unicode_literals)
import os
Expand All @@ -9,29 +12,23 @@
import re

from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError

from dbbackup.management.commands._base import BaseDbBackupCommand
from dbbackup import utils
from dbbackup.storage.base import BaseStorage
from dbbackup.storage.base import StorageError
from dbbackup import settings as dbbackup_settings


class Command(BaseCommand):
class Command(BaseDbBackupCommand):
help = "backup_media [--encrypt] [--clean] [--no-compress] " \
"--servername SERVER_NAME"
option_list = BaseCommand.option_list + (
option_list = BaseDbBackupCommand.option_list + (
make_option("-c", "--clean", help="Clean up old backup files", action="store_true", default=False),
make_option("-s", "--servername", help="Specify server name to include in backup filename"),
make_option("-e", "--encrypt", help="Encrypt the backup files", action="store_true", default=False),
make_option(
"-x",
"--no-compress",
help="Do not compress the archive",
action="store_true",
default=False
),
make_option("-x", "--no-compress", help="Do not compress the archive", action="store_true", default=False),
)

@utils.email_uncaught_exception
Expand All @@ -53,9 +50,9 @@ def handle(self, *args, **options):
def backup_mediafiles(self, encrypt, compress):
source_dir = self.get_source_dir()
if not source_dir:
print("No media source dir configured.")
self.stderr.write("No media source dir configured.")
sys.exit(0)
print("Backing up media files in %s" % source_dir)
self.log("Backing up media files in %s" % source_dir, 1)
output_file = self.create_backup_file(
source_dir,
self.get_backup_basename(
Expand All @@ -68,8 +65,8 @@ def backup_mediafiles(self, encrypt, compress):
encrypted_file = utils.encrypt_file(output_file)
output_file = encrypted_file

print(" Backup tempfile created: %s (%s)" % (output_file.name, utils.handle_size(output_file)))
print(" Writing file to %s: %s" % (self.storage.name, self.storage.backup_dir))
self.log(" Backup tempfile created: %s (%s)" % (output_file.name, utils.handle_size(output_file)), 1)
self.log(" Writing file to %s: %s" % (self.storage.name, self.storage.backup_dir), 1)
self.storage.write_file(
output_file,
self.get_backup_basename(
Expand Down Expand Up @@ -121,13 +118,13 @@ def cleanup_old_backups(self):
""" Cleanup old backups, keeping the number of backups specified by
DBBACKUP_CLEANUP_KEEP and any backups that occur on first of the month.
"""
print("Cleaning Old Backups for media files")
self.log("Cleaning Old Backups for media files", 1)

file_list = self.get_backup_file_list()

for backup_date, filename in file_list[0:-dbbackup_settings.CLEANUP_KEEP_MEDIA]:
if int(backup_date.strftime("%d")) != 1:
print(" Deleting: %s" % filename)
self.log(" Deleting: %s" % filename, 1)
self.storage.delete_file(filename)

def get_backup_file_list(self):
Expand Down
Empty file.
31 changes: 31 additions & 0 deletions dbbackup/tests/commands/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
try:
from StringIO import StringIO
except ImportError: # Py3
from io import StringIO
from django.test import TestCase
from dbbackup.management.commands._base import BaseDbBackupCommand


class BaseDbBackupCommandLogTest(TestCase):
def setUp(self):
self.command = BaseDbBackupCommand()
self.command.stdout = StringIO()

def test_less_level(self):
self.command.verbosity = 1
self.command.log("foo", 2)
self.command.stdout.seek(0)
self.assertFalse(self.command.stdout.read())

def test_more_level(self):
self.command.verbosity = 1
self.command.log("foo", 0)
self.command.stdout.seek(0)
self.assertEqual('foo', self.command.stdout.read())

def test_quiet(self):
self.command.quiet = True
self.command.verbosity = 1
self.command.log("foo", 0)
self.command.stdout.seek(0)
self.assertFalse(self.command.stdout.read())
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def setUp(self):
self.command.filepath = 'foofile'
self.command.database = TEST_DATABASE
self.command.dbcommands = DBCommands(TEST_DATABASE)
self.command.passphrase = None
self.command.storage = FakeStorage()

def test_no_filepath(self, *args):
Expand All @@ -39,6 +40,7 @@ def test_uncompress(self, *args):
self.command.uncompress = True
self.command.restore_backup()

@patch('dbbackup.management.commands.dbrestore.getpass', return_value=None)
def test_decrypt(self, *args):
if six.PY3:
self.skipTest("Decryption isn't implemented in Python3")
Expand Down Expand Up @@ -97,10 +99,12 @@ def test_uncompress(self):
class DbrestoreCommandDecryptTest(TestCase):
def setUp(self):
self.command = DbrestoreCommand()
self.command.passphrase = None
cmd = ('gpg --import %s' % GPG_PRIVATE_PATH).split()
subprocess.call(cmd, stdout=DEV_NULL, stderr=DEV_NULL)

@patch('dbbackup.management.commands.dbrestore.input', return_value=None)
@patch('dbbackup.management.commands.dbrestore.getpass', return_value=None)
def test_decrypt(self, *args):
if six.PY3:
self.skipTest("Decryption isn't implemented in Python3")
Expand Down
1 change: 1 addition & 0 deletions tests/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def main():
call_command('shell')
sys.exit(0)
else:
# call_command('test', *sys.argv[2:])
from django.test.runner import DiscoverRunner
runner = DiscoverRunner(failfast=True, verbosity=int(os.environ.get('DJANGO_DEBUG', 1)))
failures = runner.run_tests(['dbbackup'], interactive=True)
Expand Down

0 comments on commit a7b0b3e

Please sign in to comment.