Coverage for dak/external_overrides.py: 21%
91 statements
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2026-01-04 16:18 +0000
1#! /usr/bin/env python3
3"""
4Modify external overrides.
6@contact: Debian FTP Master <ftpmaster@debian.org>
7@copyright: 2011 Ansgar Burchardt <ansgar@debian.org>
8@license: GNU General Public License version 2 or later
9"""
11# This program is free software; you can redistribute it and/or modify
12# it under the terms of the GNU General Public License as published by
13# the Free Software Foundation; either version 2 of the License, or
14# (at your option) any later version.
16# This program is distributed in the hope that it will be useful,
17# but WITHOUT ANY WARRANTY; without even the implied warranty of
18# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19# GNU General Public License for more details.
21# You should have received a copy of the GNU General Public License
22# along with this program; if not, write to the Free Software
23# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25import sys
26from collections.abc import Iterable, Iterator
27from typing import NoReturn
29import apt_pkg
30from sqlalchemy import sql
32from daklib.config import Config
33from daklib.dbconn import DBConn, ExternalOverride, get_component, get_suite
36def usage() -> NoReturn:
37 print(
38 """Usage: dak external-overrides COMMAND
39Modify external overrides.
41 -h, --help show this help and exit.
42 -f, --force allow processing of untouchable suites.
44Commands can use a long or abbreviated form:
46 import SUITE COMPONENT KEY import external overrides for KEY
47 i SUITE COMPONENT KEY NOTE: This will replace existing overrides.
49 copy FROM TO copy external overrides from suite FROM to TO
50 NOTE: Needs --force for untouchable TO
52For the 'import' command, external overrides are read from standard input and
53should be given as lines of the form 'PACKAGE KEY VALUE'.
54"""
55 )
56 sys.exit()
59#############################################################################
62class ExternalOverrideReader:
63 """
64 Parses an external override file
65 """
67 def __init__(self, fh: Iterable[str]):
68 self.fh = fh
69 self.package: str = ""
70 self.key: str = ""
71 self.value: list[str] = []
73 def _flush(self) -> tuple[str, str, str]:
74 """
75 Return the parsed line that is being built and start parsing a new line
76 """
77 res = self.package, self.key, "\n".join(self.value)
78 self.package = self.key = ""
79 self.value = []
80 return res
82 def __iter__(self) -> Iterator[tuple[str, str, str]]:
83 """
84 returns a (package, key, value) tuple for every entry in the external
85 override file
86 """
87 for line in self.fh:
88 if not line:
89 continue
90 if line[0] in (" ", "\t"):
91 # Continuation line
92 self.value.append(line.rstrip())
93 else:
94 if self.package is not None:
95 yield self._flush()
97 # New line
98 (self.package, self.key, value) = line.rstrip().split(None, 2)
99 self.value = [value]
101 if self.package:
102 yield self._flush()
105#############################################################################
108def external_overrides_copy(
109 from_suite_name: str, to_suite_name: str, force=False
110) -> None:
111 session = DBConn().session()
113 from_suite = get_suite(from_suite_name, session)
114 to_suite = get_suite(to_suite_name, session)
116 if from_suite is None:
117 print("E: source %s not found." % from_suite_name)
118 session.rollback()
119 return
120 if to_suite is None:
121 print("E: target %s not found." % to_suite_name)
122 session.rollback()
123 return
125 if not force and to_suite.untouchable:
126 print("E: refusing to touch untouchable suite %s (not forced)." % to_suite_name)
127 session.rollback()
128 return
130 session.query(ExternalOverride).filter_by(suite=to_suite).delete()
131 session.execute(
132 sql.text(
133 """
134 INSERT INTO external_overrides (suite, component, package, key, value)
135 SELECT :to_suite, component, package, key, value FROM external_overrides WHERE suite = :from_suite
136 """
137 ),
138 {"from_suite": from_suite.suite_id, "to_suite": to_suite.suite_id},
139 )
141 session.commit()
144def external_overrides_import(
145 suite_name: str, component_name: str, key: str, file: Iterable[str], force=False
146) -> None:
147 session = DBConn().session()
149 suite = get_suite(suite_name, session)
150 assert suite is not None
151 component = get_component(component_name, session)
152 assert component is not None
154 if not force and suite.untouchable:
155 print("E: refusing to touch untouchable suite %s (not forced)." % suite_name)
156 session.rollback()
157 return
159 session.query(ExternalOverride).filter_by(
160 suite=suite, component=component, key=key
161 ).delete()
163 for package, key, value in ExternalOverrideReader(file):
164 eo = ExternalOverride()
165 eo.suite = suite
166 eo.component = component
167 eo.package = package
168 eo.key = key
169 eo.value = value
170 session.add(eo)
172 session.commit()
175#############################################################################
178def main() -> None:
179 cnf = Config()
181 Arguments = [
182 ("h", "help", "External-Overrides::Options::Help"),
183 ("f", "force", "External-Overrides::Options::Force"),
184 ]
186 args = apt_pkg.parse_commandline(cnf.Cnf, Arguments, sys.argv) # type: ignore[attr-defined]
187 try:
188 Options = cnf.subtree("External-Overrides::Options")
189 except KeyError:
190 Options = {}
192 if "Help" in Options: 192 ↛ 195line 192 didn't jump to line 195 because the condition on line 192 was always true
193 usage()
195 force = False
196 if "Force" in Options and Options["Force"]:
197 force = True
199 command = args[0]
200 if command in ("import", "i"):
201 external_overrides_import(args[1], args[2], args[3], sys.stdin, force)
202 elif command in ("copy", "c"):
203 external_overrides_copy(args[1], args[2], force)
204 else:
205 print("E: Unknown commands.")
208if __name__ == "__main__":
209 main()