mrok 0.1.6__py3-none-any.whl → 0.1.8__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 +12 -0
- mrok/cli/commands/admin/bootstrap.py +58 -0
- mrok/cli/commands/admin/list/__init__.py +8 -0
- mrok/cli/commands/admin/list/extensions.py +144 -0
- mrok/cli/commands/admin/list/instances.py +167 -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 +49 -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 +481 -0
- mrok/ziti/bootstrap.py +71 -0
- mrok/ziti/constants.py +9 -0
- mrok/ziti/errors.py +25 -0
- mrok/ziti/identities.py +169 -0
- mrok/ziti/pki.py +52 -0
- mrok/ziti/services.py +87 -0
- {mrok-0.1.6.dist-info → mrok-0.1.8.dist-info}/METADATA +7 -9
- mrok-0.1.8.dist-info/RECORD +64 -0
- {mrok-0.1.6.dist-info → mrok-0.1.8.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.8.dist-info}/entry_points.txt +0 -0
- {mrok-0.1.6.dist-info → mrok-0.1.8.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,12 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from mrok.cli.commands.admin import bootstrap
|
|
4
|
+
from mrok.cli.commands.admin.list import app as list_app
|
|
5
|
+
from mrok.cli.commands.admin.register import app as register_app
|
|
6
|
+
from mrok.cli.commands.admin.unregister import app as unregister_app
|
|
7
|
+
|
|
8
|
+
app = typer.Typer(help="mrok administrative commands.")
|
|
9
|
+
app.add_typer(register_app)
|
|
10
|
+
app.add_typer(unregister_app)
|
|
11
|
+
app.add_typer(list_app)
|
|
12
|
+
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,144 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich import box
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from mrok.cli.commands.admin.utils import (
|
|
9
|
+
extract_names,
|
|
10
|
+
format_tags,
|
|
11
|
+
format_timestamp,
|
|
12
|
+
tags_to_filter,
|
|
13
|
+
)
|
|
14
|
+
from mrok.cli.rich import get_console
|
|
15
|
+
from mrok.conf import Settings
|
|
16
|
+
from mrok.ziti.api import ZitiManagementAPI
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def get_extensions(
|
|
20
|
+
settings: Settings, detailed: bool, tags: list[str] | None = None
|
|
21
|
+
) -> list[dict]:
|
|
22
|
+
async with ZitiManagementAPI(settings) as api:
|
|
23
|
+
if tags is None:
|
|
24
|
+
params = None
|
|
25
|
+
else:
|
|
26
|
+
params = {"filter": tags_to_filter(tags)}
|
|
27
|
+
|
|
28
|
+
services = [service async for service in api.services(params=params)]
|
|
29
|
+
if detailed:
|
|
30
|
+
for service in services:
|
|
31
|
+
service["configs"] = [
|
|
32
|
+
config
|
|
33
|
+
async for config in api.collection_iterator(
|
|
34
|
+
f"/services/{service['id']}/configs",
|
|
35
|
+
)
|
|
36
|
+
]
|
|
37
|
+
service["policies"] = [
|
|
38
|
+
policy
|
|
39
|
+
async for policy in api.collection_iterator(
|
|
40
|
+
f"/services/{service['id']}/service-policies",
|
|
41
|
+
)
|
|
42
|
+
]
|
|
43
|
+
return services
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def render_tsv(extensions: list[dict], detailed: bool) -> None:
|
|
47
|
+
console = get_console()
|
|
48
|
+
if detailed:
|
|
49
|
+
console.print("id\tname\tconfigurations\tpolicies\ttags\tcreated\tupdated")
|
|
50
|
+
for extension in extensions:
|
|
51
|
+
console.print(
|
|
52
|
+
f"{extension['id']}\t{extension['name']}\t"
|
|
53
|
+
f"{extract_names(extension['configs'], ', ')}\t"
|
|
54
|
+
f"{extract_names(extension['policies'], ', ')}\t"
|
|
55
|
+
f"{format_tags(extension['tags'], ', ')}\t"
|
|
56
|
+
f"{format_timestamp(extension['createdAt'])}\t"
|
|
57
|
+
f"{format_timestamp(extension['updatedAt'])}"
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
console.print("id\tname\ttags\tcreated")
|
|
61
|
+
for extension in extensions:
|
|
62
|
+
console.print(
|
|
63
|
+
f"{extension['id']}\t{extension['name']}\t"
|
|
64
|
+
f"{format_tags(extension['tags'], ', ')}\t"
|
|
65
|
+
f"{format_timestamp(extension['createdAt'])}\t"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def render_table(extensions: list[dict], detailed: bool) -> None:
|
|
70
|
+
table = Table(
|
|
71
|
+
box=box.ROUNDED,
|
|
72
|
+
title="🔍 Extensions in OpenZiti (services):",
|
|
73
|
+
title_justify="left",
|
|
74
|
+
border_style="#472AFF",
|
|
75
|
+
show_lines=True,
|
|
76
|
+
)
|
|
77
|
+
table.add_column("Id", style="green")
|
|
78
|
+
table.add_column("Name", style="bold cyan")
|
|
79
|
+
if detailed:
|
|
80
|
+
table.add_column("Configurations")
|
|
81
|
+
table.add_column("Service Policies")
|
|
82
|
+
table.add_column("Tags")
|
|
83
|
+
table.add_column("Created", style="dim")
|
|
84
|
+
if detailed:
|
|
85
|
+
table.add_column("Updated", style="dim")
|
|
86
|
+
|
|
87
|
+
for extension in extensions:
|
|
88
|
+
row = [
|
|
89
|
+
extension["id"],
|
|
90
|
+
extension["name"],
|
|
91
|
+
]
|
|
92
|
+
if detailed:
|
|
93
|
+
row += [
|
|
94
|
+
extract_names(extension["configs"]),
|
|
95
|
+
extract_names(extension["policies"]),
|
|
96
|
+
]
|
|
97
|
+
row += [
|
|
98
|
+
format_tags(extension["tags"]),
|
|
99
|
+
format_timestamp(extension["createdAt"]),
|
|
100
|
+
]
|
|
101
|
+
if detailed:
|
|
102
|
+
row.append(format_timestamp(extension["updatedAt"]))
|
|
103
|
+
|
|
104
|
+
table.add_row(*row)
|
|
105
|
+
|
|
106
|
+
get_console().print(table)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def register(app: typer.Typer) -> None:
|
|
110
|
+
@app.command("extensions")
|
|
111
|
+
def list_extensions(
|
|
112
|
+
ctx: typer.Context,
|
|
113
|
+
detailed: bool = typer.Option(
|
|
114
|
+
False,
|
|
115
|
+
"--detailed",
|
|
116
|
+
"-d",
|
|
117
|
+
help="Output detailed information",
|
|
118
|
+
),
|
|
119
|
+
tags: Annotated[
|
|
120
|
+
list[str] | None,
|
|
121
|
+
typer.Option(
|
|
122
|
+
"--tag",
|
|
123
|
+
"-t",
|
|
124
|
+
help="Add tag",
|
|
125
|
+
show_default=True,
|
|
126
|
+
),
|
|
127
|
+
] = None,
|
|
128
|
+
tsv_output: bool = typer.Option(
|
|
129
|
+
False,
|
|
130
|
+
"--tsv",
|
|
131
|
+
help="Output as TSV",
|
|
132
|
+
),
|
|
133
|
+
):
|
|
134
|
+
"""List extensions in OpenZiti (service)."""
|
|
135
|
+
extensions = asyncio.run(get_extensions(ctx.obj, detailed, tags))
|
|
136
|
+
|
|
137
|
+
if len(extensions) == 0:
|
|
138
|
+
get_console().print("No extensions found.")
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
if tsv_output:
|
|
142
|
+
render_tsv(extensions, detailed)
|
|
143
|
+
else:
|
|
144
|
+
render_table(extensions, detailed)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
from rich import box
|
|
6
|
+
from rich.table import Table
|
|
7
|
+
|
|
8
|
+
from mrok.cli.commands.admin.utils import (
|
|
9
|
+
extract_names,
|
|
10
|
+
format_tags,
|
|
11
|
+
format_timestamp,
|
|
12
|
+
tags_to_filter,
|
|
13
|
+
)
|
|
14
|
+
from mrok.cli.rich import get_console
|
|
15
|
+
from mrok.conf import Settings
|
|
16
|
+
from mrok.ziti.api import ZitiManagementAPI
|
|
17
|
+
from mrok.ziti.constants import (
|
|
18
|
+
MROK_IDENTITY_TYPE_TAG_NAME,
|
|
19
|
+
MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
async def get_instances(
|
|
24
|
+
settings: Settings, detailed: bool, extension: str | None = None, tags: list[str] | None = None
|
|
25
|
+
) -> list[dict]:
|
|
26
|
+
async with ZitiManagementAPI(settings) as api:
|
|
27
|
+
tags = tags or []
|
|
28
|
+
tags.append(f"{MROK_IDENTITY_TYPE_TAG_NAME}={MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE}")
|
|
29
|
+
identities = [
|
|
30
|
+
identity async for identity in api.identities(params={"filter": tags_to_filter(tags)})
|
|
31
|
+
]
|
|
32
|
+
if detailed or extension:
|
|
33
|
+
for identity in identities:
|
|
34
|
+
identity["services"] = [
|
|
35
|
+
service
|
|
36
|
+
async for service in api.collection_iterator(
|
|
37
|
+
f"/identities/{identity['id']}/services"
|
|
38
|
+
)
|
|
39
|
+
]
|
|
40
|
+
identity["policies"] = [
|
|
41
|
+
policy
|
|
42
|
+
async for policy in api.collection_iterator(
|
|
43
|
+
f"/identities/{identity['id']}/service-policies"
|
|
44
|
+
)
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
if extension:
|
|
48
|
+
return [
|
|
49
|
+
identity
|
|
50
|
+
for identity in identities
|
|
51
|
+
if any(
|
|
52
|
+
service["id"] == extension or service["name"] == extension
|
|
53
|
+
for service in identity["services"]
|
|
54
|
+
)
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
return identities
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def render_tsv(instances: list[dict], detailed: bool) -> None:
|
|
61
|
+
console = get_console()
|
|
62
|
+
if detailed:
|
|
63
|
+
console.print("id\tname\tservices\tpolicies\ttags\tcreated\tupdated")
|
|
64
|
+
for instance in instances:
|
|
65
|
+
console.print(
|
|
66
|
+
f"{instance['id']}\t{instance['name']}\t"
|
|
67
|
+
f"{extract_names(instance['services'], ', ')}\t"
|
|
68
|
+
f"{extract_names(instance['policies'], ', ')}\t"
|
|
69
|
+
f"{format_tags(instance['tags'], ', ')}\t"
|
|
70
|
+
f"{format_timestamp(instance['createdAt'])}\t"
|
|
71
|
+
f"{format_timestamp(instance['updatedAt'])}"
|
|
72
|
+
)
|
|
73
|
+
else:
|
|
74
|
+
console.print("id\tname\ttags\tcreated")
|
|
75
|
+
for instance in instances:
|
|
76
|
+
console.print(
|
|
77
|
+
f"{instance['id']}\t{instance['name']}\t"
|
|
78
|
+
f"{format_tags(instance['tags'], ', ')}\t"
|
|
79
|
+
f"{format_timestamp(instance['createdAt'])}\t"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def render_table(instances: list[dict], detailed: bool) -> None:
|
|
84
|
+
table = Table(
|
|
85
|
+
box=box.ROUNDED,
|
|
86
|
+
title="🔍 Instances in OpenZiti (identities):",
|
|
87
|
+
title_justify="left",
|
|
88
|
+
border_style="#472AFF",
|
|
89
|
+
show_lines=True,
|
|
90
|
+
)
|
|
91
|
+
table.add_column("Id", style="green")
|
|
92
|
+
table.add_column("Name", style="bold cyan")
|
|
93
|
+
if detailed:
|
|
94
|
+
table.add_column("Associated services")
|
|
95
|
+
table.add_column("Associated service policies")
|
|
96
|
+
table.add_column("Tags")
|
|
97
|
+
table.add_column("Created", style="dim")
|
|
98
|
+
if detailed:
|
|
99
|
+
table.add_column("Updated", style="dim")
|
|
100
|
+
|
|
101
|
+
for instance in instances:
|
|
102
|
+
row = [
|
|
103
|
+
instance["id"],
|
|
104
|
+
instance["name"],
|
|
105
|
+
]
|
|
106
|
+
if detailed:
|
|
107
|
+
row += [
|
|
108
|
+
extract_names(instance["services"]),
|
|
109
|
+
extract_names(instance["policies"]),
|
|
110
|
+
]
|
|
111
|
+
row += [
|
|
112
|
+
format_tags(instance["tags"]),
|
|
113
|
+
format_timestamp(instance["createdAt"]),
|
|
114
|
+
]
|
|
115
|
+
if detailed:
|
|
116
|
+
row.append(format_timestamp(instance["updatedAt"]))
|
|
117
|
+
|
|
118
|
+
table.add_row(*row)
|
|
119
|
+
|
|
120
|
+
get_console().print(table)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def register(app: typer.Typer) -> None:
|
|
124
|
+
@app.command("instances")
|
|
125
|
+
def list_instances(
|
|
126
|
+
ctx: typer.Context,
|
|
127
|
+
extension: Annotated[
|
|
128
|
+
str | None,
|
|
129
|
+
typer.Option(
|
|
130
|
+
"--extension",
|
|
131
|
+
"-e",
|
|
132
|
+
help="Filter instances by extension",
|
|
133
|
+
show_default=True,
|
|
134
|
+
),
|
|
135
|
+
] = None,
|
|
136
|
+
tags: Annotated[
|
|
137
|
+
list[str] | None,
|
|
138
|
+
typer.Option(
|
|
139
|
+
"--tag",
|
|
140
|
+
"-t",
|
|
141
|
+
help="Add tag",
|
|
142
|
+
show_default=True,
|
|
143
|
+
),
|
|
144
|
+
] = None,
|
|
145
|
+
detailed: bool = typer.Option(
|
|
146
|
+
False,
|
|
147
|
+
"--detailed",
|
|
148
|
+
"-d",
|
|
149
|
+
help="Output detailed information",
|
|
150
|
+
),
|
|
151
|
+
tsv_output: bool = typer.Option(
|
|
152
|
+
False,
|
|
153
|
+
"--tsv",
|
|
154
|
+
help="Output as TSV",
|
|
155
|
+
),
|
|
156
|
+
):
|
|
157
|
+
"""List instances in OpenZiti (identities)."""
|
|
158
|
+
instances = asyncio.run(get_instances(ctx.obj, detailed, extension, tags))
|
|
159
|
+
|
|
160
|
+
if len(instances) == 0:
|
|
161
|
+
get_console().print("No instances found.")
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
if tsv_output:
|
|
165
|
+
render_tsv(instances, detailed)
|
|
166
|
+
else:
|
|
167
|
+
render_table(instances, detailed)
|
|
@@ -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))
|