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