Merge lp:~apw/ubuntu-archive-tools/use-kernel-series-routing-information into lp:ubuntu-archive-tools
- use-kernel-series-routing-information
- Merge into trunk
Proposed by
Andy Whitcroft
Status: | Merged |
---|---|
Approved by: | Łukasz Zemczak |
Approved revision: | 1241 |
Merged at revision: | 1250 |
Proposed branch: | lp:~apw/ubuntu-archive-tools/use-kernel-series-routing-information |
Merge into: | lp:ubuntu-archive-tools |
Diff against target: |
1453 lines (+999/-266) 3 files modified
copy-proposed-kernel (+281/-258) kernel_series.py (+657/-0) sru-release (+61/-8) |
To merge this branch: | bzr merge lp:~apw/ubuntu-archive-tools/use-kernel-series-routing-information |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Łukasz Zemczak | Approve | ||
Review via email: mp+373005@code.launchpad.net |
Commit message
Initial support for kernel routing lookups via KernelSeries data from the kernel team.
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'copy-proposed-kernel' |
2 | --- copy-proposed-kernel 2019-08-27 11:32:43 +0000 |
3 | +++ copy-proposed-kernel 2019-09-19 13:51:06 +0000 |
4 | @@ -32,12 +32,16 @@ |
5 | |
6 | from launchpadlib.launchpad import Launchpad |
7 | |
8 | +from kernel_series import KernelSeries |
9 | + |
10 | |
11 | class TestBase(unittest.TestCase): |
12 | class FakeArgs: |
13 | def __init__(self, **kwargs): |
14 | + self.testing = True |
15 | self.series = None |
16 | self.source = None |
17 | + self.ppa2 = False |
18 | self.security = False |
19 | self.security2 = False |
20 | self.esm = False |
21 | @@ -45,6 +49,7 @@ |
22 | self.ibmgt = False |
23 | self.to_signing = False |
24 | self.from_signing = False |
25 | + self.no_auto = False |
26 | |
27 | self.update(**kwargs) |
28 | |
29 | @@ -63,56 +68,139 @@ |
30 | finally: |
31 | sys.stdout, sys.stderr = old_out, old_err |
32 | |
33 | + @classmethod |
34 | + def setUpClass(cls): |
35 | + data = """ |
36 | + defaults: |
37 | + routing-table: |
38 | + default: |
39 | + security-build: |
40 | + - ['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release' ] |
41 | + - ['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release' ] |
42 | + build: |
43 | + - ['ppa:canonical-kernel-team/ubuntu/ppa', 'Release' ] |
44 | + proposed: |
45 | + - ['ubuntu', 'Proposed' ] |
46 | + esm: |
47 | + security-build: |
48 | + - ['ppa:canonical-kernel-security-team/ubuntu/esm', 'Release'] |
49 | + build: |
50 | + - ['ppa:canonical-kernel-esm/ubuntu/ppa', 'Release'] |
51 | + signing: |
52 | + - ['ppa:canonical-signing/ubuntu/esm', 'Release'] |
53 | + proposed: |
54 | + - ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'] |
55 | + 14.04: |
56 | + codename: trusty |
57 | + supported: true |
58 | + esm: true |
59 | + sources: |
60 | + linux: |
61 | + packages: |
62 | + linux: |
63 | + linux-signed: |
64 | + type: signed |
65 | + linux-meta: |
66 | + type: meta |
67 | + 16.04: |
68 | + codename: xenial |
69 | + supported: true |
70 | + sources: |
71 | + linux-fips: |
72 | + routing: |
73 | + security-build: |
74 | + - ['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'] |
75 | + - ['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'] |
76 | + build: |
77 | + - ['ppa:fips-cc-stig/ubuntu/fips-build', 'Release'] |
78 | + signing: |
79 | + - ['ppa:canonical-signing/ubuntu/fips', 'Release'] |
80 | + proposed: |
81 | + - ['ppa:ubuntu-advantage/ubuntu/fips-proposed', 'Release'] |
82 | + packages: |
83 | + linux-fips: |
84 | + linux-meta-fips: |
85 | + type: meta |
86 | + linux-signed-fips: |
87 | + type: signed |
88 | + 18.04: |
89 | + codename: bionic |
90 | + supported: true |
91 | + sources: |
92 | + linux: |
93 | + packages: |
94 | + linux: |
95 | + linux-signed: |
96 | + type: signed |
97 | + linux-meta: |
98 | + type: meta |
99 | + linux-ibm-gt: |
100 | + routing: |
101 | + security-build: |
102 | + - ['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'] |
103 | + - ['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'] |
104 | + build: |
105 | + - ['ppa:ibm-cloud/ubuntu/build', 'Release'] |
106 | + proposed: |
107 | + - ['ppa:ibm-cloud/ubuntu/proposed', 'Release'] |
108 | + packages: |
109 | + linux-ibm-gt: |
110 | + linux-meta-ibm-gt: |
111 | + type: meta |
112 | + """ |
113 | + cls.ks = KernelSeries(data=data) |
114 | + |
115 | |
116 | class TestRouting(TestBase): |
117 | def test_default(self): |
118 | - expected = ('~canonical-kernel-team/ubuntu/ppa', 'ubuntu', False) |
119 | - result = routing(self.FakeArgs()) |
120 | + expected = (['ppa:canonical-kernel-team/ubuntu/ppa', 'Release'], ['ubuntu', 'Proposed'], False) |
121 | + result = routing(self.FakeArgs(series='bionic', source='linux'), self.ks) |
122 | self.assertEqual(expected, result) |
123 | |
124 | def test_security(self): |
125 | - expected = ('~canonical-kernel-security-team/ubuntu/ppa', 'ubuntu', True) |
126 | - result = routing(self.FakeArgs(security=True)) |
127 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'], ['ubuntu', 'Proposed'], True) |
128 | + result = routing(self.FakeArgs(series='bionic', source='linux', security=True), self.ks) |
129 | self.assertEqual(expected, result) |
130 | |
131 | def test_security2(self): |
132 | - expected = ('~canonical-kernel-security-team/ubuntu/ppa2', 'ubuntu', True) |
133 | - result = routing(self.FakeArgs(security2=True)) |
134 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'], ['ubuntu', 'Proposed'], True) |
135 | + result = routing(self.FakeArgs(series='bionic', source='linux', security2=True), self.ks) |
136 | self.assertEqual(expected, result) |
137 | |
138 | def test_to_signing(self): |
139 | - expected = ('~canonical-kernel-team/ubuntu/ppa', None, False) |
140 | - result = routing(self.FakeArgs(to_signing=True)) |
141 | + expected = (['ppa:canonical-kernel-team/ubuntu/ppa', 'Release'], None, False) |
142 | + result = routing(self.FakeArgs(series='bionic', source='linux', to_signing=True), self.ks) |
143 | self.assertEqual(expected, result) |
144 | |
145 | def test_from_signing(self): |
146 | - expected = (None, 'ubuntu', False) |
147 | - result = routing(self.FakeArgs(from_signing=True)) |
148 | + expected = (None, ['ubuntu', 'Proposed'], False) |
149 | + result = routing(self.FakeArgs(series='bionic', source='linux', from_signing=True), self.ks) |
150 | self.assertEqual(expected, result) |
151 | |
152 | def test_esm(self): |
153 | - expected = ('~canonical-kernel-esm/ubuntu/ppa', '~canonical-kernel-esm/ubuntu/proposed', False) |
154 | - result = routing(self.FakeArgs(esm=True)) |
155 | + expected = (['ppa:canonical-kernel-esm/ubuntu/ppa', 'Release'], ['ppa:canonical-signing/ubuntu/esm', 'Release'], False) |
156 | + result = routing(self.FakeArgs(series='trusty', source='linux'), self.ks) |
157 | self.assertEqual(expected, result) |
158 | |
159 | def test_esm_security(self): |
160 | - expected = ('~canonical-kernel-security-team/ubuntu/esm', '~canonical-kernel-esm/ubuntu/proposed', False) |
161 | - result = routing(self.FakeArgs(esm=True, security=True)) |
162 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/esm', 'Release'], ['ppa:canonical-signing/ubuntu/esm', 'Release'], False) |
163 | + result = routing(self.FakeArgs(series='trusty', source='linux', security=True), self.ks) |
164 | self.assertEqual(expected, result) |
165 | |
166 | def test_esm_security2(self): |
167 | - expected = (None, '~canonical-kernel-esm/ubuntu/proposed', False) |
168 | - result = routing(self.FakeArgs(esm=True, security2=True)) |
169 | - self.assertEqual(expected, result) |
170 | + with self.assertRaises(SystemExit), self.capture() as (out, err): |
171 | + expected = (None, ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'], False) |
172 | + result = routing(self.FakeArgs(series='trusty', source='linux', security2=True), self.ks) |
173 | + self.assertEqual(expected, result) |
174 | |
175 | def test_esm_to_signing(self): |
176 | - expected = ('~canonical-kernel-esm/ubuntu/ppa', '~canonical-signing/ubuntu/esm', False) |
177 | - result = routing(self.FakeArgs(esm=True, to_signing=True)) |
178 | + expected = (['ppa:canonical-kernel-esm/ubuntu/ppa', 'Release'], ['ppa:canonical-signing/ubuntu/esm', 'Release'], False) |
179 | + result = routing(self.FakeArgs(series='trusty', source='linux', esm=True, to_signing=True), self.ks) |
180 | self.assertEqual(expected, result) |
181 | |
182 | def test_esm_from_signing(self): |
183 | - expected = ('~canonical-signing/ubuntu/esm', '~canonical-kernel-esm/ubuntu/proposed', False) |
184 | - result = routing(self.FakeArgs(esm=True, from_signing=True)) |
185 | + expected = (['ppa:canonical-signing/ubuntu/esm', 'Release'], ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'], False) |
186 | + result = routing(self.FakeArgs(series='trusty', source='linux', esm=True, from_signing=True), self.ks) |
187 | self.assertEqual(expected, result) |
188 | |
189 | # Autorouting will enable to_signing, the user will then want to switch us |
190 | @@ -120,226 +208,145 @@ |
191 | # simple we make from_signing take presidence over to_signing. Test this |
192 | # is honoured correctly. |
193 | def test_esm_from_signing_override_to_signing(self): |
194 | - expected = ('~canonical-signing/ubuntu/esm', '~canonical-kernel-esm/ubuntu/proposed', False) |
195 | - result = routing(self.FakeArgs(esm=True, to_signing=True, from_signing=True)) |
196 | + expected = (['ppa:canonical-signing/ubuntu/esm', 'Release'], ['ppa:canonical-kernel-esm/ubuntu/proposed', 'Release'], False) |
197 | + result = routing(self.FakeArgs(series='trusty', source='linux', esm=True, to_signing=True, from_signing=True), self.ks) |
198 | self.assertEqual(expected, result) |
199 | |
200 | def test_fips(self): |
201 | - expected = ('~fips-cc-stig/ubuntu/fips-build', '~ubuntu-advantage/ubuntu/fips-proposed', False) |
202 | - result = routing(self.FakeArgs(fips=True)) |
203 | + expected = (['ppa:fips-cc-stig/ubuntu/fips-build', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False) |
204 | + result = routing(self.FakeArgs(series='xenial', source='linux-fips'), self.ks) |
205 | self.assertEqual(expected, result) |
206 | |
207 | def test_fips_security(self): |
208 | - expected = ('~canonical-kernel-security-team/ubuntu/ppa', '~ubuntu-advantage/ubuntu/fips-proposed', False) |
209 | - result = routing(self.FakeArgs(fips=True, security=True)) |
210 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False) |
211 | + result = routing(self.FakeArgs(series='xenial', source='linux-fips', security=True), self.ks) |
212 | self.assertEqual(expected, result) |
213 | |
214 | def test_fips_security2(self): |
215 | - expected = ('~canonical-kernel-security-team/ubuntu/ppa2', '~ubuntu-advantage/ubuntu/fips-proposed', False) |
216 | - result = routing(self.FakeArgs(fips=True, security2=True)) |
217 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False) |
218 | + result = routing(self.FakeArgs(series='xenial', source='linux-fips', security2=True), self.ks) |
219 | self.assertEqual(expected, result) |
220 | |
221 | def test_fips_to_signing(self): |
222 | - expected = ('~fips-cc-stig/ubuntu/fips-build', '~canonical-signing/ubuntu/fips', False) |
223 | - result = routing(self.FakeArgs(fips=True, to_signing=True)) |
224 | + expected = (['ppa:fips-cc-stig/ubuntu/fips-build', 'Release'], ['ppa:canonical-signing/ubuntu/fips', 'Release'], False) |
225 | + result = routing(self.FakeArgs(series='xenial', source='linux-fips', to_signing=True), self.ks) |
226 | self.assertEqual(expected, result) |
227 | |
228 | def test_fips_from_signing(self): |
229 | - expected = ('~canonical-signing/ubuntu/fips', '~ubuntu-advantage/ubuntu/fips-proposed', False) |
230 | - result = routing(self.FakeArgs(fips=True, from_signing=True)) |
231 | + expected = (['ppa:canonical-signing/ubuntu/fips', 'Release'], ['ppa:ubuntu-advantage/ubuntu/fips-proposed', 'Release'], False) |
232 | + result = routing(self.FakeArgs(series='xenial', source='linux-fips', from_signing=True), self.ks) |
233 | self.assertEqual(expected, result) |
234 | |
235 | def test_ibmgt(self): |
236 | - expected = ('~ibm-cloud/ubuntu/build', '~ibm-cloud/ubuntu/proposed', False) |
237 | - result = routing(self.FakeArgs(ibmgt=True)) |
238 | + expected = (['ppa:ibm-cloud/ubuntu/build', 'Release'], ['ppa:ibm-cloud/ubuntu/proposed', 'Release'], False) |
239 | + result = routing(self.FakeArgs(series='bionic', source='linux-ibm-gt'), self.ks) |
240 | self.assertEqual(expected, result) |
241 | |
242 | def test_ibmgt_security(self): |
243 | - expected = ('~canonical-kernel-security-team/ubuntu/ppa', '~ibm-cloud/ubuntu/proposed', False) |
244 | - result = routing(self.FakeArgs(ibmgt=True, security=True)) |
245 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa', 'Release'], ['ppa:ibm-cloud/ubuntu/proposed', 'Release'], False) |
246 | + result = routing(self.FakeArgs(series='bionic', source='linux-ibm-gt', security=True), self.ks) |
247 | self.assertEqual(expected, result) |
248 | |
249 | def test_ibmgt_security2(self): |
250 | - expected = ('~canonical-kernel-security-team/ubuntu/ppa2', '~ibm-cloud/ubuntu/proposed', False) |
251 | - result = routing(self.FakeArgs(ibmgt=True, security2=True)) |
252 | + expected = (['ppa:canonical-kernel-security-team/ubuntu/ppa2', 'Release'], ['ppa:ibm-cloud/ubuntu/proposed', 'Release'], False) |
253 | + result = routing(self.FakeArgs(series='bionic', source='linux-ibm-gt', security2=True), self.ks) |
254 | self.assertEqual(expected, result) |
255 | |
256 | |
257 | -def routing(args): |
258 | +def routing(args, ks): |
259 | + series_name = args.series |
260 | + package_name = args.source |
261 | + |
262 | + series = ks.lookup_series(codename=series_name) |
263 | + if series is None: |
264 | + print("ERROR: {} -- series unknown".format(series_name)) |
265 | + sys.exit(1) |
266 | + |
267 | + package = None |
268 | + package_signed = None |
269 | + for source_srch in series.sources: |
270 | + package_signed = None |
271 | + for package_srch in source_srch.packages: |
272 | + if package_srch.name == package_name: |
273 | + package = package_srch |
274 | + if (package_srch.name.startswith('linux-signed-') or |
275 | + package_srch.name == 'linux-signed'): |
276 | + package_signed = package_srch |
277 | + if package is not None: |
278 | + break |
279 | + if package is None: |
280 | + print("ERROR: {}/{} -- package unknown".format(series_name, package_name)) |
281 | + sys.exit(1) |
282 | + |
283 | + source = package.source |
284 | + routing = source.routing |
285 | + if routing is None: |
286 | + print("ERROR: {}/{} -- package has no routing".format(series_name, package_name)) |
287 | + sys.exit(1) |
288 | + |
289 | + build_archives = routing.lookup_destination('build') |
290 | + security_archives = routing.lookup_destination('security-build') |
291 | + proposed_archive = routing.lookup_destination('proposed', primary=True) |
292 | + signing_archive = routing.lookup_destination('signing', primary=True) |
293 | + |
294 | + if build_archives is None or len(build_archives) < 1: |
295 | + print("ERROR: {}/{} -- package has no primary build archive".format(series_name, package_name)) |
296 | + sys.exit(1) |
297 | + if args.ppa2 and (build_archives is None or len(build_archives) < 2): |
298 | + print("ERROR: {}/{} -- package has no secondary build archive".format(series_name, package_name)) |
299 | + sys.exit(1) |
300 | + if build_archives is None: |
301 | + print("ERROR: {}/{} -- package has no build archive".format(series_name, package_name)) |
302 | + sys.exit(1) |
303 | + if proposed_archive is None: |
304 | + print("ERROR: {}/{} -- package has no proposed archive".format(series_name, package_name)) |
305 | + sys.exit(1) |
306 | + if args.security and (security_archives is None or len(security_archives) < 1): |
307 | + print("ERROR: {}/{} -- package has no primary security archive".format(series_name, package_name)) |
308 | + sys.exit(1) |
309 | + if args.security2 and (security_archives is None or len(security_archives) < 2): |
310 | + print("ERROR: {}/{} -- package has no secondary security archive".format(series_name, package_name)) |
311 | + sys.exit(1) |
312 | + |
313 | + # Default route build -> proposed |
314 | + if args.ppa2: |
315 | + from_archive = build_archives[1] |
316 | + else: |
317 | + from_archive = build_archives[0] |
318 | + to_archive = proposed_archive |
319 | + |
320 | unembargo = False |
321 | |
322 | - # Default routing. |
323 | - security_name = ( |
324 | - '~canonical-kernel-security-team/ubuntu/ppa', |
325 | - '~canonical-kernel-security-team/ubuntu/ppa2', |
326 | - ) |
327 | - signing = None |
328 | - |
329 | - # ESM specific routing |
330 | - if args.esm: |
331 | - ppa_name = '~canonical-kernel-esm/ubuntu/ppa' |
332 | - to = '~canonical-kernel-esm/ubuntu/proposed' |
333 | - signing = '~canonical-signing/ubuntu/esm' |
334 | - security_name = ( |
335 | - '~canonical-kernel-security-team/ubuntu/esm', |
336 | - None, |
337 | - ) |
338 | - |
339 | - # FIPS specific routing |
340 | - elif args.fips: |
341 | - ppa_name = '~fips-cc-stig/ubuntu/fips-build' |
342 | - to = '~ubuntu-advantage/ubuntu/fips-proposed' |
343 | - signing = '~canonical-signing/ubuntu/fips' |
344 | - |
345 | - # IBMGT specific routing |
346 | - elif args.ibmgt: |
347 | - ppa_name = '~ibm-cloud/ubuntu/build' |
348 | - to = '~ibm-cloud/ubuntu/proposed' |
349 | - |
350 | - # Default routing. |
351 | - else: |
352 | - ppa_name = '~canonical-kernel-team/ubuntu/ppa' |
353 | - to = 'ubuntu' |
354 | - if args.security or args.security2: |
355 | - # Allow us to unembargo when releasing from security to ubuntu. |
356 | - unembargo = True |
357 | - |
358 | # Handle security routing. |
359 | if args.security: |
360 | - ppa_name = security_name[0] |
361 | - elif args.security2: |
362 | - ppa_name = security_name[1] |
363 | + from_archive = security_archives[0] |
364 | + if args.security2: |
365 | + from_archive = security_archives[1] |
366 | + |
367 | + # Allow us to unembargo when releasing from security to ubuntu. |
368 | + if (args.security or args.security2) and to_archive[0] == 'ubuntu': |
369 | + unembargo = True |
370 | |
371 | # Handle signing routing. |
372 | if args.from_signing: |
373 | - ppa_name = signing |
374 | + from_archive = signing_archive |
375 | elif args.to_signing: |
376 | - to = signing |
377 | - |
378 | - return (ppa_name, to, unembargo) |
379 | - |
380 | -class TestAutoRoute(TestBase): |
381 | - def test_default(self): |
382 | - results = self.FakeArgs(series='bionic', source='linux') |
383 | - expected = copy(results).update() |
384 | - with self.capture() as (out, err): |
385 | - auto_route(results) |
386 | - self.assertEqual(expected.__dict__, results.__dict__) |
387 | - self.assertEqual('', out.getvalue().strip()) |
388 | - |
389 | - def test_esm_precise_linux(self): |
390 | - results = self.FakeArgs(series='precise', source='linux') |
391 | - expected = copy(results).update(esm=True) |
392 | - with self.capture() as (out, err): |
393 | - auto_route(results) |
394 | - self.assertEqual(expected.__dict__, results.__dict__) |
395 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
396 | - self.assertNotIn('via signing', out.getvalue().strip()) |
397 | - |
398 | - def test_esm_precise_linux_meta(self): |
399 | - results = self.FakeArgs(series='precise', source='linux-meta') |
400 | - expected = copy(results).update(esm=True) |
401 | - with self.capture() as (out, err): |
402 | - auto_route(results) |
403 | - self.assertEqual(expected.__dict__, results.__dict__) |
404 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
405 | - self.assertNotIn('via signing', out.getvalue().strip()) |
406 | - |
407 | - def test_esm_precise_lbm(self): |
408 | - results = self.FakeArgs(series='precise', source='linux-backports-modules-3.2.0') |
409 | - expected = copy(results).update(esm=True) |
410 | - with self.capture() as (out, err): |
411 | - auto_route(results) |
412 | - self.assertEqual(expected.__dict__, results.__dict__) |
413 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
414 | - self.assertNotIn('via signing', out.getvalue().strip()) |
415 | - |
416 | - def test_esm_precise_linux_lts_trusty(self): |
417 | - results = self.FakeArgs(series='precise', source='linux-lts-trusty') |
418 | - expected = copy(results).update(esm=True, to_signing=True) |
419 | - with self.capture() as (out, err): |
420 | - auto_route(results) |
421 | - self.assertEqual(expected.__dict__, results.__dict__) |
422 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
423 | - self.assertIn('via signing', out.getvalue().strip()) |
424 | - |
425 | - def test_esm_trusty_linux(self): |
426 | - results = self.FakeArgs(series='trusty', source='linux') |
427 | - expected = copy(results).update(esm=True, to_signing=True) |
428 | - with self.capture() as (out, err): |
429 | - auto_route(results) |
430 | - self.assertEqual(expected.__dict__, results.__dict__) |
431 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
432 | - self.assertIn('via signing', out.getvalue().strip()) |
433 | - |
434 | - def test_esm_trusty_linux_aws(self): |
435 | - results = self.FakeArgs(series='trusty', source='linux-aws') |
436 | - expected = copy(results).update(esm=True) |
437 | - with self.capture() as (out, err): |
438 | - auto_route(results) |
439 | - self.assertEqual(expected.__dict__, results.__dict__) |
440 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
441 | - self.assertNotIn('via signing', out.getvalue().strip()) |
442 | - |
443 | - def test_esm_precise_linux_lts_trusty(self): |
444 | - results = self.FakeArgs(series='precise', source='linux-lts-trusty') |
445 | - expected = copy(results).update(esm=True, to_signing=True) |
446 | - with self.capture() as (out, err): |
447 | - auto_route(results) |
448 | - self.assertEqual(expected.__dict__, results.__dict__) |
449 | - self.assertIn('from and to ESM', out.getvalue().strip()) |
450 | - self.assertIn('via signing', out.getvalue().strip()) |
451 | - |
452 | - def test_fips_bionic_linux_fips(self): |
453 | - results = self.FakeArgs(series='bionic', source='linux-fips') |
454 | - expected = copy(results).update(fips=True, to_signing=True) |
455 | - with self.capture() as (out, err): |
456 | - auto_route(results) |
457 | - self.assertEqual(expected.__dict__, results.__dict__) |
458 | - self.assertIn('from and to FIPS', out.getvalue().strip()) |
459 | - self.assertIn('via signing', out.getvalue().strip()) |
460 | - |
461 | - def test_ibmgt_bionic_linux_ibmgt(self): |
462 | - results = self.FakeArgs(series='bionic', source='linux-ibm-gt') |
463 | - expected = copy(results).update(ibmgt=True) |
464 | - with self.capture() as (out, err): |
465 | - auto_route(results) |
466 | - self.assertEqual(expected.__dict__, results.__dict__) |
467 | - self.assertIn('from and to IBM-GT', out.getvalue().strip()) |
468 | - self.assertNotIn('via signing', out.getvalue().strip()) |
469 | - |
470 | -def auto_route(args): |
471 | - before = (args.esm, args.fips, args.ibmgt, args.to_signing) |
472 | - |
473 | - if args.series in ('precise', 'trusty'): |
474 | - args.esm = True |
475 | - if args.esm: |
476 | - if (args.series == 'precise' and args.source in ('linux', 'linux-meta', 'linux-backports-modules-3.2.0') or |
477 | - args.series == 'trusty' and args.source in ('linux-aws', 'linux-meta-aws')): |
478 | - args.to_signing = False |
479 | - else: |
480 | - args.to_signing = True |
481 | - |
482 | - if args.source.endswith('-fips'): |
483 | - args.fips = True |
484 | - if args.fips: |
485 | - args.to_signing = True |
486 | - |
487 | - if args.source.endswith('-ibm-gt'): |
488 | - args.ibmgt = True |
489 | - |
490 | - if before != (args.esm, args.fips, args.ibmgt, args.to_signing): |
491 | - msg = "NOTE: directing copy " |
492 | - if args.esm: |
493 | - msg += "from and to ESM" |
494 | - elif args.fips: |
495 | - msg += "from and to FIPS" |
496 | - elif args.ibmgt: |
497 | - msg += "from and to IBM-GT PPAs" |
498 | - if args.to_signing: |
499 | - msg += " via signing" |
500 | + to_archive = signing_archive |
501 | + # Automatically route to signing by default. |
502 | + elif args.no_auto is False and signing_archive is not None and package_signed is not None: |
503 | + to_archive = signing_archive |
504 | + |
505 | + # Announce the routing if needed. |
506 | + if (args.testing is False and (routing.name != 'default' or from_archive == signing_archive or to_archive == signing_archive)): |
507 | + msg = "NOTE: directing copy using {} routes".format(routing.name) |
508 | + if from_archive == signing_archive: |
509 | + msg += ' from signing' |
510 | + elif to_archive == signing_archive: |
511 | + msg += ' to signing' |
512 | print(msg) |
513 | |
514 | + return (from_archive, to_archive, unembargo) |
515 | + |
516 | |
517 | # SELF-TESTS: |
518 | if len(sys.argv) >= 2 and sys.argv[1] == '--self-test': |
519 | @@ -347,7 +354,9 @@ |
520 | sys.exit(0) |
521 | |
522 | parser = argparse.ArgumentParser(description='Copy a proposed kernel to the apropriate archive pocket') |
523 | +parser.set_defaults(testing=False) |
524 | parser.add_argument('--dry-run', action='store_true', help='Do everything but actually copy the package') |
525 | +parser.add_argument('--ppa2', action='store_true', help='Copy from the kernel build PPA2') |
526 | parser.add_argument('--security', '-S', action='store_true', help='Copy from the kernel security PPA') |
527 | parser.add_argument('--security2', action='store_true', help='Copy from the kernel security PPA2') |
528 | parser.add_argument('--esm', '-E', action='store_true', help='Copy from the kernel ESM PPA and to the kernel ESM proposed PPA') |
529 | @@ -357,73 +366,87 @@ |
530 | parser.add_argument('--to-signing', action='store_true', help='Copy from the kernel ESM/FIPS PPA to the ESM/FIPS signing PPA') |
531 | parser.add_argument('--from-signing', action='store_true', help='Copy from the ESM/FIPS signing PPA to the ESM/FIPS proposed PPA') |
532 | parser.add_argument('series', action='store', help='The series the source package is in') |
533 | -parser.add_argument('source', action='store', help='The source package name') |
534 | +parser.add_argument('source', action='store', nargs='+', help='The source package name') |
535 | |
536 | args = parser.parse_args() |
537 | |
538 | -# If we are allowed to intuit destinations do so: |
539 | -# 1) precise is now destined for the ESM PPAs |
540 | -if not args.no_auto: |
541 | - auto_route(args) |
542 | - |
543 | -(ppa_name, to, security) = routing(args) |
544 | - |
545 | -##print("ppa_name<{}> to<{}>".format(ppa_name, to)) |
546 | - |
547 | -if ppa_name is None: |
548 | - print("ERROR: bad source PPA") |
549 | - sys.exit(1) |
550 | -if to is None: |
551 | - print("ERROR: bad destination") |
552 | - sys.exit(1) |
553 | - |
554 | -(release, pkg) = (args.series, args.source) |
555 | +if args.esm or args.fips or args.ibmgt: |
556 | + print("NOTE: flags --esm, --fips, and --ibmgt are now deprecated") |
557 | + |
558 | +release = args.series |
559 | + |
560 | +ks = KernelSeries() |
561 | |
562 | launchpad = Launchpad.login_with( |
563 | 'ubuntu-archive-tools', 'production', version='devel') |
564 | ubuntu = launchpad.distributions['ubuntu'] |
565 | distro_series = ubuntu.getSeries(name_or_version=release) |
566 | -kernel_ppa = launchpad.archives.getByReference( |
567 | - reference=ppa_name) |
568 | - |
569 | -# Grab a reference to the 'to' archive and select a pocket. |
570 | -to_archive = launchpad.archives.getByReference(reference=to) |
571 | -if to == 'ubuntu': |
572 | - to_pocket = 'proposed' |
573 | -else: |
574 | - to_pocket = 'release' |
575 | - |
576 | -# get current version in PPA for that series |
577 | -versions = kernel_ppa.getPublishedSources( |
578 | - source_name=pkg, exact_match=True, status='Published', pocket='Release', |
579 | - distro_series=distro_series) |
580 | -version = None |
581 | -if versions.total_size == 1: |
582 | - version = versions[0].source_package_version |
583 | - |
584 | -include_binaries = (pkg not in ('debian-installer') |
585 | - and not pkg.startswith('linux-signed')) |
586 | -if args.from_signing: |
587 | - include_binaries = True |
588 | - |
589 | -print("""Copying {}/{}: |
590 | - From: {} release |
591 | - To: {} {} |
592 | - Binaries: {}""".format(pkg, version, kernel_ppa, to_archive, to_pocket, include_binaries)) |
593 | - |
594 | -if not version: |
595 | - print("ERROR: no version to copy") |
596 | - sys.exit(1) |
597 | + |
598 | +copies = [] |
599 | +for pkg in list(args.source): |
600 | + # BODGE: routing should just take release/pkg. |
601 | + args.source = pkg |
602 | + |
603 | + (from_archive, to_archive, security) = routing(args, ks) |
604 | + ##print("from_archive<{}> to_archive<{}>".format(from_archive, to_archive)) |
605 | + |
606 | + if from_archive is None: |
607 | + print("ERROR: bad source PPA") |
608 | + sys.exit(1) |
609 | + if to_archive is None: |
610 | + print("ERROR: bad destination") |
611 | + sys.exit(1) |
612 | + |
613 | + (from_reference, from_pocket) = from_archive |
614 | + (to_reference, to_pocket) = to_archive |
615 | + |
616 | + # Grab a reference to the 'from' archive. |
617 | + from_archive = launchpad.archives.getByReference( |
618 | + reference=from_reference) |
619 | + |
620 | + # Grab a reference to the 'to' archive. |
621 | + to_archive = launchpad.archives.getByReference(reference=to_reference) |
622 | + |
623 | + # get current version in PPA for that series |
624 | + versions = from_archive.getPublishedSources( |
625 | + source_name=pkg, exact_match=True, status='Published', pocket=from_pocket, |
626 | + distro_series=distro_series) |
627 | + version = None |
628 | + if versions.total_size == 1: |
629 | + version = versions[0].source_package_version |
630 | + |
631 | + include_binaries = (pkg not in ('debian-installer') |
632 | + and not pkg.startswith('linux-signed')) |
633 | + if args.from_signing: |
634 | + include_binaries = True |
635 | + |
636 | + print("""Copying {}/{}: |
637 | + From: {} {} |
638 | + To: {} {} |
639 | + Binaries: {}""".format(pkg, version, from_archive.reference, from_pocket, to_archive.reference, to_pocket, include_binaries)) |
640 | + |
641 | + if not version: |
642 | + print("ERROR: no version to copy") |
643 | + sys.exit(1) |
644 | + |
645 | + copies.append({ |
646 | + 'from_archive': from_archive, |
647 | + 'include_binaries': include_binaries, |
648 | + 'source_name': pkg, |
649 | + 'to_series': release, |
650 | + 'to_pocket': to_pocket, |
651 | + 'version': version, |
652 | + 'auto_approve': True, |
653 | + 'unembargo': security, |
654 | + }) |
655 | |
656 | if args.dry_run: |
657 | print("Dry run; no packages copied.") |
658 | sys.exit(0) |
659 | |
660 | -# Finally ready to actually copy this. |
661 | -to_archive.copyPackage( |
662 | - from_archive=kernel_ppa, include_binaries=include_binaries, |
663 | - source_name=pkg, to_series=release, to_pocket=to_pocket, version=version, |
664 | - auto_approve=True, unembargo=security) |
665 | +for copy in copies: |
666 | + # We found valid packages for each requested element, actually copy them. |
667 | + to_archive.copyPackage(**copy) |
668 | |
669 | # TODO: adjust this script to use find-bin-overrides or rewrite |
670 | # find-bin-overrides to use lpapi and use it here. |
671 | |
672 | === added file 'kernel_series.py' |
673 | --- kernel_series.py 1970-01-01 00:00:00 +0000 |
674 | +++ kernel_series.py 2019-09-19 13:51:06 +0000 |
675 | @@ -0,0 +1,657 @@ |
676 | +#!/usr/bin/env python |
677 | +# |
678 | + |
679 | +try: |
680 | + from urllib.request import urlopen |
681 | +except ImportError: |
682 | + from urllib2 import urlopen |
683 | + |
684 | +import os |
685 | +import yaml |
686 | + |
687 | +class KernelRoutingEntry: |
688 | + def __init__(self, ks, source, data): |
689 | + name = "{}:{}".format(source.series.codename, source.name) |
690 | + if isinstance(data, str): |
691 | + name = data |
692 | + table = source.series.routing_table |
693 | + if table is None: |
694 | + raise ValueError("unable to map routing alias {}, " |
695 | + "no series routing table".format(data)) |
696 | + if data not in table: |
697 | + raise ValueError("unable to map routing alias {}, " |
698 | + "not listed in series routing table".format(data)) |
699 | + data = table[data] |
700 | + |
701 | + # Clear out any entries that have been overriden to None. |
702 | + for entry in dict(data): |
703 | + if data[entry] is None: |
704 | + del data[entry] |
705 | + |
706 | + self._ks = ks |
707 | + self._source = source |
708 | + self._name = name |
709 | + self._data = data if data else {} |
710 | + |
711 | + @property |
712 | + def source(self): |
713 | + return self._source |
714 | + |
715 | + @property |
716 | + def name(self): |
717 | + return self._name |
718 | + |
719 | + def __eq__(self, other): |
720 | + if isinstance(self, other.__class__): |
721 | + return list(self) == list(other) |
722 | + return False |
723 | + |
724 | + def __ne__(self, other): |
725 | + return not self.__eq__(other) |
726 | + |
727 | + def __iter__(self): |
728 | + return iter(self._data.items()) |
729 | + |
730 | + def __getitem__(self, which): |
731 | + return self._data[which] |
732 | + |
733 | + def lookup_destination(self, dest, primary=False): |
734 | + data = self._data.get(dest, None) |
735 | + if primary is False or data is None: |
736 | + return data |
737 | + return data[0] |
738 | + |
739 | + def __str__(self): |
740 | + return str(self._data) |
741 | + |
742 | + |
743 | +class KernelRepoEntry: |
744 | + def __init__(self, ks, owner, data): |
745 | + if isinstance(data, list): |
746 | + new_data = {'url': data[0]} |
747 | + if len(data) == 1: |
748 | + new_data['branch'] = 'master' |
749 | + elif len(data) == 2: |
750 | + new_data['branch'] = data[1] |
751 | + data = new_data |
752 | + |
753 | + self._ks = ks |
754 | + self._owner = owner |
755 | + self._data = data if data else {} |
756 | + |
757 | + @property |
758 | + def owner(self): |
759 | + return self._owner |
760 | + |
761 | + # XXX: should this object have a name ? |
762 | + |
763 | + def __eq__(self, other): |
764 | + if isinstance(self, other.__class__): |
765 | + return self.url == other.url and self.branch == other.branch |
766 | + return False |
767 | + |
768 | + def __ne__(self, other): |
769 | + return not self.__eq__(other) |
770 | + |
771 | + @property |
772 | + def url(self): |
773 | + return self._data['url'] |
774 | + |
775 | + @property |
776 | + def branch(self): |
777 | + return self._data.get('branch', None) |
778 | + |
779 | + def __str__(self): |
780 | + return "{} {}".format(self.url, self.branch) |
781 | + |
782 | + |
783 | +class KernelSnapEntry: |
784 | + def __init__(self, ks, source, name, data): |
785 | + self._ks = ks |
786 | + self._source = source |
787 | + self._name = name |
788 | + self._data = data if data else {} |
789 | + |
790 | + # Convert arches/track to publish-to form. |
791 | + if 'publish-to' not in self._data: |
792 | + if 'arches' in self._data: |
793 | + publish_to = {} |
794 | + for arch in self._data['arches']: |
795 | + publish_to[arch] = [self._data.get('track', 'latest')] |
796 | + self._data['publish-to'] = publish_to |
797 | + |
798 | + # Convert stable to promote-to form. |
799 | + if 'promote-to' not in self._data and 'stable' in self._data: |
800 | + if self._data['stable'] is True: |
801 | + self._data['promote-to'] = 'stable' |
802 | + else: |
803 | + self._data['promote-to'] = 'candidate' |
804 | + # Assume no promote-to data to mean just to edge. |
805 | + promote_to = self._data.get('promote-to', 'edge') |
806 | + if isinstance(promote_to, str): |
807 | + expand_promote_to = [] |
808 | + for risk in ('edge', 'beta', 'candidate', 'stable'): |
809 | + expand_promote_to.append(risk) |
810 | + if risk == promote_to: |
811 | + break |
812 | + self._data['promote-to'] = expand_promote_to |
813 | + # Ensure we have stable when promote-to is present. |
814 | + if 'promote-to' in self._data and 'stable' not in self._data: |
815 | + if 'stable' in self._data['promote-to']: |
816 | + self._data['stable'] = True |
817 | + else: |
818 | + self._data['stable'] = False |
819 | + |
820 | + def __eq__(self, other): |
821 | + if isinstance(self, other.__class__): |
822 | + return self.name == other.name and self.source == other.source |
823 | + return False |
824 | + |
825 | + def __ne__(self, other): |
826 | + return not self.__eq__(other) |
827 | + |
828 | + @property |
829 | + def series(self): |
830 | + return self._source.series |
831 | + |
832 | + @property |
833 | + def source(self): |
834 | + return self._source |
835 | + |
836 | + @property |
837 | + def name(self): |
838 | + return self._name |
839 | + |
840 | + @property |
841 | + def repo(self): |
842 | + data = self._data.get('repo', None) |
843 | + if not data: |
844 | + return None |
845 | + return KernelRepoEntry(self._ks, self, data) |
846 | + |
847 | + @property |
848 | + def primary(self): |
849 | + return self._data.get('primary', False) |
850 | + |
851 | + @property |
852 | + def gated(self): |
853 | + return self._data.get('gated', False) |
854 | + |
855 | + @property |
856 | + def stable(self): |
857 | + return self._data.get('stable', False) |
858 | + |
859 | + @property |
860 | + def qa(self): |
861 | + return self._data.get('qa', False) |
862 | + |
863 | + @property |
864 | + def hw_cert(self): |
865 | + return self._data.get('hw-cert', False) |
866 | + |
867 | + @property |
868 | + def arches(self): |
869 | + # XXX: should this be [] |
870 | + return self._data.get('arches', None) |
871 | + |
872 | + @property |
873 | + def track(self): |
874 | + return self._data.get('track', None) |
875 | + |
876 | + @property |
877 | + def publish_to(self): |
878 | + return self._data.get('publish-to', None) |
879 | + |
880 | + @property |
881 | + def promote_to(self): |
882 | + return self._data.get('promote-to', None) |
883 | + |
884 | + def promote_to_risk(self, risk): |
885 | + return risk in self._data.get('promote-to', []) |
886 | + |
887 | + def __str__(self): |
888 | + return "{} {}".format(str(self.source), self.name) |
889 | + |
890 | + |
891 | +class KernelPackageEntry: |
892 | + def __init__(self, ks, source, name, data): |
893 | + self._ks = ks |
894 | + self._source = source |
895 | + self._name = name |
896 | + self._data = data if data else {} |
897 | + |
898 | + def __eq__(self, other): |
899 | + if isinstance(self, other.__class__): |
900 | + return self.name == other.name and self.source == other.source |
901 | + return False |
902 | + |
903 | + def __ne__(self, other): |
904 | + return not self.__eq__(other) |
905 | + |
906 | + @property |
907 | + def series(self): |
908 | + return self._source.series |
909 | + |
910 | + @property |
911 | + def source(self): |
912 | + return self._source |
913 | + |
914 | + @property |
915 | + def name(self): |
916 | + return self._name |
917 | + |
918 | + @property |
919 | + def type(self): |
920 | + return self._data.get('type', None) |
921 | + |
922 | + @property |
923 | + def repo(self): |
924 | + data = self._data.get('repo', None) |
925 | + if not data: |
926 | + return None |
927 | + return KernelRepoEntry(self._ks, self, data) |
928 | + |
929 | + def __str__(self): |
930 | + return "{} {} {}".format(str(self.source), self.name, self.type) |
931 | + |
932 | + |
933 | +class KernelSourceEntry: |
934 | + def __init__(self, ks, series, name, data): |
935 | + self._ks = ks |
936 | + self._series = series |
937 | + self._name = name |
938 | + self._data = data if data else {} |
939 | + |
940 | + def __eq__(self, other): |
941 | + if isinstance(self, other.__class__): |
942 | + return self.name == other.name and self.series == other.series |
943 | + return False |
944 | + |
945 | + def __ne__(self, other): |
946 | + return not self.__eq__(other) |
947 | + |
948 | + @property |
949 | + def name(self): |
950 | + return self._name |
951 | + |
952 | + @property |
953 | + def series(self): |
954 | + return self._series |
955 | + |
956 | + @property |
957 | + def versions(self): |
958 | + if 'versions' in self._data: |
959 | + return self._data['versions'] |
960 | + |
961 | + derived_from = self.derived_from |
962 | + if derived_from is not None: |
963 | + return derived_from.versions |
964 | + |
965 | + copy_forward = self.copy_forward |
966 | + if copy_forward is not None: |
967 | + return copy_forward.versions |
968 | + |
969 | + # XXX: should this be [] |
970 | + return None |
971 | + |
972 | + @property |
973 | + def version(self): |
974 | + versions = self.versions |
975 | + if not versions: |
976 | + return None |
977 | + return versions[-1] |
978 | + |
979 | + @property |
980 | + def development(self): |
981 | + return self._data.get('development', self.series.development) |
982 | + |
983 | + @property |
984 | + def supported(self): |
985 | + return self._data.get('supported', self.series.supported) |
986 | + |
987 | + @property |
988 | + def severe_only(self): |
989 | + return self._data.get('severe-only', False) |
990 | + |
991 | + @property |
992 | + def stakeholder(self): |
993 | + return self._data.get('stakeholder', None) |
994 | + |
995 | + @property |
996 | + def packages(self): |
997 | + # XXX: should this return None when empty |
998 | + result = [] |
999 | + packages = self._data.get('packages') |
1000 | + if packages: |
1001 | + for package_key, package in packages.items(): |
1002 | + result.append(KernelPackageEntry(self._ks, self, package_key, package)) |
1003 | + return result |
1004 | + |
1005 | + def lookup_package(self, package_key): |
1006 | + packages = self._data.get('packages') |
1007 | + if not packages or package_key not in packages: |
1008 | + return None |
1009 | + return KernelPackageEntry(self._ks, self, package_key, packages[package_key]) |
1010 | + |
1011 | + @property |
1012 | + def snaps(self): |
1013 | + # XXX: should this return None when empty |
1014 | + result = [] |
1015 | + snaps = self._data.get('snaps') |
1016 | + if snaps: |
1017 | + for snap_key, snap in snaps.items(): |
1018 | + result.append(KernelSnapEntry(self._ks, self, snap_key, snap)) |
1019 | + return result |
1020 | + |
1021 | + def lookup_snap(self, snap_key): |
1022 | + snaps = self._data.get('snaps') |
1023 | + if not snaps or snap_key not in snaps: |
1024 | + return None |
1025 | + return KernelSnapEntry(self._ks, self, snap_key, snaps[snap_key]) |
1026 | + |
1027 | + @property |
1028 | + def derived_from(self): |
1029 | + if 'derived-from' not in self._data: |
1030 | + return None |
1031 | + |
1032 | + (series_key, source_key) = self._data['derived-from'] |
1033 | + |
1034 | + series = self._ks.lookup_series(series_key) |
1035 | + source = series.lookup_source(source_key) |
1036 | + |
1037 | + return source |
1038 | + |
1039 | + @property |
1040 | + def testable_flavours(self): |
1041 | + retval = [] |
1042 | + if (self._data.get('testing') is not None and |
1043 | + self._data['testing'].get('flavours') is not None |
1044 | + ): |
1045 | + for flavour in self._data['testing']['flavours'].keys(): |
1046 | + fdata = self._data['testing']['flavours'][flavour] |
1047 | + # If we have neither arches nor clouds we represent a noop |
1048 | + if not fdata: |
1049 | + continue |
1050 | + arches = fdata.get('arches', None) |
1051 | + arches = arches if arches is not None else [] |
1052 | + clouds = fdata.get('clouds', None) |
1053 | + clouds = clouds if clouds is not None else [] |
1054 | + retval.append(KernelSourceTestingFlavourEntry(flavour, arches, clouds)) |
1055 | + return retval |
1056 | + |
1057 | + @property |
1058 | + def invalid_tasks(self): |
1059 | + retval = self._data.get('invalid-tasks', []) |
1060 | + if retval is None: |
1061 | + retval = [] |
1062 | + return retval |
1063 | + |
1064 | + @property |
1065 | + def copy_forward(self): |
1066 | + if 'copy-forward' not in self._data: |
1067 | + return None |
1068 | + |
1069 | + # XXX: backwards compatibility. |
1070 | + if self._data['copy-forward'] is False: |
1071 | + return None |
1072 | + if self._data['copy-forward'] is True: |
1073 | + derived_from = self.derived_from |
1074 | + if derived_from is None: |
1075 | + return True |
1076 | + return self.derived_from |
1077 | + |
1078 | + (series_key, source_key) = self._data['copy-forward'] |
1079 | + |
1080 | + series = self._ks.lookup_series(series_key) |
1081 | + source = series.lookup_source(source_key) |
1082 | + |
1083 | + return source |
1084 | + |
1085 | + @property |
1086 | + def backport(self): |
1087 | + return self._data.get('backport', False) |
1088 | + |
1089 | + @property |
1090 | + def routing(self): |
1091 | + default = 'default' |
1092 | + if self.series.development: |
1093 | + default = 'devel' |
1094 | + if self.series.esm: |
1095 | + default = 'esm' |
1096 | + data = self._data.get('routing', default) |
1097 | + if data is None: |
1098 | + return data |
1099 | + return KernelRoutingEntry(self._ks, self, data) |
1100 | + |
1101 | + @property |
1102 | + def swm_data(self): |
1103 | + return self._data.get('swm') |
1104 | + |
1105 | + @property |
1106 | + def private(self): |
1107 | + return self._data.get('private', False) |
1108 | + |
1109 | + def __str__(self): |
1110 | + return "{} {}".format(self.series.name, self.name) |
1111 | + |
1112 | +class KernelSourceTestingFlavourEntry: |
1113 | + def __init__(self, name, arches, clouds): |
1114 | + self._name = name |
1115 | + self._arches = arches |
1116 | + self._clouds = clouds |
1117 | + |
1118 | + @property |
1119 | + def name(self): |
1120 | + return self._name |
1121 | + |
1122 | + @property |
1123 | + def arches(self): |
1124 | + return self._arches |
1125 | + |
1126 | + @property |
1127 | + def clouds(self): |
1128 | + return self._clouds |
1129 | + |
1130 | +class KernelSeriesEntry: |
1131 | + def __init__(self, ks, name, data, defaults=None): |
1132 | + self._ks = ks |
1133 | + self._name = name |
1134 | + self._data = {} |
1135 | + if defaults is not None: |
1136 | + self._data.update(defaults) |
1137 | + if data is not None: |
1138 | + self._data.update(data) |
1139 | + |
1140 | + def __eq__(self, other): |
1141 | + if isinstance(self, other.__class__): |
1142 | + return self.name == other.name |
1143 | + return False |
1144 | + |
1145 | + def __ne__(self, other): |
1146 | + return not self.__eq__(other) |
1147 | + |
1148 | + @property |
1149 | + def name(self): |
1150 | + return self._name |
1151 | + |
1152 | + @property |
1153 | + def codename(self): |
1154 | + return self._data.get('codename', None) |
1155 | + |
1156 | + @property |
1157 | + def opening(self): |
1158 | + if 'opening' in self._data: |
1159 | + if self._data['opening'] is not False: |
1160 | + return True |
1161 | + return False |
1162 | + |
1163 | + def opening_ready(self, *flags): |
1164 | + if 'opening' not in self._data: |
1165 | + return True |
1166 | + allow = self._data['opening'] |
1167 | + if allow is None: |
1168 | + return False |
1169 | + if allow in (True, False): |
1170 | + return not allow |
1171 | + for flag in flags: |
1172 | + flag_allow = allow.get(flag, False) |
1173 | + if flag_allow is None or flag_allow is False: |
1174 | + return False |
1175 | + return True |
1176 | + opening_allow = opening_ready |
1177 | + |
1178 | + @property |
1179 | + def development(self): |
1180 | + return self._data.get('development', False) |
1181 | + |
1182 | + @property |
1183 | + def supported(self): |
1184 | + return self._data.get('supported', False) |
1185 | + |
1186 | + @property |
1187 | + def lts(self): |
1188 | + return self._data.get('lts', False) |
1189 | + |
1190 | + @property |
1191 | + def esm(self): |
1192 | + return self._data.get('esm', False) |
1193 | + |
1194 | + def __str__(self): |
1195 | + return "{} ({})".format(self.name, self.codename) |
1196 | + |
1197 | + @property |
1198 | + def sources(self): |
1199 | + result = [] |
1200 | + sources = self._data.get('sources') |
1201 | + if sources: |
1202 | + for source_key, source in sources.items(): |
1203 | + result.append(KernelSourceEntry( |
1204 | + self._ks, self, source_key, source)) |
1205 | + return result |
1206 | + |
1207 | + @property |
1208 | + def routing_table(self): |
1209 | + return self._data.get('routing-table', None) |
1210 | + |
1211 | + def lookup_source(self, source_key): |
1212 | + sources = self._data.get('sources') |
1213 | + if not sources or source_key not in sources: |
1214 | + return None |
1215 | + return KernelSourceEntry(self._ks, self, source_key, sources[source_key]) |
1216 | + |
1217 | + |
1218 | +# KernelSeries |
1219 | +# |
1220 | +class KernelSeries: |
1221 | + _url = 'https://git.launchpad.net/~canonical-kernel/' \ |
1222 | + '+git/kteam-tools/plain/info/kernel-series.yaml' |
1223 | + _url_local = 'file://' + os.path.realpath(os.path.join(os.path.dirname(__file__), |
1224 | + '..', 'info', 'kernel-series.yaml')) |
1225 | + #_url = 'file:///home/apw/git2/kteam-tools/info/kernel-series.yaml' |
1226 | + #_url = 'file:///home/work/kteam-tools/info/kernel-series.yaml' |
1227 | + _data_txt = {} |
1228 | + |
1229 | + @classmethod |
1230 | + def __load_once(cls, url): |
1231 | + if url not in cls._data_txt: |
1232 | + response = urlopen(url) |
1233 | + data = response.read() |
1234 | + if not isinstance(data, str): |
1235 | + data = data.decode('utf-8') |
1236 | + cls._data_txt[url] = data |
1237 | + return cls._data_txt[url] |
1238 | + |
1239 | + def __init__(self, url=None, data=None, use_local=os.getenv("USE_LOCAL_KERNEL_SERIES_YAML", False)): |
1240 | + if data or url: |
1241 | + if url: |
1242 | + response = urlopen(url) |
1243 | + data = response.read() |
1244 | + if not isinstance(data, str): |
1245 | + data = data.decode('utf-8') |
1246 | + else: |
1247 | + data = self.__load_once(self._url_local if use_local else self._url) |
1248 | + self._data = yaml.load(data) |
1249 | + |
1250 | + self._development_series = None |
1251 | + self._codename_to_series = {} |
1252 | + for series_key, series in self._data.items(): |
1253 | + if not series: |
1254 | + continue |
1255 | + if series.get('development', False): |
1256 | + self._development_series = series_key |
1257 | + if 'codename' in series: |
1258 | + self._codename_to_series[series['codename']] = series_key |
1259 | + |
1260 | + # Pull out the defaults. |
1261 | + self._defaults_series = {} |
1262 | + if 'defaults' in self._data: |
1263 | + self._defaults_series = self._data['defaults'] |
1264 | + del self._data['defaults'] |
1265 | + |
1266 | + @staticmethod |
1267 | + def key_series_name(series): |
1268 | + return [int(x) for x in series.name.split('.')] |
1269 | + |
1270 | + @property |
1271 | + def series(self): |
1272 | + return [KernelSeriesEntry(self, series_key, series, |
1273 | + defaults=self._defaults_series) |
1274 | + for series_key, series in self._data.items()] |
1275 | + |
1276 | + def lookup_series(self, series=None, codename=None, development=False): |
1277 | + if not series and not codename and not development: |
1278 | + raise ValueError("series/codename/development required") |
1279 | + if not series and codename: |
1280 | + if codename not in self._codename_to_series: |
1281 | + return None |
1282 | + series = self._codename_to_series[codename] |
1283 | + if not series and development: |
1284 | + if not self._development_series: |
1285 | + return None |
1286 | + series = self._development_series |
1287 | + if series and series not in self._data: |
1288 | + return None |
1289 | + return KernelSeriesEntry(self, series, self._data[series], |
1290 | + defaults=self._defaults_series) |
1291 | + |
1292 | + |
1293 | +if __name__ == '__main__': |
1294 | + db = KernelSeries() |
1295 | + |
1296 | + series = db.lookup_series('16.04') |
1297 | + if series.name != '16.04': |
1298 | + print('series.name != 16.04') |
1299 | + if series.codename != 'xenial': |
1300 | + print('series.codename != xenial') |
1301 | + |
1302 | + series2 = db.lookup_series(codename='xenial') |
1303 | + if series2.name != '16.04': |
1304 | + print('series2.name != 16.04') |
1305 | + if series2.codename != 'xenial': |
1306 | + print('series2.codename != xenial') |
1307 | + |
1308 | + series3 = db.lookup_series(development=True) |
1309 | + if series3.name != '18.04': |
1310 | + print('series3.name != 18.04') |
1311 | + if series3.codename != 'bionic': |
1312 | + print('series3.codename != bionic') |
1313 | + |
1314 | + print(str(series), str(series2), str(series3)) |
1315 | + |
1316 | + for series2 in sorted(db.series, key=db.key_series_name): |
1317 | + print(series2) |
1318 | + |
1319 | + for source in series.sources: |
1320 | + print(str(source), source.series.name, source.name) |
1321 | + |
1322 | + print(source.derived_from) |
1323 | + print(source.versions) |
1324 | + |
1325 | + for package in source.packages: |
1326 | + print("PACKAGE", str(package)) |
1327 | + |
1328 | + for snap in source.snaps: |
1329 | + print("SNAP", str(snap), snap.arches) |
1330 | + |
1331 | + |
1332 | +# vi:set ts=4 sw=4 expandtab: |
1333 | |
1334 | === modified file 'sru-release' |
1335 | --- sru-release 2019-08-27 11:30:27 +0000 |
1336 | +++ sru-release 2019-09-19 13:51:06 +0000 |
1337 | @@ -39,6 +39,8 @@ |
1338 | |
1339 | from launchpadlib.launchpad import Launchpad |
1340 | |
1341 | +from kernel_series import KernelSeries |
1342 | + |
1343 | |
1344 | # Each entry in this list is a list of source packages that are known |
1345 | # to have inter-dependencies and must be released simultaneously. |
1346 | @@ -179,10 +181,10 @@ |
1347 | 'published': proposed_date} |
1348 | ''' |
1349 | versions = defaultdict(dict) |
1350 | - if options.esm: |
1351 | + if src_archive.reference == 'ubuntu': |
1352 | + pocket = 'Proposed' |
1353 | + else: |
1354 | pocket = 'Release' |
1355 | - else: |
1356 | - pocket = 'Proposed' |
1357 | |
1358 | matches = src_archive.getPublishedSources( |
1359 | source_name=sourcename, exact_match=not options.pattern, |
1360 | @@ -193,9 +195,9 @@ |
1361 | source_name=match.source_package_name, exact_match=True, |
1362 | status='Published', distro_series=series): |
1363 | key = pub.pocket.lower() |
1364 | - # special case for ESM ppas, which don't have pockets but need |
1365 | + # special case for ppas, which don't have pockets but need |
1366 | # to be treated as -proposed |
1367 | - if options.esm and key == 'release': |
1368 | + if pocket == 'Release' and key == 'release': |
1369 | key = 'proposed' |
1370 | versions[pub.source_package_name][key] = ( |
1371 | pub.source_package_version) |
1372 | @@ -213,7 +215,6 @@ |
1373 | if pocket in pub.pocket: |
1374 | versions[pub.source_package_name]['changesfile'] = ( |
1375 | pub.changesFileUrl()) |
1376 | - |
1377 | # devel version |
1378 | if devel_series: |
1379 | for pub in src_archive.getPublishedSources( |
1380 | @@ -360,6 +361,13 @@ |
1381 | release = args.pop(0) |
1382 | packages = args |
1383 | |
1384 | + # XXX: we only want to instantiate KernelSeries if we suspect this is |
1385 | + # a kernel package, this is necessarily dirty, dirty, dirty. |
1386 | + kernel_checks = False |
1387 | + for package in packages: |
1388 | + if package.startswith('linux-') or package == 'linux': |
1389 | + kernel_checks = True |
1390 | + |
1391 | if not options.skip_package_group_check: |
1392 | try: |
1393 | packages = check_package_sets(packages) |
1394 | @@ -376,12 +384,57 @@ |
1395 | sys.stderr.write( |
1396 | 'WARNING: No current development series, -d will not work\n') |
1397 | devel_series = None |
1398 | - if release in ('precise', 'trusty'): |
1399 | + |
1400 | + ks_source = None |
1401 | + if kernel_checks: |
1402 | + kernel_series = KernelSeries() |
1403 | + |
1404 | + # See if we have a kernel-series record for this package. If we do |
1405 | + # then we are going to pivot to the routing therein. |
1406 | + ks_series = kernel_series.lookup_series(codename=release) |
1407 | + for ks_source_find in ks_series.sources: |
1408 | + for ks_package in ks_source_find.packages: |
1409 | + if ks_package.name == packages[0]: |
1410 | + ks_source = ks_source_find |
1411 | + break |
1412 | + |
1413 | + # First confirm everything in this set we are attempting to release |
1414 | + # are indeed listed as valid for this kernel. |
1415 | + if ks_source is not None: |
1416 | + for package in packages: |
1417 | + if ks_source.lookup_package(package) is None: |
1418 | + sys.stderr.write( |
1419 | + 'WARNING: {} not found in packages for kernel {}\n'.format( |
1420 | + package, ks_source.name)) |
1421 | + |
1422 | + if ks_source is None and release in ('precise', 'trusty'): |
1423 | sys.stdout.write( |
1424 | 'Called for {}; assuming kernel ESM publication\n'.format(release)) |
1425 | options.esm = True |
1426 | |
1427 | - if options.esm: |
1428 | + # If we found a KernelSeries entry this has accurate routing information |
1429 | + # attached use that. |
1430 | + if ks_source is not None: |
1431 | + src_archive_ref, src_archive_pocket = ks_source.routing.lookup_destination('proposed', primary=True) |
1432 | + src_archive = launchpad.archives.getByReference( |
1433 | + reference=src_archive_ref) |
1434 | + dst_archive_ref, dst_archive_pocket = ks_source.routing.lookup_destination('updates', primary=True) |
1435 | + if dst_archive_ref == src_archive_ref: |
1436 | + dst_archive = src_archive |
1437 | + else: |
1438 | + dst_archive = launchpad.archives.getByReference( |
1439 | + reference=dst_archive_ref) |
1440 | + |
1441 | + # Announce any non-standard archive routing. |
1442 | + if src_archive_ref != 'ubuntu': |
1443 | + print("Src Archive: {}".format(src_archive_ref)) |
1444 | + if dst_archive_ref != 'ubuntu': |
1445 | + print("Dst Archive: {}".format(dst_archive_ref)) |
1446 | + # --security is meaningless for private PPA publishing (XXX: currently true) |
1447 | + options.security = False |
1448 | + options.release = True |
1449 | + |
1450 | + elif options.esm: |
1451 | # --security is meaningless for ESM everything is a security update. |
1452 | options.security = False |
1453 | options.release = True |
Ok, this generally looks good. The design of this module feels a bit strange, but I guess the reason for that is how our current tools were created. Like, it would be probably a bit more intuitive for me if KernelSeries had some get_routing_ for_package( ) function that would simply take series and package as arguments and spit out some routing info class.
Anyway, this here is good as is, and we can certainly enhance this even further if needed.