Coverage for dak/control_overrides.py: 59%

181 statements  

« prev     ^ index     » next       coverage.py v7.6.0, created at 2026-01-04 16:18 +0000

1#! /usr/bin/env python3 

2 

3"""Bulk manipulation of the overrides""" 

4# Copyright (C) 2000, 2001, 2002, 2003, 2006 James Troup <james@nocrew.org> 

5 

6# This program is free software; you can redistribute it and/or modify 

7# it under the terms of the GNU General Public License as published by 

8# the Free Software Foundation; either version 2 of the License, or 

9# (at your option) any later version. 

10 

11# This program is distributed in the hope that it will be useful, 

12# but WITHOUT ANY WARRANTY; without even the implied warranty of 

13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

14# GNU General Public License for more details. 

15 

16# You should have received a copy of the GNU General Public License 

17# along with this program; if not, write to the Free Software 

18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 

19 

20################################################################################ 

21 

22# On 30 Nov 1998, James Troup wrote: 

23# 

24# > James Troup<2> <troup2@debian.org> 

25# > 

26# > James is a clone of James; he's going to take over the world. 

27# > After he gets some sleep. 

28# 

29# Could you clone other things too? Sheep? Llamas? Giant mutant turnips? 

30# 

31# Your clone will need some help to take over the world, maybe clone up an 

32# army of penguins and threaten to unleash them on the world, forcing 

33# governments to sway to the new James' will! 

34# 

35# Yes, I can envision a day when James' duplicate decides to take a horrific 

36# vengance on the James that spawned him and unleashes his fury in the form 

37# of thousands upon thousands of chickens that look just like Captin Blue 

38# Eye! Oh the horror. 

39# 

40# Now you'll have to were name tags to people can tell you apart, unless of 

41# course the new clone is truely evil in which case he should be easy to 

42# identify! 

43# 

44# Jason 

45# Chicken. Black. Helicopters. 

46# Be afraid. 

47 

48# <Pine.LNX.3.96.981130011300.30365Z-100000@wakko> 

49 

50################################################################################ 

51 

52import sys 

53import time 

54from collections.abc import Iterable 

55from typing import TYPE_CHECKING, NoReturn 

56 

57import apt_pkg 

58from sqlalchemy import sql 

59 

60from daklib import daklog, utils 

61from daklib.config import Config 

62from daklib.dbconn import ( 

63 DBConn, 

64 get_component, 

65 get_override_type, 

66 get_priorities, 

67 get_sections, 

68 get_suite, 

69) 

70from daklib.regexes import re_comments 

71 

72if TYPE_CHECKING: 

73 from sqlalchemy.orm import Session 

74 

75################################################################################ 

76 

77Logger: daklog.Logger 

78 

79################################################################################ 

80 

81 

82def usage(exit_code=0) -> NoReturn: 

83 print( 

84 """Usage: dak control-overrides [OPTIONS] 

85 -h, --help print this help and exit 

86 

87 -c, --component=CMPT list/set overrides by component 

88 (contrib,*main,non-free) 

89 -s, --suite=SUITE list/set overrides by suite 

90 (experimental,stable,testing,*unstable) 

91 -t, --type=TYPE list/set overrides by type 

92 (*deb,dsc,udeb) 

93 

94 -a, --add add overrides (changes and deletions are ignored) 

95 -S, --set set overrides 

96 -C, --change change overrides (additions and deletions are ignored) 

97 -l, --list list overrides 

98 

99 -q, --quiet be less verbose 

100 -n, --no-action only list the action that would have been done 

101 -f, --force also work on untouchable suites 

102 

103 starred (*) values are default""" 

104 ) 

105 sys.exit(exit_code) 

106 

107 

108################################################################################ 

109 

110 

111def process_file( 

112 file: Iterable[str], 

113 suite: str, 

114 component: str, 

115 otype: str, 

116 mode: str, 

117 action: bool, 

118 session: "Session", 

119) -> None: 

120 cnf = Config() 

121 

122 s = get_suite(suite, session=session) 

123 if s is None: 123 ↛ 124line 123 didn't jump to line 124 because the condition on line 123 was never true

124 utils.fubar("Suite '%s' not recognised." % (suite)) 

125 suite_id = s.suite_id 

126 

127 c = get_component(component, session=session) 

128 if c is None: 128 ↛ 129line 128 didn't jump to line 129 because the condition on line 128 was never true

129 utils.fubar("Component '%s' not recognised." % (component)) 

130 component_id = c.component_id 

131 

132 o = get_override_type(otype) 

133 if o is None: 133 ↛ 134line 133 didn't jump to line 134 because the condition on line 133 was never true

134 utils.fubar( 

135 "Type '%s' not recognised. (Valid types are deb, udeb and dsc.)" % (otype) 

136 ) 

