]> git.immae.eu Git - perso/Immae/Config/Nix.git/blobdiff - modules/private/buildbot/common/build_helpers.py
Move rest of the modules outside of nixops
[perso/Immae/Config/Nix.git] / modules / private / buildbot / common / build_helpers.py
diff --git a/modules/private/buildbot/common/build_helpers.py b/modules/private/buildbot/common/build_helpers.py
new file mode 100644 (file)
index 0000000..384b1ac
--- /dev/null
@@ -0,0 +1,256 @@
+from buildbot.plugins import util, steps, schedulers
+from buildbot_buildslist import BuildsList
+
+__all__ = [
+        "force_scheduler", "deploy_scheduler", "hook_scheduler",
+        "clean_branch", "package_and_upload", "SlackStatusPush",
+        "XMPPStatusPush"
+        ]
+
+# 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]),
+            ]
+
+# 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"]) > 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"]) > 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