]> git.pld-linux.org Git - projects/pld-builder.new.git/blame - PLD_Builder/request.py
format requests with relative date
[projects/pld-builder.new.git] / PLD_Builder / request.py
CommitLineData
dfff8bd5
MM
1# vi: encoding=utf-8 ts=8 sts=4 sw=4 et
2
51a6a3a6 3from xml.dom.minidom import *
d867e693 4from datetime import datetime
51a6a3a6 5import string
94169186 6import time
2ba00366
MM
7import xml.sax.saxutils
8import fnmatch
482da2b1 9import os
73bfd962 10import urllib
7d73f518 11import cgi
d867e693 12import pytz
51a6a3a6 13
30dbf6a3 14import util
17f23d66 15import log
e2cad913 16from acl import acl
b3c8d962 17from config import config
30dbf6a3 18
94169186 19__all__ = ['parse_request', 'parse_requests']
0d52c382 20
51a6a3a6 21def text(e):
dfff8bd5
MM
22 res = ""
23 for n in e.childNodes:
24 if n.nodeType != Element.TEXT_NODE:
25 log.panic("xml: text expected in <%s>, got %d" % (e.nodeName, n.nodeType))
26 res += n.nodeValue
27 return res
51a6a3a6 28
30dbf6a3 29def attr(e, a, default = None):
dfff8bd5
MM
30 try:
31 return e.attributes[a].value
32 except:
33 if default != None:
34 return default
35 raise
51a6a3a6 36
cf1741af 37def escape(s):
dfff8bd5 38 return xml.sax.saxutils.escape(s)
cf1741af 39
04ce7f54
ER
40# return timestamp with timezone information
41# so we could parse it in javascript
42def tzdate(t):
43 # as strftime %z is unofficial, and does not work, need to make it numeric ourselves
c0e5c8cc 44 date = time.strftime("%a %b %d %Y %H:%M:%S", time.localtime(t))
04ce7f54 45 # NOTE: the altzone is showing CURRENT timezone, not what the "t" reflects
6e2aab74 46 # NOTE: when DST is off timezone gets it right, altzone not
42c9621c
ER
47 if time.daylight:
48 tzoffset = time.altzone
49 else:
3f85c9bc 50 tzoffset = time.timezone
ba569b5d 51 tz = '%+05d' % (-tzoffset / 3600 * 100)
04ce7f54
ER
52 return date + ' ' + tz
53
d867e693
ER
54# return date in iso8601 format
55def iso8601(ts, timezone='UTC'):
56 tz = pytz.timezone(timezone)
57 dt = datetime.fromtimestamp(ts, tz)
58 return dt.isoformat()
59
51a6a3a6 60def is_blank(e):
dfff8bd5 61 return e.nodeType == Element.TEXT_NODE and string.strip(e.nodeValue) == ""
0d52c382 62
51a6a3a6 63class Group:
dfff8bd5
MM
64 def __init__(self, e):
65 self.batches = []
66 self.kind = 'group'
67 self.id = attr(e, "id")
68 self.no = int(attr(e, "no"))
69 self.priority = 2
70 self.time = time.time()
71 self.requester = ""
85303822 72 self.max_jobs = 0
dfff8bd5
MM
73 self.requester_email = ""
74 self.flags = string.split(attr(e, "flags", ""))
75 for c in e.childNodes:
76 if is_blank(c): continue
b051f064 77
dfff8bd5
MM
78 if c.nodeType != Element.ELEMENT_NODE:
79 log.panic("xml: evil group child %d" % c.nodeType)
80 if c.nodeName == "batch":
81 self.batches.append(Batch(c))
82 elif c.nodeName == "requester":
b4d7e94a 83 self.requester = text(c)
dfff8bd5
MM
84 self.requester_email = attr(c, "email", "")
85 elif c.nodeName == "priority":
b4d7e94a 86 self.priority = int(text(c))
dfff8bd5 87 elif c.nodeName == "time":
b4d7e94a 88 self.time = int(text(c))
b3c8d962 89 elif c.nodeName == "maxjobs":
b4d7e94a 90 self.max_jobs = int(text(c))
dfff8bd5
MM
91 else:
92 log.panic("xml: evil group child (%s)" % c.nodeName)
93 # note that we also check that group is sorted WRT deps
94 m = {}
95 for b in self.batches:
96 deps = []
97 m[b.b_id] = b
98 for dep in b.depends_on:
99 if m.has_key(dep):
100 # avoid self-deps
101 if id(m[dep]) != id(b):
102 deps.append(m[dep])
103 else:
104 log.panic("xml: dependency not found in group")
105 b.depends_on = deps
106 if self.requester_email == "" and self.requester != "":
107 self.requester_email = acl.user(self.requester).mail_to()
51a6a3a6 108
dfff8bd5
MM
109 def dump(self, f):
110 f.write("group: %d (id=%s pri=%d)\n" % (self.no, self.id, self.priority))
111 f.write(" from: %s\n" % self.requester)
112 f.write(" flags: %s\n" % string.join(self.flags))
113 f.write(" time: %s\n" % time.asctime(time.localtime(self.time)))
114 for b in self.batches:
115 b.dump(f)
116 f.write("\n")
cf1741af 117
dfff8bd5 118 def dump_html(self, f):
0d52c382 119 f.write(
b5ff0ca9 120 "<div id=\"%(no)d\" class=\"request %(flags)s\">\n"
d867e693
ER
121 "<a href=\"#%(no)d\">%(no)d</a>. "
122 "<time class=\"timeago\" datetime=\"%(datetime)s\">%(time)s</time> "
123 "from <b class=requester>%(requester)s</b> "
0d52c382
ER
124 "<small>%(id)s, prio=%(priority)d, jobs=%(max_jobs)d, %(flags)s</small>\n"
125 % {
51a8fa53 126 'no': self.no,
dac6c47c 127 'id': '<a href="srpms/%(id)s">%(id)s</a>' % {'id': self.id},
04ce7f54 128 'time': escape(tzdate(self.time)),
d867e693 129 'datetime': escape(iso8601(self.time)),
0d52c382
ER
130 'requester': escape(self.requester),
131 'priority': self.priority,
132 'max_jobs': self.max_jobs,
133 'flags': string.join(self.flags)
134 })
736f9b3a 135 f.write("<ol>\n")
dfff8bd5 136 for b in self.batches:
eda57100 137 b.dump_html(f, self.id)
736f9b3a 138 f.write("</ol>\n")
0d52c382 139 f.write("</div>\n")
0cdc1fef 140
dfff8bd5
MM
141 def write_to(self, f):
142 f.write("""
0638381b 143 <group id="%s" no="%d" flags="%s">
e2cad913 144 <requester email='%s'>%s</requester>
cf1741af 145 <time>%d</time>
b3c8d962
AM
146 <priority>%d</priority>
147 <maxjobs>%d</maxjobs>\n""" % (self.id, self.no, string.join(self.flags),
0d52c382 148 escape(self.requester_email), escape(self.requester),
b3c8d962 149 self.time, self.priority, self.max_jobs))
dfff8bd5
MM
150 for b in self.batches:
151 b.write_to(f)
152 f.write(" </group>\n\n")
51a6a3a6 153
dfff8bd5
MM
154 def is_done(self):
155 ok = 1
156 for b in self.batches:
157 if not b.is_done():
158 ok = 0
159 return ok
59ce7cd6 160
51a6a3a6 161class Batch:
dfff8bd5
MM
162 def __init__(self, e):
163 self.bconds_with = []
164 self.bconds_without = []
165 self.builders = []
166 self.builders_status = {}
1432ffd5 167 self.builders_status_time = {}
b4d7e94a 168 self.builders_status_buildtime = {}
aab485ec 169 self.kernel = ""
d6139a25 170 self.defines = {}
be264f26 171 self.target = []
dfff8bd5
MM
172 self.branch = ""
173 self.src_rpm = ""
174 self.info = ""
175 self.spec = ""
176 self.command = ""
177 self.command_flags = []
db286098 178 self.skip = []
dfff8bd5
MM
179 self.gb_id = ""
180 self.b_id = attr(e, "id")
181 self.depends_on = string.split(attr(e, "depends-on"))
be44c85d 182 self.upgraded = True
266325ca
ER
183
184 self.parse_xml(e)
185
a73fab54 186 self._topdir = '/tmp/B.%s' % self.b_id
266325ca
ER
187
188 def parse_xml(self, e):
dfff8bd5
MM
189 for c in e.childNodes:
190 if is_blank(c): continue
b4d7e94a 191
dfff8bd5
MM
192 if c.nodeType != Element.ELEMENT_NODE:
193 log.panic("xml: evil batch child %d" % c.nodeType)
194 if c.nodeName == "src-rpm":
b4d7e94a 195 self.src_rpm = text(c)
dfff8bd5 196 elif c.nodeName == "spec":
8ecc8666
ER
197 # normalize specname, specname is used as buildlog and we don't
198 # want to be exposed to directory traversal attacks
b4d7e94a 199 self.spec = text(c).split('/')[-1]
dfff8bd5
MM
200 elif c.nodeName == "command":
201 self.spec = "COMMAND"
b4d7e94a 202 self.command = text(c).strip()
dfff8bd5
MM
203 self.command_flags = string.split(attr(c, "flags", ""))
204 elif c.nodeName == "info":
b4d7e94a 205 self.info = text(c)
aab485ec 206 elif c.nodeName == "kernel":
b4d7e94a 207 self.kernel = text(c)
d6139a25
ER
208 elif c.nodeName == "define":
209 define = attr(c, "name")
210 self.defines[define] = text(c)
be264f26 211 elif c.nodeName == "target":
b4d7e94a 212 self.target.append(text(c))
db286098 213 elif c.nodeName == "skip":
b4d7e94a 214 self.skip.append(text(c))
dfff8bd5 215 elif c.nodeName == "branch":
b4d7e94a 216 self.branch = text(c)
dfff8bd5 217 elif c.nodeName == "builder":
b4d7e94a 218 key = text(c)
b051f064
ER
219 self.builders.append(key)
220 self.builders_status[key] = attr(c, "status", "?")
221 self.builders_status_time[key] = attr(c, "time", "0")
b4d7e94a 222 self.builders_status_buildtime[key] = "0" #attr(c, "buildtime", "0")
dfff8bd5 223 elif c.nodeName == "with":
b4d7e94a 224 self.bconds_with.append(text(c))
dfff8bd5 225 elif c.nodeName == "without":
b4d7e94a 226 self.bconds_without.append(text(c))
dfff8bd5
MM
227 else:
228 log.panic("xml: evil batch child (%s)" % c.nodeName)
0d52c382 229
266325ca
ER
230 def get_package_name(self):
231 if len(self.spec) <= 5:
232 return None
233 return self.spec[:-5]
234
235 def tmpdir(self):
236 """
237 return tmpdir for this batch job building
238 """
239 # it's better to have TMPDIR and BUILD dir on same partition:
240 # + /usr/bin/bzip2 -dc /home/services/builder/rpm/packages/kernel/patch-2.6.27.61.bz2
241 # patch: **** Can't rename file /tmp/B.a1b1d3/poKWwRlp to drivers/scsi/hosts.c : No such file or directory
242 path = os.path.join(self._topdir, 'BUILD', 'tmp')
243 return path
244
dfff8bd5
MM
245 def is_done(self):
246 ok = 1
247 for b in self.builders:
248 s = self.builders_status[b]
61af514e 249 if not s.startswith("OK") and not s.startswith("SKIP") and not s.startswith("UNSUPP") and not s.startswith("FAIL"):
dfff8bd5
MM
250 ok = 0
251 return ok
0d52c382 252
dfff8bd5
MM
253 def dump(self, f):
254 f.write(" batch: %s/%s\n" % (self.src_rpm, self.spec))
255 f.write(" info: %s\n" % self.info)
aab485ec 256 f.write(" kernel: %s\n" % self.kernel)
d6139a25 257 f.write(" defines: %s\n" % self.defines_string())
be264f26 258 f.write(" target: %s\n" % self.target_string())
dfff8bd5
MM
259 f.write(" branch: %s\n" % self.branch)
260 f.write(" bconds: %s\n" % self.bconds_string())
261 builders = []
262 for b in self.builders:
263 builders.append("%s:%s" % (b, self.builders_status[b]))
264 f.write(" builders: %s\n" % string.join(builders))
51a6a3a6 265
dfff8bd5
MM
266 def is_command(self):
267 return self.command != ""
6953ce5d 268
eda57100 269 def dump_html(self, f, rid):
dfff8bd5 270 f.write("<li>\n")
bd080018 271 if self.is_command():
0c2e0afa 272 desc = "SH: <pre>%s</pre> flags: [%s]" % (self.command, ' '.join(self.command_flags))
dfff8bd5 273 else:
64c5a6cb 274 package_url = "http://git.pld-linux.org/gitweb.cgi?p=packages/%(package)s.git;f=%(spec)s;h=%(branch)s;a=shortlog" % {
c3347125
KK
275 'spec': urllib.quote(self.spec),
276 'branch': urllib.quote(self.branch),
277 'package': urllib.quote(self.spec[:-5]),
59b3448a 278 }
d6139a25 279 desc = "%(src_rpm)s (<a href=\"%(package_url)s\">%(spec)s -r %(branch)s</a>%(rpmopts)s)" % {
59b3448a
ER
280 'src_rpm': self.src_rpm,
281 'spec': self.spec,
282 'branch': self.branch,
d6139a25 283 'rpmopts': self.bconds_string() + self.kernel_string() + self.target_string() + self.defines_string(),
59b3448a
ER
284 'package_url': package_url,
285 }
dfff8bd5
MM
286 f.write("%s <small>[" % desc)
287 builders = []
dfff8bd5
MM
288 for b in self.builders:
289 s = self.builders_status[b]
2fec103c 290 if s.startswith("OK"):
dfff8bd5 291 c = "green"
2b9e7381 292 elif s.startswith("FAIL"):
dfff8bd5 293 c = "red"
2fec103c 294 elif s.startswith("SKIP"):
dfff8bd5 295 c = "blue"
2fec103c 296 elif s.startswith("UNSUPP"):
769856bc 297 c = "fuchsia"
dfff8bd5
MM
298 else:
299 c = "black"
300 link_pre = ""
301 link_post = ""
61af514e 302 if (s.startswith("OK") or s.startswith("SKIP") or s.startswith("UNSUPP") or s.startswith("FAIL")) and len(self.spec) > 5:
dfff8bd5
MM
303 if self.is_command():
304 bl_name = "command"
305 else:
306 bl_name = self.spec[:len(self.spec)-5]
7169155e 307 lin_ar = b.replace('noauto-','')
eda57100 308 path = "/%s/%s/%s,%s.bz2" % (lin_ar.replace('-','/'), s, bl_name, rid)
dfff8bd5 309 is_ok = 0
2fec103c 310 if s.startswith("OK"):
428b18a2 311 is_ok = 1
7169155e 312 bld = lin_ar.split('-')
e40ffefe 313 tree_name = '-'.join(bld[:-1])
7edf20a9 314 tree_arch = '-'.join(bld[-1:])
7a8e28a8
JR
315 link_pre = "<a href=\"%s/index.php?dist=%s&arch=%s&ok=%d&name=%s&id=%s&action=tail\">" \
316 % (config.buildlogs, urllib.quote(tree_name), urllib.quote(tree_arch), is_ok, urllib.quote(bl_name), urllib.quote(rid))
dfff8bd5 317 link_post = "</a>"
b4d7e94a
ER
318
319 def ftime(s):
320 t = float(s)
321 if t > 0:
322 return time.asctime(time.localtime(t))
323 else:
324 return 'N/A'
325
326 tooltip = "last update: %(time)s\nbuild time: %(buildtime)s" % {
327 'time' : ftime(self.builders_status_time[b]),
328 'buildtime' : ftime(self.builders_status_buildtime[b]),
7d73f518
ER
329 }
330 builders.append(link_pre +
331 "<font color='%(color)s'><b title=\"%(tooltip)s\">%(builder)s:%(status)s</b></font>" % {
332 'color' : c,
333 'builder' : b,
334 'status' : s,
335 'tooltip' : cgi.escape(tooltip, True),
336 }
337 + link_post)
dfff8bd5 338 f.write("%s]</small></li>\n" % string.join(builders))
0cdc1fef 339
4e14a9bc
ER
340 def rpmbuild_opts(self):
341 """
342 return all rpmbuild options related to this build
343 """
5c4d43c0 344 rpmopts = self.bconds_string() + self.kernel_string() + self.target_string() + self.defines_string()
4e14a9bc 345 rpmdefs = \
266325ca
ER
346 "--define '_topdir %s' " % self._topdir + \
347 "--define '_specdir %{_topdir}' " \
4e14a9bc 348 "--define '_sourcedir %{_specdir}' " \
266325ca 349 "--define '_rpmdir %{_topdir}/RPMS' " \
b9b4ac17 350 "--define '_builddir %{_topdir}/BUILD' "
d6139a25 351 return rpmdefs + rpmopts
4e14a9bc 352
a306e34b
ER
353 def php_ignores(self):
354 # transform php package name (52) to version (5.2)
355 def php_name_to_ver(v):
356 return '.'.join(list(v))
357
358 # transform php version (5.2) to package name (52)
359 def php_ver_to_name(v):
360 return v.replace('.', '')
361
362 # available php versions in distro
d270eb1f 363 php_versions = ['4', '5.2', '5.3', '5.4', '5.5', '5.6', '7.0']
a306e34b
ER
364
365 # current version if -D php_suffix is present
b7ce0d9c 366 php_version = php_name_to_ver(self.defines['php_suffix'])
a306e34b
ER
367
368 # remove current php version
56c8c449
ER
369 try:
370 php_versions.remove(php_version)
371 except ValueError:
372 log.notice("Attempt to remove inexistent key '%s' from %s" % (php_version, php_versions))
373 pass
a306e34b
ER
374
375 # map them to poldek ignores
376 # always ignore hhvm
377 res = ['hhvm']
378 for v in map(php_ver_to_name, php_versions):
379 res.append("php%s-*" % v)
380
381 return res
382
383 # build ignore package list
384 # currently only php ignore is filled based on build context
b37bd372 385 def ignores(self):
a306e34b
ER
386 ignores = []
387
388 # add php version based ignores
389 if self.defines.has_key('php_suffix'):
390 ignores.extend(self.php_ignores())
391
392 # return empty string if the list is empty
393 if len(ignores) == 0:
394 return ""
395
396 def add_ignore(s):
397 return "--ignore=%s" % s
398
399 return " ".join(map(add_ignore, ignores))
400
aab485ec 401 def kernel_string(self):
402 r = ""
403 if self.kernel != "":
404 r = " --define 'alt_kernel " + self.kernel + "'"
405 return r
406
be264f26
ER
407 def target_string(self):
408 if len(self.target) > 0:
409 return " --target " + ",".join(self.target)
410 else:
411 return ""
412
dfff8bd5
MM
413 def bconds_string(self):
414 r = ""
415 for b in self.bconds_with:
416 r = r + " --with " + b
417 for b in self.bconds_without:
418 r = r + " --without " + b
419 return r
0d52c382 420
d6139a25
ER
421 def defines_string(self):
422 r = ""
423 for key,value in self.defines.items():
424 r += " --define '%s %s'" % (key, value)
425 return r
426
427 def defines_xml(self):
428 r = ""
429 for key,value in self.defines.items():
430 r += "<define name='%s'>%s</define>\n" % (escape(key), escape(value))
431 return r
432
4e14a9bc
ER
433 def default_target(self, arch):
434 self.target.append("%s-pld-linux" % arch)
435
dfff8bd5
MM
436 def write_to(self, f):
437 f.write("""
30dbf6a3 438 <batch id='%s' depends-on='%s'>
cf1741af 439 <src-rpm>%s</src-rpm>
6953ce5d 440 <command flags="%s">%s</command>
cf1741af 441 <spec>%s</spec>
57b6e61d 442 <branch>%s</branch>
0d52c382 443 <info>%s</info>\n""" % (self.b_id,
dfff8bd5 444 string.join(map(lambda (b): b.b_id, self.depends_on)),
0d52c382 445 escape(self.src_rpm),
dfff8bd5 446 escape(' '.join(self.command_flags)), escape(self.command),
655fbfb2 447 escape(self.spec), escape(self.branch), escape(self.info)))
448 if self.kernel != "":
98ff6b42 449 f.write(" <kernel>%s</kernel>\n" % escape(self.kernel))
dfff8bd5
MM
450 for b in self.bconds_with:
451 f.write(" <with>%s</with>\n" % escape(b))
be264f26
ER
452 for b in self.target:
453 f.write(" <target>%s</target>\n" % escape(b))
dfff8bd5
MM
454 for b in self.bconds_without:
455 f.write(" <without>%s</without>\n" % escape(b))
d6139a25
ER
456 if self.defines:
457 f.write(" %s\n" % self.defines_xml())
dfff8bd5 458 for b in self.builders:
b4d7e94a
ER
459 if self.builders_status_buildtime.has_key(b):
460 t = self.builders_status_buildtime[b]
461 else:
462 t = "0"
463 f.write(" <builder status='%s' time='%s' buildtime='%s'>%s</builder>\n" % \
464 (escape(self.builders_status[b]), self.builders_status_time[b], t, escape(b)))
dfff8bd5 465 f.write(" </batch>\n")
0d52c382 466
dfff8bd5
MM
467 def log_line(self, l):
468 log.notice(l)
469 if self.logfile != None:
470 util.append_to(self.logfile, l)
cf1741af 471
dfff8bd5
MM
472 def expand_builders(batch, all_builders):
473 all = []
474 for bld in batch.builders:
475 res = []
476 for my_bld in all_builders:
477 if fnmatch.fnmatch(my_bld, bld):
478 res.append(my_bld)
479 if res != []:
480 all.extend(res)
481 else:
482 all.append(bld)
483 batch.builders = all
1089bec4 484
59ce7cd6 485class Notification:
dfff8bd5
MM
486 def __init__(self, e):
487 self.batches = []
488 self.kind = 'notification'
489 self.group_id = attr(e, "group-id")
490 self.builder = attr(e, "builder")
491 self.batches = {}
b4d7e94a 492 self.batches_buildtime = {}
dfff8bd5
MM
493 for c in e.childNodes:
494 if is_blank(c): continue
495 if c.nodeType != Element.ELEMENT_NODE:
496 log.panic("xml: evil notification child %d" % c.nodeType)
497 if c.nodeName == "batch":
498 id = attr(c, "id")
499 status = attr(c, "status")
b4d7e94a 500 buildtime = attr(c, "buildtime", "0")
2fec103c 501 if not status.startswith("OK") and not status.startswith("SKIP") and not status.startswith("UNSUPP") and not status.startswith("FAIL"):
b6c5ef3e 502 log.panic("xml notification: bad status: %s" % status)
dfff8bd5 503 self.batches[id] = status
b4d7e94a 504 self.batches_buildtime[id] = buildtime
dfff8bd5
MM
505 else:
506 log.panic("xml: evil notification child (%s)" % c.nodeName)
59ce7cd6 507
dfff8bd5
MM
508 def apply_to(self, q):
509 for r in q.requests:
510 if r.kind == "group":
511 for b in r.batches:
512 if self.batches.has_key(b.b_id):
513 b.builders_status[self.builder] = self.batches[b.b_id]
1432ffd5 514 b.builders_status_time[self.builder] = time.time()
b4d7e94a 515 b.builders_status_buildtime[self.builder] = "0" #self.batches_buildtime[b.b_id]
59ce7cd6 516
51a6a3a6 517def build_request(e):
dfff8bd5
MM
518 if e.nodeType != Element.ELEMENT_NODE:
519 log.panic("xml: evil request element")
520 if e.nodeName == "group":
521 return Group(e)
522 elif e.nodeName == "notification":
523 return Notification(e)
524 elif e.nodeName == "command":
525 # FIXME
526 return Command(e)
527 else:
9ef8e784 528 log.panic("xml: evil request [%s]" % e.nodeName)
51a6a3a6 529
94169186 530def parse_request(f):
5180bf1f 531 d = parseString(f)
dfff8bd5 532 return build_request(d.documentElement)
0d52c382 533
94169186 534def parse_requests(f):
5180bf1f 535 d = parseString(f)
dfff8bd5
MM
536 res = []
537 for r in d.documentElement.childNodes:
538 if is_blank(r): continue
539 res.append(build_request(r))
540 return res
This page took 1.267929 seconds and 4 git commands to generate.