137 type_id = o.overridetype_id 

138 

139 # --set is done mostly internal for performance reasons; most 

140 # invocations of --set will be updates and making people wait 2-3 

141 # minutes while 6000 select+inserts are run needlessly isn't cool. 

142 

143 original = {} 

144 new = {} 

145 c_skipped = 0 

146 c_added = 0 

147 c_updated = 0 

148 c_removed = 0 

149 c_error = 0 

150 

151 q = session.execute( 

152 sql.text( 

153 """SELECT o.package, o.priority, o.section, o.maintainer, p.priority, s.section 

154 FROM override o, priority p, section s 

155 WHERE o.suite = :suiteid AND o.component = :componentid AND o.type = :typeid 

156 and o.priority = p.id and o.section = s.id""" 

157 ), 

158 {"suiteid": suite_id, "componentid": component_id, "typeid": type_id}, 

159 ) 

160 for i in q.fetchall(): 

161 original[i[0]] = i[1:] 

162 

163 start_time = time.time() 

164 

165 section_cache = get_sections(session) 

166 priority_cache = get_priorities(session) 

167 

168 # Our session is already in a transaction 

169 

170 for line in file: 

171 line = re_comments.sub("", line).strip() 

172 if line == "": 172 ↛ 173line 172 didn't jump to line 173 because the condition on line 172 was never true

173 continue 

174 

175 maintainer_override = None 

176 if otype == "dsc": 

177 split_line = line.split(None, 2) 

178 if len(split_line) == 2: 178 ↛ 180line 178 didn't jump to line 180 because the condition on line 178 was always true

179 (package, section) = split_line 

180 elif len(split_line) == 3: 

181 (package, section, maintainer_override) = split_line 

182 else: 

183 utils.warn( 

184 "'%s' does not break into 'package section [maintainer-override]'." 

185 % (line) 

186 ) 

187 c_error += 1 

188 continue 

189 priority = "optional" 

190 else: # binary or udeb 

191 split_line = line.split(None, 3) 

192 if len(split_line) == 3: 192 ↛ 194line 192 didn't jump to line 194 because the condition on line 192 was always true

193 (package, priority, section) = split_line 

194 elif len(split_line) == 4: 

195 (package, priority, section, maintainer_override) = split_line 

196 else: 

197 utils.warn( 

198 "'%s' does not break into 'package priority section [maintainer-override]'." 

199 % (line) 

200 ) 

201 c_error += 1 

202 continue 

203 

204 if section not in section_cache: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true

205 utils.warn( 

206 "'%s' is not a valid section. ['%s' in suite %s, component %s]." 

207 % (section, package, suite, component) 

208 ) 

209 c_error += 1 

210 continue 

211 

212 section_id = section_cache[section] 

213 

214 if priority not in priority_cache: 214 ↛ 215line 214 didn't jump to line 215 because the condition on line 214 was never true

215 utils.warn( 

216 "'%s' is not a valid priority. ['%s' in suite %s, component %s]." 

217 % (priority, package, suite, component) 

218 ) 

219 c_error += 1 

220 continue 

221 

222 priority_id = priority_cache[priority] 

223 

224 if package in new: 224 ↛ 225line 224 didn't jump to line 225 because the condition on line 224 was never true

225 utils.warn( 

226 "Can't insert duplicate entry for '%s'; ignoring all but the first. [suite %s, component %s]" 

227 % (package, suite, component) 

228 ) 

229 c_error += 1 

230 continue 

231 new[package] = "" 

232 

233 if package in original: 233 ↛ 234line 233 didn't jump to line 234

234 ( 

235 old_priority_id, 

236 old_section_id, 

237 old_maintainer_override, 

238 old_priority, 

239 old_section, 

240 ) = original[package] 

241 if ( 

242 mode == "add" 

243 or old_priority_id == priority_id 

244 and old_section_id == section_id 

245 and old_maintainer_override == maintainer_override 

246 ): 

247 # If it's unchanged or we're in 'add only' mode, ignore it 

248 c_skipped += 1 

249 continue 

250 else: 

251 # If it's changed, delete the old one so we can 

252 # reinsert it with the new information 

253 c_updated += 1 

254 if action: 

255 session.execute( 

256 sql.text( 

257 """DELETE FROM override WHERE suite = :suite AND component = :component 

258 AND package = :package AND type = :typeid""" 

259 ), 

260 { 

261 "suite": suite_id, 

262 "component": component_id, 

263 "package": package, 

264 "typeid": type_id, 

265 }, 

266 ) 

267 

268 # Log changes 

269 if old_priority_id != priority_id: 

270 Logger.log(["changed priority", package, old_priority, priority]) 

271 if old_section_id != section_id: 

272 Logger.log(["changed section", package, old_section, section]) 

