diff --git a/examples/kvm-qemu/npbackup.cube.template b/examples/kvm-qemu/npbackup-cube.conf.template similarity index 100% rename from examples/kvm-qemu/npbackup.cube.template rename to examples/kvm-qemu/npbackup-cube.conf.template diff --git a/npbackup/configuration.py b/npbackup/configuration.py index 166961a..eb66f0b 100644 --- a/npbackup/configuration.py +++ b/npbackup/configuration.py @@ -7,7 +7,7 @@ __intname__ = "npbackup.configuration" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2024 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2024090601" +__build__ = "2024110701" __version__ = "npbackup 3.0.0+" MIN_CONF_VERSION = 3.0 @@ -78,7 +78,13 @@ def g(self, path, sep=".", default=None, list_ok=False): Getter for dot notation in an a dict/OrderedDict print(d.g('my.array.keys')) """ - return self.mlget(path.split(sep), default=default, list_ok=list_ok) + try: + return self.mlget(path.split(sep), default=default, list_ok=list_ok) + except AssertionError as exc: + logger.debug( + f"ERROR {exc} for path={path},sep={sep},default={default},list_ok={list_ok}" + ) + raise AssertionError def s(self, path, value, sep="."): @@ -203,6 +209,9 @@ empty_config_dict = { "yearly": 3, "tags": [], "keep_within": True, + "group_by_host": True, + "group_by_tags": True, + "group_by_paths": False, "ntp_server": None, }, # "prune_max_unused": None, # TODO diff --git a/npbackup/core/runner.py b/npbackup/core/runner.py index 333e080..30b71fa 100644 --- a/npbackup/core/runner.py +++ b/npbackup/core/runner.py @@ -7,7 +7,7 @@ __intname__ = "npbackup.gui.core.runner" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2024 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2024110301" +__build__ = "2024110701" from typing import Optional, Callable, Union, List @@ -1444,10 +1444,17 @@ class NPBackupRunner: msg = f"Empty retention policy. Won't run" self.write_logs(msg, level="error") return self.convert_to_json_output(False, msg) + + # Convert group by to list + group_by = [] + for entry in ["host", "paths", "tags"]: + if self.repo_config.g(f"repo_opts.retention_policy.group_by_{entry}"): + group_by.append(entry) + self.write_logs( f"Forgetting snapshots using retention policy: {policy}", level="info" ) - result = self.restic_runner.forget(policy=policy) + result = self.restic_runner.forget(policy=policy, group_by=group_by) else: self.write_logs( "Bogus options given to forget: snapshots={snapshots}, policy={policy}", diff --git a/npbackup/gui/config.py b/npbackup/gui/config.py index 7b0cd63..25c8ef9 100644 --- a/npbackup/gui/config.py +++ b/npbackup/gui/config.py @@ -7,7 +7,7 @@ __intname__ = "npbackup.gui.config" __author__ = "Orsiris de Jong" __copyright__ = "Copyright (C) 2022-2024 NetInvent" __license__ = "GPL-3.0-only" -__build__ = "2024072301" +__build__ = "2024110701" from typing import List, Tuple @@ -1519,6 +1519,40 @@ def config_gui(full_config: dict, config_file: str): size=(100, 1), ), ], + [sg.Text(_t("config_gui.policiy_group_by"))], + [ + sg.Image( + NON_INHERITED_ICON, + key="inherited.repo_opts.retention_policy.group_by_host", + tooltip=_t("config_gui.group_inherited"), + pad=1, + ), + sg.Checkbox( + _t("config_gui.group_by_host"), + key="repo_opts.retention_policy.group_by_host", + ), + sg.Image( + NON_INHERITED_ICON, + key="inherited.repo_opts.retention_policy.group_by_paths", + tooltip=_t("config_gui.group_inherited"), + pad=1, + ), + sg.Checkbox( + _t("config_gui.group_by_paths"), + key="repo_opts.retention_policy.group_by_paths", + ), + sg.Image( + NON_INHERITED_ICON, + key="inherited.repo_opts.retention_policy.group_by_tags", + tooltip=_t("config_gui.group_inherited"), + pad=1, + ), + sg.Checkbox( + _t("config_gui.group_by_tags"), + key="repo_opts.retention_policy.group_by_tags", + ), + ], + [sg.Text(_t("config_gui.policiy_group_by_explanation"))], [sg.HorizontalSeparator()], [ sg.Column( diff --git a/npbackup/restic_wrapper/__init__.py b/npbackup/restic_wrapper/__init__.py index 865b093..42a334c 100644 --- a/npbackup/restic_wrapper/__init__.py +++ b/npbackup/restic_wrapper/__init__.py @@ -1089,6 +1089,7 @@ class ResticRunner: self, snapshots: Optional[Union[List[str], Optional[str]]] = None, policy: Optional[dict] = None, + group_by: Optional[List[str]] = None, ) -> Union[bool, str, dict]: """ Execute forget command for given snapshot @@ -1119,6 +1120,8 @@ class ResticRunner: cmd += f" --keep-tag {tag}" else: cmd += f" --{key.replace('_', '-')} {value}" + if group_by: + cmd += f' --group-by {",".join(group_by)}' cmds = [cmd] # We need to be verbose here since server errors will not stop client from deletion attempts diff --git a/npbackup/translations/config_gui.en.yml b/npbackup/translations/config_gui.en.yml index a416d0d..462ddf2 100644 --- a/npbackup/translations/config_gui.en.yml +++ b/npbackup/translations/config_gui.en.yml @@ -162,4 +162,10 @@ en: enter_label_value: Enter label value enter_labvel: Enter label - suggested_encrypted_env_variables: Suggested encrypted environment variables \ No newline at end of file + suggested_encrypted_env_variables: Suggested encrypted environment variables + + policiy_group_by: Apply retention policy by grouping snapshots + group_by_host: Group by host + group_by_paths: Group by paths + group_by_tags: Group by tags + policiy_group_by_explanation: If none are chosen, snapshots will be grouped by host and paths \ No newline at end of file diff --git a/npbackup/translations/config_gui.fr.yml b/npbackup/translations/config_gui.fr.yml index cd9828e..d55d417 100644 --- a/npbackup/translations/config_gui.fr.yml +++ b/npbackup/translations/config_gui.fr.yml @@ -163,4 +163,10 @@ fr: enter_label_value: Entrer la valeur de l'étiquette enter_label: Entrer étiquette - suggested_encrypted_env_variables: Variables chiffrées suggérées \ No newline at end of file + suggested_encrypted_env_variables: Variables chiffrées suggérées + + policiy_group_by: Appliquer la politique de rétention par groupes d'instantanés + group_by_host: Grouper par nom d'hôte + group_by_paths: Grouper par chemins sauvegardés + group_by_tags: Grouper par tags + policiy_group_by_explanation: Si aucun groupement n'est choisi, le groupement par nom d'hôte et chemin de sauvegarde sera utilisé \ No newline at end of file