sovereign 0.14.2__py3-none-any.whl → 1.0.0a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of sovereign might be problematic. Click here for more details.
- sovereign/__init__.py +17 -78
- sovereign/app.py +74 -59
- sovereign/cache/__init__.py +245 -0
- sovereign/cache/backends/__init__.py +110 -0
- sovereign/cache/backends/s3.py +161 -0
- sovereign/cache/filesystem.py +74 -0
- sovereign/cache/types.py +17 -0
- sovereign/configuration.py +607 -0
- sovereign/constants.py +1 -0
- sovereign/context.py +271 -100
- sovereign/dynamic_config/__init__.py +112 -0
- sovereign/dynamic_config/deser.py +78 -0
- sovereign/dynamic_config/loaders.py +120 -0
- sovereign/error_info.py +61 -0
- sovereign/events.py +49 -0
- sovereign/logging/access_logger.py +85 -0
- sovereign/logging/application_logger.py +54 -0
- sovereign/logging/base_logger.py +41 -0
- sovereign/logging/bootstrapper.py +36 -0
- sovereign/logging/types.py +10 -0
- sovereign/middlewares.py +8 -7
- sovereign/modifiers/lib.py +2 -1
- sovereign/rendering.py +124 -0
- sovereign/rendering_common.py +91 -0
- sovereign/response_class.py +18 -0
- sovereign/server.py +123 -28
- sovereign/statistics.py +19 -21
- sovereign/templates/base.html +59 -46
- sovereign/templates/resources.html +203 -102
- sovereign/testing/loaders.py +9 -0
- sovereign/{modifiers/test.py → testing/modifiers.py} +0 -2
- sovereign/tracing.py +103 -0
- sovereign/types.py +304 -0
- sovereign/utils/auth.py +27 -13
- sovereign/utils/crypto/__init__.py +0 -0
- sovereign/utils/crypto/crypto.py +135 -0
- sovereign/utils/crypto/suites/__init__.py +21 -0
- sovereign/utils/crypto/suites/aes_gcm_cipher.py +42 -0
- sovereign/utils/crypto/suites/base_cipher.py +21 -0
- sovereign/utils/crypto/suites/disabled_cipher.py +25 -0
- sovereign/utils/crypto/suites/fernet_cipher.py +29 -0
- sovereign/utils/dictupdate.py +3 -2
- sovereign/utils/eds.py +40 -22
- sovereign/utils/entry_point_loader.py +18 -0
- sovereign/utils/mock.py +60 -17
- sovereign/utils/resources.py +17 -0
- sovereign/utils/templates.py +4 -2
- sovereign/utils/timer.py +5 -3
- sovereign/utils/version_info.py +8 -0
- sovereign/utils/weighted_clusters.py +2 -1
- sovereign/v2/__init__.py +0 -0
- sovereign/v2/data/data_store.py +621 -0
- sovereign/v2/data/render_discovery_response.py +24 -0
- sovereign/v2/data/repositories.py +90 -0
- sovereign/v2/data/utils.py +33 -0
- sovereign/v2/data/worker_queue.py +273 -0
- sovereign/v2/jobs/refresh_context.py +117 -0
- sovereign/v2/jobs/render_discovery_job.py +145 -0
- sovereign/v2/logging.py +81 -0
- sovereign/v2/types.py +41 -0
- sovereign/v2/web.py +101 -0
- sovereign/v2/worker.py +199 -0
- sovereign/views/__init__.py +7 -0
- sovereign/views/api.py +82 -0
- sovereign/views/crypto.py +46 -15
- sovereign/views/discovery.py +52 -67
- sovereign/views/healthchecks.py +107 -20
- sovereign/views/interface.py +173 -117
- sovereign/worker.py +193 -0
- {sovereign-0.14.2.dist-info → sovereign-1.0.0a4.dist-info}/METADATA +81 -73
- sovereign-1.0.0a4.dist-info/RECORD +85 -0
- {sovereign-0.14.2.dist-info → sovereign-1.0.0a4.dist-info}/WHEEL +1 -1
- sovereign-1.0.0a4.dist-info/entry_points.txt +46 -0
- sovereign_files/__init__.py +0 -0
- sovereign_files/static/darkmode.js +51 -0
- sovereign_files/static/node_expression.js +42 -0
- sovereign_files/static/panel.js +76 -0
- sovereign_files/static/resources.css +246 -0
- sovereign_files/static/resources.js +642 -0
- sovereign_files/static/sass/style.scss +33 -0
- sovereign_files/static/style.css +16143 -0
- sovereign_files/static/style.css.map +1 -0
- sovereign/config_loader.py +0 -225
- sovereign/discovery.py +0 -175
- sovereign/logs.py +0 -131
- sovereign/schemas.py +0 -715
- sovereign/sources/__init__.py +0 -3
- sovereign/sources/file.py +0 -21
- sovereign/sources/inline.py +0 -38
- sovereign/sources/lib.py +0 -40
- sovereign/sources/poller.py +0 -298
- sovereign/static/sass/style.scss +0 -27
- sovereign/static/style.css +0 -13553
- sovereign/templates/ul_filter.html +0 -22
- sovereign/utils/crypto.py +0 -64
- sovereign/views/admin.py +0 -120
- sovereign-0.14.2.dist-info/LICENSE.txt +0 -13
- sovereign-0.14.2.dist-info/RECORD +0 -45
- sovereign-0.14.2.dist-info/entry_points.txt +0 -10
sovereign/views/discovery.py
CHANGED
|
@@ -1,44 +1,21 @@
|
|
|
1
|
-
from typing import Dict
|
|
2
|
-
|
|
3
1
|
from fastapi import Body, Header
|
|
4
|
-
from fastapi.routing import APIRouter
|
|
5
2
|
from fastapi.responses import Response
|
|
3
|
+
from fastapi.routing import APIRouter
|
|
6
4
|
|
|
7
|
-
from sovereign import
|
|
8
|
-
from sovereign.
|
|
9
|
-
from sovereign.
|
|
5
|
+
from sovereign import cache, config, logs
|
|
6
|
+
from sovereign.cache.types import Entry
|
|
7
|
+
from sovereign.types import (
|
|
10
8
|
DiscoveryRequest,
|
|
11
9
|
DiscoveryResponse,
|
|
12
|
-
ProcessedTemplate,
|
|
13
10
|
)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
type_urls = {
|
|
19
|
-
"v2": {
|
|
20
|
-
"listeners": "type.googleapis.com/envoy.api.v2.Listener",
|
|
21
|
-
"clusters": "type.googleapis.com/envoy.api.v2.Cluster",
|
|
22
|
-
"endpoints": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment",
|
|
23
|
-
"secrets": "type.googleapis.com/envoy.api.v2.auth.Secret",
|
|
24
|
-
"routes": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
|
|
25
|
-
"scoped-routes": "type.googleapis.com/envoy.api.v2.ScopedRouteConfiguration",
|
|
26
|
-
},
|
|
27
|
-
"v3": {
|
|
28
|
-
"listeners": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
|
29
|
-
"clusters": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
|
30
|
-
"endpoints": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
|
31
|
-
"secrets": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
|
|
32
|
-
"routes": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
|
|
33
|
-
"scoped-routes": "type.googleapis.com/envoy.config.route.v3.ScopedRouteConfiguration",
|
|
34
|
-
"runtime": "type.googleapis.com/envoy.service.runtime.v3.Runtime",
|
|
35
|
-
},
|
|
36
|
-
}
|
|
11
|
+
from sovereign.utils.auth import authenticate
|
|
12
|
+
from sovereign.v2.web import wait_for_discovery_response
|
|
13
|
+
from sovereign.views import reader
|
|
37
14
|
|
|
38
15
|
|
|
39
16
|
def response_headers(
|
|
40
|
-
discovery_request: DiscoveryRequest, response:
|
|
41
|
-
) ->
|
|
17
|
+
discovery_request: DiscoveryRequest, response: Entry, xds: str
|
|
18
|
+
) -> dict[str, str]:
|
|
42
19
|
return {
|
|
43
20
|
"X-Sovereign-Client-Build": discovery_request.envoy_version,
|
|
44
21
|
"X-Sovereign-Client-Version": discovery_request.version_info,
|
|
@@ -49,6 +26,9 @@ def response_headers(
|
|
|
49
26
|
}
|
|
50
27
|
|
|
51
28
|
|
|
29
|
+
router = APIRouter()
|
|
30
|
+
|
|
31
|
+
|
|
52
32
|
@router.post(
|
|
53
33
|
"/{version}/discovery:{xds_type}",
|
|
54
34
|
summary="Envoy Discovery Service Endpoint",
|
|
@@ -62,46 +42,51 @@ def response_headers(
|
|
|
62
42
|
async def discovery_response(
|
|
63
43
|
version: str,
|
|
64
44
|
xds_type: str,
|
|
65
|
-
|
|
45
|
+
xds_req: DiscoveryRequest = Body(...),
|
|
66
46
|
host: str = Header("no_host_provided"),
|
|
67
47
|
) -> Response:
|
|
68
|
-
|
|
69
|
-
response = perform_discovery(discovery_request, version, xds_type, skip_auth=False)
|
|
70
|
-
logs.queue_log_fields(
|
|
71
|
-
XDS_RESOURCES=discovery_request.resource_names,
|
|
72
|
-
XDS_ENVOY_VERSION=discovery_request.envoy_version,
|
|
73
|
-
XDS_CLIENT_VERSION=discovery_request.version_info,
|
|
74
|
-
XDS_SERVER_VERSION=response.version,
|
|
75
|
-
)
|
|
76
|
-
headers = response_headers(discovery_request, response, xds_type)
|
|
48
|
+
authenticate(xds_req)
|
|
77
49
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
50
|
+
# Pack additional info into the request
|
|
51
|
+
xds_req.desired_controlplane = host
|
|
52
|
+
xds_req.resource_type = xds_type
|
|
53
|
+
xds_req.api_version = version
|
|
54
|
+
if xds_req.error_detail:
|
|
55
|
+
logs.access_logger.queue_log_fields(
|
|
56
|
+
XDS_ERROR_DETAIL=xds_req.error_detail.message
|
|
85
57
|
)
|
|
86
|
-
|
|
58
|
+
logs.access_logger.queue_log_fields(
|
|
59
|
+
XDS_RESOURCES=xds_req.resource_names,
|
|
60
|
+
XDS_ENVOY_VERSION=xds_req.envoy_version,
|
|
61
|
+
XDS_CLIENT_VERSION=xds_req.version_info,
|
|
62
|
+
)
|
|
87
63
|
|
|
64
|
+
def handle_response(entry: cache.Entry):
|
|
65
|
+
logs.access_logger.queue_log_fields(
|
|
66
|
+
XDS_SERVER_VERSION=entry.version,
|
|
67
|
+
)
|
|
68
|
+
headers = response_headers(xds_req, entry, xds_type)
|
|
69
|
+
if entry.len == 0:
|
|
70
|
+
return Response(status_code=404, headers=headers)
|
|
71
|
+
if entry.version == xds_req.version_info:
|
|
72
|
+
return Response(status_code=304, headers=headers)
|
|
73
|
+
return Response(entry.text, media_type="application/json", headers=headers)
|
|
88
74
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
for resource in template.resources:
|
|
101
|
-
if not resource.get("@type"):
|
|
102
|
-
resource["@type"] = type_url
|
|
103
|
-
return template
|
|
75
|
+
if config.worker_v2_enabled:
|
|
76
|
+
# we're set up to use v2 of the worker
|
|
77
|
+
response = await wait_for_discovery_response(xds_req)
|
|
78
|
+
if response is not None:
|
|
79
|
+
entry = Entry(
|
|
80
|
+
text=response.model_dump_json(indent=None),
|
|
81
|
+
len=len(response.resources),
|
|
82
|
+
version=response.version_info,
|
|
83
|
+
node=xds_req.node,
|
|
84
|
+
)
|
|
85
|
+
return handle_response(entry)
|
|
104
86
|
|
|
87
|
+
else:
|
|
88
|
+
entry: Entry | None
|
|
89
|
+
if entry := await reader.blocking_read(xds_req): # ty: ignore[possibly-missing-attribute]
|
|
90
|
+
return handle_response(entry)
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
return Response(status_code=304, headers=headers)
|
|
92
|
+
return Response(content="Something went wrong", status_code=500)
|
sovereign/views/healthchecks.py
CHANGED
|
@@ -1,38 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import pydantic
|
|
4
|
+
import requests
|
|
5
|
+
from fastapi import Query, Request, Response
|
|
6
|
+
from fastapi.responses import JSONResponse, PlainTextResponse
|
|
3
7
|
from fastapi.routing import APIRouter
|
|
4
|
-
from
|
|
5
|
-
from sovereign import XDS_TEMPLATES, __version__, json_response_class
|
|
6
|
-
from sovereign.utils.mock import mock_discovery_request
|
|
7
|
-
from sovereign.views.discovery import perform_discovery
|
|
8
|
+
from typing_extensions import Annotated, Literal
|
|
8
9
|
|
|
10
|
+
from sovereign import __version__
|
|
11
|
+
from sovereign.configuration import XDS_TEMPLATES, config
|
|
12
|
+
from sovereign.response_class import json_response_class
|
|
13
|
+
from sovereign.utils.mock import mock_discovery_request
|
|
14
|
+
from sovereign.v2.web import wait_for_discovery_response
|
|
15
|
+
from sovereign.views import reader
|
|
9
16
|
|
|
10
17
|
router = APIRouter()
|
|
11
18
|
|
|
19
|
+
State = Literal["FAIL"] | Literal["OK"]
|
|
20
|
+
Message = str
|
|
21
|
+
CheckResult = tuple[State, Message] | State
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeepCheckResult(pydantic.BaseModel):
|
|
25
|
+
templates: dict[str, CheckResult] = pydantic.Field(default_factory=dict)
|
|
26
|
+
worker: CheckResult = pydantic.Field(default=("FAIL", "Worker unavailable"))
|
|
27
|
+
|
|
28
|
+
def response(self) -> PlainTextResponse:
|
|
29
|
+
return PlainTextResponse(content=self.message, status_code=self.status)
|
|
30
|
+
|
|
31
|
+
def json_response(self) -> JSONResponse:
|
|
32
|
+
return json_response_class(content=self.model_dump(), status_code=self.status)
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def message(self) -> str:
|
|
36
|
+
msg = "Templates:\n"
|
|
37
|
+
for template, result in sorted(self.templates.items()):
|
|
38
|
+
msg += f"* {template} {result}\n"
|
|
39
|
+
msg += f"Worker: {self.worker}\n"
|
|
40
|
+
return msg
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def status(self) -> int:
|
|
44
|
+
if self.is_err():
|
|
45
|
+
return 500
|
|
46
|
+
return 200
|
|
47
|
+
|
|
48
|
+
def is_err(self):
|
|
49
|
+
for result in self.templates.values():
|
|
50
|
+
if result[0] == "FAIL":
|
|
51
|
+
return True
|
|
52
|
+
if self.worker[0] == "FAIL":
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
12
56
|
|
|
13
57
|
@router.get("/healthcheck", summary="Healthcheck (Does the server respond to HTTP?)")
|
|
14
58
|
async def health_check() -> Response:
|
|
15
|
-
return PlainTextResponse("OK")
|
|
59
|
+
return PlainTextResponse("OK", status_code=200)
|
|
16
60
|
|
|
17
61
|
|
|
18
62
|
@router.get(
|
|
19
63
|
"/deepcheck",
|
|
20
|
-
summary="Deepcheck (Can the server render all
|
|
21
|
-
response_class=json_response_class,
|
|
64
|
+
summary="Deepcheck (Can the server render all default templates?)",
|
|
22
65
|
)
|
|
23
|
-
async def deep_check(
|
|
24
|
-
|
|
25
|
-
|
|
66
|
+
async def deep_check(
|
|
67
|
+
request: Request,
|
|
68
|
+
worker_attempts: Annotated[
|
|
69
|
+
int,
|
|
70
|
+
Query(
|
|
71
|
+
description="How many times to try to contact the worker before giving up",
|
|
72
|
+
),
|
|
73
|
+
] = 5,
|
|
74
|
+
envoy_service_cluster: Annotated[
|
|
75
|
+
str,
|
|
76
|
+
Query(
|
|
77
|
+
description="Which service cluster to use when checking if a template can be rendered",
|
|
78
|
+
),
|
|
79
|
+
] = "*",
|
|
80
|
+
) -> Response:
|
|
81
|
+
result = DeepCheckResult()
|
|
26
82
|
for template in list(XDS_TEMPLATES["default"].keys()):
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
83
|
+
discovery_request = mock_discovery_request(
|
|
84
|
+
"v3",
|
|
85
|
+
template,
|
|
86
|
+
expressions=[f"cluster={envoy_service_cluster}"],
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if config.worker_v2_enabled:
|
|
90
|
+
# we're set up to use v2 of the worker
|
|
91
|
+
response = await wait_for_discovery_response(discovery_request)
|
|
92
|
+
if response:
|
|
93
|
+
result.templates[template] = "OK"
|
|
94
|
+
else:
|
|
95
|
+
result.templates[template] = (
|
|
96
|
+
"FAIL",
|
|
97
|
+
f"Failed to render {template}",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
result.worker = "OK"
|
|
33
101
|
else:
|
|
34
|
-
|
|
35
|
-
|
|
102
|
+
try:
|
|
103
|
+
_ = await reader.blocking_read(discovery_request) # ty: ignore[possibly-missing-attribute]
|
|
104
|
+
result.templates[template] = "OK"
|
|
105
|
+
except Exception as e:
|
|
106
|
+
result.templates[template] = ("FAIL", f"Failed {template}: {str(e)}")
|
|
107
|
+
|
|
108
|
+
if not config.worker_v2_enabled:
|
|
109
|
+
for attempt in range(worker_attempts):
|
|
110
|
+
try:
|
|
111
|
+
worker_health = requests.get("http://localhost:9080/health")
|
|
112
|
+
if worker_health.ok:
|
|
113
|
+
result.worker = "OK"
|
|
114
|
+
break
|
|
115
|
+
except Exception as e:
|
|
116
|
+
result.worker = ("FAIL", str(e))
|
|
117
|
+
await asyncio.sleep(attempt)
|
|
118
|
+
|
|
119
|
+
if "json" in request.headers.get("Accept", ""):
|
|
120
|
+
return result.json_response()
|
|
121
|
+
|
|
122
|
+
return result.response()
|
|
36
123
|
|
|
37
124
|
|
|
38
125
|
@router.get("/version", summary="Display the current version of Sovereign")
|
sovereign/views/interface.py
CHANGED
|
@@ -1,162 +1,211 @@
|
|
|
1
|
-
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
2
3
|
from collections import defaultdict
|
|
3
|
-
from
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Cookie, Path, Query
|
|
4
7
|
from fastapi.encoders import jsonable_encoder
|
|
5
8
|
from fastapi.requests import Request
|
|
6
|
-
from fastapi.responses import
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
|
|
10
|
-
from sovereign
|
|
11
|
-
from sovereign.
|
|
9
|
+
from fastapi.responses import HTMLResponse, JSONResponse, Response
|
|
10
|
+
from starlette.templating import Jinja2Templates
|
|
11
|
+
from structlog.typing import FilteringBoundLogger
|
|
12
|
+
|
|
13
|
+
from sovereign import __version__
|
|
14
|
+
from sovereign.cache import Entry
|
|
15
|
+
from sovereign.configuration import XDS_TEMPLATES, ConfiguredResourceTypes, config
|
|
16
|
+
from sovereign.response_class import json_response_class
|
|
17
|
+
from sovereign.utils.mock import NodeExpressionError, mock_discovery_request
|
|
18
|
+
from sovereign.utils.resources import get_package_file
|
|
19
|
+
from sovereign.v2.logging import get_named_logger
|
|
20
|
+
from sovereign.v2.web import wait_for_discovery_response
|
|
21
|
+
from sovereign.views import reader
|
|
12
22
|
|
|
13
23
|
router = APIRouter()
|
|
14
24
|
|
|
15
|
-
all_types = [t.value for t in
|
|
25
|
+
all_types = [t.value for t in ConfiguredResourceTypes]
|
|
26
|
+
|
|
27
|
+
html_templates = Jinja2Templates(
|
|
28
|
+
directory=str(get_package_file("sovereign", "templates"))
|
|
29
|
+
)
|
|
16
30
|
|
|
17
31
|
|
|
18
32
|
@router.get("/")
|
|
19
|
-
|
|
33
|
+
@router.get("/resources")
|
|
34
|
+
async def ui_main(request: Request) -> HTMLResponse:
|
|
20
35
|
try:
|
|
21
36
|
return html_templates.TemplateResponse(
|
|
37
|
+
request=request,
|
|
22
38
|
name="base.html",
|
|
23
39
|
media_type="text/html",
|
|
24
|
-
context={
|
|
25
|
-
"request": request,
|
|
26
|
-
"all_types": all_types,
|
|
27
|
-
"last_update": str(poller.last_updated),
|
|
28
|
-
},
|
|
40
|
+
context={"all_types": all_types, "sovereign_version": __version__},
|
|
29
41
|
)
|
|
30
42
|
except IndexError:
|
|
31
43
|
return html_templates.TemplateResponse(
|
|
44
|
+
request=request,
|
|
32
45
|
name="err.html",
|
|
33
46
|
media_type="text/html",
|
|
34
47
|
context={
|
|
35
|
-
"request": request,
|
|
36
48
|
"title": "No resource types configured",
|
|
37
|
-
"message":
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
"message": (
|
|
50
|
+
"A template should be defined for every resource "
|
|
51
|
+
"type that you want your envoy proxies to discover."
|
|
52
|
+
),
|
|
53
|
+
"doc_link": "https://developer.atlassian.com/platform/sovereign/tutorial/templates/#templates",
|
|
54
|
+
"sovereign_version": __version__,
|
|
40
55
|
},
|
|
41
56
|
)
|
|
42
57
|
|
|
43
58
|
|
|
44
|
-
|
|
45
|
-
"/set-version", summary="Filter the UI by a certain Envoy Version (stores a Cookie)"
|
|
46
|
-
)
|
|
47
|
-
async def set_envoy_version(
|
|
48
|
-
request: Request,
|
|
49
|
-
version: str = Query(
|
|
50
|
-
"__any__", title="The clients envoy version to emulate in this XDS request"
|
|
51
|
-
),
|
|
52
|
-
) -> Response:
|
|
53
|
-
url = request.headers.get("Referer", "/ui")
|
|
54
|
-
response = RedirectResponse(url=url)
|
|
55
|
-
response.set_cookie(key="envoy_version", value=version)
|
|
56
|
-
return response
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@router.get(
|
|
60
|
-
"/set-service-cluster",
|
|
61
|
-
summary="Filter the UI by a certain service cluster (stores a Cookie)",
|
|
62
|
-
)
|
|
63
|
-
async def set_service_cluster(
|
|
64
|
-
request: Request,
|
|
65
|
-
service_cluster: str = Query(
|
|
66
|
-
"__any__", title="The clients envoy version to emulate in this XDS request"
|
|
67
|
-
),
|
|
68
|
-
) -> Response:
|
|
69
|
-
url = request.headers.get("Referer", "/ui")
|
|
70
|
-
response = RedirectResponse(url=url)
|
|
71
|
-
response.set_cookie(key="service_cluster", value=service_cluster)
|
|
72
|
-
return response
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
# noinspection DuplicatedCode
|
|
75
60
|
@router.get(
|
|
76
61
|
"/resources/{xds_type}", summary="List available resources for a given xDS type"
|
|
77
62
|
)
|
|
78
63
|
async def resources(
|
|
79
64
|
request: Request,
|
|
80
|
-
xds_type: str = Path(
|
|
81
|
-
"clusters", title="xDS type", description="The type of request"
|
|
82
|
-
),
|
|
65
|
+
xds_type: str = Path(title="xDS type", description="The type of request"),
|
|
83
66
|
region: str = Query(
|
|
84
67
|
None, title="The clients region to emulate in this XDS request"
|
|
85
68
|
),
|
|
86
69
|
api_version: str = Query("v2", title="The desired Envoy API version"),
|
|
87
|
-
|
|
88
|
-
"
|
|
70
|
+
node_expression: str = Cookie(
|
|
71
|
+
"cluster=*", title="Node expression to filter resources with"
|
|
89
72
|
),
|
|
90
73
|
envoy_version: str = Cookie(
|
|
91
74
|
"__any__", title="The clients envoy version to emulate in this XDS request"
|
|
92
75
|
),
|
|
93
|
-
|
|
76
|
+
debug: int = Query(0, title="Show debug information on errors"),
|
|
77
|
+
) -> HTMLResponse:
|
|
78
|
+
logger: FilteringBoundLogger = get_named_logger(
|
|
79
|
+
f"{__name__}.{resources.__qualname__} ({__file__})",
|
|
80
|
+
level=logging.DEBUG,
|
|
81
|
+
)
|
|
82
|
+
|
|
94
83
|
ret: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
95
84
|
try:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
),
|
|
103
|
-
api_version=api_version,
|
|
104
|
-
resource_type=xds_type,
|
|
105
|
-
skip_auth=True,
|
|
85
|
+
mock_request = mock_discovery_request(
|
|
86
|
+
api_version,
|
|
87
|
+
xds_type,
|
|
88
|
+
version=envoy_version,
|
|
89
|
+
region=region,
|
|
90
|
+
expressions=node_expression.split(),
|
|
106
91
|
)
|
|
107
|
-
|
|
108
|
-
|
|
92
|
+
clear_cookie = False
|
|
93
|
+
error = None
|
|
94
|
+
except NodeExpressionError as e:
|
|
95
|
+
mock_request = mock_discovery_request(
|
|
96
|
+
api_version,
|
|
97
|
+
xds_type,
|
|
98
|
+
version=envoy_version,
|
|
99
|
+
region=region,
|
|
100
|
+
)
|
|
101
|
+
clear_cookie = True
|
|
102
|
+
error = str(e)
|
|
103
|
+
|
|
104
|
+
logger.debug("Making mock request", mock_request=mock_request)
|
|
105
|
+
|
|
106
|
+
entry: Entry | None = None
|
|
107
|
+
|
|
108
|
+
if config.worker_v2_enabled:
|
|
109
|
+
# we're set up to use v2 of the worker
|
|
110
|
+
discovery_response = await wait_for_discovery_response(mock_request)
|
|
111
|
+
if discovery_response is not None:
|
|
112
|
+
entry = Entry(
|
|
113
|
+
text=discovery_response.model_dump_json(indent=None),
|
|
114
|
+
len=len(discovery_response.resources),
|
|
115
|
+
version=discovery_response.version_info,
|
|
116
|
+
node=mock_request.node,
|
|
117
|
+
)
|
|
118
|
+
|
|
109
119
|
else:
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
entry = await reader.blocking_read(mock_request) # ty: ignore[possibly-missing-attribute]
|
|
121
|
+
|
|
122
|
+
if entry:
|
|
123
|
+
ret["resources"] = json.loads(entry.text).get("resources", [])
|
|
124
|
+
|
|
125
|
+
resp = html_templates.TemplateResponse(
|
|
126
|
+
request=request,
|
|
112
127
|
name="resources.html",
|
|
113
128
|
media_type="text/html",
|
|
114
129
|
context={
|
|
130
|
+
"show_debuginfo": True if debug else False,
|
|
131
|
+
"discovery_response": entry,
|
|
132
|
+
"discovery_request": mock_request,
|
|
115
133
|
"resources": ret["resources"],
|
|
116
|
-
"request": request,
|
|
117
134
|
"resource_type": xds_type,
|
|
118
135
|
"all_types": all_types,
|
|
119
136
|
"version": envoy_version,
|
|
120
137
|
"available_versions": list(XDS_TEMPLATES.keys()),
|
|
121
|
-
"
|
|
122
|
-
"
|
|
123
|
-
"last_update": str(poller.last_updated),
|
|
138
|
+
"error": error,
|
|
139
|
+
"sovereign_version": __version__,
|
|
124
140
|
},
|
|
125
141
|
)
|
|
142
|
+
if clear_cookie:
|
|
143
|
+
resp.delete_cookie("node_expression", path="/ui/resources/")
|
|
144
|
+
return resp
|
|
126
145
|
|
|
127
146
|
|
|
147
|
+
# noinspection DuplicatedCode
|
|
128
148
|
@router.get(
|
|
129
149
|
"/resources/{xds_type}/{resource_name}",
|
|
130
150
|
summary="Return JSON representation of a resource",
|
|
131
151
|
)
|
|
132
152
|
async def resource(
|
|
133
|
-
xds_type: str = Path(
|
|
134
|
-
"clusters", title="xDS type", description="The type of request"
|
|
135
|
-
),
|
|
153
|
+
xds_type: str = Path(title="xDS type", description="The type of request"),
|
|
136
154
|
resource_name: str = Path(..., title="Name of the resource to view"),
|
|
137
155
|
region: str = Query(
|
|
138
156
|
None, title="The clients region to emulate in this XDS request"
|
|
139
157
|
),
|
|
140
158
|
api_version: str = Query("v2", title="The desired Envoy API version"),
|
|
141
|
-
|
|
142
|
-
"
|
|
159
|
+
node_expression: str = Cookie(
|
|
160
|
+
"cluster=*", title="Node expression to filter resources with"
|
|
143
161
|
),
|
|
144
162
|
envoy_version: str = Cookie(
|
|
145
163
|
"__any__", title="The clients envoy version to emulate in this XDS request"
|
|
146
164
|
),
|
|
147
165
|
) -> Response:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
logger: FilteringBoundLogger = get_named_logger(
|
|
167
|
+
f"{__name__}.{resources.__qualname__} ({__file__})",
|
|
168
|
+
level=logging.DEBUG,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
mock_request = mock_discovery_request(
|
|
172
|
+
api_version,
|
|
173
|
+
xds_type,
|
|
174
|
+
version=envoy_version,
|
|
175
|
+
region=region,
|
|
176
|
+
expressions=node_expression.split(),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
logger.debug("Making mock request", mock_request=mock_request)
|
|
180
|
+
|
|
181
|
+
entry: Entry | None = None
|
|
182
|
+
|
|
183
|
+
if config.worker_v2_enabled:
|
|
184
|
+
# we're set up to use v2 of the worker
|
|
185
|
+
discovery_response = await wait_for_discovery_response(mock_request)
|
|
186
|
+
if discovery_response is not None:
|
|
187
|
+
entry = Entry(
|
|
188
|
+
text=discovery_response.model_dump_json(indent=None),
|
|
189
|
+
len=len(discovery_response.resources),
|
|
190
|
+
version=discovery_response.version_info,
|
|
191
|
+
node=mock_request.node,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
else:
|
|
195
|
+
entry = await reader.blocking_read(mock_request) # ty: ignore[possibly-missing-attribute]
|
|
196
|
+
|
|
197
|
+
if entry:
|
|
198
|
+
for res in json.loads(entry.text).get("resources", []):
|
|
199
|
+
if res.get("name", res.get("cluster_name")) == resource_name:
|
|
200
|
+
safe_response = jsonable_encoder(res)
|
|
201
|
+
try:
|
|
202
|
+
return json_response_class(content=safe_response)
|
|
203
|
+
except TypeError:
|
|
204
|
+
return JSONResponse(content=safe_response)
|
|
205
|
+
return Response(
|
|
206
|
+
json.dumps({"title": "No resources found", "status": 404}),
|
|
207
|
+
media_type="application/json+problem",
|
|
158
208
|
)
|
|
159
|
-
return Response(response.rendered, media_type="application/json")
|
|
160
209
|
|
|
161
210
|
|
|
162
211
|
@router.get(
|
|
@@ -170,36 +219,43 @@ async def virtual_hosts(
|
|
|
170
219
|
None, title="The clients region to emulate in this XDS request"
|
|
171
220
|
),
|
|
172
221
|
api_version: str = Query("v2", title="The desired Envoy API version"),
|
|
173
|
-
|
|
174
|
-
"
|
|
222
|
+
node_expression: str = Cookie(
|
|
223
|
+
"cluster=*", title="Node expression to filter resources with"
|
|
175
224
|
),
|
|
176
225
|
envoy_version: str = Cookie(
|
|
177
226
|
"__any__", title="The clients envoy version to emulate in this XDS request"
|
|
178
227
|
),
|
|
179
228
|
) -> Response:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
229
|
+
logger: FilteringBoundLogger = get_named_logger(
|
|
230
|
+
f"{__name__}.{virtual_hosts.__qualname__} ({__file__})",
|
|
231
|
+
level=logging.DEBUG,
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
mock_request = mock_discovery_request(
|
|
235
|
+
api_version,
|
|
236
|
+
"routes",
|
|
237
|
+
version=envoy_version,
|
|
238
|
+
region=region,
|
|
239
|
+
expressions=node_expression.split(),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
logger.debug("Making mock request", mock_request=mock_request)
|
|
243
|
+
|
|
244
|
+
if response := await reader.blocking_read(mock_request): # ty: ignore[possibly-missing-attribute]
|
|
245
|
+
route_configs = [
|
|
246
|
+
resource_
|
|
247
|
+
for resource_ in json.loads(response.text).get("resources", [])
|
|
248
|
+
if resource_["name"] == route_configuration
|
|
249
|
+
]
|
|
250
|
+
for route_config in route_configs:
|
|
251
|
+
for vhost in route_config["virtual_hosts"]:
|
|
252
|
+
if vhost["name"] == virtual_host:
|
|
253
|
+
safe_response = jsonable_encoder(vhost)
|
|
254
|
+
try:
|
|
255
|
+
return json_response_class(content=safe_response)
|
|
256
|
+
except TypeError:
|
|
257
|
+
return JSONResponse(content=safe_response)
|
|
258
|
+
return Response(
|
|
259
|
+
json.dumps({"title": "No resources found", "status": 404}),
|
|
260
|
+
media_type="application/json+problem",
|
|
190
261
|
)
|
|
191
|
-
route_configs = [
|
|
192
|
-
resource_
|
|
193
|
-
for resource_ in response.deserialize_resources()
|
|
194
|
-
if resource_["name"] == route_configuration
|
|
195
|
-
]
|
|
196
|
-
for route_config in route_configs:
|
|
197
|
-
for vhost in route_config["virtual_hosts"]:
|
|
198
|
-
if vhost["name"] == virtual_host:
|
|
199
|
-
safe_response = jsonable_encoder(vhost)
|
|
200
|
-
try:
|
|
201
|
-
return json_response_class(content=safe_response)
|
|
202
|
-
except TypeError:
|
|
203
|
-
return JSONResponse(content=safe_response)
|
|
204
|
-
break
|
|
205
|
-
return JSONResponse(content={})
|