зеркало из https://github.com/Azure/azfilebackup.git
200 строки
7.8 KiB
Python
200 строки
7.8 KiB
Python
"""Runner module."""
|
|
# coding=utf-8
|
|
|
|
import sys
|
|
import os
|
|
import os.path
|
|
import getpass
|
|
import socket
|
|
import logging
|
|
import argparse
|
|
import pid
|
|
|
|
from .backupagent import BackupAgent
|
|
from .backupconfiguration import BackupConfiguration
|
|
from .scheduleparser import ScheduleParser
|
|
from .timing import Timing
|
|
from .backupexception import BackupException
|
|
from .__init__ import version
|
|
|
|
class Runner(object):
|
|
"""
|
|
The Runner class parses the command line arguments and calls the
|
|
BackupAgent to execute the actual actions.
|
|
"""
|
|
|
|
@staticmethod
|
|
def configure_logging():
|
|
"""Configure logging."""
|
|
logging.basicConfig(
|
|
filename="azfilebak.log",
|
|
level=logging.DEBUG,
|
|
format="%(asctime)-15s pid-%(process)d line-%(lineno)d %(levelname)s: \"%(message)s\""
|
|
)
|
|
logging.getLogger('azure.storage').setLevel(logging.FATAL)
|
|
|
|
@staticmethod
|
|
def arg_parser():
|
|
"""Parse arguments."""
|
|
parser = argparse.ArgumentParser()
|
|
|
|
required = parser.add_argument_group("required arguments")
|
|
required.add_argument("-c", "--config", help="the path to the config file")
|
|
|
|
commands = parser.add_argument_group("commands")
|
|
|
|
commands.add_argument("-f", "--full-backup", help="Perform backup for configuration", action="store_true")
|
|
commands.add_argument("-r", "--restore", help="Perform restore for date")
|
|
commands.add_argument("-l", "--list-backups", help="Lists all backups in Azure storage",
|
|
action="store_true")
|
|
commands.add_argument("-p", "--prune-old-backups",
|
|
help="Removes old backups from Azure storage ('--prune-old-backups 30d' removes files older 30 days)")
|
|
commands.add_argument("-x", "--show-configuration",
|
|
help="Shows the VM's configuration values",
|
|
action="store_true")
|
|
commands.add_argument("-u", "--unit-tests", help="Run unit tests", action="store_true")
|
|
|
|
options = parser.add_argument_group("options")
|
|
|
|
options.add_argument("-y", "--force",
|
|
help="Perform forceful backup (ignores age of last backup or business hours)",
|
|
action="store_true")
|
|
|
|
options.add_argument("-F", "--fileset", help="Select fileset(s) to backup or restore ('--fileset A,B,C')")
|
|
|
|
options.add_argument("-o", "--output-dir", help="Specify target folder for backup files")
|
|
|
|
return parser
|
|
|
|
@staticmethod
|
|
def log_script_invocation():
|
|
"""Return a string containing invocation details"""
|
|
return ", ".join([
|
|
"Script version v{}".format(version()),
|
|
"Script arguments: {}".format(str(sys.argv)),
|
|
"Current directory: {}".format(os.getcwd()),
|
|
"User: {} (uid {}, gid {})".format(getpass.getuser(), os.getuid(), os.getgid),
|
|
"Hostname: {}".format(socket.gethostname()),
|
|
"uname: {}".format(str(os.uname())),
|
|
"ProcessID: {}".format(os.getpid()),
|
|
"Parent ProcessID: {}".format(os.getppid())
|
|
])
|
|
|
|
@staticmethod
|
|
def get_config_file(args, parser):
|
|
"""Check the existence of the configuration file and returns its absolute path."""
|
|
if args.config:
|
|
config_file = os.path.abspath(args.config)
|
|
if not os.path.isfile(config_file):
|
|
raise BackupException("Cannot find configuration file '{}'".format(config_file))
|
|
return config_file
|
|
else:
|
|
parser.print_help()
|
|
raise BackupException("Please specify a configuration file.")
|
|
|
|
@staticmethod
|
|
def get_output_dir(args):
|
|
"""Determine output (temp) directory and check that it is usable."""
|
|
|
|
if args.output_dir:
|
|
output_dir = os.path.abspath(args.output_dir)
|
|
specified_via = "dir was user-supplied via command line"
|
|
elif args.config:
|
|
output_dir = os.path.abspath(BackupConfiguration(args.config).get_standard_local_directory())
|
|
specified_via = "dir is specified in config file {}".format(args.config)
|
|
else:
|
|
output_dir = os.path.abspath("/tmp")
|
|
specified_via = "fallback dir"
|
|
|
|
logging.debug("Output dir (%s): %s", specified_via, output_dir)
|
|
|
|
if not os.path.exists(output_dir):
|
|
raise BackupException("Directory {} does not exist".format(output_dir))
|
|
|
|
try:
|
|
test_file_name = os.path.join(output_dir, '__delete_me_ase_backup_test__.txt')
|
|
with open(test_file_name, 'wt') as testfile:
|
|
testfile.write("Hallo")
|
|
os.remove(test_file_name)
|
|
except Exception:
|
|
raise BackupException("Directory {} ({}) is not writable"
|
|
.format(output_dir, specified_via))
|
|
|
|
return output_dir
|
|
|
|
@staticmethod
|
|
def get_filesets(args):
|
|
""" Determine filesets to backup or restore."""
|
|
if args.fileset:
|
|
filesets = args.fileset.split(",")
|
|
logging.debug("User manually selected filesets: %s", str(filesets))
|
|
return filesets
|
|
|
|
logging.debug("User did not select filesets, trying to backup all filesets")
|
|
return []
|
|
|
|
@staticmethod
|
|
def run_unit_tests():
|
|
"""Run doctests."""
|
|
import doctest
|
|
# import unittest
|
|
# suite = unittest.TestSuite()
|
|
# result = unittest.TestResult()
|
|
# finder = doctest.DocTestFinder(exclude_empty=False)
|
|
# suite.addTest(doctest.DocTestSuite('backupagent', test_finder=finder))
|
|
# suite.run(result)
|
|
doctest.testmod() # doctest.testmod(verbose=True)
|
|
|
|
@staticmethod
|
|
def main():
|
|
"""Main method."""
|
|
|
|
Runner.configure_logging()
|
|
logging.info("#######################################################################################################")
|
|
logging.info("# #")
|
|
logging.info("#######################################################################################################")
|
|
parser = Runner.arg_parser()
|
|
args = parser.parse_args()
|
|
|
|
if args.unit_tests:
|
|
Runner.run_unit_tests()
|
|
return
|
|
|
|
logging.debug(Runner.log_script_invocation())
|
|
|
|
config_file = Runner.get_config_file(args=args, parser=parser)
|
|
backup_configuration = BackupConfiguration(config_file)
|
|
backup_agent = BackupAgent(backup_configuration)
|
|
output_dir = Runner.get_output_dir(args)
|
|
filesets = Runner.get_filesets(args)
|
|
|
|
force = args.force
|
|
|
|
for line in backup_agent.get_configuration_printable(output_dir=output_dir):
|
|
logging.debug(line)
|
|
|
|
if args.full_backup:
|
|
try:
|
|
with pid.PidFile(pidname='fileset-backup-full', piddir=".") as _p:
|
|
backup_agent.backup(filesets=filesets, is_full=args.full_backup, force=force)
|
|
except pid.PidFileAlreadyLockedError:
|
|
logging.warn("Skip full backup, already running")
|
|
elif args.restore:
|
|
try:
|
|
Timing.parse(args.restore)
|
|
except Exception:
|
|
raise BackupException("Cannot parse restore point \"{}\"".format(args.restore))
|
|
|
|
backup_agent.restore(restore_point=args.restore, output_dir=output_dir, filesets=filesets)
|
|
elif args.list_backups:
|
|
backup_agent.list_backups(filesets=filesets)
|
|
elif args.prune_old_backups:
|
|
age = ScheduleParser.parse_timedelta(args.prune_old_backups)
|
|
backup_agent.prune_old_backups(older_than=age, filesets=filesets)
|
|
elif args.unit_tests:
|
|
Runner.run_unit_tests()
|
|
elif args.show_configuration:
|
|
print(backup_agent.show_configuration(output_dir=output_dir))
|
|
else:
|
|
parser.print_help()
|