azfilebackup/azfilebak/runner.py

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()