sovereign 0.14.2__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 +17 -78
  2. sovereign/app.py +74 -59
  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 +271 -100
  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 +61 -0
  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 +123 -28
  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 +18 -0
  45. sovereign/utils/mock.py +60 -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 +52 -67
  67. sovereign/views/healthchecks.py +107 -20
  68. sovereign/views/interface.py +173 -117
  69. sovereign/worker.py +193 -0
  70. {sovereign-0.14.2.dist-info → sovereign-1.0.0a4.dist-info}/METADATA +81 -73
  71. sovereign-1.0.0a4.dist-info/RECORD +85 -0
  72. {sovereign-0.14.2.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 -715
  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 -298
  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 -64
  96. sovereign/views/admin.py +0 -120
  97. sovereign-0.14.2.dist-info/LICENSE.txt +0 -13
  98. sovereign-0.14.2.dist-info/RECORD +0 -45
  99. sovereign-0.14.2.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,298 +0,0 @@
1
- import asyncio
2
- import traceback
3
- from copy import deepcopy
4
- from datetime import timedelta, datetime
5
- from pkg_resources import iter_entry_points, EntryPoint
6
- from typing import Iterable, Any, Dict, List, Union, Type, Optional
7
-
8
- from glom import glom, PathAccessError
9
-
10
- from sovereign.sources.lib import Source
11
- from sovereign.modifiers.lib import Modifier, GlobalModifier
12
- from sovereign.schemas import (
13
- ConfiguredSource,
14
- SourceData,
15
- Node,
16
- )
17
-
18
-
19
- def is_debug_request(v: str, debug: bool = False) -> bool:
20
- return v == "" and debug
21
-
22
-
23
- def is_wildcard(v: List[str]) -> bool:
24
- return v in [["*"], "*", ("*",)]
25
-
26
-
27
- def contains(container: Iterable[Any], item: Any) -> bool:
28
- return item in container
29
-
30
-
31
- Mods = Dict[str, Type[Modifier]]
32
- GMods = Dict[str, Type[GlobalModifier]]
33
-
34
-
35
- class SourcePoller:
36
- def __init__(
37
- self,
38
- sources: List[ConfiguredSource],
39
- matching_enabled: bool,
40
- node_match_key: str,
41
- source_match_key: str,
42
- source_refresh_rate: int,
43
- logger: Any,
44
- stats: Any,
45
- ):
46
- self.matching_enabled = matching_enabled
47
- self.node_match_key = node_match_key
48
- self.source_match_key = source_match_key
49
- self.source_refresh_rate = source_refresh_rate
50
- self.logger = logger
51
- self.stats = stats
52
-
53
- self.entry_points: Dict[str, List[EntryPoint]] = dict()
54
- for entry_point in ("sources", "modifiers", "global_modifiers"):
55
- self.entry_points[entry_point] = list(
56
- iter_entry_points(f"sovereign.{entry_point}")
57
- )
58
-
59
- self.source_classes: Dict[str, Type[Source]] = {
60
- e.name: e.load() for e in self.entry_points["sources"]
61
- }
62
- self.sources = [self.setup_source(s) for s in sources]
63
- if not self.sources:
64
- raise RuntimeError("No data sources available!")
65
-
66
- # These have to be loaded later to avoid circular imports
67
- self.modifiers: Mods = dict()
68
- self.global_modifiers: GMods = dict()
69
-
70
- # initially set data and modify
71
- self.source_data = self.refresh()
72
- self.last_updated = datetime.now()
73
- self.instance_count = 0
74
-
75
- @property
76
- def data_is_stale(self) -> bool:
77
- return self.last_updated < datetime.now() - timedelta(minutes=2)
78
-
79
- def setup_source(self, configured_source: ConfiguredSource) -> Source:
80
- source_class = self.source_classes[configured_source.type]
81
- source = source_class(
82
- config=configured_source.config,
83
- scope=configured_source.scope,
84
- )
85
- source.setup()
86
- return source
87
-
88
- def lazy_load_modifiers(self, modifiers: List[str]) -> None:
89
- if len(self.modifiers) == len(modifiers):
90
- return
91
- self.modifiers = self.load_modifier_entrypoints(
92
- self.entry_points["modifiers"], modifiers
93
- )
94
-
95
- def lazy_load_global_modifiers(self, global_modifiers: List[str]) -> None:
96
- if len(self.global_modifiers) == len(global_modifiers):
97
- return
98
- self.global_modifiers = self.load_global_modifier_entrypoints(
99
- self.entry_points["global_modifiers"], global_modifiers
100
- )
101
-
102
- def load_modifier_entrypoints(
103
- self, entry_points: Iterable[EntryPoint], configured_modifiers: List[str]
104
- ) -> Dict[str, Type[Modifier]]:
105
- ret = dict()
106
- for entry_point in entry_points:
107
- if entry_point.name in configured_modifiers:
108
- self.logger(event=f"Loading modifier {entry_point.name}")
109
- ret[entry_point.name] = entry_point.load()
110
- loaded = len(ret)
111
- configured = len(configured_modifiers)
112
- assert loaded == configured, (
113
- f"Number of modifiers loaded ({loaded})"
114
- f"differ from configured: {configured_modifiers}"
115
- )
116
- return ret
117
-
118
- def load_global_modifier_entrypoints(
119
- self, entry_points: Iterable[EntryPoint], configured_modifiers: List[str]
120
- ) -> Dict[str, Type[GlobalModifier]]:
121
- ret = dict()
122
- for entry_point in entry_points:
123
- if entry_point.name in configured_modifiers:
124
- self.logger(event=f"Loading global modifier {entry_point.name}")
125
- ret[entry_point.name] = entry_point.load()
126
-
127
- loaded = len(ret)
128
- configured = len(configured_modifiers)
129
- assert loaded == configured, (
130
- f"Number of global modifiers loaded ({loaded})"
131
- f"differ from configured: {configured_modifiers}"
132
- )
133
- return ret
134
-
135
- def apply_modifications(self, data: Optional[SourceData]) -> SourceData:
136
- with self.stats.timed("modifiers.apply_ms"):
137
- if data is None:
138
- data = self.source_data
139
-
140
- source_data = deepcopy(data)
141
- for scope, instances in source_data.scopes.items():
142
- for g in self.global_modifiers.values():
143
- global_modifier = g(instances)
144
- global_modifier.apply()
145
- source_data.scopes[scope] = global_modifier.join()
146
-
147
- for instance in source_data.scopes[scope]:
148
- for m in self.modifiers.values():
149
- modifier = m(instance)
150
- if modifier.match():
151
- # Modifies the instance in-place
152
- modifier.apply()
153
- return source_data
154
-
155
- def refresh(self) -> SourceData:
156
- self.stats.increment("sources.attempt")
157
- try:
158
- new = SourceData()
159
- for source in self.sources:
160
- new.scopes[source.scope].extend(source.get())
161
- except Exception as e:
162
- self.logger(
163
- event="Error while refreshing sources",
164
- traceback=[line for line in traceback.format_exc().split("\n")],
165
- error=e.__class__.__name__,
166
- detail=getattr(e, "detail", "-"),
167
- )
168
- self.stats.increment("sources.error")
169
- raise
170
-
171
- # Is the new data the same as what we currently have
172
- if new == getattr(self, "source_data", None):
173
- self.stats.increment("sources.unchanged")
174
- self.last_updated = datetime.now()
175
- return self.source_data
176
- else:
177
- self.stats.increment("sources.refreshed")
178
- self.last_updated = datetime.now()
179
- self.instance_count = len(
180
- [instance for scope in new.scopes.values() for instance in scope]
181
- )
182
- return new
183
-
184
- def extract_node_key(self, node: Union[Node, Dict[Any, Any]]) -> Any:
185
- if "." not in self.node_match_key:
186
- # key is not nested, don't need glom
187
- node_value = getattr(node, self.node_match_key)
188
- else:
189
- try:
190
- node_value = glom(node, self.node_match_key)
191
- except PathAccessError:
192
- raise RuntimeError(
193
- f'Failed to find key "{self.node_match_key}" in discoveryRequest({node}).\n'
194
- f"See the docs for more info: "
195
- f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
196
- )
197
- return node_value
198
-
199
- def extract_source_key(self, source: Dict[Any, Any]) -> Any:
200
- if "." not in self.source_match_key:
201
- # key is not nested, don't need glom
202
- source_value = source[self.source_match_key]
203
- else:
204
- try:
205
- source_value = glom(source, self.source_match_key)
206
- except PathAccessError:
207
- raise RuntimeError(
208
- f'Failed to find key "{self.source_match_key}" in instance({source}).\n'
209
- f"See the docs for more info: "
210
- f"https://vsyrakis.bitbucket.io/sovereign/docs/html/guides/node_matching.html"
211
- )
212
- return source_value
213
-
214
- def match_node(
215
- self,
216
- node_value: Any,
217
- modify: bool = True,
218
- ) -> SourceData:
219
- """
220
- Checks a node against all sources, using the node_match_key and source_match_key
221
- to determine if the node should receive the source in its configuration.
222
- """
223
-
224
- if self.data_is_stale:
225
- # Log/emit metric and manually refresh sources.
226
- self.stats.increment("sources.stale")
227
- self.logger(
228
- event="Sources have not been refreshed in 2 minutes",
229
- last_update=self.last_updated,
230
- instance_count=self.instance_count,
231
- )
232
- self.poll()
233
-
234
- ret = SourceData()
235
-
236
- if modify:
237
- if not hasattr(self, "source_data_modified"):
238
- self.poll()
239
- data = self.source_data_modified
240
- else:
241
- data = self.source_data
242
-
243
- for scope, instances in data.scopes.items():
244
- if self.matching_enabled is False:
245
- ret.scopes[scope] = instances
246
- continue
247
-
248
- for instance in instances:
249
- source_value = self.extract_source_key(instance)
250
-
251
- # If a single expression evaluates true, the remaining are not evaluated/executed.
252
- # This saves (a small amount of) computation, which helps when the server starts
253
- # to receive thousands of requests. The list has been ordered descending by what
254
- # we think will more commonly be true.
255
- match = (
256
- contains(source_value, node_value)
257
- or node_value == source_value
258
- or is_wildcard(node_value)
259
- or is_wildcard(source_value)
260
- or is_debug_request(node_value)
261
- )
262
- if match:
263
- ret.scopes[scope].append(instance)
264
- return ret
265
-
266
- @property
267
- def match_keys(self) -> List[str]:
268
- """
269
- Checks for all match keys present in existing sources and adds them to a list
270
-
271
- A dict is used instead of a set because dicts cannot have duplicate keys, and
272
- have ordering since python 3.6
273
- """
274
- ret: Dict[str, None] = dict()
275
- ret["*"] = None
276
- for _, instances in self.source_data.scopes.items():
277
- if self.matching_enabled is False:
278
-
279
- break
280
- for instance in instances:
281
- source_value = glom(instance, self.source_match_key)
282
- if isinstance(source_value, str):
283
- ret[source_value] = None
284
- elif isinstance(source_value, Iterable):
285
- for item in source_value:
286
- ret[item] = None
287
- continue
288
- ret[source_value] = None
289
- return list(ret.keys())
290
-
291
- def poll(self) -> None:
292
- self.source_data = self.refresh()
293
- self.source_data_modified = self.apply_modifications(self.source_data)
294
-
295
- async def poll_forever(self) -> None:
296
- while True:
297
- self.poll()
298
- 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";