1
2
3 """ General purpose package removal tool for ftpmaster """
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 import functools
44 import os
45 import sys
46 import apt_pkg
47
48 from daklib.config import Config
49 from daklib.dbconn import *
50 from daklib import utils
51 from daklib.rm import remove
52
53
54
55 Options = None
56
57
58
59
61 print("""Usage: dak rm [OPTIONS] PACKAGE[...]
62 Remove PACKAGE(s) from suite(s).
63
64 -A, --no-arch-all-rdeps Do not report breaking arch:all packages
65 or Build-Depends-Indep
66 -a, --architecture=ARCH only act on this architecture
67 -b, --binary PACKAGE are binary packages to remove
68 -B, --binary-only remove binaries only
69 --binary-version=VER only remove packages with binary vesion VER
70 -c, --component=COMPONENT act on this component
71 -C, --carbon-copy=EMAIL send a CC of removal message to EMAIL
72 -d, --done=BUG# send removal message as closure to bug#
73 -D, --do-close also close all bugs associated to that package
74 -h, --help show this help and exit
75 -m, --reason=MSG reason for removal
76 -n, --no-action don't do anything
77 -o, --outdated remove only outdated sources or binaries that were
78 built from previous source versions
79 -p, --partial don't affect override files
80 -R, --rdep-check check reverse dependencies
81 -s, --suite=SUITE act on this suite
82 -S, --source-only remove source only
83 --source-version=VER only remove packages with source version VER
84
85 ARCH, BUG#, COMPONENT and SUITE can be comma (or space) separated lists, e.g.
86 --architecture=amd64,i386""")
87
88 sys.exit(exit_code)
89
90
91
92
93
94
95
96
97
99 answer = utils.input_or_exit("Continue (y/N)? ").lower()
100 if answer != "y":
101 print("Aborted.")
102 sys.exit(1)
103
104
105
106
108 print("Checking reverse dependencies...")
109 if utils.check_reverse_depends(removals, suite, arches, session, include_arch_all=include_arch_all):
110 print("Dependency problem found.")
111 if not Options["No-Action"]:
112 game_over()
113 else:
114 print("No dependency problem found.")
115 print()
116
117
118
119
121 global Options
122
123 cnf = Config()
124
125 Arguments = [('h', "help", "Rm::Options::Help"),
126 ('A', 'no-arch-all-rdeps', 'Rm::Options::NoArchAllRdeps'),
127 ('a', "architecture", "Rm::Options::Architecture", "HasArg"),
128 ('b', "binary", "Rm::Options::Binary"),
129 ('B', "binary-only", "Rm::Options::Binary-Only"),
130 ('\0', "binary-version", "Rm::Options::Binary-Version", "HasArg"),
131 ('c', "component", "Rm::Options::Component", "HasArg"),
132 ('C', "carbon-copy", "Rm::Options::Carbon-Copy", "HasArg"),
133 ('d', "done", "Rm::Options::Done", "HasArg"),
134 ('D', "do-close", "Rm::Options::Do-Close"),
135 ('R', "rdep-check", "Rm::Options::Rdep-Check"),
136 ('m', "reason", "Rm::Options::Reason", "HasArg"),
137 ('n', "no-action", "Rm::Options::No-Action"),
138 ('o', "outdated", "Rm::Options::Outdated"),
139 ('p', "partial", "Rm::Options::Partial"),
140 ('s', "suite", "Rm::Options::Suite", "HasArg"),
141 ('S', "source-only", "Rm::Options::Source-Only"),
142 ('\0', "source-version", "Rm::Options::Source-Version", "HasArg"),
143 ]
144
145 for i in ['NoArchAllRdeps',
146 "architecture", "binary", "binary-only", "carbon-copy", "component",
147 "done", "help", "no-action", "outdated", "partial", "rdep-check", "reason",
148 "source-only", "Do-Close"]:
149 key = "Rm::Options::%s" % (i)
150 if key not in cnf:
151 cnf[key] = ""
152 if "Rm::Options::Suite" not in cnf:
153 cnf["Rm::Options::Suite"] = "unstable"
154
155 arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv)
156 Options = cnf.subtree("Rm::Options")
157
158 if Options["Help"]:
159 usage()
160
161 session = DBConn().session()
162
163
164 if not arguments:
165 utils.fubar("need at least one package name as an argument.")
166 if Options["Architecture"] and Options["Source-Only"]:
167 utils.fubar("can't use -a/--architecture and -S/--source-only options simultaneously.")
168 actions = [Options["Binary"], Options["Binary-Only"], Options["Source-Only"]]
169 nr_actions = len([act for act in actions if act])
170 if nr_actions > 1:
171 utils.fubar("Only one of -b/--binary, -B/--binary-only and -S/--source-only can be used.")
172 if Options["Architecture"] and not Options["Partial"]:
173 utils.warn("-a/--architecture implies -p/--partial.")
174 Options["Partial"] = "true"
175 if Options["Outdated"] and not Options["Partial"]:
176 utils.warn("-o/--outdated implies -p/--partial.")
177 Options["Partial"] = "true"
178 if Options["Do-Close"] and not Options["Done"]:
179 utils.fubar("-D/--do-close needs -d/--done (bugnr).")
180 if (Options["Do-Close"]
181 and (Options["Binary"] or Options["Binary-Only"] or Options["Source-Only"])):
182 utils.fubar("-D/--do-close cannot be used with -b/--binary, -B/--binary-only or -S/--source-only.")
183
184
185
186
187 if not Options["No-Action"] and not Options["Carbon-Copy"] \
188 and not Options["Done"] and Options["Reason"].find("[auto-cruft]") == -1:
189 utils.fubar("Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal.")
190
191 parameters = {
192 "binary_version": Options.get("Binary-Version", "") or None,
193 "source_version": Options.get("Source-Version", "") or None,
194 }
195
196 if Options["Binary"]:
197 con_packages = "AND b.package IN :packages"
198 parameters['packages'] = tuple(arguments)
199 else:
200 con_packages = "AND s.source IN :sources"
201 parameters['sources'] = tuple(arguments)
202
203 (con_suites, con_architectures, con_components, check_source) = \
204 utils.parse_args(Options)
205
206
207 suite_ids_list = []
208 whitelists = []
209 suites = utils.split_args(Options["Suite"])
210 suites_list = utils.join_with_commas_and(suites)
211 if not Options["No-Action"]:
212 for suite in suites:
213 s = get_suite(suite, session=session)
214 if s is not None:
215 suite_ids_list.append(s.suite_id)
216 whitelists.append(s.mail_whitelist)
217 if suite in ("oldstable", "stable"):
218 print("**WARNING** About to remove from the (old)stable suite!")
219 print("This should only be done just prior to a (point) release and not at")
220 print("any other time.")
221 game_over()
222 elif suite == "testing":
223 print("**WARNING About to remove from the testing suite!")
224 print("There's no need to do this normally as removals from unstable will")
225 print("propogate to testing automagically.")
226 game_over()
227
228
229 if Options["Architecture"] and check_source:
230 utils.warn("'source' in -a/--argument makes no sense and is ignored.")
231
232
233 if Options["Rdep-Check"] and len(suites) > 1:
234 utils.fubar("Reverse dependency check on multiple suites is not implemented.")
235
236 q_outdated = "TRUE"
237 if Options["Outdated"]:
238 q_outdated = "s.version < newest_source.version"
239
240 to_remove = []
241 maintainers = {}
242
243
244
245
246
247
248
249 if Options["Binary"]:
250
251 q = session.execute("""
252 SELECT b.package, b.version, a.arch_string, b.id, b.maintainer, s.source,
253 s.version as source_version, newest_source.version as newest_sversion
254 FROM binaries b
255 JOIN source s ON s.id = b.source
256 JOIN bin_associations ba ON ba.bin = b.id
257 JOIN architecture a ON a.id = b.architecture
258 JOIN suite su ON su.id = ba.suite
259 JOIN files f ON f.id = b.file
260 JOIN files_archive_map af ON af.file_id = f.id AND af.archive_id = su.archive_id
261 JOIN component c ON c.id = af.component_id
262 JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite
263 WHERE
264 (:binary_version IS NULL OR b.version = :binary_version)
265 AND (:source_version IS NULL OR s.version = :source_version)
266 AND %s %s %s %s %s
267 """ % (q_outdated, con_packages, con_suites, con_components, con_architectures), parameters)
268 to_remove.extend(q)
269 else:
270
271 if not Options["Binary-Only"]:
272 q = session.execute("""
273 SELECT s.source, s.version, 'source', s.id, s.maintainer, s.source,
274 s.version as source_version, newest_source.version as newest_sversion
275 FROM source s
276 JOIN src_associations sa ON sa.source = s.id
277 JOIN suite su ON su.id = sa.suite
278 JOIN archive ON archive.id = su.archive_id
279 JOIN files f ON f.id = s.file
280 JOIN files_archive_map af ON af.file_id = f.id AND af.archive_id = su.archive_id
281 JOIN component c ON c.id = af.component_id
282 JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite
283 WHERE
284 (:source_version IS NULL OR s.version = :source_version)
285 AND %s %s %s %s
286 """ % (q_outdated, con_packages, con_suites, con_components), parameters)
287 to_remove.extend(q)
288 if not Options["Source-Only"]:
289
290 q = session.execute("""
291 SELECT b.package, b.version, a.arch_string, b.id, b.maintainer, s.source,
292 s.version as source_version, newest_source.version as newest_sversion
293 FROM binaries b
294 JOIN bin_associations ba ON b.id = ba.bin
295 JOIN architecture a ON b.architecture = a.id
296 JOIN suite su ON ba.suite = su.id
297 JOIN archive ON archive.id = su.archive_id
298 JOIN files_archive_map af ON b.file = af.file_id AND af.archive_id = archive.id
299 JOIN component c ON af.component_id = c.id
300 JOIN source s ON b.source = s.id
301 JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite
302 WHERE
303 (:binary_version IS NULL OR b.version = :binary_version)
304 AND (:source_version IS NULL OR s.version = :source_version)
305 AND %s %s %s %s %s
306 """ % (q_outdated, con_packages, con_suites, con_components, con_architectures), parameters)
307 to_remove.extend(q)
308
309 if not to_remove:
310 print("Nothing to do.")
311 sys.exit(0)
312
313
314
315
316
317
318
319
320 carbon_copy = []
321 for copy_to in utils.split_args(Options.get("Carbon-Copy")):
322 if copy_to.isdigit():
323 if "Dinstall::BugServer" in cnf:
324 carbon_copy.append(copy_to + "@" + cnf["Dinstall::BugServer"])
325 else:
326 utils.fubar("Asked to send mail to #%s in BTS but Dinstall::BugServer is not configured" % copy_to)
327 elif copy_to == 'package':
328 for package in set([s[5] for s in to_remove]):
329 if "Dinstall::PackagesServer" in cnf:
330 carbon_copy.append(package + "@" + cnf["Dinstall::PackagesServer"])
331 elif '@' in copy_to:
332 carbon_copy.append(copy_to)
333 else:
334 utils.fubar("Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." % (copy_to))
335
336
337
338 if not Options["Reason"] and not Options["No-Action"]:
339 Options["Reason"] = utils.call_editor()
340
341
342 d = {}
343 for i in to_remove:
344 package = i[0]
345 version = i[1]
346 architecture = i[2]
347 maintainer = i[4]
348 maintainers[maintainer] = ""
349 source = i[5]
350 source_version = i[6]
351 source_newest = i[7]
352 if package not in d:
353 d[package] = {}
354 if version not in d[package]:
355 d[package][version] = []
356 if architecture not in d[package][version]:
357 d[package][version].append(architecture)
358
359 maintainer_list = []
360 for maintainer_id in maintainers.keys():
361 maintainer_list.append(get_maintainer(maintainer_id).name)
362 summary = ""
363 removals = sorted(d)
364 for package in removals:
365 versions = sorted(d[package], key=functools.cmp_to_key(apt_pkg.version_compare))
366 for version in versions:
367 d[package][version].sort(key=utils.ArchKey)
368 summary += "%10s | %10s | %s\n" % (package, version, ", ".join(d[package][version]))
369 print("Will remove the following packages from %s:" % (suites_list))
370 print()
371 print(summary)
372 print("Maintainer: %s" % ", ".join(maintainer_list))
373 if Options["Done"]:
374 print("Will also close bugs: " + Options["Done"])
375 if carbon_copy:
376 print("Will also send CCs to: " + ", ".join(carbon_copy))
377 if Options["Do-Close"]:
378 print("Will also close associated bug reports.")
379 print()
380 print("------------------- Reason -------------------")
381 print(Options["Reason"])
382 print("----------------------------------------------")
383 print()
384
385 if Options["Rdep-Check"]:
386 arches = utils.split_args(Options["Architecture"])
387 include_arch_all = Options['NoArchAllRdeps'] == ''
388 if include_arch_all and 'all' in arches:
389
390 arches = None
391 reverse_depends_check(removals, suites[0], arches, session, include_arch_all=include_arch_all)
392
393
394 if Options["No-Action"]:
395 sys.exit(0)
396
397 print("Going to remove the packages now.")
398 game_over()
399
400
401 print("Deleting...", end=' ')
402 sys.stdout.flush()
403
404 try:
405 bugs = utils.split_args(Options["Done"])
406 remove(session, Options["Reason"], suites, to_remove,
407 partial=Options["Partial"], components=utils.split_args(Options["Component"]),
408 done_bugs=bugs, carbon_copy=carbon_copy, close_related_bugs=Options["Do-Close"]
409 )
410 except ValueError as ex:
411 utils.fubar(ex.message)
412 else:
413 print("done.")
414
415
416
417
418 if __name__ == '__main__':
419 main()
420