sovereign 0.19.3__py3-none-any.whl → 1.0.0a4__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.

Files changed (99) hide show
  1. sovereign/__init__.py +13 -81
  2. sovereign/app.py +62 -48
  3. sovereign/cache/__init__.py +245 -0
  4. sovereign/cache/backends/__init__.py +110 -0
  5. sovereign/cache/backends/s3.py +161 -0
  6. sovereign/cache/filesystem.py +74 -0
  7. sovereign/cache/types.py +17 -0
  8. sovereign/configuration.py +607 -0
  9. sovereign/constants.py +1 -0
  10. sovereign/context.py +270 -104
  11. sovereign/dynamic_config/__init__.py +112 -0
  12. sovereign/dynamic_config/deser.py +78 -0
  13. sovereign/dynamic_config/loaders.py +120 -0
  14. sovereign/error_info.py +2 -3
  15. sovereign/events.py +49 -0
  16. sovereign/logging/access_logger.py +85 -0
  17. sovereign/logging/application_logger.py +54 -0
  18. sovereign/logging/base_logger.py +41 -0
  19. sovereign/logging/bootstrapper.py +36 -0
  20. sovereign/logging/types.py +10 -0
  21. sovereign/middlewares.py +8 -7
  22. sovereign/modifiers/lib.py +2 -1
  23. sovereign/rendering.py +124 -0
  24. sovereign/rendering_common.py +91 -0
  25. sovereign/response_class.py +18 -0
  26. sovereign/server.py +112 -35
  27. sovereign/statistics.py +19 -21
  28. sovereign/templates/base.html +59 -46
  29. sovereign/templates/resources.html +203 -102
  30. sovereign/testing/loaders.py +9 -0
  31. sovereign/{modifiers/test.py → testing/modifiers.py} +0 -2
  32. sovereign/tracing.py +103 -0
  33. sovereign/types.py +304 -0
  34. sovereign/utils/auth.py +27 -13
  35. sovereign/utils/crypto/__init__.py +0 -0
  36. sovereign/utils/crypto/crypto.py +135 -0
  37. sovereign/utils/crypto/suites/__init__.py +21 -0
  38. sovereign/utils/crypto/suites/aes_gcm_cipher.py +42 -0
  39. sovereign/utils/crypto/suites/base_cipher.py +21 -0
  40. sovereign/utils/crypto/suites/disabled_cipher.py +25 -0
  41. sovereign/utils/crypto/suites/fernet_cipher.py +29 -0
  42. sovereign/utils/dictupdate.py +3 -2
  43. sovereign/utils/eds.py +40 -22
  44. sovereign/utils/entry_point_loader.py +2 -2
  45. sovereign/utils/mock.py +56 -17
  46. sovereign/utils/resources.py +17 -0
  47. sovereign/utils/templates.py +4 -2
  48. sovereign/utils/timer.py +5 -3
  49. sovereign/utils/version_info.py +8 -0
  50. sovereign/utils/weighted_clusters.py +2 -1
  51. sovereign/v2/__init__.py +0 -0
  52. sovereign/v2/data/data_store.py +621 -0
  53. sovereign/v2/data/render_discovery_response.py +24 -0
  54. sovereign/v2/data/repositories.py +90 -0
  55. sovereign/v2/data/utils.py +33 -0
  56. sovereign/v2/data/worker_queue.py +273 -0
  57. sovereign/v2/jobs/refresh_context.py +117 -0
  58. sovereign/v2/jobs/render_discovery_job.py +145 -0
  59. sovereign/v2/logging.py +81 -0
  60. sovereign/v2/types.py +41 -0
  61. sovereign/v2/web.py +101 -0
  62. sovereign/v2/worker.py +199 -0
  63. sovereign/views/__init__.py +7 -0
  64. sovereign/views/api.py +82 -0
  65. sovereign/views/crypto.py +46 -15
  66. sovereign/views/discovery.py +55 -119
  67. sovereign/views/healthchecks.py +107 -20
  68. sovereign/views/interface.py +171 -111
  69. sovereign/worker.py +193 -0
  70. {sovereign-0.19.3.dist-info → sovereign-1.0.0a4.dist-info}/METADATA +80 -76
  71. sovereign-1.0.0a4.dist-info/RECORD +85 -0
  72. {sovereign-0.19.3.dist-info → sovereign-1.0.0a4.dist-info}/WHEEL +1 -1
  73. sovereign-1.0.0a4.dist-info/entry_points.txt +46 -0
  74. sovereign_files/__init__.py +0 -0
  75. sovereign_files/static/darkmode.js +51 -0
  76. sovereign_files/static/node_expression.js +42 -0
  77. sovereign_files/static/panel.js +76 -0
  78. sovereign_files/static/resources.css +246 -0
  79. sovereign_files/static/resources.js +642 -0
  80. sovereign_files/static/sass/style.scss +33 -0
  81. sovereign_files/static/style.css +16143 -0
  82. sovereign_files/static/style.css.map +1 -0
  83. sovereign/config_loader.py +0 -225
  84. sovereign/discovery.py +0 -175
  85. sovereign/logs.py +0 -131
  86. sovereign/schemas.py +0 -780
  87. sovereign/sources/__init__.py +0 -3
  88. sovereign/sources/file.py +0 -21
  89. sovereign/sources/inline.py +0 -38
  90. sovereign/sources/lib.py +0 -40
  91. sovereign/sources/poller.py +0 -294
  92. sovereign/static/sass/style.scss +0 -27
  93. sovereign/static/style.css +0 -13553
  94. sovereign/templates/ul_filter.html +0 -22
  95. sovereign/utils/crypto.py +0 -103
  96. sovereign/views/admin.py +0 -120
  97. sovereign-0.19.3.dist-info/LICENSE.txt +0 -13
  98. sovereign-0.19.3.dist-info/RECORD +0 -47
  99. sovereign-0.19.3.dist-info/entry_points.txt +0 -10
