Split configuration file handling into its own module
This commit is contained in:
parent
792d641ff9
commit
49f5d0f276
68
bin/backup
68
bin/backup
@ -17,13 +17,10 @@ files are hardlinked into the new snapshot.
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from subprocess import CalledProcessError
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
import rbackup.config as config
|
||||
from rbackup.rsync import rsync
|
||||
from rbackup.struct.repository import Repository
|
||||
|
||||
@ -48,12 +45,6 @@ EXTRA_RSYNC_OPTS = {
|
||||
"update": "--update",
|
||||
}
|
||||
|
||||
# ----- File Options -----
|
||||
CONFIG_DIR = "/etc/rbackup"
|
||||
FILE_OPTS = [f"--exclude-from={CONFIG_DIR}/home-exclude.conf"]
|
||||
INCLUDE_PATHS = [f"{CONFIG_DIR}/etc-include.conf", f"{CONFIG_DIR}/system-include.conf"]
|
||||
COMMENT_REGEX = r"^[^#; ]+"
|
||||
|
||||
|
||||
# ----- Error Codes -----
|
||||
E_FAILED_PROCESS = 1
|
||||
@ -119,43 +110,6 @@ def parse_cmdline_arguments(**kwargs):
|
||||
return parser.parse_args(**kwargs)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def merge_files(*config_files):
|
||||
"""Parse, filter, and sort through config files to create a single
|
||||
--files-from argument.
|
||||
|
||||
Any files included that do not exist send a warning to the log.
|
||||
|
||||
>>> merge_files('/etc/rbackup/etc-include.conf', '/etc/rbackup/system-include.conf') # doctest: +ELLIPSIS
|
||||
>>> '/tmp/...'
|
||||
|
||||
:param config_files: files including paths to read from
|
||||
:type config_files: iterable of str
|
||||
:returns: path to file that lists include paths
|
||||
:rtype: str
|
||||
"""
|
||||
file_paths = [Path(p) for p in config_files]
|
||||
include_lines = []
|
||||
|
||||
for config_file in file_paths:
|
||||
if config_file.exists():
|
||||
with config_file.open(mode="r") as opened_file:
|
||||
include_lines.extend(
|
||||
l for l in opened_file.readlines() if re.match(COMMENT_REGEX, l)
|
||||
)
|
||||
else:
|
||||
syslog.warning(f"{config_file} does not exist, ignoring.")
|
||||
|
||||
include_lines.sort()
|
||||
|
||||
with NamedTemporaryFile(mode="w", delete=False) as include_paths:
|
||||
include_paths.writelines(include_lines)
|
||||
|
||||
tmpfile = Path(include_paths.name)
|
||||
yield tmpfile
|
||||
tmpfile.unlink()
|
||||
|
||||
|
||||
# ========== Main Script ==========
|
||||
if __name__ == "__main__":
|
||||
args = parse_cmdline_arguments()
|
||||
@ -178,24 +132,24 @@ if __name__ == "__main__":
|
||||
|
||||
curr_snapshot = None
|
||||
|
||||
try:
|
||||
with merge_files(*INCLUDE_PATHS) as include_file:
|
||||
with config.merge_include_files() as include_file, config.merge_exclude_files() as exclude_file:
|
||||
try:
|
||||
curr_snapshot = repo.create_snapshot(args.name)
|
||||
rsync(
|
||||
*rsync_opts,
|
||||
*FILE_OPTS,
|
||||
f"--exclude-from={exclude_file}",
|
||||
f"--files-from={include_file}",
|
||||
*link_dests,
|
||||
"/",
|
||||
str(curr_snapshot.path),
|
||||
)
|
||||
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}")
|
||||
exit(E_FAILED_PROCESS)
|
||||
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}")
|
||||
exit(E_FAILED_PROCESS)
|
||||
|
||||
snapshot_symlink = repo.path / "current"
|
||||
|
||||
|
108
rbackup/config.py
Normal file
108
rbackup/config.py
Normal file
@ -0,0 +1,108 @@
|
||||
"""
|
||||
.. :author:: Eric Torres
|
||||
.. :module:: rbackup.config
|
||||
.. :synopsis: Functions for handling config files.
|
||||
"""
|
||||
import configparser
|
||||
import logging
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
# ========== Logging Setup ===========
|
||||
syslog = logging.getLogger(__name__)
|
||||
|
||||
# ========== Constants ==========
|
||||
COMMENT_REGEX = r"^[^#; ]+"
|
||||
|
||||
# ----- Paths -----
|
||||
CONFIG_DIR = Path("/etc/rbackup")
|
||||
MAIN_CONFIG_FILE = CONFIG_DIR / "backup.conf"
|
||||
|
||||
# ----- Exit Codes -----
|
||||
E_NO_CONFIG_FILE = 4
|
||||
|
||||
|
||||
# ========== Functions ==========
|
||||
def get_files_by_suffix(suffix):
|
||||
"""Retrieve all include files from the program configuration directory.
|
||||
|
||||
>>> get_files_by_suffix('-include.conf') # doctest: +ELLIPSIS
|
||||
...
|
||||
|
||||
:param suffix: the suffix to search for
|
||||
:type suffix: str
|
||||
:returns: paths pointing to include files
|
||||
:rtype: generator of path-like objects
|
||||
"""
|
||||
yield from CONFIG_DIR.glob(f"*{suffix}")
|
||||
|
||||
|
||||
def merge_files(files):
|
||||
"""Parse, filter, and sort through config files to create a single
|
||||
--files-from argument.
|
||||
|
||||
Any files included that do not exist send a warning to the log.
|
||||
|
||||
>>> merge_files() # doctest: +ELLIPSIS
|
||||
PosixPath(/tmp/...)
|
||||
|
||||
:param files: files including paths to read from
|
||||
:type files: iterable of path-like objects
|
||||
:returns: path to file that lists include paths
|
||||
:rtype: path-like object
|
||||
"""
|
||||
include_lines = []
|
||||
|
||||
for file in files:
|
||||
with file.open(mode="r") as opened_file:
|
||||
include_lines.extend(
|
||||
l for l in opened_file.readlines() if re.match(COMMENT_REGEX, l)
|
||||
)
|
||||
|
||||
include_lines.sort()
|
||||
|
||||
with NamedTemporaryFile(mode="w", delete=False) as include_paths:
|
||||
include_paths.writelines(include_lines)
|
||||
|
||||
return Path(include_paths.name)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def merge_include_files():
|
||||
"""Merge include file paths into one file and yield its path for use with rsync.
|
||||
|
||||
:return: path-like object
|
||||
"""
|
||||
filelist = merge_files(get_files_by_suffix("-include.conf"))
|
||||
yield filelist
|
||||
filelist.unlink()
|
||||
|
||||
|
||||
@contextmanager
|
||||
def merge_exclude_files():
|
||||
"""Merge exclude file paths into one file and yield its path for use with rsync.
|
||||
|
||||
:return: path-like object
|
||||
"""
|
||||
filelist = merge_files(get_files_by_suffix("-exclude.conf"))
|
||||
yield filelist
|
||||
filelist.unlink()
|
||||
|
||||
|
||||
def parse_configfile():
|
||||
"""Parse a config file given its path and return
|
||||
a ConfigParser object.
|
||||
|
||||
:returns: object used to parse config file
|
||||
:rtype: ConfigParser object
|
||||
:raises: FileNotFoundError if path does not exist
|
||||
"""
|
||||
if not MAIN_CONFIG_FILE.is_file():
|
||||
raise FileNotFoundError(f"{MAIN_CONFIG_FILE} does not exist")
|
||||
|
||||
config_reader = configparser.ConfigParser()
|
||||
config_reader.read(MAIN_CONFIG_FILE)
|
||||
|
||||
return config_reader
|
Loading…
x
Reference in New Issue
Block a user