]> git.ipfire.org Git - pbs.git/blob - src/web/builds.py
builds: Make sending emails async
[pbs.git] / src / web / builds.py
1 #!/usr/bin/python
2
3 import tornado.web
4
5 from ..errors import NoSuchDistroError
6 from .. import misc
7
8 from . import base
9 from . import ui_modules
10
11 class APIv1IndexHandler(base.APIMixin, base.BaseHandler):
12 # Allow users to create builds
13 allow_users = True
14
15 @tornado.web.authenticated
16 async def post(self):
17 # Fetch the upload
18 upload = self.get_argument_upload("upload_id")
19 if not upload:
20 raise tornado.web.HTTPError(404, "Could not find upload")
21
22 # Check permissions of the upload
23 if not upload.has_perm(self.current_user):
24 raise tornado.web.HTTPError(403, "No permission for using upload %s" % upload)
25
26 # Fetch the repository
27 repo_name = self.get_argument("repo", None)
28
29 # Did the uploader request to disable test builds?
30 disable_test_builds = self.get_argument_bool("disable_test_builds")
31
32 with self.db.transaction():
33 # Import the package
34 try:
35 package = await self.backend.packages.create(upload)
36
37 # If the distribution that is coded into the package could not be found,
38 # we will send that error to the user...
39 except NoSuchDistroError as e:
40 raise tornado.web.HTTPError(404, "Could not find distribution: %s" % e)
41
42 # Find the repository
43 repo = self.current_user.get_repo(package.distro, repo_name)
44 if not repo:
45 raise tornado.web.HTTPError(404, "Could not find repository")
46
47 # Create a new build
48 build = await self.backend.builds.create(repo, package,
49 owner=self.current_user, disable_test_builds=disable_test_builds)
50
51 # Delete the upload
52 await upload.delete()
53
54 # Send some data about the build
55 self.finish({
56 "uuid" : build.uuid,
57 "name" : "%s" % build,
58 })
59
60 # Launch all jobs (in the background)
61 self.backend.run_task(self.backend.builds.launch, [build])
62
63
64 class IndexHandler(base.BaseHandler):
65 def get(self):
66 # Pagination
67 offset = self.get_argument_int("offset", None) or 0
68 limit = self.get_argument_int("limit", None) or 25
69
70 # Filters
71 user = self.get_argument_user("user", None)
72
73 # Fetch the most recent builds
74 if user:
75 builds = self.backend.builds.get_by_user(user, limit=limit, offset=offset)
76 else:
77 builds = self.backend.builds.get_recent(limit=limit, offset=offset)
78
79 # Group builds by date
80 builds = misc.group(builds, lambda build: build.created_at.date())
81
82 self.render("builds/index.html", builds=builds, user=user,
83 limit=limit, offset=offset)
84
85
86 class QueueHandler(base.BaseHandler):
87 def get(self):
88 self.render("builds/queue.html", queue=self.backend.jobs.queue)
89
90
91 class ShowHandler(base.BaseHandler):
92 async def get(self, uuid):
93 build = self.backend.builds.get_by_uuid(uuid)
94 if not build:
95 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
96
97 # Fetch any fixed Bugs
98 bugs = await build.get_bugs()
99
100 self.render("builds/show.html", build=build, pkg=build.pkg,
101 distro=build.distro, bugs=bugs)
102
103
104 class CloneHandler(base.BaseHandler):
105 @tornado.web.authenticated
106 def get(self, uuid):
107 build = self.backend.builds.get_by_uuid(uuid)
108 if not build:
109 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
110
111 # Fetch repositories
112 try:
113 repos = self.current_user.repos[build.distro]
114 except KeyError:
115 repos = []
116
117 self.render("builds/clone.html", build=build, repos=repos)
118
119 @tornado.web.authenticated
120 async def post(self, uuid):
121 build = self.backend.builds.get_by_uuid(uuid)
122 if not build:
123 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
124
125 # Fetch the repository
126 repo = self.current_user.get_repo(build.distro, self.get_argument("repo"))
127
128 # Clone the build
129 with self.db.transaction():
130 clone = await self.backend.builds.create(
131 repo=repo, package=build.pkg, owner=self.current_user,
132 )
133
134 # Launch all jobs (in the background)
135 self.backend.run_task(self.backend.builds.launch, [clone])
136
137 self.redirect("/builds/%s" % clone.uuid)
138
139
140 class DeleteHandler(base.BaseHandler):
141 @tornado.web.authenticated
142 def get(self, uuid):
143 build = self.backend.builds.get_by_uuid(uuid)
144 if not build:
145 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
146
147 # Check permissions
148 if not build.can_be_deleted(self.current_user):
149 raise tornado.web.HTTPError(403, "%s cannot delete build %s" \
150 % (self.current_user, build))
151
152 self.render("builds/delete.html", build=build)
153
154 @tornado.web.authenticated
155 async def post(self, uuid):
156 build = self.backend.builds.get_by_uuid(uuid)
157 if not build:
158 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
159
160 # Check permissions
161 if not build.can_be_deleted(self.current_user):
162 raise tornado.web.HTTPError(403, "%s cannot delete build %s" \
163 % (self.current_user, build))
164
165 # Perform Deletion
166 with self.db.transaction():
167 await build.delete(self.current_user)
168
169 self.redirect("/builds")
170
171
172 class WatchHandler(base.BaseHandler):
173 @tornado.web.authenticated
174 def post(self, uuid):
175 build = self.backend.builds.get_by_uuid(uuid)
176 if not build:
177 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
178
179 with self.db.transaction():
180 build.add_watcher(self.current_user)
181
182 self.redirect("/builds/%s" % build.uuid)
183
184
185 class UnwatchHandler(base.BaseHandler):
186 @tornado.web.authenticated
187 def post(self, uuid):
188 build = self.backend.builds.get_by_uuid(uuid)
189 if not build:
190 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
191
192 with self.db.transaction():
193 build.remove_watcher(self.current_user)
194
195 self.redirect("/builds/%s" % build.uuid)
196
197
198 class CommentHandler(base.BaseHandler):
199 @tornado.web.authenticated
200 async def post(self, uuid):
201 build = self.backend.builds.get_by_uuid(uuid)
202 if not build:
203 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
204
205 text = self.get_argument("text")
206
207 # Add a new comment to the build
208 with self.db.transaction():
209 await build.comment(self.current_user, text)
210
211 # Redirect to the build
212 self.redirect("/builds/%s" % build.uuid)
213
214
215 class BugHandler(base.BaseHandler):
216 @tornado.web.authenticated
217 async def get(self, uuid):
218 build = self.backend.builds.get_by_uuid(uuid)
219 if not build:
220 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
221
222 # Fetch fields
223 fields = await self.backend.bugzilla.fields
224
225 self.render("builds/bug.html", build=build, fields=fields)
226
227 @tornado.web.authenticated
228 async def post(self, uuid):
229 build = self.backend.builds.get_by_uuid(uuid)
230 if not build:
231 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
232
233 # Is the user connected to Bugzilla?
234 if not self.current_user.bugzilla:
235 raise tornado.web.HTTPError(400, "%s is not connected to Bugzilla" \
236 % self.current_user)
237
238 kwargs = {
239 # Summary & Description
240 "summary" : self.get_argument("summary"),
241 "description" : self.get_argument("description", None),
242 } | build.bugzilla_fields
243
244 # Create the bug
245 bug = await self.current_user.bugzilla.create_bug(**kwargs)
246
247 # Send the attachments
248 for job in build.jobs:
249 if not self.get_argument_bool("attach_log_%s" % job.uuid):
250 continue
251
252 # Open the logfile
253 try:
254 log = await job.open_log()
255 except FileNotFoundError as e:
256 log.warning("Could not open log file for %s" % job)
257 continue
258
259 # Attach it to the bug
260 await bug.attach(summary="Log file for %s" % job, filename="%s.log" % job,
261 data=log, content_type="text/plain")
262
263 self.render("builds/bug-created.html", build=build, bug=bug)
264
265
266 class ReposAddHandler(base.BaseHandler):
267 @tornado.web.authenticated
268 def get(self, uuid):
269 build = self.backend.builds.get_by_uuid(uuid)
270 if not build:
271 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
272
273 # Fetch all available repositories
274 try:
275 repos = self.current_user.repos[build.distro]
276 except KeyError:
277 repos = None
278
279 self.render("builds/repos/add.html", build=build, repos=repos)
280
281 @tornado.web.authenticated
282 async def post(self, uuid):
283 build = self.backend.builds.get_by_uuid(uuid)
284 if not build:
285 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
286
287 slug = self.get_argument("repo")
288
289 # Fetch the repository
290 repo = self.current_user.get_repo(build.distro, slug)
291 if not repo:
292 raise tornado.web.HTTPError(400, "Could not find repository '%s'" % slug)
293
294 # Add the build to the repository
295 with self.db.transaction():
296 await repo.add_build(build, user=self.current_user, update=False)
297
298 # Update the repository in the background
299 self.backend.run_task(repo.update)
300
301 self.redirect("/builds/%s" % build.uuid)
302
303
304 class ReposRemoveHandler(base.BaseHandler):
305 @tornado.web.authenticated
306 def get(self, uuid):
307 build = self.backend.builds.get_by_uuid(uuid)
308 if not build:
309 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
310
311 # Raise error when the build is in to repositories
312 if not build.repos:
313 raise tornado.web.HTTPError(400)
314
315 self.render("builds/repos/remove.html", build=build)
316
317 @tornado.web.authenticated
318 async def post(self, uuid):
319 build = self.backend.builds.get_by_uuid(uuid)
320 if not build:
321 raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
322
323 # Fetch all selected repos
324 repos = self.get_arguments("repo")
325
326 # Raise an error if nothing has been selected
327 if not repos:
328 raise tornado.web.HTTPError(400, "No repositories selected")
329
330 # Find all selected repositories
331 repos = [repo for repo in build.repos if repo.slug in repos]
332
333 # Remove build from all repositories
334 with self.db.transaction():
335 for repo in repos:
336 await repo.remove_build(build, user=self.current_user, update=False)
337
338 # Update all repositories in the background
339 for repo in repos:
340 self.backend.run_task(repo.update)
341
342 self.redirect("/builds/%s" % build.uuid)
343
344
345 class GroupShowHandler(base.BaseHandler):
346 def get(self, uuid):
347 group = self.backend.builds.groups.get_by_uuid(uuid)
348 if not group:
349 raise tornado.web.HTTPError(404, "Could not find build group %s" % uuid)
350
351 self.render("builds/groups/show.html", group=group)
352
353
354 class ListModule(ui_modules.UIModule):
355 def render(self, builds):
356 return self.render_string("builds/modules/list.html", builds=builds)
357
358
359 class GroupListModule(ui_modules.UIModule):
360 def render(self, group, limit=None):
361 return self.render_string("builds/groups/modules/list.html",
362 group=group, limit=limit)
363
364
365 class WatchersModule(ui_modules.UIModule):
366 def render(self, build, watchers=None):
367 if watchers is None:
368 watchers = build.watchers
369
370 return self.render_string("builds/modules/watchers.html",
371 build=build, watchers=watchers)