summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--modules/zfssnap/default.nix2
-rw-r--r--modules/zfssnap/zfssnap.py37
2 files changed, 23 insertions, 16 deletions
diff --git a/modules/zfssnap/default.nix b/modules/zfssnap/default.nix
index 86e7fdc0..27e5198d 100644
--- a/modules/zfssnap/default.nix
+++ b/modules/zfssnap/default.nix
@@ -77,7 +77,7 @@ in {
77 ExecStart = let 77 ExecStart = let
78 mkSectionName = name: strings.escape [ "[" "]" ] (strings.toUpper name); 78 mkSectionName = name: strings.escape [ "[" "]" ] (strings.toUpper name);
79 zfssnapConfig = generators.toINI { inherit mkSectionName; } cfg.config; 79 zfssnapConfig = generators.toINI { inherit mkSectionName; } cfg.config;
80 in "${zfssnap}/bin/zfssnap -v prune --config=${pkgs.writeText "zfssnap.ini" zfssnapConfig}"; 80 in "${zfssnap}/bin/zfssnap -v prune --dry-run --config=${pkgs.writeText "zfssnap.ini" zfssnapConfig}";
81 }; 81 };
82 }; 82 };
83 83
diff --git a/modules/zfssnap/zfssnap.py b/modules/zfssnap/zfssnap.py
index 1c3e1f9a..cf6ab28c 100644
--- a/modules/zfssnap/zfssnap.py
+++ b/modules/zfssnap/zfssnap.py
@@ -53,8 +53,7 @@ def _get_items():
53 53
54 return items 54 return items
55 55
56def prune(config, dry_run): 56def prune(config, dry_run, keep_newest):
57
58 items = defaultdict(list) 57 items = defaultdict(list)
59 58
60 args = ['zfs', 'get', '-H', '-p', '-o', 'name,value', '-t', 'snapshot', 'creation'] 59 args = ['zfs', 'get', '-H', '-p', '-o', 'name,value', '-t', 'snapshot', 'creation']
@@ -116,21 +115,25 @@ def prune(config, dry_run):
116 desired_count = config.getint('KEEP', rule, fallback=0) 115 desired_count = config.getint('KEEP', rule, fallback=0)
117 116
118 for base, snaps in items.items(): 117 for base, snaps in items.items():
119 last_period = None 118 periods = OrderedDict()
120 to_keep = desired_count
121
122 if to_keep == 0:
123 continue
124 119
125 for snap in sorted(snaps, key=lambda snap: snap['creation'], reverse=True): 120 for snap in sorted(snaps, key=lambda snap: snap['creation'], reverse=keep_newest):
121 period = pattern(snap['creation'])
122 if period not in periods:
123 periods[period] = deque()
124 periods[period].append(snap)
125
126 to_keep = desired_count
127 ordered_periods = reversed(periods.items()) if keep_newest else periods.items()
128 for period, period_snaps in ordered_periods:
126 if to_keep == 0: 129 if to_keep == 0:
127 break 130 break
128 131
129 period = pattern(snap['creation']) 132 for snap in period_snaps:
130 if period != last_period: 133 if snap['name'] not in keep:
131 last_period = period 134 keep_because(base, snap['name'], rule, period=period)
132 keep_because(base, snap['name'], rule, period=period) 135 to_keep -= 1
133 to_keep -= 1 136 break
134 137
135 if to_keep > 0: 138 if to_keep > 0:
136 logger.debug(f'Missing {to_keep} to fulfill {rule}={desired_count} for ‘{base}’') 139 logger.debug(f'Missing {to_keep} to fulfill {rule}={desired_count} for ‘{base}’')
@@ -147,7 +150,10 @@ def prune(config, dry_run):
147 args += [snap] 150 args += [snap]
148 _log_cmd(*args) 151 _log_cmd(*args)
149 subprocess.run(args, check=True) 152 subprocess.run(args, check=True)
150 logger.info(f'Pruned ‘{snap}’') 153 if dry_run:
154 logger.info(f'Would have pruned ‘{snap}’')
155 else:
156 logger.info(f'Pruned ‘{snap}’')
151 157
152def rename(snapshots, destroy=False): 158def rename(snapshots, destroy=False):
153 args = ['zfs', 'get', '-H', '-p', '-o', 'name,value', 'creation', *snapshots] 159 args = ['zfs', 'get', '-H', '-p', '-o', 'name,value', 'creation', *snapshots]
@@ -269,6 +275,7 @@ def main():
269 prune_parser = subparsers.add_parser('prune') 275 prune_parser = subparsers.add_parser('prune')
270 prune_parser.add_argument('--config', '-c', dest='config_files', nargs='*', default=list()) 276 prune_parser.add_argument('--config', '-c', dest='config_files', nargs='*', default=list())
271 prune_parser.add_argument('--dry-run', '-n', action='store_true', default=False) 277 prune_parser.add_argument('--dry-run', '-n', action='store_true', default=False)
278 prune_parser.add_argument('--keep-newest', action='store_true', default=False)
272 prune_parser.set_defaults(cmd=prune) 279 prune_parser.set_defaults(cmd=prune)
273 args = parser.parse_args() 280 args = parser.parse_args()
274 281
@@ -280,7 +287,7 @@ def main():
280 logger.setLevel(logging.DEBUG) 287 logger.setLevel(logging.DEBUG)
281 288
282 cmdArgs = {} 289 cmdArgs = {}
283 for copy in {'snapshots', 'dry_run', 'destroy'}: 290 for copy in {'snapshots', 'dry_run', 'destroy', 'keep_newest'}:
284 if copy in vars(args): 291 if copy in vars(args):
285 cmdArgs[copy] = vars(args)[copy] 292 cmdArgs[copy] = vars(args)[copy]
286 if 'config_files' in vars(args): 293 if 'config_files' in vars(args):