Package dak :: Module rm
[hide private]
[frames] | no frames]

Source Code for Module dak.rm

  1  #! /usr/bin/env python3 
  2   
  3  """General purpose package removal tool for ftpmaster""" 
  4  # Copyright (C) 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org> 
  5  # Copyright (C) 2010 Alexander Reichle-Schmehl <tolimar@debian.org> 
  6   
  7  # This program is free software; you can redistribute it and/or modify 
  8  # it under the terms of the GNU General Public License as published by 
  9  # the Free Software Foundation; either version 2 of the License, or 
 10  # (at your option) any later version. 
 11   
 12  # This program is distributed in the hope that it will be useful, 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  # GNU General Public License for more details. 
 16   
 17  # You should have received a copy of the GNU General Public License 
 18  # along with this program; if not, write to the Free Software 
 19  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 20   
 21  ################################################################################ 
 22   
 23  # o OpenBSD team wants to get changes incorporated into IPF. Darren no 
 24  #    respond. 
 25  # o Ask again -> No respond. Darren coder supreme. 
 26  # o OpenBSD decide to make changes, but only in OpenBSD source 
 27  #    tree. Darren hears, gets angry! Decides: "LICENSE NO ALLOW!" 
 28  # o Insert Flame War. 
 29  # o OpenBSD team decide to switch to different packet filter under BSD 
 30  #    license. Because Project Goal: Every user should be able to make 
 31  #    changes to source tree. IPF license bad!! 
 32  # o Darren try get back: says, NetBSD, FreeBSD allowed! MUAHAHAHAH!!! 
 33  # o Theo say: no care, pf much better than ipf! 
 34  # o Darren changes mind: changes license. But OpenBSD will not change 
 35  #    back to ipf. Darren even much more bitter. 
 36  # o Darren so bitterbitter. Decides: I'LL GET BACK BY FORKING OPENBSD AND 
 37  #    RELEASING MY OWN VERSION. HEHEHEHEHE. 
 38   
 39  #                        http://slashdot.org/comments.pl?sid=26697&cid=2883271 
 40   
 41  ################################################################################ 
 42   
 43  import functools 
 44  import sys 
 45   
 46  import apt_pkg 
 47   
 48  from daklib import utils 
 49  from daklib.config import Config 
 50  from daklib.dbconn import DBConn, get_maintainer, get_suite 
 51  from daklib.rm import remove 
 52   
 53  ################################################################################ 
 54   
 55  Options = None 
 56   
 57  ################################################################################ 
 58   
 59   
