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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from mrok.ziti.constants import MROK_SERVICE_TAG_NAME, MROK_VERSION_TAG_NAME
|
|
2
|
+
|
|
3
|
+
EXTENSION_RESPONSE = {
|
|
4
|
+
"id": "5Jm3PpLQ4mdzqXNRszhE0G",
|
|
5
|
+
"name": "ext-1234-5678",
|
|
6
|
+
"extension": {"id": "EXT-1234-5678"},
|
|
7
|
+
"tags": {"account": "ACC-5555-3333", MROK_VERSION_TAG_NAME: "1.0"},
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
INSTANCE_RESPONSE = {
|
|
12
|
+
"id": "h.KUkPOyZ4",
|
|
13
|
+
"name": "ins-1234-5678-0001.ext-1234-5678",
|
|
14
|
+
"extension": {"id": "EXT-1234-5678"},
|
|
15
|
+
"instance": {"id": "INS-1234-5678-0001"},
|
|
16
|
+
"tags": {
|
|
17
|
+
"account": "ACC-5555-3333",
|
|
18
|
+
MROK_VERSION_TAG_NAME: "1.0",
|
|
19
|
+
MROK_SERVICE_TAG_NAME: "ext-1234-5678",
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
INSTANCE_CREATE_RESPONSE = {
|
|
24
|
+
"id": "h.KUkPOyZ4",
|
|
25
|
+
"name": "ins-1234-5678-0001.ext-1234-5678",
|
|
26
|
+
"extension": {"id": "EXT-1234-5678"},
|
|
27
|
+
"instance": {"id": "INS-1234-5678-0001"},
|
|
28
|
+
"identity": {
|
|
29
|
+
"ztAPI": "https://ziti.exts.platform.softwareone.com/edge/client/v1",
|
|
30
|
+
"ztAPIs": None,
|
|
31
|
+
"configTypes": None,
|
|
32
|
+
"id": {
|
|
33
|
+
"key": "pem:-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
|
|
34
|
+
"cert": "pem:-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
|
|
35
|
+
"ca": "pem:-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----\n",
|
|
36
|
+
},
|
|
37
|
+
"enableHa": None,
|
|
38
|
+
},
|
|
39
|
+
"tags": {
|
|
40
|
+
"account": "ACC-5555-3333",
|
|
41
|
+
MROK_VERSION_TAG_NAME: "1.0",
|
|
42
|
+
MROK_SERVICE_TAG_NAME: "ext-1234-5678",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from fastapi import FastAPI
|
|
2
|
+
from fastapi.openapi.utils import get_openapi
|
|
3
|
+
|
|
4
|
+
from mrok.conf import Settings
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def generate_openapi_spec(app: FastAPI, settings: Settings):
|
|
8
|
+
if app.openapi_schema: # pragma: no cover
|
|
9
|
+
return app.openapi_schema
|
|
10
|
+
|
|
11
|
+
# for api_route in app.routes:
|
|
12
|
+
# if isinstance(api_route, APIRoute):
|
|
13
|
+
# for dep in api_route.dependant.dependencies:
|
|
14
|
+
# if dep.call and isinstance(dep.call, RQLQuery):
|
|
15
|
+
# api_route.description = (
|
|
16
|
+
# f"{api_route.description}\n\n"
|
|
17
|
+
# "## Available RQL filters\n\n"
|
|
18
|
+
# f"{dep.call.rules.get_documentation()}"
|
|
19
|
+
# )
|
|
20
|
+
|
|
21
|
+
spec = get_openapi(
|
|
22
|
+
title=app.title,
|
|
23
|
+
version=app.version,
|
|
24
|
+
openapi_version=app.openapi_version,
|
|
25
|
+
description=app.description,
|
|
26
|
+
tags=app.openapi_tags,
|
|
27
|
+
routes=app.routes,
|
|
28
|
+
)
|
|
29
|
+
# spec = inject_code_samples(
|
|
30
|
+
# spec,
|
|
31
|
+
# SnippetRenderer(),
|
|
32
|
+
# settings.api_base_url,
|
|
33
|
+
# )
|
|
34
|
+
app.openapi_schema = spec
|
|
35
|
+
return app.openapi_schema
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fastapi import Query
|
|
7
|
+
from fastapi_pagination import create_page, resolve_params
|
|
8
|
+
from fastapi_pagination.bases import AbstractPage, AbstractParams, RawParams
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
from mrok.controller.schemas import BaseSchema
|
|
12
|
+
from mrok.ziti.api import BaseZitiAPI
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MetaPagination(BaseModel):
|
|
16
|
+
limit: int
|
|
17
|
+
offset: int
|
|
18
|
+
total: int
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Meta(BaseModel):
|
|
22
|
+
pagination: MetaPagination
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LimitOffsetParams(BaseModel, AbstractParams):
|
|
26
|
+
limit: int = Query(50, ge=0, le=1000, description="Page size limit")
|
|
27
|
+
offset: int = Query(0, ge=0, description="Page offset")
|
|
28
|
+
|
|
29
|
+
def to_raw_params(self) -> RawParams:
|
|
30
|
+
return RawParams( # pragma: no cover
|
|
31
|
+
limit=self.limit,
|
|
32
|
+
offset=self.offset,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LimitOffsetPage[S: BaseSchema](AbstractPage[S]):
|
|
37
|
+
data: list[S]
|
|
38
|
+
meta: Meta = Field(alias="$meta")
|
|
39
|
+
|
|
40
|
+
__params_type__ = LimitOffsetParams # type: ignore
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def create(
|
|
44
|
+
cls,
|
|
45
|
+
items: Sequence[S],
|
|
46
|
+
params: AbstractParams,
|
|
47
|
+
*,
|
|
48
|
+
total: int | None = None,
|
|
49
|
+
**kwargs: Any,
|
|
50
|
+
) -> LimitOffsetPage[S]:
|
|
51
|
+
if not isinstance(params, LimitOffsetParams): # pragma: no cover
|
|
52
|
+
raise TypeError("params must be of type LimitOffsetParams")
|
|
53
|
+
return cls( # type: ignore
|
|
54
|
+
data=items,
|
|
55
|
+
meta=Meta(
|
|
56
|
+
pagination=MetaPagination(
|
|
57
|
+
limit=params.limit,
|
|
58
|
+
offset=params.offset,
|
|
59
|
+
total=total,
|
|
60
|
+
)
|
|
61
|
+
),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def paginate[S: BaseSchema](
|
|
66
|
+
api: BaseZitiAPI,
|
|
67
|
+
endpoint: str,
|
|
68
|
+
schema_cls: type[S],
|
|
69
|
+
extra_params: dict | None = None,
|
|
70
|
+
) -> AbstractPage[S]:
|
|
71
|
+
params: LimitOffsetParams = resolve_params()
|
|
72
|
+
page = await api.get_page(endpoint, params.limit, params.offset, extra_params)
|
|
73
|
+
pagination_meta = page["meta"]["pagination"]
|
|
74
|
+
total = pagination_meta["totalCount"]
|
|
75
|
+
return create_page(
|
|
76
|
+
[schema_cls(**item) for item in page["data"]],
|
|
77
|
+
params=params,
|
|
78
|
+
total=total,
|
|
79
|
+
)
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from fastapi import APIRouter, Body, HTTPException, status
|
|
5
|
+
|
|
6
|
+
from mrok.controller.dependencies import AppSettings, ZitiClientAPI, ZitiManagementAPI
|
|
7
|
+
from mrok.controller.openapi import examples
|
|
8
|
+
from mrok.controller.pagination import LimitOffsetPage, paginate
|
|
9
|
+
from mrok.controller.schemas import ExtensionCreate, ExtensionRead, InstanceCreate, InstanceRead
|
|
10
|
+
from mrok.ziti.constants import MROK_SERVICE_TAG_NAME
|
|
11
|
+
from mrok.ziti.errors import (
|
|
12
|
+
ConfigTypeNotFoundError,
|
|
13
|
+
ProxyIdentityNotFoundError,
|
|
14
|
+
ServiceAlreadyRegisteredError,
|
|
15
|
+
ServiceNotFoundError,
|
|
16
|
+
)
|
|
17
|
+
from mrok.ziti.identities import register_instance, unregister_instance
|
|
18
|
+
from mrok.ziti.services import register_extension, unregister_extension
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger("mrok.controller")
|
|
21
|
+
|
|
22
|
+
router = APIRouter()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def fetch_extension_or_404(mgmt_api: ZitiManagementAPI, id_or_extension_id: str):
|
|
26
|
+
service = await mgmt_api.search_service(id_or_extension_id)
|
|
27
|
+
if not service:
|
|
28
|
+
raise HTTPException(
|
|
29
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
30
|
+
)
|
|
31
|
+
return service
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def fetch_instance_or_404(
|
|
35
|
+
mgmt_api: ZitiManagementAPI, id_or_extension_id: str, id_or_instance_id: str
|
|
36
|
+
):
|
|
37
|
+
service = await fetch_extension_or_404(mgmt_api, id_or_extension_id)
|
|
38
|
+
if id_or_instance_id.startswith("INS-"):
|
|
39
|
+
id_or_name = f"{id_or_instance_id}.{service['name']}"
|
|
40
|
+
else:
|
|
41
|
+
id_or_name = id_or_instance_id
|
|
42
|
+
identity = await mgmt_api.search_identity(id_or_name)
|
|
43
|
+
if not identity:
|
|
44
|
+
raise HTTPException(
|
|
45
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
46
|
+
)
|
|
47
|
+
return identity
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@router.post(
|
|
51
|
+
"",
|
|
52
|
+
response_model=ExtensionRead,
|
|
53
|
+
responses={
|
|
54
|
+
201: {
|
|
55
|
+
"description": "Extension",
|
|
56
|
+
"content": {
|
|
57
|
+
"application/json": {
|
|
58
|
+
"example": examples.EXTENSION_RESPONSE,
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
status_code=status.HTTP_201_CREATED,
|
|
64
|
+
tags=["Extensions"],
|
|
65
|
+
)
|
|
66
|
+
async def create_extension(
|
|
67
|
+
settings: AppSettings,
|
|
68
|
+
mgmt_api: ZitiManagementAPI,
|
|
69
|
+
data: Annotated[
|
|
70
|
+
ExtensionCreate,
|
|
71
|
+
Body(
|
|
72
|
+
openapi_examples={
|
|
73
|
+
"create_extension": {
|
|
74
|
+
"summary": "Create an Extension",
|
|
75
|
+
"description": ("Create a new Extension."),
|
|
76
|
+
"value": {
|
|
77
|
+
"extension": {"id": "EXT-1234-5678"},
|
|
78
|
+
"tags": {"account": "ACC-5555-3333"},
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
],
|
|
84
|
+
):
|
|
85
|
+
try:
|
|
86
|
+
service = await register_extension(settings, mgmt_api, data.extension.id, data.tags)
|
|
87
|
+
return ExtensionRead(
|
|
88
|
+
id=service["id"],
|
|
89
|
+
name=service["name"],
|
|
90
|
+
tags=service["tags"],
|
|
91
|
+
)
|
|
92
|
+
except (ProxyIdentityNotFoundError, ConfigTypeNotFoundError) as e:
|
|
93
|
+
raise HTTPException(
|
|
94
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
95
|
+
detail=f"OpenZiti not configured properly: {e}",
|
|
96
|
+
) from e
|
|
97
|
+
except ServiceAlreadyRegisteredError as e:
|
|
98
|
+
raise HTTPException(
|
|
99
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
100
|
+
detail=str(e),
|
|
101
|
+
) from e
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@router.get(
|
|
105
|
+
"/{id_or_extension_id}",
|
|
106
|
+
response_model=ExtensionRead,
|
|
107
|
+
responses={
|
|
108
|
+
200: {
|
|
109
|
+
"description": "Extension",
|
|
110
|
+
"content": {"application/json": {"example": examples.EXTENSION_RESPONSE}},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
dependencies=[],
|
|
114
|
+
tags=["Extensions"],
|
|
115
|
+
)
|
|
116
|
+
async def get_extension_by_id_or_extension_id(
|
|
117
|
+
mgmt_api: ZitiManagementAPI,
|
|
118
|
+
id_or_extension_id: str,
|
|
119
|
+
):
|
|
120
|
+
return ExtensionRead(**(await fetch_extension_or_404(mgmt_api, id_or_extension_id)))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@router.delete(
|
|
124
|
+
"/{id_or_extension_id}",
|
|
125
|
+
status_code=status.HTTP_204_NO_CONTENT,
|
|
126
|
+
tags=["Extensions"],
|
|
127
|
+
)
|
|
128
|
+
async def delete_instance_by_id_or_extension_id(
|
|
129
|
+
settings: AppSettings,
|
|
130
|
+
mgmt_api: ZitiManagementAPI,
|
|
131
|
+
id_or_extension_id: str,
|
|
132
|
+
):
|
|
133
|
+
try:
|
|
134
|
+
await unregister_extension(settings, mgmt_api, id_or_extension_id)
|
|
135
|
+
except ServiceNotFoundError:
|
|
136
|
+
raise HTTPException(
|
|
137
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@router.get(
|
|
142
|
+
"",
|
|
143
|
+
response_model=LimitOffsetPage[ExtensionRead],
|
|
144
|
+
responses={
|
|
145
|
+
200: {
|
|
146
|
+
"description": "List of Extensions",
|
|
147
|
+
"content": {
|
|
148
|
+
"application/json": {
|
|
149
|
+
"example": {
|
|
150
|
+
"data": [examples.EXTENSION_RESPONSE],
|
|
151
|
+
"$meta": {
|
|
152
|
+
"pagination": {
|
|
153
|
+
"total": 1,
|
|
154
|
+
"limit": 10,
|
|
155
|
+
"offset": 0,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
tags=["Extensions"],
|
|
164
|
+
)
|
|
165
|
+
async def get_extensions(
|
|
166
|
+
mgmt_api: ZitiManagementAPI,
|
|
167
|
+
):
|
|
168
|
+
return await paginate(mgmt_api, "/services", ExtensionRead)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@router.post(
|
|
172
|
+
"/{id_or_extension_id}/instances",
|
|
173
|
+
response_model=InstanceRead,
|
|
174
|
+
responses={
|
|
175
|
+
201: {
|
|
176
|
+
"description": "Instance",
|
|
177
|
+
"content": {
|
|
178
|
+
"application/json": {
|
|
179
|
+
"example": examples.INSTANCE_CREATE_RESPONSE,
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
status_code=status.HTTP_201_CREATED,
|
|
185
|
+
tags=["Instances"],
|
|
186
|
+
)
|
|
187
|
+
async def create_extension_instances(
|
|
188
|
+
mgmt_api: ZitiManagementAPI,
|
|
189
|
+
client_api: ZitiClientAPI,
|
|
190
|
+
id_or_extension_id: str,
|
|
191
|
+
data: Annotated[
|
|
192
|
+
InstanceCreate,
|
|
193
|
+
Body(
|
|
194
|
+
openapi_examples={
|
|
195
|
+
"create_extension": {
|
|
196
|
+
"summary": "Create an Extension Instance",
|
|
197
|
+
"description": ("Create a new Instance of an Extension."),
|
|
198
|
+
"value": {
|
|
199
|
+
"instance": {"id": "INS-1234-5678-0001"},
|
|
200
|
+
"tags": {"account": "ACC-5555-3333"},
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
),
|
|
205
|
+
],
|
|
206
|
+
):
|
|
207
|
+
service = await fetch_extension_or_404(mgmt_api, id_or_extension_id)
|
|
208
|
+
identity, identity_file = await register_instance(
|
|
209
|
+
mgmt_api, client_api, service["name"], data.instance.id, data.tags
|
|
210
|
+
)
|
|
211
|
+
return InstanceRead(
|
|
212
|
+
id=identity["id"],
|
|
213
|
+
name=identity["name"],
|
|
214
|
+
identity=identity_file,
|
|
215
|
+
tags=identity["tags"],
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@router.get(
|
|
220
|
+
"/{id_or_extension_id}/instances",
|
|
221
|
+
response_model=LimitOffsetPage[InstanceRead],
|
|
222
|
+
responses={
|
|
223
|
+
200: {
|
|
224
|
+
"description": "List of Instances.",
|
|
225
|
+
"content": {
|
|
226
|
+
"application/json": {
|
|
227
|
+
"example": {
|
|
228
|
+
"data": [examples.INSTANCE_RESPONSE],
|
|
229
|
+
"$meta": {
|
|
230
|
+
"pagination": {
|
|
231
|
+
"total": 1,
|
|
232
|
+
"limit": 10,
|
|
233
|
+
"offset": 0,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
dependencies=[],
|
|
242
|
+
tags=["Instances"],
|
|
243
|
+
)
|
|
244
|
+
async def list_extension_instances(
|
|
245
|
+
mgmt_api: ZitiManagementAPI,
|
|
246
|
+
id_or_extension_id: str,
|
|
247
|
+
):
|
|
248
|
+
service = await fetch_extension_or_404(mgmt_api, id_or_extension_id)
|
|
249
|
+
return await paginate(
|
|
250
|
+
mgmt_api,
|
|
251
|
+
"/identities",
|
|
252
|
+
InstanceRead,
|
|
253
|
+
{"filter": f'tags.{MROK_SERVICE_TAG_NAME} = "{service["name"]}"'},
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@router.get(
|
|
258
|
+
"/{id_or_extension_id}/instances/{id_or_instance_id}",
|
|
259
|
+
response_model=InstanceRead,
|
|
260
|
+
responses={
|
|
261
|
+
200: {
|
|
262
|
+
"description": "Instance",
|
|
263
|
+
"content": {"application/json": {"example": examples.INSTANCE_RESPONSE}},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
dependencies=[],
|
|
267
|
+
tags=["Instances"],
|
|
268
|
+
)
|
|
269
|
+
async def get_instance_by_id_or_instance_id(
|
|
270
|
+
mgmt_api: ZitiManagementAPI,
|
|
271
|
+
id_or_extension_id: str,
|
|
272
|
+
id_or_instance_id: str,
|
|
273
|
+
):
|
|
274
|
+
identity = await fetch_instance_or_404(mgmt_api, id_or_extension_id, id_or_instance_id)
|
|
275
|
+
return InstanceRead(
|
|
276
|
+
id=identity["id"],
|
|
277
|
+
name=identity["name"],
|
|
278
|
+
tags=identity["tags"],
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@router.delete(
|
|
283
|
+
"/{id_or_extension_id}/instances/{id_or_instance_id}",
|
|
284
|
+
status_code=status.HTTP_204_NO_CONTENT,
|
|
285
|
+
tags=["Instances"],
|
|
286
|
+
)
|
|
287
|
+
async def delete_instance_by_id_or_instance_id(
|
|
288
|
+
mgmt_api: ZitiManagementAPI,
|
|
289
|
+
id_or_extension_id: str,
|
|
290
|
+
id_or_instance_id: str,
|
|
291
|
+
):
|
|
292
|
+
identity = await fetch_instance_or_404(mgmt_api, id_or_extension_id, id_or_instance_id)
|
|
293
|
+
instance_id, extension_id = identity["name"].split(".")
|
|
294
|
+
await unregister_instance(mgmt_api, extension_id, instance_id)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
from pydantic import (
|
|
4
|
+
BaseModel,
|
|
5
|
+
ConfigDict,
|
|
6
|
+
Field,
|
|
7
|
+
computed_field,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from mrok.ziti.api import TagsType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseSchema(BaseModel):
|
|
14
|
+
model_config = ConfigDict(from_attributes=True, extra="ignore")
|
|
15
|
+
tags: TagsType | None = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class IdSchema(BaseModel):
|
|
19
|
+
id: str
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ExtensionIdSchema(BaseModel):
|
|
23
|
+
id: Annotated[str, Field(pattern=r"EXT-\d{4}-\d{4}")]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# For instance
|
|
27
|
+
class InstanceIdSchema(BaseModel):
|
|
28
|
+
id: Annotated[str, Field(pattern=r"INS-\d{4}-\d{4}-\d{4}")]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ExtensionBase(BaseSchema):
|
|
32
|
+
extension: ExtensionIdSchema
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ExtensionRead(BaseSchema, IdSchema):
|
|
36
|
+
name: str
|
|
37
|
+
|
|
38
|
+
@computed_field
|
|
39
|
+
def extension(self) -> dict:
|
|
40
|
+
return {"id": self.name.upper()}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ExtensionCreate(ExtensionBase):
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class InstanceBase(BaseSchema):
|
|
48
|
+
instance: InstanceIdSchema
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class InstanceRead(BaseSchema, IdSchema):
|
|
52
|
+
name: str
|
|
53
|
+
identity: dict[str, Any] | None = None
|
|
54
|
+
|
|
55
|
+
@computed_field
|
|
56
|
+
def instance(self) -> dict:
|
|
57
|
+
instance_id, _ = self.name.split(".", 1)
|
|
58
|
+
return {"id": instance_id.upper()}
|
|
59
|
+
|
|
60
|
+
@computed_field
|
|
61
|
+
def extension(self) -> dict:
|
|
62
|
+
_, extension_id = self.name.split(".", 1)
|
|
63
|
+
return {"id": extension_id.upper()}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class InstanceCreate(InstanceBase):
|
|
67
|
+
pass
|
mrok/errors.py
ADDED
mrok/http/__init__.py
ADDED
|
File without changes
|
mrok/http/config.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import openziti
|
|
9
|
+
from uvicorn import config
|
|
10
|
+
|
|
11
|
+
from mrok.conf import get_settings
|
|
12
|
+
from mrok.http.protocol import MrokHttpToolsProtocol
|
|
13
|
+
from mrok.logging import setup_logging
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger("mrok.proxy")
|
|
16
|
+
|
|
17
|
+
config.LIFESPAN["auto"] = "mrok.http.lifespan:MrokLifespan"
|
|
18
|
+
|
|
19
|
+
ASGIApplication = config.ASGIApplication
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class MrokBackendConfig(config.Config):
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
app: ASGIApplication | Callable[..., Any] | str,
|
|
26
|
+
identity_file: str | Path,
|
|
27
|
+
ziti_load_timeout_ms: int = 5000,
|
|
28
|
+
backlog: int = 2048,
|
|
29
|
+
):
|
|
30
|
+
self.identity_file = identity_file
|
|
31
|
+
self.ziti_load_timeout_ms = ziti_load_timeout_ms
|
|
32
|
+
self.service_name, self.identity_name, self.instance_id = self.get_identity_info(
|
|
33
|
+
identity_file
|
|
34
|
+
)
|
|
35
|
+
super().__init__(
|
|
36
|
+
app,
|
|
37
|
+
loop="asyncio",
|
|
38
|
+
http=MrokHttpToolsProtocol,
|
|
39
|
+
backlog=backlog,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def get_identity_info(self, identity_file: str | Path):
|
|
43
|
+
with open(identity_file) as f:
|
|
44
|
+
identity_data = json.load(f)
|
|
45
|
+
try:
|
|
46
|
+
identity_name = identity_data["mrok"]["identity"]
|
|
47
|
+
instance_id, service_name = identity_name.split(".", 1)
|
|
48
|
+
return service_name, identity_name, instance_id
|
|
49
|
+
except KeyError:
|
|
50
|
+
raise ValueError("Invalid identity file: identity file is not mrok compatible.")
|
|
51
|
+
|
|
52
|
+
def bind_socket(self) -> socket.socket:
|
|
53
|
+
logger.info(f"Connect to Ziti service '{self.service_name} ({self.instance_id})'")
|
|
54
|
+
|
|
55
|
+
ctx, err = openziti.load(str(self.identity_file), timeout=self.ziti_load_timeout_ms)
|
|
56
|
+
if err != 0:
|
|
57
|
+
raise RuntimeError(f"Failed to load Ziti identity from {self.identity_file}: {err}")
|
|
58
|
+
|
|
59
|
+
sock = ctx.bind(self.service_name)
|
|
60
|
+
sock.listen(self.backlog)
|
|
61
|
+
logger.info(f"listening on ziti service {self.service_name} for connections")
|
|
62
|
+
return sock
|
|
63
|
+
|
|
64
|
+
def configure_logging(self) -> None:
|
|
65
|
+
setup_logging(get_settings())
|