mrok 0.1.6__py3-none-any.whl → 0.1.7__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/__init__.py +6 -0
- mrok/agent/__init__.py +0 -0
- mrok/agent/sidecar/__init__.py +3 -0
- mrok/agent/sidecar/app.py +30 -0
- mrok/agent/sidecar/main.py +27 -0
- mrok/agent/ziticorn.py +29 -0
- mrok/cli/__init__.py +3 -0
- mrok/cli/commands/__init__.py +7 -0
- mrok/cli/commands/admin/__init__.py +10 -0
- mrok/cli/commands/admin/bootstrap.py +58 -0
- mrok/cli/commands/admin/register/__init__.py +8 -0
- mrok/cli/commands/admin/register/extensions.py +46 -0
- mrok/cli/commands/admin/register/instances.py +60 -0
- mrok/cli/commands/admin/unregister/__init__.py +8 -0
- mrok/cli/commands/admin/unregister/extensions.py +33 -0
- mrok/cli/commands/admin/unregister/instances.py +34 -0
- mrok/cli/commands/admin/utils.py +23 -0
- mrok/cli/commands/agent/__init__.py +6 -0
- mrok/cli/commands/agent/run/__init__.py +7 -0
- mrok/cli/commands/agent/run/asgi.py +49 -0
- mrok/cli/commands/agent/run/sidecar.py +54 -0
- mrok/cli/commands/controller/__init__.py +7 -0
- mrok/cli/commands/controller/openapi.py +47 -0
- mrok/cli/commands/controller/run.py +87 -0
- mrok/cli/main.py +97 -0
- mrok/cli/rich.py +18 -0
- mrok/conf.py +32 -0
- mrok/controller/__init__.py +0 -0
- mrok/controller/app.py +62 -0
- mrok/controller/auth.py +87 -0
- mrok/controller/dependencies/__init__.py +4 -0
- mrok/controller/dependencies/conf.py +7 -0
- mrok/controller/dependencies/ziti.py +27 -0
- mrok/controller/openapi/__init__.py +3 -0
- mrok/controller/openapi/examples.py +44 -0
- mrok/controller/openapi/utils.py +35 -0
- mrok/controller/pagination.py +79 -0
- mrok/controller/routes.py +294 -0
- mrok/controller/schemas.py +67 -0
- mrok/errors.py +2 -0
- mrok/http/__init__.py +0 -0
- mrok/http/config.py +65 -0
- mrok/http/forwarder.py +299 -0
- mrok/http/lifespan.py +10 -0
- mrok/http/master.py +90 -0
- mrok/http/protocol.py +11 -0
- mrok/http/server.py +14 -0
- mrok/logging.py +76 -0
- mrok/ziti/__init__.py +15 -0
- mrok/ziti/api.py +467 -0
- mrok/ziti/bootstrap.py +71 -0
- mrok/ziti/constants.py +6 -0
- mrok/ziti/errors.py +25 -0
- mrok/ziti/identities.py +161 -0
- mrok/ziti/pki.py +52 -0
- mrok/ziti/services.py +87 -0
- {mrok-0.1.6.dist-info → mrok-0.1.7.dist-info}/METADATA +7 -9
- mrok-0.1.7.dist-info/RECORD +61 -0
- {mrok-0.1.6.dist-info → mrok-0.1.7.dist-info}/WHEEL +1 -2
- mrok-0.1.6.dist-info/RECORD +0 -6
- mrok-0.1.6.dist-info/top_level.txt +0 -1
- {mrok-0.1.6.dist-info → mrok-0.1.7.dist-info}/entry_points.txt +0 -0
- {mrok-0.1.6.dist-info → mrok-0.1.7.dist-info}/licenses/LICENSE.txt +0 -0
mrok/__init__.py
ADDED
mrok/agent/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
from collections.abc import Awaitable, Callable
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from mrok.http.forwarder import ForwardAppBase
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("mrok.proxy")
|
|
10
|
+
|
|
11
|
+
Scope = dict[str, Any]
|
|
12
|
+
ASGIReceive = Callable[[], Awaitable[dict[str, Any]]]
|
|
13
|
+
ASGISend = Callable[[dict[str, Any]], Awaitable[None]]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ForwardApp(ForwardAppBase):
|
|
17
|
+
def __init__(
|
|
18
|
+
self, target_address: str | Path | tuple[str, int], read_chunk_size: int = 65536
|
|
19
|
+
) -> None:
|
|
20
|
+
super().__init__(read_chunk_size=read_chunk_size)
|
|
21
|
+
self._target_address = target_address
|
|
22
|
+
|
|
23
|
+
async def select_backend(
|
|
24
|
+
self,
|
|
25
|
+
scope: Scope,
|
|
26
|
+
headers: dict[str, str],
|
|
27
|
+
) -> tuple[asyncio.StreamReader, asyncio.StreamWriter] | tuple[None, None]:
|
|
28
|
+
if isinstance(self._target_address, tuple):
|
|
29
|
+
return await asyncio.open_connection(*self._target_address)
|
|
30
|
+
return await asyncio.open_unix_connection(str(self._target_address))
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
from functools import partial
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from mrok.agent.sidecar.app import ForwardApp
|
|
7
|
+
from mrok.http.config import MrokBackendConfig
|
|
8
|
+
from mrok.http.master import Master
|
|
9
|
+
from mrok.http.server import MrokServer
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def run_sidecar(identity_file: str, target_addr: str | Path | tuple[str, int]):
|
|
13
|
+
config = MrokBackendConfig(ForwardApp(target_addr), identity_file)
|
|
14
|
+
server = MrokServer(config)
|
|
15
|
+
with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
|
|
16
|
+
server.run()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(
|
|
20
|
+
identity_file: str,
|
|
21
|
+
target_addr: str | Path | tuple[str, int],
|
|
22
|
+
workers=4,
|
|
23
|
+
reload=False,
|
|
24
|
+
):
|
|
25
|
+
start_fn = partial(run_sidecar, identity_file, target_addr)
|
|
26
|
+
master = Master(start_fn, workers=workers, reload=reload)
|
|
27
|
+
master.run()
|
mrok/agent/ziticorn.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import os
|
|
4
|
+
from functools import partial
|
|
5
|
+
|
|
6
|
+
from mrok.http.config import ASGIApplication, MrokBackendConfig
|
|
7
|
+
from mrok.http.master import Master
|
|
8
|
+
from mrok.http.server import MrokServer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def run_ziticorn(app: ASGIApplication | str, identity_file: str):
|
|
12
|
+
import sys
|
|
13
|
+
|
|
14
|
+
sys.path.insert(0, os.getcwd())
|
|
15
|
+
config = MrokBackendConfig(app, identity_file)
|
|
16
|
+
server = MrokServer(config)
|
|
17
|
+
with contextlib.suppress(KeyboardInterrupt, asyncio.CancelledError):
|
|
18
|
+
server.run()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(
|
|
22
|
+
app: ASGIApplication | str,
|
|
23
|
+
identity_file: str,
|
|
24
|
+
workers: int = 4,
|
|
25
|
+
reload: bool = False,
|
|
26
|
+
):
|
|
27
|
+
start_fn = partial(run_ziticorn, app, identity_file)
|
|
28
|
+
master = Master(start_fn, workers=workers, reload=reload)
|
|
29
|
+
master.run()
|
mrok/cli/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from mrok.cli.commands.admin import bootstrap
|
|
4
|
+
from mrok.cli.commands.admin.register import app as register_app
|
|
5
|
+
from mrok.cli.commands.admin.unregister import app as unregister_app
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="mrok administrative commands.")
|
|
8
|
+
app.add_typer(register_app)
|
|
9
|
+
app.add_typer(unregister_app)
|
|
10
|
+
bootstrap.register(app)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated, Any
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from mrok.cli.commands.admin.utils import parse_tags
|
|
10
|
+
from mrok.conf import Settings
|
|
11
|
+
from mrok.ziti.api import TagsType, ZitiClientAPI, ZitiManagementAPI
|
|
12
|
+
from mrok.ziti.bootstrap import bootstrap_identity
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def bootstrap(
|
|
18
|
+
settings: Settings, forced: bool, tags: TagsType | None
|
|
19
|
+
) -> tuple[str, dict[str, Any] | None]:
|
|
20
|
+
async with ZitiManagementAPI(settings) as mgmt_api, ZitiClientAPI(settings) as client_api:
|
|
21
|
+
return await bootstrap_identity(
|
|
22
|
+
mgmt_api,
|
|
23
|
+
client_api,
|
|
24
|
+
settings.proxy.identity,
|
|
25
|
+
settings.proxy.mode,
|
|
26
|
+
forced,
|
|
27
|
+
tags,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register(app: typer.Typer) -> None:
|
|
32
|
+
@app.command("bootstrap")
|
|
33
|
+
def run_bootstrap(
|
|
34
|
+
ctx: typer.Context,
|
|
35
|
+
identity_file: Path = typer.Argument(
|
|
36
|
+
Path("proxy_identity.json"),
|
|
37
|
+
help="Path to identity output file",
|
|
38
|
+
writable=True,
|
|
39
|
+
),
|
|
40
|
+
forced: bool = typer.Option(
|
|
41
|
+
False,
|
|
42
|
+
"--force",
|
|
43
|
+
help="Regenerate identity even if it already exists",
|
|
44
|
+
),
|
|
45
|
+
tags: Annotated[
|
|
46
|
+
list[str] | None,
|
|
47
|
+
typer.Option(
|
|
48
|
+
"--tag",
|
|
49
|
+
"-t",
|
|
50
|
+
help="Add tag",
|
|
51
|
+
show_default=True,
|
|
52
|
+
),
|
|
53
|
+
] = None,
|
|
54
|
+
):
|
|
55
|
+
"""Run the mrok bootstrap."""
|
|
56
|
+
_, identity_json = asyncio.run(bootstrap(ctx.obj, forced, parse_tags(tags)))
|
|
57
|
+
if identity_json:
|
|
58
|
+
json.dump(identity_json, identity_file.open("w"))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from rich import print
|
|
7
|
+
|
|
8
|
+
from mrok.cli.commands.admin.utils import parse_tags
|
|
9
|
+
from mrok.conf import Settings
|
|
10
|
+
from mrok.ziti.api import ZitiManagementAPI
|
|
11
|
+
from mrok.ziti.services import register_extension
|
|
12
|
+
|
|
13
|
+
RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
async def do_register(settings: Settings, extension_id: str, tags: list[str] | None):
|
|
17
|
+
async with ZitiManagementAPI(settings) as api:
|
|
18
|
+
await register_extension(settings, api, extension_id, tags=parse_tags(tags))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def validate_extension_id(extension_id: str) -> str:
|
|
22
|
+
if not RE_EXTENSION_ID.fullmatch(extension_id):
|
|
23
|
+
raise typer.BadParameter("ext_id must match EXT-xxxx-yyyy (case-insensitive)")
|
|
24
|
+
return extension_id
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def register(app: typer.Typer) -> None:
|
|
28
|
+
@app.command("extension")
|
|
29
|
+
def register_extension(
|
|
30
|
+
ctx: typer.Context,
|
|
31
|
+
extension_id: str = typer.Argument(
|
|
32
|
+
..., callback=validate_extension_id, help="Extension ID in format EXT-xxxx-yyyy"
|
|
33
|
+
),
|
|
34
|
+
tags: Annotated[
|
|
35
|
+
list[str] | None,
|
|
36
|
+
typer.Option(
|
|
37
|
+
"--tag",
|
|
38
|
+
"-t",
|
|
39
|
+
help="Add tag",
|
|
40
|
+
show_default=True,
|
|
41
|
+
),
|
|
42
|
+
] = None,
|
|
43
|
+
):
|
|
44
|
+
"""Register a new Extension in OpenZiti (service)."""
|
|
45
|
+
asyncio.run(do_register(ctx.obj, extension_id, tags))
|
|
46
|
+
print(f"🍻 [green]Extension [bold]{extension_id}[/bold] registered.[/green]")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Annotated
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from mrok.cli.commands.admin.utils import parse_tags
|
|
10
|
+
from mrok.conf import Settings
|
|
11
|
+
from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
|
|
12
|
+
from mrok.ziti.identities import register_instance
|
|
13
|
+
|
|
14
|
+
RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
async def do_register(
|
|
18
|
+
settings: Settings, extension_id: str, instance_id: str, tags: list[str] | None
|
|
19
|
+
):
|
|
20
|
+
async with ZitiManagementAPI(settings) as mgmt_api, ZitiClientAPI(settings) as client_api:
|
|
21
|
+
return await register_instance(
|
|
22
|
+
mgmt_api, client_api, extension_id, instance_id, tags=parse_tags(tags)
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def validate_extension_id(extension_id: str):
|
|
27
|
+
if not RE_EXTENSION_ID.fullmatch(extension_id):
|
|
28
|
+
raise typer.BadParameter("ext_id must match EXT-xxxx-yyyy (case-insensitive)")
|
|
29
|
+
return extension_id
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register(app: typer.Typer) -> None:
|
|
33
|
+
@app.command("instance")
|
|
34
|
+
def register_instance(
|
|
35
|
+
ctx: typer.Context,
|
|
36
|
+
extension_id: str = typer.Argument(
|
|
37
|
+
..., callback=validate_extension_id, help="Extension ID in format EXT-xxxx-yyyy"
|
|
38
|
+
),
|
|
39
|
+
instance_id: str = typer.Argument(..., help="Instance ID"),
|
|
40
|
+
output: Path = typer.Argument(
|
|
41
|
+
...,
|
|
42
|
+
file_okay=True,
|
|
43
|
+
dir_okay=False,
|
|
44
|
+
writable=True,
|
|
45
|
+
resolve_path=True,
|
|
46
|
+
help="Output file (default: stdout)",
|
|
47
|
+
),
|
|
48
|
+
tags: Annotated[
|
|
49
|
+
list[str] | None,
|
|
50
|
+
typer.Option(
|
|
51
|
+
"--tag",
|
|
52
|
+
"-t",
|
|
53
|
+
help="Add tag",
|
|
54
|
+
show_default=True,
|
|
55
|
+
),
|
|
56
|
+
] = None,
|
|
57
|
+
):
|
|
58
|
+
"""Register a new Extension Instance in OpenZiti (identity)."""
|
|
59
|
+
_, identity_file = asyncio.run(do_register(ctx.obj, extension_id, instance_id, tags))
|
|
60
|
+
json.dump(identity_file, output.open("w"))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from mrok.conf import Settings
|
|
7
|
+
from mrok.ziti.api import ZitiManagementAPI
|
|
8
|
+
from mrok.ziti.services import unregister_extension
|
|
9
|
+
|
|
10
|
+
RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def do_unregister(settings: Settings, extension_id: str):
|
|
14
|
+
async with ZitiManagementAPI(settings) as api:
|
|
15
|
+
await unregister_extension(settings, api, extension_id)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_extension_id(extension_id: str):
|
|
19
|
+
if not RE_EXTENSION_ID.fullmatch(extension_id):
|
|
20
|
+
raise typer.BadParameter("ext_id must match EXT-xxxx-yyyy (case-insensitive)")
|
|
21
|
+
return extension_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def register(app: typer.Typer) -> None:
|
|
25
|
+
@app.command("extension")
|
|
26
|
+
def unregister_extension(
|
|
27
|
+
ctx: typer.Context,
|
|
28
|
+
extension_id: str = typer.Argument(
|
|
29
|
+
..., callback=validate_extension_id, help="Extension ID in format EXT-xxxx-yyyy"
|
|
30
|
+
),
|
|
31
|
+
):
|
|
32
|
+
"""Unregister a new Extension in OpenZiti (service)."""
|
|
33
|
+
asyncio.run(do_unregister(ctx.obj, extension_id))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from mrok.conf import Settings
|
|
7
|
+
from mrok.ziti.api import ZitiManagementAPI
|
|
8
|
+
from mrok.ziti.identities import unregister_instance
|
|
9
|
+
|
|
10
|
+
RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def do_unregister(settings: Settings, extension_id: str, instance_id: str):
|
|
14
|
+
async with ZitiManagementAPI(settings) as api:
|
|
15
|
+
await unregister_instance(api, extension_id, instance_id)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def validate_extension_id(extension_id: str):
|
|
19
|
+
if not RE_EXTENSION_ID.fullmatch(extension_id):
|
|
20
|
+
raise typer.BadParameter("ext_id must match EXT-xxxx-yyyy (case-insensitive)")
|
|
21
|
+
return extension_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def register(app: typer.Typer) -> None:
|
|
25
|
+
@app.command("instance")
|
|
26
|
+
def unregister_instance(
|
|
27
|
+
ctx: typer.Context,
|
|
28
|
+
extension_id: str = typer.Argument(
|
|
29
|
+
..., callback=validate_extension_id, help="Extension ID in format EXT-xxxx-yyyy"
|
|
30
|
+
),
|
|
31
|
+
instance_id: str = typer.Argument(..., help="Instance ID"),
|
|
32
|
+
):
|
|
33
|
+
"""Register a new Extension Instance in OpenZiti (identity)."""
|
|
34
|
+
asyncio.run(do_unregister(ctx.obj, extension_id, instance_id))
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from mrok.ziti.api import TagsType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def parse_tags(pairs: list[str] | None) -> TagsType | None:
|
|
7
|
+
if not pairs:
|
|
8
|
+
return None
|
|
9
|
+
|
|
10
|
+
result: dict[str, str | bool | None] = {}
|
|
11
|
+
for item in pairs:
|
|
12
|
+
if "=" not in item:
|
|
13
|
+
raise typer.BadParameter(f"Invalid format {item!r}, expected key=value")
|
|
14
|
+
key, raw = item.split("=", 1)
|
|
15
|
+
raw_lower = raw.strip().lower()
|
|
16
|
+
if raw_lower in ("true", "false"):
|
|
17
|
+
val: str | bool | None = raw_lower == "true"
|
|
18
|
+
elif raw == "":
|
|
19
|
+
val = None
|
|
20
|
+
else:
|
|
21
|
+
val = raw
|
|
22
|
+
result[key.strip()] = val
|
|
23
|
+
return result
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
# from app.logging import get_logging_config
|
|
8
|
+
from mrok.agent import ziticorn
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def number_of_workers():
|
|
12
|
+
return (multiprocessing.cpu_count() * 2) + 1
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
default_workers = number_of_workers()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def register(app: typer.Typer) -> None:
|
|
19
|
+
@app.command("asgi")
|
|
20
|
+
def run_asgi(
|
|
21
|
+
app: str = typer.Argument(
|
|
22
|
+
...,
|
|
23
|
+
help="ASGI application",
|
|
24
|
+
),
|
|
25
|
+
identity_file: Path = typer.Argument(
|
|
26
|
+
...,
|
|
27
|
+
help="Identity json file",
|
|
28
|
+
),
|
|
29
|
+
workers: Annotated[
|
|
30
|
+
int,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--workers",
|
|
33
|
+
"-w",
|
|
34
|
+
help=f"Number of workers. Default: {default_workers}",
|
|
35
|
+
show_default=True,
|
|
36
|
+
),
|
|
37
|
+
] = default_workers,
|
|
38
|
+
reload: Annotated[
|
|
39
|
+
bool,
|
|
40
|
+
typer.Option(
|
|
41
|
+
"--reload",
|
|
42
|
+
"-r",
|
|
43
|
+
help="Enable auto-reload. Default: False",
|
|
44
|
+
show_default=True,
|
|
45
|
+
),
|
|
46
|
+
] = False,
|
|
47
|
+
):
|
|
48
|
+
"""Run an ASGI application exposing it through OpenZiti network."""
|
|
49
|
+
ziticorn.run(app, str(identity_file), workers=workers, reload=reload)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from mrok.agent import sidecar
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def number_of_workers():
|
|
11
|
+
return (multiprocessing.cpu_count() * 2) + 1
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
default_workers = number_of_workers()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register(app: typer.Typer) -> None:
|
|
18
|
+
@app.command("sidecar")
|
|
19
|
+
def run_sidecar(
|
|
20
|
+
identity_file: Path = typer.Argument(
|
|
21
|
+
...,
|
|
22
|
+
help="Identity json file",
|
|
23
|
+
),
|
|
24
|
+
target: Path = typer.Argument(
|
|
25
|
+
...,
|
|
26
|
+
help="Target service (host:port or path to unix domain socket)",
|
|
27
|
+
),
|
|
28
|
+
workers: Annotated[
|
|
29
|
+
int,
|
|
30
|
+
typer.Option(
|
|
31
|
+
"--workers",
|
|
32
|
+
"-w",
|
|
33
|
+
help=f"Number of workers. Default: {default_workers}",
|
|
34
|
+
show_default=True,
|
|
35
|
+
),
|
|
36
|
+
] = default_workers,
|
|
37
|
+
reload: Annotated[
|
|
38
|
+
bool,
|
|
39
|
+
typer.Option(
|
|
40
|
+
"--reload",
|
|
41
|
+
"-r",
|
|
42
|
+
help="Enable auto-reload. Default: False",
|
|
43
|
+
show_default=True,
|
|
44
|
+
),
|
|
45
|
+
] = False,
|
|
46
|
+
):
|
|
47
|
+
"""Run a Sidecar Proxy to expose a web application through OpenZiti."""
|
|
48
|
+
if ":" in str(target):
|
|
49
|
+
host, port = str(target).split(":", 1)
|
|
50
|
+
target_addr = (host or "127.0.0.1", int(port))
|
|
51
|
+
else:
|
|
52
|
+
target_addr = str(target) # type: ignore
|
|
53
|
+
|
|
54
|
+
sidecar.run(str(identity_file), target_addr, workers=workers, reload=reload)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from mrok.controller.openapi import generate_openapi_spec
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OutputFormat(str, Enum):
|
|
13
|
+
json = "json"
|
|
14
|
+
yaml = "yaml"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def register(app: typer.Typer) -> None:
|
|
18
|
+
@app.command("openapi")
|
|
19
|
+
def generate_spec(
|
|
20
|
+
ctx: typer.Context,
|
|
21
|
+
output: Annotated[
|
|
22
|
+
Path | None,
|
|
23
|
+
typer.Option(
|
|
24
|
+
"--output",
|
|
25
|
+
"-o",
|
|
26
|
+
help="Output file",
|
|
27
|
+
),
|
|
28
|
+
] = Path("mrok_openapi_spec.yml"),
|
|
29
|
+
output_format: Annotated[
|
|
30
|
+
OutputFormat,
|
|
31
|
+
typer.Option(
|
|
32
|
+
"--output-format",
|
|
33
|
+
"-f",
|
|
34
|
+
help="Output file format",
|
|
35
|
+
),
|
|
36
|
+
] = OutputFormat.yaml,
|
|
37
|
+
):
|
|
38
|
+
"""
|
|
39
|
+
Generates the mrok controller OpenAPI spec file.
|
|
40
|
+
"""
|
|
41
|
+
from mrok.controller.app import app
|
|
42
|
+
|
|
43
|
+
dump_fn = json.dump if output_format == OutputFormat.json else yaml.dump
|
|
44
|
+
spec = generate_openapi_spec(app, ctx.obj)
|
|
45
|
+
|
|
46
|
+
with open(output, "w") as f: # type: ignore
|
|
47
|
+
dump_fn(spec, f, indent=2)
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import multiprocessing
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from typing import Annotated, Any
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
from gunicorn.app.base import BaseApplication
|
|
7
|
+
|
|
8
|
+
from mrok.controller.app import app as asgi_app
|
|
9
|
+
from mrok.logging import get_logging_config
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def number_of_workers():
|
|
13
|
+
return (multiprocessing.cpu_count() * 2) + 1
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StandaloneApplication(BaseApplication): # pragma: no cover
|
|
17
|
+
def __init__(self, application: Callable, options: dict[str, Any] | None = None):
|
|
18
|
+
self.options = options or {}
|
|
19
|
+
self.application = application
|
|
20
|
+
super().__init__()
|
|
21
|
+
|
|
22
|
+
def load_config(self):
|
|
23
|
+
config = {
|
|
24
|
+
key: value
|
|
25
|
+
for key, value in self.options.items()
|
|
26
|
+
if key in self.cfg.settings and value is not None
|
|
27
|
+
}
|
|
28
|
+
for key, value in config.items():
|
|
29
|
+
self.cfg.set(key.lower(), value)
|
|
30
|
+
|
|
31
|
+
def load(self):
|
|
32
|
+
return self.application
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
default_workers = number_of_workers()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def register(app: typer.Typer) -> None:
|
|
39
|
+
@app.command("run")
|
|
40
|
+
def run_controller(
|
|
41
|
+
ctx: typer.Context,
|
|
42
|
+
host: Annotated[
|
|
43
|
+
str,
|
|
44
|
+
typer.Option(
|
|
45
|
+
"--host",
|
|
46
|
+
"-h",
|
|
47
|
+
help="Host to bind to. Default: 127.0.0.1",
|
|
48
|
+
show_default=True,
|
|
49
|
+
),
|
|
50
|
+
] = "127.0.0.1",
|
|
51
|
+
port: Annotated[
|
|
52
|
+
int,
|
|
53
|
+
typer.Option(
|
|
54
|
+
"--port",
|
|
55
|
+
"-p",
|
|
56
|
+
help="Port to bind to. Default: 8000",
|
|
57
|
+
show_default=True,
|
|
58
|
+
),
|
|
59
|
+
] = 8000,
|
|
60
|
+
workers: Annotated[
|
|
61
|
+
int,
|
|
62
|
+
typer.Option(
|
|
63
|
+
"--workers",
|
|
64
|
+
"-w",
|
|
65
|
+
help=f"Number of workers. Default: {default_workers}",
|
|
66
|
+
show_default=True,
|
|
67
|
+
),
|
|
68
|
+
] = default_workers,
|
|
69
|
+
dev: Annotated[
|
|
70
|
+
bool,
|
|
71
|
+
typer.Option(
|
|
72
|
+
"--reload",
|
|
73
|
+
"-r",
|
|
74
|
+
help="Enable auto-reload. Default: False",
|
|
75
|
+
show_default=True,
|
|
76
|
+
),
|
|
77
|
+
] = False,
|
|
78
|
+
):
|
|
79
|
+
"""Run the mrok controller with Gunicorn and Uvicorn workers."""
|
|
80
|
+
options = {
|
|
81
|
+
"bind": f"{host}:{port}",
|
|
82
|
+
"workers": workers,
|
|
83
|
+
"worker_class": "uvicorn_worker.UvicornWorker",
|
|
84
|
+
"logconfig_dict": get_logging_config(ctx.obj),
|
|
85
|
+
"reload": dev,
|
|
86
|
+
}
|
|
87
|
+
StandaloneApplication(asgi_app, options).run()
|