]> git.immae.eu Git - perso/Immae/Config/Nix.git/blame - 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
CommitLineData
e2b96bf5
IB
1from buildbot.plugins import util, steps, schedulers
2from buildbot_buildslist import BuildsList
3
4__all__ = [
5 "force_scheduler", "deploy_scheduler", "hook_scheduler",
256d607c
IB
6 "clean_branch", "package_and_upload", "SlackStatusPush",
7 "XMPPStatusPush"
e2b96bf5
IB
8 ]
9
10# Small helpers"
11@util.renderer
12def clean_branch(props):
13 if props.hasProperty("branch") and len(props["branch"]) > 0:
14 return props["branch"].replace("/", "_")
15 else:
16 return "HEAD"
17
18def package_and_upload(package, package_dest, package_url):
19 return [
20 steps.ShellCommand(name="build package",
21 logEnviron=False, haltOnFailure=True, workdir="source",
22 command=["git", "archive", "HEAD", "-o", package]),
23
24 steps.FileUpload(name="upload package", workersrc=package,
25 workdir="source", masterdest=package_dest,
26 url=package_url, mode=0o644),
27
28 steps.ShellCommand(name="cleanup package", logEnviron=False,
29 haltOnFailure=True, workdir="source", alwaysRun=True,
30 command=["rm", "-f", package]),
31 ]
32
33# Schedulers
34def force_scheduler(name, builders):
35 return schedulers.ForceScheduler(name=name,
36 label="Force build", buttonName="Force build",
37 reason=util.StringParameter(name="reason", label="Reason", default="Force build"),
38 codebases=[
39 util.CodebaseParameter("",
40 branch=util.StringParameter(
41 name="branch", label="Git reference (tag, branch)", required=True),
42 revision=util.FixedParameter(name="revision", default=""),
43 repository=util.FixedParameter(name="repository", default=""),
44 project=util.FixedParameter(name="project", default=""),
45 ),
46 ],
47 username=util.FixedParameter(name="username", default="Web button"),
48 builderNames=builders)
49
50def deploy_scheduler(name, builders):
51 return schedulers.ForceScheduler(name=name,
52 builderNames=builders,
53 label="Deploy built package", buttonName="Deploy",
54 username=util.FixedParameter(name="username", default="Web button"),
55 codebases=[
56 util.CodebaseParameter(codebase="",
57 branch=util.FixedParameter(name="branch", default=""),
58 revision=util.FixedParameter(name="revision", default=""),
59 repository=util.FixedParameter(name="repository", default=""),
60 project=util.FixedParameter(name="project", default=""))],
61 reason=util.FixedParameter(name="reason", default="Deploy"),
62 properties=[
63 util.ChoiceStringParameter(label="Environment",
64 name="environment", default="integration",
65 choices=["integration", "production"]),
66 BuildsList(label="Build to deploy", name="build"),
67 ]
68 )
69
70def hook_scheduler(project, timer=10):
71 return schedulers.AnyBranchScheduler(
72 change_filter=util.ChangeFilter(category="hooks", project=project),
73 name=project, treeStableTimer=timer, builderNames=["{}_build".format(project)])
74
256d607c 75# Slack/XMPP status push
e2b96bf5
IB
76from buildbot.reporters.http import HttpStatusPushBase
77from twisted.internet import defer
78from twisted.python import log
79from buildbot.util import httpclientservice
80from buildbot.reporters import utils
81from buildbot.process import results
256d607c
IB
82from twisted.words.protocols.jabber.jid import JID
83from wokkel import client, xmppim
7158b76e 84from functools import partial
e2b96bf5
IB
85
86class SlackStatusPush(HttpStatusPushBase):
87 name = "SlackStatusPush"
88
89 @defer.inlineCallbacks
90 def reconfigService(self, serverUrl, **kwargs):
91 yield HttpStatusPushBase.reconfigService(self, **kwargs)
92 self._http = yield httpclientservice.HTTPClientService.getService(
93 self.master, serverUrl)
94
95 @defer.inlineCallbacks
96 def send(self, build):
97 yield utils.getDetailsForBuild(self.master, build, wantProperties=True)
98 response = yield self._http.post("", json=self.format(build))
99 if response.code != 200:
100 log.msg("%s: unable to upload status: %s" %
101 (response.code, response.content))
102
103 def format(self, build):
104 colors = [
105 "#36A64F", # success
106 "#F1E903", # warnings
107 "#DA0505", # failure
108 "#FFFFFF", # skipped
109 "#000000", # exception
110 "#FFFFFF", # retry
111 "#D02CA9", # cancelled
112 ]
113
114 if "environment" in build["properties"]:
115 msg = "{} environment".format(build["properties"]["environment"][0])
116 if "build" in build["properties"]:
117 msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg
118 elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0:
119 msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"])
120 else:
121 msg = "build"
122
123 if build["complete"]:
124 timedelta = int((build["complete_at"] - build["started_at"]).total_seconds())
125 hours, rest = divmod(timedelta, 3600)
126 minutes, seconds = divmod(rest, 60)
127 if hours > 0:
128 duration = "{}h {}min {}s".format(hours, minutes, seconds)
129 elif minutes > 0:
130 duration = "{}min {}s".format(minutes, seconds)
131 else:
132 duration = "{}s".format(seconds)
133
134 text = "Build <{}|{}> of {}'s {} was {} in {}.".format(
135 build["url"], build["buildid"],
136 build["builder"]["name"],
137 msg,
138 results.Results[build["results"]],
139 duration,
140 )
141 fields = [
142 {
143 "title": "Build",
144 "value": "<{}|{}>".format(build["url"], build["buildid"]),
145 "short": True,
146 },
147 {
148 "title": "Project",
149 "value": build["builder"]["name"],
150 "short": True,
151 },
152 {
153 "title": "Build status",
154 "value": results.Results[build["results"]],
155 "short": True,
156 },
157 {
158 "title": "Build duration",
159 "value": duration,
160 "short": True,
161 },
162 ]
163 if "environment" in build["properties"]:
164 fields.append({
165 "title": "Environment",
166 "value": build["properties"]["environment"][0],
167 "short": True,
168 })
169 if "build" in build["properties"]:
170 fields.append({
171 "title": "Archive",
172 "value": build["properties"]["build"][0],
173 "short": True,
174 })
175 attachments = [{
176 "fallback": "",
177 "color": colors[build["results"]],
178 "fields": fields
179 }]
180 else:
181 text = "Build <{}|{}> of {}'s {} started.".format(
182 build["url"], build["buildid"],
183 build["builder"]["name"],
184 msg,
185 )
186 attachments = []
187
188 return {
189 "username": "Buildbot",
190 "icon_url": "http://docs.buildbot.net/current/_static/icon.png",
191 "text": text,
192 "attachments": attachments,
193 }
194
256d607c
IB
195class XMPPStatusPush(HttpStatusPushBase):
196 name = "XMPPStatusPush"
197
198 @defer.inlineCallbacks
199 def reconfigService(self, password, recipients, **kwargs):
200 yield HttpStatusPushBase.reconfigService(self, **kwargs)
201 self.password = password
202 self.recipients = recipients
203
204 @defer.inlineCallbacks
205 def send(self, build):
206 yield utils.getDetailsForBuild(self.master, build, wantProperties=True)
207 body = self.format(build)
208 factory = client.DeferredClientFactory(JID("notify_bot@immae.fr/buildbot"), self.password)
256d607c 209 d = client.clientCreator(factory)
7158b76e 210 def send_message(recipient, stream):
256d607c
IB
211 message = xmppim.Message(recipient=JID(recipient), body=body)
212 message.stanzaType = 'chat'
213 stream.send(message.toElement())
214 # To allow chaining
215 return stream
216 for recipient in self.recipients:
7158b76e 217 d.addCallback(partial(send_message, recipient))
256d607c
IB
218 d.addCallback(lambda _: factory.streamManager.xmlstream.sendFooter())
219 d.addErrback(log.err)
220
221 def format(self, build):
222 if "environment" in build["properties"]:
223 msg = "{} environment".format(build["properties"]["environment"][0])
224 if "build" in build["properties"]:
225 msg = "of archive {} in ".format(build["properties"]["build"][0]) + msg
226 elif len(build["buildset"]["sourcestamps"][0]["branch"]) > 0:
227 msg = "revision {}".format(build["buildset"]["sourcestamps"][0]["branch"])
228 else:
229 msg = "build"
230
231 if build["complete"]:
232 timedelta = int((build["complete_at"] - build["started_at"]).total_seconds())
233 hours, rest = divmod(timedelta, 3600)
234 minutes, seconds = divmod(rest, 60)
235 if hours > 0:
236 duration = "{}h {}min {}s".format(hours, minutes, seconds)
237 elif minutes > 0:
238 duration = "{}min {}s".format(minutes, seconds)
239 else:
240 duration = "{}s".format(seconds)
e2b96bf5 241
256d607c
IB
242 text = "Build {} ( {} ) of {}'s {} was {} in {}.".format(
243 build["buildid"], build["url"],
244 build["builder"]["name"],
245 msg,
246 results.Results[build["results"]],
247 duration,
248 )
249 else:
250 text = "Build {} ( {} ) of {}'s {} started.".format(
251 build["buildid"], build["url"],
252 build["builder"]["name"],
253 msg,
254 )
e2b96bf5 255
256d607c 256 return text