from buildbot.plugins import util, steps, schedulers from buildbot_buildslist import BuildsList from shutil import which __all__ = [ "force_scheduler", "deploy_scheduler", "hook_scheduler", "clean_branch", "package_and_upload", "SlackStatusPush", "XMPPStatusPush", "NixShellCommand" ] # Small helpers" @util.renderer def clean_branch(props): if props.hasProperty("branch") and len(props["branch"]) > 0: return props["branch"].replace("/", "_") else: return "HEAD" def package_and_upload(package, package_dest, package_url): return [ steps.ShellCommand(name="build package", logEnviron=False, haltOnFailure=True, workdir="source", command=["git", "archive", "HEAD", "-o", package]), steps.FileUpload(name="upload package", workersrc=package, workdir="source", masterdest=package_dest, url=package_url, mode=0o644), steps.ShellCommand(name="cleanup package", logEnviron=False, haltOnFailure=True, workdir="source", alwaysRun=True, command=["rm", "-f", package]), ] # Steps class NixShellCommand(steps.ShellCommand): def __init__(self, command=None, pure=True, nixfile=None, **kwargs): assert(isinstance(command, str)) oldpath = kwargs.get("env", {}).get("PATH", None) if which("nix-shell", path=oldpath) is None: kwargs["env"] = kwargs.get("env", {}) if isinstance(oldpath, str): kwargs["env"]["PATH"] = "/run/current-system/sw/bin:" + oldpath elif isinstance(oldpath, list): kwargs["env"]["PATH"] = ["/run/current-system/sw/bin"] + oldpath nixcommand = ["nix-shell"] if pure: nixcommand.append("--pure") nixcommand.append("--run") nixcommand.append(command) if nixfile is not None: nixcommand.append(nixfile) super().__init__(command=nixcommand, **kwargs) # Schedulers def force_scheduler(name, builders): return schedulers.ForceScheduler(name=name, label="Force build", buttonName="Force build", reason=util.StringParameter(name="reason", label="Reason", default="Force build"), codebases=[ util.CodebaseParameter("", branch=util.StringParameter( name="branch", label="Git reference (tag, branch)", required=True), revision=util.FixedParameter(name="revision", default=""), repository=util.FixedParameter(name="repository", default=""), project=util.FixedParameter(name="project", default=""), ), ], username=util.FixedParameter(name="username", default="Web button"), builderNames=builders) def deploy_scheduler(name, builders): return schedulers.ForceScheduler(name=name, builderNames=builders, label="Deploy built package", buttonName="Deploy", username=util.FixedParameter(name="username", default="Web button"), codebases=[ util.CodebaseParameter(codebase="", branch=util.FixedParameter(name="branch", default=""), revision=util.FixedParameter(name="revision", default=""), repository=util.FixedParameter(name="repository", default=""), project=util.FixedParameter(name="project", default=""))], reason=util.FixedParameter(name="reason", default="Deploy"), properties=[ util.ChoiceStringParameter(label="Environment", name="environment", default="integration", choices=["integration", "production"]), BuildsList(label="Build to deploy", name="build"), ] ) def hook_scheduler(project, timer=10): return schedulers.AnyBranchScheduler( change_filter=util.ChangeFilter(category="hooks", project=project), name=project, treeStableTimer=timer, builderNames=["{}_build".format(project)]) # Slack/XMPP status push from buildbot.reporters.http import HttpStatusPushBase from twisted.internet import defer from twisted.python import log from buildbot.util import httpclientservice from buildbot.reporters import utils from buildbot.process import results from twisted.words.protocols.jabber.jid import JID from wokkel import client, xmppim from functools import partial class SlackStatusPush(HttpStatusPushBase): name = "SlackStatusPush" @defer.inlineCallbacks def reconfigService(self, serverUrl, **kwargs): yield HttpStatusPushBase.reconfigService(self, **kwargs) self._http = yield httpclientservice.HTTPClientService.getService( self.master, serverUrl) @defer.inlineCallbacks def send(self, build): yield utils.getDetailsForBuild(self.master, build, wantProperties=True) response = yield self._http.post("", json=self.format(build)) if response.code != 200: log.msg("%s: unable to upload status: %s" % (response.code, response.content)) def format(self, build): colors = [ "#36A64F", # success "#F1E903", # warnings "#DA0505", # failure "#FFFFFF", # skipped "#000000", # exception "#FFFFFF", # retry "#D02CA9", # cancelled ] if "environment" in build["properties"]: msg = "{} environment".format(build["properties"]["environment"][0]) if "build" in build["properties"]: msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg elif len(build["buildset"]["sourcestamps"][0]["branch"] or []) > 0: msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"]) else: msg = "build" if build["complete"]: timedelta = int((build["complete_at"] - build["started_at"]).total_seconds()) hours, rest = divmod(timedelta, 3600) minutes, seconds = divmod(rest, 60) if hours > 0: duration = "{}h {}min {}s".format(hours, minutes, seconds) elif minutes > 0: duration = "{}min {}s".format(minutes, seconds) else: duration = "{}s".format(seconds) text = "Build <{}|{}> of {}'s {} was {} in {}.".format( build["url"], build["buildid"], build["builder"]["name"], msg, results.Results[build["results"]], duration, ) fields = [ { "title": "Build", "value": "<{}|{}>".format(build["url"], build["buildid"]), "short": True, }, { "title": "Project", "value": build["builder"]["name"], "short": True, }, { "title": "Build status", "value": results.Results[build["results"]], "short": True, }, { "title": "Build duration", "value": duration, "short": True, }, ] if "environment" in build["properties"]: fields.append({ "title": "Environment", "value": build["properties"]["environment"][0], "short": True, }) if "build" in build["properties"]: fields.append({ "title": "Archive", "value": build["properties"]["build"][0], "short": True, }) attachments = [{ "fallback": "", "color": colors[build["results"]], "fields": fields }] else: text = "Build <{}|{}> of {}'s {} started.".format( build["url"], build["buildid"], build["builder"]["name"], msg, ) attachments = [] return { "username": "Buildbot", "icon_url": "http://docs.buildbot.net/current/_static/icon.png", "text": text, "attachments": attachments, } class XMPPStatusPush(HttpStatusPushBase): name = "XMPPStatusPush" @defer.inlineCallbacks def reconfigService(self, password, recipients, **kwargs): yield HttpStatusPushBase.reconfigService(self, **kwargs) self.password = password self.recipients = recipients @defer.inlineCallbacks def send(self, build): yield utils.getDetailsForBuild(self.master, build, wantProperties=True) body = self.format(build) factory = client.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self.password) d = client.clientCreator(factory) def send_message(recipient, stream): message = xmppim.Message(recipient=JID(recipient), body=body) message.stanzaType = 'chat' stream.send(message.toElement()) # To allow chaining return stream for recipient in self.recipients: d.addCallback(partial(send_message, recipient)) d.addCallback(lambda _: factory.streamManager.xmlstream.sendFooter()) d.addErrback(log.err) def format(self, build): if "environment" in build["properties"]: msg = "{} environment".format(build["properties"]["environment"][0]) if "build" in build["properties"]: msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg elif len(build["buildset"]["sourcestamps"][0]["branch"] or []) > 0: msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"]) else: msg = "build" if build["complete"]: timedelta = int((build["complete_at"] - build["started_at"]).total_seconds()) hours, rest = divmod(timedelta, 3600) minutes, seconds = divmod(rest, 60) if hours > 0: duration = "{}h {}min {}s".format(hours, minutes, seconds) elif minutes > 0: duration = "{}min {}s".format(minutes, seconds) else: duration = "{}s".format(seconds) text = "Build {} ( {} ) of {}'s {} was {} in {}.".format( build["buildid"], build["url"], build["builder"]["name"], msg, results.Results[build["results"]], duration, ) else: text = "Build {} ( {} ) of {}'s {} started.".format( build["buildid"], build["url"], build["builder"]["name"], msg, ) return text