sovereign 1.0.0b127__tar.gz → 1.0.0b148__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-1.0.0b127 → sovereign-1.0.0b148}/PKG-INFO +42 -48
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/README.md +5 -4
- sovereign-1.0.0b148/pyproject.toml +146 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/__init__.py +2 -20
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/app.py +20 -23
- sovereign-1.0.0b148/src/sovereign/cache/__init__.py +172 -0
- sovereign-1.0.0b148/src/sovereign/cache/backends/__init__.py +110 -0
- sovereign-1.0.0b148/src/sovereign/cache/backends/s3.py +143 -0
- sovereign-1.0.0b148/src/sovereign/cache/filesystem.py +73 -0
- sovereign-1.0.0b148/src/sovereign/cache/types.py +15 -0
- sovereign-1.0.0b148/src/sovereign/configuration.py +573 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/context.py +31 -22
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/dynamic_config/__init__.py +48 -30
- sovereign-1.0.0b148/src/sovereign/events.py +49 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/logging/access_logger.py +1 -1
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/logging/application_logger.py +1 -1
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/logging/bootstrapper.py +1 -1
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/rendering.py +65 -38
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/server.py +5 -4
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/sources/poller.py +46 -19
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/statistics.py +2 -2
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/testing/modifiers.py +0 -2
- sovereign-1.0.0b148/src/sovereign/tracing.py +102 -0
- sovereign-1.0.0b148/src/sovereign/types.py +299 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/auth.py +3 -2
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/crypto.py +1 -1
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/eds.py +2 -2
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/mock.py +2 -2
- sovereign-1.0.0b148/src/sovereign/views/__init__.py +4 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/views/api.py +13 -13
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/views/crypto.py +1 -1
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/views/discovery.py +10 -6
- sovereign-1.0.0b148/src/sovereign/views/healthchecks.py +109 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/views/interface.py +13 -6
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/worker.py +80 -74
- sovereign-1.0.0b148/src/sovereign_files/static/style.css +16143 -0
- sovereign-1.0.0b148/src/sovereign_files/static/style.css.map +1 -0
- sovereign-1.0.0b127/LICENSE.txt +0 -13
- sovereign-1.0.0b127/pyproject.toml +0 -143
- sovereign-1.0.0b127/src/sovereign/cache.py +0 -179
- sovereign-1.0.0b127/src/sovereign/schemas.py +0 -1052
- sovereign-1.0.0b127/src/sovereign/tracing.py +0 -85
- sovereign-1.0.0b127/src/sovereign/views/healthchecks.py +0 -55
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/constants.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/dynamic_config/deser.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/dynamic_config/loaders.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/error_info.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/logging/base_logger.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/logging/types.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/middlewares.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/modifiers/__init__.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/modifiers/lib.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/response_class.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/sources/__init__.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/sources/file.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/sources/inline.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/sources/lib.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/templates/base.html +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/templates/err.html +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/templates/resources.html +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/testing/loaders.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/__init__.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/__init__.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/__init__.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/base_cipher.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/disabled_cipher.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/crypto/suites/fernet_cipher.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/dictupdate.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/entry_point_loader.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/resources.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/templates.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/timer.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/version_info.py +0 -0
- {sovereign-1.0.0b127 → sovereign-1.0.0b148}/src/sovereign/utils/weighted_clusters.py +0 -0
- {sovereign-1.0.0b127/src/sovereign/views → sovereign-1.0.0b148/src/sovereign_files}/__init__.py +0 -0
- {sovereign-1.0.0b127/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/darkmode.js +0 -0
- {sovereign-1.0.0b127/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/node_expression.js +0 -0
- {sovereign-1.0.0b127/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/panel.js +0 -0
- {sovereign-1.0.0b127/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/resources.css +0 -0
- {sovereign-1.0.0b127/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/resources.js +0 -0
- {sovereign-1.0.0b127/src/sovereign → sovereign-1.0.0b148/src/sovereign_files}/static/sass/style.scss +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: sovereign
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0b148
|
|
4
4
|
Summary: Envoy Proxy control-plane written in Python
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Author: Vasili Syrakis
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
Project-URL: Homepage, https://pypi.org/project/sovereign/
|
|
6
|
+
Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
|
|
7
|
+
Project-URL: Documentation, https://developer.atlassian.com/platform/sovereign/
|
|
8
|
+
Author-email: Vasili Syrakis <vsyrakis@atlassian.com>
|
|
9
|
+
License-Expression: Apache-2.0
|
|
10
|
+
Keywords: control-plane,envoy,envoyproxy,management,server
|
|
11
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: Environment :: No Input/Output (Daemon)
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -16,51 +16,45 @@ Classifier: Intended Audience :: System Administrators
|
|
|
16
16
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
17
|
Classifier: Natural Language :: English
|
|
18
18
|
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
-
Classifier: Programming Language :: Python :: 3
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
24
19
|
Classifier: Programming Language :: Python :: 3.8
|
|
25
20
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
26
23
|
Classifier: Topic :: Internet :: Proxy Servers
|
|
24
|
+
Requires-Python: <4.0,>=3.11.0
|
|
25
|
+
Requires-Dist: aiofiles<24,>=23.2.1
|
|
26
|
+
Requires-Dist: cachelib<0.11,>=0.10.2
|
|
27
|
+
Requires-Dist: cachetools<6,>=5.3.2
|
|
28
|
+
Requires-Dist: croniter<2,>=1.4.1
|
|
29
|
+
Requires-Dist: cryptography>=45.0.2
|
|
30
|
+
Requires-Dist: fastapi<0.117,>=0.116.0
|
|
31
|
+
Requires-Dist: glom<24,>=23.3.0
|
|
32
|
+
Requires-Dist: h11<0.17,>=0.16.0
|
|
33
|
+
Requires-Dist: jinja2<4,>=3.1.2
|
|
34
|
+
Requires-Dist: jmespath<2,>=1.0.1
|
|
35
|
+
Requires-Dist: pydantic-settings<2.6.0
|
|
36
|
+
Requires-Dist: pydantic<3,>=2.7.2
|
|
37
|
+
Requires-Dist: pyyaml<7,>=6.0.1
|
|
38
|
+
Requires-Dist: requests<3,>=2.32.4
|
|
39
|
+
Requires-Dist: starlette-context<0.4,>=0.3.6
|
|
40
|
+
Requires-Dist: starlette<0.48,>=0.47.2
|
|
41
|
+
Requires-Dist: structlog<24,>=23.1.0
|
|
42
|
+
Requires-Dist: supervisor<5,>=4.2.5
|
|
43
|
+
Requires-Dist: uvicorn<0.24,>=0.23.2
|
|
44
|
+
Requires-Dist: uvloop<1.0,>0.19.0
|
|
27
45
|
Provides-Extra: boto
|
|
46
|
+
Requires-Dist: boto3<2,>=1.28.62; extra == 'boto'
|
|
28
47
|
Provides-Extra: caching
|
|
29
48
|
Provides-Extra: httptools
|
|
49
|
+
Requires-Dist: httptools<0.7,>=0.6.0; extra == 'httptools'
|
|
30
50
|
Provides-Extra: orjson
|
|
51
|
+
Requires-Dist: orjson<4,>=3.9.15; extra == 'orjson'
|
|
31
52
|
Provides-Extra: sentry
|
|
53
|
+
Requires-Dist: sentry-sdk<3,>=2.14.0; extra == 'sentry'
|
|
32
54
|
Provides-Extra: statsd
|
|
55
|
+
Requires-Dist: datadog>=0.50.1; extra == 'statsd'
|
|
33
56
|
Provides-Extra: ujson
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
|
|
36
|
-
Requires-Dist: aiofiles (>=23.2.1,<24.0.0)
|
|
37
|
-
Requires-Dist: boto3 (>=1.28.62,<2.0.0) ; extra == "boto"
|
|
38
|
-
Requires-Dist: cachelib (>=0.10.2,<0.11.0)
|
|
39
|
-
Requires-Dist: cachetools (>=5.3.2,<6.0.0)
|
|
40
|
-
Requires-Dist: cashews[redis] (>=6.3.0,<7.0.0) ; extra == "caching"
|
|
41
|
-
Requires-Dist: croniter (>=1.4.1,<2.0.0)
|
|
42
|
-
Requires-Dist: cryptography (>=45.0.2)
|
|
43
|
-
Requires-Dist: datadog (>=0.50.1) ; extra == "statsd"
|
|
44
|
-
Requires-Dist: fastapi (>=0.116.0,<0.117.0)
|
|
45
|
-
Requires-Dist: glom (>=23.3.0,<24.0.0)
|
|
46
|
-
Requires-Dist: h11 (>=0.16.0,<0.17.0)
|
|
47
|
-
Requires-Dist: httptools (>=0.6.0,<0.7.0) ; extra == "httptools"
|
|
48
|
-
Requires-Dist: jmespath (>=1.0.1,<2.0.0)
|
|
49
|
-
Requires-Dist: orjson (>=3.9.15,<4.0.0) ; extra == "orjson"
|
|
50
|
-
Requires-Dist: pydantic (>=2.7.2,<3.0.0)
|
|
51
|
-
Requires-Dist: pydantic-settings (<2.6.0)
|
|
52
|
-
Requires-Dist: redis (<=5.0.0)
|
|
53
|
-
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
54
|
-
Requires-Dist: sentry-sdk (>=2.14.0,<3.0.0) ; extra == "sentry"
|
|
55
|
-
Requires-Dist: starlette (>=0.47.2,<0.48.0)
|
|
56
|
-
Requires-Dist: starlette-context (>=0.3.6,<0.4.0)
|
|
57
|
-
Requires-Dist: structlog (>=23.1.0,<24.0.0)
|
|
58
|
-
Requires-Dist: supervisor (>=4.2.5,<5.0.0)
|
|
59
|
-
Requires-Dist: ujson (>=5.8.0,<6.0.0) ; extra == "ujson"
|
|
60
|
-
Requires-Dist: uvicorn (>=0.23.2,<0.24.0)
|
|
61
|
-
Requires-Dist: uvloop (>=0.19.0,<0.20.0)
|
|
62
|
-
Project-URL: Documentation, https://developer.atlassian.com/platform/sovereign/
|
|
63
|
-
Project-URL: Repository, https://bitbucket.org/atlassian/sovereign/src/master/
|
|
57
|
+
Requires-Dist: ujson<6,>=5.8.0; extra == 'ujson'
|
|
64
58
|
Description-Content-Type: text/markdown
|
|
65
59
|
|
|
66
60
|
sovereign
|
|
@@ -136,17 +130,18 @@ Local development
|
|
|
136
130
|
|
|
137
131
|
Requirements
|
|
138
132
|
------------
|
|
139
|
-
*
|
|
133
|
+
* uv
|
|
140
134
|
* Docker
|
|
141
135
|
* Docker-compose
|
|
142
136
|
|
|
143
137
|
|
|
144
138
|
Installing dependencies for dev
|
|
145
139
|
-------------------------------
|
|
146
|
-
Dependencies and creation of virtualenv is handled by
|
|
140
|
+
Dependencies and creation of virtualenv is handled by uv
|
|
147
141
|
```
|
|
148
|
-
|
|
149
|
-
|
|
142
|
+
uv sync
|
|
143
|
+
uv venv activate
|
|
144
|
+
uv run <command>
|
|
150
145
|
```
|
|
151
146
|
|
|
152
147
|
Running locally
|
|
@@ -211,4 +206,3 @@ Copyright (c) 2018 Atlassian and others.
|
|
|
211
206
|
Apache 2.0 licensed, see [LICENSE.txt](LICENSE.txt) file.
|
|
212
207
|
|
|
213
208
|
|
|
214
|
-
|
|
@@ -71,17 +71,18 @@ Local development
|
|
|
71
71
|
|
|
72
72
|
Requirements
|
|
73
73
|
------------
|
|
74
|
-
*
|
|
74
|
+
* uv
|
|
75
75
|
* Docker
|
|
76
76
|
* Docker-compose
|
|
77
77
|
|
|
78
78
|
|
|
79
79
|
Installing dependencies for dev
|
|
80
80
|
-------------------------------
|
|
81
|
-
Dependencies and creation of virtualenv is handled by
|
|
81
|
+
Dependencies and creation of virtualenv is handled by uv
|
|
82
82
|
```
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
uv sync
|
|
84
|
+
uv venv activate
|
|
85
|
+
uv run <command>
|
|
85
86
|
```
|
|
86
87
|
|
|
87
88
|
Running locally
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sovereign"
|
|
3
|
+
version = "1.0.0b148"
|
|
4
|
+
description = "Envoy Proxy control-plane written in Python"
|
|
5
|
+
authors = [{ name = "Vasili Syrakis", email = "vsyrakis@atlassian.com" }]
|
|
6
|
+
requires-python = ">=3.11.0, <4.0"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
license = "Apache-2.0"
|
|
9
|
+
keywords = [
|
|
10
|
+
"envoy",
|
|
11
|
+
"envoyproxy",
|
|
12
|
+
"control-plane",
|
|
13
|
+
"management",
|
|
14
|
+
"server",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
|
+
"Environment :: No Input/Output (Daemon)",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Intended Audience :: Information Technology",
|
|
21
|
+
"Intended Audience :: System Administrators",
|
|
22
|
+
"License :: OSI Approved :: Apache Software License",
|
|
23
|
+
"Natural Language :: English",
|
|
24
|
+
"Operating System :: POSIX :: Linux",
|
|
25
|
+
"Programming Language :: Python :: 3.8",
|
|
26
|
+
"Programming Language :: Python :: 3.9",
|
|
27
|
+
"Programming Language :: Python :: 3.10",
|
|
28
|
+
"Programming Language :: Python :: 3.11",
|
|
29
|
+
"Topic :: Internet :: Proxy Servers",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"uvicorn>=0.23.2,<0.24",
|
|
33
|
+
"aiofiles>=23.2.1,<24",
|
|
34
|
+
"requests>=2.32.4,<3",
|
|
35
|
+
"PyYAML>=6.0.1,<7",
|
|
36
|
+
"Jinja2>=3.1.2,<4",
|
|
37
|
+
"structlog>=23.1.0,<24",
|
|
38
|
+
"cachelib>=0.10.2,<0.11",
|
|
39
|
+
"glom>=23.3.0,<24",
|
|
40
|
+
"cryptography>=45.0.2",
|
|
41
|
+
"fastapi>=0.116.0,<0.117",
|
|
42
|
+
"uvloop>0.19.0,<1.0",
|
|
43
|
+
"croniter>=1.4.1,<2",
|
|
44
|
+
"cachetools>=5.3.2,<6",
|
|
45
|
+
"pydantic>=2.7.2,<3",
|
|
46
|
+
"pydantic-settings<2.6.0",
|
|
47
|
+
"starlette-context>=0.3.6,<0.4",
|
|
48
|
+
"starlette>=0.47.2,<0.48",
|
|
49
|
+
"h11>=0.16.0,<0.17",
|
|
50
|
+
"supervisor>=4.2.5,<5",
|
|
51
|
+
"jmespath>=1.0.1,<2",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.optional-dependencies]
|
|
55
|
+
sentry = ["sentry-sdk>=2.14.0,<3"]
|
|
56
|
+
boto = ["boto3>=1.28.62,<2"]
|
|
57
|
+
statsd = ["datadog>=0.50.1"]
|
|
58
|
+
ujson = ["ujson>=5.8.0,<6"]
|
|
59
|
+
orjson = ["orjson>=3.9.15,<4"]
|
|
60
|
+
caching = []
|
|
61
|
+
httptools = ["httptools>=0.6.0,<0.7"]
|
|
62
|
+
|
|
63
|
+
[project.urls]
|
|
64
|
+
Homepage = "https://pypi.org/project/sovereign/"
|
|
65
|
+
Repository = "https://bitbucket.org/atlassian/sovereign/src/master/"
|
|
66
|
+
Documentation = "https://developer.atlassian.com/platform/sovereign/"
|
|
67
|
+
|
|
68
|
+
[project.scripts]
|
|
69
|
+
sovereign = "sovereign.server:main"
|
|
70
|
+
sovereign-web = "sovereign.server:web"
|
|
71
|
+
sovereign-worker = "sovereign.server:worker"
|
|
72
|
+
|
|
73
|
+
[project.entry-points."sovereign.sources"]
|
|
74
|
+
file = "sovereign.sources.file:File"
|
|
75
|
+
inline = "sovereign.sources.inline:Inline"
|
|
76
|
+
|
|
77
|
+
[project.entry-points."sovereign.modifiers"]
|
|
78
|
+
sovereign_3rd_party_test = "sovereign.testing.modifiers:Test"
|
|
79
|
+
|
|
80
|
+
[project.entry-points."sovereign.loaders"]
|
|
81
|
+
example = "sovereign.testing.loaders:Multiply"
|
|
82
|
+
file = "sovereign.dynamic_config.loaders:File"
|
|
83
|
+
pkgdata = "sovereign.dynamic_config.loaders:PackageData"
|
|
84
|
+
http = "sovereign.dynamic_config.loaders:Web"
|
|
85
|
+
https = "sovereign.dynamic_config.loaders:Web"
|
|
86
|
+
env = "sovereign.dynamic_config.loaders:EnvironmentVariable"
|
|
87
|
+
module = "sovereign.dynamic_config.loaders:PythonModule"
|
|
88
|
+
s3 = "sovereign.dynamic_config.loaders:S3Bucket"
|
|
89
|
+
python = "sovereign.dynamic_config.loaders:PythonInlineCode"
|
|
90
|
+
inline = "sovereign.dynamic_config.loaders:Inline"
|
|
91
|
+
|
|
92
|
+
[project.entry-points."sovereign.deserializers"]
|
|
93
|
+
yaml = "sovereign.dynamic_config.deser:YamlDeserializer"
|
|
94
|
+
json = "sovereign.dynamic_config.deser:JsonDeserializer"
|
|
95
|
+
jinja = "sovereign.dynamic_config.deser:JinjaDeserializer"
|
|
96
|
+
jinja2 = "sovereign.dynamic_config.deser:JinjaDeserializer"
|
|
97
|
+
string = "sovereign.dynamic_config.deser:StringDeserializer"
|
|
98
|
+
raw = "sovereign.dynamic_config.deser:PassthroughDeserializer"
|
|
99
|
+
none = "sovereign.dynamic_config.deser:PassthroughDeserializer"
|
|
100
|
+
passthrough = "sovereign.dynamic_config.deser:PassthroughDeserializer"
|
|
101
|
+
ujson = "sovereign.dynamic_config.deser:UjsonDeserializer"
|
|
102
|
+
orjson = "sovereign.dynamic_config.deser:OrjsonDeserializer"
|
|
103
|
+
|
|
104
|
+
[project.entry-points."sovereign.cache.backends"]
|
|
105
|
+
s3 = "sovereign.cache.backends.s3:S3Backend"
|
|
106
|
+
|
|
107
|
+
[dependency-groups]
|
|
108
|
+
dev = [
|
|
109
|
+
"tavern",
|
|
110
|
+
"moto",
|
|
111
|
+
"freezegun",
|
|
112
|
+
"pytest-mock",
|
|
113
|
+
"pytest-asyncio",
|
|
114
|
+
"ruff",
|
|
115
|
+
"toml",
|
|
116
|
+
"httpx",
|
|
117
|
+
"types-croniter",
|
|
118
|
+
"types-requests",
|
|
119
|
+
"types-setuptools",
|
|
120
|
+
"types-ujson",
|
|
121
|
+
"types-PyYAML",
|
|
122
|
+
"types-cachetools",
|
|
123
|
+
"ty",
|
|
124
|
+
]
|
|
125
|
+
|
|
126
|
+
[tool.hatch.build.targets.sdist]
|
|
127
|
+
include = [
|
|
128
|
+
"src/sovereign",
|
|
129
|
+
"src/sovereign_files",
|
|
130
|
+
"src/sovereign_files/**/*",
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[tool.hatch.build.targets.wheel]
|
|
134
|
+
include = [
|
|
135
|
+
"src/sovereign",
|
|
136
|
+
"src/sovereign_files",
|
|
137
|
+
"src/sovereign_files/**/*",
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
141
|
+
"src/sovereign" = "sovereign"
|
|
142
|
+
"src/sovereign_files" = "sovereign_files"
|
|
143
|
+
|
|
144
|
+
[build-system]
|
|
145
|
+
requires = ["hatchling"]
|
|
146
|
+
build-backend = "hatchling.build"
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
from contextvars import ContextVar
|
|
3
2
|
from importlib.metadata import version
|
|
4
|
-
from sovereign.context import TemplateContext
|
|
5
3
|
|
|
6
4
|
from sovereign.utils.crypto.suites import EncryptionType
|
|
7
|
-
from starlette.templating import Jinja2Templates
|
|
8
|
-
|
|
9
5
|
from sovereign.logging.bootstrapper import LoggerBootstrapper
|
|
10
|
-
from sovereign.
|
|
11
|
-
config,
|
|
12
|
-
EncryptionConfig,
|
|
13
|
-
migrate_configs,
|
|
14
|
-
)
|
|
6
|
+
from sovereign.configuration import config, EncryptionConfig
|
|
15
7
|
from sovereign.statistics import configure_statsd
|
|
16
8
|
from sovereign.utils.crypto.crypto import CipherContainer
|
|
17
|
-
from sovereign.utils.resources import get_package_file
|
|
18
9
|
|
|
19
10
|
_request_id_ctx_var: ContextVar[str] = ContextVar("request_id", default="")
|
|
20
11
|
|
|
@@ -23,23 +14,14 @@ def get_request_id() -> str:
|
|
|
23
14
|
return _request_id_ctx_var.get()
|
|
24
15
|
|
|
25
16
|
|
|
17
|
+
WORKER_URL = "http://localhost:9080"
|
|
26
18
|
DIST_NAME = "sovereign"
|
|
27
|
-
|
|
28
19
|
__version__ = version(DIST_NAME)
|
|
29
20
|
|
|
30
|
-
html_templates = Jinja2Templates(
|
|
31
|
-
directory=str(get_package_file(DIST_NAME, "templates"))
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
if sys.argv[0].endswith("sovereign"):
|
|
35
|
-
migrate_configs()
|
|
36
|
-
|
|
37
21
|
stats = configure_statsd()
|
|
38
22
|
logs = LoggerBootstrapper(config)
|
|
39
23
|
application_logger = logs.application_logger.logger
|
|
40
24
|
|
|
41
|
-
template_context = TemplateContext.from_config()
|
|
42
|
-
|
|
43
25
|
encryption_configs = config.authentication.encryption_configs
|
|
44
26
|
server_cipher_container = CipherContainer.from_encryption_configs(
|
|
45
27
|
encryption_configs, logger=application_logger
|
|
@@ -6,12 +6,8 @@ from fastapi import FastAPI, Request
|
|
|
6
6
|
from fastapi.responses import FileResponse, JSONResponse, RedirectResponse, Response
|
|
7
7
|
from starlette_context.middleware import RawContextMiddleware
|
|
8
8
|
|
|
9
|
-
from sovereign import
|
|
10
|
-
|
|
11
|
-
config,
|
|
12
|
-
logs,
|
|
13
|
-
)
|
|
14
|
-
from sovereign.schemas import DiscoveryTypes
|
|
9
|
+
from sovereign import __version__, logs
|
|
10
|
+
from sovereign.configuration import config, ConfiguredResourceTypes
|
|
15
11
|
from sovereign.response_class import json_response_class
|
|
16
12
|
from sovereign.error_info import ErrorInfo
|
|
17
13
|
from sovereign.middlewares import LoggingMiddleware, RequestContextLogMiddleware
|
|
@@ -21,15 +17,6 @@ from sovereign.views import crypto, discovery, healthchecks, interface, api
|
|
|
21
17
|
Router = namedtuple("Router", "module tags prefix")
|
|
22
18
|
|
|
23
19
|
DEBUG = config.debug
|
|
24
|
-
SENTRY_DSN = config.sentry_dsn.get_secret_value()
|
|
25
|
-
|
|
26
|
-
try:
|
|
27
|
-
import sentry_sdk
|
|
28
|
-
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
|
|
29
|
-
|
|
30
|
-
SENTRY_INSTALLED = True
|
|
31
|
-
except ImportError: # pragma: no cover
|
|
32
|
-
SENTRY_INSTALLED = False
|
|
33
20
|
|
|
34
21
|
|
|
35
22
|
def generic_error_response(e: Exception) -> JSONResponse:
|
|
@@ -76,14 +63,22 @@ def init_app() -> FastAPI:
|
|
|
76
63
|
router.module, tags=router.tags, prefix=router.prefix
|
|
77
64
|
)
|
|
78
65
|
|
|
79
|
-
application.add_middleware(RequestContextLogMiddleware)
|
|
80
|
-
application.add_middleware(LoggingMiddleware)
|
|
66
|
+
application.add_middleware(RequestContextLogMiddleware) # type: ignore
|
|
67
|
+
application.add_middleware(LoggingMiddleware) # type: ignore
|
|
81
68
|
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
if dsn := config.sentry_dsn.get_secret_value():
|
|
70
|
+
try:
|
|
71
|
+
import sentry_sdk
|
|
72
|
+
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
|
|
85
73
|
|
|
86
|
-
|
|
74
|
+
sentry_sdk.init(dsn)
|
|
75
|
+
application.add_middleware(SentryAsgiMiddleware) # type: ignore
|
|
76
|
+
except ImportError:
|
|
77
|
+
logs.application_logger.logger.error(
|
|
78
|
+
"Sentry DSN configured but failed to attach to webserver"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
application.add_middleware(RawContextMiddleware) # type: ignore
|
|
87
82
|
|
|
88
83
|
@application.exception_handler(500)
|
|
89
84
|
async def exception_handler(_: Request, exc: Exception) -> JSONResponse:
|
|
@@ -101,14 +96,16 @@ def init_app() -> FastAPI:
|
|
|
101
96
|
|
|
102
97
|
@application.get("/static/{filename}", summary="Return a static asset")
|
|
103
98
|
async def static(filename: str) -> Response:
|
|
104
|
-
return FileResponse(get_package_file("
|
|
99
|
+
return FileResponse(get_package_file("sovereign_files", f"static/{filename}")) # type: ignore[arg-type]
|
|
105
100
|
|
|
106
101
|
@application.get(
|
|
107
102
|
"/admin/xds_dump",
|
|
108
103
|
summary="Deprecated API, please use /api/resources/{resource_type}",
|
|
109
104
|
)
|
|
110
105
|
async def dump_resources(request: Request) -> Response:
|
|
111
|
-
resource_type =
|
|
106
|
+
resource_type = ConfiguredResourceTypes(
|
|
107
|
+
request.query_params.get("xds_type", "cluster")
|
|
108
|
+
)
|
|
112
109
|
resource_name = request.query_params.get("name")
|
|
113
110
|
api_version = request.query_params.get("api_version", "v3")
|
|
114
111
|
service_cluster = request.query_params.get("service_cluster", "*")
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Sovereign Cache Module
|
|
3
|
+
|
|
4
|
+
This module provides an extensible cache backend system that allows clients
|
|
5
|
+
to configure their own remote cache backends through entry points.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
from typing import Any
|
|
10
|
+
from typing_extensions import final
|
|
11
|
+
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
from sovereign import WORKER_URL, stats, application_logger as log
|
|
15
|
+
from sovereign.types import DiscoveryRequest, RegisterClientRequest
|
|
16
|
+
from sovereign.configuration import config
|
|
17
|
+
from sovereign.cache.types import Entry, CacheResult
|
|
18
|
+
from sovereign.cache.backends import CacheBackend, get_backend
|
|
19
|
+
from sovereign.cache.filesystem import FilesystemCache
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
CACHE_READ_TIMEOUT = config.cache.read_timeout
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class CacheManagerBase:
|
|
26
|
+
def __init__(self) -> None:
|
|
27
|
+
self.local: FilesystemCache = FilesystemCache()
|
|
28
|
+
self.remote: CacheBackend | None = get_backend()
|
|
29
|
+
if self.remote is None:
|
|
30
|
+
log.info("Cache initialized with filesystem backend only")
|
|
31
|
+
else:
|
|
32
|
+
log.info("Cache initialized with filesystem and remote backends")
|
|
33
|
+
|
|
34
|
+
# Client Id registration
|
|
35
|
+
|
|
36
|
+
def register(self, req: DiscoveryRequest):
|
|
37
|
+
id = client_id(req)
|
|
38
|
+
log.debug(f"Registering client {id}")
|
|
39
|
+
self.local.register(id, req)
|
|
40
|
+
stats.increment("client.registration", tags=["status:registered"])
|
|
41
|
+
return id, req
|
|
42
|
+
|
|
43
|
+
def registered(self, req: DiscoveryRequest) -> bool:
|
|
44
|
+
ret = False
|
|
45
|
+
id = client_id(req)
|
|
46
|
+
if value := self.local.registered(id):
|
|
47
|
+
ret = value
|
|
48
|
+
log.debug(f"Client {id} registered={ret}")
|
|
49
|
+
return ret
|
|
50
|
+
|
|
51
|
+
def get_registered_clients(self) -> list[tuple[str, DiscoveryRequest]]:
|
|
52
|
+
if value := self.local.get_registered_clients():
|
|
53
|
+
return value
|
|
54
|
+
return []
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@final
|
|
58
|
+
class CacheReader(CacheManagerBase):
|
|
59
|
+
def try_read(self, key: str) -> CacheResult | None:
|
|
60
|
+
# Try filesystem first
|
|
61
|
+
if value := self.local.get(key):
|
|
62
|
+
stats.increment("cache.fs.hit")
|
|
63
|
+
return CacheResult(value=value, from_remote=False)
|
|
64
|
+
stats.increment("cache.fs.miss")
|
|
65
|
+
|
|
66
|
+
# Fallback to remote cache if available
|
|
67
|
+
if self.remote:
|
|
68
|
+
try:
|
|
69
|
+
if value := self.remote.get(key):
|
|
70
|
+
ret = CacheResult(value=value, from_remote=True)
|
|
71
|
+
stats.increment("cache.remote.hit")
|
|
72
|
+
return ret
|
|
73
|
+
except Exception as e:
|
|
74
|
+
log.warning(f"Failed to read from remote cache: {e}")
|
|
75
|
+
stats.increment("cache.remote.error")
|
|
76
|
+
stats.increment("cache.remote.miss")
|
|
77
|
+
log.warning(f"Failed to read from either cache for {key}")
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
def get(self, req: DiscoveryRequest) -> Entry | None:
|
|
81
|
+
id = client_id(req)
|
|
82
|
+
if result := self.try_read(id):
|
|
83
|
+
if result.from_remote:
|
|
84
|
+
self.register(req)
|
|
85
|
+
# Write back to filesystem
|
|
86
|
+
self.local.set(id, result.value)
|
|
87
|
+
return result.value
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
@stats.timed("cache.read_ms")
|
|
91
|
+
async def blocking_read(
|
|
92
|
+
self, req: DiscoveryRequest, timeout_s=CACHE_READ_TIMEOUT, poll_interval_s=0.5
|
|
93
|
+
) -> Entry | None:
|
|
94
|
+
cid = client_id(req)
|
|
95
|
+
metric = "client.registration"
|
|
96
|
+
if entry := self.get(req):
|
|
97
|
+
return entry
|
|
98
|
+
|
|
99
|
+
log.info(f"Cache entry not found for {cid}, registering and waiting")
|
|
100
|
+
registered = False
|
|
101
|
+
start = asyncio.get_event_loop().time()
|
|
102
|
+
attempt = 1
|
|
103
|
+
while (asyncio.get_event_loop().time() - start) < timeout_s:
|
|
104
|
+
if not registered:
|
|
105
|
+
try:
|
|
106
|
+
if self.register_over_http(req):
|
|
107
|
+
stats.increment(metric, tags=["status:registered"])
|
|
108
|
+
registered = True
|
|
109
|
+
log.info(f"Client {cid} registered")
|
|
110
|
+
else:
|
|
111
|
+
stats.increment(metric, tags=["status:ratelimited"])
|
|
112
|
+
await asyncio.sleep(min(attempt, CACHE_READ_TIMEOUT))
|
|
113
|
+
attempt *= 2
|
|
114
|
+
except Exception as e:
|
|
115
|
+
stats.increment(metric, tags=["status:failed"])
|
|
116
|
+
log.exception(f"Tried to register client but failed: {e}")
|
|
117
|
+
if entry := self.get(req):
|
|
118
|
+
log.info(f"Entry has been populated for {cid}")
|
|
119
|
+
return entry
|
|
120
|
+
await asyncio.sleep(poll_interval_s)
|
|
121
|
+
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
def register_over_http(self, req: DiscoveryRequest) -> bool:
|
|
125
|
+
registration = RegisterClientRequest(request=req)
|
|
126
|
+
log.debug(f"Sending registration to worker for {req}")
|
|
127
|
+
try:
|
|
128
|
+
response = requests.put(
|
|
129
|
+
f"{WORKER_URL}/client",
|
|
130
|
+
json=registration.model_dump(),
|
|
131
|
+
timeout=3,
|
|
132
|
+
)
|
|
133
|
+
match response.status_code:
|
|
134
|
+
case 200 | 202:
|
|
135
|
+
log.debug("Worker responded OK to registration")
|
|
136
|
+
return True
|
|
137
|
+
case code:
|
|
138
|
+
log.debug(f"Worker responded with {code} to registration")
|
|
139
|
+
except Exception as e:
|
|
140
|
+
log.exception(f"Error while registering client: {e}")
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@final
|
|
145
|
+
class CacheWriter(CacheManagerBase):
|
|
146
|
+
def set(
|
|
147
|
+
self, key: str, value: Entry, timeout: int | None = None
|
|
148
|
+
) -> tuple[bool, list[tuple[str, str]]]:
|
|
149
|
+
msg = []
|
|
150
|
+
cached = False
|
|
151
|
+
try:
|
|
152
|
+
self.local.set(key, value, timeout)
|
|
153
|
+
stats.increment("cache.fs.write.success")
|
|
154
|
+
cached = True
|
|
155
|
+
except Exception as e:
|
|
156
|
+
log.warning(f"Failed to write to filesystem cache: {e}")
|
|
157
|
+
msg.append(("warning", f"Failed to write to filesystem cache: {e}"))
|
|
158
|
+
stats.increment("cache.fs.write.error")
|
|
159
|
+
if self.remote:
|
|
160
|
+
try:
|
|
161
|
+
self.remote.set(key, value, timeout)
|
|
162
|
+
stats.increment("cache.remote.write.success")
|
|
163
|
+
cached = True
|
|
164
|
+
except Exception as e:
|
|
165
|
+
log.warning(f"Failed to write to remote cache: {e}")
|
|
166
|
+
msg.append(("warning", f"Failed to write to remote cache: {e}"))
|
|
167
|
+
stats.increment("cache.remote.write.error")
|
|
168
|
+
return cached, msg
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def client_id(req: DiscoveryRequest) -> str:
|
|
172
|
+
return req.cache_key(config.cache.hash_rules)
|