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,66 +1,82 @@
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
- <meta name="last_update" content="{{ last_update }}">
7
- {{ filter }}
5
+ <link rel="preconnect" href="https://fonts.googleapis.com">
6
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
7
+ <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
8
+ <link rel="stylesheet" href="/static/resources.css">
9
+ <script src="/static/resources.js"></script>
10
+ <script src="/static/panel.js"></script>
8
11
  {% endblock %}
9
12
 
10
13
 
11
14
  {%- block body %}
12
15
  <div class="content">
16
+ {% if error %}
17
+ <span class="panel-icon">
18
+ <i class="fas fa-arrow-right" aria-hidden="true"></i>
19
+ </span>
20
+ <div class="notification is-danger">
21
+ {{ error }}
22
+ </div>
23
+ {% endif %}
13
24
  <p class="content">
14
- <div class="dropdown is-hoverable">
15
- <div class="dropdown-trigger">
16
- <button class="button is-dark" aria-haspopup="true" aria-controls="dropdown-menu">
17
- <span>Envoy Version</span>
18
- <span class="icon is-small">
19
- <i class="fas fa-angle-down" aria-hidden="true"></i>
20
- </span>
21
- </button>
22
- </div>
23
- <div class="dropdown-menu" id="dropdown-menu" role="menu">
24
- <div class="dropdown-content">
25
- <div class="dropdown-item">
26
- <p>All resources will be formatted for the selected version</p>
25
+ <div class="columns">
26
+ <div class="column is-narrow">
27
+ <div class="dropdown is-hoverable">
28
+ <div class="dropdown-trigger">
29
+ <button class="button is-primary" aria-haspopup="true" aria-controls="dropdown-menu">
30
+ <span>Template Version</span>
31
+ <span class="icon is-small">
32
+ <i class="fas fa-angle-down" aria-hidden="false"></i>
33
+ </span>
34
+ </button>
35
+ </div>
36
+ <div class="dropdown-menu" id="dropdown-menu" role="menu">
37
+ <div class="dropdown-content">
38
+ <div class="dropdown-item">
39
+ <p>All resources will be formatted for the selected version</p>
40
+ </div>
41
+ {% for v in available_versions %}
42
+ <a class="dropdown-item{% if v == version %} is-active{% endif %}"
43
+ href="#" onclick="setEnvoyVersion('{{ v }}'); return false;">
44
+ {{ v.replace('_', '') }}
45
+ </a>
46
+ {% endfor %}
47
+ </div>
27
48
  </div>
28
- {% for v in available_versions %}
29
- <a class="dropdown-item{% if v == version %} is-active{% endif %}"
30
- href="/ui/set-version?version={{ v }}">
31
- {{ v.replace('_', '') }}
32
- </a>
33
- {% endfor %}
34
49
  </div>
35
50
  </div>
36
- </div>
37
- <div class="dropdown is-hoverable">
38
- <div class="dropdown-trigger">
39
- <button class="button is-dark" aria-haspopup="true" aria-controls="dropdown-menu">
40
- <span>Service Cluster</span>
41
- <span class="icon is-small">
42
- <i class="fas fa-angle-down" aria-hidden="true"></i>
43
- </span>
44
- </button>
51
+ <!-- node expression box -->
52
+ <div class="column">
53
+ <form id="filterForm">
54
+ <input id="filterInput" class="input" type="text" placeholder="Node filter expression"/>
55
+ </form>
56
+ <p id="filterMessage" class="help"></p>
45
57
  </div>
46
- <div class="dropdown-menu" id="dropdown-menu" role="menu">
47
- <div class="dropdown-content">
48
- <div class="dropdown-item">
49
- <p>Only resources for the selected service cluster will be shown</p>
50
- </div>
51
- {% for cluster in available_service_clusters %}
52
- <a class="dropdown-item{% if cluster == service_cluster %} is-active{% endif %}"
53
- href="/ui/set-service-cluster?service_cluster={{ cluster }}">
54
- {{ cluster.replace('*', 'any') }}
55
- </a>
56
- {% endfor %}
58
+ <div class="column is-narrow">
59
+ <div class="tooltip">
60
+ <span class="button is-primary">?</span>
61
+ <div class="tooltip-text">
62
+ <p>Space-delimited Node filter.<p>
63
+ Examples:<br>
64
+ <code>id=envoy-1234</code><br>
65
+ <code>cluster=a_service_cluster</code><br>
66
+ <code>locality.zone=us-east-1</code><br>
67
+ <code>locality.sub_zone=a</code><br>
68
+ <code>metadata.field_name=abcdef</code>
69
+ </div>
57
70
  </div>
58
71
  </div>
