diff options
Diffstat (limited to 'lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/words.py')
-rw-r--r-- | lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/words.py | 1097 |
1 files changed, 0 insertions, 1097 deletions
diff --git a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/words.py b/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/words.py deleted file mode 100644 index 32565ac6..00000000 --- a/lib/python2.7/site-packages/buildbot-0.8.8-py2.7.egg/buildbot/status/words.py +++ /dev/null @@ -1,1097 +0,0 @@ -# This file is part of Buildbot. Buildbot is free software: you can -# redistribute it and/or modify it under the terms of the GNU General Public -# License as published by the Free Software Foundation, version 2. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# this program; if not, write to the Free Software Foundation, Inc., 51 -# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Copyright Buildbot Team Members - -import re, shlex, random -from string import join, capitalize, lower - -from zope.interface import implements -from twisted.internet import protocol, reactor -from twisted.words.protocols import irc -from twisted.python import usage, log -from twisted.application import internet -from twisted.internet import defer, task - -from buildbot import config, interfaces, util -from buildbot import version -from buildbot.interfaces import IStatusReceiver -from buildbot.sourcestamp import SourceStamp -from buildbot.status import base -from buildbot.status.results import SUCCESS, WARNINGS, FAILURE, EXCEPTION, RETRY -from buildbot.process.properties import Properties - -# twisted.internet.ssl requires PyOpenSSL, so be resilient if it's missing -try: - from twisted.internet import ssl - have_ssl = True -except ImportError: - have_ssl = False - -def maybeColorize(text, color, useColors): - irc_colors = [ - 'WHITE', - 'BLACK', - 'NAVY_BLUE', - 'GREEN', - 'RED', - 'BROWN', - 'PURPLE', - 'OLIVE', - 'YELLOW', - 'LIME_GREEN', - 'TEAL', - 'AQUA_LIGHT', - 'ROYAL_BLUE', - 'HOT_PINK', - 'DARK_GRAY', - 'LIGHT_GRAY' - ] - - if useColors: - return "%c%d%s%c" % (3, irc_colors.index(color), text, 3) - else: - return text - -class UsageError(ValueError): - def __init__(self, string = "Invalid usage", *more): - ValueError.__init__(self, string, *more) - -class ForceOptions(usage.Options): - optParameters = [ - ["builder", None, None, "which Builder to start"], - ["branch", None, None, "which branch to build"], - ["revision", None, None, "which revision to build"], - ["reason", None, None, "the reason for starting the build"], - ["props", None, None, - "A set of properties made available in the build environment, " - "format is --properties=prop1=value1,prop2=value2,.. " - "option can be specified multiple times."], - ] - - def parseArgs(self, *args): - args = list(args) - if len(args) > 0: - if self['builder'] is not None: - raise UsageError("--builder provided in two ways") - self['builder'] = args.pop(0) - if len(args) > 0: - if self['reason'] is not None: - raise UsageError("--reason provided in two ways") - self['reason'] = " ".join(args) - - -class IrcBuildRequest: - hasStarted = False - timer = None - - def __init__(self, parent, useRevisions=False, useColors=True): - self.parent = parent - self.useRevisions = useRevisions - self.useColors = useColors - self.timer = reactor.callLater(5, self.soon) - - def soon(self): - del self.timer - if not self.hasStarted: - self.parent.send("The build has been queued, I'll give a shout" - " when it starts") - - def started(self, s): - self.hasStarted = True - if self.timer: - self.timer.cancel() - del self.timer - eta = s.getETA() - if self.useRevisions: - response = "build containing revision(s) [%s] forced" % s.getRevisions() - else: - response = "build #%d forced" % s.getNumber() - if eta is not None: - response = "build forced [ETA %s]" % self.parent.convertTime(eta) - self.parent.send(response) - self.parent.send("I'll give a shout when the build finishes") - d = s.waitUntilFinished() - d.addCallback(self.parent.watchedBuildFinished) - -class IRCContact(base.StatusReceiver): - implements(IStatusReceiver) - """I hold the state for a single user's interaction with the buildbot. - - There will be one instance of me for each user who interacts personally - with the buildbot. There will be an additional instance for each - 'broadcast contact' (chat rooms, IRC channels as a whole). - """ - - def __init__(self, bot, dest): - self.bot = bot - self.master = bot.master - self.notify_events = {} - self.subscribed = 0 - self.muted = False - self.useRevisions = bot.useRevisions - self.useColors = bot.useColors - self.reported_builds = [] # tuples (when, buildername, buildnum) - self.add_notification_events(bot.notify_events) - - # when people send us public messages ("buildbot: command"), - # self.dest is the name of the channel ("#twisted"). When they send - # us private messages (/msg buildbot command), self.dest is their - # username. - self.dest = dest - - # silliness - - silly = { - "What happen ?": [ "Somebody set up us the bomb." ], - "It's You !!": ["How are you gentlemen !!", - "All your base are belong to us.", - "You are on the way to destruction."], - "What you say !!": ["You have no chance to survive make your time.", - "HA HA HA HA ...."], - } - - def doSilly(self, message): - response = self.silly[message] - when = 0.5 - for r in response: - reactor.callLater(when, self.send, r) - when += 2.5 - - def getBuilder(self, which): - try: - b = self.bot.status.getBuilder(which) - except KeyError: - raise UsageError, "no such builder '%s'" % which - return b - - def getControl(self, which): - if not self.bot.control: - raise UsageError("builder control is not enabled") - try: - bc = self.bot.control.getBuilder(which) - except KeyError: - raise UsageError("no such builder '%s'" % which) - return bc - - def getAllBuilders(self): - """ - @rtype: list of L{buildbot.process.builder.Builder} - """ - names = self.bot.status.getBuilderNames(categories=self.bot.categories) - names.sort() - builders = [self.bot.status.getBuilder(n) for n in names] - return builders - - def convertTime(self, seconds): - if seconds < 60: - return "%d seconds" % seconds - minutes = int(seconds / 60) - seconds = seconds - 60*minutes - if minutes < 60: - return "%dm%02ds" % (minutes, seconds) - hours = int(minutes / 60) - minutes = minutes - 60*hours - return "%dh%02dm%02ds" % (hours, minutes, seconds) - - def reportBuild(self, builder, buildnum): - """Returns True if this build should be reported for this contact - (eliminating duplicates), and also records the report for later""" - for w, b, n in self.reported_builds: - if b == builder and n == buildnum: - return False - self.reported_builds.append([util.now(), builder, buildnum]) - - # clean the reported builds - horizon = util.now() - 60 - while self.reported_builds and self.reported_builds[0][0] < horizon: - self.reported_builds.pop(0) - - # and return True, since this is a new one - return True - - def splitArgs(self, args): - """Returns list of arguments parsed by shlex.split() or - raise UsageError if failed""" - try: - return shlex.split(args) - except ValueError, e: - raise UsageError(e) - - def command_HELLO(self, args, who): - self.send("yes?") - - def command_VERSION(self, args, who): - self.send("buildbot-%s at your service" % version) - - def command_LIST(self, args, who): - args = self.splitArgs(args) - if len(args) == 0: - raise UsageError, "try 'list builders'" - if args[0] == 'builders': - builders = self.getAllBuilders() - str = "Configured builders: " - for b in builders: - str += b.name - state = b.getState()[0] - if state == 'offline': - str += "[offline]" - str += " " - str.rstrip() - self.send(str) - return - command_LIST.usage = "list builders - List configured builders" - - def command_STATUS(self, args, who): - args = self.splitArgs(args) - if len(args) == 0: - which = "all" - elif len(args) == 1: - which = args[0] - else: - raise UsageError, "try 'status <builder>'" - if which == "all": - builders = self.getAllBuilders() - for b in builders: - self.emit_status(b.name) - return - self.emit_status(which) - command_STATUS.usage = "status [<which>] - List status of a builder (or all builders)" - - def validate_notification_event(self, event): - if not re.compile("^(started|finished|success|failure|exception|warnings|(success|warnings|exception|failure)To(Failure|Success|Warnings|Exception))$").match(event): - raise UsageError("try 'notify on|off <EVENT>'") - - def list_notified_events(self): - self.send( "The following events are being notified: %r" % self.notify_events.keys() ) - - def notify_for(self, *events): - for event in events: - if self.notify_events.has_key(event): - return 1 - return 0 - - def subscribe_to_build_events(self): - self.bot.status.subscribe(self) - self.subscribed = 1 - - def unsubscribe_from_build_events(self): - self.bot.status.unsubscribe(self) - self.subscribed = 0 - - def add_notification_events(self, events): - for event in events: - self.validate_notification_event(event) - self.notify_events[event] = 1 - - if not self.subscribed: - self.subscribe_to_build_events() - - def remove_notification_events(self, events): - for event in events: - self.validate_notification_event(event) - del self.notify_events[event] - - if len(self.notify_events) == 0 and self.subscribed: - self.unsubscribe_from_build_events() - - def remove_all_notification_events(self): - self.notify_events = {} - - if self.subscribed: - self.unsubscribe_from_build_events() - - def command_NOTIFY(self, args, who): - args = self.splitArgs(args) - - if not args: - raise UsageError("try 'notify on|off|list <EVENT>'") - action = args.pop(0) - events = args - - if action == "on": - if not events: events = ('started','finished') - self.add_notification_events(events) - - self.list_notified_events() - - elif action == "off": - if events: - self.remove_notification_events(events) - else: - self.remove_all_notification_events() - - self.list_notified_events() - - elif action == "list": - self.list_notified_events() - return - - else: - raise UsageError("try 'notify on|off <EVENT>'") - - command_NOTIFY.usage = "notify on|off|list [<EVENT>] ... - Notify me about build events. event should be one or more of: 'started', 'finished', 'failure', 'success', 'exception' or 'xToY' (where x and Y are one of success, warnings, failure, exception, but Y is capitalized)" - - def command_WATCH(self, args, who): - args = self.splitArgs(args) - if len(args) != 1: - raise UsageError("try 'watch <builder>'") - which = args[0] - b = self.getBuilder(which) - builds = b.getCurrentBuilds() - if not builds: - self.send("there are no builds currently running") - return - for build in builds: - assert not build.isFinished() - d = build.waitUntilFinished() - d.addCallback(self.watchedBuildFinished) - if self.useRevisions: - r = "watching build %s containing revision(s) [%s] until it finishes" \ - % (which, build.getRevisions()) - else: - r = "watching build %s #%d until it finishes" \ - % (which, build.getNumber()) - eta = build.getETA() - if eta is not None: - r += " [%s]" % self.convertTime(eta) - r += ".." - self.send(r) - command_WATCH.usage = "watch <which> - announce the completion of an active build" - - def builderAdded(self, builderName, builder): - if (self.bot.categories != None and - builder.category not in self.bot.categories): - return - - log.msg('[Contact] Builder %s added' % (builder)) - builder.subscribe(self) - - def builderRemoved(self, builderName): - log.msg('[Contact] Builder %s removed' % (builderName)) - - def buildStarted(self, builderName, build): - builder = build.getBuilder() - log.msg('[Contact] Builder %r in category %s started' % (builder, builder.category)) - - # only notify about builders we are interested in - - if (self.bot.categories != None and - builder.category not in self.bot.categories): - log.msg('Not notifying for a build in the wrong category') - return - - if not self.notify_for('started'): - return - - if self.useRevisions: - r = "build containing revision(s) [%s] on %s started" % \ - (build.getRevisions(), builder.getName()) - else: - r = "build #%d of %s started, including [%s]" % \ - (build.getNumber(), - builder.getName(), - ", ".join([str(c.revision) for c in build.getChanges()]) - ) - - self.send(r) - - results_descriptions = { - SUCCESS: ("Success", 'GREEN'), - WARNINGS: ("Warnings", 'YELLOW'), - FAILURE: ("Failure", 'RED'), - EXCEPTION: ("Exception", 'PURPLE'), - RETRY: ("Retry", 'AQUA_LIGHT'), - } - - def getResultsDescriptionAndColor(self, results): - return self.results_descriptions.get(results, ("??",'RED')) - - def buildFinished(self, builderName, build, results): - builder = build.getBuilder() - - if (self.bot.categories != None and - builder.category not in self.bot.categories): - return - - if not self.notify_for_finished(build): - return - - builder_name = builder.getName() - buildnum = build.getNumber() - buildrevs = build.getRevisions() - - results = self.getResultsDescriptionAndColor(build.getResults()) - if self.reportBuild(builder_name, buildnum): - if self.useRevisions: - r = "build containing revision(s) [%s] on %s is complete: %s" % \ - (buildrevs, builder_name, results[0]) - else: - r = "build #%d of %s is complete: %s" % \ - (buildnum, builder_name, results[0]) - - r += ' [%s]' % maybeColorize(" ".join(build.getText()), results[1], self.useColors) - buildurl = self.bot.status.getURLForThing(build) - if buildurl: - r += " Build details are at %s" % buildurl - - if self.bot.showBlameList and build.getResults() != SUCCESS and len(build.changes) != 0: - r += ' blamelist: ' + ', '.join(list(set([c.who for c in build.changes]))) - - self.send(r) - - def notify_for_finished(self, build): - results = build.getResults() - - if self.notify_for('finished'): - return True - - if self.notify_for(lower(self.results_descriptions.get(results)[0])): - return True - - prevBuild = build.getPreviousBuild() - if prevBuild: - prevResult = prevBuild.getResults() - - required_notification_control_string = join((lower(self.results_descriptions.get(prevResult)[0]), \ - 'To', \ - capitalize(self.results_descriptions.get(results)[0])), \ - '') - - if (self.notify_for(required_notification_control_string)): - return True - - return False - - def watchedBuildFinished(self, b): - - # only notify about builders we are interested in - builder = b.getBuilder() - if (self.bot.categories != None and - builder.category not in self.bot.categories): - return - - builder_name = builder.getName() - buildnum = b.getNumber() - buildrevs = b.getRevisions() - - results = self.getResultsDescriptionAndColor(b.getResults()) - if self.reportBuild(builder_name, buildnum): - if self.useRevisions: - r = "Hey! build %s containing revision(s) [%s] is complete: %s" % \ - (builder_name, buildrevs, results[0]) - else: - r = "Hey! build %s #%d is complete: %s" % \ - (builder_name, buildnum, results[0]) - - r += ' [%s]' % maybeColorize(" ".join(b.getText()), results[1], self.useColors) - self.send(r) - buildurl = self.bot.status.getURLForThing(b) - if buildurl: - self.send("Build details are at %s" % buildurl) - - def command_FORCE(self, args, who): - errReply = "try 'force build [--branch=BRANCH] [--revision=REVISION] [--props=PROP1=VAL1,PROP2=VAL2...] <WHICH> <REASON>'" - args = self.splitArgs(args) - if not args: - raise UsageError(errReply) - what = args.pop(0) - if what != "build": - raise UsageError(errReply) - opts = ForceOptions() - opts.parseOptions(args) - - which = opts['builder'] - branch = opts['branch'] - revision = opts['revision'] - reason = opts['reason'] - props = opts['props'] - - if which is None: - raise UsageError("you must provide a Builder, " + errReply) - - # keep weird stuff out of the branch, revision, and properties args. - branch_validate = self.master.config.validation['branch'] - revision_validate = self.master.config.validation['revision'] - pname_validate = self.master.config.validation['property_name'] - pval_validate = self.master.config.validation['property_value'] - if branch and not branch_validate.match(branch): - log.msg("bad branch '%s'" % branch) - self.send("sorry, bad branch '%s'" % branch) - return - if revision and not revision_validate.match(revision): - log.msg("bad revision '%s'" % revision) - self.send("sorry, bad revision '%s'" % revision) - return - - properties = Properties() - if props: - # split props into name:value dict - pdict = {} - propertylist = props.split(",") - for i in range(0,len(propertylist)): - splitproperty = propertylist[i].split("=", 1) - pdict[splitproperty[0]] = splitproperty[1] - - # set properties - for prop in pdict: - pname = prop - pvalue = pdict[prop] - if not pname_validate.match(pname) \ - or not pval_validate.match(pvalue): - log.msg("bad property name='%s', value='%s'" % (pname, pvalue)) - self.send("sorry, bad property name='%s', value='%s'" % - (pname, pvalue)) - return - properties.setProperty(pname, pvalue, "Force Build IRC") - - bc = self.getControl(which) - - reason = "forced: by %s: %s" % (self.describeUser(who), reason) - ss = SourceStamp(branch=branch, revision=revision) - d = bc.submitBuildRequest(ss, reason, props=properties.asDict()) - def subscribe(buildreq): - ireq = IrcBuildRequest(self, self.useRevisions) - buildreq.subscribe(ireq.started) - d.addCallback(subscribe) - d.addErrback(log.err, "while forcing a build") - - - command_FORCE.usage = "force build [--branch=branch] [--revision=revision] [--props=prop1=val1,prop2=val2...] <which> <reason> - Force a build" - - def command_STOP(self, args, who): - args = self.splitArgs(args) - if len(args) < 3 or args[0] != 'build': - raise UsageError, "try 'stop build WHICH <REASON>'" - which = args[1] - reason = args[2] - - buildercontrol = self.getControl(which) - - r = "stopped: by %s: %s" % (self.describeUser(who), reason) - - # find an in-progress build - builderstatus = self.getBuilder(which) - builds = builderstatus.getCurrentBuilds() - if not builds: - self.send("sorry, no build is currently running") - return - for build in builds: - num = build.getNumber() - revs = build.getRevisions() - - # obtain the BuildControl object - buildcontrol = buildercontrol.getBuild(num) - - # make it stop - buildcontrol.stopBuild(r) - - if self.useRevisions: - response = "build containing revision(s) [%s] interrupted" % revs - else: - response = "build %d interrupted" % num - self.send(response) - - command_STOP.usage = "stop build <which> <reason> - Stop a running build" - - def emit_status(self, which): - b = self.getBuilder(which) - str = "%s: " % which - state, builds = b.getState() - str += state - if state == "idle": - last = b.getLastFinishedBuild() - if last: - start,finished = last.getTimes() - str += ", last build %s ago: %s" % \ - (self.convertTime(int(util.now() - finished)), " ".join(last.getText())) - if state == "building": - t = [] - for build in builds: - step = build.getCurrentStep() - if step: - s = "(%s)" % " ".join(step.getText()) - else: - s = "(no current step)" - ETA = build.getETA() - if ETA is not None: - s += " [ETA %s]" % self.convertTime(ETA) - t.append(s) - str += ", ".join(t) - self.send(str) - - def command_LAST(self, args, who): - args = self.splitArgs(args) - - if len(args) == 0: - which = "all" - elif len(args) == 1: - which = args[0] - else: - raise UsageError, "try 'last <builder>'" - - def emit_last(which): - last = self.getBuilder(which).getLastFinishedBuild() - if not last: - str = "(no builds run since last restart)" - else: - start,finish = last.getTimes() - str = "%s ago: " % (self.convertTime(int(util.now() - finish))) - str += " ".join(last.getText()) - self.send("last build [%s]: %s" % (which, str)) - - if which == "all": - builders = self.getAllBuilders() - for b in builders: - emit_last(b.name) - return - emit_last(which) - command_LAST.usage = "last <which> - list last build status for builder <which>" - - def build_commands(self): - commands = [] - for k in dir(self): - if k.startswith('command_'): - commands.append(k[8:].lower()) - commands.sort() - return commands - - def describeUser(self, user): - if self.dest[0] == '#': - return "IRC user <%s> on channel %s" % (user, self.dest) - return "IRC user <%s> (privmsg)" % user - - # commands - - def command_MUTE(self, args, who): - # The order of these is important! ;) - self.send("Shutting up for now.") - self.muted = True - command_MUTE.usage = "mute - suppress all messages until a corresponding 'unmute' is issued" - - def command_UNMUTE(self, args, who): - if self.muted: - # The order of these is important! ;) - self.muted = False - self.send("I'm baaaaaaaaaaack!") - else: - self.send("You hadn't told me to be quiet, but it's the thought that counts, right?") - command_UNMUTE.usage = "unmute - disable a previous 'mute'" - - def command_HELP(self, args, who): - args = self.splitArgs(args) - if len(args) == 0: - self.send("Get help on what? (try 'help <foo>', 'help <foo> <bar>, " - "or 'commands' for a command list)") - return - command = args[0] - meth = self.getCommandMethod(command) - if not meth: - raise UsageError, "no such command '%s'" % command - usage = getattr(meth, 'usage', None) - if isinstance(usage, dict): - if len(args) == 1: - k = None # command - elif len(args) == 2: - k = args[1] # command arg - else: - k = tuple(args[1:]) # command arg subarg ... - usage = usage.get(k, None) - if usage: - self.send("Usage: %s" % usage) - else: - self.send("No usage info for " + ' '.join(["'%s'" % arg for arg in args])) - command_HELP.usage = "help <command> [<arg> [<subarg> ...]] - Give help for <command> or one of it's arguments" - - def command_SOURCE(self, args, who): - self.send("My source can be found at " - "https://github.com/buildbot/buildbot") - command_SOURCE.usage = "source - the source code for Buildbot" - - def command_COMMANDS(self, args, who): - commands = self.build_commands() - str = "buildbot commands: " + ", ".join(commands) - self.send(str) - command_COMMANDS.usage = "commands - List available commands" - - def command_DESTROY(self, args, who): - if self.bot.nickname not in args: - self.act("readies phasers") - - def command_DANCE(self, args, who): - reactor.callLater(1.0, self.send, "<(^.^<)") - reactor.callLater(2.0, self.send, "<(^.^)>") - reactor.callLater(3.0, self.send, "(>^.^)>") - reactor.callLater(3.5, self.send, "(7^.^)7") - reactor.callLater(5.0, self.send, "(>^.^<)") - - def command_SHUTDOWN(self, args, who): - if args not in ('check','start','stop','now'): - raise UsageError("try 'shutdown check|start|stop|now'") - - if not self.bot.factory.allowShutdown: - raise UsageError("shutdown control is not enabled") - - botmaster = self.master.botmaster - shuttingDown = botmaster.shuttingDown - - if args == 'check': - if shuttingDown: - self.send("Status: buildbot is shutting down") - else: - self.send("Status: buildbot is running") - elif args == 'start': - if shuttingDown: - self.send("Already started") - else: - self.send("Starting clean shutdown") - botmaster.cleanShutdown() - elif args == 'stop': - if not shuttingDown: - self.send("Nothing to stop") - else: - self.send("Stopping clean shutdown") - botmaster.cancelCleanShutdown() - elif args == 'now': - self.send("Stopping buildbot") - reactor.stop() - command_SHUTDOWN.usage = { - None: "shutdown check|start|stop|now - shutdown the buildbot master", - "check": "shutdown check - check if the buildbot master is running or shutting down", - "start": "shutdown start - start a clean shutdown", - "stop": "shutdown cancel - stop the clean shutdown", - "now": "shutdown now - shutdown immediately without waiting for the builders to finish"} - - # communication with the user - - def send(self, message): - if not self.muted: - self.bot.msgOrNotice(self.dest, message.encode("ascii", "replace")) - - def act(self, action): - if not self.muted: - self.bot.describe(self.dest, action.encode("ascii", "replace")) - - # main dispatchers for incoming messages - - def getCommandMethod(self, command): - return getattr(self, 'command_' + command.upper(), None) - - def handleMessage(self, message, who): - # a message has arrived from 'who'. For broadcast contacts (i.e. when - # people do an irc 'buildbot: command'), this will be a string - # describing the sender of the message in some useful-to-log way, and - # a single Contact may see messages from a variety of users. For - # unicast contacts (i.e. when people do an irc '/msg buildbot - # command'), a single Contact will only ever see messages from a - # single user. - message = message.lstrip() - if self.silly.has_key(message): - self.doSilly(message) - return defer.succeed(None) - - parts = message.split(' ', 1) - if len(parts) == 1: - parts = parts + [''] - cmd, args = parts - log.msg("irc command", cmd) - - meth = self.getCommandMethod(cmd) - if not meth and message[-1] == '!': - self.send("What you say!") - return defer.succeed(None) - - if meth: - d = defer.maybeDeferred(meth, args.strip(), who) - @d.addErrback - def usageError(f): - f.trap(UsageError) - self.send(str(f.value)) - @d.addErrback - def logErr(f): - log.err(f) - self.send("Something bad happened (see logs)") - d.addErrback(log.err) - return d - return defer.succeed(None) - - def handleAction(self, data, user): - # this is sent when somebody performs an action that mentions the - # buildbot (like '/me kicks buildbot'). 'user' is the name/nick/id of - # the person who performed the action, so if their action provokes a - # response, they can be named. This is 100% silly. - if not data.endswith("s "+ self.bot.nickname): - return - words = data.split() - verb = words[-2] - if verb == "kicks": - response = "%s back" % verb - else: - response = "%s %s too" % (verb, user) - self.act(response) - - -class IrcStatusBot(irc.IRCClient): - """I represent the buildbot to an IRC server. - """ - contactClass = IRCContact - - def __init__(self, nickname, password, channels, pm_to_nicks, status, - categories, notify_events, noticeOnChannel=False, - useRevisions=False, showBlameList=False, useColors=True): - self.nickname = nickname - self.channels = channels - self.pm_to_nicks = pm_to_nicks - self.password = password - self.status = status - self.master = status.master - self.categories = categories - self.notify_events = notify_events - self.hasQuit = 0 - self.contacts = {} - self.noticeOnChannel = noticeOnChannel - self.useColors = useColors - self.useRevisions = useRevisions - self.showBlameList = showBlameList - self._keepAliveCall = task.LoopingCall(lambda: self.ping(self.nickname)) - - def connectionMade(self): - irc.IRCClient.connectionMade(self) - self._keepAliveCall.start(60) - - def connectionLost(self, reason): - if self._keepAliveCall.running: - self._keepAliveCall.stop() - irc.IRCClient.connectionLost(self, reason) - - def msgOrNotice(self, dest, message): - if self.noticeOnChannel and dest[0] == '#': - self.notice(dest, message) - else: - self.msg(dest, message) - - def getContact(self, name): - name = name.lower() # nicknames and channel names are case insensitive - if name in self.contacts: - return self.contacts[name] - new_contact = self.contactClass(self, name) - self.contacts[name] = new_contact - return new_contact - - def log(self, msg): - log.msg("%s: %s" % (self, msg)) - - - # the following irc.IRCClient methods are called when we have input - - def privmsg(self, user, channel, message): - user = user.split('!', 1)[0] # rest is ~user@hostname - # channel is '#twisted' or 'buildbot' (for private messages) - if channel == self.nickname: - # private message - contact = self.getContact(user) - contact.handleMessage(message, user) - return - # else it's a broadcast message, maybe for us, maybe not. 'channel' - # is '#twisted' or the like. - contact = self.getContact(channel) - if message.startswith("%s:" % self.nickname) or message.startswith("%s," % self.nickname): - message = message[len("%s:" % self.nickname):] - contact.handleMessage(message, user) - - def action(self, user, channel, data): - user = user.split('!', 1)[0] # rest is ~user@hostname - # somebody did an action (/me actions) in the broadcast channel - contact = self.getContact(channel) - if self.nickname in data: - contact.handleAction(data, user) - - def signedOn(self): - if self.password: - self.msg("Nickserv", "IDENTIFY " + self.password) - for c in self.channels: - if isinstance(c, dict): - channel = c.get('channel', None) - password = c.get('password', None) - else: - channel = c - password = None - self.join(channel=channel, key=password) - for c in self.pm_to_nicks: - self.getContact(c) - - def joined(self, channel): - self.log("I have joined %s" % (channel,)) - # trigger contact contructor, which in turn subscribes to notify events - self.getContact(channel) - - def left(self, channel): - self.log("I have left %s" % (channel,)) - - def kickedFrom(self, channel, kicker, message): - self.log("I have been kicked from %s by %s: %s" % (channel, - kicker, - message)) - - -class ThrottledClientFactory(protocol.ClientFactory): - lostDelay = random.randint(1, 5) - failedDelay = random.randint(45, 60) - - def __init__(self, lostDelay=None, failedDelay=None): - if lostDelay is not None: - self.lostDelay = lostDelay - if failedDelay is not None: - self.failedDelay = failedDelay - - def clientConnectionLost(self, connector, reason): - reactor.callLater(self.lostDelay, connector.connect) - - def clientConnectionFailed(self, connector, reason): - reactor.callLater(self.failedDelay, connector.connect) - - -class IrcStatusFactory(ThrottledClientFactory): - protocol = IrcStatusBot - - status = None - control = None - shuttingDown = False - p = None - - def __init__(self, nickname, password, channels, pm_to_nicks, categories, notify_events, - noticeOnChannel=False, useRevisions=False, showBlameList=False, - lostDelay=None, failedDelay=None, useColors=True, allowShutdown=False): - ThrottledClientFactory.__init__(self, lostDelay=lostDelay, - failedDelay=failedDelay) - self.status = None - self.nickname = nickname - self.password = password - self.channels = channels - self.pm_to_nicks = pm_to_nicks - self.categories = categories - self.notify_events = notify_events - self.noticeOnChannel = noticeOnChannel - self.useRevisions = useRevisions - self.showBlameList = showBlameList - self.useColors = useColors - self.allowShutdown = allowShutdown - - def __getstate__(self): - d = self.__dict__.copy() - del d['p'] - return d - - def shutdown(self): - self.shuttingDown = True - if self.p: - self.p.quit("buildmaster reconfigured: bot disconnecting") - - def buildProtocol(self, address): - p = self.protocol(self.nickname, self.password, - self.channels, self.pm_to_nicks, self.status, - self.categories, self.notify_events, - noticeOnChannel = self.noticeOnChannel, - useColors = self.useColors, - useRevisions = self.useRevisions, - showBlameList = self.showBlameList) - p.factory = self - p.status = self.status - p.control = self.control - self.p = p - return p - - # TODO: I think a shutdown that occurs while the connection is being - # established will make this explode - - def clientConnectionLost(self, connector, reason): - if self.shuttingDown: - log.msg("not scheduling reconnection attempt") - return - ThrottledClientFactory.clientConnectionLost(self, connector, reason) - - def clientConnectionFailed(self, connector, reason): - if self.shuttingDown: - log.msg("not scheduling reconnection attempt") - return - ThrottledClientFactory.clientConnectionFailed(self, connector, reason) - - -class IRC(base.StatusReceiverMultiService): - implements(IStatusReceiver) - - in_test_harness = False - - compare_attrs = ["host", "port", "nick", "password", - "channels", "pm_to_nicks", "allowForce", "useSSL", - "useRevisions", "categories", "useColors", - "lostDelay", "failedDelay", "allowShutdown"] - - def __init__(self, host, nick, channels, pm_to_nicks=[], port=6667, - allowForce=False, categories=None, password=None, notify_events={}, - noticeOnChannel = False, showBlameList = True, useRevisions=False, - useSSL=False, lostDelay=None, failedDelay=None, useColors=True, - allowShutdown=False): - base.StatusReceiverMultiService.__init__(self) - - if allowForce not in (True, False): - config.error("allowForce must be boolean, not %r" % (allowForce,)) - if allowShutdown not in (True, False): - config.error("allowShutdown must be boolean, not %r" % (allowShutdown,)) - - # need to stash these so we can detect changes later - self.host = host - self.port = port - self.nick = nick - self.channels = channels - self.pm_to_nicks = pm_to_nicks - self.password = password - self.allowForce = allowForce - self.useRevisions = useRevisions - self.categories = categories - self.notify_events = notify_events - self.allowShutdown = allowShutdown - - self.f = IrcStatusFactory(self.nick, self.password, - self.channels, self.pm_to_nicks, - self.categories, self.notify_events, - noticeOnChannel = noticeOnChannel, - useRevisions = useRevisions, - showBlameList = showBlameList, - lostDelay = lostDelay, - failedDelay = failedDelay, - useColors = useColors, - allowShutdown = allowShutdown) - - if useSSL: - # SSL client needs a ClientContextFactory for some SSL mumbo-jumbo - if not have_ssl: - raise RuntimeError("useSSL requires PyOpenSSL") - cf = ssl.ClientContextFactory() - c = internet.SSLClient(self.host, self.port, self.f, cf) - else: - c = internet.TCPClient(self.host, self.port, self.f) - - c.setServiceParent(self) - - def setServiceParent(self, parent): - base.StatusReceiverMultiService.setServiceParent(self, parent) - self.f.status = parent - if self.allowForce: - self.f.control = interfaces.IControl(self.master) - - def stopService(self): - # make sure the factory will stop reconnecting - self.f.shutdown() - return base.StatusReceiverMultiService.stopService(self) - |