mrok 0.5.0__py3-none-any.whl → 0.6.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.
- mrok/agent/devtools/inspector/app.py +2 -2
- mrok/agent/sidecar/app.py +9 -9
- mrok/agent/sidecar/main.py +31 -5
- mrok/agent/ziticorn.py +8 -2
- mrok/cli/commands/admin/bootstrap.py +3 -2
- mrok/cli/commands/admin/utils.py +2 -2
- mrok/cli/commands/agent/run/sidecar.py +59 -1
- mrok/cli/commands/frontend/run.py +43 -1
- mrok/controller/schemas.py +2 -2
- mrok/frontend/app.py +8 -8
- mrok/frontend/main.py +9 -1
- mrok/proxy/app.py +10 -9
- mrok/proxy/asgi.py +96 -0
- mrok/proxy/backend.py +5 -3
- mrok/proxy/event_publisher.py +66 -0
- mrok/proxy/master.py +18 -60
- mrok/proxy/metrics.py +2 -2
- mrok/proxy/{middlewares.py → middleware.py} +5 -35
- mrok/proxy/{datastructures.py → models.py} +8 -8
- mrok/proxy/{streams.py → stream.py} +24 -1
- mrok/proxy/worker.py +64 -0
- mrok/proxy/{config.py → ziticorn.py} +29 -6
- mrok/types/__init__.py +0 -0
- mrok/{proxy/types.py → types/proxy.py} +7 -2
- mrok/types/ziti.py +1 -0
- mrok/ziti/api.py +15 -18
- mrok/ziti/bootstrap.py +3 -2
- mrok/ziti/identities.py +5 -4
- mrok/ziti/services.py +3 -2
- {mrok-0.5.0.dist-info → mrok-0.6.0.dist-info}/METADATA +1 -1
- {mrok-0.5.0.dist-info → mrok-0.6.0.dist-info}/RECORD +34 -32
- mrok/proxy/lifespan.py +0 -10
- mrok/proxy/protocol.py +0 -11
- mrok/proxy/server.py +0 -14
- {mrok-0.5.0.dist-info → mrok-0.6.0.dist-info}/WHEEL +0 -0
- {mrok-0.5.0.dist-info → mrok-0.6.0.dist-info}/entry_points.txt +0 -0
- {mrok-0.5.0.dist-info → mrok-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
mrok/proxy/master.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import contextlib
|
|
3
1
|
import logging
|
|
4
2
|
import os
|
|
5
3
|
import signal
|
|
@@ -10,20 +8,14 @@ from pathlib import Path
|
|
|
10
8
|
from uuid import uuid4
|
|
11
9
|
|
|
12
10
|
import zmq
|
|
13
|
-
import zmq.asyncio
|
|
14
|
-
from uvicorn.importer import import_from_string
|
|
15
11
|
from watchfiles import watch
|
|
16
12
|
from watchfiles.filters import PythonFilter
|
|
17
13
|
from watchfiles.run import CombinedProcess, start_process
|
|
18
14
|
|
|
19
15
|
from mrok.conf import get_settings
|
|
20
16
|
from mrok.logging import setup_logging
|
|
21
|
-
from mrok.proxy.
|
|
22
|
-
from mrok.proxy
|
|
23
|
-
from mrok.proxy.metrics import WorkerMetricsCollector
|
|
24
|
-
from mrok.proxy.middlewares import CaptureMiddleware, LifespanMiddleware, MetricsMiddleware
|
|
25
|
-
from mrok.proxy.server import MrokServer
|
|
26
|
-
from mrok.proxy.types import ASGIApp
|
|
17
|
+
from mrok.proxy.worker import Worker
|
|
18
|
+
from mrok.types.proxy import ASGIApp
|
|
27
19
|
|
|
28
20
|
logger = logging.getLogger("mrok.agent")
|
|
29
21
|
|
|
@@ -62,76 +54,41 @@ def start_uvicorn_worker(
|
|
|
62
54
|
worker_id: str,
|
|
63
55
|
app: ASGIApp | str,
|
|
64
56
|
identity_file: str,
|
|
57
|
+
events_enabled: bool,
|
|
65
58
|
events_pub_port: int,
|
|
66
59
|
metrics_interval: float = 5.0,
|
|
67
60
|
):
|
|
68
61
|
import sys
|
|
69
62
|
|
|
70
63
|
sys.path.insert(0, os.getcwd())
|
|
71
|
-
asgi_app = app if not isinstance(app, str) else import_from_string(app)
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
pub = ctx.socket(zmq.PUB)
|
|
77
|
-
pub.connect(f"tcp://localhost:{events_pub_port}")
|
|
78
|
-
metrics = WorkerMetricsCollector(worker_id)
|
|
79
|
-
|
|
80
|
-
task = None
|
|
81
|
-
|
|
82
|
-
async def status_sender(): # pragma: no cover
|
|
83
|
-
while True:
|
|
84
|
-
snap = await metrics.snapshot()
|
|
85
|
-
event = Event(type="status", data=Status(meta=identity.mrok, metrics=snap))
|
|
86
|
-
await pub.send_string(event.model_dump_json())
|
|
87
|
-
await asyncio.sleep(metrics_interval)
|
|
88
|
-
|
|
89
|
-
async def on_startup(): # pragma: no cover
|
|
90
|
-
nonlocal task
|
|
91
|
-
await asyncio.sleep(0)
|
|
92
|
-
task = asyncio.create_task(status_sender())
|
|
93
|
-
|
|
94
|
-
async def on_shutdown(): # pragma: no cover
|
|
95
|
-
await asyncio.sleep(0)
|
|
96
|
-
if task:
|
|
97
|
-
task.cancel()
|
|
98
|
-
|
|
99
|
-
async def on_response_complete(response: HTTPResponse): # pragma: no cover
|
|
100
|
-
event = Event(type="response", data=response)
|
|
101
|
-
await pub.send_string(event.model_dump_json())
|
|
102
|
-
|
|
103
|
-
config = MrokBackendConfig(
|
|
104
|
-
LifespanMiddleware(
|
|
105
|
-
MetricsMiddleware(
|
|
106
|
-
CaptureMiddleware(
|
|
107
|
-
asgi_app,
|
|
108
|
-
on_response_complete,
|
|
109
|
-
),
|
|
110
|
-
metrics,
|
|
111
|
-
),
|
|
112
|
-
on_startup=on_startup,
|
|
113
|
-
on_shutdown=on_shutdown,
|
|
114
|
-
),
|
|
65
|
+
worker = Worker(
|
|
66
|
+
worker_id,
|
|
67
|
+
app,
|
|
115
68
|
identity_file,
|
|
69
|
+
events_enabled=events_enabled,
|
|
70
|
+
event_publisher_port=events_pub_port,
|
|
71
|
+
metrics_interval=metrics_interval,
|
|
116
72
|
)
|
|
117
|
-
|
|
118
|
-
with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
|
|
119
|
-
server.run()
|
|
73
|
+
worker.run()
|
|
120
74
|
|
|
121
75
|
|
|
122
76
|
class MasterBase(ABC):
|
|
123
77
|
def __init__(
|
|
124
78
|
self,
|
|
125
79
|
identity_file: str,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
80
|
+
*,
|
|
81
|
+
workers: int = 4,
|
|
82
|
+
reload: bool = False,
|
|
83
|
+
events_enabled: bool = True,
|
|
84
|
+
events_pub_port: int = 50000,
|
|
85
|
+
events_sub_port: int = 50001,
|
|
130
86
|
metrics_interval: float = 5.0,
|
|
131
87
|
):
|
|
132
88
|
self.identity_file = identity_file
|
|
133
89
|
self.workers = workers
|
|
134
90
|
self.reload = reload
|
|
91
|
+
self.events_enabled = events_enabled
|
|
135
92
|
self.events_pub_port = events_pub_port
|
|
136
93
|
self.events_sub_port = events_sub_port
|
|
137
94
|
self.metrics_interval = metrics_interval
|
|
@@ -171,6 +128,7 @@ class MasterBase(ABC):
|
|
|
171
128
|
worker_id,
|
|
172
129
|
self.get_asgi_app(),
|
|
173
130
|
self.identity_file,
|
|
131
|
+
self.events_enabled,
|
|
174
132
|
self.events_pub_port,
|
|
175
133
|
self.metrics_interval,
|
|
176
134
|
),
|
mrok/proxy/metrics.py
CHANGED
|
@@ -8,7 +8,7 @@ import time
|
|
|
8
8
|
import psutil
|
|
9
9
|
from hdrh.histogram import HdrHistogram
|
|
10
10
|
|
|
11
|
-
from mrok.proxy.
|
|
11
|
+
from mrok.proxy.models import (
|
|
12
12
|
DataTransferMetrics,
|
|
13
13
|
ProcessMetrics,
|
|
14
14
|
RequestsMetrics,
|
|
@@ -49,7 +49,7 @@ async def get_process_metrics(interval: float = 0.1) -> ProcessMetrics:
|
|
|
49
49
|
return await asyncio.to_thread(_collect_process_usage, interval)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
class
|
|
52
|
+
class MetricsCollector:
|
|
53
53
|
def __init__(self, worker_id: str, lowest=1, highest=60000, sigfigs=3):
|
|
54
54
|
self.worker_id = worker_id
|
|
55
55
|
self.total_requests = 0
|
|
@@ -3,18 +3,17 @@ import logging
|
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
5
|
from mrok.proxy.constants import MAX_REQUEST_BODY_BYTES, MAX_RESPONSE_BODY_BYTES
|
|
6
|
-
from mrok.proxy.
|
|
7
|
-
from mrok.proxy.
|
|
8
|
-
from mrok.proxy.
|
|
6
|
+
from mrok.proxy.metrics import MetricsCollector
|
|
7
|
+
from mrok.proxy.models import FixedSizeByteBuffer, HTTPHeaders, HTTPRequest, HTTPResponse
|
|
8
|
+
from mrok.proxy.utils import must_capture_request, must_capture_response
|
|
9
|
+
from mrok.types.proxy import (
|
|
9
10
|
ASGIApp,
|
|
10
11
|
ASGIReceive,
|
|
11
12
|
ASGISend,
|
|
12
|
-
LifespanCallback,
|
|
13
13
|
Message,
|
|
14
14
|
ResponseCompleteCallback,
|
|
15
15
|
Scope,
|
|
16
16
|
)
|
|
17
|
-
from mrok.proxy.utils import must_capture_request, must_capture_response
|
|
18
17
|
|
|
19
18
|
logger = logging.getLogger("mrok.proxy")
|
|
20
19
|
|
|
@@ -99,7 +98,7 @@ class CaptureMiddleware:
|
|
|
99
98
|
|
|
100
99
|
|
|
101
100
|
class MetricsMiddleware:
|
|
102
|
-
def __init__(self, app: ASGIApp, metrics:
|
|
101
|
+
def __init__(self, app: ASGIApp, metrics: MetricsCollector):
|
|
103
102
|
self.app = app
|
|
104
103
|
self.metrics = metrics
|
|
105
104
|
|
|
@@ -133,32 +132,3 @@ class MetricsMiddleware:
|
|
|
133
132
|
await self.app(scope, wrapped_receive, wrapped_send)
|
|
134
133
|
finally:
|
|
135
134
|
await self.metrics.on_request_end(start_time, status_code)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
class LifespanMiddleware:
|
|
139
|
-
def __init__(
|
|
140
|
-
self,
|
|
141
|
-
app,
|
|
142
|
-
on_startup: LifespanCallback | None = None,
|
|
143
|
-
on_shutdown: LifespanCallback | None = None,
|
|
144
|
-
):
|
|
145
|
-
self.app = app
|
|
146
|
-
self.on_startup = on_startup
|
|
147
|
-
self.on_shutdown = on_shutdown
|
|
148
|
-
|
|
149
|
-
async def __call__(self, scope, receive, send):
|
|
150
|
-
if scope["type"] == "lifespan":
|
|
151
|
-
while True:
|
|
152
|
-
event = await receive()
|
|
153
|
-
if event["type"] == "lifespan.startup":
|
|
154
|
-
if self.on_startup: # pragma: no branch
|
|
155
|
-
await self.on_startup()
|
|
156
|
-
await send({"type": "lifespan.startup.complete"})
|
|
157
|
-
|
|
158
|
-
elif event["type"] == "lifespan.shutdown":
|
|
159
|
-
if self.on_shutdown: # pragma: no branch
|
|
160
|
-
await self.on_shutdown()
|
|
161
|
-
await send({"type": "lifespan.shutdown.complete"})
|
|
162
|
-
break
|
|
163
|
-
else:
|
|
164
|
-
await self.app(scope, receive, send)
|
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
8
8
|
from pydantic_core import core_schema
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
class
|
|
11
|
+
class X509Credentials(BaseModel):
|
|
12
12
|
key: str
|
|
13
13
|
cert: str
|
|
14
14
|
ca: str
|
|
@@ -21,7 +21,7 @@ class ZitiId(BaseModel):
|
|
|
21
21
|
return value
|
|
22
22
|
|
|
23
23
|
|
|
24
|
-
class
|
|
24
|
+
class ServiceMetadata(BaseModel):
|
|
25
25
|
model_config = ConfigDict(extra="ignore")
|
|
26
26
|
identity: str
|
|
27
27
|
extension: str
|
|
@@ -30,21 +30,21 @@ class ZitiMrokMeta(BaseModel):
|
|
|
30
30
|
tags: dict[str, str | bool | None] | None = None
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
class
|
|
33
|
+
class Identity(BaseModel):
|
|
34
34
|
model_config = ConfigDict(extra="ignore")
|
|
35
35
|
zt_api: str = Field(validation_alias="ztAPI")
|
|
36
|
-
id:
|
|
36
|
+
id: X509Credentials
|
|
37
37
|
zt_apis: str | None = Field(default=None, validation_alias="ztAPIs")
|
|
38
38
|
config_types: str | None = Field(default=None, validation_alias="configTypes")
|
|
39
39
|
enable_ha: bool = Field(default=False, validation_alias="enableHa")
|
|
40
|
-
mrok:
|
|
40
|
+
mrok: ServiceMetadata | None = None
|
|
41
41
|
|
|
42
42
|
@staticmethod
|
|
43
|
-
def load_from_file(path: str | Path) ->
|
|
43
|
+
def load_from_file(path: str | Path) -> Identity:
|
|
44
44
|
path = Path(path)
|
|
45
45
|
with path.open("r", encoding="utf-8") as f:
|
|
46
46
|
data = json.load(f)
|
|
47
|
-
return
|
|
47
|
+
return Identity.model_validate(data)
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
class FixedSizeByteBuffer:
|
|
@@ -183,7 +183,7 @@ class WorkerMetrics(BaseModel):
|
|
|
183
183
|
|
|
184
184
|
class Status(BaseModel):
|
|
185
185
|
type: Literal["status"] = "status"
|
|
186
|
-
meta:
|
|
186
|
+
meta: ServiceMetadata
|
|
187
187
|
metrics: WorkerMetrics
|
|
188
188
|
|
|
189
189
|
|
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import select
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
2
5
|
|
|
3
6
|
from httpcore import AsyncNetworkStream
|
|
4
7
|
|
|
5
|
-
from mrok.proxy
|
|
8
|
+
from mrok.types.proxy import ASGIReceive
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_readable(sock): # pragma: no cover
|
|
12
|
+
# Stolen from
|
|
13
|
+
# https://github.com/python-trio/trio/blob/20ee2b1b7376db637435d80e266212a35837ddcc/trio/_socket.py#L471C1-L478C31
|
|
14
|
+
|
|
15
|
+
# use select.select on Windows, and select.poll everywhere else
|
|
16
|
+
if sys.platform == "win32":
|
|
17
|
+
rready, _, _ = select.select([sock], [], [], 0)
|
|
18
|
+
return bool(rready)
|
|
19
|
+
p = select.poll()
|
|
20
|
+
p.register(sock, select.POLLIN)
|
|
21
|
+
return bool(p.poll(0))
|
|
6
22
|
|
|
7
23
|
|
|
8
24
|
class AIONetworkStream(AsyncNetworkStream):
|
|
@@ -21,6 +37,13 @@ class AIONetworkStream(AsyncNetworkStream):
|
|
|
21
37
|
self._writer.close()
|
|
22
38
|
await self._writer.wait_closed()
|
|
23
39
|
|
|
40
|
+
def get_extra_info(self, info: str) -> Any:
|
|
41
|
+
transport = self._writer.transport
|
|
42
|
+
if info == "is_readable":
|
|
43
|
+
sock = transport.get_extra_info("socket")
|
|
44
|
+
return is_readable(sock)
|
|
45
|
+
return transport.get_extra_info(info)
|
|
46
|
+
|
|
24
47
|
|
|
25
48
|
class ASGIRequestBodyStream:
|
|
26
49
|
def __init__(self, receive: ASGIReceive):
|
mrok/proxy/worker.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from uvicorn.importer import import_from_string
|
|
7
|
+
|
|
8
|
+
from mrok.conf import get_settings
|
|
9
|
+
from mrok.logging import setup_logging
|
|
10
|
+
from mrok.proxy.asgi import ASGIAppWrapper
|
|
11
|
+
from mrok.proxy.event_publisher import EventPublisher
|
|
12
|
+
from mrok.proxy.models import Identity
|
|
13
|
+
from mrok.proxy.ziticorn import BackendConfig, Server
|
|
14
|
+
from mrok.types.proxy import ASGIApp
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("mrok.proxy")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Worker:
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
worker_id: str,
|
|
23
|
+
app: ASGIApp | str,
|
|
24
|
+
identity_file: str | Path,
|
|
25
|
+
*,
|
|
26
|
+
events_enabled: bool = True,
|
|
27
|
+
event_publisher_port: int = 50000,
|
|
28
|
+
metrics_interval: float = 5.0,
|
|
29
|
+
):
|
|
30
|
+
self._worker_id = worker_id
|
|
31
|
+
self._identity_file = identity_file
|
|
32
|
+
self._identity = Identity.load_from_file(self._identity_file)
|
|
33
|
+
self._app = app
|
|
34
|
+
|
|
35
|
+
self._events_enabled = events_enabled
|
|
36
|
+
self._event_publisher = (
|
|
37
|
+
EventPublisher(
|
|
38
|
+
worker_id=worker_id,
|
|
39
|
+
meta=self._identity.mrok,
|
|
40
|
+
event_publisher_port=event_publisher_port,
|
|
41
|
+
metrics_interval=metrics_interval,
|
|
42
|
+
)
|
|
43
|
+
if events_enabled
|
|
44
|
+
else None
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def setup_app(self):
|
|
48
|
+
app = ASGIAppWrapper(
|
|
49
|
+
self._app if not isinstance(self._app, str) else import_from_string(self._app),
|
|
50
|
+
lifespan=self._event_publisher.lifespan if self._events_enabled else None,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if self._events_enabled:
|
|
54
|
+
self._event_publisher.setup_middleware(app)
|
|
55
|
+
return app
|
|
56
|
+
|
|
57
|
+
def run(self):
|
|
58
|
+
setup_logging(get_settings())
|
|
59
|
+
app = self.setup_app()
|
|
60
|
+
|
|
61
|
+
config = BackendConfig(app, self._identity_file)
|
|
62
|
+
server = Server(config)
|
|
63
|
+
with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
|
|
64
|
+
server.run()
|
|
@@ -6,17 +6,40 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
import openziti
|
|
9
|
-
from uvicorn import config
|
|
9
|
+
from uvicorn import config, server
|
|
10
|
+
from uvicorn.lifespan.on import LifespanOn
|
|
11
|
+
from uvicorn.protocols.http.httptools_impl import HttpToolsProtocol as UvHttpToolsProtocol
|
|
10
12
|
|
|
11
|
-
from mrok.proxy
|
|
12
|
-
from mrok.proxy.types import ASGIApp
|
|
13
|
+
from mrok.types.proxy import ASGIApp
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger("mrok.proxy")
|
|
15
16
|
|
|
16
|
-
config.LIFESPAN["auto"] = "mrok.proxy.
|
|
17
|
+
config.LIFESPAN["auto"] = "mrok.proxy.ziticorn:Lifespan"
|
|
17
18
|
|
|
18
19
|
|
|
19
|
-
class
|
|
20
|
+
class Lifespan(LifespanOn):
|
|
21
|
+
def __init__(self, lf_config: config.Config) -> None:
|
|
22
|
+
super().__init__(lf_config)
|
|
23
|
+
self.logger = logging.getLogger("mrok.proxy")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HttpToolsProtocol(UvHttpToolsProtocol):
|
|
27
|
+
def __init__(self, *args, **kwargs):
|
|
28
|
+
super().__init__(*args, **kwargs)
|
|
29
|
+
self.logger = logging.getLogger("mrok.proxy")
|
|
30
|
+
self.access_logger = logging.getLogger("mrok.access")
|
|
31
|
+
self.access_log = self.access_logger.hasHandlers()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Server(server.Server):
|
|
35
|
+
async def serve(self, sockets: list[socket.socket] | None = None) -> None:
|
|
36
|
+
if not sockets:
|
|
37
|
+
sockets = [self.config.bind_socket()]
|
|
38
|
+
with self.capture_signals():
|
|
39
|
+
await self._serve(sockets)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BackendConfig(config.Config):
|
|
20
43
|
def __init__(
|
|
21
44
|
self,
|
|
22
45
|
app: ASGIApp | Callable[..., Any] | str,
|
|
@@ -32,7 +55,7 @@ class MrokBackendConfig(config.Config):
|
|
|
32
55
|
super().__init__(
|
|
33
56
|
app,
|
|
34
57
|
loop="asyncio",
|
|
35
|
-
http=
|
|
58
|
+
http=HttpToolsProtocol,
|
|
36
59
|
backlog=backlog,
|
|
37
60
|
)
|
|
38
61
|
|
mrok/types/__init__.py
ADDED
|
File without changes
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Awaitable, Callable, Coroutine, MutableMapping
|
|
3
|
+
from collections.abc import Awaitable, Callable, Coroutine, Mapping, MutableMapping
|
|
4
|
+
from contextlib import AbstractAsyncContextManager
|
|
4
5
|
from typing import Any, Never
|
|
5
6
|
|
|
6
|
-
from mrok.proxy.
|
|
7
|
+
from mrok.proxy.models import HTTPResponse
|
|
7
8
|
|
|
8
9
|
Scope = MutableMapping[str, Any]
|
|
9
10
|
Message = MutableMapping[str, Any]
|
|
@@ -11,5 +12,9 @@ Message = MutableMapping[str, Any]
|
|
|
11
12
|
ASGIReceive = Callable[[], Awaitable[Message]]
|
|
12
13
|
ASGISend = Callable[[Message], Awaitable[None]]
|
|
13
14
|
ASGIApp = Callable[[Scope, ASGIReceive, ASGISend], Awaitable[None]]
|
|
15
|
+
StatelessLifespan = Callable[[ASGIApp], AbstractAsyncContextManager[None]]
|
|
16
|
+
StatefulLifespan = Callable[[ASGIApp], AbstractAsyncContextManager[Mapping[str, Any]]]
|
|
17
|
+
Lifespan = StatelessLifespan | StatefulLifespan
|
|
18
|
+
|
|
14
19
|
LifespanCallback = Callable[[], Awaitable[None]]
|
|
15
20
|
ResponseCompleteCallback = Callable[[HTTPResponse], Coroutine[Any, Any, Never]]
|
mrok/types/ziti.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Tags = dict[str, str | bool | None]
|
mrok/ziti/api.py
CHANGED
|
@@ -11,12 +11,11 @@ from typing import Any, Literal
|
|
|
11
11
|
import httpx
|
|
12
12
|
|
|
13
13
|
from mrok.conf import Settings
|
|
14
|
+
from mrok.types.ziti import Tags
|
|
14
15
|
from mrok.ziti.constants import MROK_VERSION_TAG, MROK_VERSION_TAG_NAME
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
18
|
-
TagsType = dict[str, str | bool | None]
|
|
19
|
-
|
|
20
19
|
|
|
21
20
|
class ZitiAPIError(Exception):
|
|
22
21
|
pass
|
|
@@ -70,7 +69,7 @@ class BaseZitiAPI(ABC):
|
|
|
70
69
|
),
|
|
71
70
|
)
|
|
72
71
|
|
|
73
|
-
async def create(self, endpoint: str, payload: dict[str, Any], tags:
|
|
72
|
+
async def create(self, endpoint: str, payload: dict[str, Any], tags: Tags | None) -> str:
|
|
74
73
|
payload["tags"] = self._merge_tags(tags)
|
|
75
74
|
response: httpx.Response = await self.httpx_client.post(
|
|
76
75
|
endpoint,
|
|
@@ -156,8 +155,8 @@ class BaseZitiAPI(ABC):
|
|
|
156
155
|
) -> None:
|
|
157
156
|
return await self.httpx_client.__aexit__(exc_type, exc_val, exc_tb)
|
|
158
157
|
|
|
159
|
-
def _merge_tags(self, tags:
|
|
160
|
-
prepared_tags:
|
|
158
|
+
def _merge_tags(self, tags: Tags | None) -> Tags:
|
|
159
|
+
prepared_tags: Tags = tags or {}
|
|
161
160
|
prepared_tags.update(MROK_VERSION_TAG)
|
|
162
161
|
return prepared_tags
|
|
163
162
|
|
|
@@ -281,9 +280,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
281
280
|
async def search_config(self, id_or_name) -> dict[str, Any] | None:
|
|
282
281
|
return await self.search_by_id_or_name("/configs", id_or_name)
|
|
283
282
|
|
|
284
|
-
async def create_config(
|
|
285
|
-
self, name: str, config_type_id: str, tags: TagsType | None = None
|
|
286
|
-
) -> str:
|
|
283
|
+
async def create_config(self, name: str, config_type_id: str, tags: Tags | None = None) -> str:
|
|
287
284
|
return await self.create(
|
|
288
285
|
"/configs",
|
|
289
286
|
{
|
|
@@ -302,7 +299,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
302
299
|
async def delete_config(self, config_id: str) -> None:
|
|
303
300
|
return await self.delete("/configs", config_id)
|
|
304
301
|
|
|
305
|
-
async def create_config_type(self, name: str, tags:
|
|
302
|
+
async def create_config_type(self, name: str, tags: Tags | None = None) -> str:
|
|
306
303
|
return await self.create(
|
|
307
304
|
"/config-types",
|
|
308
305
|
{
|
|
@@ -316,7 +313,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
316
313
|
self,
|
|
317
314
|
name: str,
|
|
318
315
|
config_id: str,
|
|
319
|
-
tags:
|
|
316
|
+
tags: Tags | None = None,
|
|
320
317
|
) -> str:
|
|
321
318
|
return await self.create(
|
|
322
319
|
"/services",
|
|
@@ -332,7 +329,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
332
329
|
self,
|
|
333
330
|
name: str,
|
|
334
331
|
service_id: str,
|
|
335
|
-
tags:
|
|
332
|
+
tags: Tags | None = None,
|
|
336
333
|
) -> str:
|
|
337
334
|
return await self.create(
|
|
338
335
|
"/service-edge-router-policies",
|
|
@@ -351,7 +348,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
351
348
|
self,
|
|
352
349
|
name: str,
|
|
353
350
|
identity_id: str,
|
|
354
|
-
tags:
|
|
351
|
+
tags: Tags | None = None,
|
|
355
352
|
) -> str:
|
|
356
353
|
return await self.create(
|
|
357
354
|
"/edge-router-policies",
|
|
@@ -385,10 +382,10 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
385
382
|
async def delete_service(self, service_id: str) -> None:
|
|
386
383
|
return await self.delete("/services", service_id)
|
|
387
384
|
|
|
388
|
-
async def create_user_identity(self, name: str, tags:
|
|
385
|
+
async def create_user_identity(self, name: str, tags: Tags | None = None) -> str:
|
|
389
386
|
return await self._create_identity(name, "User", tags=tags)
|
|
390
387
|
|
|
391
|
-
async def create_device_identity(self, name: str, tags:
|
|
388
|
+
async def create_device_identity(self, name: str, tags: Tags | None = None) -> str:
|
|
392
389
|
return await self._create_identity(name, "Device", tags=tags)
|
|
393
390
|
|
|
394
391
|
async def search_identity(self, id_or_name: str) -> dict[str, Any] | None:
|
|
@@ -412,12 +409,12 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
412
409
|
return response.text
|
|
413
410
|
|
|
414
411
|
async def create_dial_service_policy(
|
|
415
|
-
self, name: str, service_id: str, identity_id: str, tags:
|
|
412
|
+
self, name: str, service_id: str, identity_id: str, tags: Tags | None = None
|
|
416
413
|
) -> str:
|
|
417
414
|
return await self._create_service_policy("Dial", name, service_id, identity_id, tags)
|
|
418
415
|
|
|
419
416
|
async def create_bind_service_policy(
|
|
420
|
-
self, name: str, service_id: str, identity_id: str, tags:
|
|
417
|
+
self, name: str, service_id: str, identity_id: str, tags: Tags | None = None
|
|
421
418
|
) -> str:
|
|
422
419
|
return await self._create_service_policy("Bind", name, service_id, identity_id, tags)
|
|
423
420
|
|
|
@@ -433,7 +430,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
433
430
|
name: str,
|
|
434
431
|
service_id: str,
|
|
435
432
|
identity_id: str,
|
|
436
|
-
tags:
|
|
433
|
+
tags: Tags | None = None,
|
|
437
434
|
) -> str:
|
|
438
435
|
return await self.create(
|
|
439
436
|
"/service-policies",
|
|
@@ -451,7 +448,7 @@ class ZitiManagementAPI(BaseZitiAPI):
|
|
|
451
448
|
self,
|
|
452
449
|
name: str,
|
|
453
450
|
type: Literal["User", "Device", "Default"],
|
|
454
|
-
tags:
|
|
451
|
+
tags: Tags | None = None,
|
|
455
452
|
) -> str:
|
|
456
453
|
return await self.create(
|
|
457
454
|
"/identities",
|
mrok/ziti/bootstrap.py
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from mrok.ziti
|
|
4
|
+
from mrok.types.ziti import Tags
|
|
5
|
+
from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
|
|
5
6
|
from mrok.ziti.identities import enroll_proxy_identity
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
@@ -13,7 +14,7 @@ async def bootstrap_identity(
|
|
|
13
14
|
identity_name: str,
|
|
14
15
|
mode: str,
|
|
15
16
|
forced: bool,
|
|
16
|
-
tags:
|
|
17
|
+
tags: Tags | None,
|
|
17
18
|
) -> tuple[str, dict[str, Any] | None]:
|
|
18
19
|
logger.info(f"Bootstrapping '{identity_name}' identity...")
|
|
19
20
|
|
mrok/ziti/identities.py
CHANGED
|
@@ -5,8 +5,9 @@ from typing import Any
|
|
|
5
5
|
import jwt
|
|
6
6
|
|
|
7
7
|
from mrok.conf import Settings
|
|
8
|
+
from mrok.types.ziti import Tags
|
|
8
9
|
from mrok.ziti import pki
|
|
9
|
-
from mrok.ziti.api import
|
|
10
|
+
from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
|
|
10
11
|
from mrok.ziti.constants import (
|
|
11
12
|
MROK_IDENTITY_TYPE_TAG_NAME,
|
|
12
13
|
MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE,
|
|
@@ -29,7 +30,7 @@ async def register_identity(
|
|
|
29
30
|
client_api: ZitiClientAPI,
|
|
30
31
|
service_external_id: str,
|
|
31
32
|
identity_external_id: str,
|
|
32
|
-
tags:
|
|
33
|
+
tags: Tags | None = None,
|
|
33
34
|
):
|
|
34
35
|
service_name = service_external_id.lower()
|
|
35
36
|
identity_tags = copy.copy(tags or {})
|
|
@@ -39,7 +40,7 @@ async def register_identity(
|
|
|
39
40
|
if not service:
|
|
40
41
|
raise ServiceNotFoundError(f"A service with name `{service_external_id}` does not exists.")
|
|
41
42
|
|
|
42
|
-
identity_name =
|
|
43
|
+
identity_name = identity_external_id.lower()
|
|
43
44
|
service_policy_name = f"{identity_name}:bind"
|
|
44
45
|
self_service_policy_name = f"self.{service_policy_name}"
|
|
45
46
|
|
|
@@ -129,7 +130,7 @@ async def enroll_proxy_identity(
|
|
|
129
130
|
mgmt_api: ZitiManagementAPI,
|
|
130
131
|
client_api: ZitiClientAPI,
|
|
131
132
|
identity_name: str,
|
|
132
|
-
tags:
|
|
133
|
+
tags: Tags | None = None,
|
|
133
134
|
):
|
|
134
135
|
identity = await mgmt_api.search_identity(identity_name)
|
|
135
136
|
if identity:
|
mrok/ziti/services.py
CHANGED
|
@@ -2,7 +2,8 @@ import logging
|
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
4
|
from mrok.conf import Settings
|
|
5
|
-
from mrok.ziti
|
|
5
|
+
from mrok.types.ziti import Tags
|
|
6
|
+
from mrok.ziti.api import ZitiManagementAPI
|
|
6
7
|
from mrok.ziti.errors import (
|
|
7
8
|
ConfigTypeNotFoundError,
|
|
8
9
|
ProxyIdentityNotFoundError,
|
|
@@ -14,7 +15,7 @@ logger = logging.getLogger(__name__)
|
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
async def register_service(
|
|
17
|
-
settings: Settings, mgmt_api: ZitiManagementAPI, external_id: str, tags:
|
|
18
|
+
settings: Settings, mgmt_api: ZitiManagementAPI, external_id: str, tags: Tags | None
|
|
18
19
|
) -> dict[str, Any]:
|
|
19
20
|
service_name = external_id.lower()
|
|
20
21
|
registered = False
|