2019-04-12 12:03:16 -07:00
|
|
|
"""
|
|
|
|
.. :author:: Eric Torres
|
|
|
|
.. :module:: rbackup.config
|
2019-04-14 14:56:09 -07:00
|
|
|
:synopsis: Functions for handling config files.
|
2019-04-12 12:03:16 -07:00
|
|
|
"""
|
|
|
|
import configparser
|
2019-04-14 22:11:07 -07:00
|
|
|
import json
|
2019-04-12 12:03:16 -07:00
|
|
|
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
|
2019-04-12 12:31:08 -07:00
|
|
|
<generator object ...>
|
2019-04-12 12:03:16 -07:00
|
|
|
|
|
|
|
: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}")
|
|
|
|
|
|
|
|
|
2019-04-14 22:15:00 -07:00
|
|
|
def load_list_from_option(parser, *, section="", option="", fallback=None):
|
2019-04-14 22:11:07 -07:00
|
|
|
"""Using a combination of ``ConfigParser`` and JSON, load a
|
|
|
|
list from a configuration file option.
|
|
|
|
|
|
|
|
:param parser: the parsed config file
|
|
|
|
:type parser: ``ConfigParser`` object
|
|
|
|
:param section: the section of the config file to load
|
|
|
|
:type section: str
|
|
|
|
:param option: the option value inside the specified section
|
|
|
|
:type option: str
|
|
|
|
:returns: the list parsed by JSON
|
|
|
|
:param fallback: the fallback value to return if the option is empty
|
|
|
|
:type fallback: list
|
2019-04-14 22:15:00 -07:00
|
|
|
:rtype: list or type of fallback value
|
2019-04-14 22:11:07 -07:00
|
|
|
"""
|
|
|
|
try:
|
|
|
|
return json.loads(parser[section][option])
|
|
|
|
except (json.decoder.JSONDecodeError, KeyError):
|
|
|
|
return fallback
|
|
|
|
|
|
|
|
|
2019-04-12 12:03:16 -07:00
|
|
|
def merge_files(files):
|
|
|
|
"""Parse, filter, and sort through config files to create a single
|
|
|
|
--files-from argument.
|
|
|
|
|
2019-04-14 14:56:09 -07:00
|
|
|
>>> merge_files(get_files_by_suffix('-include.conf')) # doctest: +ELLIPSIS
|
2019-04-12 12:31:08 -07:00
|
|
|
PosixPath('/tmp/...')
|
2019-04-12 12:03:16 -07:00
|
|
|
|
|
|
|
: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():
|
2019-04-14 22:11:07 -07:00
|
|
|
"""Parse the main backup config file and return
|
|
|
|
a ``configparser.ConfigParser`` object.
|
2019-04-12 12:03:16 -07:00
|
|
|
|
|
|
|
:returns: object used to parse config file
|
|
|
|
:rtype: ConfigParser object
|
2019-04-14 08:45:17 -07:00
|
|
|
:raises FileNotFoundError: if path does not exist
|
2019-04-12 12:03:16 -07:00
|
|
|
"""
|
|
|
|
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
|