sovereign 0.29.4__py3-none-any.whl → 0.30.0__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 +34 -40
- sovereign/app.py +11 -10
- sovereign/context.py +37 -24
- sovereign/discovery.py +8 -9
- sovereign/dynamic_config/deser.py +1 -2
- sovereign/dynamic_config/loaders.py +1 -2
- sovereign/logging/access_logger.py +14 -1
- sovereign/logging/application_logger.py +0 -1
- sovereign/logging/base_logger.py +3 -27
- sovereign/logging/bootstrapper.py +1 -1
- sovereign/middlewares.py +3 -2
- sovereign/modifiers/lib.py +1 -0
- sovereign/schemas.py +86 -37
- sovereign/sources/inline.py +1 -0
- sovereign/sources/lib.py +1 -0
- sovereign/sources/poller.py +8 -8
- sovereign/static/panel.js +56 -0
- sovereign/static/search_filter.js +20 -0
- sovereign/templates/resources.html +39 -36
- sovereign/tracing.py +52 -52
- sovereign/utils/crypto/suites/base_cipher.py +5 -10
- sovereign/utils/dictupdate.py +2 -1
- sovereign/views/admin.py +1 -0
- sovereign/views/crypto.py +2 -1
- sovereign/views/healthchecks.py +2 -1
- sovereign/views/interface.py +21 -5
- {sovereign-0.29.4.dist-info → sovereign-0.30.0.dist-info}/METADATA +43 -45
- {sovereign-0.29.4.dist-info → sovereign-0.30.0.dist-info}/RECORD +31 -31
- {sovereign-0.29.4.dist-info → sovereign-0.30.0.dist-info}/entry_points.txt +2 -0
- sovereign/configuration.py +0 -66
- sovereign/templates/ul_filter.html +0 -22
- {sovereign-0.29.4.dist-info → sovereign-0.30.0.dist-info}/LICENSE.txt +0 -0
- {sovereign-0.29.4.dist-info → sovereign-0.30.0.dist-info}/WHEEL +0 -0
sovereign/schemas.py
CHANGED
|
@@ -1,48 +1,35 @@
|
|
|
1
|
-
import multiprocessing
|
|
2
1
|
import warnings
|
|
2
|
+
import multiprocessing
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from os import getenv
|
|
7
7
|
from types import ModuleType
|
|
8
|
-
from typing import Any, Dict, List, Optional, Self, Tuple,
|
|
8
|
+
from typing import Any, Dict, List, Mapping, Optional, Self, Tuple, Union
|
|
9
9
|
|
|
10
|
+
import yaml
|
|
10
11
|
from croniter import CroniterBadCronError, croniter
|
|
11
|
-
from fastapi.responses import JSONResponse
|
|
12
12
|
from jinja2 import Template, meta
|
|
13
13
|
from pydantic import (
|
|
14
14
|
BaseModel,
|
|
15
15
|
ConfigDict,
|
|
16
16
|
Field,
|
|
17
17
|
SecretStr,
|
|
18
|
+
ValidationError,
|
|
18
19
|
model_validator,
|
|
19
20
|
field_validator,
|
|
20
21
|
)
|
|
21
22
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
22
23
|
|
|
24
|
+
from sovereign.response_class import json_response_class
|
|
23
25
|
from sovereign.dynamic_config import Loadable
|
|
24
26
|
from sovereign.dynamic_config.deser import jinja_env
|
|
25
27
|
from sovereign.utils.crypto.suites import EncryptionType
|
|
28
|
+
from sovereign.utils import dictupdate
|
|
26
29
|
from sovereign.utils.version_info import compute_hash
|
|
27
30
|
|
|
28
31
|
missing_arguments = {"missing", "positional", "arguments:"}
|
|
29
32
|
|
|
30
|
-
JsonResponseClass: Type[JSONResponse] = JSONResponse
|
|
31
|
-
# pylint: disable=unused-import
|
|
32
|
-
try:
|
|
33
|
-
import orjson
|
|
34
|
-
from fastapi.responses import ORJSONResponse
|
|
35
|
-
|
|
36
|
-
JsonResponseClass = ORJSONResponse
|
|
37
|
-
except ImportError:
|
|
38
|
-
try:
|
|
39
|
-
import ujson
|
|
40
|
-
from fastapi.responses import UJSONResponse
|
|
41
|
-
|
|
42
|
-
JsonResponseClass = UJSONResponse
|
|
43
|
-
except ImportError:
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
33
|
|
|
47
34
|
class CacheStrategy(str, Enum):
|
|
48
35
|
context = "context"
|
|
@@ -206,7 +193,7 @@ class ProcessedTemplate:
|
|
|
206
193
|
self.resources = resources
|
|
207
194
|
self.version_info = version_info
|
|
208
195
|
self._rendered: Optional[bytes] = None
|
|
209
|
-
if metadata
|
|
196
|
+
if metadata is None:
|
|
210
197
|
metadata = []
|
|
211
198
|
self.metadata = metadata
|
|
212
199
|
|
|
@@ -217,7 +204,7 @@ class ProcessedTemplate:
|
|
|
217
204
|
@property
|
|
218
205
|
def rendered(self) -> bytes:
|
|
219
206
|
if self._rendered is None:
|
|
220
|
-
result =
|
|
207
|
+
result = json_response_class(content="").render(
|
|
221
208
|
content={
|
|
222
209
|
"version_info": self.version,
|
|
223
210
|
"resources": self.resources,
|
|
@@ -381,7 +368,7 @@ class SovereignAsgiConfig(BaseSettings):
|
|
|
381
368
|
log_level: str = "warning"
|
|
382
369
|
worker_class: str = "uvicorn.workers.UvicornWorker"
|
|
383
370
|
worker_timeout: int = Field(30, alias="SOVEREIGN_WORKER_TIMEOUT")
|
|
384
|
-
worker_tmp_dir: str = "
|
|
371
|
+
worker_tmp_dir: Optional[str] = Field(None, alias="SOVEREIGN_WORKER_TMP_DIR")
|
|
385
372
|
graceful_timeout: Optional[int] = Field(None)
|
|
386
373
|
max_requests: int = Field(0, alias="SOVEREIGN_MAX_REQUESTS")
|
|
387
374
|
max_requests_jitter: int = Field(0, alias="SOVEREIGN_MAX_REQUESTS_JITTER")
|
|
@@ -399,7 +386,7 @@ class SovereignAsgiConfig(BaseSettings):
|
|
|
399
386
|
return self
|
|
400
387
|
|
|
401
388
|
def as_gunicorn_conf(self) -> Dict[str, Any]:
|
|
402
|
-
|
|
389
|
+
ret = {
|
|
403
390
|
"bind": ":".join(map(str, [self.host, self.port])),
|
|
404
391
|
"keepalive": self.keepalive,
|
|
405
392
|
"reuse_port": self.reuse_port,
|
|
@@ -409,11 +396,13 @@ class SovereignAsgiConfig(BaseSettings):
|
|
|
409
396
|
"threads": self.threads,
|
|
410
397
|
"workers": self.workers,
|
|
411
398
|
"worker_class": self.worker_class,
|
|
412
|
-
"worker_tmp_dir": self.worker_tmp_dir,
|
|
413
399
|
"graceful_timeout": self.graceful_timeout,
|
|
414
400
|
"max_requests": self.max_requests,
|
|
415
401
|
"max_requests_jitter": self.max_requests_jitter,
|
|
416
402
|
}
|
|
403
|
+
if self.worker_tmp_dir is not None:
|
|
404
|
+
ret["worker_tmp_dir"] = self.worker_tmp_dir
|
|
405
|
+
return ret
|
|
417
406
|
|
|
418
407
|
|
|
419
408
|
class SovereignConfig(BaseSettings):
|
|
@@ -437,7 +426,7 @@ class SovereignConfig(BaseSettings):
|
|
|
437
426
|
"service_clusters", alias="SOVEREIGN_SOURCE_MATCH_KEY"
|
|
438
427
|
)
|
|
439
428
|
sources_refresh_rate: int = Field(30, alias="SOVEREIGN_SOURCES_REFRESH_RATE")
|
|
440
|
-
cache_strategy:
|
|
429
|
+
cache_strategy: Optional[Any] = Field(None, alias="SOVEREIGN_CACHE_STRATEGY")
|
|
441
430
|
refresh_context: bool = Field(False, alias="SOVEREIGN_REFRESH_CONTEXT")
|
|
442
431
|
context_refresh_rate: Optional[int] = Field(
|
|
443
432
|
None, alias="SOVEREIGN_CONTEXT_REFRESH_RATE"
|
|
@@ -461,6 +450,17 @@ class SovereignConfig(BaseSettings):
|
|
|
461
450
|
populate_by_name=True,
|
|
462
451
|
)
|
|
463
452
|
|
|
453
|
+
def __init__(self, *args, **kwargs):
|
|
454
|
+
warnings.warn(
|
|
455
|
+
(
|
|
456
|
+
"This version of Sovereign config is deprecated and will be removed in a future release. "
|
|
457
|
+
"To migrate, use `sovereign config migrate file:///etc/sovereign.yaml,file://./config/example.yaml`"
|
|
458
|
+
),
|
|
459
|
+
DeprecationWarning,
|
|
460
|
+
stacklevel=2,
|
|
461
|
+
)
|
|
462
|
+
super().__init__(*args, **kwargs)
|
|
463
|
+
|
|
464
464
|
@property
|
|
465
465
|
def passwords(self) -> List[str]:
|
|
466
466
|
return self.auth_passwords.split(",") or []
|
|
@@ -629,9 +629,7 @@ class ContextConfiguration(BaseSettings):
|
|
|
629
629
|
|
|
630
630
|
class SourcesConfiguration(BaseSettings):
|
|
631
631
|
refresh_rate: int = Field(30, alias="SOVEREIGN_SOURCES_REFRESH_RATE")
|
|
632
|
-
cache_strategy:
|
|
633
|
-
CacheStrategy.context, alias="SOVEREIGN_CACHE_STRATEGY"
|
|
634
|
-
)
|
|
632
|
+
cache_strategy: Optional[Any] = None
|
|
635
633
|
model_config = SettingsConfigDict(
|
|
636
634
|
env_file=".env",
|
|
637
635
|
extra="ignore",
|
|
@@ -753,21 +751,30 @@ class LegacyConfig(BaseSettings):
|
|
|
753
751
|
|
|
754
752
|
|
|
755
753
|
class SovereignConfigv2(BaseSettings):
|
|
756
|
-
sources: List[ConfiguredSource]
|
|
757
754
|
templates: Dict[str, List[TemplateSpecification]]
|
|
758
|
-
source_config: SourcesConfiguration = SourcesConfiguration()
|
|
759
|
-
modifiers: List[str] = []
|
|
760
|
-
global_modifiers: List[str] = []
|
|
761
755
|
template_context: ContextConfiguration = ContextConfiguration()
|
|
762
|
-
matching: NodeMatching = NodeMatching()
|
|
763
756
|
authentication: AuthConfiguration = AuthConfiguration()
|
|
764
757
|
logging: LoggingConfiguration = LoggingConfiguration()
|
|
765
758
|
statsd: StatsdConfig = StatsdConfig()
|
|
766
759
|
sentry_dsn: SecretStr = Field(SecretStr(""), alias="SOVEREIGN_SENTRY_DSN")
|
|
767
|
-
debug: bool = Field(False, alias="SOVEREIGN_DEBUG")
|
|
768
|
-
legacy_fields: LegacyConfig = LegacyConfig()
|
|
769
760
|
discovery_cache: DiscoveryCacheConfig = DiscoveryCacheConfig()
|
|
770
|
-
tracing: TracingConfig = TracingConfig
|
|
761
|
+
tracing: Optional[TracingConfig] = Field(default_factory=TracingConfig)
|
|
762
|
+
debug: bool = Field(False, alias="SOVEREIGN_DEBUG")
|
|
763
|
+
|
|
764
|
+
# Deprecated in 0.30
|
|
765
|
+
sources: Optional[List[ConfiguredSource]] = Field(None, deprecated=True)
|
|
766
|
+
source_config: SourcesConfiguration = Field(
|
|
767
|
+
default_factory=SourcesConfiguration, deprecated=True
|
|
768
|
+
)
|
|
769
|
+
matching: Optional[NodeMatching] = Field(
|
|
770
|
+
default_factory=NodeMatching, deprecated=True
|
|
771
|
+
)
|
|
772
|
+
modifiers: List[str] = Field(default_factory=list, deprecated=True)
|
|
773
|
+
global_modifiers: List[str] = Field(default_factory=list, deprecated=True)
|
|
774
|
+
legacy_fields: LegacyConfig = Field(default_factory=LegacyConfig, deprecated=True)
|
|
775
|
+
|
|
776
|
+
expected_service_clusters: List[str] = Field(default_factory=list, deprecated=True)
|
|
777
|
+
|
|
771
778
|
model_config = SettingsConfigDict(
|
|
772
779
|
env_file=".env",
|
|
773
780
|
extra="ignore",
|
|
@@ -823,7 +830,7 @@ class SovereignConfigv2(BaseSettings):
|
|
|
823
830
|
templates=new_templates,
|
|
824
831
|
source_config=SourcesConfiguration(
|
|
825
832
|
refresh_rate=other.sources_refresh_rate,
|
|
826
|
-
cache_strategy=
|
|
833
|
+
cache_strategy=None,
|
|
827
834
|
),
|
|
828
835
|
modifiers=other.modifiers,
|
|
829
836
|
global_modifiers=other.global_modifiers,
|
|
@@ -867,3 +874,45 @@ class SovereignConfigv2(BaseSettings):
|
|
|
867
874
|
),
|
|
868
875
|
discovery_cache=other.discovery_cache,
|
|
869
876
|
)
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def migrate_configs():
|
|
880
|
+
import argparse
|
|
881
|
+
|
|
882
|
+
parser = argparse.ArgumentParser(description="Tool to manage configurations.")
|
|
883
|
+
subparsers = parser.add_subparsers(dest="command", help="Main commands")
|
|
884
|
+
config_parser = subparsers.add_parser("config", help="Configuration commands")
|
|
885
|
+
config_subparsers = config_parser.add_subparsers(
|
|
886
|
+
dest="subcommand", help="Config subcommands"
|
|
887
|
+
)
|
|
888
|
+
migrate_parser = config_subparsers.add_parser(
|
|
889
|
+
"migrate", help="Migrate configuration files"
|
|
890
|
+
)
|
|
891
|
+
migrate_parser.add_argument("files", help="Files to migrate")
|
|
892
|
+
args = parser.parse_args()
|
|
893
|
+
|
|
894
|
+
if args.command == "config" and args.subcommand == "migrate":
|
|
895
|
+
|
|
896
|
+
def secret_str_representer(dumper, data):
|
|
897
|
+
return dumper.represent_scalar(
|
|
898
|
+
"tag:yaml.org,2002:str", data.get_secret_value()
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
yaml.SafeDumper.add_representer(SecretStr, secret_str_representer)
|
|
902
|
+
try:
|
|
903
|
+
SovereignConfigv2(**parse_raw_configuration(args.files))
|
|
904
|
+
except ValidationError:
|
|
905
|
+
print("Already v2")
|
|
906
|
+
old_config = SovereignConfig(**parse_raw_configuration(args.files))
|
|
907
|
+
config = SovereignConfigv2.from_legacy_config(old_config)
|
|
908
|
+
print(yaml.safe_dump(config.model_dump()))
|
|
909
|
+
exit(0)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
def parse_raw_configuration(path: str) -> Mapping[Any, Any]:
|
|
913
|
+
ret: Mapping[Any, Any] = dict()
|
|
914
|
+
for p in path.split(","):
|
|
915
|
+
spec = Loadable.from_legacy_fmt(p)
|
|
916
|
+
# For some reason mypy is broken here
|
|
917
|
+
ret = dictupdate.merge(obj_a=ret, obj_b=spec.load(), merge_lists=True) # type: ignore
|
|
918
|
+
return ret
|
sovereign/sources/inline.py
CHANGED
sovereign/sources/lib.py
CHANGED
sovereign/sources/poller.py
CHANGED
|
@@ -40,8 +40,8 @@ class SourcePoller:
|
|
|
40
40
|
self,
|
|
41
41
|
sources: List[ConfiguredSource],
|
|
42
42
|
matching_enabled: bool,
|
|
43
|
-
node_match_key: str,
|
|
44
|
-
source_match_key: str,
|
|
43
|
+
node_match_key: Optional[str],
|
|
44
|
+
source_match_key: Optional[str],
|
|
45
45
|
source_refresh_rate: int,
|
|
46
46
|
logger: BoundLogger,
|
|
47
47
|
stats: Any,
|
|
@@ -181,6 +181,8 @@ class SourcePoller:
|
|
|
181
181
|
return new
|
|
182
182
|
|
|
183
183
|
def extract_node_key(self, node: Union[Node, Dict[Any, Any]]) -> Any:
|
|
184
|
+
if self.node_match_key is None:
|
|
185
|
+
return
|
|
184
186
|
if "." not in self.node_match_key:
|
|
185
187
|
# key is not nested, don't need glom
|
|
186
188
|
node_value = getattr(node, self.node_match_key)
|
|
@@ -189,13 +191,13 @@ class SourcePoller:
|
|
|
189
191
|
node_value = glom(node, self.node_match_key)
|
|
190
192
|
except PathAccessError:
|
|
191
193
|
raise RuntimeError(
|
|
192
|
-
f'Failed to find key "{self.node_match_key}" in discoveryRequest({node})
|
|
193
|
-
f"See the docs for more info: "
|
|
194
|
-
f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
|
|
194
|
+
f'Failed to find key "{self.node_match_key}" in discoveryRequest({node})'
|
|
195
195
|
)
|
|
196
196
|
return node_value
|
|
197
197
|
|
|
198
198
|
def extract_source_key(self, source: Dict[Any, Any]) -> Any:
|
|
199
|
+
if self.source_match_key is None:
|
|
200
|
+
return
|
|
199
201
|
if "." not in self.source_match_key:
|
|
200
202
|
# key is not nested, don't need glom
|
|
201
203
|
source_value = source[self.source_match_key]
|
|
@@ -204,9 +206,7 @@ class SourcePoller:
|
|
|
204
206
|
source_value = glom(source, self.source_match_key)
|
|
205
207
|
except PathAccessError:
|
|
206
208
|
raise RuntimeError(
|
|
207
|
-
f'Failed to find key "{self.source_match_key}" in instance({source})
|
|
208
|
-
f"See the docs for more info: "
|
|
209
|
-
f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
|
|
209
|
+
f'Failed to find key "{self.source_match_key}" in instance({source})'
|
|
210
210
|
)
|
|
211
211
|
return source_value
|
|
212
212
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
+
function clearSearch() {
|
|
3
|
+
const searchInput = document.getElementById('searchInput');
|
|
4
|
+
searchInput.placeholder = '';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
// Function to hide all panels except active
|
|
8
|
+
function updateVisibility() {
|
|
9
|
+
const panelBlocks = document.querySelectorAll('.virtualhost');
|
|
10
|
+
panelBlocks.forEach(block => {
|
|
11
|
+
if (!block.classList.contains('is-active')) {
|
|
12
|
+
block.classList.add('filtered');
|
|
13
|
+
} else {
|
|
14
|
+
block.classList.remove('filtered');
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
updateVisibility();
|
|
19
|
+
|
|
20
|
+
window.filterTabs = function(element, filter) {
|
|
21
|
+
const tabs = document.querySelectorAll('.panel-tabs a');
|
|
22
|
+
tabs.forEach(tab => tab.classList.remove('is-active'));
|
|
23
|
+
element.classList.add('is-active');
|
|
24
|
+
|
|
25
|
+
const virtualHosts = document.querySelectorAll('.virtualhost');
|
|
26
|
+
if (filter === "all") {
|
|
27
|
+
virtualHosts.forEach(vh => vh.classList.remove('filtered'));
|
|
28
|
+
} else {
|
|
29
|
+
virtualHosts.forEach(vh => {
|
|
30
|
+
if (vh.getAttribute("data-category") == filter) {
|
|
31
|
+
vh.classList.remove('filtered');
|
|
32
|
+
} else {
|
|
33
|
+
vh.classList.add('filtered');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
clearSearch();
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const searchInput = document.getElementById('searchInput');
|
|
41
|
+
searchInput.addEventListener('input', function() {
|
|
42
|
+
const query = searchInput.value.toLowerCase();
|
|
43
|
+
const panelBlocks = document.querySelectorAll('.virtualhost');
|
|
44
|
+
panelBlocks.forEach(block => {
|
|
45
|
+
const text = block.textContent.toLowerCase();
|
|
46
|
+
if (text.includes(query)) {
|
|
47
|
+
block.classList.remove('filtered');
|
|
48
|
+
} else {
|
|
49
|
+
block.classList.add('filtered');
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const allTab = document.querySelector('.panel-tabs a.is-active');
|
|
55
|
+
filterTabs(allTab, 'all');
|
|
56
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
function filter_results(id, list) {
|
|
2
|
+
// Declare variables
|
|
3
|
+
let input, filter, container, iterable, resource, i, txtValue;
|
|
4
|
+
input = document.getElementById(id);
|
|
5
|
+
filter = input.value.toUpperCase();
|
|
6
|
+
|
|
7
|
+
container = document.getElementById(list);
|
|
8
|
+
iterable = container.getElementsByTagName("a");
|
|
9
|
+
|
|
10
|
+
// Loop through all list items, and hide those who don't match the search query
|
|
11
|
+
for (i = 0; i < iterable.length; i++) {
|
|
12
|
+
resource = iterable[i];
|
|
13
|
+
txtValue = resource.textContent;
|
|
14
|
+
if (txtValue.toUpperCase().indexOf(filter) > -1) {
|
|
15
|
+
iterable[i].style.display = "";
|
|
16
|
+
} else {
|
|
17
|
+
iterable[i].style.display = "none";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{%- extends 'base.html' %}
|
|
2
|
-
{%- import 'ul_filter.html' as filter %}
|
|
3
2
|
{% block title %}{{ resource_type|capitalize }}{% endblock %}
|
|
4
3
|
|
|
5
4
|
{% block head %}
|
|
6
5
|
<meta name="last_update" content="{{ last_update }}">
|
|
7
|
-
|
|
6
|
+
<script src="/static/search_filter.js"></script>
|
|
7
|
+
<script src="/static/panel.js"></script>
|
|
8
|
+
<style>
|
|
9
|
+
.filtered {
|
|
10
|
+
display: none;
|
|
11
|
+
}
|
|
12
|
+
</style>
|
|
8
13
|
{% endblock %}
|
|
9
14
|
|
|
10
15
|
|
|
@@ -74,7 +79,7 @@
|
|
|
74
79
|
type="text"
|
|
75
80
|
id="search_filter"
|
|
76
81
|
onkeyup="filter_results('search_filter', 'resources')"
|
|
77
|
-
placeholder="Filter
|
|
82
|
+
placeholder="Filter {{ resource_type }} by any string"
|
|
78
83
|
>
|
|
79
84
|
</label>
|
|
80
85
|
<span class="icon is-left">
|
|
@@ -118,39 +123,37 @@
|
|
|
118
123
|
</nav>
|
|
119
124
|
|
|
120
125
|
{% if resource_type == 'routes' %}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
</nav>
|
|
153
|
-
{% endfor %}
|
|
126
|
+
<nav class="panel">
|
|
127
|
+
<p class="panel-heading">Virtual Hosts</p>
|
|
128
|
+
<div class="panel-block">
|
|
129
|
+
<p class="control has-icons-left">
|
|
130
|
+
<input id="searchInput" class="input" type="text" placeholder="Filter virtual-hosts by any string" />
|
|
131
|
+
<span class="icon is-left">
|
|
132
|
+
<i class="fas fa-search" aria-hidden="true"></i>
|
|
133
|
+
</span>
|
|
134
|
+
</p>
|
|
135
|
+
</div>
|
|
136
|
+
<p class="panel-tabs">
|
|
137
|
+
<a class="is-active" onclick="filterTabs(this, 'all')">All</a>
|
|
138
|
+
{% for resource in res %}
|
|
139
|
+
<a onclick="filterTabs(this, '{{ resource["name"] }}')">
|
|
140
|
+
{{ resource['name'] }}
|
|
141
|
+
</a>
|
|
142
|
+
{% endfor %}
|
|
143
|
+
</p>
|
|
144
|
+
{% for resource in res %}
|
|
145
|
+
{% for virtualhost in resource['virtual_hosts'] %}
|
|
146
|
+
<a class="panel-block virtualhost"
|
|
147
|
+
data-category="{{ resource['name'] }}"
|
|
148
|
+
href="/ui/resources/routes/{{ resource['name'] }}/{{ virtualhost['name'] }}">
|
|
149
|
+
<span class="panel-icon">
|
|
150
|
+
<i class="fas fa-arrow-right" aria-hidden="true"></i>
|
|
151
|
+
</span>
|
|
152
|
+
{{ virtualhost['name'] }}
|
|
153
|
+
</a>
|
|
154
|
+
{% endfor %}
|
|
155
|
+
{% endfor %}
|
|
156
|
+
</nav>
|
|
154
157
|
{% endif %}
|
|
155
158
|
{% else %}
|
|
156
159
|
<span class="panel-icon">
|
sovereign/tracing.py
CHANGED
|
@@ -31,55 +31,55 @@ def timestamp():
|
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
TRACING = config.tracing
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class Tracer:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
34
|
+
if TRACING is not None:
|
|
35
|
+
TRACING_DISABLED = not TRACING.enabled
|
|
36
|
+
|
|
37
|
+
class Tracer:
|
|
38
|
+
def gen_id(self):
|
|
39
|
+
if TRACING.trace_id_128bit:
|
|
40
|
+
trace_id = generate_128bit()
|
|
41
|
+
else:
|
|
42
|
+
trace_id = generate_64bit()
|
|
43
|
+
_trace_id_ctx_var.set(trace_id)
|
|
44
|
+
return trace_id
|
|
45
|
+
|
|
46
|
+
def __init__(self, span_name):
|
|
47
|
+
if TRACING_DISABLED:
|
|
48
|
+
return
|
|
49
|
+
span_id = get_span_id()
|
|
50
|
+
self.parent_span_id = None
|
|
51
|
+
if span_id != "":
|
|
52
|
+
# We are already inside a trace context
|
|
53
|
+
self.parent_span_id = span_id
|
|
54
|
+
self.trace_id = get_trace_id()
|
|
55
|
+
self.span_id = self.gen_id()
|
|
56
|
+
self.span_name = span_name
|
|
57
|
+
|
|
58
|
+
def __enter__(self):
|
|
59
|
+
if TRACING_DISABLED:
|
|
60
|
+
return nullcontext()
|
|
61
|
+
self.trace = {
|
|
62
|
+
"traceId": self.trace_id,
|
|
63
|
+
"id": self.span_id,
|
|
64
|
+
"name": self.span_name,
|
|
65
|
+
"timestamp": time.time(),
|
|
66
|
+
"tags": TRACING.tags,
|
|
67
|
+
}
|
|
68
|
+
if self.parent_span_id:
|
|
69
|
+
self.trace["parent_span_id"] = self.parent_span_id
|
|
70
|
+
return self
|
|
71
|
+
|
|
72
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
73
|
+
if TRACING_DISABLED:
|
|
74
|
+
return
|
|
75
|
+
self.trace["duration"] = time.time() - self.trace["timestamp"]
|
|
76
|
+
self.submit()
|
|
77
|
+
|
|
78
|
+
def submit(self):
|
|
79
|
+
print(f"{self.span_name}: {self.trace['duration']}")
|
|
80
|
+
try:
|
|
81
|
+
url = f"{TRACING.collector}{TRACING.endpoint}"
|
|
82
|
+
requests.post(url, json=self.trace)
|
|
83
|
+
# pylint: disable=broad-except
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"Failed to submit trace: {self.trace}, Error:{e}")
|
|
@@ -4,23 +4,18 @@ from typing import Any
|
|
|
4
4
|
|
|
5
5
|
class CipherSuite(ABC):
|
|
6
6
|
@abstractmethod
|
|
7
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
8
|
-
...
|
|
7
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: ...
|
|
9
8
|
|
|
10
9
|
@abstractmethod
|
|
11
|
-
def encrypt(self, data: str) -> str:
|
|
12
|
-
...
|
|
10
|
+
def encrypt(self, data: str) -> str: ...
|
|
13
11
|
|
|
14
12
|
@abstractmethod
|
|
15
|
-
def decrypt(self, data: str) -> str:
|
|
16
|
-
...
|
|
13
|
+
def decrypt(self, data: str) -> str: ...
|
|
17
14
|
|
|
18
15
|
@property
|
|
19
16
|
@abstractmethod
|
|
20
|
-
def key_available(self) -> bool:
|
|
21
|
-
...
|
|
17
|
+
def key_available(self) -> bool: ...
|
|
22
18
|
|
|
23
19
|
@classmethod
|
|
24
20
|
@abstractmethod
|
|
25
|
-
def generate_key(cls) -> bytes:
|
|
26
|
-
...
|
|
21
|
+
def generate_key(cls) -> bytes: ...
|
sovereign/utils/dictupdate.py
CHANGED
sovereign/views/admin.py
CHANGED
|
@@ -92,6 +92,7 @@ def instances(
|
|
|
92
92
|
title="Whether the sources should run Modifiers/Global Modifiers prior to being returned",
|
|
93
93
|
),
|
|
94
94
|
) -> JSONResponse:
|
|
95
|
+
assert poller is not None # how else would there be sources
|
|
95
96
|
node = mock_discovery_request(service_cluster=service_cluster).node
|
|
96
97
|
args = {
|
|
97
98
|
"modify": yaml.safe_load(modified),
|
sovereign/views/crypto.py
CHANGED
|
@@ -4,7 +4,8 @@ from fastapi import APIRouter, Body
|
|
|
4
4
|
from fastapi.responses import JSONResponse
|
|
5
5
|
from pydantic import BaseModel, Field
|
|
6
6
|
|
|
7
|
-
from sovereign import
|
|
7
|
+
from sovereign import logs, server_cipher_container
|
|
8
|
+
from sovereign.response_class import json_response_class
|
|
8
9
|
from sovereign.schemas import EncryptionConfig
|
|
9
10
|
from sovereign.utils.crypto.crypto import CipherContainer
|
|
10
11
|
from sovereign.utils.crypto.suites import EncryptionType
|
sovereign/views/healthchecks.py
CHANGED
|
@@ -2,7 +2,8 @@ from typing import List
|
|
|
2
2
|
from fastapi import Response
|
|
3
3
|
from fastapi.routing import APIRouter
|
|
4
4
|
from fastapi.responses import PlainTextResponse
|
|
5
|
-
from sovereign import XDS_TEMPLATES, __version__
|
|
5
|
+
from sovereign import XDS_TEMPLATES, __version__
|
|
6
|
+
from sovereign.response_class import json_response_class
|
|
6
7
|
from sovereign.utils.mock import mock_discovery_request
|
|
7
8
|
from sovereign.views.discovery import perform_discovery
|
|
8
9
|
|