273 if old_maintainer_override != maintainer_override: 

274 Logger.log( 

275 [ 

276 "changed maintainer override", 

277 package, 

278 old_maintainer_override, 

279 maintainer_override, 

280 ] 

281 ) 

282 update_p = 1 

283 elif mode == "change": 283 ↛ 285line 283 didn't jump to line 285 because the condition on line 283 was never true

284 # Ignore additions in 'change only' mode 

285 c_skipped += 1 

286 continue 

287 else: 

288 c_added += 1 

289 update_p = 0 

290 

291 if action: 291 ↛ 315line 291 didn't jump to line 315 because the condition on line 291 was always true

292 if not maintainer_override: 292 ↛ 295line 292 didn't jump to line 295 because the condition on line 292 was always true

293 m_o = None 

294 else: 

295 m_o = maintainer_override 

296 session.execute( 

297 sql.text( 

298 """INSERT INTO override (suite, component, type, package, 

299 priority, section, maintainer) 

300 VALUES (:suiteid, :componentid, :typeid, 

301 :package, :priorityid, :sectionid, 

302 :maintainer)""" 

303 ), 

304 { 

305 "suiteid": suite_id, 

306 "componentid": component_id, 

307 "typeid": type_id, 

308 "package": package, 

309 "priorityid": priority_id, 

310 "sectionid": section_id, 

311 "maintainer": m_o, 

312 }, 

313 ) 

314 

315 if not update_p: 315 ↛ 170line 315 didn't jump to line 170 because the condition on line 315 was always true

316 Logger.log( 

317 [ 

318 "new override", 

319 suite, 

320 component, 

321 otype, 

322 package, 

323 priority, 

324 section, 

325 maintainer_override, 

326 ] 

327 ) 

328 

329 if mode == "set": 329 ↛ 331line 329 didn't jump to line 331 because the condition on line 329 was never true

330 # Delete any packages which were removed 

331 for package in original.keys(): 

332 if package not in new: 

333 if action: 

334 session.execute( 

335 sql.text( 

336 """DELETE FROM override 

337 WHERE suite = :suiteid AND component = :componentid 

338 AND package = :package AND type = :typeid""" 

339 ), 

340 { 

341 "suiteid": suite_id, 

342 "componentid": component_id, 

343 "package": package, 

344 "typeid": type_id, 

345 }, 

346 ) 

347 c_removed += 1 

348 Logger.log(["removed override", suite, component, otype, package]) 

349 

350 if action: 350 ↛ 353line 350 didn't jump to line 353 because the condition on line 350 was always true

351 session.commit() 

352 

353 if not cnf["Control-Overrides::Options::Quiet"]: 353 ↛ 366line 353 didn't jump to line 366 because the condition on line 353 was always true

354 print( 

355 "Done in %d seconds. [Updated = %d, Added = %d, Removed = %d, Skipped = %d, Errors = %d]" 

356 % ( 

357 int(time.time() - start_time), 

358 c_updated, 

359 c_added, 

360 c_removed, 

361 c_skipped, 

362 c_error, 

363 ) 

364 ) 

365 

366 Logger.log(["set complete", c_updated, c_added, c_removed, c_skipped, c_error]) 

367 

368 

369################################################################################ 

370 

371 

372def list_overrides(suite: str, component: str, otype: str, session: "Session") -> None: 

373 dat = {} 

374 s = get_suite(suite, session) 

375 if s is None: 375 ↛ 376line 375 didn't jump to line 376 because the condition on line 375 was never true

376 utils.fubar("Suite '%s' not recognised." % (suite)) 

377 

378 dat["suiteid"] = s.suite_id 

379 

380 c = get_component(component, session) 

381 if c is None: 381 ↛ 382line 381 didn't jump to line 382 because the condition on line 381 was never true

382 utils.fubar("Component '%s' not recognised." % (component)) 

383 

384 dat["componentid"] = c.component_id 

385 

386 o = get_override_type(otype) 

387 if o is None: 387 ↛ 388line 387 didn't jump to line 388 because the condition on line 387 was never true

388 utils.fubar( 

389 "Type '%s' not recognised. (Valid types are deb, udeb and dsc)" % (otype) 

390 ) 

391 

392 dat["typeid"] = o.overridetype_id 

393 

394 if otype == "dsc": 394 ↛ 395line 394 didn't jump to line 395 because the condition on line 394 was never true

395 q = session.execute( 

396 sql.text( 

397 """SELECT o.package, s.section, o.maintainer FROM override o, section s 

398 WHERE o.suite = :suiteid AND o.component = :componentid 

399 AND o.type = :typeid AND o.section = s.id 

400 ORDER BY s.section, o.package""" 

401 ), 

402 dat, 

403 ) 

404 for i in q.fetchall(): 

