diff options
Diffstat (limited to 'modules')
| -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): |
