sovereign 1.0.0b125__tar.gz → 1.0.0b127__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.

Files changed (73) hide show
  1. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/PKG-INFO +1 -1
  2. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/pyproject.toml +1 -1
  3. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/rendering.py +46 -29
  4. sovereign-1.0.0b127/src/sovereign/static/darkmode.js +51 -0
  5. sovereign-1.0.0b127/src/sovereign/static/node_expression.js +42 -0
  6. sovereign-1.0.0b127/src/sovereign/static/resources.css +246 -0
  7. sovereign-1.0.0b127/src/sovereign/static/resources.js +642 -0
  8. sovereign-1.0.0b127/src/sovereign/static/sass/style.scss +33 -0
  9. sovereign-1.0.0b127/src/sovereign/templates/base.html +77 -0
  10. sovereign-1.0.0b127/src/sovereign/templates/resources.html +260 -0
  11. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/mock.py +6 -2
  12. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/views/interface.py +34 -15
  13. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/worker.py +15 -28
  14. sovereign-1.0.0b125/src/sovereign/static/node_expression.js +0 -16
  15. sovereign-1.0.0b125/src/sovereign/static/sass/style.scss +0 -27
  16. sovereign-1.0.0b125/src/sovereign/static/style.css +0 -13553
  17. sovereign-1.0.0b125/src/sovereign/templates/base.html +0 -64
  18. sovereign-1.0.0b125/src/sovereign/templates/resources.html +0 -1055
  19. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/LICENSE.txt +0 -0
  20. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/README.md +0 -0
  21. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/__init__.py +0 -0
  22. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/app.py +0 -0
  23. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/cache.py +0 -0
  24. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/constants.py +0 -0
  25. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/context.py +0 -0
  26. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/dynamic_config/__init__.py +0 -0
  27. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/dynamic_config/deser.py +0 -0
  28. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/dynamic_config/loaders.py +0 -0
  29. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/error_info.py +0 -0
  30. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/logging/access_logger.py +0 -0
  31. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/logging/application_logger.py +0 -0
  32. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/logging/base_logger.py +0 -0
  33. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/logging/bootstrapper.py +0 -0
  34. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/logging/types.py +0 -0
  35. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/middlewares.py +0 -0
  36. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/modifiers/__init__.py +0 -0
  37. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/modifiers/lib.py +0 -0
  38. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/response_class.py +0 -0
  39. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/schemas.py +0 -0
  40. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/server.py +0 -0
  41. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/sources/__init__.py +0 -0
  42. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/sources/file.py +0 -0
  43. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/sources/inline.py +0 -0
  44. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/sources/lib.py +0 -0
  45. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/sources/poller.py +0 -0
  46. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/static/panel.js +0 -0
  47. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/statistics.py +0 -0
  48. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/templates/err.html +0 -0
  49. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/testing/loaders.py +0 -0
  50. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/testing/modifiers.py +0 -0
  51. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/tracing.py +0 -0
  52. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/__init__.py +0 -0
  53. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/auth.py +0 -0
  54. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/__init__.py +0 -0
  55. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/crypto.py +0 -0
  56. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/suites/__init__.py +0 -0
  57. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py +0 -0
  58. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/suites/base_cipher.py +0 -0
  59. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/suites/disabled_cipher.py +0 -0
  60. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/crypto/suites/fernet_cipher.py +0 -0
  61. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/dictupdate.py +0 -0
  62. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/eds.py +0 -0
  63. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/entry_point_loader.py +0 -0
  64. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/resources.py +0 -0
  65. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/templates.py +0 -0
  66. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/timer.py +0 -0
  67. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/version_info.py +0 -0
  68. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/utils/weighted_clusters.py +0 -0
  69. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/views/__init__.py +0 -0
  70. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/views/api.py +0 -0
  71. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/views/crypto.py +0 -0
  72. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/views/discovery.py +0 -0
  73. {sovereign-1.0.0b125 → sovereign-1.0.0b127}/src/sovereign/views/healthchecks.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sovereign
3
- Version: 1.0.0b125
3
+ Version: 1.0.0b127
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "sovereign"
3
- version = "1.0.0b125"
3
+ version = "1.0.0b127"
4
4
  description = "Envoy Proxy control-plane written in Python"
5
5
  license = "Apache-2.0"
