#!/usr/bin/env python3 from __future__ import print_function import argparse import os import shutil from pathlib import Path def main(): parser = argparse.ArgumentParser(description='Keep a bounded number of immutable platform releases.') parser.add_argument('root') parser.add_argument('limit', type=int) parser.add_argument('protected', nargs='*') args = parser.parse_args() root = Path(args.root).resolve() releases = (root / 'releases').resolve() if root == Path('/') or releases.parent != root or not releases.is_dir(): raise SystemExit('unsafe or missing release root: {}'.format(releases)) if args.limit < 2: raise SystemExit('release history limit must be at least 2') protected = set() for value in args.protected: path = Path(value).resolve() if path.parent != releases or not path.is_dir(): raise SystemExit('protected release is outside the release root: {}'.format(path)) protected.add(path) metadata_removed = 0 for path in releases.iterdir(): if path.is_file() and path.name.startswith('._'): path.unlink() metadata_removed += 1 candidates = [path.resolve() for path in releases.iterdir() if path.is_dir() and not path.is_symlink()] candidates.sort(key=lambda path: path.stat().st_mtime, reverse=True) keep = set(protected) for path in candidates: if len(keep) >= args.limit: break keep.add(path) removed = 0 for path in candidates: if path in keep: continue if path.parent != releases: raise SystemExit('refusing to remove a nested or escaped path: {}'.format(path)) shutil.rmtree(str(path)) removed += 1 print('release_prune=ok before={} kept={} removed={} metadata_removed={}'.format( len(candidates), len(keep), removed, metadata_removed )) if __name__ == '__main__': main()