]> git.ipfire.org Git - pbs.git/blobdiff - src/web/builds.py
builds: Show scratch builds for packages and enhance search
[pbs.git] / src / web / builds.py
index e6c57679c2179cd596171b1c08cbc18e19dc581a..070594cd479cc6c054f3963b4afdeda5f8d302d8 100644 (file)
 
 import tornado.web
 
+from ..errors import NoSuchDistroError
+from .. import misc
+
 from . import base
 from . import ui_modules
 
-class BuildBaseHandler(base.BaseHandler):
-       def get_build(self, uuid):
-               build = self.backend.builds.get_by_uuid(uuid)
-               if not build:
-                       raise tornado.web.HTTPError(404, "No such build: %s" % uuid)
+class APIv1IndexHandler(base.APIMixin, base.BaseHandler):
+       # Allow users to create builds
+       allow_users = True
 
-               return build
+       @tornado.web.authenticated
+       async def post(self):
+               # Fetch the upload
+               upload = self.get_argument_upload("upload_id")
+               if not upload:
+                       raise tornado.web.HTTPError(404, "Could not find upload")
 
+               # Check permissions of the upload
+               if not upload.has_perm(self.current_user):
+                       raise tornado.web.HTTPError(403, "No permission for using upload %s" % upload)
 
-class IndexHandler(base.BaseHandler):
-       def get(self):
-               # Fetch the most recent builds
-               builds = self.backend.builds.get_recent(limit=25)
+               # Fetch the repository
+               repo_name = self.get_argument("repo", None)
 
-               self.render("builds/index.html", builds=builds)
+               # Did the uploader request to disable test builds?
+               disable_test_builds = self.get_argument_bool("disable_test_builds")
 
+               with self.db.transaction():
+                       # Import the package
+                       try:
+                               package = await self.backend.packages.create(upload)
 
-class ShowHandler(BuildBaseHandler):
-       def get(self, uuid):
-               build = self.backend.builds.get_by_uuid(uuid)
-               if not build:
-                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
+                       # If the distribution that is coded into the package could not be found,
+                       # we will send that error to the user...
+                       except NoSuchDistroError as e:
+                               raise tornado.web.HTTPError(404, "Could not find distribution: %s" % e)
 
-               # Cache the log.
-               log = build.get_log()
+                       # Find the repository
+                       repo = self.current_user.get_repo(package.distro, repo_name)
+                       if not repo:
+                               raise tornado.web.HTTPError(404, "Could not find repository")
 
-               # Bugs.
-               bugs = build.get_bugs()
+                       # Create a new build
+                       build = await self.backend.builds.create(repo, package,
+                               owner=self.current_user, disable_test_builds=disable_test_builds)
 
-               self.render("builds/show.html", build=build, log=log, pkg=build.pkg,
-                       distro=build.distro, bugs=bugs, repo=build.repo)
+                       # Delete the upload
+                       await upload.delete()
 
+               # Send some data about the build
+               self.finish({
+                       "uuid" : build.uuid,
+                       "name" : "%s" % build,
+               })
 
-class BuildDeleteHandler(BuildBaseHandler):
-       @tornado.web.authenticated
-       def get(self, uuid):
-               build = self.get_build(uuid)
+               # Launch all jobs (in the background)
+               self.backend.run_task(self.backend.builds.launch, [build])
 
-               # Check if the user has got sufficient rights to do this modification.
-               if not build.has_perm(self.current_user):
-                       raise tornado.web.HTTPError(403)
 
-               # Check if the user confirmed the action.
-               confirmed = self.get_argument("confirmed", None)
-               if confirmed:
-                       # Save the name of the package to redirect the user
-                       # to the other packages of that name.
-                       package_name = build.pkg.name
+class IndexHandler(base.BaseHandler):
+       def get(self):
+               # Pagination
+               offset = self.get_argument_int("offset", None) or 0
+               limit  = self.get_argument_int("limit", None) or 25
 
