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 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   
60 -def usage(exit_code=0):
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 # "Hudson: What that's great, that's just fucking great man, now what 93 # the fuck are we supposed to do? We're in some real pretty shit now 94 # man...That's it man, game over man, game over, man! Game over! What 95 # the fuck are we gonna do now? What are we gonna do?" 96 97
98 -def game_over():
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
107 -def reverse_depends_check(removals, suite, arches=None, session=None, include_arch_all=True):
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
120 -def main():
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"), # Bugs to Cc 133 ('d', "done", "Rm::Options::Done", "HasArg"), # Bugs fixed 134 ('D', "do-close", "Rm::Options::Do-Close"), 135 ('R', "rdep-check", "Rm::Options::Rdep-Check"), 136 ('m', "reason", "Rm::Options::Reason", "HasArg"), # Hysterical raisins; -m is old-dinstall option for rejection reason 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 # Sanity check options 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 # Force the admin to tell someone if we're not doing a 'dak 185 # cruft-report' inspired removal (or closing a bug, which counts 186 # as telling someone). 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 # Additional suite checks 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 # Additional architecture checks 229 if Options["Architecture"] and check_source: 230 utils.warn("'source' in -a/--argument makes no sense and is ignored.") 231 232 # Don't do dependency checks on multiple suites 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 # We have 3 modes of package selection: binary, source-only, binary-only 244 # and source+binary. 245 246 # XXX: TODO: This all needs converting to use placeholders or the object 247 # API. It's an SQL injection dream at the moment 248 249 if Options["Binary"]: 250 # Removal by binary package name 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 # Source-only 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 # Source + Binary 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 # Process -C/--carbon-copy 314 # 315 # Accept 3 types of arguments (space separated): 316 # 1) a number - assumed to be a bug number, i.e. nnnnn@bugs.debian.org 317 # 2) the keyword 'package' - cc's $package@packages.debian.org for every argument 318 # 3) contains a '@' - assumed to be an email address, used unmodified 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 # If we don't have a reason; spawn an editor so the user can add one 337 # Write the rejection email out as the <foo>.reason file 338 if not Options["Reason"] and not Options["No-Action"]: 339 Options["Reason"] = utils.call_editor() 340 341 # Generate the summary of what's to be removed 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 # when arches is None, rdeps are checked on all arches in the suite 390 arches = None 391 reverse_depends_check(removals, suites[0], arches, session, include_arch_all=include_arch_all) 392 393 # If -n/--no-action, drop out here 394 if Options["No-Action"]: 395 sys.exit(0) 396 397 print("Going to remove the packages now.") 398 game_over() 399 400 # Do the actual deletion 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