|
1 |
| -#!/usr/bin/env bash |
2 |
| -set -euo pipefail |
3 |
| - |
4 |
| -# This is just a thin wrapper around prune_builds. That way we still get the |
5 |
| -# preflight checks to make sure the workdir looks sane. |
6 |
| - |
7 |
| -dn=$(dirname "$0") |
8 |
| -# shellcheck source=src/cmdlib.sh |
9 |
| -. "${dn}"/cmdlib.sh |
10 |
| - |
11 |
| -print_help() { |
12 |
| - cat 1>&2 <<'EOF' |
13 |
| -Usage: coreos-assembler prune --help |
14 |
| - coreos-assembler prune [--keep=N] [--keep-last-days=N] |
15 |
| -
|
16 |
| - Delete older untagged build artifacts. By default, only the last 3 untagged |
17 |
| - builds are kept. This can be overridden with the `--keep` option. |
18 |
| -EOF |
19 |
| -} |
20 |
| - |
21 |
| -# Parse options |
22 |
| -KEEP_LAST_N= |
23 |
| -KEEP_LAST_DAYS= |
24 |
| -rc=0 |
25 |
| -options=$(getopt --options h --longoptions help,keep:,keep-last-days: -- "$@") || rc=$? |
26 |
| -[ $rc -eq 0 ] || { |
27 |
| - print_help |
28 |
| - exit 1 |
29 |
| -} |
30 |
| -eval set -- "$options" |
31 |
| -while true; do |
32 |
| - case "$1" in |
33 |
| - -h | --help) |
34 |
| - print_help |
35 |
| - exit 0 |
36 |
| - ;; |
37 |
| - --keep) |
38 |
| - shift |
39 |
| - KEEP_LAST_N="$1" |
40 |
| - ;; |
41 |
| - --keep-last-days) |
42 |
| - shift |
43 |
| - KEEP_LAST_DAYS="$1" |
44 |
| - ;; |
45 |
| - --) |
46 |
| - shift |
47 |
| - break |
48 |
| - ;; |
49 |
| - *) |
50 |
| - fatal "$0: unrecognized option: $1" |
51 |
| - exit 1 |
52 |
| - ;; |
53 |
| - esac |
54 |
| - shift |
55 |
| -done |
56 |
| - |
57 |
| -if [ $# -ne 0 ]; then |
58 |
| - print_help |
59 |
| - fatal "ERROR: Too many arguments" |
60 |
| - exit 1 |
61 |
| -fi |
62 |
| - |
63 |
| -# just support one of the two for now |
64 |
| -if [ -n "${KEEP_LAST_N:-}" ] && [ -n "${KEEP_LAST_DAYS:-}" ]; then |
65 |
| - fatal "ERROR: Only one of --keep or --keep-last-days allowed" |
66 |
| -elif [ -z "${KEEP_LAST_N:-}" ] && [ -z "${KEEP_LAST_DAYS:-}" ]; then |
67 |
| - KEEP_LAST_N=3 |
68 |
| -fi |
69 |
| - |
70 |
| -if [ -n "${KEEP_LAST_DAYS:-}" ]; then |
71 |
| - set -- --keep-last-days "${KEEP_LAST_DAYS}" |
72 |
| -else |
73 |
| - set -- --keep-last-n "${KEEP_LAST_N}" |
74 |
| -fi |
75 |
| - |
76 |
| -prepare_build |
77 |
| - |
78 |
| -"${dn}"/prune_builds --workdir "${workdir:?}" "$@" |
| 1 | +#!/usr/bin/python3 -u |
| 2 | + |
| 3 | +''' |
| 4 | + This script removes previous builds. DO NOT USE on production pipelines |
| 5 | +''' |
| 6 | + |
| 7 | +import os |
| 8 | +import sys |
| 9 | +import argparse |
| 10 | + |
| 11 | + |
| 12 | +from datetime import timedelta, datetime, timezone |
| 13 | + |
| 14 | +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
| 15 | +from cosalib.builds import Builds |
| 16 | +from cosalib.prune import fetch_build_meta, delete_build |
| 17 | + |
| 18 | +# Let's just hardcode this here for now |
| 19 | +DEFAULT_KEEP_LAST_N = 3 |
| 20 | +DEFAULT_KEEP_LAST_DAYS = 7 |
| 21 | + |
| 22 | + |
| 23 | +parser = argparse.ArgumentParser(prog="coreos-assembler prune") |
| 24 | +parser.add_argument("--workdir", default='.', help="Path to workdir") |
| 25 | +parser.add_argument("--dry-run", help="Don't actually delete anything", |
| 26 | + action='store_true') |
| 27 | +parser.add_argument("--bucket", help="S3 bucket") |
| 28 | +parser.add_argument("--prefix", help="S3 prefix") |
| 29 | +parser.add_argument("--insert-only", metavar="BUILDID", action='store', |
| 30 | + help="Append a new latest build, do not prune") |
| 31 | +keep_options = parser.add_mutually_exclusive_group() |
| 32 | +keep_options.add_argument("--keep-last-n", type=int, metavar="N", |
| 33 | + default=DEFAULT_KEEP_LAST_N, |
| 34 | + help="Number of untagged builds to keep (0 for all)") |
| 35 | +keep_options.add_argument("--keep-last-days", metavar="N", type=int, |
| 36 | + default=DEFAULT_KEEP_LAST_DAYS, |
| 37 | + help="Keep untagged builds within number of days") |
| 38 | +args = parser.parse_args() |
| 39 | + |
| 40 | +keep_younger_than = None |
| 41 | +if args.keep_last_days is not None: |
| 42 | + if args.keep_last_days <= 0: |
| 43 | + raise argparse.ArgumentTypeError("value must be positive: %d" % |
| 44 | + args.keep_last_days) |
| 45 | + keep_younger_than = (datetime.now(timezone.utc) - |
| 46 | + timedelta(days=args.keep_last_days)) |
| 47 | + |
| 48 | +skip_pruning = (not keep_younger_than and args.keep_last_n == 0) |
| 49 | +print("prune: skip_pruning: {skip_pruning}") |
| 50 | + |
| 51 | +builds = Builds(args.workdir) |
| 52 | +# collect all builds being pointed to by tags |
| 53 | +tagged_builds = set([tag['target'] for tag in builds.raw().get('tags', [])]) |
| 54 | + |
| 55 | +# Handle --insert-only |
| 56 | +if args.insert_only: |
| 57 | + builds.insert_build(args.insert_only) |
| 58 | + builds.flush() |
| 59 | + print("prune: --insert-only completed") |
| 60 | + sys.exit(0) |
| 61 | + |
| 62 | +scanned_builds = [] |
| 63 | +for build in builds.raw()["builds"]: |
| 64 | + for arch in build['arches']: |
| 65 | + build = fetch_build_meta(build['id'], arch) |
| 66 | + if build: |
| 67 | + scanned_builds.append(build) |
| 68 | + |
| 69 | +new_builds = [] |
| 70 | +builds_to_delete = [] |
| 71 | + |
| 72 | +# Don't prune known builds |
| 73 | +if skip_pruning: |
| 74 | + new_builds = scanned_builds |
| 75 | +else: |
| 76 | + if keep_younger_than: |
| 77 | + for build in scanned_builds: |
| 78 | + if build.id in tagged_builds: |
| 79 | + print(f"Skipping tagged build {build.id}") |
| 80 | + new_builds.append(build) |
| 81 | + continue |
| 82 | + |
| 83 | + if build.timestamp < keep_younger_than: |
| 84 | + builds_to_delete.append(build) |
| 85 | + else: |
| 86 | + new_builds.append(build) |
| 87 | + else: |
| 88 | + n = args.keep_last_n |
| 89 | + assert(n > 0) |
| 90 | + for build in scanned_builds: |
| 91 | + if n == 0: |
| 92 | + builds_to_delete.append(build) |
| 93 | + else: |
| 94 | + new_builds.append(build) |
| 95 | + n = n - 1 |
| 96 | + |
| 97 | +print(f"prune: new builds: {new_builds}") |
| 98 | + |
| 99 | +# create a new builds list |
| 100 | +builds.raw()['builds'] = [] |
| 101 | +for build in reversed(new_builds): |
| 102 | + for arch in build['arches']: |
| 103 | + builds.insert_build(build['id'], arch) |
| 104 | + |
| 105 | +builds.bump_timestamp() |
| 106 | + |
| 107 | +if len(builds_to_delete) == 0: |
| 108 | + print("prune: not removing any builds") |
| 109 | +else: |
| 110 | + buildids = [x['id'] for x in builds_to_delete] |
| 111 | + print(f"prune: removing {' '.join(buildids)}") |
| 112 | + |
| 113 | +# now delete other build dirs not in the manifest |
| 114 | +error_during_pruning = False |
| 115 | +for build in builds_to_delete: |
| 116 | + print(f"Pruning {build}") |
| 117 | + if not args.dry_run: |
| 118 | + try: |
| 119 | + delete_build(build) |
| 120 | + except Exception as e: |
| 121 | + error_during_pruning = True |
| 122 | + print(f"{e}") |
| 123 | + |
| 124 | +if error_during_pruning: |
| 125 | + sys.exit(1) |
0 commit comments