-
Notifications
You must be signed in to change notification settings - Fork 98
/
Copy pathreleaser.py
336 lines (271 loc) · 15 KB
/
releaser.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# -*- coding: utf-8 -*-
#
# (C) 2014 mhrusecky@suse.cz, openSUSE.org
# (C) 2014 tchvatal@suse.cz, openSUSE.org
# (C) 2014 aplanas@suse.de, openSUSE.org
# (C) 2014 coolo@suse.de, openSUSE.org
# (C) 2017 okurz@suse.de, openSUSE.org
# (C) 2018 dheidler@suse.de, openSUSE.org
# Distribute under GPLv2 or GPLv3
import re
from lxml import etree as ET
from ttm.manager import ToTestManager, NotFoundException, QAResult
class ToTestReleaser(ToTestManager):
def __init__(self, tool):
ToTestManager.__init__(self, tool)
def setup(self, project):
super(ToTestReleaser, self).setup(project)
def release(self, project, force=False):
self.setup(project)
testing_snapshot = self.get_status('testing')
if not testing_snapshot and not force:
self.logger.debug("No snapshot in testing, waiting for publisher to tell us")
return None
new_snapshot = self.version_from_project()
if not force:
# not overwriting
if new_snapshot == testing_snapshot:
self.logger.debug('no change in snapshot version')
return None
if testing_snapshot != self.get_status('failed') and testing_snapshot != self.get_status('published'):
self.logger.debug(f'Snapshot {testing_snapshot} is still in progress')
return QAResult.inprogress
self.logger.info('testing snapshot %s', testing_snapshot)
self.logger.debug('new snapshot %s', new_snapshot)
if not self.is_snapshotable():
self.logger.debug('not snapshotable')
return QAResult.failed
if not self.all_built_products_in_config():
self.logger.debug('config incomplete')
return QAResult.failed
self.update_totest(new_snapshot)
self.update_status('testing', new_snapshot)
self.update_status('failed', '')
self.write_version_to_dashboard('totest', new_snapshot)
return QAResult.passed
def release_version(self):
url = self.api.makeurl(['build', self.project.name, 'standard', self.project.arch,
f'000release-packages:{self.project.base}-release'])
f = self.api.retried_GET(url)
root = ET.parse(f).getroot()
for binary in root.findall('binary'):
binary = binary.get('filename', '')
result = re.match(r'.*-([^-]*)-[^-]*.src.rpm', binary)
if result:
return result.group(1)
raise NotFoundException(f"can't find {self.project.name} version")
def version_from_project(self):
if not self.project.take_source_from_product:
return self.release_version()
if len(self.project.main_products):
# 000productcompose has ftp built only and the build number
# agama-installer carry over build number from 000prodcutcompose
# but they are not from the same package container
if 'productcompose' in self.project.main_products[0]:
return self.productcompose_build_version(self.project.name, self.project.main_products[0])
return self.iso_build_version(self.project.name, self.project.main_products[0])
return self.iso_build_version(self.project.name, self.project.image_products[0].package,
arch=self.project.image_products[0].archs[0])
def maxsize_for_package(self, package, arch):
if re.match(r'.*-mini-.*', package):
return 737280000 # a CD needs to match
if re.match(r'.*-dvd5-.*', package):
return 4700372992 # a DVD needs to match
if re.match(r'.*-(dvd9-dvd|cd-DVD)-.*', package):
return 8539996159
# Other types don't have a fixed size limit
return None
def package_ok(self, prjresult, project, package, repository, arch):
"""Checks one package in a project and returns True if it's succeeded"""
status = prjresult.xpath(f'result[@repository="{repository}"][@arch="{arch}"]/'
f'status[@package="{package}"]')
failed = [s for s in status if s.get('code') != 'succeeded']
if len(failed):
self.logger.info(
f"{project} {package} {repository} {arch} -> {failed[0].get('code')}")
return False
succeeded = [s for s in status if s.get('code') == 'succeeded']
if not len(succeeded):
self.logger.info(f'No "succeeded" for {project} {package} {repository} {arch}')
return False
maxsize = self.maxsize_for_package(package, arch)
if not maxsize:
return True
url = self.api.makeurl(['build', project, repository, arch, package])
f = self.api.retried_GET(url)
root = ET.parse(f).getroot()
for binary in root.findall('binary'):
if not binary.get('filename', '').endswith('.iso'):
continue
isosize = int(binary.get('size', 0))
if isosize > maxsize:
self.logger.error('%s %s %s %s: %s' % (
project, package, repository, arch, 'too large by %s bytes' % (isosize - maxsize)))
return False
return True
def all_built_products_in_config(self):
"""Verify that all succeeded products are mentioned in the ttm config"""
# First for all products in product_repo
products = {}
for simple_product in self.project.ftp_products + self.project.main_products:
products[simple_product] = [self.project.product_arch]
for image_product in self.project.image_products + self.project.container_products:
products[image_product.package] = image_product.archs
all_found = self.verify_package_list_complete(self.project.product_repo, products)
# Then for containerfile_products
if self.project.containerfile_products:
products = {}
for image_product in self.project.containerfile_products:
products[image_product.package] = image_product.archs
all_found = self.verify_package_list_complete('containerfile', products) and all_found
return all_found
def verify_package_list_complete(self, repository, product_archs):
"""Loop through all successfully built products and check whether they
are part of product_archs (e.g. {'foo:ftp': ['local'], some-image': ['x86_64'], ...})"""
# Don't return false early, to show all errors at once
all_found = True
# Get all results for the product repo from OBS
url = self.api.makeurl(['build', self.project.name, "_result"],
{'repository': repository,
'multibuild': 1})
f = self.api.retried_GET(url)
resultlist = ET.parse(f).getroot()
for result in resultlist.findall('result'):
arch = result.get('arch')
for package in result.findall('status[@code="succeeded"]'):
packagename = package.get('package')
released_archs = None
if packagename in product_archs:
released_archs = product_archs[packagename]
elif ':' in packagename:
# For multibuild, it's enough to release the container
multibuildcontainer = packagename.split(':')[0]
if multibuildcontainer in product_archs:
released_archs = product_archs[multibuildcontainer]
# Ignore the arch check for multibuild containers,
# as it might not build for the same archs as all flavors.
continue
if released_archs is None:
self.logger.error("%s is built for %s, but not mentioned as product" % (
packagename, arch))
all_found = False
elif arch not in released_archs:
self.logger.error("%s is built for %s, but that arch is not mentioned" % (
packagename, arch))
all_found = False
return all_found
def is_snapshotable(self):
"""Check various conditions required for factory to be snapshotable
"""
if not self.all_repos_done(self.project.name):
return False
all_ok = True
resultxml = self.api.retried_GET(self.api.makeurl(['build', self.project.name, '_result']))
prjresult = ET.parse(resultxml).getroot()
for product in self.project.ftp_products + self.project.main_products:
if not self.package_ok(prjresult, self.project.name, product, self.project.product_repo, self.project.product_arch):
all_ok = False
# agama-installer in Leap uses images repo as source repo as well as target repo
source_repo = self.project.product_repo
if self.project.same_target_images_repo_for_source_repo:
source_repo = self.project.totest_images_repo
for product in self.project.image_products + self.project.container_products:
for arch in product.archs:
if not self.package_ok(prjresult, self.project.name, product.package, source_repo, arch):
all_ok = False
for product in self.project.containerfile_products:
for arch in product.archs:
if not self.package_ok(prjresult, self.project.name, product.package, 'containerfile', arch):
all_ok = False
if len(self.project.livecd_products):
liveprjname = f'{self.project.name}:Live'
if not self.all_repos_done(liveprjname):
return False
liveresultxml = self.api.retried_GET(self.api.makeurl(['build', liveprjname, '_result']))
liveprjresult = ET.parse(liveresultxml).getroot()
for product in self.project.livecd_products:
for arch in product.archs:
if not self.package_ok(liveprjresult, liveprjname, product.package,
self.project.product_repo, arch):
all_ok = False
if not all_ok:
return False
# The FTP tree isn't released with setrelease, so it needs to contain
# the product version already.
product_version = self.get_product_version()
if product_version is not None:
for product in self.project.ftp_products:
for binary in self.binaries_of_product(self.project.name, product):
# The NonOSS tree doesn't include the version...
if binary.endswith('.report') and 'NonOss' not in binary and product_version not in binary:
self.logger.debug(f'{binary} in {product} does not include {product_version}')
return False
if self.project.need_same_build_number:
# make sure all medias have the same build number
builds = set()
for p in self.project.ftp_products:
if 'Addon-NonOss' in p:
# XXX: don't care about nonoss atm.
continue
builds.add(self.ftp_build_version(self.project.name, p))
for p in self.project.main_products:
builds.add(self.iso_build_version(self.project.name, p))
for p in self.project.livecd_products + self.project.image_products:
for arch in p.archs:
builds.add(self.iso_build_version(self.project.name, p.package,
arch=arch))
if len(builds) != 1:
self.logger.debug('not all medias have the same build number')
return False
return True
def _release(self, set_release=None):
for container in self.project.container_products:
# Containers are built in the same repo as other image products,
# but released into a different repo in :ToTest
self.release_package(self.project.name, container.package, repository=self.project.product_repo,
target_project=self.project.test_project,
target_repository=self.project.totest_container_repo)
for container in self.project.containerfile_products:
# Dockerfile builds are done in a separate repo, but released into the same location
# as container_products
self.release_package(self.project.name, container.package, repository='containerfile',
target_project=self.project.test_project,
target_repository=self.project.totest_container_repo)
if len(self.project.main_products):
for product in self.project.ftp_products:
self.release_package(self.project.name, product, repository=self.project.product_repo)
for cd in self.project.main_products:
# do not set release number if it is productcompose
if 'productcompose' in self.project.main_products[0]:
self.release_package(self.project.name, cd, repository=self.project.product_repo)
else:
self.release_package(self.project.name, cd, set_release=set_release,
repository=self.project.product_repo)
for cd in self.project.livecd_products:
self.release_package('%s:Live' %
self.project.name, cd.package, set_release=set_release,
repository=self.project.livecd_repo)
for image in self.project.image_products:
source_repo = self.project.product_repo
if self.project.same_target_images_repo_for_source_repo:
source_repo = self.project.totest_images_repo
self.release_package(self.project.name, image.package, set_release=set_release,
repository=source_repo,
target_project=self.project.test_project,
target_repository=self.project.totest_images_repo)
def update_totest(self, snapshot=None):
# omit snapshot, we don't want to rename on release
if not self.project.set_snapshot_number:
snapshot = None
if snapshot:
release = self.project.snapshot_number_prefix + snapshot
self.logger.info(f'Updating snapshot {snapshot}')
else:
release = None
if not (self.dryrun or self.project.do_not_release):
self.api.switch_flag_in_prj(self.project.test_project, flag='publish', state='disable',
repository=self.project.product_repo)
if self.project.totest_images_repo != self.project.product_repo:
self.api.switch_flag_in_prj(self.project.test_project, flag='publish', state='disable',
repository=self.project.totest_images_repo)
self._release(set_release=release)