1
2
3 """
4 Remove obsolete source and binary associations from suites.
5
6 @contact: Debian FTP Master <ftpmaster@debian.org>
7 @copyright: 2009 Torsten Werner <twerner@debian.org>
8 @license: GNU General Public License version 2 or later
9 """
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import sys
26
27 import apt_pkg
28 from sqlalchemy.sql import exists, text
29 from tabulate import tabulate
30
31 from daklib import daklog, utils
32 from daklib.config import Config
33 from daklib.dbconn import DBConn, PolicyQueue, Suite
34
35 Options = None
36 Logger = None
37
38
40 return session.execute(
41 text(
42 """
43 WITH
44 -- Provide (source, suite) tuple of all source packages to remain
45 remain_source AS (
46 SELECT
47 *
48 FROM (
49 SELECT
50 source.id AS source_id,
51 src_associations.suite AS suite_id,
52 -- generate rank over versions of a source package in one suite
53 -- "1" being the newest
54 dense_rank() OVER (
55 PARTITION BY source.source, src_associations.suite
56 ORDER BY source.version DESC
57 ) AS version_rank
58 FROM
59 source
60 INNER JOIN src_associations ON
61 src_associations.source = source.id
62 AND src_associations.suite = ANY(:suite_ids)
63 ) AS source_ranked
64 WHERE
65 version_rank = 1
66 ),
67 -- Provide (source, arch, suite) tuple of all binary packages to remain
68 remain_binaries AS (
69 SELECT
70 *
71 FROM (
72 SELECT
73 binaries.id,
74 binaries.architecture AS arch_id,
75 bin_associations.suite AS suite_id,
76 source.id AS source_id,
77 architecture.arch_string AS arch,
78 -- arch of newest version
79 first_value(architecture.arch_string) OVER (
80 PARTITION BY binaries.package, bin_associations.suite
81 ORDER BY binaries.version DESC
82 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
83 ) as arch_first,
84 -- generate rank over versions of a source package in one suite
85 -- "1" being the newest
86 -- if newest package is arch-any, we use the rank only over current arch
87 dense_rank() OVER (
88 PARTITION BY binaries.package, binaries.architecture, bin_associations.suite
89 ORDER BY binaries.version DESC
90 ) AS version_rank_any,
91 -- if newest package is arch-all, we use the rank over all arches
92 -- this makes it possible to replace all by any and any by all
93 dense_rank() OVER (
94 PARTITION BY binaries.package, bin_associations.suite
95 ORDER BY binaries.version DESC
96 ) AS version_rank_all
97 FROM
98 binaries
99 INNER JOIN source ON source.id = binaries.source
100 INNER JOIN bin_associations ON
101 bin_associations.bin = binaries.id
102 AND bin_associations.suite = ANY(:suite_ids)
103 INNER JOIN architecture ON architecture.id = binaries.architecture
104 ) AS source_rank
105 WHERE
106 -- we only want to retain the newest of each
107 CASE
108 WHEN arch != 'all' AND arch_first != 'all' THEN version_rank_any = 1
109 ELSE version_rank_all = 1
110 END
111 ),
112 -- Figure out which source we should remove
113 -- A binary forces the corresponding source to remain
114 dominate_source AS (
115 SELECT
116 source.source AS source_package,
117 source.version AS source_version,
118 source.source AS package,
119 source.version,
120 'source'::text AS arch,
121 suite.suite_name AS suite,
122 src_associations.id AS assoc_id
123 FROM
124 source
125 INNER JOIN src_associations ON
126 src_associations.source = source.id
127 AND src_associations.suite = ANY(:suite_ids)
128 INNER join suite ON suite.id = src_associations.suite
129 LEFT JOIN remain_binaries ON
130 remain_binaries.source_id = source.id
131 AND remain_binaries.suite_id = src_associations.suite
132 LEFT JOIN remain_source ON
133 remain_source.source_id = source.id
134 AND remain_source.suite_id = src_associations.suite
135 WHERE
136 remain_binaries.source_id IS NULL
137 AND remain_source.source_id IS NULL
138 ),
139 -- Figure out which arch-any binaries we should remove
140 dominate_binaries AS (
141 SELECT
142 source.source AS source_package,
143 source.version AS source_version,
144 binaries.package AS package,
145 binaries.version,
146 architecture.arch_string AS arch,
147 suite.suite_name AS suite,
148 bin_associations.id AS assoc_id
149 FROM
150 binaries
151 INNER JOIN source ON source.id = binaries.source
152 INNER JOIN bin_associations ON
153 bin_associations.bin = binaries.id
154 AND bin_associations.suite = ANY(:suite_ids)
155 INNER JOIN architecture ON architecture.id = binaries.architecture
156 INNER join suite ON suite.id = bin_associations.suite
157 LEFT JOIN remain_binaries ON
158 remain_binaries.id = binaries.id
159 AND remain_binaries.arch_id = binaries.architecture
160 AND remain_binaries.suite_id = bin_associations.suite
161 WHERE
162 remain_binaries.source_id IS NULL
163 AND binaries.architecture != (SELECT id from architecture WHERE arch_string = 'all')
164 ),
165 -- Figure out which arch-all binaries we should remove
166 -- A arch-any binary forces the related arch-all binaries to remain
167 dominate_binaries_all AS (
168 SELECT
169 source.source AS source_package,
170 source.version AS source_version,
171 binaries.package AS package,
172 binaries.version,
173 architecture.arch_string AS arch,
174 suite.suite_name AS suite,
175 bin_associations.id AS assoc_id
176 FROM
177 binaries
178 INNER JOIN source ON source.id = binaries.source
179 INNER JOIN bin_associations ON
180 bin_associations.bin = binaries.id
181 AND bin_associations.suite = ANY(:suite_ids)
182 INNER JOIN architecture ON architecture.id = binaries.architecture
183 INNER join suite ON suite.id = bin_associations.suite
184 LEFT JOIN remain_binaries ON
185 remain_binaries.id = binaries.id
186 AND remain_binaries.arch_id = binaries.architecture
187 AND remain_binaries.suite_id = bin_associations.suite
188 LEFT JOIN remain_binaries AS remain_binaries_any ON
189 remain_binaries_any.source_id = source.id
190 AND remain_binaries_any.suite_id = bin_associations.suite
191 AND remain_binaries_any.arch_id != (SELECT id from architecture WHERE arch_string = 'all')
192 WHERE
193 remain_binaries.source_id IS NULL
194 AND remain_binaries_any.source_id IS NULL
195 AND binaries.architecture = (SELECT id from architecture WHERE arch_string = 'all')
196 )
197 SELECT
198 *
199 FROM
200 dominate_source
201 UNION SELECT
202 *
203 FROM
204 dominate_binaries
205 UNION SELECT
206 *
207 FROM
208 dominate_binaries_all
209 ORDER BY
210 source_package, source_version, package, version, arch, suite
211 """
212 ).params(
213 suite_ids=[s.suite_id for s in suites],
214 )
215 )
216
217
219 result = session.execute(
220 text(
221 """
222 DELETE
223 FROM {}
224 WHERE id = ANY(:assoc_ids)
225 """.format(
226 table
227 )
228 ).params(
229 assoc_ids=list(ids),
230 )
231 )
232
233 assert result.rowcount == len(
234 ids
235 ), "Rows deleted are not equal to deletion requests"
236
237
239 ids_bin = set()
240 ids_src = set()
241
242 for e in assocs:
243 Logger.log(["newer", e.package, e.version, e.suite, e.arch, e.assoc_id])
244
245 if e.arch == "source":
246 ids_src.add(e.assoc_id)
247 else:
248 ids_bin.add(e.assoc_id)
249
250 delete_associations_table("bin_associations", ids_bin, session)
251 delete_associations_table("src_associations", ids_src, session)
252
253
255 print(
256 """Usage: dak dominate [OPTIONS]
257 Remove obsolete source and binary associations from suites.
258
259 -s, --suite=SUITE act on this suite
260 -h, --help show this help and exit
261 -n, --no-action don't commit changes
262 -f, --force also clean up untouchable suites
263
264 SUITE can be comma (or space) separated list, e.g.
265 --suite=testing,unstable"""
266 )
267 sys.exit()
268
269
271 global Options, Logger
272 cnf = Config()
273 Arguments = [
274 ("h", "help", "Obsolete::Options::Help"),
275 ("s", "suite", "Obsolete::Options::Suite", "HasArg"),
276 ("n", "no-action", "Obsolete::Options::No-Action"),
277 ("f", "force", "Obsolete::Options::Force"),
278 ]
279 cnf["Obsolete::Options::Help"] = ""
280 cnf["Obsolete::Options::No-Action"] = ""
281 cnf["Obsolete::Options::Force"] = ""
282 apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
283 Options = cnf.subtree("Obsolete::Options")
284 if Options["Help"]:
285 usage()
286
287 if not Options["No-Action"]:
288 Logger = daklog.Logger("dominate")
289 session = DBConn().session()
290
291 suites_query = (
292 session.query(Suite)
293 .order_by(Suite.suite_name)
294 .filter(~exists().where(Suite.suite_id == PolicyQueue.suite_id))
295 )
296 if "Suite" in Options:
297 suites_query = suites_query.filter(
298 Suite.suite_name.in_(utils.split_args(Options["Suite"]))
299 )
300 if not Options["Force"]:
301 suites_query = suites_query.filter_by(untouchable=False)
302 suites = suites_query.all()
303
304 assocs = list(retrieve_associations(suites, session))
305
306 if Options["No-Action"]:
307 headers = (
308 "source package",
309 "source version",
310 "package",
311 "version",
312 "arch",
313 "suite",
314 "id",
315 )
316 print((tabulate(assocs, headers, tablefmt="orgtbl")))
317 session.rollback()
318
319 else:
320 delete_associations(assocs, session)
321 session.commit()
322
323 if Logger:
324 Logger.close()
325
326
327 if __name__ == "__main__":
328 main()
329