# vi: encoding=utf-8 ts=8 sts=4 sw=4 et from xml.dom.minidom import * import string import time import xml.sax.saxutils import fnmatch import os import urllib import cgi import util import log from acl import acl from config import config __all__ = ['parse_request', 'parse_requests'] def text(e): res = "" for n in e.childNodes: if n.nodeType != Element.TEXT_NODE: log.panic("xml: text expected in <%s>, got %d" % (e.nodeName, n.nodeType)) res += n.nodeValue return res def attr(e, a, default = None): try: return e.attributes[a].value except: if default != None: return default raise def escape(s): return xml.sax.saxutils.escape(s) # return timestamp with timezone information # so we could parse it in javascript def tzdate(t): # as strftime %z is unofficial, and does not work, need to make it numeric ourselves # date = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(t)) date = time.strftime("%a %b %d %Y %H:%M:%S", time.localtime(t)) # NOTE: the altzone is showing CURRENT timezone, not what the "t" reflects # NOTE: when DST is off timezone gets it right, altzone not if time.daylight: tzoffset = time.altzone else: tzoffset = time.timezone tz = '%+05d' % (-tzoffset / 3600 * 100) return date + ' ' + tz def is_blank(e): return e.nodeType == Element.TEXT_NODE and string.strip(e.nodeValue) == "" class Group: def __init__(self, e): self.batches = [] self.kind = 'group' self.id = attr(e, "id") self.no = int(attr(e, "no")) self.priority = 2 self.time = time.time() self.requester = "" self.max_jobs = 0 self.requester_email = "" self.flags = string.split(attr(e, "flags", "")) for c in e.childNodes: if is_blank(c): continue if c.nodeType != Element.ELEMENT_NODE: log.panic("xml: evil group child %d" % c.nodeType) if c.nodeName == "batch": self.batches.append(Batch(c)) elif c.nodeName == "requester": self.requester = text(c) self.requester_email = attr(c, "email", "") elif c.nodeName == "priority": self.priority = int(text(c)) elif c.nodeName == "time": self.time = int(text(c)) elif c.nodeName == "maxjobs": self.max_jobs = int(text(c)) else: log.panic("xml: evil group child (%s)" % c.nodeName) # note that we also check that group is sorted WRT deps m = {} for b in self.batches: deps = [] m[b.b_id] = b for dep in b.depends_on: if m.has_key(dep): # avoid self-deps if id(m[dep]) != id(b): deps.append(m[dep]) else: log.panic("xml: dependency not found in group") b.depends_on = deps if self.requester_email == "" and self.requester != "": self.requester_email = acl.user(self.requester).mail_to() def dump(self, f): f.write("group: %d (id=%s pri=%d)\n" % (self.no, self.id, self.priority)) f.write(" from: %s\n" % self.requester) f.write(" flags: %s\n" % string.join(self.flags)) f.write(" time: %s\n" % time.asctime(time.localtime(self.time))) for b in self.batches: b.dump(f) f.write("\n") def dump_html(self, f): f.write( "
\n" "%(no)d. %(time)s from %(requester)s " "%(id)s, prio=%(priority)d, jobs=%(max_jobs)d, %(flags)s\n" % { 'no': self.no, 'id': '%(id)s' % {'id': self.id}, 'time': escape(tzdate(self.time)), 'requester': escape(self.requester), 'priority': self.priority, 'max_jobs': self.max_jobs, 'flags': string.join(self.flags) }) f.write("\n") f.write("
\n") def write_to(self, f): f.write(""" %s %d %d\n""" % (self.id, self.no, string.join(self.flags), escape(self.requester_email), escape(self.requester), self.time, self.priority, self.max_jobs)) for b in self.batches: b.write_to(f) f.write(" \n\n") def is_done(self): ok = 1 for b in self.batches: if not b.is_done(): ok = 0 return ok class Batch: def __init__(self, e): self.bconds_with = [] self.bconds_without = [] self.builders = [] self.builders_status = {} self.builders_status_time = {} self.builders_status_buildtime = {} self.kernel = "" self.target = [] self.branch = "" self.src_rpm = "" self.info = "" self.spec = "" self.command = "" self.command_flags = [] self.skip = [] self.gb_id = "" self.b_id = attr(e, "id") self.depends_on = string.split(attr(e, "depends-on")) self.upgraded = True for c in e.childNodes: if is_blank(c): continue if c.nodeType != Element.ELEMENT_NODE: log.panic("xml: evil batch child %d" % c.nodeType) if c.nodeName == "src-rpm": self.src_rpm = text(c) elif c.nodeName == "spec": # normalize specname, specname is used as buildlog and we don't # want to be exposed to directory traversal attacks self.spec = text(c).split('/')[-1] elif c.nodeName == "command": self.spec = "COMMAND" self.command = text(c).strip() self.command_flags = string.split(attr(c, "flags", "")) elif c.nodeName == "info": self.info = text(c) elif c.nodeName == "kernel": self.kernel = text(c) elif c.nodeName == "target": self.target.append(text(c)) elif c.nodeName == "skip": self.skip.append(text(c)) elif c.nodeName == "branch": self.branch = text(c) elif c.nodeName == "builder": key = text(c) self.builders.append(key) self.builders_status[key] = attr(c, "status", "?") self.builders_status_time[key] = attr(c, "time", "0") self.builders_status_buildtime[key] = "0" #attr(c, "buildtime", "0") elif c.nodeName == "with": self.bconds_with.append(text(c)) elif c.nodeName == "without": self.bconds_without.append(text(c)) else: log.panic("xml: evil batch child (%s)" % c.nodeName) def is_done(self): ok = 1 for b in self.builders: s = self.builders_status[b] if not s.startswith("OK") and not s.startswith("SKIP") and not s.startswith("UNSUPP") and not s.startswith("FAIL"): ok = 0 return ok def dump(self, f): f.write(" batch: %s/%s\n" % (self.src_rpm, self.spec)) f.write(" info: %s\n" % self.info) f.write(" kernel: %s\n" % self.kernel) f.write(" target: %s\n" % self.target_string()) f.write(" branch: %s\n" % self.branch) f.write(" bconds: %s\n" % self.bconds_string()) builders = [] for b in self.builders: builders.append("%s:%s" % (b, self.builders_status[b])) f.write(" builders: %s\n" % string.join(builders)) def is_command(self): return self.command != "" def dump_html(self, f, rid): f.write("
  • \n") if self.is_command(): desc = "SH:
    %s
    flags: [%s]" % (self.command, ' '.join(self.command_flags)) else: package_url = "http://cvs.pld-linux.org/packages/%(package)s/%(spec)s?only_with_tag=%(branch)s" % { 'spec': self.spec, 'branch': self.branch, 'package': self.spec[:-5], } desc = "%(src_rpm)s (%(spec)s -r %(branch)s%(bconds)s)" % { 'src_rpm': self.src_rpm, 'spec': self.spec, 'branch': self.branch, 'bconds': self.bconds_string() + self.kernel_string() + self.target_string(), 'package_url': package_url, } f.write("%s [" % desc) builders = [] for b in self.builders: s = self.builders_status[b] if s.startswith("OK"): c = "green" elif s.startswith("FAIL"): c = "red" elif s.startswith("SKIP"): c = "blue" elif s.startswith("UNSUPP"): c = "fuchsia" else: c = "black" link_pre = "" link_post = "" if (s.startswith("OK") or s.startswith("SKIP") or s.startswith("UNSUPP") or s.startswith("FAIL")) and len(self.spec) > 5: if self.is_command(): bl_name = "command" else: bl_name = self.spec[:len(self.spec)-5] lin_ar = b.replace('noauto-','') path = "/%s/%s/%s,%s.bz2" % (lin_ar.replace('-','/'), s, bl_name, rid) is_ok = 0 if s.startswith("OK"): is_ok = 1 bld = lin_ar.split('-') tree_name = '-'.join(bld[:-1]) tree_arch = '-'.join(bld[-1:]) link_pre = "" \ % (urllib.quote(tree_name), urllib.quote(tree_arch), is_ok, urllib.quote(bl_name), urllib.quote(rid)) link_post = "" def ftime(s): t = float(s) if t > 0: return time.asctime(time.localtime(t)) else: return 'N/A' tooltip = "last update: %(time)s\nbuild time: %(buildtime)s" % { 'time' : ftime(self.builders_status_time[b]), 'buildtime' : ftime(self.builders_status_buildtime[b]), } builders.append(link_pre + "%(builder)s:%(status)s" % { 'color' : c, 'builder' : b, 'status' : s, 'tooltip' : cgi.escape(tooltip, True), } + link_post) f.write("%s]
  • \n" % string.join(builders)) def rpmbuild_opts(self): """ return all rpmbuild options related to this build """ bconds = self.bconds_string() + self.kernel_string() + self.target_string() rpmdefs = \ "--define '_topdir %(echo $HOME/rpm)' " \ "--define '_specdir %{_topdir}/packages/%{name}' " \ "--define '_sourcedir %{_specdir}' " \ "--define '_builddir %{_topdir}/BUILD/%{name}' " return rpmdefs + bconds def kernel_string(self): r = "" if self.kernel != "": r = " --define 'alt_kernel " + self.kernel + "'" return r def target_string(self): if len(self.target) > 0: return " --target " + ",".join(self.target) else: return "" def bconds_string(self): r = "" for b in self.bconds_with: r = r + " --with " + b for b in self.bconds_without: r = r + " --without " + b return r def default_target(self, arch): self.target.append("%s-pld-linux" % arch) def write_to(self, f): f.write(""" %s %s %s %s %s\n""" % (self.b_id, string.join(map(lambda (b): b.b_id, self.depends_on)), escape(self.src_rpm), escape(' '.join(self.command_flags)), escape(self.command), escape(self.spec), escape(self.branch), escape(self.info))) if self.kernel != "": f.write(" %s\n" % escape(self.kernel)) for b in self.bconds_with: f.write(" %s\n" % escape(b)) for b in self.target: f.write(" %s\n" % escape(b)) for b in self.bconds_without: f.write(" %s\n" % escape(b)) for b in self.builders: if self.builders_status_buildtime.has_key(b): t = self.builders_status_buildtime[b] else: t = "0" f.write(" %s\n" % \ (escape(self.builders_status[b]), self.builders_status_time[b], t, escape(b))) f.write(" \n") def log_line(self, l): log.notice(l) if self.logfile != None: util.append_to(self.logfile, l) def expand_builders(batch, all_builders): all = [] for bld in batch.builders: res = [] for my_bld in all_builders: if fnmatch.fnmatch(my_bld, bld): res.append(my_bld) if res != []: all.extend(res) else: all.append(bld) batch.builders = all class Notification: def __init__(self, e): self.batches = [] self.kind = 'notification' self.group_id = attr(e, "group-id") self.builder = attr(e, "builder") self.batches = {} self.batches_buildtime = {} for c in e.childNodes: if is_blank(c): continue if c.nodeType != Element.ELEMENT_NODE: log.panic("xml: evil notification child %d" % c.nodeType) if c.nodeName == "batch": id = attr(c, "id") status = attr(c, "status") buildtime = attr(c, "buildtime", "0") if not status.startswith("OK") and not status.startswith("SKIP") and not status.startswith("UNSUPP") and not status.startswith("FAIL"): log.panic("xml notification: bad status: %s" % status) self.batches[id] = status self.batches_buildtime[id] = buildtime else: log.panic("xml: evil notification child (%s)" % c.nodeName) def apply_to(self, q): for r in q.requests: if r.kind == "group": for b in r.batches: if self.batches.has_key(b.b_id): b.builders_status[self.builder] = self.batches[b.b_id] b.builders_status_time[self.builder] = time.time() b.builders_status_buildtime[self.builder] = "0" #self.batches_buildtime[b.b_id] def build_request(e): if e.nodeType != Element.ELEMENT_NODE: log.panic("xml: evil request element") if e.nodeName == "group": return Group(e) elif e.nodeName == "notification": return Notification(e) elif e.nodeName == "command": # FIXME return Command(e) else: log.panic("xml: evil request [%s]" % e.nodeName) def parse_request(f): d = parseString(f) return build_request(d.documentElement) def parse_requests(f): d = parseString(f) res = [] for r in d.documentElement.childNodes: if is_blank(r): continue res.append(build_request(r)) return res