| # Copyright 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| |
| from google.appengine.api.modules import modules |
| |
| from components import auth |
| from components import config as config_api |
| from components import decorators |
| from components import endpoints_webapp2 |
| from components import prpc |
| |
| import webapp2 |
| |
| from legacy import api as legacy_api |
| from legacy import api_common |
| from legacy import swarmbucket_api |
| import access |
| import api |
| import bq |
| import bulkproc |
| import config |
| import expiration |
| import model |
| import notifications |
| import resultdb |
| import service |
| import swarming |
| import user |
| |
| README_MD = ( |
| 'https://chromium.googlesource.com/infra/infra/+/master/' |
| 'appengine/cr-buildbucket/README.md' |
| ) |
| |
| |
| class DummyStartHandler(auth.AuthenticatingHandler): # pragma: no cover |
| """Dummy handler for /_ah/start. |
| |
| Derived from AuthenticatingHandler to initialize auth db before the first |
| request. |
| """ |
| |
| @auth.public |
| def get(self): |
| pass |
| |
| |
| class MainHandler(webapp2.RequestHandler): # pragma: no cover |
| """Redirects to README.md.""" |
| |
| def get(self): |
| return self.redirect(README_MD) |
| |
| |
| class CronUpdateBuckets(webapp2.RequestHandler): # pragma: no cover |
| """Updates buckets from configs.""" |
| |
| @decorators.require_cronjob |
| def get(self): |
| config.cron_update_buckets() |
| |
| |
| class BuildRPCHandler(webapp2.RequestHandler): # pragma: no cover |
| """Redirects to API explorer to see the build.""" |
| |
| def get(self, build_id): |
| api_path = '/_ah/api/buildbucket/v1/builds/%s' % build_id |
| return self.redirect(api_path) |
| |
| |
| class RedirectHandlerBase(auth.AuthenticatingHandler): # pragma: no cover |
| |
| def _get_build(self, build_id): |
| try: |
| build_id = int(build_id) |
| except ValueError: |
| self.response.write('invalid build id') |
| self.abort(400) |
| |
| build = model.Build.get_by_id(build_id) |
| can_view = build and user.has_perm(user.PERM_BUILDS_GET, build.bucket_id) |
| |
| if not can_view: |
| if auth.get_current_identity().is_anonymous: |
| self.redirect(self.create_login_url(self.request.url), abort=True) |
| self.response.write('build %d not found' % build_id) |
| self.abort(404) |
| |
| return build |
| |
| |
| class ViewBuildHandler(RedirectHandlerBase): # pragma: no cover |
| """Redirects to Milo build page.""" |
| |
| @auth.public |
| def get(self, build_id): |
| build = self._get_build(build_id) |
| return self.redirect(str(api_common.get_build_url(build))) |
| |
| |
| class ViewLogHandler(RedirectHandlerBase): # pragma: no cover |
| """Redirects to LogDog log content page.""" |
| |
| @staticmethod |
| def _find_log(build_proto, step_name, log_name): |
| for step in build_proto.steps: |
| if step.name == step_name: |
| for log in step.logs: |
| if log.name == log_name: |
| return log |
| break |
| return None |
| |
| @auth.public |
| def get(self, build_id, step_name): |
| log_name = self.request.params.get('log') or 'stdout' |
| build = self._get_build(build_id) |
| bundle = model.BuildBundle.get(build, steps=True) |
| bundle.to_proto(build.proto, load_tags=False) |
| log = self._find_log(build.proto, step_name, log_name) |
| if not log or not log.view_url: |
| self.abort( |
| 404, 'view url for log %r in step %r in build %r not found' % |
| (log_name, step_name, build_id) |
| ) |
| return self.redirect(str(log.view_url)) |
| |
| |
| class UnregisterBuilders(webapp2.RequestHandler): # pragma: no cover |
| """Unregisters builders that didn't have builds for a long time.""" |
| |
| @decorators.require_cronjob |
| def get(self): |
| service.unregister_builders() |
| |
| |
| def _beefy_service_interceptor( |
| request, context, call_details, continuation |
| ): # pragma: no cover |
| """Requires the requester to be a member of "buildbucket-beefy-users" group. |
| """ |
| if auth.is_group_member('buildbucket-beefy-users'): |
| return continuation(request, context, call_details) |
| |
| who = auth.get_current_identity().to_bytes() |
| logging.warning('%s tried to use beefy service', who) |
| context.set_code(prpc.StatusCode.PERMISSION_DENIED) |
| context.set_details( |
| '%s is not allowed to use beefy buildbucket service' % who |
| ) |
| return None |
| |
| |
| def get_frontend_routes(): # pragma: no cover |
| endpoints_services = [ |
| legacy_api.BuildBucketApi, |
| config_api.ConfigApi, |
| swarmbucket_api.SwarmbucketApi, |
| ] |
| routes = [ |
| webapp2.Route(r'/_ah/start', DummyStartHandler), |
| webapp2.Route(r'/', MainHandler), |
| webapp2.Route(r'/b/<build_id:\d+>', BuildRPCHandler), |
| webapp2.Route(r'/build/<build_id:\d+>', ViewBuildHandler), |
| webapp2.Route(r'/builds/<build_id:\d+>', ViewBuildHandler), |
| webapp2.Route(r'/log/<build_id:\d+>/<step_name:.+>', ViewLogHandler), |
| ] |
| routes.extend(endpoints_webapp2.api_routes(endpoints_services)) |
| # /api routes should be removed once clients are hitting /_ah/api. |
| routes.extend( |
| endpoints_webapp2.api_routes(endpoints_services, base_path='/api') |
| ) |
| |
| prpc_server = prpc.Server() |
| prpc_server.add_interceptor(auth.prpc_interceptor) |
| if modules.get_current_module_name() == 'beefy': |
| prpc_server.add_interceptor(_beefy_service_interceptor) |
| prpc_server.add_service(access.AccessServicer()) |
| prpc_server.add_service(api.BuildsApi()) |
| routes += prpc_server.get_routes() |
| routes += prpc_server.get_routes(prefix='/python') |
| |
| return routes |
| |
| |
| def get_backend_routes(): # pragma: no cover |
| prpc_server = prpc.Server() |
| prpc_server.add_interceptor(auth.prpc_interceptor) |
| prpc_server.add_service(api.BuildsApi()) |
| |
| return [ # pragma: no branch |
| webapp2.Route(r'/internal/cron/buildbucket/expire_build_leases', |
| expiration.CronExpireBuildLeases), |
| webapp2.Route(r'/internal/cron/buildbucket/expire_builds', |
| expiration.CronExpireBuilds), |
| webapp2.Route(r'/internal/cron/buildbucket/delete_builds', |
| expiration.CronDeleteBuilds), |
| webapp2.Route(r'/internal/cron/buildbucket/update_buckets', |
| CronUpdateBuckets), |
| webapp2.Route(r'/internal/cron/buildbucket/bq-export', |
| bq.CronExportBuilds), |
| webapp2.Route(r'/internal/cron/buildbucket/unregister-builders', |
| UnregisterBuilders), |
| webapp2.Route(r'/internal/task/buildbucket/notify/<build_id:\d+>', |
| notifications.TaskPublishNotification), |
| webapp2.Route(r'/internal/task/bq/export/<build_id:\d+>', |
| bq.TaskExport), |
| webapp2.Route(r'/internal/task/resultdb/finalize/<build_id:\d+>', |
| resultdb.FinalizeInvocation), |
| ] + (bulkproc.get_routes() + prpc_server.get_routes() |
| + prpc_server.get_routes(prefix='/python')) |