sovereign 0.19.3__py3-none-any.whl → 1.0.0b148__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 +59 -48
- sovereign/cache/__init__.py +172 -0
- sovereign/cache/backends/__init__.py +110 -0
- sovereign/cache/backends/s3.py +143 -0
- sovereign/cache/filesystem.py +73 -0
- sovereign/cache/types.py +15 -0
- sovereign/configuration.py +573 -0
- sovereign/constants.py +1 -0
- sovereign/context.py +271 -104
- sovereign/dynamic_config/__init__.py +113 -0
- sovereign/dynamic_config/deser.py +78 -0
- sovereign/dynamic_config/loaders.py +120 -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 +1 -0
- sovereign/rendering.py +192 -0
- sovereign/response_class.py +18 -0
- sovereign/server.py +93 -35
- sovereign/sources/file.py +1 -1
- sovereign/sources/inline.py +1 -0
- sovereign/sources/lib.py +1 -0
- sovereign/sources/poller.py +296 -53
- sovereign/statistics.py +17 -20
- sovereign/templates/base.html +59 -46
- sovereign/templates/resources.html +203 -102
- sovereign/testing/loaders.py +8 -0
- sovereign/{modifiers/test.py → testing/modifiers.py} +0 -2
- sovereign/tracing.py +102 -0
- sovereign/types.py +299 -0
- sovereign/utils/auth.py +26 -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 +2 -1
- sovereign/utils/eds.py +37 -21
- sovereign/utils/mock.py +54 -16
- sovereign/utils/resources.py +17 -0
- sovereign/utils/version_info.py +8 -0
- sovereign/views/__init__.py +4 -0
- sovereign/views/api.py +61 -0
- sovereign/views/crypto.py +46 -15
- sovereign/views/discovery.py +37 -116
- sovereign/views/healthchecks.py +87 -18
- sovereign/views/interface.py +112 -112
- sovereign/worker.py +204 -0
- {sovereign-0.19.3.dist-info → sovereign-1.0.0b148.dist-info}/METADATA +79 -76
- sovereign-1.0.0b148.dist-info/RECORD +77 -0
- {sovereign-0.19.3.dist-info → sovereign-1.0.0b148.dist-info}/WHEEL +1 -1
- sovereign-1.0.0b148.dist-info/entry_points.txt +38 -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/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/schemas.py
DELETED
|
@@ -1,780 +0,0 @@
|
|
|
1
|
-
from os import getenv
|
|
2
|
-
import warnings
|
|
3
|
-
import multiprocessing
|
|
4
|
-
from collections import defaultdict
|
|
5
|
-
from enum import Enum
|
|
6
|
-
from pydantic import (
|
|
7
|
-
BaseModel,
|
|
8
|
-
Field,
|
|
9
|
-
BaseSettings,
|
|
10
|
-
SecretStr,
|
|
11
|
-
validator,
|
|
12
|
-
root_validator,
|
|
13
|
-
)
|
|
14
|
-
from typing import List, Any, Dict, Union, Optional, Tuple, Type
|
|
15
|
-
from types import ModuleType
|
|
16
|
-
from jinja2 import meta, Template
|
|
17
|
-
from fastapi.responses import JSONResponse
|
|
18
|
-
from sovereign.config_loader import jinja_env, Serialization, Protocol, Loadable
|
|
19
|
-
from sovereign.utils.version_info import compute_hash
|
|
20
|
-
from croniter import croniter, CroniterBadCronError
|
|
21
|
-
|
|
22
|
-
missing_arguments = {"missing", "positional", "arguments:"}
|
|
23
|
-
|
|
24
|
-
JsonResponseClass: Type[JSONResponse] = JSONResponse
|
|
25
|
-
# pylint: disable=unused-import
|
|
26
|
-
try:
|
|
27
|
-
import orjson
|
|
28
|
-
from fastapi.responses import ORJSONResponse
|
|
29
|
-
|
|
30
|
-
JsonResponseClass = ORJSONResponse
|
|
31
|
-
except ImportError:
|
|
32
|
-
try:
|
|
33
|
-
import ujson
|
|
34
|
-
from fastapi.responses import UJSONResponse
|
|
35
|
-
|
|
36
|
-
JsonResponseClass = UJSONResponse
|
|
37
|
-
except ImportError:
|
|
38
|
-
pass
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class CacheStrategy(str, Enum):
|
|
42
|
-
context = "context"
|
|
43
|
-
content = "content"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
class SourceData(BaseModel):
|
|
47
|
-
scopes: Dict[str, List[Dict[str, Any]]] = defaultdict(list)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class ConfiguredSource(BaseModel):
|
|
51
|
-
type: str
|
|
52
|
-
config: Dict[str, Any]
|
|
53
|
-
scope: str = "default" # backward compatibility
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class StatsdConfig(BaseModel):
|
|
57
|
-
host: str = "127.0.0.1"
|
|
58
|
-
port: Union[int, str] = 8125
|
|
59
|
-
tags: Dict[str, Union[Loadable, str]] = dict()
|
|
60
|
-
namespace: str = "sovereign"
|
|
61
|
-
enabled: bool = False
|
|
62
|
-
use_ms: bool = True
|
|
63
|
-
|
|
64
|
-
@validator("host", pre=True)
|
|
65
|
-
def load_host(cls, v: str) -> Any:
|
|
66
|
-
return Loadable.from_legacy_fmt(v).load()
|
|
67
|
-
|
|
68
|
-
@validator("port", pre=True)
|
|
69
|
-
def load_port(cls, v: Union[int, str]) -> Any:
|
|
70
|
-
if isinstance(v, int):
|
|
71
|
-
return v
|
|
72
|
-
elif isinstance(v, str):
|
|
73
|
-
return Loadable.from_legacy_fmt(v).load()
|
|
74
|
-
else:
|
|
75
|
-
raise ValueError(f"Received an invalid port: {v}")
|
|
76
|
-
|
|
77
|
-
@validator("tags", pre=True)
|
|
78
|
-
def load_tags(cls, v: Dict[str, Union[Loadable, str]]) -> Dict[str, Any]:
|
|
79
|
-
ret = dict()
|
|
80
|
-
for key, value in v.items():
|
|
81
|
-
if isinstance(value, dict):
|
|
82
|
-
ret[key] = Loadable(**value).load()
|
|
83
|
-
elif isinstance(value, str):
|
|
84
|
-
ret[key] = Loadable.from_legacy_fmt(value).load()
|
|
85
|
-
else:
|
|
86
|
-
raise ValueError(f"Received an invalid tag for statsd: {value}")
|
|
87
|
-
return ret
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
class DiscoveryCacheConfig(BaseModel):
|
|
91
|
-
enabled: bool = False
|
|
92
|
-
host: str = "localhost"
|
|
93
|
-
port: int = 6379
|
|
94
|
-
secure: bool = False
|
|
95
|
-
protocol: str = "redis://"
|
|
96
|
-
password: SecretStr = SecretStr("")
|
|
97
|
-
client_side: bool = True # True = Try in-memory cache before hitting redis
|
|
98
|
-
wait_for_connection_timeout: int = 5
|
|
99
|
-
socket_connect_timeout: int = 5
|
|
100
|
-
socket_timeout: int = 5
|
|
101
|
-
max_connections: int = 100
|
|
102
|
-
retry_on_timeout: bool = True # Retry connections if they timeout.
|
|
103
|
-
suppress: bool = False # False = Don't suppress connection errors. True = suppress connection errors
|
|
104
|
-
socket_keepalive: bool = True # Try to keep connections to redis around.
|
|
105
|
-
ttl: int = 60
|
|
106
|
-
|
|
107
|
-
@root_validator
|
|
108
|
-
def set_default_protocol(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
109
|
-
secure = values.get("secure")
|
|
110
|
-
if secure:
|
|
111
|
-
values["protocol"] = "rediss://"
|
|
112
|
-
return values
|
|
113
|
-
|
|
114
|
-
@root_validator
|
|
115
|
-
def set_environmental_variables(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
116
|
-
if host := getenv("SOVEREIGN_DISCOVERY_CACHE_REDIS_HOST"):
|
|
117
|
-
values["host"] = host
|
|
118
|
-
if port := getenv("SOVEREIGN_DISCOVERY_CACHE_REDIS_PORT"):
|
|
119
|
-
values["port"] = int(port)
|
|
120
|
-
if password := getenv("SOVEREIGN_DISCOVERY_CACHE_REDIS_PASSWORD"):
|
|
121
|
-
values["password"] = SecretStr(password)
|
|
122
|
-
return values
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
class XdsTemplate:
|
|
126
|
-
def __init__(self, path: Union[str, Loadable]) -> None:
|
|
127
|
-
if isinstance(path, str):
|
|
128
|
-
self.loadable: Loadable = Loadable.from_legacy_fmt(path)
|
|
129
|
-
elif isinstance(path, Loadable):
|
|
130
|
-
self.loadable = path
|
|
131
|
-
self.is_python_source = self.loadable.protocol == Protocol.python
|
|
132
|
-
self.source = self.load_source()
|
|
133
|
-
template_ast = jinja_env.parse(self.source)
|
|
134
|
-
self.jinja_variables = meta.find_undeclared_variables(template_ast)
|
|
135
|
-
|
|
136
|
-
def __call__(
|
|
137
|
-
self, *args: Any, **kwargs: Any
|
|
138
|
-
) -> Optional[Union[Dict[str, Any], str]]:
|
|
139
|
-
if not hasattr(self, "code"):
|
|
140
|
-
self.code: Union[Template, ModuleType] = self.loadable.load()
|
|
141
|
-
if isinstance(self.code, ModuleType):
|
|
142
|
-
try:
|
|
143
|
-
return {"resources": list(self.code.call(*args, **kwargs))}
|
|
144
|
-
except TypeError as e:
|
|
145
|
-
if not set(str(e).split()).issuperset(missing_arguments):
|
|
146
|
-
raise e
|
|
147
|
-
message_start = str(e).find(":")
|
|
148
|
-
missing_args = str(e)[message_start + 2 :]
|
|
149
|
-
supplied_args = list(kwargs.keys())
|
|
150
|
-
raise TypeError(
|
|
151
|
-
f"Tried to render a template using partial arguments. "
|
|
152
|
-
f"Missing args: {missing_args}. Supplied args: {args} "
|
|
153
|
-
f"Supplied keyword args: {supplied_args}"
|
|
154
|
-
)
|
|
155
|
-
else:
|
|
156
|
-
return self.code.render(*args, **kwargs)
|
|
157
|
-
|
|
158
|
-
def load_source(self) -> str:
|
|
159
|
-
if self.loadable.serialization in (Serialization.jinja, Serialization.jinja2):
|
|
160
|
-
# The Jinja2 template serializer does not properly set a name
|
|
161
|
-
# for the loaded template.
|
|
162
|
-
# The repr for the template prints out as the memory address
|
|
163
|
-
# This makes it really hard to generate a consistent version_info string
|
|
164
|
-
# in rendered configuration.
|
|
165
|
-
# For this reason, we re-load the template as a string instead, and create a checksum.
|
|
166
|
-
old_serialization = self.loadable.serialization
|
|
167
|
-
self.loadable.serialization = Serialization("string")
|
|
168
|
-
ret = self.loadable.load()
|
|
169
|
-
self.loadable.serialization = old_serialization
|
|
170
|
-
return str(ret)
|
|
171
|
-
elif self.is_python_source:
|
|
172
|
-
# If the template specified is a python source file,
|
|
173
|
-
# we can simply read and return the source of it.
|
|
174
|
-
old_protocol = self.loadable.protocol
|
|
175
|
-
old_serialization = self.loadable.serialization
|
|
176
|
-
self.loadable.protocol = Protocol("inline")
|
|
177
|
-
self.loadable.serialization = Serialization("string")
|
|
178
|
-
ret = self.loadable.load()
|
|
179
|
-
self.loadable.protocol = old_protocol
|
|
180
|
-
self.loadable.serialization = old_serialization
|
|
181
|
-
return str(ret)
|
|
182
|
-
ret = self.loadable.load()
|
|
183
|
-
return str(ret)
|
|
184
|
-
|
|
185
|
-
def __repr__(self) -> str:
|
|
186
|
-
return f"XdsTemplate({self.loadable=}, {self.is_python_source=}, {self.source=}, {self.jinja_variables=})"
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
class ProcessedTemplate:
|
|
190
|
-
def __init__(
|
|
191
|
-
self,
|
|
192
|
-
resources: List[Dict[str, Any]],
|
|
193
|
-
version_info: Optional[str],
|
|
194
|
-
) -> None:
|
|
195
|
-
self.resources = resources
|
|
196
|
-
self.version_info = version_info
|
|
197
|
-
self._rendered: Optional[bytes] = None
|
|
198
|
-
|
|
199
|
-
@property
|
|
200
|
-
def version(self) -> str:
|
|
201
|
-
return self.version_info or compute_hash(self.resources)
|
|
202
|
-
|
|
203
|
-
@property
|
|
204
|
-
def rendered(self) -> bytes:
|
|
205
|
-
if self._rendered is None:
|
|
206
|
-
result = JsonResponseClass(content="").render(
|
|
207
|
-
content={
|
|
208
|
-
"version_info": self.version,
|
|
209
|
-
"resources": self.resources,
|
|
210
|
-
}
|
|
211
|
-
)
|
|
212
|
-
self._rendered = result
|
|
213
|
-
return self._rendered
|
|
214
|
-
|
|
215
|
-
def deserialize_resources(self) -> List[Dict[str, Any]]:
|
|
216
|
-
return self.resources
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
class Locality(BaseModel):
|
|
220
|
-
region: str = Field(None)
|
|
221
|
-
zone: str = Field(None)
|
|
222
|
-
sub_zone: str = Field(None)
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
class SemanticVersion(BaseModel):
|
|
226
|
-
major_number: int = 0
|
|
227
|
-
minor_number: int = 0
|
|
228
|
-
patch: int = 0
|
|
229
|
-
|
|
230
|
-
def __str__(self) -> str:
|
|
231
|
-
return f"{self.major_number}.{self.minor_number}.{self.patch}"
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
class BuildVersion(BaseModel):
|
|
235
|
-
version: SemanticVersion = SemanticVersion()
|
|
236
|
-
metadata: Dict[str, Any] = {}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
class Extension(BaseModel):
|
|
240
|
-
name: Optional[str] = None
|
|
241
|
-
category: Optional[str] = None
|
|
242
|
-
version: Optional[BuildVersion] = None
|
|
243
|
-
disabled: Optional[bool] = None
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
class Node(BaseModel):
|
|
247
|
-
id: str = Field("-", title="Hostname")
|
|
248
|
-
cluster: str = Field(
|
|
249
|
-
...,
|
|
250
|
-
title="Envoy service-cluster",
|
|
251
|
-
description="The ``--service-cluster`` configured by the Envoy client",
|
|
252
|
-
)
|
|
253
|
-
metadata: Dict[str, Any] = Field(default_factory=dict, title="Key:value metadata")
|
|
254
|
-
locality: Locality = Field(Locality(), title="Locality") # type: ignore
|
|
255
|
-
build_version: str = Field(
|
|
256
|
-
None, # Optional in the v3 Envoy API
|
|
257
|
-
title="Envoy build/release version string",
|
|
258
|
-
description="Used to identify what version of Envoy the "
|
|
259
|
-
"client is running, and what config to provide in response",
|
|
260
|
-
)
|
|
261
|
-
user_agent_name: str = "envoy"
|
|
262
|
-
user_agent_version: str = ""
|
|
263
|
-
user_agent_build_version: BuildVersion = BuildVersion()
|
|
264
|
-
extensions: List[Extension] = []
|
|
265
|
-
client_features: List[str] = []
|
|
266
|
-
|
|
267
|
-
@property
|
|
268
|
-
def common(self) -> Tuple[str, str, str, BuildVersion, Locality]:
|
|
269
|
-
"""
|
|
270
|
-
Returns fields that are the same in adjacent proxies
|
|
271
|
-
ie. proxies that are part of the same logical group
|
|
272
|
-
"""
|
|
273
|
-
return (
|
|
274
|
-
self.cluster,
|
|
275
|
-
self.build_version,
|
|
276
|
-
self.user_agent_version,
|
|
277
|
-
self.user_agent_build_version,
|
|
278
|
-
self.locality,
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
class Resources(List[str]):
|
|
283
|
-
"""
|
|
284
|
-
Acts like a regular list except it returns True
|
|
285
|
-
for all membership tests when empty.
|
|
286
|
-
"""
|
|
287
|
-
|
|
288
|
-
def __contains__(self, item: object) -> bool:
|
|
289
|
-
if len(self) == 0:
|
|
290
|
-
return True
|
|
291
|
-
return item in list(self)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
class Status(BaseModel):
|
|
295
|
-
code: int
|
|
296
|
-
message: str
|
|
297
|
-
details: List[Any]
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
class DiscoveryRequest(BaseModel):
|
|
301
|
-
node: Node = Field(..., title="Node information about the envoy proxy")
|
|
302
|
-
version_info: str = Field(
|
|
303
|
-
"0", title="The version of the envoy clients current configuration"
|
|
304
|
-
)
|
|
305
|
-
resource_names: Resources = Field(
|
|
306
|
-
Resources(), title="List of requested resource names"
|
|
307
|
-
)
|
|
308
|
-
hide_private_keys: bool = False
|
|
309
|
-
type_url: Optional[str] = Field(
|
|
310
|
-
None, title="The corresponding type_url for the requested resource"
|
|
311
|
-
)
|
|
312
|
-
desired_controlplane: str = Field(
|
|
313
|
-
None, title="The host header provided in the Discovery Request"
|
|
314
|
-
)
|
|
315
|
-
error_detail: Status = Field(
|
|
316
|
-
None, title="Error details from the previous xDS request"
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
@property
|
|
320
|
-
def envoy_version(self) -> str:
|
|
321
|
-
try:
|
|
322
|
-
version = str(self.node.user_agent_build_version.version)
|
|
323
|
-
assert version != "0.0.0"
|
|
324
|
-
except AssertionError:
|
|
325
|
-
try:
|
|
326
|
-
build_version = self.node.build_version
|
|
327
|
-
_, version, *_ = build_version.split("/")
|
|
328
|
-
except (AttributeError, ValueError):
|
|
329
|
-
# TODO: log/metric this?
|
|
330
|
-
return "default"
|
|
331
|
-
return version
|
|
332
|
-
|
|
333
|
-
@property
|
|
334
|
-
def resources(self) -> Resources:
|
|
335
|
-
return Resources(self.resource_names)
|
|
336
|
-
|
|
337
|
-
@property
|
|
338
|
-
def uid(self) -> str:
|
|
339
|
-
return compute_hash(
|
|
340
|
-
self.resources,
|
|
341
|
-
self.node.common,
|
|
342
|
-
self.desired_controlplane,
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
class DiscoveryResponse(BaseModel):
|
|
347
|
-
version_info: str = Field(
|
|
348
|
-
..., title="The version of the configuration in the response"
|
|
349
|
-
)
|
|
350
|
-
resources: List[Any] = Field(..., title="The requested configuration resources")
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
class SovereignAsgiConfig(BaseSettings):
|
|
354
|
-
host: str = "0.0.0.0"
|
|
355
|
-
port: int = 8080
|
|
356
|
-
keepalive: int = 5
|
|
357
|
-
workers: int = multiprocessing.cpu_count() * 2 + 1
|
|
358
|
-
threads: int = 1
|
|
359
|
-
reuse_port: bool = True
|
|
360
|
-
preload_app: bool = True
|
|
361
|
-
log_level: str = "warning"
|
|
362
|
-
worker_class: str = "uvicorn.workers.UvicornWorker"
|
|
363
|
-
worker_timeout: int = 30
|
|
364
|
-
worker_tmp_dir: str = "/dev/shm"
|
|
365
|
-
graceful_timeout: int = worker_timeout * 2
|
|
366
|
-
|
|
367
|
-
class Config:
|
|
368
|
-
fields = {
|
|
369
|
-
"host": {"env": "SOVEREIGN_HOST"},
|
|
370
|
-
"port": {"env": "SOVEREIGN_PORT"},
|
|
371
|
-
"keepalive": {"env": "SOVEREIGN_KEEPALIVE"},
|
|
372
|
-
"workers": {"env": "SOVEREIGN_WORKERS"},
|
|
373
|
-
"threads": {"env": "SOVEREIGN_THREADS"},
|
|
374
|
-
"preload_app": {"env": "SOVEREIGN_PRELOAD"},
|
|
375
|
-
"worker_timeout": {"env": "SOVEREIGN_WORKER_TIMEOUT"},
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
def as_gunicorn_conf(self) -> Dict[str, Any]:
|
|
379
|
-
return {
|
|
380
|
-
"bind": ":".join(map(str, [self.host, self.port])),
|
|
381
|
-
"keepalive": self.keepalive,
|
|
382
|
-
"reuse_port": self.reuse_port,
|
|
383
|
-
"preload_app": self.preload_app,
|
|
384
|
-
"loglevel": self.log_level,
|
|
385
|
-
"timeout": self.worker_timeout,
|
|
386
|
-
"threads": self.threads,
|
|
387
|
-
"workers": self.workers,
|
|
388
|
-
"worker_class": self.worker_class,
|
|
389
|
-
"worker_tmp_dir": self.worker_tmp_dir,
|
|
390
|
-
"graceful_timeout": self.graceful_timeout,
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
class SovereignConfig(BaseSettings):
|
|
395
|
-
sources: List[ConfiguredSource]
|
|
396
|
-
templates: Dict[str, Dict[str, Union[str, Loadable]]]
|
|
397
|
-
template_context: Dict[str, Any] = {}
|
|
398
|
-
eds_priority_matrix: Dict[str, Dict[str, str]] = {}
|
|
399
|
-
modifiers: List[str] = []
|
|
400
|
-
global_modifiers: List[str] = []
|
|
401
|
-
regions: List[str] = []
|
|
402
|
-
statsd: StatsdConfig = StatsdConfig()
|
|
403
|
-
auth_enabled: bool = False
|
|
404
|
-
auth_passwords: str = ""
|
|
405
|
-
encryption_key: str = ""
|
|
406
|
-
environment: str = "local"
|
|
407
|
-
debug_enabled: bool = False
|
|
408
|
-
sentry_dsn: str = ""
|
|
409
|
-
node_match_key: str = "cluster"
|
|
410
|
-
node_matching: bool = True
|
|
411
|
-
source_match_key: str = "service_clusters"
|
|
412
|
-
sources_refresh_rate: int = 30
|
|
413
|
-
cache_strategy: str = "context"
|
|
414
|
-
refresh_context: bool = False
|
|
415
|
-
context_refresh_rate: Optional[int]
|
|
416
|
-
context_refresh_cron: Optional[str]
|
|
417
|
-
dns_hard_fail: bool = False
|
|
418
|
-
enable_application_logs: bool = False
|
|
419
|
-
enable_access_logs: bool = True
|
|
420
|
-
log_fmt: Optional[str] = ""
|
|
421
|
-
ignore_empty_log_fields: bool = False
|
|
422
|
-
discovery_cache: DiscoveryCacheConfig = DiscoveryCacheConfig()
|
|
423
|
-
|
|
424
|
-
class Config:
|
|
425
|
-
fields = {
|
|
426
|
-
"auth_enabled": {"env": "SOVEREIGN_AUTH_ENABLED"},
|
|
427
|
-
"auth_passwords": {"env": "SOVEREIGN_AUTH_PASSWORDS"},
|
|
428
|
-
"encryption_key": {"env": "SOVEREIGN_ENCRYPTION_KEY"},
|
|
429
|
-
"environment": {"env": "SOVEREIGN_ENVIRONMENT"},
|
|
430
|
-
"debug_enabled": {"env": "SOVEREIGN_DEBUG_ENABLED"},
|
|
431
|
-
"sentry_dsn": {"env": "SOVEREIGN_SENTRY_DSN"},
|
|
432
|
-
"node_match_key": {"env": "SOVEREIGN_NODE_MATCH_KEY"},
|
|
433
|
-
"node_matching": {"env": "SOVEREIGN_NODE_MATCHING"},
|
|
434
|
-
"source_match_key": {"env": "SOVEREIGN_SOURCE_MATCH_KEY"},
|
|
435
|
-
"sources_refresh_rate": {"env": "SOVEREIGN_SOURCES_REFRESH_RATE"},
|
|
436
|
-
"cache_strategy": {"env": "SOVEREIGN_CACHE_STRATEGY"},
|
|
437
|
-
"refresh_context": {"env": "SOVEREIGN_REFRESH_CONTEXT"},
|
|
438
|
-
"context_refresh_rate": {"env": "SOVEREIGN_CONTEXT_REFRESH_RATE"},
|
|
439
|
-
"context_refresh_cron": {"env": "SOVEREIGN_CONTEXT_REFRESH_CRON"},
|
|
440
|
-
"dns_hard_fail": {"env": "SOVEREIGN_DNS_HARD_FAIL"},
|
|
441
|
-
"enable_application_logs": {"env": "SOVEREIGN_ENABLE_APPLICATION_LOGS"},
|
|
442
|
-
"enable_access_logs": {"env": "SOVEREIGN_ENABLE_ACCESS_LOGS"},
|
|
443
|
-
"log_fmt": {"env": "SOVEREIGN_LOG_FORMAT"},
|
|
444
|
-
"ignore_empty_fields": {"env": "SOVEREIGN_LOG_IGNORE_EMPTY"},
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
@property
|
|
448
|
-
def passwords(self) -> List[str]:
|
|
449
|
-
return self.auth_passwords.split(",") or []
|
|
450
|
-
|
|
451
|
-
def xds_templates(self) -> Dict[str, Dict[str, XdsTemplate]]:
|
|
452
|
-
ret: Dict[str, Dict[str, XdsTemplate]] = {
|
|
453
|
-
"__any__": {}
|
|
454
|
-
} # Special key to hold templates from all versions
|
|
455
|
-
for version, templates in self.templates.items():
|
|
456
|
-
loaded_templates = {
|
|
457
|
-
_type: XdsTemplate(path=path) for _type, path in templates.items()
|
|
458
|
-
}
|
|
459
|
-
ret[str(version)] = loaded_templates
|
|
460
|
-
ret["__any__"].update(loaded_templates)
|
|
461
|
-
return ret
|
|
462
|
-
|
|
463
|
-
def __str__(self) -> str:
|
|
464
|
-
return self.__repr__()
|
|
465
|
-
|
|
466
|
-
def __repr__(self) -> str:
|
|
467
|
-
kwargs = [f"{k}={v}" for k, v in self.show().items()]
|
|
468
|
-
return f"SovereignConfig({kwargs})"
|
|
469
|
-
|
|
470
|
-
def show(self) -> Dict[str, Any]:
|
|
471
|
-
safe_items = dict()
|
|
472
|
-
for key, value in self.__dict__.items():
|
|
473
|
-
if key in ["auth_passwords", "encryption_key", "passwords", "sentry_dsn"]:
|
|
474
|
-
value = "redacted"
|
|
475
|
-
safe_items[key] = value
|
|
476
|
-
return safe_items
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
class TemplateSpecification(BaseModel):
|
|
480
|
-
type: str
|
|
481
|
-
spec: Loadable
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
class NodeMatching(BaseSettings):
|
|
485
|
-
enabled: bool = True
|
|
486
|
-
source_key: str = "service_clusters"
|
|
487
|
-
node_key: str = "cluster"
|
|
488
|
-
|
|
489
|
-
class Config:
|
|
490
|
-
fields = {
|
|
491
|
-
"enabled": {"env": "SOVEREIGN_NODE_MATCHING_ENABLED"},
|
|
492
|
-
"source_key": {"env": "SOVEREIGN_SOURCE_MATCH_KEY"},
|
|
493
|
-
"node_key": {"env": "SOVEREIGN_NODE_MATCH_KEY"},
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
class AuthConfiguration(BaseSettings):
|
|
498
|
-
enabled: bool = False
|
|
499
|
-
auth_passwords: SecretStr = SecretStr("")
|
|
500
|
-
encryption_key: SecretStr = SecretStr("")
|
|
501
|
-
|
|
502
|
-
class Config:
|
|
503
|
-
fields = {
|
|
504
|
-
"enabled": {"env": "SOVEREIGN_AUTH_ENABLED"},
|
|
505
|
-
"auth_passwords": {"env": "SOVEREIGN_AUTH_PASSWORDS"},
|
|
506
|
-
"encryption_key": {"env": "SOVEREIGN_ENCRYPTION_KEY"},
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
class ApplicationLogConfiguration(BaseSettings):
|
|
511
|
-
enabled: bool = False
|
|
512
|
-
# currently only support /dev/stdout as JSON
|
|
513
|
-
|
|
514
|
-
class Config:
|
|
515
|
-
fields = {
|
|
516
|
-
"enabled": {"env": "SOVEREIGN_ENABLE_APPLICATION_LOGS"},
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
class AccessLogConfiguration(BaseSettings):
|
|
521
|
-
enabled: bool = True
|
|
522
|
-
log_fmt: Optional[str] = None
|
|
523
|
-
ignore_empty_fields: bool = False
|
|
524
|
-
|
|
525
|
-
class Config:
|
|
526
|
-
fields = {
|
|
527
|
-
"enabled": {"env": "SOVEREIGN_ENABLE_ACCESS_LOGS"},
|
|
528
|
-
"log_fmt": {"env": "SOVEREIGN_LOG_FORMAT"},
|
|
529
|
-
"ignore_empty_fields": {"env": "SOVEREIGN_LOG_IGNORE_EMPTY"},
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
class LoggingConfiguration(BaseSettings):
|
|
534
|
-
application_logs: ApplicationLogConfiguration = ApplicationLogConfiguration()
|
|
535
|
-
access_logs: AccessLogConfiguration = AccessLogConfiguration()
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
class ContextConfiguration(BaseSettings):
|
|
539
|
-
context: Dict[str, Loadable] = {}
|
|
540
|
-
refresh: bool = False
|
|
541
|
-
refresh_rate: Optional[int] = None
|
|
542
|
-
refresh_cron: Optional[str] = None
|
|
543
|
-
|
|
544
|
-
@staticmethod
|
|
545
|
-
def context_from_legacy(context: Dict[str, str]) -> Dict[str, Loadable]:
|
|
546
|
-
ret = dict()
|
|
547
|
-
for key, value in context.items():
|
|
548
|
-
ret[key] = Loadable.from_legacy_fmt(value)
|
|
549
|
-
return ret
|
|
550
|
-
|
|
551
|
-
@root_validator(pre=False)
|
|
552
|
-
def validate_single_use_refresh_method(
|
|
553
|
-
cls, values: Dict[str, Any]
|
|
554
|
-
) -> Dict[str, Any]:
|
|
555
|
-
refresh_rate = values.get("refresh_rate")
|
|
556
|
-
refresh_cron = values.get("refresh_cron")
|
|
557
|
-
|
|
558
|
-
if (refresh_rate is not None) and (refresh_cron is not None):
|
|
559
|
-
raise RuntimeError(
|
|
560
|
-
f"Only one of SOVEREIGN_CONTEXT_REFRESH_RATE or SOVEREIGN_CONTEXT_REFRESH_CRON can be defined. Got {refresh_rate=} and {refresh_cron=}"
|
|
561
|
-
)
|
|
562
|
-
return values
|
|
563
|
-
|
|
564
|
-
@root_validator
|
|
565
|
-
def set_default_refresh_rate(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
566
|
-
refresh_rate = values.get("refresh_rate")
|
|
567
|
-
refresh_cron = values.get("refresh_cron")
|
|
568
|
-
|
|
569
|
-
if (refresh_rate is None) and (refresh_cron is None):
|
|
570
|
-
values["refresh_rate"] = 3600
|
|
571
|
-
return values
|
|
572
|
-
|
|
573
|
-
@validator("refresh_cron")
|
|
574
|
-
def validate_refresh_cron(cls, v: Optional[str]) -> Optional[str]:
|
|
575
|
-
if v is None:
|
|
576
|
-
return v
|
|
577
|
-
if not croniter.is_valid(v):
|
|
578
|
-
raise CroniterBadCronError(f"'{v}' is not a valid cron expression")
|
|
579
|
-
return v
|
|
580
|
-
|
|
581
|
-
class Config:
|
|
582
|
-
fields = {
|
|
583
|
-
"refresh": {"env": "SOVEREIGN_REFRESH_CONTEXT"},
|
|
584
|
-
"refresh_rate": {"env": "SOVEREIGN_CONTEXT_REFRESH_RATE"},
|
|
585
|
-
"refresh_cron": {"env": "SOVEREIGN_CONTEXT_REFRESH_CRON"},
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
class SourcesConfiguration(BaseSettings):
|
|
590
|
-
refresh_rate: int = 30
|
|
591
|
-
cache_strategy: CacheStrategy = CacheStrategy.context
|
|
592
|
-
|
|
593
|
-
class Config:
|
|
594
|
-
fields = {
|
|
595
|
-
"refresh_rate": {"env": "SOVEREIGN_SOURCES_REFRESH_RATE"},
|
|
596
|
-
"cache_strategy": {"env": "SOVEREIGN_CACHE_STRATEGY"},
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
class LegacyConfig(BaseSettings):
|
|
601
|
-
regions: Optional[List[str]] = None
|
|
602
|
-
eds_priority_matrix: Optional[Dict[str, Dict[str, str]]] = None
|
|
603
|
-
dns_hard_fail: Optional[bool] = None
|
|
604
|
-
environment: Optional[str] = None
|
|
605
|
-
|
|
606
|
-
@validator("regions")
|
|
607
|
-
def regions_is_set(cls, v: Optional[List[str]]) -> List[str]:
|
|
608
|
-
if v is not None:
|
|
609
|
-
warnings.warn(
|
|
610
|
-
"Setting regions via config is deprecated. "
|
|
611
|
-
"It is suggested to use a modifier or template "
|
|
612
|
-
"logic in order to achieve the same goal.",
|
|
613
|
-
DeprecationWarning,
|
|
614
|
-
)
|
|
615
|
-
return v
|
|
616
|
-
else:
|
|
617
|
-
return []
|
|
618
|
-
|
|
619
|
-
@validator("eds_priority_matrix")
|
|
620
|
-
def eds_priority_matrix_is_set(
|
|
621
|
-
cls, v: Optional[Dict[str, Dict[str, Any]]]
|
|
622
|
-
) -> Dict[str, Dict[str, Any]]:
|
|
623
|
-
if v is not None:
|
|
624
|
-
warnings.warn(
|
|
625
|
-
"Setting eds_priority_matrix via config is deprecated. "
|
|
626
|
-
"It is suggested to use a modifier or template "
|
|
627
|
-
"logic in order to achieve the same goal.",
|
|
628
|
-
DeprecationWarning,
|
|
629
|
-
)
|
|
630
|
-
return v
|
|
631
|
-
else:
|
|
632
|
-
return {}
|
|
633
|
-
|
|
634
|
-
@validator("dns_hard_fail")
|
|
635
|
-
def dns_hard_fail_is_set(cls, v: Optional[bool]) -> bool:
|
|
636
|
-
if v is not None:
|
|
637
|
-
warnings.warn(
|
|
638
|
-
"Setting dns_hard_fail via config is deprecated. "
|
|
639
|
-
"It is suggested to supply a module that can perform "
|
|
640
|
-
"dns resolution to template_context, so that it can "
|
|
641
|
-
"be used via templates instead.",
|
|
642
|
-
DeprecationWarning,
|
|
643
|
-
)
|
|
644
|
-
return v
|
|
645
|
-
else:
|
|
646
|
-
return False
|
|
647
|
-
|
|
648
|
-
@validator("environment")
|
|
649
|
-
def environment_is_set(cls, v: Optional[str]) -> Optional[str]:
|
|
650
|
-
if v is not None:
|
|
651
|
-
warnings.warn(
|
|
652
|
-
"Setting environment via config is deprecated. "
|
|
653
|
-
"It is suggested to configure this value through log_fmt "
|
|
654
|
-
"instead.",
|
|
655
|
-
DeprecationWarning,
|
|
656
|
-
)
|
|
657
|
-
return v
|
|
658
|
-
else:
|
|
659
|
-
return None
|
|
660
|
-
|
|
661
|
-
class Config:
|
|
662
|
-
fields = {
|
|
663
|
-
"dns_hard_fail": {"env": "SOVEREIGN_DNS_HARD_FAIL"},
|
|
664
|
-
"environment": {"env": "SOVEREIGN_ENVIRONMENT"},
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
class SovereignConfigv2(BaseSettings):
|
|
669
|
-
sources: List[ConfiguredSource]
|
|
670
|
-
templates: Dict[str, List[TemplateSpecification]]
|
|
671
|
-
source_config: SourcesConfiguration = SourcesConfiguration()
|
|
672
|
-
modifiers: List[str] = []
|
|
673
|
-
global_modifiers: List[str] = []
|
|
674
|
-
template_context: ContextConfiguration = ContextConfiguration()
|
|
675
|
-
matching: NodeMatching = NodeMatching()
|
|
676
|
-
authentication: AuthConfiguration = AuthConfiguration()
|
|
677
|
-
logging: LoggingConfiguration = LoggingConfiguration()
|
|
678
|
-
statsd: StatsdConfig = StatsdConfig()
|
|
679
|
-
sentry_dsn: SecretStr = SecretStr("")
|
|
680
|
-
debug: bool = False
|
|
681
|
-
legacy_fields: LegacyConfig = LegacyConfig()
|
|
682
|
-
discovery_cache: DiscoveryCacheConfig = DiscoveryCacheConfig()
|
|
683
|
-
|
|
684
|
-
class Config:
|
|
685
|
-
fields = {
|
|
686
|
-
"sentry_dsn": {"env": "SOVEREIGN_SENTRY_DSN"},
|
|
687
|
-
"debug": {"env": "SOVEREIGN_DEBUG"},
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
@property
|
|
691
|
-
def passwords(self) -> List[str]:
|
|
692
|
-
return self.authentication.auth_passwords.get_secret_value().split(",") or []
|
|
693
|
-
|
|
694
|
-
def xds_templates(self) -> Dict[str, Dict[str, XdsTemplate]]:
|
|
695
|
-
ret: Dict[str, Dict[str, XdsTemplate]] = {
|
|
696
|
-
"__any__": {}
|
|
697
|
-
} # Special key to hold templates from all versions
|
|
698
|
-
for version, template_specs in self.templates.items():
|
|
699
|
-
loaded_templates = {
|
|
700
|
-
template.type: XdsTemplate(path=template.spec)
|
|
701
|
-
for template in template_specs
|
|
702
|
-
}
|
|
703
|
-
ret[str(version)] = loaded_templates
|
|
704
|
-
ret["__any__"].update(loaded_templates)
|
|
705
|
-
return ret
|
|
706
|
-
|
|
707
|
-
def __str__(self) -> str:
|
|
708
|
-
return self.__repr__()
|
|
709
|
-
|
|
710
|
-
def __repr__(self) -> str:
|
|
711
|
-
return f"SovereignConfigv2({self.dict()})"
|
|
712
|
-
|
|
713
|
-
def show(self) -> Dict[str, Any]:
|
|
714
|
-
return self.dict()
|
|
715
|
-
|
|
716
|
-
@staticmethod
|
|
717
|
-
def from_legacy_config(other: SovereignConfig) -> "SovereignConfigv2":
|
|
718
|
-
new_templates = dict()
|
|
719
|
-
for version, templates in other.templates.items():
|
|
720
|
-
specs = list()
|
|
721
|
-
for type, path in templates.items():
|
|
722
|
-
if isinstance(path, str):
|
|
723
|
-
specs.append(
|
|
724
|
-
TemplateSpecification(
|
|
725
|
-
type=type, spec=Loadable.from_legacy_fmt(path)
|
|
726
|
-
)
|
|
727
|
-
)
|
|
728
|
-
else:
|
|
729
|
-
# Just in case? Although this shouldn't happen
|
|
730
|
-
specs.append(TemplateSpecification(type=type, spec=path))
|
|
731
|
-
new_templates[str(version)] = specs
|
|
732
|
-
|
|
733
|
-
return SovereignConfigv2(
|
|
734
|
-
sources=other.sources,
|
|
735
|
-
templates=new_templates,
|
|
736
|
-
source_config=SourcesConfiguration(
|
|
737
|
-
refresh_rate=other.sources_refresh_rate,
|
|
738
|
-
cache_strategy=CacheStrategy(other.cache_strategy),
|
|
739
|
-
),
|
|
740
|
-
modifiers=other.modifiers,
|
|
741
|
-
global_modifiers=other.global_modifiers,
|
|
742
|
-
template_context=ContextConfiguration(
|
|
743
|
-
context=ContextConfiguration.context_from_legacy(
|
|
744
|
-
other.template_context
|
|
745
|
-
),
|
|
746
|
-
refresh=other.refresh_context,
|
|
747
|
-
refresh_rate=other.context_refresh_rate,
|
|
748
|
-
refresh_cron=other.context_refresh_cron,
|
|
749
|
-
),
|
|
750
|
-
matching=NodeMatching(
|
|
751
|
-
enabled=other.node_matching,
|
|
752
|
-
source_key=other.source_match_key,
|
|
753
|
-
node_key=other.node_match_key,
|
|
754
|
-
),
|
|
755
|
-
authentication=AuthConfiguration(
|
|
756
|
-
enabled=other.auth_enabled,
|
|
757
|
-
auth_passwords=SecretStr(other.auth_passwords),
|
|
758
|
-
encryption_key=SecretStr(other.encryption_key),
|
|
759
|
-
),
|
|
760
|
-
logging=LoggingConfiguration(
|
|
761
|
-
application_logs=ApplicationLogConfiguration(
|
|
762
|
-
enabled=other.enable_application_logs,
|
|
763
|
-
),
|
|
764
|
-
access_logs=AccessLogConfiguration(
|
|
765
|
-
enabled=other.enable_access_logs,
|
|
766
|
-
log_fmt=other.log_fmt,
|
|
767
|
-
ignore_empty_fields=other.ignore_empty_log_fields,
|
|
768
|
-
),
|
|
769
|
-
),
|
|
770
|
-
statsd=other.statsd,
|
|
771
|
-
sentry_dsn=SecretStr(other.sentry_dsn),
|
|
772
|
-
debug=other.debug_enabled,
|
|
773
|
-
legacy_fields=LegacyConfig(
|
|
774
|
-
regions=other.regions,
|
|
775
|
-
eds_priority_matrix=other.eds_priority_matrix,
|
|
776
|
-
dns_hard_fail=other.dns_hard_fail,
|
|
777
|
-
environment=other.environment,
|
|
778
|
-
),
|
|
779
|
-
discovery_cache=other.discovery_cache,
|
|
780
|
-
)
|