sovereign/utils/eds.py CHANGED
@@ -1,9 +1,11 @@
1
1
  import random
2
- from typing import Dict, Any, Optional, List
3
2
  from copy import deepcopy
3
+ from typing import Any, Dict, List, Optional
4
+
4
5
  from starlette.exceptions import HTTPException
5
- from sovereign import config
6
- from sovereign.schemas import DiscoveryRequest
6
+
7
+ from sovereign.configuration import config
8
+ from sovereign.types import DiscoveryRequest
7
9
  from sovereign.utils.templates import resolve
8
10
 
9
11
  HARD_FAIL_ON_DNS_FAILURE = config.legacy_fields.dns_hard_fail
@@ -26,12 +28,16 @@ def _upstream_kwargs(
26
28
  if hard_fail:
27
29
  raise
28
30
  ip_addresses = [upstream["address"]]
29
- return {
31
+ ret = {
30
32
  "addrs": ip_addresses,
31
33
  "port": upstream["port"],
32
34
  "region": default_region or upstream.get("region", "unknown"),
33
35
  "zone": proxy_region,
34
36
  }
37
+ if "health_check_config" in upstream:
38
+ ret["health_check_config"] = upstream["health_check_config"]
39
+
40
+ return ret
35
41
 
36
42
 
37
43
  def total_zones(endpoints: List[Dict[str, Dict[str, Any]]]) -> int:
@@ -84,14 +90,21 @@ def locality_lb_endpoints(
84
90
  return ret
85
91
 
86
92
 
87
- def lb_endpoints(addrs: List[str], port: int, region: str, zone: str) -> Dict[str, Any]:
93
+ def lb_endpoints(
94
+ addrs: List[str],
95
+ port: int,
96
+ region: str,
97
+ zone: str,
98
+ health_check_config: Optional[Dict[str, Any]] = None,
99
+ ) -> Dict[str, Any]:
88
100
  """
89
101
  Creates an envoy endpoint.LbEndpoints proto
90
102
 
91
- :param addrs: The IP addresses or hostname(s) of the upstream.
92
- :param port: The port that the upstream should be accessed on.
93
- :param region: The region of the upstream.
94
- :param zone: The region of the proxy asking for the endpoint configuration.
103
+ :param addrs: The IP addresses or hostname(s) of the upstream.
104
+ :param port: The port that the upstream should be accessed on.
105
+ :param region: The region of the upstream.
106
+ :param zone: The region of the proxy asking for the endpoint configuration.
107
+ :param health_check_config: Optional health check config for the upstream.
95
108
  """
96
109
  if PRIORITY_MAPPING is None:
97
110
  raise RuntimeError(
@@ -100,20 +113,25 @@ def lb_endpoints(addrs: List[str], port: int, region: str, zone: str) -> Dict[st
100
113
  )
101
114
  node_priorities = PRIORITY_MAPPING.get(zone, {})
102
115
  priority = node_priorities.get(region, 10)
116
+
117
+ endpoints = []
118
+ for addr in addrs:
119
+ endpoint = {
120
+ "endpoint": {
121
+ "address": {
122
+ "socket_address": {
123
+ "address": addr,
124
+ "port_value": port,
125
+ }
126
+ },
127
+ }
128
+ }
129
+ if health_check_config:
130
+ endpoint["endpoint"]["health_check_config"] = health_check_config
131
+ endpoints.append(endpoint)
132
+
103
133
  return {
104
134
  "priority": priority,
105
135
  "locality": {"zone": region},
106
- "lb_endpoints": [
107
- {
108
- "endpoint": {
109
- "address": {
110
- "socket_address": {
111
- "address": addr,
112
- "port_value": port,
113
- }
114
- }
115
- }
116
- }
117
- for addr in addrs
118
- ],
136
+ "lb_endpoints": endpoints,
119
137
  }
@@ -1,6 +1,6 @@
1
- from typing import Dict
2
1
  from functools import cached_property
3
- from importlib.metadata import entry_points, EntryPoints
2
+ from importlib.metadata import EntryPoints, entry_points
3
+ from typing import Dict
4
4
 
5
5
 
6
6
  class EntryPointLoader:
sovereign/utils/mock.py CHANGED
@@ -1,36 +1,75 @@
1
- from typing import Optional, Dict, List
1
+ import ast
2
+ import re
2
3
  from random import randint
3
- from sovereign.schemas import DiscoveryRequest, Node, Locality, Resources, Status
4
+ from typing import Dict, List, Optional
5
+
6
+ from sovereign.types import DiscoveryRequest, Locality, Node, Status
7
+
8
+ scrub = re.compile(r"[^a-zA-Z_\.]")
9
+
10
+
11
+ class NodeExpressionError(Exception):
12
+ pass
4
13
 
5
14
 
6
15
  def mock_discovery_request(
7
- service_cluster: Optional[str] = None,
8
- resource_names: Optional[List[str]] = None,
9
- region: str = "none",
10
- version: str = "1.11.1",
16
+ api_version: Optional[str] = "V3",
17
+ resource_type: Optional[str] = None,
18
+ resource_names: Optional[List[str] | str] = None,
19
+ region: Optional[str] = "none",
20
+ version: Optional[str] = "<envoy_version>",
11
21
  metadata: Optional[Dict[str, str]] = None,
12
22
  error_message: Optional[str] = None,
23
+ expressions: Optional[list[str]] = None,
13
24
  ) -> DiscoveryRequest:
14
25
  if resource_names is None:
15
- resource_names = Resources()
16
- else:
17
- resource_names = Resources(resource_names)
26
+ resource_names = []
27
+ if isinstance(resource_names, str):
28
+ resource_names = [resource_names]
29
+ if expressions is None:
30
+ expressions = []
31
+ base_node = Node(
32
+ id="sovereign-interface",
33
+ cluster="*",
34
+ build_version=f"<randomHash>/{version}/Clean/RELEASE",
35
+ locality=Locality(zone=region),
36
+ ).model_dump()
37
+ set_node_expressions(base_node, expressions)
18
38
  request = DiscoveryRequest(
19
39
  type_url=None,
20
- node=Node(
21
- id="mock",
22
- cluster=service_cluster or "",
23
- build_version=f"e5f864a82d4f27110359daa2fbdcb12d99e415b9/{version}/Clean/RELEASE",
24
- locality=Locality(zone=region, region="example", sub_zone="a"),
25
- ),
40
+ node=Node.model_validate(base_node),
26
41
  version_info=str(randint(100000, 1000000000)),
27
42
  resource_names=resource_names,
28
- hide_private_keys=True,
29
- desired_controlplane="controlplane",
43
+ is_internal_request=True,
44
+ desired_controlplane="__sovereign__",
30
45
  error_detail=Status(code=200, message="None", details=["None"]),
46
+ api_version=api_version,
47
+ resource_type=resource_type,
31
48
  )
32
49
  if isinstance(metadata, dict):
33
50
  request.node.metadata = metadata
34
51
  if error_message:
35
52
  request.error_detail = Status(code=666, message=error_message, details=["foo"])
36
53
  return request
54
+
55
+
56
+ def set_node_expressions(node, expressions):
57
+ for expr in expressions:
58
+ try:
59
+ field, value = re.split(r"\s*=\s*", expr, maxsplit=1)
60
+ value = f'"{value}"'
61
+ except ValueError:
62
+ raise NodeExpressionError(f"Invalid node filter format: {expr}")
63
+
64
+ field = scrub.sub("", field)
65
+ parts = field.split(".")
66
+
67
+ try:
68
+ value = ast.literal_eval(value)
69
+ except Exception as e:
70
+ raise NodeExpressionError(f"Invalid node filter value: {value}") from e
71
+
72
+ current = node
73
+ for part in parts[:-1]:
74
+ current = current.setdefault(part, {})
75
+ current[parts[-1]] = value
@@ -0,0 +1,17 @@
1
+ import importlib.resources as res
2
+ from functools import cache
3
+ from importlib.resources.abc import Traversable
4
+
5
+
6
+ @cache
7
+ def get_package(name: str) -> Traversable:
8
+ return res.files(name)
9
+
10
+
11
+ def get_package_file(package_name: str, filename: str) -> Traversable:
12
+ return get_package(package_name).joinpath(filename)
13
+
14
+
15
+ def get_package_file_bytes(package_name: str, filename: str) -> bytes:
16
+ file = get_package_file(package_name, filename)
17
+ return file.read_bytes()
@@ -1,7 +1,9 @@
1
- from typing import List, Optional, Any, Dict
2
- from socket import gethostbyname_ex
3
1
  from socket import gaierror as dns_error
2
+ from socket import gethostbyname_ex
3
+ from typing import Any, Dict, List, Optional
4
+
4
5
  from starlette.exceptions import HTTPException
6
+
5
7
  from sovereign import config, stats
6
8
 
7
9
  REGIONS = config.legacy_fields.regions
sovereign/utils/timer.py CHANGED
@@ -6,9 +6,11 @@ from typing import Any, Callable, Coroutine, NoReturn
6
6
  from croniter import croniter
7
7
 
8
8
 
9
- def wait_until(dt: datetime) -> float:
10
- now = datetime.now()
11
- sleep_time = (dt - now).total_seconds()
9
+ def wait_until(target_dt: datetime) -> float:
10
+ now = datetime.now(dt.timezone.utc)
11
+ if target_dt.tzinfo is None:
12
+ target_dt = target_dt.replace(tzinfo=dt.timezone.utc)
13
+ sleep_time = (target_dt - now).total_seconds()
12
14
  return sleep_time
13
15
 
14
16
 
@@ -11,3 +11,11 @@ def compute_hash(*args: Any) -> str:
11
11
  zlib.crc32(data) & 0xFFFFFFFF
12
12
  ) # same numeric value across all py versions & platforms
13
13
  return str(version_info)
14
+
15
+
16
+ def compute_hash_int(*args: Any) -> int:
17
+ data: bytes = repr(args).encode()
18
+ version_info = (
19
+ zlib.crc32(data) & 0xFFFFFFFF
20
+ ) # same numeric value across all py versions & platforms
21
+ return version_info
@@ -1,5 +1,6 @@
1
1
  from __future__ import division
2
- from typing import List, Any, Dict, Generator
2
+
3
+ from typing import Any, Dict, Generator, List
3
4
 
4
5
 
5
6
  def _round_to_n(sequence: List[int], n: int = 100) -> List[int]:
File without changes