Coverage for /builds/BuildGrid/buildgrid/buildgrid/server/browser/app.py: 91.30%
46 statements
« prev ^ index » next coverage.py v7.4.1, created at 2024-10-04 17:48 +0000
« prev ^ index » next coverage.py v7.4.1, created at 2024-10-04 17:48 +0000
1# Copyright (C) 2021 Bloomberg LP
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# <http://www.apache.org/licenses/LICENSE-2.0>
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
15import os
16import re
17from typing import TYPE_CHECKING, Awaitable, Callable, Optional, cast
19from aiohttp import web
21# TODO Possible upstream fix needed. Module "aiohttp_middlewares" does not explicitly export attribute
22# Possibly fixed with dependency update from 1.2.1 to 2.2.0
23from aiohttp_middlewares import cors_middleware # type: ignore[attr-defined]
24from aiohttp_middlewares.annotations import Handler, Middleware, UrlCollection
26from buildgrid.server.browser import rest_api, utils
27from buildgrid.server.browser.utils import ResponseCache
29if TYPE_CHECKING:
30 from buildgrid.server.app.cli import Context
33def serve_client(base_path: str) -> Callable[[web.Request], Awaitable[web.FileResponse]]:
34 async def _serve_client(request: web.Request) -> web.FileResponse:
35 if not request.match_info["path"]:
36 path = os.path.join(base_path, "index.html")
37 else:
38 path = os.path.join(base_path, request.match_info["path"])
39 if not os.path.exists(path):
40 path = os.path.join(base_path, "index.html")
41 return web.FileResponse(path)
43 return _serve_client
46def cors_middleware_with_error(origins: UrlCollection, urls: UrlCollection, allow_all: bool = False) -> Middleware:
47 assert origins is not None
48 assert urls is not None
49 middleware = cors_middleware(allow_all=allow_all, origins=origins, urls=urls)
51 @web.middleware
52 async def _middleware(request: web.Request, handler: Handler) -> web.StreamResponse:
53 origin = request.headers.get("Origin")
54 headers = utils.get_cors_headers(origin, origins, allow_all)
55 try:
56 return cast(web.StreamResponse, await middleware(request, handler))
58 except web.HTTPNotFound:
59 raise web.HTTPNotFound(headers=headers)
61 except web.HTTPBadRequest:
62 raise web.HTTPBadRequest(headers=headers)
64 except web.HTTPInternalServerError:
65 raise web.HTTPInternalServerError(headers=headers)
67 return _middleware
70def create_app(
71 context: "Context",
72 cache: ResponseCache,
73 cors_origins: UrlCollection,
74 static_path: Optional[str] = None,
75 allow_cancelling_operations: bool = False,
76 tarball_dir: Optional[str] = None,
77) -> web.Application:
78 allow_all = len(cors_origins) == 0
79 app = web.Application(
80 middlewares=(
81 cors_middleware_with_error(allow_all=allow_all, origins=cors_origins, urls=[re.compile(r"^\/api")]),
82 )
83 )
84 routes = [
85 web.get("/api/v1/build_events", rest_api.query_build_events_handler(context)),
86 web.get("/api/v1/operation_filters", rest_api.get_operation_filters_handler),
87 web.get("/api/v1/operations", rest_api.list_operations_handler(context, cache)),
88 web.get("/api/v1/operations/{name}", rest_api.get_operation_handler(context, cache)),
89 web.get(
90 "/api/v1/operations/{name}/request_metadata", rest_api.get_operation_request_metadata_handler(context)
91 ),
92 web.get("/api/v1/operations/{name}/client_identity", rest_api.get_operation_client_identity_handler(context)),
93 web.get("/api/v1/action_results/{hash}/{size_bytes}", rest_api.get_action_result_handler(context, cache)),
94 web.get(
95 "/api/v1/blobs/{hash}/{size_bytes}",
96 rest_api.get_blob_handler(context, cache, allow_all=allow_all, allowed_origins=cors_origins),
97 ),
98 web.get(
99 "/api/v1/tarballs/{hash}_{size_bytes}.tar.gz",
100 rest_api.get_tarball_handler(
101 context, cache, allow_all=allow_all, allowed_origins=cors_origins, tarball_dir=tarball_dir
102 ),
103 ),
104 web.get("/ws/logstream", rest_api.logstream_handler(context)),
105 ]
107 if allow_cancelling_operations:
108 routes.append(web.delete("/api/v1/operations/{name}", rest_api.cancel_operation_handler(context)))
110 if static_path is not None:
111 routes.append(web.get("/{path:.*}", serve_client(static_path)))
112 app.add_routes(routes)
113 return app