60 -def usage(exit_code=0):
61 print( 62 """Usage: dak rm [OPTIONS] PACKAGE[...] 63 Remove PACKAGE(s) from suite(s). 64 65 -A, --no-arch-all-rdeps Do not report breaking arch:all packages 66 or Build-Depends-Indep 67 -a, --architecture=ARCH only act on this architecture 68 -b, --binary PACKAGE are binary packages to remove 69 -B, --binary-only remove binaries only 70 --binary-version=VER only remove packages with binary vesion VER 71 -c, --component=COMPONENT act on this component 72 -C, --carbon-copy=EMAIL send a CC of removal message to EMAIL 73 -d, --done=BUG# send removal message as closure to bug# 74 -D, --do-close also close all bugs associated to that package 75 -h, --help show this help and exit 76 -m, --reason=MSG reason for removal 77 -n, --no-action don't do anything 78 -o, --outdated remove only outdated sources or binaries that were 79 built from previous source versions 80 -p, --partial don't affect override files 81 -R, --rdep-check check reverse dependencies 82 -s, --suite=SUITE act on this suite 83 -S, --source-only remove source only 84 --source-version=VER only remove packages with source version VER 85 86 ARCH, BUG#, COMPONENT and SUITE can be comma (or space) separated lists, e.g. 87 --architecture=amd64,i386""" 88 ) 89 90 sys.exit(exit_code)
91 92 93 ################################################################################ 94 95 # "Hudson: What that's great, that's just fucking great man, now what 96 # the fuck are we supposed to do? We're in some real pretty shit now 97 # man...That's it man, game over man, game over, man! Game over! What 98 # the fuck are we gonna do now? What are we gonna do?" 99 100
101 -def game_over():
102 answer = utils.input_or_exit("Continue (y/N)? ").lower() 103 if answer != "y": 104 print("Aborted.") 105 sys.exit(1)
106 107 108 ################################################################################ 109 110
111 -def reverse_depends_check( 112 removals, suite, arches=None, session=None, include_arch_all=True 113 ):
114 print("Checking reverse dependencies...") 115 if utils.check_reverse_depends( 116 removals, suite, arches, session, include_arch_all=include_arch_all 117 ): 118 print("Dependency problem found.") 119 if not Options["No-Action"]: 120 game_over() 121 else: 122 print("No dependency problem found.") 123 print()
124 125 126 ################################################################################ 127 128
129 -def main():
130 global Options 131 132 cnf = Config() 133 134 Arguments = [ 135 ("h", "help", "Rm::Options::Help"), 136 ("A", "no-arch-all-rdeps", "Rm::Options::NoArchAllRdeps"), 137 ("a", "architecture", "Rm::Options::Architecture", "HasArg"), 138 ("b", "binary", "Rm::Options::Binary"), 139 ("B", "binary-only", "Rm::Options::Binary-Only"), 140 ("\0", "binary-version", "Rm::Options::Binary-Version", "HasArg"), 141 ("c", "component", "Rm::Options::Component", "HasArg"), 142 ("C", "carbon-copy", "Rm::Options::Carbon-Copy", "HasArg"), # Bugs to Cc 143 ("d", "done", "Rm::Options::Done", "HasArg"), # Bugs fixed 144 ("D", "do-close", "Rm::Options::Do-Close"), 145 ("R", "rdep-check", "Rm::Options::Rdep-Check"), 146 ( 147 "m", 148 "reason", 149 "Rm::Options::Reason", 150 "HasArg", 151 ), # Hysterical raisins; -m is old-dinstall option for rejection reason 152 ("n", "no-action", "Rm::Options::No-Action"), 153 ("o", "outdated", "Rm::Options::Outdated"), 154 ("p", "partial", "Rm::Options::Partial"), 155 ("s", "suite", "Rm::Options::Suite", "HasArg"), 156 ("S", "source-only", "Rm::Options::Source-Only"), 157 ("\0", "source-version", "Rm::Options::Source-Version", "HasArg"), 158 ] 159 160 for i in [ 161 "NoArchAllRdeps", 162 "architecture", 163 "binary", 164 "binary-only", 165 "carbon-copy", 166 "component", 167 "done", 168 "help", 169 "no-action", 170 "outdated", 171 "partial", 172 "rdep-check", 173 "reason", 174 "source-only", 175 "Do-Close", 176 ]: 177 key = "Rm::Options::%s" % (i) 178 if key not in cnf: 179 cnf[key] = "" 180 if "Rm::Options::Suite" not in cnf: 181 cnf["Rm::Options::Suite"] = "unstable" 182 183 arguments = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) 184 Options = cnf.subtree("Rm::Options") 185 186 if Options["Help"]: 187 usage() 188 189 session = DBConn().session() 190 191 # Sanity check options 192 if not arguments: 193 utils.fubar("need at least one package name as an argument.") 194 if Options["Architecture"] and Options["Source-Only"]: 195 utils.fubar( 196 "can't use -a/--architecture and -S/--source-only options simultaneously." 197 ) 198 actions = [Options["Binary"], Options["Binary-Only"], Options["Source-Only"]] 199 nr_actions = len([act for act in actions if act]) 200 if nr_actions > 1: 201 utils.fubar( 202 "Only one of -b/--binary, -B/--binary-only and -S/--source-only can be used." 203 ) 204 if Options["Architecture"] and not Options["Partial"]: 205 utils.warn("-a/--architecture implies -p/--partial.") 206 Options["Partial"] = "true" 207 if Options["Outdated"] and not Options["Partial"]: 208 utils.warn("-o/--outdated implies -p/--partial.") 209 Options["Partial"] = "true" 210 if Options["Do-Close"] and not Options["Done"]: 211 utils.fubar("-D/--do-close needs -d/--done (bugnr).") 212 if Options["Do-Close"] and ( 213 Options["Binary"] or Options["Binary-Only"] or Options["Source-Only"] 214 ): 215 utils.fubar( 216 "-D/--do-close cannot be used with -b/--binary, -B/--binary-only or -S/--source-only." 217 ) 218 219 # Force the admin to tell someone if we're not doing a 'dak 220 # cruft-report' inspired removal (or closing a bug, which counts 221 # as telling someone). 222 if ( 223 not Options["No-Action"] 224 and not Options["Carbon-Copy"] 225 and not Options["Done"] 226 and Options["Reason"].find("[auto-cruft]") == -1 227 ): 228 utils.fubar( 229 "Need a -C/--carbon-copy if not closing a bug and not doing a cruft removal." 230 ) 231 232 parameters = { 233 "binary_version": Options.get("Binary-Version", "") or None, 234 "source_version": Options.get("Source-Version", "") or None, 235 } 236 237 if Options["Binary"]: 238 con_packages = "AND b.package IN :packages" 239 parameters["packages"] = tuple(arguments) 240 else: 241 con_packages = "AND s.source IN :sources" 242 parameters["sources"] = tuple(arguments) 243 244 (con_suites, con_architectures, con_components, check_source) = utils.parse_args( 245 Options 246 ) 247 248 # Additional suite checks 249 suite_ids_list = [] 250 whitelists = [] 251 suites = utils.split_args(Options["Suite"]) 252 suites_list = utils.join_with_commas_and(suites) 253 if not Options["No-Action"]: 254 for suite in suites: 255 s = get_suite(suite, session=session) 256 if s is not None: 257 suite_ids_list.append(s.suite_id) 258 whitelists.append(s.mail_whitelist) 259 if suite in ("oldstable", "stable"): 260 print("**WARNING** About to remove from the (old)stable suite!") 261 print( 262 "This should only be done just prior to a (point) release and not at" 263 ) 264 print("any other time.") 265 game_over() 266 elif suite == "testing": 267 print("**WARNING About to remove from the testing suite!") 268 print( 269 "There's no need to do this normally as removals from unstable will" 270 ) 271 print("propogate to testing automagically.") 272 game_over() 273 274 # Additional architecture checks 275 if Options["Architecture"] and check_source: 276 utils.warn("'source' in -a/--argument makes no sense and is ignored.") 277 278 # Don't do dependency checks on multiple suites 279 if Options["Rdep-Check"] and len(suites) > 1: 280 utils.fubar("Reverse dependency check on multiple suites is not implemented.") 281 282 q_outdated = "TRUE" 283 if Options["Outdated"]: 284 q_outdated = "s.version < newest_source.version" 285 286 to_remove = [] 287 maintainers = {} 288 289 # We have 3 modes of package selection: binary, source-only, binary-only 290 # and source+binary. 291 292 # XXX: TODO: This all needs converting to use placeholders or the object 293 # API. It's an SQL injection dream at the moment 294 295 if Options["Binary"]: 296 # Removal by binary package name 297 q = session.execute( 298 """ 299 SELECT b.package, b.version, a.arch_string, b.id, b.maintainer, s.source, 300 s.version as source_version, newest_source.version as newest_sversion 301 FROM binaries b 302 JOIN source s ON s.id = b.source 303 JOIN bin_associations ba ON ba.bin = b.id 304 JOIN architecture a ON a.id = b.architecture 305 JOIN suite su ON su.id = ba.suite 306 JOIN files f ON f.id = b.file 307 JOIN files_archive_map af ON af.file_id = f.id AND af.archive_id = su.archive_id 308 JOIN component c ON c.id = af.component_id 309 JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite 310 WHERE 311 (:binary_version IS NULL OR b.version = :binary_version) 312 AND (:source_version IS NULL OR s.version = :source_version) 313 AND %s %s %s %s %s 314 """ 315 % (q_outdated, con_packages, con_suites, con_components, con_architectures), 316 parameters, 317 ) 318 to_remove.extend(q) 319 else: 320 # Source-only 321 if not Options["Binary-Only"]: 322 q = session.execute( 323 """ 324 SELECT s.source, s.version, 'source', s.id, s.maintainer, s.source, 325 s.version as source_version, newest_source.version as newest_sversion 326 FROM source s 327 JOIN src_associations sa ON sa.source = s.id 328 JOIN suite su ON su.id = sa.suite 329 JOIN archive ON archive.id = su.archive_id 330 JOIN files f ON f.id = s.file 331 JOIN files_archive_map af ON af.file_id = f.id AND af.archive_id = su.archive_id 332 JOIN component c ON c.id = af.component_id 333 JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite 334 WHERE 335 (:source_version IS NULL OR s.version = :source_version) 336 AND %s %s %s %s 337 """ 338 % (q_outdated, con_packages, con_suites, con_components), 339 parameters, 340 ) 341 to_remove.extend(q) 342 if not Options["Source-Only"]: 343 # Source + Binary 344 q = session.execute( 345 """ 346 SELECT b.package, b.version, a.arch_string, b.id, b.maintainer, s.source, 347 s.version as source_version, newest_source.version as newest_sversion 348 FROM binaries b 349 JOIN bin_associations ba ON b.id = ba.bin 350 JOIN architecture a ON b.architecture = a.id 351 JOIN suite su ON ba.suite = su.id 352 JOIN archive ON archive.id = su.archive_id 353 JOIN files_archive_map af ON b.file = af.file_id AND af.archive_id = archive.id 354 JOIN component c ON af.component_id = c.id 355 JOIN source s ON b.source = s.id 356 JOIN newest_source on s.source = newest_source.source AND su.id = newest_source.suite 357 WHERE 358 (:binary_version IS NULL OR b.version = :binary_version) 359 AND (:source_version IS NULL OR s.version = :source_version) 360 AND %s %s %s %s %s 361 """ 362 % ( 363 q_outdated, 364 con_packages, 365 con_suites, 366 con_components, 367 con_architectures, 368 ), 369 parameters, 370 ) 371 to_remove.extend(q) 372 373 if not to_remove: 374 print("Nothing to do.") 375 sys.exit(0) 376 377 # Process -C/--carbon-copy 378 # 379 # Accept 3 types of arguments (space separated): 380 # 1) a number - assumed to be a bug number, i.e. nnnnn@bugs.debian.org 381 # 2) the keyword 'package' - cc's $package@packages.debian.org for every argument 382 # 3) contains a '@' - assumed to be an email address, used unmodified 383 # 384 carbon_copy = [] 385 for copy_to in utils.split_args(Options.get("Carbon-Copy")): 386 if copy_to.isdigit(): 387 if "Dinstall::BugServer" in cnf: 388 carbon_copy.append(copy_to + "@" + cnf["Dinstall::BugServer"]) 389 else: 390 utils.fubar( 391 "Asked to send mail to #%s in BTS but Dinstall::BugServer is not configured" 392 % copy_to 393 ) 394 elif copy_to == "package": 395 for package in set([s[5] for s in to_remove]): 396 if "Dinstall::PackagesServer" in cnf: 397 carbon_copy.append(package + "@" + cnf["Dinstall::PackagesServer"]) 398 elif "@" in copy_to: 399 carbon_copy.append(copy_to) 400 else: 401 utils.fubar( 402 "Invalid -C/--carbon-copy argument '%s'; not a bug number, 'package' or email address." 403 % (copy_to) 404 ) 405 406 # If we don't have a reason; spawn an editor so the user can add one 407 # Write the rejection email out as the <foo>.reason file 408 if not Options["Reason"] and not Options["No-Action"]: 409 Options["Reason"] = utils.call_editor() 410 411 # Generate the summary of what's to be removed 412 d = {} 413 for i in to_remove: 414 package = i[0] 415 version = i[1] 416 architecture = i[2] 417 maintainer = i[4] 418 maintainers[maintainer] = "" 419 # source = i[5] 420 # source_version = i[6] 421 # source_newest = i[7] 422 if package not in d: 423 d[package] = {} 424 if version not in d[package]: 425 d[package][version] = [] 426 if architecture not in d[package][version]: 427 d[package][version].append(architecture) 428 429 maintainer_list = [] 430 for maintainer_id in maintainers.keys(): 431 maintainer_list.append(get_maintainer(maintainer_id).name) 432 summary = "" 433 removals = sorted(d) 434 for package in removals: 435 versions = sorted(d[package], key=functools.cmp_to_key(apt_pkg.version_compare)) 436 for version in versions: 437 d[package][version].sort(key=utils.ArchKey) 438 summary += "%10s | %10s | %s\n" % ( 439 package, 440 version, 441 ", ".join(d[package][version]), 442 ) 443 print("Will remove the following packages from %s:" % (suites_list)) 444 print() 445 print(summary) 446 print("Maintainer: %s" % ", ".join(maintainer_list)) 447 if Options["Done"]: 448 print("Will also close bugs: " + Options["Done"]) 449 if carbon_copy: 450 print("Will also send CCs to: " + ", ".join(carbon_copy)) 451 if Options["Do-Close"]: 452 print("Will also close associated bug reports.") 453 print() 454 print("------------------- Reason -------------------") 455 print(Options["Reason"]) 456 print("----------------------------------------------") 457 print() 458 459 if Options["Rdep-Check"]: 460 arches = utils.split_args(Options["Architecture"]) 461 include_arch_all = Options["NoArchAllRdeps"] == "" 462 if include_arch_all and "all" in arches: 463 # when arches is None, rdeps are checked on all arches in the suite 464 arches = None 465 reverse_depends_check( 466 removals, suites[0], arches, session, include_arch_all=include_arch_all 467 ) 468 469 # If -n/--no-action, drop out here 470 if Options["No-Action"]: 471 sys.exit(0) 472 473 print("Going to remove the packages now.") 474 game_over() 475 476 # Do the actual deletion 477 print("Deleting...", end=" ") 478 sys.stdout.flush() 479 480 try: 481 bugs = utils.split_args(Options["Done"]) 482 remove( 483 session, 484 Options["Reason"], 485 suites, 486 to_remove, 487 partial=Options["Partial"], 488 components=utils.split_args(Options["Component"]), 489 done_bugs=bugs, 490 carbon_copy=carbon_copy, 491 close_related_bugs=Options["Do-Close"], 492 ) 493 except ValueError as ex: 494 utils.fubar(ex.message) 495 else: 496 print("done.")
497 498 499 ####################################################################################### 500 501 502 if __name__ == "__main__": 503 main() 504