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
@@ -1,3 +0,0 @@
1
- from sovereign.sources.poller import SourcePoller
2
-
3
- __all__ = ["SourcePoller"]
sovereign/sources/file.py DELETED
@@ -1,21 +0,0 @@
1
- from typing import Any, Dict
2
- from sovereign.sources.lib import Source
3
- from sovereign.config_loader import Loadable
4
-
5
-
6
- class File(Source):
7
- def __init__(self, config: Dict[str, Any], scope: str = "default"):
8
- super(File, self).__init__(config, scope)
9
- try:
10
- self.path = Loadable.from_legacy_fmt(config["path"])
11
- except KeyError:
12
- try:
13
- self.path = Loadable(**config["spec"])
14
- except KeyError:
15
- raise KeyError('File source needs to specify "spec" within config')
16
-
17
- def get(self) -> Any:
18
- """
19
- Uses the file config loader to load the given path
20
- """
21
- return self.path.load()
@@ -1,38 +0,0 @@
1
- """
2
- Inline Source
3
- -------------
4
-
5
- Example configuration (YAML):
6
-
7
- .. code-block:: yaml
8
-
9
- sources:
10
- - type: inline
11
- config:
12
- instances:
13
- - instance_id: my_service
14
- service_clusters:
15
- - P2
16
- parameters:
17
- upstream_address:
18
- - address: service.domain.com
19
- region: us-east-1
20
- plan_id: 7d57270a-0348-58d3-829d-447a98fe98d5
21
- """
22
- from typing import Dict, Any, List
23
- from sovereign.sources.lib import Source
24
-
25
-
26
- class Inline(Source):
27
- def __init__(
28
- self, config: Dict[str, List[Dict[str, Any]]], scope: str = "default"
29
- ) -> None:
30
- super(Inline, self).__init__(config, scope)
31
- try:
32
- self.instances = config["instances"]
33
- except KeyError:
34
- raise KeyError('Inline source config must contain "instances"')
35
-
36
- def get(self) -> List[Dict[str, Any]]:
37
- """Returns the data passed via configuration"""
38
- return self.instances
sovereign/sources/lib.py DELETED
@@ -1,40 +0,0 @@
1
- """
2
- Source abstract class
3
- ---------------------
4
- This class can be subclassed, installed as an entry point, and then
5
- used via configuration.
6
-
7
- `todo entry point install guide`
8
- """
9
- import abc
10
- from typing import Any, Dict, List
11
-
12
-
13
- class Source(abc.ABC):
14
- def __init__(self, config: Dict[str, Any], scope: str) -> None:
15
- """
16
- :param config: arbitrary configuration which can be used by the subclass
17
- """
18
- self.config = config
19
- self.scope = scope
20
-
21
- def setup(self) -> None:
22
- """
23
- Optional method which is invoked prior to the Source running self.get()
24
- """
25
- return None
26
-
27
- @abc.abstractmethod
28
- def get(self) -> List[Dict[str, Any]]:
29
- """
30
- Required method to retrieve data from an arbitrary source
31
- """
32
- raise NotImplementedError
33
-
34
-
35
- class SourceImplementation(Source):
36
- def __call__(self, config: Dict[str, Any], scope: str) -> "SourceImplementation":
37
- return self
38
-
39
- def get(self) -> List[Dict[str, Any]]:
40
- return []
@@ -1,294 +0,0 @@
1
- import asyncio
2
- import traceback
3
- from copy import deepcopy
4
- from importlib.metadata import EntryPoint
5
- from datetime import timedelta, datetime
6
- from typing import Iterable, Any, Dict, List, Union, Type, Optional
7
-
8
- from glom import glom, PathAccessError
9
-
10
- from sovereign.utils.entry_point_loader import EntryPointLoader
11
- from sovereign.sources.lib import Source
12
- from sovereign.modifiers.lib import Modifier, GlobalModifier
13
- from sovereign.schemas import (
14
- ConfiguredSource,
15
- SourceData,
16
- Node,
17
- )
18
-
19
-
20
- def is_debug_request(v: str, debug: bool = False) -> bool:
21
- return v == "" and debug
22
-
23
-
24
- def is_wildcard(v: List[str]) -> bool:
25
- return v in [["*"], "*", ("*",)]
26
-
27
-
28
- def contains(container: Iterable[Any], item: Any) -> bool:
29
- return item in container
30
-
31
-
32
- Mods = Dict[str, Type[Modifier]]
33
- GMods = Dict[str, Type[GlobalModifier]]
34
-
35
-
36
- class SourcePoller:
37
- def __init__(
38
- self,
39
- sources: List[ConfiguredSource],
40
- matching_enabled: bool,
41
- node_match_key: str,
42
- source_match_key: str,
43
- source_refresh_rate: int,
44
- logger: Any,
45
- stats: Any,
46
- ):
47
- self.matching_enabled = matching_enabled
48
- self.node_match_key = node_match_key
49
- self.source_match_key = source_match_key
50
- self.source_refresh_rate = source_refresh_rate
51
- self.logger = logger
52
- self.stats = stats
53
-
54
- self.entry_points = EntryPointLoader("sources", "modifiers", "global_modifiers")
55
-
56
- self.source_classes: Dict[str, Type[Source]] = {
57
- e.name: e.load() for e in self.entry_points.groups["sources"]
58
- }
59
- self.sources = [self.setup_source(s) for s in sources]
60
- if not self.sources:
61
- raise RuntimeError("No data sources available!")
62
-
63
- # These have to be loaded later to avoid circular imports
64
- self.modifiers: Mods = dict()
65
- self.global_modifiers: GMods = dict()
66
-
67
- # initially set data and modify
68
- self.source_data = self.refresh()
69
- self.last_updated = datetime.now()
70
- self.instance_count = 0
71
-
72
- @property
73
- def data_is_stale(self) -> bool:
74
- return self.last_updated < datetime.now() - timedelta(minutes=2)
75
-
76
- def setup_source(self, configured_source: ConfiguredSource) -> Source:
77
- source_class = self.source_classes[configured_source.type]
78
- source = source_class(
79
- config=configured_source.config,
80
- scope=configured_source.scope,
81
- )
82
- source.setup()
83
- return source
84
-
85
- def lazy_load_modifiers(self, modifiers: List[str]) -> None:
86
- if len(self.modifiers) == len(modifiers):
87
- return
88
- self.modifiers = self.load_modifier_entrypoints(
89
- self.entry_points.groups["modifiers"], modifiers
90
- )
91
-
92
- def lazy_load_global_modifiers(self, global_modifiers: List[str]) -> None:
93
- if len(self.global_modifiers) == len(global_modifiers):
94
- return
95
- self.global_modifiers = self.load_global_modifier_entrypoints(
96
- self.entry_points.groups["global_modifiers"], global_modifiers
97
- )
98
-
99
- def load_modifier_entrypoints(
100
- self, entry_points: Iterable[EntryPoint], configured_modifiers: List[str]
101
- ) -> Dict[str, Type[Modifier]]:
102
- ret = dict()
103
- for entry_point in entry_points:
104
- if entry_point.name in configured_modifiers:
105
- self.logger(event=f"Loading modifier {entry_point.name}")
106
- ret[entry_point.name] = entry_point.load()
107
- loaded = len(ret)
108
- configured = len(configured_modifiers)
109
- assert loaded == configured, (
110
- f"Number of modifiers loaded ({loaded})"
111
- f"differ from configured: {configured_modifiers}"
112
- )
113
- return ret
114
-
115
- def load_global_modifier_entrypoints(
116
- self, entry_points: Iterable[EntryPoint], configured_modifiers: List[str]
117
- ) -> Dict[str, Type[GlobalModifier]]:
118
- ret = dict()
119
- for entry_point in entry_points:
120
- if entry_point.name in configured_modifiers:
121
- self.logger(event=f"Loading global modifier {entry_point.name}")
122
- ret[entry_point.name] = entry_point.load()
123
-
124
- loaded = len(ret)
125
- configured = len(configured_modifiers)
126
- assert loaded == configured, (
127
- f"Number of global modifiers loaded ({loaded})"
128
- f"differ from configured: {configured_modifiers}"
129
- )
130
- return ret
131
-
132
- def apply_modifications(self, data: Optional[SourceData]) -> SourceData:
133
- with self.stats.timed("modifiers.apply_ms"):
134
- if data is None:
135
- data = self.source_data
136
-
137
- source_data = deepcopy(data)
138
- for scope, instances in source_data.scopes.items():
139
- for g in self.global_modifiers.values():
140
- global_modifier = g(instances)
141
- global_modifier.apply()
142
- source_data.scopes[scope] = global_modifier.join()
143
-
144
- for instance in source_data.scopes[scope]:
145
- for m in self.modifiers.values():
146
- modifier = m(instance)
147
- if modifier.match():
148
- # Modifies the instance in-place
149
- modifier.apply()
150
- return source_data
151
-
152
- def refresh(self) -> SourceData:
153
- self.stats.increment("sources.attempt")
154
- try:
155
- new = SourceData()
156
- for source in self.sources:
157
- new.scopes[source.scope].extend(source.get())
158
- except Exception as e:
159
- self.logger(
160
- event="Error while refreshing sources",
161
- traceback=[line for line in traceback.format_exc().split("\n")],
162
- error=e.__class__.__name__,
163
- detail=getattr(e, "detail", "-"),
164
- )
165
- self.stats.increment("sources.error")
166
- raise
167
-
168
- # Is the new data the same as what we currently have
169
- if new == getattr(self, "source_data", None):
170
- self.stats.increment("sources.unchanged")
171
- self.last_updated = datetime.now()
172
- return self.source_data
173
- else:
174
- self.stats.increment("sources.refreshed")
175
- self.last_updated = datetime.now()
176
- self.instance_count = len(
177
- [instance for scope in new.scopes.values() for instance in scope]
178
- )
179
- return new
180
-
181
- def extract_node_key(self, node: Union[Node, Dict[Any, Any]]) -> Any:
182
- if "." not in self.node_match_key:
183
- # key is not nested, don't need glom
184
- node_value = getattr(node, self.node_match_key)
185
- else:
186
- try:
187
- node_value = glom(node, self.node_match_key)
188
- except PathAccessError:
189
- raise RuntimeError(
190
- f'Failed to find key "{self.node_match_key}" in discoveryRequest({node}).\n'
191
- f"See the docs for more info: "
192
- f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
193
- )
194
- return node_value
195
-
196
- def extract_source_key(self, source: Dict[Any, Any]) -> Any:
197
- if "." not in self.source_match_key:
198
- # key is not nested, don't need glom
199
- source_value = source[self.source_match_key]
200
- else:
201
- try:
202
- source_value = glom(source, self.source_match_key)
203
- except PathAccessError:
204
- raise RuntimeError(
205
- f'Failed to find key "{self.source_match_key}" in instance({source}).\n'
206
- f"See the docs for more info: "
207
- f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
208
- )
209
- return source_value
210
-
211
- def match_node(
212
- self,
213
- node_value: Any,
214
- modify: bool = True,
215
- ) -> SourceData:
216
- """
217
- Checks a node against all sources, using the node_match_key and source_match_key
218
- to determine if the node should receive the source in its configuration.
219
- """
220
-
221
- if self.data_is_stale:
222
- # Log/emit metric and manually refresh sources.
223
- self.stats.increment("sources.stale")
224
- self.logger(
225
- event="Sources have not been refreshed in 2 minutes",
226
- last_update=self.last_updated,
227
- instance_count=self.instance_count,
228
- )
229
- self.poll()
230
-
231
- ret = SourceData()
232
-
233
- if modify:
234
- if not hasattr(self, "source_data_modified"):
235
- self.poll()
236
- data = self.source_data_modified
237
- else:
238
- data = self.source_data
239
-
240
- for scope, instances in data.scopes.items():
241
- if self.matching_enabled is False:
242
- ret.scopes[scope] = instances
243
- continue
244
-
245
- for instance in instances:
246
- source_value = self.extract_source_key(instance)
247
-
248
- # If a single expression evaluates true, the remaining are not evaluated/executed.
249
- # This saves (a small amount of) computation, which helps when the server starts
250
- # to receive thousands of requests. The list has been ordered descending by what
251
- # we think will more commonly be true.
252
- match = (
253
- contains(source_value, node_value)
254
- or node_value == source_value
255
- or is_wildcard(node_value)
256
- or is_wildcard(source_value)
257
- or is_debug_request(node_value)
258
- )
259
- if match:
260
- ret.scopes[scope].append(instance)
261
- return ret
262
-
263
- @property
264
- def match_keys(self) -> List[str]:
265
- """
266
- Checks for all match keys present in existing sources and adds them to a list
267
-
268
- A dict is used instead of a set because dicts cannot have duplicate keys, and
269
- have ordering since python 3.6
270
- """
271
- ret: Dict[str, None] = dict()
272
- ret["*"] = None
273
- for _, instances in self.source_data.scopes.items():
274
- if self.matching_enabled is False:
275
- break
276
- for instance in instances:
277
- source_value = glom(instance, self.source_match_key)
278
- if isinstance(source_value, str):
279
- ret[source_value] = None
280
- elif isinstance(source_value, Iterable):
281
- for item in source_value:
282
- ret[item] = None
283
- continue
284
- ret[source_value] = None
285
- return list(ret.keys())
286
-
287
- def poll(self) -> None:
288
- self.source_data = self.refresh()
289
- self.source_data_modified = self.apply_modifications(self.source_data)
290
-
291
- async def poll_forever(self) -> None:
292
- while True:
293
- self.poll()
294
- await asyncio.sleep(self.source_refresh_rate)
@@ -1,27 +0,0 @@
1
- @charset "utf-8";
2
-
3
- // Import a Google Font
4
- @import url('https://fonts.googleapis.com/css?family=Nunito:400,700');
5
-
6
- // Update Bulma's global variables
7
- $family-sans-serif: "Nunito", sans-serif;
8
- $primary: #433fca;
9
- $link: #6c90ec;
10
-
11
- // Force the navbar menu to not collapse
12
- $desktop: 1px;
13
- $body-background-color: rgb(250, 250, 250);
14
-
15
- // Import only what you need from Bulma
16
- @import "../../../../node_modules/bulma/sass/base/_all.sass";
17
- @import "../../../../node_modules/bulma/sass/form/_all.sass";
18
- @import "../../../../node_modules/bulma/sass/utilities/_all.sass";
19
- @import "../../../../node_modules/bulma/sass/helpers/_all.sass";
20
- @import "../../../../node_modules/bulma/sass/elements/button.sass";
21
- @import "../../../../node_modules/bulma/sass/elements/title.sass";
22
- @import "../../../../node_modules/bulma/sass/elements/content.sass";
23
- @import "../../../../node_modules/bulma/sass/components/navbar.sass";
24
- @import "../../../../node_modules/bulma/sass/components/dropdown.sass";
25
- @import "../../../../node_modules/bulma/sass/components/panel.sass";
26
- @import "../../../../node_modules/bulma/sass/grid/columns.sass";
27
- @import "../../../../node_modules/bulma/bulma.sass";