]>
git.immae.eu Git - perso/Immae/Config/Nix.git/blob - modules/private/buildbot/common/build_helpers.py
ebd49ae9d4a53310b9aaf7ecb605bb07769a00ac
1 from buildbot
.plugins
import util
, steps
, schedulers
2 from buildbot_buildslist
import BuildsList
3 from shutil
import which
6 "force_scheduler", "deploy_scheduler", "git_hook_scheduler",
7 "clean_branch", "package_and_upload", "SlackStatusPush",
8 "XMPPStatusPush", "NixShellCommand",
9 "all_builder_names", "compute_build_infos", "deploy_ssh_command",
10 "configure_slack_push", "configure_xmpp_push", "deploy_hook_scheduler",
15 def clean_branch(props
):
16 if props
.hasProperty("branch") and len(props
["branch"]) > 0:
17 return props
["branch"].replace("/", "_")
21 def package_and_upload(package
, package_dest
, package_url
):
23 steps
.ShellCommand(name
="build package",
24 logEnviron
=False, haltOnFailure
=True,
25 command
=["git", "archive", "HEAD", "-o", package
]),
27 steps
.FileUpload(name
="upload package", workersrc
=package
,
28 masterdest
=package_dest
,
29 url
=package_url
, mode
=0o644),
31 steps
.ShellCommand(name
="cleanup package", logEnviron
=False,
32 haltOnFailure
=True, alwaysRun
=True,
33 command
=["rm", "-f", package
]),
37 class NixShellCommand(steps
.ShellCommand
):
38 def __init__(self
, command
=None, nixPackages
=[], pure
=True, nixFile
=None, nixIncludes
={}, nixArgs={}
, **kwargs
):
39 oldpath
= kwargs
.get("env", {}).get("PATH", None)
40 if which("nix-shell", path
=oldpath
) is None:
41 kwargs
["env"] = kwargs
.get("env", {})
42 if isinstance(oldpath
, str):
43 kwargs
["env"]["PATH"] = "/run/current-system/sw/bin:" + oldpath
44 elif isinstance(oldpath
, list):
45 kwargs
["env"]["PATH"] = ["/run/current-system/sw/bin"] + oldpath
46 nixcommand
= ["nix-shell"]
47 for k
, v
in nixArgs
.items():
48 nixcommand
.append("--arg")
52 nixcommand
.append("--pure")
53 for k
, v
in nixIncludes
.items():
54 nixcommand
.append("-I")
55 nixcommand
.append("{}={}".format(k
, v
))
56 nixcommand
.append("--run")
57 nixcommand
.append(command
)
58 if len(nixPackages
) > 0:
59 nixcommand
.append("-p")
60 nixcommand
+= nixPackages
61 elif nixFile
is not None:
62 nixcommand
.append(nixFile
)
63 super().__init
__(command
=nixcommand
, **kwargs
)
66 def force_scheduler(name
, builders
, nobranch
=False):
68 branch
= util
.FixedParameter(name
="branch", default
="")
70 branch
=util
.StringParameter(name
="branch", label
="Git reference (tag, branch)", required
=True)
72 return schedulers
.ForceScheduler(name
=name
,
73 label
="Force build", buttonName
="Force build",
74 reason
=util
.StringParameter(name
="reason", label
="Reason", default
="Force build"),
76 util
.CodebaseParameter("",
78 revision
=util
.FixedParameter(name
="revision", default
=""),
79 repository
=util
.FixedParameter(name
="repository", default
=""),
80 project
=util
.FixedParameter(name
="project", default
=""),
83 username
=util
.FixedParameter(name
="username", default
="Web button"),
84 builderNames
=builders
)
86 def deploy_scheduler(name
, builders
):
87 return schedulers
.ForceScheduler(name
=name
,
88 builderNames
=builders
,
89 label
="Deploy built package", buttonName
="Deploy",
90 username
=util
.FixedParameter(name
="username", default
="Web button"),
92 util
.CodebaseParameter(codebase
="",
93 branch
=util
.FixedParameter(name
="branch", default
=""),
94 revision
=util
.FixedParameter(name
="revision", default
=""),
95 repository
=util
.FixedParameter(name
="repository", default
=""),
96 project
=util
.FixedParameter(name
="project", default
=""))],
97 reason
=util
.FixedParameter(name
="reason", default
="Deploy"),
99 util
.ChoiceStringParameter(label
="Environment",
100 name
="environment", default
="integration",
101 choices
=["integration", "production"]),
102 BuildsList(label
="Build to deploy", name
="build"),
106 def git_hook_scheduler(project
, builders
=[], timer
=1):
107 if len(builders
) == 0:
108 builders
= ["{}_build".format(project
)]
109 return schedulers
.AnyBranchScheduler(
110 change_filter
=util
.ChangeFilter(category
="gitolite-hooks", project
=project
),
111 name
="{}_git_hook".format(project
), treeStableTimer
=timer
, builderNames
=builders
)
113 def deploy_hook_scheduler(project
, builders
, timer
=1):
114 return schedulers
.AnyBranchScheduler(
115 change_filter
=util
.ChangeFilter(category
="deploy_webhook", project
=project
),
116 name
="{}_deploy".format(project
), treeStableTimer
=timer
, builderNames
=builders
)
119 def all_builder_names(c
):
120 return [builder
.name
for builder
in c
['builders']]
122 # Slack/XMPP status push
123 from buildbot
.reporters
.http
import HttpStatusPushBase
124 from twisted
.internet
import defer
125 from twisted
.python
import log
126 from buildbot
.util
import httpclientservice
127 from buildbot
.reporters
import utils
128 from buildbot
.process
import results
129 from twisted
.words
.protocols
.jabber
.jid
import JID
130 from wokkel
import client
, xmppim
131 from functools
import partial
133 class SlackStatusPush(HttpStatusPushBase
):
134 name
= "SlackStatusPush"
136 @defer.inlineCallbacks
137 def reconfigService(self
, serverUrl
, **kwargs
):
138 yield HttpStatusPushBase
.reconfigService(self
, **kwargs
)
139 self
._http
= yield httpclientservice
.HTTPClientService
.getService(
140 self
.master
, serverUrl
)
142 @defer.inlineCallbacks
143 def send(self
, build
):
144 yield utils
.getDetailsForBuild(self
.master
, build
, wantProperties
=True)
145 response
= yield self
._http
.post("", json
=self
.format(build
))
146 if response
.code
!= 200:
147 log
.msg("%s: unable to upload status: %s" %
148 (response
.code
, response
.content
))
150 def format(self
, build
):
153 "#F1E903", # warnings
156 "#000000", # exception
158 "#D02CA9", # cancelled
161 if "environment" in build
["properties"]:
162 msg
= "{} environment".format(build
["properties"]["environment"][0])
163 if "build" in build
["properties"]:
164 msg
= "of archive {} in ".format(build
["properties"]["build"][0]) + msg
165 elif len(build
["buildset"]["sourcestamps"][0]["branch"] or []) > 0:
166 msg
= "revision {}".format(build
["buildset"]["sourcestamps"][0]["branch"])
170 if build
["complete"]:
171 timedelta
= int((build
["complete_at"] - build
["started_at"]).total_seconds())
172 hours
, rest
= divmod(timedelta
, 3600)
173 minutes
, seconds
= divmod(rest
, 60)
175 duration
= "{}h {}min {}s".format(hours
, minutes
, seconds
)
177 duration
= "{}min {}s".format(minutes
, seconds
)
179 duration
= "{}s".format(seconds
)
181 text
= "Build <{}|{}> of {}'s {} was {} in {}.".format(
182 build
["url"], build
["buildid"],
183 build
["builder"]["name"],
185 results
.Results
[build
["results"]],
191 "value": "<{}|{}>".format(build
["url"], build
["buildid"]),
196 "value": build
["builder"]["name"],
200 "title": "Build status",
201 "value": results
.Results
[build
["results"]],
205 "title": "Build duration",
210 if "environment" in build
["properties"]:
212 "title": "Environment",
213 "value": build
["properties"]["environment"][0],
216 if "build" in build
["properties"]:
219 "value": build
["properties"]["build"][0],
224 "color": colors
[build
["results"]],
228 text
= "Build <{}|{}> of {}'s {} started.".format(
229 build
["url"], build
["buildid"],
230 build
["builder"]["name"],
236 "username": "Buildbot",
237 "icon_url": "http://docs.buildbot.net/current/_static/icon.png",
239 "attachments": attachments
,
242 def configure_slack_push(c
, secrets_file
, builders
):
243 c
['services'].append(SlackStatusPush(
244 name
="slack_status", builders
=builders
,
245 serverUrl
=open(secrets_file
+ "/slack_webhook", "r").read().rstrip()))
247 class XMPPStatusPush(HttpStatusPushBase
):
248 name
= "XMPPStatusPush"
250 @defer.inlineCallbacks
251 def reconfigService(self
, password
, recipients
, **kwargs
):
252 yield HttpStatusPushBase
.reconfigService(self
, **kwargs
)
253 self
.password
= password
254 self
.recipients
= recipients
256 @defer.inlineCallbacks
257 def send(self
, build
):
258 yield utils
.getDetailsForBuild(self
.master
, build
, wantProperties
=True)
259 body
= self
.format(build
)
260 factory
= client
.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self
.password
)
261 d
= client
.clientCreator(factory
)
262 def send_message(recipient
, stream
):
263 message
= xmppim
.Message(recipient
=JID(recipient
), body
=body
)
264 message
.stanzaType
= 'chat'
265 stream
.send(message
.toElement())
268 for recipient
in self
.recipients
:
269 d
.addCallback(partial(send_message
, recipient
))
270 d
.addCallback(lambda _
: factory
.streamManager
.xmlstream
.sendFooter())
271 d
.addErrback(log
.err
)
273 def format(self
, build
):
274 if "environment" in build
["properties"]:
275 msg
= "{} environment".format(build
["properties"]["environment"][0])
276 if "build" in build
["properties"]:
277 msg
= "of archive {} in ".format(build
["properties"]["build"][0]) + msg
278 elif len(build
["buildset"]["sourcestamps"][0]["branch"] or []) > 0:
279 msg
= "revision {}".format(build
["buildset"]["sourcestamps"][0]["branch"])
283 if build
["complete"]:
284 timedelta
= int((build
["complete_at"] - build
["started_at"]).total_seconds())
285 hours
, rest
= divmod(timedelta
, 3600)
286 minutes
, seconds
= divmod(rest
, 60)
288 duration
= "{}h {}min {}s".format(hours
, minutes
, seconds
)
290 duration
= "{}min {}s".format(minutes
, seconds
)
292 duration
= "{}s".format(seconds
)
294 text
= "Build {} ( {} ) of {}'s {} was {} in {}.".format(
295 build
["buildid"], build
["url"],
296 build
["builder"]["name"],
298 results
.Results
[build
["results"]],
302 text
= "Build {} ( {} ) of {}'s {} started.".format(
303 build
["buildid"], build
["url"],
304 build
["builder"]["name"],
310 def configure_xmpp_push(c
, secrets_file
, builders
, recipients
):
311 c
['services'].append(XMPPStatusPush(
312 name
="xmpp_status", builders
=builders
, recipients
=recipients
,
313 password
=open(secrets_file
+ "/notify_xmpp_password", "r").read().rstrip()))
316 from buildbot
.process
.buildstep
import FAILURE
317 from buildbot
.process
.buildstep
import SUCCESS
318 from buildbot
.process
.buildstep
import BuildStep
320 def compute_build_infos(prefix
, release_path
):
324 build_file
= props
.getProperty("build")
325 package_dest
= "{}/{}".format(release_path
, build_file
)
326 version
= re
.match(r
"{0}_(.*).tar.gz".format(prefix
), build_file
).group(1)
327 with open(package_dest
, "rb") as f
:
328 sha
= hashlib
.sha256(f
.read()).hexdigest()
330 "build_version": version
,
335 def deploy_ssh_command(ssh_key_path
, deploy_hosts
):
338 environment
= props
["environment"] if props
.hasProperty("environment") else "integration"
340 "ssh", "-o", "UserKnownHostsFile=/dev/null", "-o", "StrictHostKeyChecking=no", "-o", "CheckHostIP=no",
342 return ssh_command
+ deploy_hosts
.get(environment
, ["host.invalid"])