diff options
Diffstat (limited to 'modules/zfssnap')
-rw-r--r-- | modules/zfssnap/default.nix | 2 | ||||
-rw-r--r-- | modules/zfssnap/zfssnap.py | 37 |
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 | ||
56 | def prune(config, dry_run): | 56 | def 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 | ||
152 | def rename(snapshots, destroy=False): | 158 | def 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): |