72
+ </p>
59
73
  </div>
60
- </p>
61
74
  </div>
62
75
 
63
- <nav class="panel is-dark" id="resources">
76
+
77
+ {% set count = resources|length %}
78
+ {% if count > 0 %}
79
+ <nav class="panel is-primary" id="resources">
64
80
  <p class="panel-heading">
65
81
  {{ resource_type|capitalize }}
66
82
  </p>
@@ -71,8 +87,8 @@
71
87
  class="input"
72
88
  type="text"
73
89
  id="search_filter"
74
- onkeyup="filter_results('search_filter', 'resources')"
75
- placeholder="Filter resources by any string"
90
+ onkeyup="filter_results('search_filter')"
91
+ placeholder="Filter {{ resource_type }} by any string"
76
92
  >
77
93
  </label>
78
94
  <span class="icon is-left">
@@ -82,78 +98,163 @@
82
98
  </div>
83
99
 
84
100
  {% set res = resources|selectattr('get')|list %}
85
- {% set count = res|length %}
86
101
  {% set plural = {
87
102
  0: 'resources',
88
103
  1: 'resource'
89
104
  } %}
90
- {% for resource in res %}
91
- {% set name = resource.get('name') or resource['cluster_name'] %}
92
- <a class="panel-block has-text-weight-medium"
93
- href="/ui/resources/{{ resource_type }}/{{ name }}">
94
- <span class="panel-icon">
95
- <i class="fas fa-arrow-right" aria-hidden="true"></i>
96
- </span>
97
- {{ name }}
98
- </a>
99
- {% endfor %}
105
+ {# begin pagination #}
106
+ <div id="resource-container">
107
+ {# Will be filled in #}
108
+ </div>
109
+ {# pagination control bar #}
100
110
  <div class="panel-block">
101
- <p class="content is-small">
111
+ <p class="content is-small" id="resource-count">
102
112
  {{ count }} {{ plural.get(count, 'resources') }}
103
113
  </p>
104
114
  </div>
105
115
  </nav>
116
+ {% if count < 10 %}
117
+ {% set hidden = "display: none;" %}
118
+ {% endif %}
119
+ <div style="{{ hidden }}">
120
+ <nav class="pagination is-right is-small" role="navigation" aria-label="pagination">
121
+ <a id="prev-btn" class="pagination-previous">Previous</a>
122
+ <a id="next-btn" class="pagination-next">Next</a>
123
+ <ul id="page-numbers" class="pagination-list"></ul>
124
+ </nav>
125
+ </div>
106
126
 
107
- {% if resource_type == 'routes' %}
108
- <nav class="panel" id="virtual_hosts">
109
- <p class="panel-heading">
110
- Virtual Hosts
111
- </p>
112
- <div class="panel-block">
127
+ {% if resource_type == 'routes' %}
128
+ <nav class="panel is-primary">
129
+ <p class="panel-heading">Virtual Hosts</p>
130
+ <div class="panel-block">
113
131
  <p class="control has-icons-left">
114
- <label for="search_filter_virtual_hosts">
115
- <input
116
- class="input"
117
- type="text"
118
- id="search_filter_virtual_hosts"
119
- onkeyup="filter_results('search_filter_virtual_hosts', 'virtual_hosts')"
120
- placeholder="Filter resources by any string"
121
- >
122
- </label>
123
- <span class="icon is-left">
124
- <i class="fas fa-search" aria-hidden="true"></i>
125
- </span>
132
+ <input id="searchInput" class="input" type="text" placeholder="Filter virtual-hosts by any string" />
133
+ <span class="icon is-left">
134
+ <i class="fas fa-search" aria-hidden="true"></i>
135
+ </span>
126
136
  </p>
127
- </div>
128
-
129
- {% set vs_count = 0 %}
130
- {% for resource in res %}
131
- {% set vs_len = resource['virtual_hosts']|length %}
132
- {% set vs_count = vs_count + vs_len %}
133
- {% for virtualhost in resource['virtual_hosts'] %}
134
- {% if loop.first and loop.last %}
135
- {# A single virtualhost makes no sense to render. It will be in one of the routes above. #}
136
- {% else %}
137
- <a class="panel-block"
138
- href="/ui/resources/routes/{{ resource['name'] }}/{{ virtualhost['name'] }}">
139
- <span class="panel-icon">
140
- <i class="fas fa-arrow-right" aria-hidden="true"></i>
141
- </span>
142
- {{ virtualhost['name'] }}
143
- </a>
144
- {% endif %}
137
+ </div>
138
+ <p class="panel-tabs">
139
+ <a class="is-active" onclick="filterTabs(this, 'all')">All</a>
140
+ {% for resource in res %}
141
+ <a onclick="filterTabs(this, '{{ resource["name"] }}')">
142
+ {{ resource['name'] }}
143
+ </a>
145
144
  {% endfor %}
146
- {% endfor %}
147
- <div class="panel-block">
148
- <p class="content is-small">
149
- {{ vs_count }} {{ plural.get(vs_count, 'resources') }}
150
- </p>
151
- </div>
152
- </nav>
145
+ </p>
146
+ {% for resource in res %}
147
+ {% if resource["virtual_hosts"] %}
148
+ {% for virtualhost in resource['virtual_hosts'] %}
149
+ <a class="panel-block virtualhost"
150
+ data-category="{{ resource['name'] }}"
151
+ href="#"
152
+ onclick="loadVirtualHostInSidePanel('{{ resource['name'] }}', '{{ virtualhost['name'] }}'); return false;">
153
+ <p class="tag is-small is-primary">
154
+ {{ resource['name'] }}
155
+ </p>
156
+ <span class="panel-icon">
157
+ <i class="fas fa-arrow-right" aria-hidden="true"></i>
158
+ </span>
159
+ {{ virtualhost['name'] }}
160
+ </a>
161
+ {% endfor %}
162
+ {% else %}
163
+ <span class="panel-icon">
164
+ <i class="fas fa-arrow-right" aria-hidden="true"></i>
165
+ </span>
166
+ <div class="notification is-danger">
167
+ No resources found
168
+ <script>
169
+ setTimeout(() => location.reload(), 6000);
170
+ </script>
171
+ </div>
172
+ {% endif %}
173
+ {% endfor %}
174
+ </nav>
175
+ {% endif %}
176
+ {% else %}
177
+ <span class="panel-icon">
178
+ <i class="fas fa-arrow-right" aria-hidden="true"></i>
179
+ </span>
180
+ <div class="notification is-danger">
181
+ No resources found
182
+ <script>
183
+ setTimeout(() => location.reload(), 2000);
184
+ </script>
185
+ </div>
186
+ {% endif %}
187
+ {% if show_debuginfo %}
188
+ <pre>
189
+ {%- if discovery_request != None %}
190
+ ---
191
+ DiscoveryRequest:
192
+ Node:
193
+ Id: {{ discovery_request.node.id }}
194
+ Cluster: {{ discovery_request.node.cluster }}
195
+ Metadata: {{ discovery_request.node.metadata }}
196
+ Locality: {{ discovery_request.node.locality }}
197
+ Build Version: {{ discovery_request.node.build_version }}
198
+ Version Info: {{ discovery_request.version_info }}
199
+ Resource Names: {{ discovery_request.resources or "*" }}
200
+ # Sovereign-generated fields
201
+ Envoy Version: {{ discovery_request.envoy_version }}
202
+ Host Header: {{ discovery_request.desired_controlplane }}
203
+ {%- endif %}
204
+ {%- if discovery_response != None %}
205
+ ---
206
+ DiscoveryResponse:
207
+ Config version: {{ discovery_response.version }}
208
+ {%- endif %}
209
+ </pre>
153
210
  {% endif %}
211
+
212
+ <!-- Side Panel Backdrop -->
213
+ <div id="sidePanelBackdrop" class="side-panel-backdrop"></div>
214
+
215
+ <!-- Side Panel -->
216
+ <div id="sidePanel" class="side-panel has-background-dark has-text-white">
217
+ <div class="hero is-primary is-small">
218
+ <div class="hero-body p-4">
219
+ <div class="level">
220
+ <div class="level-left">
221
+ <h4 class="title is-4 has-text-white mb-0">
222
+ <span id="sidePanelTitle">Resource Details</span>
223
+ </h4>
224
+ </div>
225
+ <div class="level-right">
226
+ <button class="delete is-large" onclick="closeSidePanel()"></button>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ <div class="side-panel-content">
232
+ <div id="sidePanelContent">
233
+ <div class="content has-text-centered p-6">
234
+ <p>Select a resource to view its details.</p>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ </div>
154
239
  {% endblock -%}
155
240
  {% block footer %}
156
241
  <div class="content has-text-centered is-small">
157
- <p>{{ last_update }}</p>
242
+ <script src="/static/node_expression.js"></script>
243
+ <script>
244
+ document.addEventListener('DOMContentLoaded', function() {
245
+ const resourceNames = [
246
+ {% for resource in resources %}
247
+ "{{ resource.name or resource.cluster_name or 'Unknown' }}"{% if not loop.last %},{% endif %}
248
+ {% endfor %}
249
+ ];
250
+ if (typeof initializeResources === 'function') {
251
+ initializeResources(resourceNames, '{{ resource_type }}');
252
+ }
253
+ });
254
+ function setEnvoyVersion(version) {
255
+ document.cookie = `envoy_version=${version}; path=/ui/resources/; max-age=31536000`;
256
+ window.location.reload();
257
+ }
258
+ </script>
158
259
  </div>
159
260
  {% endblock %}
@@ -0,0 +1,9 @@
1
+ from typing import Any
2
+
3
+ from sovereign.dynamic_config.loaders import CustomLoader
4
+
5
+
6
+ class Multiply(CustomLoader):
7
+ def load(self, path: str) -> Any:
8
+ result = path * 2
9
+ return result
@@ -1,4 +1,3 @@
1
- from sovereign import template_context
2
1
  from sovereign.modifiers.lib import Modifier
3
2
  from sovereign.utils import eds, templates
4
3
 
@@ -8,7 +7,6 @@ class Test(Modifier):
8
7
  return True
9
8
 
10
9
  def apply(self) -> None:
11
- assert template_context
12
10
  assert eds
13
11
  assert templates
14
12
  self.instance["modifier_test_executed"] = True
sovereign/tracing.py ADDED
@@ -0,0 +1,103 @@
1
+ import time
2
+ import uuid
3
+ from contextlib import nullcontext
4
+ from contextvars import ContextVar
5
+ from typing import Any, TypedDict
6
+
7
+ import requests
8
+ from typing_extensions import NotRequired
9
+
10
+ from sovereign import config
11
+ from sovereign.configuration import TracingConfig
12
+
13
+ _trace_id_ctx_var: ContextVar[str] = ContextVar("trace_id", default="")
14
+ _span_id_ctx_var: ContextVar[str] = ContextVar("span_id", default="")
15
+
16
+
17
+ def get_trace_id() -> str:
18
+ return _trace_id_ctx_var.get()
19
+
20
+
21
+ def get_span_id() -> str:
22
+ return _span_id_ctx_var.get()
23
+
24
+
25
+ def generate_128bit():
26
+ return str(uuid.uuid4()).replace("-", "")
27
+
28
+
29
+ def generate_64bit():
30
+ return generate_128bit()[:32]
31
+
32
+
33
+ def timestamp():
34
+ return str(time.time()).replace(".", "")
35
+
36
+
37
+ TRACING = config.tracing
38
+ TRACING_DISABLED = not getattr(TRACING, "enabled", False)
39
+
40
+
41
+ class Trace(TypedDict):
42
+ traceId: str
43
+ id: str
44
+ name: str
45
+ timestamp: float
46
+ tags: dict[str, Any]
47
+ duration: NotRequired[float]
48
+ parentSpanId: NotRequired[str]
49
+
50
+
51
+ class Tracer:
52
+ def __init__(self, span_name):
53
+ if TRACING_DISABLED:
54
+ return
55
+
56
+ self.tracing: TracingConfig = TRACING
57
+ span_id = get_span_id()
58
+ self.parent_span_id = None
59
+ if span_id != "":
60
+ # We are already inside a trace context
61
+ self.parent_span_id = span_id
62
+ self.trace_id = get_trace_id()
63
+ self.span_id = self.gen_id()
64
+ self.span_name = span_name
65
+
66
+ def gen_id(self):
67
+ if self.tracing.trace_id_128bit:
68
+ trace_id = generate_128bit()
69
+ else:
70
+ trace_id = generate_64bit()
71
+ _trace_id_ctx_var.set(trace_id)
72
+ return trace_id
73
+
74
+ def __enter__(self):
75
+ if TRACING_DISABLED:
76
+ return nullcontext()
77
+ self.trace = Trace(
78
+ {
79
+ "traceId": self.trace_id,
80
+ "id": self.span_id,
81
+ "name": self.span_name,
82
+ "timestamp": time.time(),
83
+ "tags": self.tracing.tags,
84
+ }
85
+ )
86
+ if self.parent_span_id:
87
+ self.trace["parentSpanId"] = self.parent_span_id
88
+ return self
89
+
90
+ def __exit__(self, exc_type, exc_value, traceback):
91
+ if TRACING_DISABLED:
92
+ return
93
+ self.trace["duration"] = time.time() - self.trace["timestamp"]
94
+ self.submit()
95
+
96
+ def submit(self):
97
+ print(f"{self.span_name}: {self.trace['duration']}")
98
+ try:
99
+ url = f"{self.tracing.collector}{self.tracing.endpoint}"
100
+ requests.post(url, json=self.trace)
101
+ # pylint: disable=broad-except
102
+ except Exception as e:
103
+ print(f"Failed to submit trace: {self.trace}, Error:{e}")