Merge branch '18093-create-release-ticket'
[arvados-dev.git] / jenkins / packaging-move-dev-to-attic.py
1 #!/usr/bin/env python3
2
3 # Copyright (C) The Arvados Authors. All rights reserved.
4 #
5 # SPDX-License-Identifier: AGPL-3.0
6
7 import argparse
8 import datetime
9 import os
10 import subprocess
11 import pprint
12 import re
13
14 from datetime import date
15
16 class DebugExecutor:
17   def __init__(self, package_list):
18     self.package_list = package_list
19
20   def do_it(self):
21     for a in self.package_list:
22       print (a[2])
23
24 class MoveExecutor:
25   def __init__(self, distro, dry_run, package_list):
26     self.distro = distro
27     self.dry_run = dry_run
28     self.package_list = package_list
29
30   def move_it(self):
31     for a in self.package_list:
32       if a[3]:
33         source = self.distro
34         destination = source.replace('dev','attic')
35         f = os.path.basename(os.path.splitext(a[1])[0])
36         print ("Moving " + f + " to " + destination)
37         extra = ""
38         if self.dry_run:
39             extra = "-dry-run "
40         output = subprocess.getoutput("aptly repo move " + extra + source + " " + destination + " " + f)
41         print(output)
42
43   def update_it(self):
44     distroBase = re.sub('-.*$', '', self.distro)
45     if not self.dry_run:
46         output = subprocess.getoutput("aptly publish update " + distroBase + "-dev filesystem:" + distroBase + ":")
47         print(output)
48         output = subprocess.getoutput("aptly publish update " + distroBase + "-attic filesystem:" + distroBase + ":")
49         print(output)
50     else:
51         print("Dry-run: skipping aptly publish update " + distroBase + "-dev filesystem:" + distroBase + ":")
52         print("Dry-run: skipping aptly publish update " + distroBase + "-attic filesystem:" + distroBase + ":")
53
54 class CollectPackageName:
55   def __init__(self, cache_dir, distro, min_packages,  cutoff_date):
56     self.cache_dir = cache_dir
57     self.distro = distro
58     self.min_packages = min_packages
59     self.cutoff_date_unixepoch = int(cutoff_date.strftime('%s'))
60
61   def collect_packages(self):
62     distroBase = re.sub('-.*$', '', self.distro)
63     directory=os.path.join(self.cache_dir,distroBase,'pool/main')
64
65     ## rtn will have 4 element tuple: package_name, the path, the creation time for sorting, and if it's a candidate for deletion
66     rtn = []
67
68     # Get the list of packages in the repo
69     output = subprocess.getoutput("aptly repo search " + self.distro)
70     for f in output.splitlines():
71       pkg = f.split('_')[0]
72       # This is nasty and slow, but aptly doesn't seem to have a way to provide
73       # the on-disk path for a package in its repository. We also can't query
74       # for the list of packages that fit the cutoff date constraint with a
75       # 'package-query' parameter: the 'Date' field would be appropriate for
76       # that, but it's not populated for our packages because we don't ship a
77       # changelog with them (that's where the 'Date' field comes from, as per
78       # the Debian policy manual, cf.
79       # https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-date).
80       the_file = subprocess.getoutput("find " + directory + " -name " + f + ".deb")
81       if the_file == "":
82           print("WARNING: skipping package, could not find file for package " + f + " under directory " + directory)
83           continue
84       rtn.append ( (pkg, the_file,
85                     os.path.getmtime(the_file),
86                     os.path.getmtime(the_file) < self.cutoff_date_unixepoch) )
87     return self.collect_candidates_excluding_N_last(rtn)
88
89   def collect_candidates_excluding_N_last(self, tuples_with_packages):
90     return_value = []
91
92     ## separate all file into packages. (use the first element in the tuple for this)
93     dictionary_per_package  = {}
94     for x in tuples_with_packages:
95       dictionary_per_package.setdefault(x[0], []).append(x[0:])
96
97     for pkg_name, metadata in dictionary_per_package.items():
98       candidates_local_copy = metadata[:]
99
100       ## order them by date
101       candidates_local_copy.sort(key=lambda tup: tup[2])
102
103       return_value.extend(candidates_local_copy[:-self.min_packages])
104
105     return return_value
106
107 def distro(astring):
108     if re.fullmatch(r'.*-dev', astring) == None:
109         raise ValueError
110     return astring
111
112 today = date.today()
113 parser = argparse.ArgumentParser(description='List the packages to delete.')
114 parser.add_argument('distro',
115                     type=distro,
116                     help='distro to process, must be a dev repository, e.g. buster-dev')
117 parser.add_argument('--repo_dir',
118                     default='/var/www/aptly_public/',
119                     help='parent directory of the aptly repositories (default:  %(default)s)')
120 parser.add_argument('--min_packages', type=int,
121                     default=5,
122                     help='minimum amount of packages to leave in the repo (default:  %(default)s)')
123 parser.add_argument('--cutoff_date', type=lambda s: datetime.datetime.strptime(s, '%Y-%m-%d'),
124                     default=today.strftime("%Y-%m-%d"),
125                     help='date to cut-off in format YYYY-MM-DD (default:  %(default)s)')
126 parser.add_argument('--dry_run', type=bool,
127                     default=False,
128                     help='show what would be done, without doing it (default:  %(default)s)')
129
130 args = parser.parse_args()
131
132
133 p = CollectPackageName(args.repo_dir, args.distro, args.min_packages,  args.cutoff_date)
134
135 executor = MoveExecutor(args.distro, args.dry_run, p.collect_packages())
136
137 executor.move_it()
138 executor.update_it()
139