-                       # Delete the build and everything that comes with it.
-                       with self.db.transaction():
-                               build.delete()
+               # Filters
+               name = self.get_argument("name", None)
+               user = self.get_argument_user("user", None)
 
-                       return self.redirect("/package/%s" % package_name)
+               # Fetch the most recent builds
+               if user:
+                       builds = user.get_builds(name, limit=limit, offset=offset)
+               else:
+                       builds = self.backend.builds.get_recent(name=name, limit=limit, offset=offset)
 
-               self.render("build-delete.html", build=build)
+               # Group builds by date
+               builds = misc.group(builds, lambda build: build.created_at.date())
 
+               self.render("builds/index.html", builds=builds, name=name, user=user,
+                       limit=limit, offset=offset)
 
-class BuildBugsHandler(base.BaseHandler):
-       @tornado.web.authenticated
-       def get(self, uuid):
-               build = self.backend.builds.get_by_uuid(uuid)
-               if not build:
-                       raise tornado.web.HTTPError(404, "No such build: %s" % uuid)
 
-               # Check if the user has got the right to alter this build.
-               if not build.has_perm(self.current_user):
-                       raise tornado.web.HTTPError(403)
+class QueueHandler(base.BaseHandler):
+       def get(self):
+               self.render("builds/queue.html", queue=self.backend.jobs.queue)
 
-               # Bugs.
-               fixed_bugs = build.get_bugs()
-               open_bugs = []
 
-               for bug in self.backend.bugzilla.get_bugs_from_component(build.pkg.name):
-                       if bug in fixed_bugs:
-                               continue
+class ShowHandler(base.BaseHandler):
+       async def get(self, uuid):
+               build = self.backend.builds.get_by_uuid(uuid)
+               if not build:
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
+
+               # Fetch any fixed Bugs
+               bugs = await build.get_bugs()
 
-                       open_bugs.append(bug)
+               self.render("builds/show.html", build=build, pkg=build.pkg,
+                       distro=build.distro, bugs=bugs)
 
-               self.render("build-bugs.html", build=build, pkg=build.pkg,
-                       fixed_bugs=fixed_bugs, open_bugs=open_bugs)
 
+class CloneHandler(base.BaseHandler):
        @tornado.web.authenticated
