Split main backup script into separate library modules
This commit is contained in:
parent
dd64b66a1f
commit
73995d8efb
69
bin/backup
69
bin/backup
@ -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
71
rbackup/script.py
Normal 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
36
rbackup/system.py
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user