diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82adb58 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +venv diff --git a/Makefile b/Makefile index 0ae72b5..45bef53 100644 --- a/Makefile +++ b/Makefile @@ -23,10 +23,9 @@ SHARE_DIR = $(DESTDIR)$(PREFIX)/share .PHONY: install install: - @install -Dm755 scripts/* -t $(SHARE_DIR)/libalpm/scripts/ + @install -Dm755 snap_pac.py $(SHARE_DIR)/libalpm/scripts/snap-pac @install -Dm644 hooks/* -t $(SHARE_DIR)/libalpm/hooks/ @install -Dm644 LICENSE -t $(SHARE_DIR)/licenses/$(PKGNAME)/ @install -Dm644 man8/* -t $(SHARE_DIR)/man/man8/ @install -Dm644 README.md -t $(SHARE_DIR)/doc/$(PKGNAME)/ - @install -Dm644 extra/snap-pac.conf.example -t $(DESTDIR)/etc/ - @install -Dm644 extra/root.conf.example -t $(DESTDIR)/etc/snap-pac/ + @install -Dm644 extra/snap-pac.ini -t $(DESTDIR)/etc/snap-pac.ini.example diff --git a/extra/snap-pac.ini b/extra/snap-pac.ini index 4d3260e..9189727 100644 --- a/extra/snap-pac.ini +++ b/extra/snap-pac.ini @@ -2,9 +2,8 @@ # See snap-pac(8) for details [root] -abort_on_fail = False desc_limit = 72 snapshot = True cleanup_algorithm = number -#pre_description = "pacman pre snapshot" -#post_description = "pacman post snapshot" +#pre_description = pacman pre snapshot +#post_description = pacman post snapshot diff --git a/scripts/snap-pac b/snap_pac.py similarity index 79% rename from scripts/snap-pac rename to snap_pac.py index 930d31a..52fd7b7 100755 --- a/scripts/snap-pac +++ b/snap_pac.py @@ -27,47 +27,41 @@ import sys logging.basicConfig(format="%(message)s", level=logging.INFO) -def create_snapper_cmd(args, config, section): - chroot = os.stat("/") != os.stat("/proc/1/root/.") +def create_snapper_cmd(preorpost, config, section, prefile): + """Populate the snapper command.""" + try: + chroot = os.stat("/") != os.stat("/proc/1/root/.") + except PermissionError: + logging.warning("Unable to detect if in chroot. Run script as root.") + chroot = False snapper_cmd = ["snapper"] if chroot: snapper_cmd.append("--no-dbus") snapper_cmd.append(f"--config {section} create") - snapper_cmd.append(f"--type {args.preorpost}") + snapper_cmd.append(f"--type {preorpost}") snapper_cmd.append(f"--cleanup-algorithm {config.get(section, 'cleanup_algorithm')}") snapper_cmd.append("--print-number") desc_limit = config.getint(section, "desc_limit") - if args.preorpost == "pre": + if preorpost == "pre": snapper_cmd.append(f"--description {config.get(section, 'pre_description')[:desc_limit]}") else: snapper_cmd.append(f"--description {config.get(section, 'post_description')[:desc_limit]}") + with open(prefile, "r") as f: + pre_number = f.read().rstrip("\n") + snapper_cmd.append(f"--pre-number {pre_number}") + os.remove(prefile) return snapper_cmd def do_snapshot(preorpost, cmd, prefile): - if preorpost == "pre": - return do_pre_snapshot(cmd, prefile) - else: - return do_post_snapshot(cmd, prefile) - - -def do_pre_snapshot(cmd, prefile): - """Run snapper command, write snapshot number to file.""" + """Run the actual snapper command and save snapshot number if pre snapshot.""" num = os.popen(" ".join(cmd)).read().rstrip("\n") - with open(prefile, "w") as f: - f.write(num) + if preorpost == "pre": + with open(prefile, "w") as f: + f.write(num) return num -def do_post_snapshot(cmd, prefile): - """Read pre snapshot number from file, run snapper, delete prefile.""" - with open(prefile, "r") as f: - pre_number = f.read().rstrip("\n") - cmd.append(f"--pre-number {pre_number}") - os.remove(prefile) - return os.popen(" ".join(cmd)).read().rstrip("\n") - - def get_snapper_configs(conf_file): """Get the snapper configurations.""" with open(conf_file, "r") as f: @@ -77,10 +71,8 @@ def get_snapper_configs(conf_file): return line[1].lstrip("\"").split() -def setup_config_parser(ini_file): +def setup_config_parser(ini_file, parent_cmd, packages): """Set up defaults for snap-pac configuration.""" - parent_cmd = os.popen(f"ps -p {os.getppid()} -o args=").read().strip() - packages = " ".join([line.rstrip("\n") for line in sys.stdin]) config = ConfigParser() config["DEFAULT"] = { @@ -98,11 +90,14 @@ def setup_config_parser(ini_file): def main(snap_pac_ini, snapper_conf_file, args): - config = setup_config_parser(snap_pac_ini) - snapper_configs = get_snapper_configs(snapper_conf_file) if os.getenv("SNAP_PAC_SKIP", "n") in ["y", "Y", "yes", "Yes", "YES"]: - return + return False + + parent_cmd = os.popen(f"ps -p {os.getppid()} -o args=").read().strip() + packages = " ".join([line.rstrip("\n") for line in sys.stdin]) + config = setup_config_parser(snap_pac_ini, parent_cmd, packages) + snapper_configs = get_snapper_configs(snapper_conf_file) for c in snapper_configs: @@ -113,11 +108,13 @@ def main(snap_pac_ini, snapper_conf_file, args): if config.getboolean(c, "snapshot"): prefile = f"/tmp/snap-pac-pre_{c}" logging.debug(f"{prefile = }") - snapper_cmd = create_snapper_cmd(args, config, c) + snapper_cmd = create_snapper_cmd(args.preorpost, config, c, prefile) logging.debug(f"{snapper_cmd = }") num = do_snapshot(args.preorpost, snapper_cmd, prefile) logging.info(f"==> {c}: {num}") + return True + if __name__ == "__main__": diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..a9a1a62 --- /dev/null +++ b/tests.py @@ -0,0 +1,67 @@ +from configparser import ConfigParser +import tempfile +import os + +import pytest + +from snap_pac import create_snapper_cmd, get_snapper_configs, main, setup_config_parser + + +@pytest.fixture +def config(): + config = ConfigParser() + config["DEFAULT"] = { + "snapshot": False, + "cleanup_algorithm": "number", + "pre_description": "\"foo\"", + "post_description": "\"bar\"", + "desc_limit": 72 + } + config["root"] = { + "snapshot": True + } + config["home"] = { + "snapshot": True + } + return config + + +def test_create_snapper_pre_cmd(config): + snapper_cmd = create_snapper_cmd("pre", config, "root", "/tmp/somefile") + cmd = "snapper --config root create --type pre --cleanup-algorithm number --print-number --description \"foo\"" + assert " ".join(snapper_cmd) == cmd + + +def test_create_snapper_post_cmd(config): + with tempfile.NamedTemporaryFile("w", delete=False) as f: + f.write("1234") + snapper_cmd = create_snapper_cmd("post", config, "root", f.name) + cmd = "snapper --config root create --type post --cleanup-algorithm number --print-number" + cmd += " --description \"bar\" --pre-number 1234" + assert " ".join(snapper_cmd) == cmd + + +def test_get_snapper_configs(): + with tempfile.NamedTemporaryFile("w", delete=False) as f: + f.write("## Path: System/Snapper\n") + f.write("\n") + f.write("## Type: string\n") + f.write("## Default: \"\"\n") + f.write("# List of snapper configurations.\n") + f.write("SNAPPER_CONFIGS=\"home root foo bar\"\n") + name = f.name + assert get_snapper_configs(name) == ["home", "root", "foo", "bar"] + + +def test_skip_snap_pac(): + os.environ["SNAP_PAC_SKIP"] = "y" + assert main("foo", "bar", "yep") is False + + +def test_setup_config_parser(config): + with tempfile.NamedTemporaryFile("w", delete=False) as f: + f.write("[home]\n") + f.write("snapshot = True") + name = f.name + config2 = setup_config_parser(name, "foo", "bar") + assert config == config2