-       def post(self, uuid):
+       def get(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
                if not build:
-                       raise tornado.web.HTTPError(404, "No such build: %s" % uuid)
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               # Check if the user has got the right to alter this build.
-               if not build.has_perm(self.current_user):
-                       raise tornado.web.HTTPError(403)
+               # Fetch repositories
+               try:
+                       repos = self.current_user.repos[build.distro]
+               except KeyError:
+                       repos = []
 
-               action = self.get_argument("action", None)
-               bugid  = self.get_argument("bugid")
+               self.render("builds/clone.html", build=build, repos=repos)
 
-               # Convert the bug id to integer.
-               try:
-                       bugid = int(bugid)
-               except ValueError:
-                       raise tornado.web.HTTPError(400, "Bad bug id given: %s" % bugid)
+       @tornado.web.authenticated
+       async def post(self, uuid):
+               build = self.backend.builds.get_by_uuid(uuid)
+               if not build:
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               if action == "add":
-                       # Add bug to the build.
-                       build.add_bug(bugid, user=self.current_user)
+               # Fetch the repository
+               repo = self.current_user.get_repo(build.distro, self.get_argument("repo"))
 
-               elif action == "remove":
-                       # Remove bug from the build.
-                       build.rem_bug(bugid, user=self.current_user)
+               # Clone the build
+               with self.db.transaction():
+                       clone = await self.backend.builds.create(
+                               repo=repo, package=build.pkg, owner=self.current_user,
+                       )
 
-               else:
-                       raise tornado.web.HTTPError(400, "Unhandled action: %s" % action)
+               # Launch all jobs (in the background)
+               self.backend.run_task(self.backend.builds.launch, [clone])
 
-               self.redirect("/build/%s/bugs" % build.uuid)
+               self.redirect("/builds/%s" % clone.uuid)
 
 
-class BuildStateHandler(base.BaseHandler):
+class DeleteHandler(base.BaseHandler):
+       @tornado.web.authenticated
        def get(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
                if not build:
-                       raise tornado.web.HTTPError(404, "No such build: %s" % uuid)
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               self.render("build-state.html", build=build)
+               # Check permissions
+               if not build.can_be_deleted(self.current_user):
+                       raise tornado.web.HTTPError(403, "%s cannot delete build %s" \
+                               % (self.current_user, build))
+
+               self.render("builds/delete.html", build=build)
 
        @tornado.web.authenticated
-       def post(self, uuid):
+       async def post(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
                if not build:
-                       raise tornado.web.HTTPError(404, "No such build: %s" % uuid)
-
-               # Check if user has the right to perform this action.
-               if not build.has_perm(self.current_user):
-                       raise tornado.web.HTTPError(403, "User is not allowed to perform this action")
-
-               # Check if given state is valid.
-               state = self.get_argument("state", None)
-               if not state in ("broken", "unbreak", "obsolete"):
-                       raise tornado.web.HTTPError(400, "Invalid argument given: %s" % state)
-
-               # XXX this is not quite accurate
-               if state == "unbreak":
-                       state = "stable"
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               rem_from_repo = self.get_argument("rem_from_repo", False)
-               if rem_from_repo == "on":
-                       rem_from_repo = True
+               # Check permissions
+               if not build.can_be_deleted(self.current_user):
+                       raise tornado.web.HTTPError(403, "%s cannot delete build %s" \
+                               % (self.current_user, build))
 
-               # Perform the state change.
-               build.update_state(state, user=self.current_user, remove=rem_from_repo)
+               # Perform Deletion
+               with self.db.transaction():
+                       await build.delete(self.current_user)
 
-               self.redirect("/build/%s" % build.uuid)
+               self.redirect("/builds")
 
 
-class BuildDetailCommentHandler(base.BaseHandler):
+class WatchHandler(base.BaseHandler):
        @tornado.web.authenticated
        def post(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
-
                if not build:
-                       raise tornado.web.HTTPError(404, "Build not found")
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               vote = self.get_argument("vote", "none")
+               with self.db.transaction():
+                       build.add_watcher(self.current_user)
+
+               self.redirect("/builds/%s" % build.uuid)
 
-               if vote == "up":
-                       vote = 1
-               elif vote == "down":
-                       vote = -1
-               else:
-                       vote = 0
 
-               text = self.get_argument("text", "")
+class UnwatchHandler(base.BaseHandler):
+       @tornado.web.authenticated
+       def post(self, uuid):
+               build = self.backend.builds.get_by_uuid(uuid)
+               if not build:
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               # Add a new comment to the build.
-               if text or vote:
-                       build.add_comment(self.current_user, text, vote)
+               with self.db.transaction():
+                       build.remove_watcher(self.current_user)
 
-               # Redirect to the build detail page.
-               self.redirect("/build/%s" % build.uuid)
+               self.redirect("/builds/%s" % build.uuid)
 
 
-class BuildManageHandler(base.BaseHandler):
+class CommentHandler(base.BaseHandler):
        @tornado.web.authenticated
-       def get(self, uuid):
+       async def post(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
                if not build:
-                       raise tornado.web.HTTPError(404, "Build not found: %s" % uuid)
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               mode = "user"
-               if self.current_user.is_admin():
-                       mode = self.get_argument("mode", "user")
+               text = self.get_argument("text")
 
-               # Get the next repo.
-               if build.repo:
-                       next_repo = build.repo.__next__
-               else:
-                       next_repo = build.distro.first_repo
+               # Add a new comment to the build
+               with self.db.transaction():
+                       await build.comment(self.current_user, text)
+
+               # Redirect to the build
+               self.redirect("/builds/%s" % build.uuid)
 
-               self.render("build-manage.html", mode=mode, build=build,
-                       distro=build.distro, repo=build.repo, next_repo=next_repo)
 
+class BugHandler(base.BaseHandler):
        @tornado.web.authenticated
-       def post(self, uuid):
+       async def get(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
                if not build:
-                       raise tornado.web.HTTPError(404, "Build not found: %s" % uuid)
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               # check for sufficient permissions
-               if not build.has_perm(self.current_user):
-                       raise tornado.web.HTTPError(403)
+               # Fetch fields
+               fields = await self.backend.bugzilla.fields
 
-               action = self.get_argument("action")
-               assert action in ("push", "unpush")
+               self.render("builds/bug.html", build=build, fields=fields)
 
-               current_repo = build.repo
+       @tornado.web.authenticated
+       async def post(self, uuid):
+               build = self.backend.builds.get_by_uuid(uuid)
+               if not build:
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               if action == "unpush":
-                       current_repo.rem_build(build, user=self.current_user)
+               # Is the user connected to Bugzilla?
+               if not self.current_user.bugzilla:
+                       raise tornado.web.HTTPError(400, "%s is not connected to Bugzilla" \
+                               % self.current_user)
 
-               elif action == "push":
-                       repo_name = self.get_argument("repo")
-                       next_repo = build.distro.get_repo(repo_name)
+               kwargs = {
+                       # Summary & Description
+                       "summary"     : self.get_argument("summary"),
+                       "description" : self.get_argument("description", None),
+               } | build.bugzilla_fields
 
-                       if not next_repo:
-                               raise tornado.web.HTTPError(404, "No such repository: %s" % next_repo)
+               # Create the bug
+               bug = await self.current_user.bugzilla.create_bug(**kwargs)
 
-                       if not self.current_user.is_admin():
-                               if not distro.repo.__next__ == next_repo:
-                                       raise tornado.web.HTTPError(403)
+               # Send the attachments
+               for job in build.jobs:
+                       if not self.get_argument_bool("attach_log_%s" % job.uuid):
+                               continue
 
-                       if current_repo:
-                               current_repo.move_build(build, next_repo, user=self.current_user)
-                       else:
-                               next_repo.add_build(build, user=self.current_user)
+                       # Open the logfile
+                       try:
+                               log = await job.open_log()
+                       except FileNotFoundError as e:
+                               log.warning("Could not open log file for %s" % job)
+                               continue
+
+                       # Attach it to the bug
+                       await bug.attach(summary="Log file for %s" % job, filename="%s.log" % job,
+                               data=log, content_type="text/plain")
 
-               self.redirect("/build/%s" % build.uuid)
+               self.render("builds/bug-created.html", build=build, bug=bug)
 
 
-class BuildWatchersHandler(base.BaseHandler):
+class ReposAddHandler(base.BaseHandler):
+       @tornado.web.authenticated
        def get(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
+               if not build:
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
+               # Fetch all available repositories
+               try:
+                       repos = self.current_user.repos[build.distro]
+               except KeyError:
+                       repos = None
+
+               self.render("builds/repos/add.html", build=build, repos=repos)
+
+       @tornado.web.authenticated
+       async def post(self, uuid):
+               build = self.backend.builds.get_by_uuid(uuid)
                if not build:
-                       raise tornado.web.HTTPError(404, "Build not found")
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               # Get a list of all watchers and sort them by their realname.
-               watchers = build.get_watchers()
-               watchers.sort(key=lambda watcher: watcher.realname)
+               slug = self.get_argument("repo")
 
-               self.render("builds-watchers-list.html", build=build, watchers=watchers)
+               # Fetch the repository
+               repo = self.current_user.get_repo(build.distro, slug)
+               if not repo:
+                       raise tornado.web.HTTPError(400, "Could not find repository '%s'" % slug)
 
+               # Add the build to the repository
+               with self.db.transaction():
+                       await repo.add_build(build, user=self.current_user)
 
-class BuildWatchersAddHandler(base.BaseHandler):
+               self.redirect("/builds/%s" % build.uuid)
+
+
+class ReposRemoveHandler(base.BaseHandler):
        @tornado.web.authenticated
-       def get(self, uuid, error_msg=None):
+       def get(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
-
                if not build:
-                       raise tornado.web.HTTPError(404, "Build not found")
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               # Get a list of all users that are currently watching this build.
-               watchers = build.get_watchers()
+               # Raise error when the build is in to repositories
+               if not build.repos:
+                       raise tornado.web.HTTPError(400)
 
-               self.render("builds-watchers-add.html", error_msg=error_msg,
-                       build=build, users=self.backend.users, watchers=watchers)
+               self.render("builds/repos/remove.html", build=build)
 
        @tornado.web.authenticated
-       def post(self, uuid):
+       async def post(self, uuid):
                build = self.backend.builds.get_by_uuid(uuid)
-
                if not build:
-                       raise tornado.web.HTTPError(404, "Build not found")
+                       raise tornado.web.HTTPError(404, "Could not find build %s" % uuid)
 
-               # Get the user id of the new watcher.
-               user_id = self.current_user.id
+               # Fetch all selected repos
+               repos = self.get_arguments("repo")
 
-               if self.current_user.is_admin():
-                       user_id = self.get_argument("user_id", self.current_user.id)
-               assert user_id
+               # Raise an error if nothing has been selected
+               if not repos:
+                       raise tornado.web.HTTPError(400, "No repositories selected")
 
-               user = self.backend.users.get_by_id(user_id)
-               if not user:
-                       _ = self.locale.translate
-                       error_msg = _("User not found.")
+               # Find all selected repositories
+               repos = [repo for repo in build.repos if repo.slug in repos]
 
-                       return self.get(uuid, error_msg=error_msg)
+               # Remove build from all repositories
+               with self.db.transaction():
+                       for repo in repos:
+                               await repo.remove_build(build, user=self.current_user)
 
-               # Actually add the user to the list of watchers.
-               build.add_watcher(user)
+               self.redirect("/builds/%s" % build.uuid)
 
-               # Send user back to the build detail page.
-               self.redirect("/build/%s" % build.uuid)
 
+class GroupShowHandler(base.BaseHandler):
+       def get(self, uuid):
+               group = self.backend.builds.groups.get_by_uuid(uuid)
+               if not group:
+                       raise tornado.web.HTTPError(404, "Could not find build group %s" % uuid)
 
-class BuildListHandler(base.BaseHandler):
-       def get(self):
-               builder = self.get_argument("builder", None)
-               state = self.get_argument("state", None)
+               self.render("builds/groups/show.html", group=group)
 
-               builds = self.backend.builds.get_latest(state=state, builder=builder,
-                       limit=25)
 
-               self.render("build-list.html", builds=builds)
+class ListModule(ui_modules.UIModule):
+       def render(self, builds, limit=None, more_url=None):
+               rest = None
 
+               # Limit builds
+               if limit:
+                       builds, rest = builds[:limit], builds[limit:]
 
-class ListModule(ui_modules.UIModule):
-       def render(self, builds):
-               return self.render_string("builds/modules/list.html", builds=builds)
+               return self.render_string("builds/modules/list.html", builds=builds,
+                       rest=rest, more_url=more_url)
+
+
+class GroupListModule(ui_modules.UIModule):
+       def render(self, group, limit=None):
+               return self.render_string("builds/groups/modules/list.html",
+                       group=group, limit=limit)
+
+
+class WatchersModule(ui_modules.UIModule):
+       def render(self, build, watchers=None):
+               if watchers is None:
+                       watchers = build.watchers
+
+               return self.render_string("builds/modules/watchers.html",
+                       build=build, watchers=watchers)