405 print(utils.result_join(i)) 

406 else: 

407 q = session.execute( 

408 sql.text( 

409 """SELECT o.package, p.priority, s.section, o.maintainer, p.level 

410 FROM override o, priority p, section s 

411 WHERE o.suite = :suiteid AND o.component = :componentid 

412 AND o.type = :typeid AND o.priority = p.id AND o.section = s.id 

413 ORDER BY s.section, p.level, o.package""" 

414 ), 

415 dat, 

416 ) 

417 for i in q.fetchall(): 417 ↛ 418line 417 didn't jump to line 418 because the loop on line 417 never started

418 print(utils.result_join(i[:-1])) 

419 

420 

421################################################################################ 

422 

423 

424def main() -> None: 

425 global Logger 

426 

427 cnf = Config() 

428 Arguments = [ 

429 ("a", "add", "Control-Overrides::Options::Add"), 

430 ("c", "component", "Control-Overrides::Options::Component", "HasArg"), 

431 ("h", "help", "Control-Overrides::Options::Help"), 

432 ("l", "list", "Control-Overrides::Options::List"), 

433 ("q", "quiet", "Control-Overrides::Options::Quiet"), 

434 ("s", "suite", "Control-Overrides::Options::Suite", "HasArg"), 

435 ("S", "set", "Control-Overrides::Options::Set"), 

436 ("C", "change", "Control-Overrides::Options::Change"), 

437 ("n", "no-action", "Control-Overrides::Options::No-Action"), 

438 ("t", "type", "Control-Overrides::Options::Type", "HasArg"), 

439 ("f", "force", "Control-Overrides::Options::Force"), 

440 ] 

441 

442 # Default arguments 

443 for i in ["add", "help", "list", "quiet", "set", "change", "no-action"]: 

444 key = "Control-Overrides::Options::%s" % i 

445 if key not in cnf: 445 ↛ 443line 445 didn't jump to line 443 because the condition on line 445 was always true

446 cnf[key] = "" 

447 if "Control-Overrides::Options::Component" not in cnf: 447 ↛ 449line 447 didn't jump to line 449 because the condition on line 447 was always true

448 cnf["Control-Overrides::Options::Component"] = "main" 

449 if "Control-Overrides::Options::Suite" not in cnf: 449 ↛ 451line 449 didn't jump to line 451 because the condition on line 449 was always true

450 cnf["Control-Overrides::Options::Suite"] = "unstable" 

451 if "Control-Overrides::Options::Type" not in cnf: 451 ↛ 454line 451 didn't jump to line 454 because the condition on line 451 was always true

452 cnf["Control-Overrides::Options::Type"] = "deb" 

453 

454 file_list = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined] 

455 

456 if cnf["Control-Overrides::Options::Help"]: 

457 usage() 

458 

459 session = DBConn().session() 

460 

461 mode = None 

462 for i in ["add", "list", "set", "change"]: 

463 if cnf["Control-Overrides::Options::%s" % (i)]: 

464 if mode: 464 ↛ 465line 464 didn't jump to line 465 because the condition on line 464 was never true

465 utils.fubar("Can not perform more than one action at once.") 

466 mode = i 

467 

468 # Need an action... 

469 if mode is None: 469 ↛ 470line 469 didn't jump to line 470 because the condition on line 469 was never true

470 utils.fubar("No action specified.") 

471 

472 (suite_name, component, otype) = ( 

473 cnf["Control-Overrides::Options::Suite"], 

474 cnf["Control-Overrides::Options::Component"], 

475 cnf["Control-Overrides::Options::Type"], 

476 ) 

477 

478 if mode == "list": 

479 list_overrides(suite_name, component, otype, session) 

480 else: 

481 suite = get_suite(suite_name, session) 

482 assert suite is not None 

483 if suite.untouchable and not cnf["Control-Overrides::Options::Force"]: 483 ↛ 484line 483 didn't jump to line 484 because the condition on line 483 was never true

484 utils.fubar("%s: suite is untouchable" % suite_name) 

485 

486 action = True 

487 if cnf["Control-Overrides::Options::No-Action"]: 487 ↛ 488line 487 didn't jump to line 488 because the condition on line 487 was never true

488 utils.warn("In No-Action Mode") 

489 action = False 

490 

491 Logger = daklog.Logger("control-overrides", mode) 

492 if file_list: 492 ↛ 493line 492 didn't jump to line 493 because the condition on line 492 was never true

493 for f in file_list: 

494 process_file( 

495 open(f), suite_name, component, otype, mode, action, session 

496 ) 

497 else: 

498 process_file(sys.stdin, suite_name, component, otype, mode, action, session) 

499 Logger.close() 

500 

501 

502####################################################################################### 

503 

504 

505if __name__ == "__main__": 

506 main()