sovereign 0.26.0__tar.gz → 0.27.0__tar.gz
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-0.26.0 → sovereign-0.27.0}/PKG-INFO +4 -2
- {sovereign-0.26.0 → sovereign-0.27.0}/pyproject.toml +10 -2
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/__init__.py +1 -1
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/configuration.py +1 -1
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/schemas.py +184 -187
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/crypto.py +2 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/views/admin.py +2 -2
- {sovereign-0.26.0 → sovereign-0.27.0}/LICENSE.txt +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/README.md +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/app.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/config_loader.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/constants.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/context.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/discovery.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/error_info.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/logging/access_logger.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/logging/application_logger.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/logging/base_logger.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/logging/bootstrapper.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/logging/types.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/middlewares.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/modifiers/__init__.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/modifiers/lib.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/response_class.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/server.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/sources/__init__.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/sources/file.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/sources/inline.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/sources/lib.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/sources/poller.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/static/sass/style.scss +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/static/style.css +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/statistics.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/templates/base.html +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/templates/err.html +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/templates/resources.html +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/templates/ul_filter.html +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/testing/loaders.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/testing/modifiers.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/__init__.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/auth.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/__init__.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/suites/__init__.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/suites/base_cipher.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/suites/disabled_cipher.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/crypto/suites/fernet_cipher.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/dictupdate.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/eds.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/entry_point_loader.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/mock.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/resources.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/templates.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/timer.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/version_info.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/utils/weighted_clusters.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/views/__init__.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/views/crypto.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/views/discovery.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/views/healthchecks.py +0 -0
- {sovereign-0.26.0 → sovereign-0.27.0}/src/sovereign/views/interface.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sovereign
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: Envoy Proxy control-plane written in Python
|
|
5
5
|
Home-page: https://pypi.org/project/sovereign/
|
|
6
6
|
License: Apache-2.0
|
|
@@ -44,13 +44,15 @@ Requires-Dist: glom (>=23.3.0,<24.0.0)
|
|
|
44
44
|
Requires-Dist: gunicorn (>=22.0.0,<23.0.0)
|
|
45
45
|
Requires-Dist: httptools (>=0.6.0,<0.7.0) ; extra == "httptools"
|
|
46
46
|
Requires-Dist: orjson (>=3.9.15,<4.0.0) ; extra == "orjson"
|
|
47
|
+
Requires-Dist: pydantic (>=2.7.2,<3.0.0)
|
|
48
|
+
Requires-Dist: pydantic-settings (>=2.3.1,<3.0.0)
|
|
47
49
|
Requires-Dist: redis (<=5.0.0)
|
|
48
50
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
49
51
|
Requires-Dist: sentry-sdk (>=1.23.1,<2.0.0) ; extra == "sentry"
|
|
50
52
|
Requires-Dist: structlog (>=23.1.0,<24.0.0)
|
|
51
53
|
Requires-Dist: ujson (>=5.8.0,<6.0.0) ; extra == "ujson"
|
|
52
54
|
Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
|
|
53
|
-
Requires-Dist: uvloop (>=0.
|
|
55
|
+
Requires-Dist: uvloop (>=0.19.0,<0.20.0)
|
|
54
56
|
Project-URL: Documentation, https://vsyrakis.bitbucket.io/sovereign/docs/
|
|
55
57
|
Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
|
|
56
58
|
Description-Content-Type: text/markdown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "sovereign"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.0"
|
|
4
4
|
description = "Envoy Proxy control-plane written in Python"
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
packages = [
|
|
@@ -55,7 +55,7 @@ cachelib = "^0.10.2"
|
|
|
55
55
|
glom = "^23.3.0"
|
|
56
56
|
cryptography = "^42.0.5"
|
|
57
57
|
fastapi = "^0.110.0"
|
|
58
|
-
uvloop = "^0.
|
|
58
|
+
uvloop = "^0.19.0"
|
|
59
59
|
sentry-sdk = "^1.23.1"
|
|
60
60
|
boto3 = {version = "^1.28.62", optional = true}
|
|
61
61
|
datadog = {version = "^0.47.0", optional = true}
|
|
@@ -66,6 +66,8 @@ cashews = {extras = ["redis"], version = "^6.3.0", optional = true}
|
|
|
66
66
|
redis = {version = "<= 5.0.0", optional = true}
|
|
67
67
|
httptools = {version = "^0.6.0", optional = true}
|
|
68
68
|
cachetools = "^5.3.2"
|
|
69
|
+
pydantic = "^2.7.2"
|
|
70
|
+
pydantic-settings = "^2.3.1"
|
|
69
71
|
|
|
70
72
|
[tool.poetry.extras]
|
|
71
73
|
sentry = ["sentry-sdk"]
|
|
@@ -114,6 +116,12 @@ lint = { cmd = "pylint src/sovereign", help = "Run linter checks" }
|
|
|
114
116
|
[tool.black]
|
|
115
117
|
target-version = ['py311']
|
|
116
118
|
|
|
119
|
+
[tool.mypy]
|
|
120
|
+
plugins = [
|
|
121
|
+
"pydantic.mypy"
|
|
122
|
+
]
|
|
123
|
+
ignore_missing_imports = true
|
|
124
|
+
|
|
117
125
|
[tool.coverage.run]
|
|
118
126
|
omit = ["test/*"]
|
|
119
127
|
|
|
@@ -4,7 +4,7 @@ from importlib.metadata import version
|
|
|
4
4
|
from typing import Any, Mapping, Type
|
|
5
5
|
|
|
6
6
|
from fastapi.responses import JSONResponse
|
|
7
|
-
from pydantic
|
|
7
|
+
from pydantic import ValidationError
|
|
8
8
|
from starlette.templating import Jinja2Templates
|
|
9
9
|
|
|
10
10
|
from sovereign import config_loader
|
|
@@ -5,19 +5,20 @@ from dataclasses import dataclass
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from os import getenv
|
|
7
7
|
from types import ModuleType
|
|
8
|
-
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
|
8
|
+
from typing import Any, Dict, List, Optional, Self, Tuple, Type, Union
|
|
9
9
|
|
|
10
10
|
from croniter import CroniterBadCronError, croniter
|
|
11
11
|
from fastapi.responses import JSONResponse
|
|
12
12
|
from jinja2 import Template, meta
|
|
13
13
|
from pydantic import (
|
|
14
14
|
BaseModel,
|
|
15
|
-
|
|
15
|
+
ConfigDict,
|
|
16
16
|
Field,
|
|
17
17
|
SecretStr,
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
model_validator,
|
|
19
|
+
field_validator,
|
|
20
20
|
)
|
|
21
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
21
22
|
|
|
22
23
|
from sovereign.config_loader import Loadable, Serialization, jinja_env
|
|
23
24
|
from sovereign.utils.crypto.suites import EncryptionType
|
|
@@ -65,11 +66,13 @@ class StatsdConfig(BaseModel):
|
|
|
65
66
|
enabled: bool = False
|
|
66
67
|
use_ms: bool = True
|
|
67
68
|
|
|
68
|
-
@
|
|
69
|
+
@field_validator("host", mode="before")
|
|
70
|
+
@classmethod
|
|
69
71
|
def load_host(cls, v: str) -> Any:
|
|
70
72
|
return Loadable.from_legacy_fmt(v).load()
|
|
71
73
|
|
|
72
|
-
@
|
|
74
|
+
@field_validator("port", mode="before")
|
|
75
|
+
@classmethod
|
|
73
76
|
def load_port(cls, v: Union[int, str]) -> Any:
|
|
74
77
|
if isinstance(v, int):
|
|
75
78
|
return v
|
|
@@ -78,7 +81,8 @@ class StatsdConfig(BaseModel):
|
|
|
78
81
|
else:
|
|
79
82
|
raise ValueError(f"Received an invalid port: {v}")
|
|
80
83
|
|
|
81
|
-
@
|
|
84
|
+
@field_validator("tags", mode="before")
|
|
85
|
+
@classmethod
|
|
82
86
|
def load_tags(cls, v: Dict[str, Union[Loadable, str]]) -> Dict[str, Any]:
|
|
83
87
|
ret = dict()
|
|
84
88
|
for key, value in v.items():
|
|
@@ -108,14 +112,14 @@ class DiscoveryCacheConfig(BaseModel):
|
|
|
108
112
|
socket_keepalive: bool = True # Try to keep connections to redis around.
|
|
109
113
|
ttl: int = 60
|
|
110
114
|
|
|
111
|
-
@
|
|
112
|
-
def set_default_protocol(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
return values
|
|
115
|
+
@model_validator(mode="after")
|
|
116
|
+
def set_default_protocol(self) -> Self:
|
|
117
|
+
if self.secure:
|
|
118
|
+
self.protocol = "rediss://"
|
|
119
|
+
return self
|
|
117
120
|
|
|
118
|
-
@
|
|
121
|
+
@model_validator(mode="before")
|
|
122
|
+
@classmethod
|
|
119
123
|
def set_environmental_variables(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
120
124
|
if host := getenv("SOVEREIGN_DISCOVERY_CACHE_REDIS_HOST"):
|
|
121
125
|
values["host"] = host
|
|
@@ -221,9 +225,9 @@ class ProcessedTemplate:
|
|
|
221
225
|
|
|
222
226
|
|
|
223
227
|
class Locality(BaseModel):
|
|
224
|
-
region: str = Field(None)
|
|
225
|
-
zone: str = Field(None)
|
|
226
|
-
sub_zone: str = Field(None)
|
|
228
|
+
region: Optional[str] = Field(None)
|
|
229
|
+
zone: Optional[str] = Field(None)
|
|
230
|
+
sub_zone: Optional[str] = Field(None)
|
|
227
231
|
|
|
228
232
|
|
|
229
233
|
class SemanticVersion(BaseModel):
|
|
@@ -255,8 +259,8 @@ class Node(BaseModel):
|
|
|
255
259
|
description="The ``--service-cluster`` configured by the Envoy client",
|
|
256
260
|
)
|
|
257
261
|
metadata: Dict[str, Any] = Field(default_factory=dict, title="Key:value metadata")
|
|
258
|
-
locality: Locality = Field(Locality(), title="Locality")
|
|
259
|
-
build_version: str = Field(
|
|
262
|
+
locality: Locality = Field(Locality(), title="Locality")
|
|
263
|
+
build_version: Optional[str] = Field(
|
|
260
264
|
None, # Optional in the v3 Envoy API
|
|
261
265
|
title="Envoy build/release version string",
|
|
262
266
|
description="Used to identify what version of Envoy the "
|
|
@@ -269,7 +273,7 @@ class Node(BaseModel):
|
|
|
269
273
|
client_features: List[str] = []
|
|
270
274
|
|
|
271
275
|
@property
|
|
272
|
-
def common(self) -> Tuple[str, str, str, BuildVersion, Locality]:
|
|
276
|
+
def common(self) -> Tuple[str, Optional[str], str, BuildVersion, Locality]:
|
|
273
277
|
"""
|
|
274
278
|
Returns fields that are the same in adjacent proxies
|
|
275
279
|
ie. proxies that are part of the same logical group
|
|
@@ -292,7 +296,7 @@ class Resources(List[str]):
|
|
|
292
296
|
def __contains__(self, item: object) -> bool:
|
|
293
297
|
if len(self) == 0:
|
|
294
298
|
return True
|
|
295
|
-
return
|
|
299
|
+
return super().__contains__(item)
|
|
296
300
|
|
|
297
301
|
|
|
298
302
|
class Status(BaseModel):
|
|
@@ -306,19 +310,20 @@ class DiscoveryRequest(BaseModel):
|
|
|
306
310
|
version_info: str = Field(
|
|
307
311
|
"0", title="The version of the envoy clients current configuration"
|
|
308
312
|
)
|
|
309
|
-
resource_names: Resources = Field(
|
|
313
|
+
resource_names: list[str] | Resources = Field(
|
|
310
314
|
Resources(), title="List of requested resource names"
|
|
311
315
|
)
|
|
312
316
|
hide_private_keys: bool = False
|
|
313
317
|
type_url: Optional[str] = Field(
|
|
314
318
|
None, title="The corresponding type_url for the requested resource"
|
|
315
319
|
)
|
|
316
|
-
desired_controlplane: str = Field(
|
|
320
|
+
desired_controlplane: Optional[str] = Field(
|
|
317
321
|
None, title="The host header provided in the Discovery Request"
|
|
318
322
|
)
|
|
319
|
-
error_detail: Status = Field(
|
|
323
|
+
error_detail: Optional[Status] = Field(
|
|
320
324
|
None, title="Error details from the previous xDS request"
|
|
321
325
|
)
|
|
326
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
322
327
|
|
|
323
328
|
@property
|
|
324
329
|
def envoy_version(self) -> str:
|
|
@@ -328,6 +333,8 @@ class DiscoveryRequest(BaseModel):
|
|
|
328
333
|
except AssertionError:
|
|
329
334
|
try:
|
|
330
335
|
build_version = self.node.build_version
|
|
336
|
+
if build_version is None:
|
|
337
|
+
return "default"
|
|
331
338
|
_, version, *_ = build_version.split("/")
|
|
332
339
|
except (AttributeError, ValueError):
|
|
333
340
|
# TODO: log/metric this?
|
|
@@ -355,33 +362,35 @@ class DiscoveryResponse(BaseModel):
|
|
|
355
362
|
|
|
356
363
|
|
|
357
364
|
class SovereignAsgiConfig(BaseSettings):
|
|
358
|
-
host: str = "0.0.0.0"
|
|
359
|
-
port: int = 8080
|
|
360
|
-
keepalive: int = 5
|
|
361
|
-
workers: int =
|
|
362
|
-
|
|
365
|
+
host: str = Field("0.0.0.0", alias="SOVEREIGN_HOST")
|
|
366
|
+
port: int = Field(8080, alias="SOVEREIGN_PORT")
|
|
367
|
+
keepalive: int = Field(5, alias="SOVEREIGN_KEEPALIVE")
|
|
368
|
+
workers: int = Field(
|
|
369
|
+
default_factory=lambda: multiprocessing.cpu_count() * 2 + 1,
|
|
370
|
+
alias="SOVEREIGN_WORKERS",
|
|
371
|
+
)
|
|
372
|
+
threads: int = Field(1, alias="SOVEREIGN_THREADS")
|
|
363
373
|
reuse_port: bool = True
|
|
364
|
-
preload_app: bool = True
|
|
374
|
+
preload_app: bool = Field(True, alias="SOVEREIGN_PRELOAD")
|
|
365
375
|
log_level: str = "warning"
|
|
366
376
|
worker_class: str = "uvicorn.workers.UvicornWorker"
|
|
367
|
-
worker_timeout: int = 30
|
|
377
|
+
worker_timeout: int = Field(30, alias="SOVEREIGN_WORKER_TIMEOUT")
|
|
368
378
|
worker_tmp_dir: str = "/dev/shm"
|
|
369
|
-
graceful_timeout: int =
|
|
370
|
-
max_requests: int = 0
|
|
371
|
-
max_requests_jitter: int = 0
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
379
|
+
graceful_timeout: Optional[int] = Field(None)
|
|
380
|
+
max_requests: int = Field(0, alias="SOVEREIGN_MAX_REQUESTS")
|
|
381
|
+
max_requests_jitter: int = Field(0, alias="SOVEREIGN_MAX_REQUESTS_JITTER")
|
|
382
|
+
model_config = SettingsConfigDict(
|
|
383
|
+
env_file=".env",
|
|
384
|
+
extra="ignore",
|
|
385
|
+
env_file_encoding="utf-8",
|
|
386
|
+
populate_by_name=True,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
@model_validator(mode="after")
|
|
390
|
+
def validate_graceful_timeout(self) -> Self:
|
|
391
|
+
if self.graceful_timeout is None:
|
|
392
|
+
self.graceful_timeout = self.worker_timeout * 2
|
|
393
|
+
return self
|
|
385
394
|
|
|
386
395
|
def as_gunicorn_conf(self) -> Dict[str, Any]:
|
|
387
396
|
return {
|
|
@@ -405,54 +414,45 @@ class SovereignConfig(BaseSettings):
|
|
|
405
414
|
sources: List[ConfiguredSource]
|
|
406
415
|
templates: Dict[str, Dict[str, Union[str, Loadable]]]
|
|
407
416
|
template_context: Dict[str, Any] = {}
|
|
408
|
-
eds_priority_matrix: Dict[str, Dict[str,
|
|
417
|
+
eds_priority_matrix: Dict[str, Dict[str, int]] = {}
|
|
409
418
|
modifiers: List[str] = []
|
|
410
419
|
global_modifiers: List[str] = []
|
|
411
420
|
regions: List[str] = []
|
|
412
421
|
statsd: StatsdConfig = StatsdConfig()
|
|
413
|
-
auth_enabled: bool = False
|
|
414
|
-
auth_passwords: str = ""
|
|
415
|
-
encryption_key: str = ""
|
|
416
|
-
environment: str = "local"
|
|
417
|
-
debug_enabled: bool = False
|
|
418
|
-
sentry_dsn: str = ""
|
|
419
|
-
node_match_key: str = "cluster"
|
|
420
|
-
node_matching: bool = True
|
|
421
|
-
source_match_key: str =
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
422
|
+
auth_enabled: bool = Field(False, alias="SOVEREIGN_AUTH_ENABLED")
|
|
423
|
+
auth_passwords: str = Field("", alias="SOVEREIGN_AUTH_PASSWORDS")
|
|
424
|
+
encryption_key: str = Field("", alias="SOVEREIGN_ENCRYPTION_KEY")
|
|
425
|
+
environment: str = Field("local", alias="SOVEREIGN_ENVIRONMENT")
|
|
426
|
+
debug_enabled: bool = Field(False, alias="SOVEREIGN_DEBUG_ENABLED")
|
|
427
|
+
sentry_dsn: str = Field("", alias="SOVEREIGN_SENTRY_DSN")
|
|
428
|
+
node_match_key: str = Field("cluster", alias="SOVEREIGN_NODE_MATCH_KEY")
|
|
429
|
+
node_matching: bool = Field(True, alias="SOVEREIGN_NODE_MATCHING")
|
|
430
|
+
source_match_key: str = Field(
|
|
431
|
+
"service_clusters", alias="SOVEREIGN_SOURCE_MATCH_KEY"
|
|
432
|
+
)
|
|
433
|
+
sources_refresh_rate: int = Field(30, alias="SOVEREIGN_SOURCES_REFRESH_RATE")
|
|
434
|
+
cache_strategy: str = Field("context", alias="SOVEREIGN_CACHE_STRATEGY")
|
|
435
|
+
refresh_context: bool = Field(False, alias="SOVEREIGN_REFRESH_CONTEXT")
|
|
436
|
+
context_refresh_rate: Optional[int] = Field(
|
|
437
|
+
None, alias="SOVEREIGN_CONTEXT_REFRESH_RATE"
|
|
438
|
+
)
|
|
439
|
+
context_refresh_cron: Optional[str] = Field(
|
|
440
|
+
None, alias="SOVEREIGN_CONTEXT_REFRESH_CRON"
|
|
441
|
+
)
|
|
442
|
+
dns_hard_fail: bool = Field(False, alias="SOVEREIGN_DNS_HARD_FAIL")
|
|
443
|
+
enable_application_logs: bool = Field(
|
|
444
|
+
True, alias="SOVEREIGN_ENABLE_APPLICATION_LOGS"
|
|
445
|
+
)
|
|
446
|
+
enable_access_logs: bool = Field(True, alias="SOVEREIGN_ENABLE_ACCESS_LOGS")
|
|
447
|
+
log_fmt: Optional[str] = Field("", alias="SOVEREIGN_LOG_FORMAT")
|
|
448
|
+
ignore_empty_log_fields: bool = Field(False, alias="SOVEREIGN_LOG_IGNORE_EMPTY")
|
|
432
449
|
discovery_cache: DiscoveryCacheConfig = DiscoveryCacheConfig()
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
"environment": {"env": "SOVEREIGN_ENVIRONMENT"},
|
|
440
|
-
"debug_enabled": {"env": "SOVEREIGN_DEBUG_ENABLED"},
|
|
441
|
-
"sentry_dsn": {"env": "SOVEREIGN_SENTRY_DSN"},
|
|
442
|
-
"node_match_key": {"env": "SOVEREIGN_NODE_MATCH_KEY"},
|
|
443
|
-
"node_matching": {"env": "SOVEREIGN_NODE_MATCHING"},
|
|
444
|
-
"source_match_key": {"env": "SOVEREIGN_SOURCE_MATCH_KEY"},
|
|
445
|
-
"sources_refresh_rate": {"env": "SOVEREIGN_SOURCES_REFRESH_RATE"},
|
|
446
|
-
"cache_strategy": {"env": "SOVEREIGN_CACHE_STRATEGY"},
|
|
447
|
-
"refresh_context": {"env": "SOVEREIGN_REFRESH_CONTEXT"},
|
|
448
|
-
"context_refresh_rate": {"env": "SOVEREIGN_CONTEXT_REFRESH_RATE"},
|
|
449
|
-
"context_refresh_cron": {"env": "SOVEREIGN_CONTEXT_REFRESH_CRON"},
|
|
450
|
-
"dns_hard_fail": {"env": "SOVEREIGN_DNS_HARD_FAIL"},
|
|
451
|
-
"enable_application_logs": {"env": "SOVEREIGN_ENABLE_APPLICATION_LOGS"},
|
|
452
|
-
"enable_access_logs": {"env": "SOVEREIGN_ENABLE_ACCESS_LOGS"},
|
|
453
|
-
"log_fmt": {"env": "SOVEREIGN_LOG_FORMAT"},
|
|
454
|
-
"ignore_empty_fields": {"env": "SOVEREIGN_LOG_IGNORE_EMPTY"},
|
|
455
|
-
}
|
|
450
|
+
model_config = SettingsConfigDict(
|
|
451
|
+
env_file=".env",
|
|
452
|
+
extra="ignore",
|
|
453
|
+
env_file_encoding="utf-8",
|
|
454
|
+
populate_by_name=True,
|
|
455
|
+
)
|
|
456
456
|
|
|
457
457
|
@property
|
|
458
458
|
def passwords(self) -> List[str]:
|
|
@@ -492,16 +492,15 @@ class TemplateSpecification(BaseModel):
|
|
|
492
492
|
|
|
493
493
|
|
|
494
494
|
class NodeMatching(BaseSettings):
|
|
495
|
-
enabled: bool = True
|
|
496
|
-
source_key: str = "service_clusters"
|
|
497
|
-
node_key: str = "cluster"
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
495
|
+
enabled: bool = Field(True, alias="SOVEREIGN_NODE_MATCHING_ENABLED")
|
|
496
|
+
source_key: str = Field("service_clusters", alias="SOVEREIGN_SOURCE_MATCH_KEY")
|
|
497
|
+
node_key: str = Field("cluster", alias="SOVEREIGN_NODE_MATCH_KEY")
|
|
498
|
+
model_config = SettingsConfigDict(
|
|
499
|
+
env_file=".env",
|
|
500
|
+
extra="ignore",
|
|
501
|
+
env_file_encoding="utf-8",
|
|
502
|
+
populate_by_name=True,
|
|
503
|
+
)
|
|
505
504
|
|
|
506
505
|
|
|
507
506
|
@dataclass
|
|
@@ -511,9 +510,15 @@ class EncryptionConfig:
|
|
|
511
510
|
|
|
512
511
|
|
|
513
512
|
class AuthConfiguration(BaseSettings):
|
|
514
|
-
enabled: bool = False
|
|
515
|
-
auth_passwords: SecretStr = SecretStr("")
|
|
516
|
-
encryption_key: SecretStr = SecretStr("")
|
|
513
|
+
enabled: bool = Field(False, alias="SOVEREIGN_AUTH_ENABLED")
|
|
514
|
+
auth_passwords: SecretStr = Field(SecretStr(""), alias="SOVEREIGN_AUTH_PASSWORDS")
|
|
515
|
+
encryption_key: SecretStr = Field(SecretStr(""), alias="SOVEREIGN_ENCRYPTION_KEY")
|
|
516
|
+
model_config = SettingsConfigDict(
|
|
517
|
+
env_file=".env",
|
|
518
|
+
extra="ignore",
|
|
519
|
+
env_file_encoding="utf-8",
|
|
520
|
+
populate_by_name=True,
|
|
521
|
+
)
|
|
517
522
|
|
|
518
523
|
@staticmethod
|
|
519
524
|
def _create_encryption_config(encryption_key_setting: str) -> EncryptionConfig:
|
|
@@ -534,37 +539,29 @@ class AuthConfiguration(BaseSettings):
|
|
|
534
539
|
)
|
|
535
540
|
return configs
|
|
536
541
|
|
|
537
|
-
class Config:
|
|
538
|
-
fields = {
|
|
539
|
-
"enabled": {"env": "SOVEREIGN_AUTH_ENABLED"},
|
|
540
|
-
"auth_passwords": {"env": "SOVEREIGN_AUTH_PASSWORDS"},
|
|
541
|
-
"encryption_key": {"env": "SOVEREIGN_ENCRYPTION_KEY"},
|
|
542
|
-
}
|
|
543
|
-
|
|
544
542
|
|
|
545
543
|
class ApplicationLogConfiguration(BaseSettings):
|
|
546
|
-
enabled: bool = False
|
|
547
|
-
log_fmt: Optional[str] = None
|
|
544
|
+
enabled: bool = Field(False, alias="SOVEREIGN_ENABLE_APPLICATION_LOGS")
|
|
545
|
+
log_fmt: Optional[str] = Field(None, alias="SOVEREIGN_APPLICATION_LOG_FORMAT")
|
|
548
546
|
# currently only support /dev/stdout as JSON
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
547
|
+
model_config = SettingsConfigDict(
|
|
548
|
+
env_file=".env",
|
|
549
|
+
extra="ignore",
|
|
550
|
+
env_file_encoding="utf-8",
|
|
551
|
+
populate_by_name=True,
|
|
552
|
+
)
|
|
555
553
|
|
|
556
554
|
|
|
557
555
|
class AccessLogConfiguration(BaseSettings):
|
|
558
|
-
enabled: bool = True
|
|
559
|
-
log_fmt: Optional[str] = None
|
|
560
|
-
ignore_empty_fields: bool = False
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
556
|
+
enabled: bool = Field(True, alias="SOVEREIGN_ENABLE_ACCESS_LOGS")
|
|
557
|
+
log_fmt: Optional[str] = Field(None, alias="SOVEREIGN_LOG_FORMAT")
|
|
558
|
+
ignore_empty_fields: bool = Field(False, alias="SOVEREIGN_LOG_IGNORE_EMPTY")
|
|
559
|
+
model_config = SettingsConfigDict(
|
|
560
|
+
env_file=".env",
|
|
561
|
+
extra="ignore",
|
|
562
|
+
env_file_encoding="utf-8",
|
|
563
|
+
populate_by_name=True,
|
|
564
|
+
)
|
|
568
565
|
|
|
569
566
|
|
|
570
567
|
class LoggingConfiguration(BaseSettings):
|
|
@@ -574,11 +571,19 @@ class LoggingConfiguration(BaseSettings):
|
|
|
574
571
|
|
|
575
572
|
class ContextConfiguration(BaseSettings):
|
|
576
573
|
context: Dict[str, Loadable] = {}
|
|
577
|
-
refresh: bool = False
|
|
578
|
-
refresh_rate: Optional[int] = None
|
|
579
|
-
refresh_cron: Optional[str] = None
|
|
580
|
-
refresh_num_retries: int = 3
|
|
581
|
-
refresh_retry_interval_secs: int =
|
|
574
|
+
refresh: bool = Field(False, alias="SOVEREIGN_REFRESH_CONTEXT")
|
|
575
|
+
refresh_rate: Optional[int] = Field(None, alias="SOVEREIGN_CONTEXT_REFRESH_RATE")
|
|
576
|
+
refresh_cron: Optional[str] = Field(None, alias="SOVEREIGN_CONTEXT_REFRESH_CRON")
|
|
577
|
+
refresh_num_retries: int = Field(3, alias="SOVEREIGN_CONTEXT_REFRESH_NUM_RETRIES")
|
|
578
|
+
refresh_retry_interval_secs: int = Field(
|
|
579
|
+
10, alias="SOVEREIGN_CONTEXT_REFRESH_RETRY_INTERVAL_SECS"
|
|
580
|
+
)
|
|
581
|
+
model_config = SettingsConfigDict(
|
|
582
|
+
env_file=".env",
|
|
583
|
+
extra="ignore",
|
|
584
|
+
env_file_encoding="utf-8",
|
|
585
|
+
populate_by_name=True,
|
|
586
|
+
)
|
|
582
587
|
|
|
583
588
|
@staticmethod
|
|
584
589
|
def context_from_legacy(context: Dict[str, str]) -> Dict[str, Loadable]:
|
|
@@ -587,20 +592,16 @@ class ContextConfiguration(BaseSettings):
|
|
|
587
592
|
ret[key] = Loadable.from_legacy_fmt(value)
|
|
588
593
|
return ret
|
|
589
594
|
|
|
590
|
-
@
|
|
591
|
-
def validate_single_use_refresh_method(
|
|
592
|
-
|
|
593
|
-
) -> Dict[str, Any]:
|
|
594
|
-
refresh_rate = values.get("refresh_rate")
|
|
595
|
-
refresh_cron = values.get("refresh_cron")
|
|
596
|
-
|
|
597
|
-
if (refresh_rate is not None) and (refresh_cron is not None):
|
|
595
|
+
@model_validator(mode="after")
|
|
596
|
+
def validate_single_use_refresh_method(self) -> Self:
|
|
597
|
+
if (self.refresh_rate is not None) and (self.refresh_cron is not None):
|
|
598
598
|
raise RuntimeError(
|
|
599
|
-
f"Only one of SOVEREIGN_CONTEXT_REFRESH_RATE or SOVEREIGN_CONTEXT_REFRESH_CRON can be defined. Got {refresh_rate=} and {refresh_cron=}"
|
|
599
|
+
f"Only one of SOVEREIGN_CONTEXT_REFRESH_RATE or SOVEREIGN_CONTEXT_REFRESH_CRON can be defined. Got {self.refresh_rate=} and {self.refresh_cron=}"
|
|
600
600
|
)
|
|
601
|
-
return
|
|
601
|
+
return self
|
|
602
602
|
|
|
603
|
-
@
|
|
603
|
+
@model_validator(mode="before")
|
|
604
|
+
@classmethod
|
|
604
605
|
def set_default_refresh_rate(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
605
606
|
refresh_rate = values.get("refresh_rate")
|
|
606
607
|
refresh_cron = values.get("refresh_cron")
|
|
@@ -609,7 +610,8 @@ class ContextConfiguration(BaseSettings):
|
|
|
609
610
|
values["refresh_rate"] = 3600
|
|
610
611
|
return values
|
|
611
612
|
|
|
612
|
-
@
|
|
613
|
+
@field_validator("refresh_cron")
|
|
614
|
+
@classmethod
|
|
613
615
|
def validate_refresh_cron(cls, v: Optional[str]) -> Optional[str]:
|
|
614
616
|
if v is None:
|
|
615
617
|
return v
|
|
@@ -617,36 +619,34 @@ class ContextConfiguration(BaseSettings):
|
|
|
617
619
|
raise CroniterBadCronError(f"'{v}' is not a valid cron expression")
|
|
618
620
|
return v
|
|
619
621
|
|
|
620
|
-
class Config:
|
|
621
|
-
fields = {
|
|
622
|
-
"refresh": {"env": "SOVEREIGN_REFRESH_CONTEXT"},
|
|
623
|
-
"refresh_rate": {"env": "SOVEREIGN_CONTEXT_REFRESH_RATE"},
|
|
624
|
-
"refresh_cron": {"env": "SOVEREIGN_CONTEXT_REFRESH_CRON"},
|
|
625
|
-
"refresh_num_retries": {"env": "SOVEREIGN_CONTEXT_REFRESH_NUM_RETRIES"},
|
|
626
|
-
"refresh_retry_interval_secs": {
|
|
627
|
-
"env": "SOVEREIGN_CONTEXT_REFRESH_RETRY_INTERVAL_SECS"
|
|
628
|
-
},
|
|
629
|
-
}
|
|
630
|
-
|
|
631
622
|
|
|
632
623
|
class SourcesConfiguration(BaseSettings):
|
|
633
|
-
refresh_rate: int = 30
|
|
634
|
-
cache_strategy: CacheStrategy =
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
624
|
+
refresh_rate: int = Field(30, alias="SOVEREIGN_SOURCES_REFRESH_RATE")
|
|
625
|
+
cache_strategy: CacheStrategy = Field(
|
|
626
|
+
CacheStrategy.context, alias="SOVEREIGN_CACHE_STRATEGY"
|
|
627
|
+
)
|
|
628
|
+
model_config = SettingsConfigDict(
|
|
629
|
+
env_file=".env",
|
|
630
|
+
extra="ignore",
|
|
631
|
+
env_file_encoding="utf-8",
|
|
632
|
+
populate_by_name=True,
|
|
633
|
+
)
|
|
641
634
|
|
|
642
635
|
|
|
643
636
|
class LegacyConfig(BaseSettings):
|
|
644
637
|
regions: Optional[List[str]] = None
|
|
645
|
-
eds_priority_matrix: Optional[Dict[str, Dict[str,
|
|
646
|
-
dns_hard_fail: Optional[bool] = None
|
|
647
|
-
environment: Optional[str] = None
|
|
638
|
+
eds_priority_matrix: Optional[Dict[str, Dict[str, int]]] = None
|
|
639
|
+
dns_hard_fail: Optional[bool] = Field(None, alias="SOVEREIGN_DNS_HARD_FAIL")
|
|
640
|
+
environment: Optional[str] = Field(None, alias="SOVEREIGN_ENVIRONMENT")
|
|
641
|
+
model_config = SettingsConfigDict(
|
|
642
|
+
env_file=".env",
|
|
643
|
+
extra="ignore",
|
|
644
|
+
env_file_encoding="utf-8",
|
|
645
|
+
populate_by_name=True,
|
|
646
|
+
)
|
|
648
647
|
|
|
649
|
-
@
|
|
648
|
+
@field_validator("regions")
|
|
649
|
+
@classmethod
|
|
650
650
|
def regions_is_set(cls, v: Optional[List[str]]) -> List[str]:
|
|
651
651
|
if v is not None:
|
|
652
652
|
warnings.warn(
|
|
@@ -659,7 +659,8 @@ class LegacyConfig(BaseSettings):
|
|
|
659
659
|
else:
|
|
660
660
|
return []
|
|
661
661
|
|
|
662
|
-
@
|
|
662
|
+
@field_validator("eds_priority_matrix")
|
|
663
|
+
@classmethod
|
|
663
664
|
def eds_priority_matrix_is_set(
|
|
664
665
|
cls, v: Optional[Dict[str, Dict[str, Any]]]
|
|
665
666
|
) -> Dict[str, Dict[str, Any]]:
|
|
@@ -674,7 +675,8 @@ class LegacyConfig(BaseSettings):
|
|
|
674
675
|
else:
|
|
675
676
|
return {}
|
|
676
677
|
|
|
677
|
-
@
|
|
678
|
+
@field_validator("dns_hard_fail")
|
|
679
|
+
@classmethod
|
|
678
680
|
def dns_hard_fail_is_set(cls, v: Optional[bool]) -> bool:
|
|
679
681
|
if v is not None:
|
|
680
682
|
warnings.warn(
|
|
@@ -688,7 +690,8 @@ class LegacyConfig(BaseSettings):
|
|
|
688
690
|
else:
|
|
689
691
|
return False
|
|
690
692
|
|
|
691
|
-
@
|
|
693
|
+
@field_validator("environment")
|
|
694
|
+
@classmethod
|
|
692
695
|
def environment_is_set(cls, v: Optional[str]) -> Optional[str]:
|
|
693
696
|
if v is not None:
|
|
694
697
|
warnings.warn(
|
|
@@ -701,12 +704,6 @@ class LegacyConfig(BaseSettings):
|
|
|
701
704
|
else:
|
|
702
705
|
return None
|
|
703
706
|
|
|
704
|
-
class Config:
|
|
705
|
-
fields = {
|
|
706
|
-
"dns_hard_fail": {"env": "SOVEREIGN_DNS_HARD_FAIL"},
|
|
707
|
-
"environment": {"env": "SOVEREIGN_ENVIRONMENT"},
|
|
708
|
-
}
|
|
709
|
-
|
|
710
707
|
|
|
711
708
|
class SovereignConfigv2(BaseSettings):
|
|
712
709
|
sources: List[ConfiguredSource]
|
|
@@ -719,16 +716,16 @@ class SovereignConfigv2(BaseSettings):
|
|
|
719
716
|
authentication: AuthConfiguration = AuthConfiguration()
|
|
720
717
|
logging: LoggingConfiguration = LoggingConfiguration()
|
|
721
718
|
statsd: StatsdConfig = StatsdConfig()
|
|
722
|
-
sentry_dsn: SecretStr = SecretStr("")
|
|
723
|
-
debug: bool = False
|
|
719
|
+
sentry_dsn: SecretStr = Field(SecretStr(""), alias="SOVEREIGN_SENTRY_DSN")
|
|
720
|
+
debug: bool = Field(False, alias="SOVEREIGN_DEBUG")
|
|
724
721
|
legacy_fields: LegacyConfig = LegacyConfig()
|
|
725
722
|
discovery_cache: DiscoveryCacheConfig = DiscoveryCacheConfig()
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
723
|
+
model_config = SettingsConfigDict(
|
|
724
|
+
env_file=".env",
|
|
725
|
+
extra="ignore",
|
|
726
|
+
env_file_encoding="utf-8",
|
|
727
|
+
populate_by_name=True,
|
|
728
|
+
)
|
|
732
729
|
|
|
733
730
|
@property
|
|
734
731
|
def passwords(self) -> List[str]:
|
|
@@ -751,10 +748,10 @@ class SovereignConfigv2(BaseSettings):
|
|
|
751
748
|
return self.__repr__()
|
|
752
749
|
|
|
753
750
|
def __repr__(self) -> str:
|
|
754
|
-
return f"SovereignConfigv2({self.
|
|
751
|
+
return f"SovereignConfigv2({self.model_dump()})"
|
|
755
752
|
|
|
756
753
|
def show(self) -> Dict[str, Any]:
|
|
757
|
-
return self.
|
|
754
|
+
return self.model_dump()
|
|
758
755
|
|
|
759
756
|
@staticmethod
|
|
760
757
|
def from_legacy_config(other: SovereignConfig) -> "SovereignConfigv2":
|
|
@@ -16,7 +16,7 @@ router = APIRouter()
|
|
|
16
16
|
@router.get("/xds_dump", summary="Displays all xDS resources as JSON")
|
|
17
17
|
async def display_config(
|
|
18
18
|
xds_type: str = Query(
|
|
19
|
-
..., title="xDS type", description="The type of request",
|
|
19
|
+
..., title="xDS type", description="The type of request", examples=["clusters"]
|
|
20
20
|
),
|
|
21
21
|
service_cluster: str = Query(
|
|
22
22
|
"*", title="The clients service cluster to emulate in this XDS request"
|
|
@@ -48,7 +48,7 @@ async def display_config(
|
|
|
48
48
|
)
|
|
49
49
|
async def debug_template(
|
|
50
50
|
xds_type: str = Query(
|
|
51
|
-
..., title="xDS type", description="The type of request",
|
|
51
|
+
..., title="xDS type", description="The type of request", examples=["clusters"]
|
|
52
52
|
),
|
|
53
53
|
service_cluster: str = Query(
|
|
54
54
|
"*", title="The clients service cluster to emulate in this XDS request"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|