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