6
6
  packages = [
@@ -7,6 +7,8 @@ Functions used to render and return discovery responses to Envoy proxies.
7
7
  The templates are configurable. `todo See ref:Configuration#Templates`
8
8
  """
9
9
 
10
+ import threading
11
+ from multiprocessing import Process, Semaphore, cpu_count
10
12
  from typing import Any, Dict, List
11
13
 
12
14
  import yaml
@@ -27,6 +29,8 @@ from sovereign.schemas import (
27
29
  ProcessedTemplate,
28
30
  )
29
31
 
32
+ # limit render jobs to number of cores
33
+ RENDER_SEMAPHORE = Semaphore(cpu_count())
30
34
 
31
35
  type_urls = {
32
36
  "v2": {
@@ -54,39 +58,52 @@ class RenderJob(pydantic.BaseModel):
54
58
  request: DiscoveryRequest
55
59
  context: dict[str, Any]
56
60
 
61
+ def spawn(self):
62
+ t = threading.Thread(target=self._run)
63
+ t.start()
64
+
65
+ def _run(self):
66
+ with RENDER_SEMAPHORE:
67
+ proc = Process(target=generate, args=[self])
68
+ proc.start()
69
+ proc.join()
70
+
57
71
 
58
72
  def generate(job: RenderJob) -> None:
59
73
  request = job.request
60
74
  tags = [f"type:{request.resource_type}"]
61
- stats.increment("template.render", tags=tags)
62
- with stats.timed("template.render_ms", tags=tags):
63
- content = request.template(
64
- discovery_request=request,
65
- host_header=request.desired_controlplane,
66
- resource_names=request.resources,
67
- **job.context,
68
- )
69
- if not request.template.is_python_source:
70
- assert isinstance(content, str)
71
- content = deserialize_config(content)
72
- assert isinstance(content, dict)
73
- resources = filter_resources(content["resources"], request.resources)
74
- add_type_urls(request.api_version, request.resource_type, resources)
75
- response = ProcessedTemplate(resources=resources)
76
- cache.write(
77
- job.id,
78
- cache.Entry(
79
- text=response.model_dump_json(indent=None),
80
- len=len(response.resources),
81
- version=response.version_info,
82
- node=request.node,
83
- ),
84
- )
85
-
86
-
87
- def batch_generate(jobs: list[RenderJob]) -> None:
88
- for job in jobs:
89
- generate(job)
75
+ try:
76
+ with stats.timed("template.render_ms", tags=tags):
77
+ content = request.template(
78
+ discovery_request=request,
79
+ host_header=request.desired_controlplane,
80
+ resource_names=request.resources,
81
+ **job.context,
82
+ )
83
+ if not request.template.is_python_source:
84
+ assert isinstance(content, str)
85
+ content = deserialize_config(content)
86
+ assert isinstance(content, dict)
87
+ resources = filter_resources(content["resources"], request.resources)
88
+ add_type_urls(request.api_version, request.resource_type, resources)
89
+ response = ProcessedTemplate(resources=resources)
90
+ cache.write(
91
+ job.id,
92
+ cache.Entry(
93
+ text=response.model_dump_json(indent=None),
94
+ len=len(response.resources),
95
+ version=response.version_info,
96
+ node=request.node,
97
+ ),
98
+ )
99
+ tags.append("result:ok")
100
+ except Exception as e:
101
+ tags.append("result:err")
102
+ tags.append(f"error:{e.__class__.__name__.lower()}")
103
+ if SENTRY_INSTALLED and config.sentry_dsn:
104
+ sentry_sdk.capture_exception(e)
105
+ finally:
106
+ stats.increment("template.render", tags=tags)
90
107
 
91
108
 
92
109
  def deserialize_config(content: str) -> Dict[str, Any]:
@@ -0,0 +1,51 @@
1
+ document.addEventListener('DOMContentLoaded', function() {
2
+ const darkmode = "theme-dark";
3
+ const lightmode = "theme-light";
4
+ const toggle = document.getElementById('dark-mode-toggle');
5
+ const htmlTag = document.documentElement;
6
+
7
+ function preferredTheme() {
8
+ const preference = localStorage.getItem("theme");
9
+ if (preference) {
10
+ return preference;
11
+ }
12
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
13
+ return "dark";
14
+ } else {
15
+ return "light";
16
+ };
17
+ }
18
+
19
+ function currentTheme() {
20
+ if (htmlTag.classList.contains(darkmode)) {
21
+ return "dark"
22
+ } else {
23
+ return "light"
24
+ }
25
+ }
26
+
27
+ function setTheme(theme) {
28
+ localStorage.setItem("theme", theme);
29
+ if (theme === "dark") {
30
+ htmlTag.classList.remove(lightmode);
31
+ htmlTag.classList.add(darkmode);
32
+ toggle.textContent = '🌘';
33
+ } else {
34
+ htmlTag.classList.remove(darkmode);
35
+ htmlTag.classList.add(lightmode);
36
+ toggle.textContent = '🌞';
37
+ }
38
+ }
39
+
40
+ setTheme(preferredTheme());
41
+
42
+ toggle.addEventListener("click", function() {
43
+ let current = currentTheme();
44
+ console.log("Current theme: " + current);
45
+ if (current === "dark") {
46
+ setTheme("light");
47
+ } else {
48
+ setTheme("dark");
49
+ }
50
+ });
51
+ });
@@ -0,0 +1,42 @@
1
+ const input = document.getElementById('filterInput');
2
+ const inputMessage = document.getElementById('filterMessage');
3
+ const form = document.getElementById('filterForm');
4
+
5
+ function validateInput(inputString) {
6
+ if (!inputString || inputString.trim() === '') {
7
+ return "empty";
8
+ }
9
+ const validationRegex = /^(?:(?:id|cluster|metadata\.[\w\.\=\-]+|locality\.?(?:zone|sub_zone|region))=[a-zA-Z0-9_-]+ ?)*$/;
10
+ return validationRegex.test(inputString);
11
+ }
12
+
13
+ window.addEventListener('DOMContentLoaded', () => {
14
+ const match = document.cookie.match(/(?:^|; )node_expression=([^;]*)/);
15
+ if (match) {
16
+ input.value = match[1];
17
+ }
18
+ });
19
+
20
+ input.addEventListener('input', (event) => {
21
+ const result = validateInput(event.target.value);
22
+ if (result === "empty") {
23
+ input.className = "input is-dark";
24
+ inputMessage.className = "help is-dark";
25
+ inputMessage.innerHTML = "";
26
+ } else if (result === true) {
27
+ input.className = "input is-success";
28
+ inputMessage.className = "help is-success";
29
+ inputMessage.innerHTML = "Press enter to apply filter expression";
30
+ } else {
31
+ input.className = "input is-danger";
32
+ inputMessage.className = "help is-danger";
33
+ inputMessage.innerHTML = "The node filter expression may have no effect, or be invalid";
34
+ }
35
+ });
36
+
37
+ form.addEventListener('submit', (event) => {
38
+ event.preventDefault();
39
+ const value = input.value.trim();
40
+ document.cookie = `node_expression=${value}; path=/ui/resources/; max-age=31536000`;
41
+ location.reload();
42
+ });
@@ -0,0 +1,246 @@
1
+ /* Resources page styles */
2
+ #filterInput {
3
+ font-family: monospace;
4
+ }
5
+
6
+ .filtered {
7
+ display: none;
8
+ }
9
+
10
+ .tooltip {
11
+ position: relative;
12
+ display: inline-block;
13
+ cursor: help;
14
+ }
15
+
16
+ .tooltip .tooltip-text {
17
+ visibility: hidden;
18
+ width: 220px;
19
+ background-color: #363636;
20
+ color: #fff;
21
+ text-align: left;
22
+ padding: 0.5rem;
23
+ border-radius: 6px;
24
+ position: absolute;
25
+ z-index: 1;
26
+ top: 125%;
27
+ left: 50%;
28
+ margin-left: -110px;
29
+ opacity: 0;
30
+ transition: opacity 0.3s;
31
+ font-size: 0.875rem;
32
+ }
33
+
34
+ .tooltip:hover .tooltip-text {
35
+ visibility: visible;
36
+ opacity: 1;
37
+ }
38
+
39
+ /* Side panel styles - using Bulma-compatible approach */
40
+ .side-panel {
41
+ position: fixed;
42
+ top: 0;
43
+ right: -100%;
44
+ width: 95%;
45
+ max-width: 1200px;
46
+ min-width: 400px;
47
+ height: 100vh;
48
+ z-index: 1000;
49
+ transition: right 0.3s ease-in-out;
50
+ overflow-y: auto;
51
+ }
52
+
53
+ .side-panel.is-active {
54
+ right: 0;
55
+ }
56
+
57
+ /* Responsive side panel sizing */
58
+ @media (min-width: 768px) {
59
+ .side-panel { width: 75%; right: -75%; }
60
+ }
61
+
62
+ @media (min-width: 1024px) {
63
+ .side-panel { width: 65%; right: -65%; }
64
+ }
65
+
66
+ @media (min-width: 1400px) {
67
+ .side-panel { width: 55%; right: -55%; }
68
+ }
69
+
70
+ @media (min-width: 1600px) {
71
+ .side-panel { width: 50%; right: -50%; }
72
+ }
73
+
74
+ @media (max-width: 480px) {
75
+ .side-panel { width: 95%; min-width: 300px; right: -95%; }
76
+ }
77
+
78
+ .json-container {
79
+ position: relative;
80
+ }
81
+
82
+ .json-header {
83
+ padding: 1rem;
84
+ border-bottom: 1px solid #555;
85
+ background-color: #2d2d2d;
86
+ display: flex;
87
+ justify-content: space-between;
88
+ align-items: center;
89
+ }
90
+
91
+ .json-content {
92
+ background-color: #1e1e1e;
93
+ border: none;
94
+ border-radius: 0;
95
+ padding: 1rem;
96
+ font-family: 'JetBrains Mono', 'Fira Code', 'SF Mono', 'Monaco', 'Inconsolata', 'Roboto Mono', 'Source Code Pro', 'Menlo', 'Consolas', monospace;
97
+ font-size: 0.875rem;
98
+ line-height: 1.5;
99
+ white-space: pre-wrap;
100
+ word-wrap: break-word;
101
+ max-height: calc(100vh - 250px);
102
+ overflow-y: auto;
103
+ color: #d4d4d4;
104
+ margin: 0;
105
+ }
106
+
107
+ /* JSON Syntax Highlighting - VS Code Dark+ theme colors */
108
+ .json-key {
109
+ color: #9cdcfe;
110
+ font-weight: 400;
111
+ }
112
+
113
+ .json-string {
114
+ color: #ce9178;
115
+ }
116
+
117
+ .json-number {
118
+ color: #b5cea8;
119
+ }
120
+
121
+ .json-boolean {
122
+ color: #569cd6;
123
+ font-weight: 500;
124
+ }
125
+
126
+ .json-null {
127
+ color: #569cd6;
128
+ font-weight: 500;
129
+ font-style: italic;
130
+ }
131
+
132
+ .json-punctuation {
133
+ color: #d4d4d4;
134
+ }
135
+
136
+ /* Improve readability */
137
+ .json-content::-webkit-scrollbar {
138
+ width: 8px;
139
+ }
140
+
141
+ .json-content::-webkit-scrollbar-track {
142
+ background: #2d2d2d;
143
+ }
144
+
145
+ .json-content::-webkit-scrollbar-thumb {
146
+ background: #555;
147
+ border-radius: 4px;
148
+ }
149
+
150
+ .json-content::-webkit-scrollbar-thumb:hover {
151
+ background: #666;
152
+ }
153
+
154
+ /* Collapsible JSON */
155
+ .json-toggle {
156
+ cursor: pointer;
157
+ user-select: none;
158
+ color: #808080;
159
+ margin-right: 0.25rem;
160
+ font-family: monospace;
161
+ font-size: 0.875rem;
162
+ }
163
+
164
+ .json-toggle:hover {
165
+ color: #fff;
166
+ }
167
+
168
+ .json-collapsible {
169
+ margin-left: 1rem;
170
+ }
171
+
172
+ .json-collapsed {
173
+ display: none;
174
+ }
175
+
176
+ .loading-spinner {
177
+ display: inline-block;
178
+ width: 20px;
179
+ height: 20px;
180
+ border: 3px solid #f3f3f3;
181
+ border-top: 3px solid #433fca;
182
+ border-radius: 50%;
183
+ animation: spin 1s linear infinite;
184
+ }
185
+
186
+ @keyframes spin {
187
+ 0% { transform: rotate(0deg); }
188
+ 100% { transform: rotate(360deg); }
189
+ }
190
+
191
+ /* Prevent body scroll when side panel is open */
192
+ body.side-panel-open {
193
+ overflow: hidden;
194
+ }
195
+
196
+ /* Backdrop overlay for side panel */
197
+ .side-panel-backdrop {
198
+ position: fixed;
199
+ top: 0;
200
+ left: 0;
201
+ width: 100%;
202
+ height: 100%;
203
+ background-color: rgba(0, 0, 0, 0.5);
204
+ z-index: 999;
205
+ opacity: 0;
206
+ visibility: hidden;
207
+ transition: opacity 0.3s ease-in-out, visibility 0.3s ease-in-out;
208
+ }
209
+
210
+ .side-panel-backdrop.is-active {
211
+ opacity: 1;
212
+ visibility: visible;
213
+ }
214
+
215
+ /* Show backdrop on smaller screens */
216
+ @media (max-width: 1024px) {
217
+ .side-panel-backdrop.is-active {
218
+ opacity: 1;
219
+ visibility: visible;
220
+ }
221
+ }
222
+
223
+ /* Minimal custom styles - using Bulma classes for most styling */
224
+ .json-chunk-controls {
225
+ background-color: #2d2d2d;
226
+ border-bottom: 1px solid #555;
227
+ }
228
+
229
+ /* Preferences details styling */
230
+ details summary {
231
+ list-style: none;
232
+ }
233
+
234
+ details summary::-webkit-details-marker {
235
+ display: none;
236
+ }
237
+
238
+ details summary::before {
239
+ content: "▶";
240
+ margin-right: 0.5rem;
241
+ transition: transform 0.2s;
242
+ }
243
+
244
+ details[open] summary::before {
245
+ transform: rotate(90deg);
246
+ }