diff --git a/pmb/chroot/init.py b/pmb/chroot/init.py index 0347f713..245ea576 100644 --- a/pmb/chroot/init.py +++ b/pmb/chroot/init.py @@ -8,6 +8,7 @@ import filecmp import pmb.chroot import pmb.chroot.apk_static import pmb.config +import pmb.config.workdir import pmb.helpers.repo import pmb.helpers.run import pmb.parse.arch @@ -73,6 +74,8 @@ def init(args, suffix="native"): copy_resolv_conf(args, suffix) pmb.chroot.apk.update_repository_list(args, suffix) + pmb.config.workdir.chroot_save_date(args, suffix) + # Install alpine-base pmb.helpers.repo.update(args, arch) pmb.chroot.apk_static.run(args, ["--no-progress", "--root", chroot, diff --git a/pmb/chroot/zap.py b/pmb/chroot/zap.py index 1d873bd9..29951fe8 100644 --- a/pmb/chroot/zap.py +++ b/pmb/chroot/zap.py @@ -6,6 +6,7 @@ import math import os import pmb.chroot +import pmb.config.workdir import pmb.helpers.pmaports import pmb.helpers.run import pmb.parse.apkindex @@ -72,6 +73,9 @@ def zap(args, confirm=True, dry=False, pkgs_local=False, http=False, if not dry: pmb.helpers.run.root(args, ["rm", "-rf", match]) + # Remove config init dates for deleted chroots + pmb.config.workdir.clean(args) + # Chroots were zapped, so no repo lists exist anymore args.cache["apk_repository_list_updated"].clear() diff --git a/pmb/config/__init__.py b/pmb/config/__init__.py index 618d11b8..7a214869 100644 --- a/pmb/config/__init__.py +++ b/pmb/config/__init__.py @@ -148,6 +148,10 @@ chroot_device_nodes = [ # You can force-update them with 'pmbootstrap update'. apkindex_retention_time = 4 + +# When chroot is considered outdated (in seconds) +chroot_outdated = 3600 * 24 * 2 + # # BUILD # diff --git a/pmb/config/workdir.py b/pmb/config/workdir.py new file mode 100644 index 00000000..282341c8 --- /dev/null +++ b/pmb/config/workdir.py @@ -0,0 +1,87 @@ +# Copyright 2020 Oliver Smith +# SPDX-License-Identifier: GPL-3.0-or-later +""" Save, read, verify workdir state related information in $WORK/workdir.cfg, + for example the init dates of the chroots. This is not saved in + pmbootstrap.cfg, because pmbootstrap.cfg is not tied to a specific work + dir. """ +import configparser +import os +import time + +import pmb.config + + +def chroot_save_date(args, suffix): + """ Save the chroot initialization date in $WORK/workdir.cfg. """ + # Read existing cfg + cfg = configparser.ConfigParser() + path = args.work + "/workdir.cfg" + if os.path.isfile(path): + cfg.read(path) + + # Set current date for chroot suffix + key = "chroot-init-dates" + if key not in cfg: + cfg[key] = {} + cfg[key][suffix] = str(int(time.time())) + + # Write back + with open(path, "w") as handle: + cfg.write(handle) + + +def chroots_outdated(args): + """ Check if init dates from workdir.cfg indicate that any chroot is + outdated. + :returns: True if any of the chroots are outdated and should be zapped, + False otherwise """ + # Skip if workdir.cfg doesn't exist + path = args.work + "/workdir.cfg" + if not os.path.exists(path): + return False + + cfg = configparser.ConfigParser() + cfg.read(path) + key = "chroot-init-dates" + if key not in cfg: + return False + + date_outdated = time.time() - pmb.config.chroot_outdated + for suffix in cfg[key]: + date_init = int(cfg[key][suffix]) + if date_init <= date_outdated: + return True + return False + + +def clean(args): + """ Remove obsolete data data from workdir.cfg. + :returns: None if workdir does not exist, + True if config was rewritten, + False if config did not change """ + # Skip if workdir.cfg doesn't exist + path = args.work + "/workdir.cfg" + if not os.path.exists(path): + return None + + # Read + cfg = configparser.ConfigParser() + cfg.read(path) + + # Remove entries for deleted chroots + key = "chroot-init-dates" + changed = False + if key in cfg: + for suffix in cfg[key]: + path_suffix = args.work + "/chroot_" + suffix + if os.path.exists(path_suffix): + continue + changed = True + del cfg[key][suffix] + + # Write back + if changed: + with open(path, "w") as handle: + cfg.write(handle) + + return changed diff --git a/test/test_config_workdir.py b/test/test_config_workdir.py new file mode 100644 index 00000000..3543412f --- /dev/null +++ b/test/test_config_workdir.py @@ -0,0 +1,103 @@ +# Copyright 2020 Oliver Smith +# SPDX-License-Identifier: GPL-3.0-or-later +""" Test pmb/config/workdir.py """ +import os +import pytest +import sys +import time + +import pmb_test # noqa +import pmb.config +import pmb.config.workdir + + +@pytest.fixture +def args(request): + import pmb.parse + sys.argv = ["pmbootstrap", "init"] + args = pmb.parse.arguments() + args.log = args.work + "/log_testsuite.txt" + pmb.helpers.logging.init(args) + request.addfinalizer(args.logfd.close) + return args + + +def test_chroot_save_date(args, tmpdir, monkeypatch): + # Override time.time() + def fake_time(): + return 1234567890.1234 + monkeypatch.setattr(time, "time", fake_time) + + args.work = str(tmpdir) + func = pmb.config.workdir.chroot_save_date + func(args, "native") + + expected = "[chroot-init-dates]\nnative = 1234567890\n\n" + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == expected + + # Write again (different code path) + func(args, "buildroot_armhf") + expected = ("[chroot-init-dates]\nnative = 1234567890\n" + "buildroot_armhf = 1234567890\n\n") + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == expected + + +def test_chroots_outdated(args, tmpdir, monkeypatch): + args.work = str(tmpdir) + + # Override time.time(): now is "100" + def fake_time(): + return 100.0 + monkeypatch.setattr(time, "time", fake_time) + + # workdir.cfg does not exist + func = pmb.config.workdir.chroots_outdated + assert func(args) is False + + # workdir.cfg is empty file + with open(args.work + "/workdir.cfg", "w") as handle: + handle.write("") + assert func(args) is False + + # Write fake workdir.cfg: native was created at "90" + with open(args.work + "/workdir.cfg", "w") as handle: + handle.write("[chroot-init-dates]\nnative = 90\n\n") + + # Outdated (date_outdated: 90) + monkeypatch.setattr(pmb.config, "chroot_outdated", 10) + assert func(args) is True + + # Not outdated (date_outdated: 89) + monkeypatch.setattr(pmb.config, "chroot_outdated", 11) + assert func(args) is False + + +def test_clean(args, tmpdir): + args.work = str(tmpdir) + + # 0. workdir.cfg does not exist + func = pmb.config.workdir.clean + assert func(args) is None + + # Write fake workdir.cfg + cfg_fake = "[chroot-init-dates]\nnative = 1337\n\n" + with open(args.work + "/workdir.cfg", "w") as handle: + handle.write(cfg_fake) + + # 1. chroot_native dir exists + os.makedirs(args.work + "/chroot_native") + assert func(args) is False + + # workdir.cfg: unchanged + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == cfg_fake + + # 2. chroot_native dir does not exist + os.rmdir(args.work + "/chroot_native") + assert func(args) is True + + # workdir.cfg: "native" entry removed + with open(args.work + "/workdir.cfg", "r") as handle: + assert handle.read() == "[chroot-init-dates]\n\n"