Split main backup script into separate library modules

This commit is contained in:
Eric Torres 2019-04-23 22:47:05 -07:00
parent dd64b66a1f
commit 73995d8efb
3 changed files with 112 additions and 64 deletions

View File

@ -23,15 +23,12 @@ import argparse
import logging import logging
import os import os
import sys import sys
from contextlib import contextmanager
from subprocess import CalledProcessError
import rbackup.config.config_files as config import rbackup.config.config_files as config
import rbackup.rsync import rbackup.script as script
from rbackup.struct.repository import Repository from rbackup.struct.repository import Repository
# ========== Constants ========== # ========== Constants ==========
SCRIPT_UMASK = 0000
LOGFORMAT = "==> %(levelname)s %(message)s" LOGFORMAT = "==> %(levelname)s %(message)s"
EXTRA_RSYNC_OPTS = { EXTRA_RSYNC_OPTS = {
"dry_run": "--dry-run", "dry_run": "--dry-run",
@ -40,11 +37,10 @@ EXTRA_RSYNC_OPTS = {
"update": "--update", "update": "--update",
} }
# ----- Error Codes ----- # ----- Error Codes -----
E_INVALID_SNAPSHOT_NAME = 2
E_PERMISSION = 13 E_PERMISSION = 13
# ========== Logging Setup ========== # ========== Logging Setup ==========
console_formatter = logging.Formatter(LOGFORMAT) console_formatter = logging.Formatter(LOGFORMAT)
syslog = logging.getLogger("rbackup") syslog = logging.getLogger("rbackup")
@ -111,47 +107,18 @@ def parse_cmdline_arguments(**kwargs):
return parser.parse_args(**kwargs) return parser.parse_args(**kwargs)
@contextmanager
def change_umask(override=None):
"""Creates a context manager in which the umask is changed.
This is to ensure that the script's desired umask is not
visible to the user.
:param override: non-script-default umask value to use
:type override: int
"""
try:
if override is not None:
old_umask = os.umask(override)
else:
old_umask = os.umask(SCRIPT_UMASK)
yield
finally:
os.umask(old_umask)
# ========== Main Script ========== # ========== Main Script ==========
if __name__ == "__main__": if __name__ == "__main__":
args = parse_cmdline_arguments() args = parse_cmdline_arguments()
parsed_config = config.parse_configfile()
repo = None
try: try:
parsed_config = config.parse_configfile()
repo = Repository(args.repository) repo = Repository(args.repository)
except PermissionError as e: except PermissionError as e:
syslog.critical(e) syslog.critical(e)
exit(E_PERMISSION) exit(E_PERMISSION)
else:
rsync_opts = config.load_list_from_option( script.do_backup(repo, parsed_config, args)
parsed_config,
section="main",
option="RsyncOptions",
fallback=rbackup.rsync.DEFAULT_RSYNC_OPTS.copy(),
)
if args.extra_rsync_opts is not None:
rsync_opts.extend(args.extra_rsync_opts)
if args.verbose: if args.verbose:
stdout_handler.setLevel(logging.INFO) stdout_handler.setLevel(logging.INFO)
@ -159,32 +126,6 @@ if __name__ == "__main__":
if args.debug: if args.debug:
stdout_handler.setLevel(logging.DEBUG) stdout_handler.setLevel(logging.DEBUG)
# We want to iterate through the repository and create the --link-dest
# options before creating the new snapshot
link_dests = tuple(f"--link-dest={s.path}" for s in repo)
curr_snapshot = None
with change_umask(args.umask), config.merge_include_files() as include_file, config.merge_exclude_files() as exclude_file:
try:
curr_snapshot = repo.create_snapshot(args.name)
rbackup.rsync.rsync(
*rsync_opts,
f"--files-from={include_file}",
f"--exclude-from={exclude_file}",
*link_dests,
"/",
str(curr_snapshot),
)
except ValueError as e:
syslog.critical(e)
exit(E_INVALID_SNAPSHOT_NAME)
except CalledProcessError as e:
syslog.critical("Backup process failed")
syslog.critical(f"Failing command: {e.cmd}")
syslog.critical(e.stderr)
exit(e.returncode)
if args.run_post_sync: if args.run_post_sync:
syslog.info("Running sync operation") syslog.info("Running sync operation")
os.sync() os.sync()

71
rbackup/script.py Normal file
View File

@ -0,0 +1,71 @@
"""
.. moduleauthor:: Eric Torres
.. module:: rbackup.script
:synopsis: library for common script operations
"""
import logging
from subprocess import CalledProcessError
import rbackup.config.config_files as config
import rbackup.rsync
import rbackup.system as system
# ========== Constants ==========
# ----- Error Codes -----
E_INVALID_SNAPSHOT_NAME = 2
E_PERMISSION = 13
# ========== Logging Setup ===========
syslog = logging.getLogger(__name__)
# ========== Functions ==========
def do_backup(repository, parsed_config, args):
"""Run a backup operation.
It is up to the caller to decide on how to instantiate the Repository object.
:param repository: repository to operate on
:type repository: ``rbackup.struct.repository.Repository`` object
:param parsed_config:
:type parsed_config: ``configparser.ConfigParser`` object
:param args: arguments relevant to the backup operation
:type args: ``argparse.Namespace`` object
"""
rsync_opts = config.load_list_from_option(
parsed_config,
section="main",
option="RsyncOptions",
fallback=rbackup.rsync.DEFAULT_RSYNC_OPTS.copy(),
)
if args.extra_rsync_opts is not None:
rsync_opts.extend(args.extra_rsync_opts)
# We want to iterate through the repository and create the --link-dest
# options before creating the new snapshot
link_dests = tuple(f"--link-dest={s.path}" for s in repository)
with system.change_umask(
args.umask
), config.merge_include_files() as include_file, config.merge_exclude_files() as exclude_file:
try:
curr_snapshot = repository.create_snapshot(args.name)
rbackup.rsync.rsync(
*rsync_opts,
f"--files-from={include_file}",
f"--exclude-from={exclude_file}",
*link_dests,
"/",
str(curr_snapshot),
)
except ValueError as e:
syslog.critical(e)
exit(E_INVALID_SNAPSHOT_NAME)
except CalledProcessError as e:
syslog.critical("Backup process failed")
syslog.critical(f"Failing command: {e.cmd}")
syslog.critical(e.stderr)
exit(e.returncode)

36
rbackup/system.py Normal file
View File

@ -0,0 +1,36 @@
"""
.. moduleauthor:: Eric Torres
.. module:: rbackup.system
:synopsis: library for interacting with rbackup over the network
"""
import logging
import os
from contextlib import contextmanager
# ========== Constants ==========
DEFAULT_UMASK = 0000
# ========== Logging Setup ===========
syslog = logging.getLogger(__name__)
# ========== Functions ==========
@contextmanager
def change_umask(override=None):
"""Creates a context manager in which the umask is changed.
This is to ensure that the script's desired umask is not
visible to the user.
:param override: non-script-default umask value to use
:type override: int
"""
try:
if override is not None:
old_umask = os.umask(override)
else:
old_umask = os.umask(DEFAULT_UMASK)
yield
finally:
os.umask(old_umask)