sovereign 1.0.0b101__tar.gz → 1.0.0b103__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.0b101 → sovereign-1.0.0b103}/PKG-INFO +1 -1
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/pyproject.toml +1 -1
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/context.py +1 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/schemas.py +3 -2
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/sources/poller.py +198 -6
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/templates/resources.html +7 -1
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/views/api.py +24 -1
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/views/interface.py +2 -16
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/worker.py +23 -37
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/LICENSE.txt +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/README.md +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/app.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/cache.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/constants.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/dynamic_config/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/dynamic_config/deser.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/dynamic_config/loaders.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/error_info.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/logging/access_logger.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/logging/application_logger.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/logging/base_logger.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/logging/bootstrapper.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/logging/types.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/middlewares.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/modifiers/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/modifiers/lib.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/rendering.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/response_class.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/server.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/sources/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/sources/file.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/sources/inline.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/sources/lib.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/static/node_expression.js +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/static/panel.js +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/static/sass/style.scss +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/static/style.css +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/statistics.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/templates/base.html +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/templates/err.html +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/testing/loaders.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/testing/modifiers.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/tracing.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/auth.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/crypto.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/base_cipher.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/disabled_cipher.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/fernet_cipher.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/dictupdate.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/eds.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/entry_point_loader.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/mock.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/resources.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/templates.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/timer.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/version_info.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/weighted_clusters.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/views/__init__.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/views/crypto.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/views/discovery.py +0 -0
- {sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/views/healthchecks.py +0 -0
|
@@ -44,7 +44,7 @@ class CacheStrategy(str, Enum):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
class SourceData(BaseModel):
|
|
47
|
-
scopes: Dict[str, List[Dict[str, Any]]] =
|
|
47
|
+
scopes: Dict[str, List[Dict[str, Any]]] = Field(default_factory=dict)
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
class ConfiguredSource(BaseModel):
|
|
@@ -410,7 +410,7 @@ class DiscoveryRequest(BaseModel):
|
|
|
410
410
|
return f"version={self.envoy_version}, cluster={self.node.cluster}, resource={self.resource_type}, names={self.resources}"
|
|
411
411
|
|
|
412
412
|
def __str__(self) -> str:
|
|
413
|
-
return f"
|
|
413
|
+
return f"DiscoveryRequest({self.debug()})"
|
|
414
414
|
|
|
415
415
|
|
|
416
416
|
class DiscoveryResponse(BaseModel):
|
|
@@ -645,6 +645,7 @@ class AccessLogConfiguration(BaseSettings):
|
|
|
645
645
|
class LoggingConfiguration(BaseSettings):
|
|
646
646
|
application_logs: ApplicationLogConfiguration = ApplicationLogConfiguration()
|
|
647
647
|
access_logs: AccessLogConfiguration = AccessLogConfiguration()
|
|
648
|
+
log_source_diffs: bool = False
|
|
648
649
|
|
|
649
650
|
|
|
650
651
|
class ContextFileCache(BaseSettings):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import uuid
|
|
1
3
|
import asyncio
|
|
2
4
|
import traceback
|
|
3
5
|
from copy import deepcopy
|
|
@@ -7,11 +9,7 @@ from typing import Iterable, Any, Dict, List, Union, Type, Optional
|
|
|
7
9
|
|
|
8
10
|
from glom import glom, PathAccessError
|
|
9
11
|
|
|
10
|
-
from sovereign.schemas import
|
|
11
|
-
ConfiguredSource,
|
|
12
|
-
SourceData,
|
|
13
|
-
Node,
|
|
14
|
-
)
|
|
12
|
+
from sovereign.schemas import ConfiguredSource, SourceData, Node, config
|
|
15
13
|
from sovereign.utils.entry_point_loader import EntryPointLoader
|
|
16
14
|
from sovereign.sources.lib import Source
|
|
17
15
|
from sovereign.modifiers.lib import Modifier, GlobalModifier
|
|
@@ -36,6 +34,179 @@ Mods = Dict[str, Type[Modifier]]
|
|
|
36
34
|
GMods = Dict[str, Type[GlobalModifier]]
|
|
37
35
|
|
|
38
36
|
|
|
37
|
+
def _deep_diff(old, new, path="") -> list[dict[str, Any]]:
|
|
38
|
+
changes = []
|
|
39
|
+
|
|
40
|
+
# handle add/remove
|
|
41
|
+
if (old, new) == (None, None):
|
|
42
|
+
return changes
|
|
43
|
+
elif old is None:
|
|
44
|
+
changes.append({
|
|
45
|
+
"op": "add",
|
|
46
|
+
"path": path,
|
|
47
|
+
"value": new
|
|
48
|
+
})
|
|
49
|
+
return changes
|
|
50
|
+
elif new is None:
|
|
51
|
+
changes.append({
|
|
52
|
+
"op": "remove",
|
|
53
|
+
"path": path,
|
|
54
|
+
"old_value": old
|
|
55
|
+
})
|
|
56
|
+
return changes
|
|
57
|
+
|
|
58
|
+
# handle completely different types
|
|
59
|
+
if type(old) is not type(new):
|
|
60
|
+
changes.append({
|
|
61
|
+
"op": "change",
|
|
62
|
+
"path": path,
|
|
63
|
+
"old_value": old,
|
|
64
|
+
"new_value": new
|
|
65
|
+
})
|
|
66
|
+
return changes
|
|
67
|
+
|
|
68
|
+
# handle fields recursively
|
|
69
|
+
if isinstance(old, dict) and isinstance(new, dict):
|
|
70
|
+
all_keys = set(old.keys()) | set(new.keys())
|
|
71
|
+
|
|
72
|
+
for key in sorted(all_keys):
|
|
73
|
+
old_val = old.get(key)
|
|
74
|
+
new_val = new.get(key)
|
|
75
|
+
|
|
76
|
+
current_path = f"{path}.{key}" if path else key
|
|
77
|
+
|
|
78
|
+
if key not in old:
|
|
79
|
+
changes.append({
|
|
80
|
+
"op": "add",
|
|
81
|
+
"path": current_path,
|
|
82
|
+
"value": new_val
|
|
83
|
+
})
|
|
84
|
+
elif key not in new:
|
|
85
|
+
changes.append({
|
|
86
|
+
"op": "remove",
|
|
87
|
+
"path": current_path,
|
|
88
|
+
"old_value": old_val
|
|
89
|
+
})
|
|
90
|
+
elif old_val != new_val:
|
|
91
|
+
nested_changes = _deep_diff(old_val, new_val, current_path)
|
|
92
|
+
changes.extend(nested_changes)
|
|
93
|
+
|
|
94
|
+
# handle items recursively
|
|
95
|
+
elif isinstance(old, list) and isinstance(new, list):
|
|
96
|
+
max_len = max(len(old), len(new))
|
|
97
|
+
|
|
98
|
+
for i in range(max_len):
|
|
99
|
+
current_path = f"{path}[{i}]" if path else f"[{i}]"
|
|
100
|
+
|
|
101
|
+
if i >= len(old):
|
|
102
|
+
changes.append({
|
|
103
|
+
"op": "add",
|
|
104
|
+
"path": current_path,
|
|
105
|
+
"value": new[i]
|
|
106
|
+
})
|
|
107
|
+
elif i >= len(new):
|
|
108
|
+
changes.append({
|
|
109
|
+
"op": "remove",
|
|
110
|
+
"path": current_path,
|
|
111
|
+
"old_value": old[i]
|
|
112
|
+
})
|
|
113
|
+
elif old[i] != new[i]:
|
|
114
|
+
nested_changes = _deep_diff(old[i], new[i], current_path)
|
|
115
|
+
changes.extend(nested_changes)
|
|
116
|
+
|
|
117
|
+
# handle primitives
|
|
118
|
+
else:
|
|
119
|
+
if old != new:
|
|
120
|
+
changes.append({
|
|
121
|
+
"op": "change",
|
|
122
|
+
"path": path,
|
|
123
|
+
"old_value": old,
|
|
124
|
+
"new_value": new
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
return changes
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def per_field_diff(old, new) -> list[dict[str, Any]]:
|
|
131
|
+
changes = []
|
|
132
|
+
max_len = max(len(old), len(new))
|
|
133
|
+
|
|
134
|
+
for i in range(max_len):
|
|
135
|
+
old_inst = old[i] if i < len(old) else None
|
|
136
|
+
new_inst = new[i] if i < len(new) else None
|
|
137
|
+
|
|
138
|
+
if old_inst is None:
|
|
139
|
+
changes.append({
|
|
140
|
+
"op": "add",
|
|
141
|
+
"path": f"[{i}]",
|
|
142
|
+
"value": new_inst
|
|
143
|
+
})
|
|
144
|
+
elif new_inst is None:
|
|
145
|
+
changes.append({
|
|
146
|
+
"op": "remove",
|
|
147
|
+
"path": f"[{i}]",
|
|
148
|
+
"old_value": old_inst
|
|
149
|
+
})
|
|
150
|
+
elif old_inst != new_inst:
|
|
151
|
+
# Use the deep diff with index prefix
|
|
152
|
+
field_changes = _deep_diff(old_inst, new_inst, f"[{i}]")
|
|
153
|
+
changes.extend(field_changes)
|
|
154
|
+
|
|
155
|
+
return changes
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _gen_uuid(diff_summary: dict[str, Any]) -> str:
|
|
159
|
+
blob = json.dumps(diff_summary, sort_keys=True, separators=('', ''))
|
|
160
|
+
return str(uuid.uuid5(uuid.NAMESPACE_DNS, blob))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def source_diff_summary(prev, curr) -> dict[str, Any]:
|
|
164
|
+
if prev is None:
|
|
165
|
+
summary = {
|
|
166
|
+
"type": "initial_load",
|
|
167
|
+
"scopes": {
|
|
168
|
+
scope: {"added": len(instances)}
|
|
169
|
+
for scope, instances in curr.scopes.items()
|
|
170
|
+
if instances
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
else:
|
|
174
|
+
summary = {
|
|
175
|
+
"type": "update",
|
|
176
|
+
"scopes": {}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
all_scopes = set(prev.scopes.keys()) | set(curr.scopes.keys())
|
|
180
|
+
|
|
181
|
+
for scope in sorted(all_scopes):
|
|
182
|
+
old = prev.scopes.get(scope, [])
|
|
183
|
+
new = curr.scopes.get(scope, [])
|
|
184
|
+
|
|
185
|
+
n_old = len(old)
|
|
186
|
+
n_new = len(new)
|
|
187
|
+
|
|
188
|
+
scope_changes = {}
|
|
189
|
+
|
|
190
|
+
if n_old == 0 and n_new > 0:
|
|
191
|
+
scope_changes["added"] = n_new
|
|
192
|
+
elif n_old > 0 and n_new == 0:
|
|
193
|
+
scope_changes["removed"] = n_old
|
|
194
|
+
elif old != new:
|
|
195
|
+
detailed_changes = per_field_diff(old, new)
|
|
196
|
+
if detailed_changes:
|
|
197
|
+
scope_changes["field_changes"] = detailed_changes
|
|
198
|
+
scope_changes["count_change"] = n_new - n_old
|
|
199
|
+
|
|
200
|
+
if scope_changes:
|
|
201
|
+
summary["scopes"][scope] = scope_changes
|
|
202
|
+
|
|
203
|
+
if not summary["scopes"]:
|
|
204
|
+
summary = {"type": "no_changes"}
|
|
205
|
+
|
|
206
|
+
summary["uuid"] = _gen_uuid(summary)
|
|
207
|
+
return summary
|
|
208
|
+
|
|
209
|
+
|
|
39
210
|
class SourcePoller:
|
|
40
211
|
def __init__(
|
|
41
212
|
self,
|
|
@@ -160,7 +331,10 @@ class SourcePoller:
|
|
|
160
331
|
try:
|
|
161
332
|
new = SourceData()
|
|
162
333
|
for source in self.sources:
|
|
163
|
-
|
|
334
|
+
scope = source.scope
|
|
335
|
+
if scope not in new.scopes:
|
|
336
|
+
new.scopes[scope] = []
|
|
337
|
+
new.scopes[scope].extend(source.get())
|
|
164
338
|
except Exception as e:
|
|
165
339
|
self.logger.error(
|
|
166
340
|
event="Error while refreshing sources",
|
|
@@ -179,9 +353,25 @@ class SourcePoller:
|
|
|
179
353
|
else:
|
|
180
354
|
self.stats.increment("sources.refreshed")
|
|
181
355
|
self.last_updated = datetime.now()
|
|
356
|
+
old_data = getattr(self, "source_data", None)
|
|
182
357
|
self.instance_count = len(
|
|
183
358
|
[instance for scope in new.scopes.values() for instance in scope]
|
|
184
359
|
)
|
|
360
|
+
|
|
361
|
+
if config.logging.log_source_diffs:
|
|
362
|
+
diff_summary = source_diff_summary(old_data, new)
|
|
363
|
+
# printing json directly because the logger is fucking stupid
|
|
364
|
+
print(
|
|
365
|
+
json.dumps(
|
|
366
|
+
dict(
|
|
367
|
+
event="Sources refreshed with changes",
|
|
368
|
+
level="info",
|
|
369
|
+
diff=diff_summary,
|
|
370
|
+
total_instances=self.instance_count,
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
185
375
|
self.source_data = new
|
|
186
376
|
return True
|
|
187
377
|
|
|
@@ -260,6 +450,8 @@ class SourcePoller:
|
|
|
260
450
|
or is_debug_request(node_value)
|
|
261
451
|
)
|
|
262
452
|
if match:
|
|
453
|
+
if scope not in ret.scopes:
|
|
454
|
+
ret.scopes[scope] = []
|
|
263
455
|
ret.scopes[scope].append(instance)
|
|
264
456
|
return ret
|
|
265
457
|
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
];
|
|
15
15
|
const resources = resourceNames.map((name, index) => ({ name: name, index: index }));
|
|
16
16
|
let filteredResources = [...resources];
|
|
17
|
+
|
|
18
|
+
// Function to set envoy_version cookie and reload page
|
|
19
|
+
function setEnvoyVersion(version) {
|
|
20
|
+
document.cookie = `envoy_version=${version}; path=/ui/resources/; max-age=31536000`;
|
|
21
|
+
window.location.reload();
|
|
22
|
+
}
|
|
17
23
|
</script>
|
|
18
24
|
<style>
|
|
19
25
|
#filterInput {
|
|
@@ -284,7 +290,7 @@
|
|
|
284
290
|
</div>
|
|
285
291
|
{% for v in available_versions %}
|
|
286
292
|
<a class="dropdown-item{% if v == version %} is-active{% endif %}"
|
|
287
|
-
href="
|
|
293
|
+
href="#" onclick="setEnvoyVersion('{{ v }}'); return false;">
|
|
288
294
|
{{ v.replace('_', '') }}
|
|
289
295
|
</a>
|
|
290
296
|
{% endfor %}
|
|
@@ -11,6 +11,20 @@ from sovereign.utils.mock import mock_discovery_request
|
|
|
11
11
|
router = APIRouter()
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
def _traverse(data, prefix, expressions):
|
|
15
|
+
for key, value in data.items():
|
|
16
|
+
path = f"{prefix}.{key}" if prefix else key
|
|
17
|
+
if isinstance(value, dict):
|
|
18
|
+
yield from _traverse(value, path, expressions)
|
|
19
|
+
else:
|
|
20
|
+
yield f"{path}={value}"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def expand_metadata_to_expr(m):
|
|
24
|
+
exprs = []
|
|
25
|
+
yield from _traverse(m, "", exprs)
|
|
26
|
+
|
|
27
|
+
|
|
14
28
|
@router.get("/resources/{resource_type}", summary="Get resources for a given type")
|
|
15
29
|
async def resource(
|
|
16
30
|
resource_type: DiscoveryTypes = Path(title="xDS Resource type"),
|
|
@@ -19,16 +33,25 @@ async def resource(
|
|
|
19
33
|
service_cluster: Optional[str] = Query("*", title="Envoy Service cluster"),
|
|
20
34
|
region: Optional[str] = Query(None, title="Locality Zone"),
|
|
21
35
|
version: Optional[str] = Query(None, title="Envoy Semantic Version"),
|
|
36
|
+
metadata: Optional[str] = Query(None, title="Envoy node metadata to filter by"),
|
|
22
37
|
) -> Response:
|
|
38
|
+
expressions = [f"cluster={service_cluster}"]
|
|
39
|
+
try:
|
|
40
|
+
metadata = json.loads(metadata or "{}")
|
|
41
|
+
for expr in expand_metadata_to_expr(metadata):
|
|
42
|
+
expressions.append(expr)
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
23
45
|
kwargs = dict(
|
|
24
46
|
api_version=api_version,
|
|
25
47
|
resource_type=DiscoveryTypes(resource_type).value,
|
|
26
48
|
resource_names=resource_name,
|
|
27
49
|
version=version,
|
|
28
50
|
region=region,
|
|
29
|
-
expressions=
|
|
51
|
+
expressions=expressions,
|
|
30
52
|
)
|
|
31
53
|
req = mock_discovery_request(**{k: v for k, v in kwargs.items() if v is not None})
|
|
54
|
+
print(req)
|
|
32
55
|
response = await cache.blocking_read(req)
|
|
33
56
|
if content := getattr(response, "text", None):
|
|
34
57
|
return Response(content, media_type="application/json")
|
|
@@ -5,7 +5,7 @@ from typing import Any, Dict, List
|
|
|
5
5
|
from fastapi import APIRouter, Cookie, Path, Query
|
|
6
6
|
from fastapi.encoders import jsonable_encoder
|
|
7
7
|
from fastapi.requests import Request
|
|
8
|
-
from fastapi.responses import HTMLResponse, JSONResponse,
|
|
8
|
+
from fastapi.responses import HTMLResponse, JSONResponse, Response
|
|
9
9
|
|
|
10
10
|
from sovereign import html_templates, cache
|
|
11
11
|
from sovereign.schemas import DiscoveryTypes, XDS_TEMPLATES
|
|
@@ -18,6 +18,7 @@ all_types = [t.value for t in DiscoveryTypes]
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@router.get("/")
|
|
21
|
+
@router.get("/resources")
|
|
21
22
|
async def ui_main(request: Request) -> HTMLResponse:
|
|
22
23
|
try:
|
|
23
24
|
return html_templates.TemplateResponse(
|
|
@@ -42,21 +43,6 @@ async def ui_main(request: Request) -> HTMLResponse:
|
|
|
42
43
|
)
|
|
43
44
|
|
|
44
45
|
|
|
45
|
-
@router.get(
|
|
46
|
-
"/set-version", summary="Filter the UI by a certain Envoy Version (stores a Cookie)"
|
|
47
|
-
)
|
|
48
|
-
async def set_envoy_version(
|
|
49
|
-
request: Request,
|
|
50
|
-
version: str = Query(
|
|
51
|
-
"__any__", title="The clients envoy version to emulate in this XDS request"
|
|
52
|
-
),
|
|
53
|
-
) -> Response:
|
|
54
|
-
url = request.headers.get("Referer", "/ui")
|
|
55
|
-
response = RedirectResponse(url=url)
|
|
56
|
-
response.set_cookie(key="envoy_version", value=version)
|
|
57
|
-
return response
|
|
58
|
-
|
|
59
|
-
|
|
60
46
|
@router.get(
|
|
61
47
|
"/resources/{xds_type}", summary="List available resources for a given xDS type"
|
|
62
48
|
)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from typing import Optional
|
|
3
|
-
from multiprocessing import Process
|
|
3
|
+
from multiprocessing import Process, cpu_count
|
|
4
4
|
from contextlib import asynccontextmanager
|
|
5
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
6
5
|
|
|
7
6
|
from fastapi import FastAPI, Body
|
|
8
7
|
|
|
@@ -23,10 +22,9 @@ from sovereign.context import NEW_CONTEXT
|
|
|
23
22
|
|
|
24
23
|
ClientId = str
|
|
25
24
|
ONDEMAND: asyncio.Queue[tuple[ClientId, DiscoveryRequest]] = asyncio.Queue(100)
|
|
26
|
-
|
|
25
|
+
RENDER_SEMAPHORE = asyncio.Semaphore(cpu_count())
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
# TODO: do something about this ---------------------------------------
|
|
30
28
|
def hidden_field(*args, **kwargs):
|
|
31
29
|
return "(value hidden)"
|
|
32
30
|
|
|
@@ -62,43 +60,37 @@ if config.sources is not None:
|
|
|
62
60
|
context_middleware.append(poller.add_to_context)
|
|
63
61
|
|
|
64
62
|
|
|
65
|
-
if poller is not None:
|
|
66
|
-
poller.lazy_load_modifiers(config.modifiers)
|
|
67
|
-
poller.lazy_load_global_modifiers(config.global_modifiers)
|
|
68
|
-
|
|
69
|
-
template_context.middleware = context_middleware
|
|
70
|
-
# ---------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
|
|
73
63
|
def render(job: rendering.RenderJob):
|
|
74
64
|
log.debug(f"Spawning render process for {job.id}")
|
|
75
65
|
Process(target=rendering.generate, args=[job]).start()
|
|
76
66
|
|
|
77
67
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
68
|
+
async def submit_render(job: rendering.RenderJob):
|
|
69
|
+
async with RENDER_SEMAPHORE:
|
|
70
|
+
render(job)
|
|
81
71
|
|
|
82
72
|
|
|
83
73
|
async def render_on_event():
|
|
84
74
|
while True:
|
|
85
75
|
# block forever until new context arrives
|
|
86
76
|
await NEW_CONTEXT.wait()
|
|
87
|
-
stats.increment("template.render_on_event")
|
|
88
77
|
log.debug("New context detected, re-rendering templates")
|
|
89
78
|
try:
|
|
90
79
|
if registered := cache.clients():
|
|
91
80
|
log.debug("New context detected, re-rendering templates")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
)
|
|
81
|
+
jobs = [
|
|
82
|
+
rendering.RenderJob(
|
|
83
|
+
id=client,
|
|
84
|
+
request=request,
|
|
85
|
+
context=template_context.get_context(request),
|
|
86
|
+
)
|
|
87
|
+
for client, request in registered
|
|
88
|
+
]
|
|
89
|
+
tasks = [submit_render(job) for job in jobs]
|
|
90
|
+
size = len(tasks)
|
|
91
|
+
stats.increment("template.render_on_event", tags=[f"batch_size:{size}"])
|
|
92
|
+
await asyncio.gather(*tasks)
|
|
93
|
+
log.debug(f"Completed rendering {size} jobs")
|
|
102
94
|
finally:
|
|
103
95
|
NEW_CONTEXT.clear()
|
|
104
96
|
|
|
@@ -111,7 +103,7 @@ async def render_on_demand():
|
|
|
111
103
|
job = rendering.RenderJob(
|
|
112
104
|
id=id, request=request, context=template_context.get_context(request)
|
|
113
105
|
)
|
|
114
|
-
await
|
|
106
|
+
await submit_render(job)
|
|
115
107
|
ONDEMAND.task_done()
|
|
116
108
|
|
|
117
109
|
|
|
@@ -124,25 +116,19 @@ async def lifespan(_: FastAPI):
|
|
|
124
116
|
|
|
125
117
|
# Template context
|
|
126
118
|
log.debug("Starting context loop")
|
|
119
|
+
template_context.middleware = context_middleware
|
|
127
120
|
asyncio.create_task(template_context.start())
|
|
128
121
|
await NEW_CONTEXT.wait() # first refresh finished
|
|
129
122
|
|
|
130
123
|
# Source polling
|
|
131
124
|
if poller is not None:
|
|
132
125
|
log.debug("Starting source poller")
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
poller.lazy_load_modifiers(config.modifiers)
|
|
127
|
+
poller.lazy_load_global_modifiers(config.global_modifiers)
|
|
128
|
+
asyncio.create_task(poller.poll_forever())
|
|
136
129
|
yield
|
|
137
130
|
|
|
138
131
|
|
|
139
|
-
def poller_thread(poller):
|
|
140
|
-
log.debug("Starting source poller")
|
|
141
|
-
loop = asyncio.new_event_loop()
|
|
142
|
-
asyncio.set_event_loop(loop)
|
|
143
|
-
loop.run_until_complete(poller.poll_forever())
|
|
144
|
-
|
|
145
|
-
|
|
146
132
|
worker = FastAPI(lifespan=lifespan)
|
|
147
133
|
try:
|
|
148
134
|
import sentry_sdk
|
|
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
|
{sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/aes_gcm_cipher.py
RENAMED
|
File without changes
|
{sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/base_cipher.py
RENAMED
|
File without changes
|
{sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/disabled_cipher.py
RENAMED
|
File without changes
|
{sovereign-1.0.0b101 → sovereign-1.0.0b103}/src/sovereign/utils/crypto/suites/fernet_cipher.py
RENAMED
|
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
|