sovereign 0.19.3__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 +13 -81
- sovereign/app.py +62 -48
- 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 +270 -104
- 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 +2 -3
- 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 +112 -35
- 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 +2 -2
- sovereign/utils/mock.py +56 -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 +55 -119
- sovereign/views/healthchecks.py +107 -20
- sovereign/views/interface.py +171 -111
- sovereign/worker.py +193 -0
- {sovereign-0.19.3.dist-info → sovereign-1.0.0a4.dist-info}/METADATA +80 -76
- sovereign-1.0.0a4.dist-info/RECORD +85 -0
- {sovereign-0.19.3.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 -780
- 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 -294
- 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 -103
- sovereign/views/admin.py +0 -120
- sovereign-0.19.3.dist-info/LICENSE.txt +0 -13
- sovereign-0.19.3.dist-info/RECORD +0 -47
- sovereign-0.19.3.dist-info/entry_points.txt +0 -10
sovereign/views/discovery.py
CHANGED
|
@@ -1,63 +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.
|
|
10
|
-
from sovereign.schemas import (
|
|
5
|
+
from sovereign import cache, config, logs
|
|
6
|
+
from sovereign.cache.types import Entry
|
|
7
|
+
from sovereign.types import (
|
|
11
8
|
DiscoveryRequest,
|
|
12
9
|
DiscoveryResponse,
|
|
13
|
-
ProcessedTemplate,
|
|
14
10
|
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if discovery_cache.enabled:
|
|
19
|
-
from cashews import cache
|
|
20
|
-
|
|
21
|
-
cache.setup(
|
|
22
|
-
f"{discovery_cache.protocol}{discovery_cache.host}:{discovery_cache.port}",
|
|
23
|
-
password=discovery_cache.password.get_secret_value(),
|
|
24
|
-
client_side=discovery_cache.client_side,
|
|
25
|
-
wait_for_connection_timeout=discovery_cache.wait_for_connection_timeout,
|
|
26
|
-
socket_connect_timeout=discovery_cache.socket_connect_timeout,
|
|
27
|
-
socket_timeout=discovery_cache.socket_timeout,
|
|
28
|
-
max_connections=discovery_cache.max_connections,
|
|
29
|
-
retry_on_timeout=discovery_cache.retry_on_timeout,
|
|
30
|
-
suppress=discovery_cache.suppress,
|
|
31
|
-
socket_keepalive=discovery_cache.socket_keepalive,
|
|
32
|
-
enable=discovery_cache.enabled,
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
router = APIRouter()
|
|
36
|
-
|
|
37
|
-
type_urls = {
|
|
38
|
-
"v2": {
|
|
39
|
-
"listeners": "type.googleapis.com/envoy.api.v2.Listener",
|
|
40
|
-
"clusters": "type.googleapis.com/envoy.api.v2.Cluster",
|
|
41
|
-
"endpoints": "type.googleapis.com/envoy.api.v2.ClusterLoadAssignment",
|
|
42
|
-
"secrets": "type.googleapis.com/envoy.api.v2.auth.Secret",
|
|
43
|
-
"routes": "type.googleapis.com/envoy.api.v2.RouteConfiguration",
|
|
44
|
-
"scoped-routes": "type.googleapis.com/envoy.api.v2.ScopedRouteConfiguration",
|
|
45
|
-
},
|
|
46
|
-
"v3": {
|
|
47
|
-
"listeners": "type.googleapis.com/envoy.config.listener.v3.Listener",
|
|
48
|
-
"clusters": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
|
|
49
|
-
"endpoints": "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment",
|
|
50
|
-
"secrets": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret",
|
|
51
|
-
"routes": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
|
|
52
|
-
"scoped-routes": "type.googleapis.com/envoy.config.route.v3.ScopedRouteConfiguration",
|
|
53
|
-
"runtime": "type.googleapis.com/envoy.service.runtime.v3.Runtime",
|
|
54
|
-
},
|
|
55
|
-
}
|
|
11
|
+
from sovereign.utils.auth import authenticate
|
|
12
|
+
from sovereign.v2.web import wait_for_discovery_response
|
|
13
|
+
from sovereign.views import reader
|
|
56
14
|
|
|
57
15
|
|
|
58
16
|
def response_headers(
|
|
59
|
-
discovery_request: DiscoveryRequest, response:
|
|
60
|
-
) ->
|
|
17
|
+
discovery_request: DiscoveryRequest, response: Entry, xds: str
|
|
18
|
+
) -> dict[str, str]:
|
|
61
19
|
return {
|
|
62
20
|
"X-Sovereign-Client-Build": discovery_request.envoy_version,
|
|
63
21
|
"X-Sovereign-Client-Version": discovery_request.version_info,
|
|
@@ -68,6 +26,9 @@ def response_headers(
|
|
|
68
26
|
}
|
|
69
27
|
|
|
70
28
|
|
|
29
|
+
router = APIRouter()
|
|
30
|
+
|
|
31
|
+
|
|
71
32
|
@router.post(
|
|
72
33
|
"/{version}/discovery:{xds_type}",
|
|
73
34
|
summary="Envoy Discovery Service Endpoint",
|
|
@@ -81,76 +42,51 @@ def response_headers(
|
|
|
81
42
|
async def discovery_response(
|
|
82
43
|
version: str,
|
|
83
44
|
xds_type: str,
|
|
84
|
-
|
|
45
|
+
xds_req: DiscoveryRequest = Body(...),
|
|
85
46
|
host: str = Header("no_host_provided"),
|
|
86
47
|
) -> Response:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
)
|
|
97
|
-
if discovery_request.error_detail:
|
|
98
|
-
logs.queue_log_fields(XDS_ERROR_DETAIL=discovery_request.error_detail.message)
|
|
99
|
-
headers = response_headers(discovery_request, response, xds_type)
|
|
100
|
-
|
|
101
|
-
if response.version == discovery_request.version_info:
|
|
102
|
-
return not_modified(headers)
|
|
103
|
-
elif getattr(response, "resources", None) == []:
|
|
104
|
-
return Response(status_code=404, headers=headers)
|
|
105
|
-
elif response.version != discovery_request.version_info:
|
|
106
|
-
return Response(
|
|
107
|
-
response.rendered, headers=headers, media_type="application/json"
|
|
48
|
+
authenticate(xds_req)
|
|
49
|
+
|
|
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
|
|
108
57
|
)
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
)
|
|
111
63
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
resource_type: str,
|
|
116
|
-
skip_auth: bool = False,
|
|
117
|
-
) -> ProcessedTemplate:
|
|
118
|
-
if not skip_auth:
|
|
119
|
-
authenticate(req)
|
|
120
|
-
if discovery_cache.enabled:
|
|
121
|
-
logs.queue_log_fields(CACHE_XDS_HIT=False)
|
|
122
|
-
cache_key = compute_hash(
|
|
123
|
-
[
|
|
124
|
-
api_version,
|
|
125
|
-
resource_type,
|
|
126
|
-
req.envoy_version,
|
|
127
|
-
req.resource_names,
|
|
128
|
-
req.desired_controlplane,
|
|
129
|
-
req.hide_private_keys,
|
|
130
|
-
req.type_url,
|
|
131
|
-
req.node.cluster,
|
|
132
|
-
req.node.locality,
|
|
133
|
-
req.node.metadata.get("auth", None),
|
|
134
|
-
req.node.metadata.get("num_cpus", None),
|
|
135
|
-
]
|
|
64
|
+
def handle_response(entry: cache.Entry):
|
|
65
|
+
logs.access_logger.queue_log_fields(
|
|
66
|
+
XDS_SERVER_VERSION=entry.version,
|
|
136
67
|
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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)
|
|
74
|
+
|
|
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)
|
|
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)
|
|
91
|
+
|
|
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")
|