mrok 0.2.3__tar.gz → 0.3.0__tar.gz

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.
Files changed (120) hide show
  1. {mrok-0.2.3 → mrok-0.3.0}/PKG-INFO +1 -1
  2. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/list/instances.py +24 -4
  3. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/register/extensions.py +2 -2
  4. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/register/instances.py +3 -3
  5. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/unregister/extensions.py +2 -2
  6. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/unregister/instances.py +2 -2
  7. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/routes/extensions.py +9 -7
  8. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/identities.py +50 -20
  9. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/services.py +8 -8
  10. {mrok-0.2.3 → mrok-0.3.0}/pyproject.toml +1 -1
  11. {mrok-0.2.3 → mrok-0.3.0}/settings.yaml +2 -2
  12. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/admin/test_list.py +36 -15
  13. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/admin/test_register.py +8 -3
  14. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/admin/test_unregister.py +3 -2
  15. {mrok-0.2.3 → mrok-0.3.0}/tests/controller/test_extensions.py +13 -12
  16. {mrok-0.2.3 → mrok-0.3.0}/tests/ziti/test_identities.py +163 -37
  17. {mrok-0.2.3 → mrok-0.3.0}/tests/ziti/test_services.py +16 -16
  18. {mrok-0.2.3 → mrok-0.3.0}/.github/actions/setup-python-env/action.yml +0 -0
  19. {mrok-0.2.3 → mrok-0.3.0}/.github/workflows/assets/turing_team_pr_bot.png +0 -0
  20. {mrok-0.2.3 → mrok-0.3.0}/.github/workflows/notify-pr-closed.yaml +0 -0
  21. {mrok-0.2.3 → mrok-0.3.0}/.github/workflows/notify-pr-reviewed.yml +0 -0
  22. {mrok-0.2.3 → mrok-0.3.0}/.github/workflows/pr-build-merge.yaml +0 -0
  23. {mrok-0.2.3 → mrok-0.3.0}/.github/workflows/release.yml +0 -0
  24. {mrok-0.2.3 → mrok-0.3.0}/.gitignore +0 -0
  25. {mrok-0.2.3 → mrok-0.3.0}/.pre-commit-config.yaml +0 -0
  26. {mrok-0.2.3 → mrok-0.3.0}/.python-version +0 -0
  27. {mrok-0.2.3 → mrok-0.3.0}/LICENSE.txt +0 -0
  28. {mrok-0.2.3 → mrok-0.3.0}/README.md +0 -0
  29. {mrok-0.2.3 → mrok-0.3.0}/dev.Dockerfile +0 -0
  30. {mrok-0.2.3 → mrok-0.3.0}/docker-compose.yaml +0 -0
  31. {mrok-0.2.3 → mrok-0.3.0}/entrypoint.sh +0 -0
  32. {mrok-0.2.3 → mrok-0.3.0}/mrok/__init__.py +0 -0
  33. {mrok-0.2.3 → mrok-0.3.0}/mrok/agent/__init__.py +0 -0
  34. {mrok-0.2.3 → mrok-0.3.0}/mrok/agent/sidecar/__init__.py +0 -0
  35. {mrok-0.2.3 → mrok-0.3.0}/mrok/agent/sidecar/app.py +0 -0
  36. {mrok-0.2.3 → mrok-0.3.0}/mrok/agent/sidecar/main.py +0 -0
  37. {mrok-0.2.3 → mrok-0.3.0}/mrok/agent/ziticorn.py +0 -0
  38. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/__init__.py +0 -0
  39. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/__init__.py +0 -0
  40. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/__init__.py +0 -0
  41. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/bootstrap.py +0 -0
  42. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/list/__init__.py +0 -0
  43. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/list/extensions.py +0 -0
  44. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/register/__init__.py +0 -0
  45. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/unregister/__init__.py +0 -0
  46. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/admin/utils.py +0 -0
  47. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/agent/__init__.py +0 -0
  48. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/agent/run/__init__.py +0 -0
  49. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/agent/run/asgi.py +0 -0
  50. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/agent/run/sidecar.py +0 -0
  51. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/controller/__init__.py +0 -0
  52. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/controller/openapi.py +0 -0
  53. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/commands/controller/run.py +0 -0
  54. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/main.py +0 -0
  55. {mrok-0.2.3 → mrok-0.3.0}/mrok/cli/rich.py +0 -0
  56. {mrok-0.2.3 → mrok-0.3.0}/mrok/conf.py +0 -0
  57. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/__init__.py +0 -0
  58. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/app.py +0 -0
  59. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/auth.py +0 -0
  60. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/dependencies/__init__.py +0 -0
  61. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/dependencies/conf.py +0 -0
  62. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/dependencies/ziti.py +0 -0
  63. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/openapi/__init__.py +0 -0
  64. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/openapi/examples.py +0 -0
  65. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/openapi/utils.py +0 -0
  66. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/pagination.py +0 -0
  67. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/routes/__init__.py +0 -0
  68. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/routes/instances.py +0 -0
  69. {mrok-0.2.3 → mrok-0.3.0}/mrok/controller/schemas.py +0 -0
  70. {mrok-0.2.3 → mrok-0.3.0}/mrok/errors.py +0 -0
  71. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/__init__.py +0 -0
  72. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/config.py +0 -0
  73. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/forwarder.py +0 -0
  74. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/lifespan.py +0 -0
  75. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/master.py +0 -0
  76. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/protocol.py +0 -0
  77. {mrok-0.2.3 → mrok-0.3.0}/mrok/http/server.py +0 -0
  78. {mrok-0.2.3 → mrok-0.3.0}/mrok/logging.py +0 -0
  79. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/__init__.py +0 -0
  80. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/api.py +0 -0
  81. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/bootstrap.py +0 -0
  82. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/constants.py +0 -0
  83. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/errors.py +0 -0
  84. {mrok-0.2.3 → mrok-0.3.0}/mrok/ziti/pki.py +0 -0
  85. {mrok-0.2.3 → mrok-0.3.0}/prod.Dockerfile +0 -0
  86. {mrok-0.2.3 → mrok-0.3.0}/scripts/ziti.sh +0 -0
  87. {mrok-0.2.3 → mrok-0.3.0}/sonar-project.properties +0 -0
  88. {mrok-0.2.3 → mrok-0.3.0}/tests/__init__.py +0 -0
  89. {mrok-0.2.3 → mrok-0.3.0}/tests/agent/__init__.py +0 -0
  90. {mrok-0.2.3 → mrok-0.3.0}/tests/agent/sidecar/__init__.py +0 -0
  91. {mrok-0.2.3 → mrok-0.3.0}/tests/agent/sidecar/test_app.py +0 -0
  92. {mrok-0.2.3 → mrok-0.3.0}/tests/agent/sidecar/test_main.py +0 -0
  93. {mrok-0.2.3 → mrok-0.3.0}/tests/agent/test_ziticorn.py +0 -0
  94. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/__init__.py +0 -0
  95. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/admin/__init__.py +0 -0
  96. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/admin/test_bootstrap.py +0 -0
  97. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/admin/test_utils.py +0 -0
  98. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/agent/__init__.py +0 -0
  99. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/agent/test_run.py +0 -0
  100. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/controller/__init__.py +0 -0
  101. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/controller/test_openapi.py +0 -0
  102. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/controller/test_run.py +0 -0
  103. {mrok-0.2.3 → mrok-0.3.0}/tests/cli/test_main.py +0 -0
  104. {mrok-0.2.3 → mrok-0.3.0}/tests/conftest.py +0 -0
  105. {mrok-0.2.3 → mrok-0.3.0}/tests/controller/__init__.py +0 -0
  106. {mrok-0.2.3 → mrok-0.3.0}/tests/controller/test_auth.py +0 -0
  107. {mrok-0.2.3 → mrok-0.3.0}/tests/controller/test_instances.py +0 -0
  108. {mrok-0.2.3 → mrok-0.3.0}/tests/controller/test_openapi.py +0 -0
  109. {mrok-0.2.3 → mrok-0.3.0}/tests/http/__init__.py +0 -0
  110. {mrok-0.2.3 → mrok-0.3.0}/tests/http/test_config.py +0 -0
  111. {mrok-0.2.3 → mrok-0.3.0}/tests/http/test_forwarder.py +0 -0
  112. {mrok-0.2.3 → mrok-0.3.0}/tests/http/test_lifespan.py +0 -0
  113. {mrok-0.2.3 → mrok-0.3.0}/tests/http/test_master.py +0 -0
  114. {mrok-0.2.3 → mrok-0.3.0}/tests/http/test_protocol.py +0 -0
  115. {mrok-0.2.3 → mrok-0.3.0}/tests/http/test_server.py +0 -0
  116. {mrok-0.2.3 → mrok-0.3.0}/tests/ziti/__init__.py +0 -0
  117. {mrok-0.2.3 → mrok-0.3.0}/tests/ziti/test_api.py +0 -0
  118. {mrok-0.2.3 → mrok-0.3.0}/tests/ziti/test_bootstrap.py +0 -0
  119. {mrok-0.2.3 → mrok-0.3.0}/tests/ziti/test_pki.py +0 -0
  120. {mrok-0.2.3 → mrok-0.3.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrok
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: MPT Extensions OpenZiti Orchestrator
5
5
  Author: SoftwareOne AG
6
6
  License: Apache License
@@ -21,7 +21,11 @@ from mrok.ziti.constants import (
21
21
 
22
22
 
23
23
  async def get_instances(
24
- settings: Settings, detailed: bool, extension: str | None = None, tags: list[str] | None = None
24
+ settings: Settings,
25
+ detailed: bool,
26
+ extension: str | None = None,
27
+ tags: list[str] | None = None,
28
+ online_only: bool = False,
25
29
  ) -> list[dict]:
26
30
  async with ZitiManagementAPI(settings) as api:
27
31
  tags = tags or []
@@ -29,6 +33,9 @@ async def get_instances(
29
33
  identities = [
30
34
  identity async for identity in api.identities(params={"filter": tags_to_filter(tags)})
31
35
  ]
36
+ if online_only:
37
+ identities = list(filter(lambda i: i["hasEdgeRouterConnection"], identities))
38
+
32
39
  if detailed or extension:
33
40
  for identity in identities:
34
41
  identity["services"] = [
@@ -60,10 +67,11 @@ async def get_instances(
60
67
  def render_tsv(instances: list[dict], detailed: bool) -> None:
61
68
  console = get_console()
62
69
  if detailed:
63
- console.print("id\tname\tservices\tpolicies\ttags\tcreated\tupdated")
70
+ console.print("id\tname\tstatus\tservices\tpolicies\ttags\tcreated\tupdated")
64
71
  for instance in instances:
65
72
  console.print(
66
73
  f"{instance['id']}\t{instance['name']}\t"
74
+ f"{'online' if instance['hasEdgeRouterConnection'] else 'offline'}\t"
67
75
  f"{extract_names(instance['services'], ', ')}\t"
68
76
  f"{extract_names(instance['policies'], ', ')}\t"
69
77
  f"{format_tags(instance['tags'], ', ')}\t"
@@ -71,10 +79,11 @@ def render_tsv(instances: list[dict], detailed: bool) -> None:
71
79
  f"{format_timestamp(instance['updatedAt'])}"
72
80
  )
73
81
  else:
74
- console.print("id\tname\ttags\tcreated")
82
+ console.print("id\tname\tstatus\ttags\tcreated")
75
83
  for instance in instances:
76
84
  console.print(
77
85
  f"{instance['id']}\t{instance['name']}\t"
86
+ f"{'online' if instance['hasEdgeRouterConnection'] else 'offline'}\t"
78
87
  f"{format_tags(instance['tags'], ', ')}\t"
79
88
  f"{format_timestamp(instance['createdAt'])}\t"
80
89
  )
@@ -90,6 +99,7 @@ def render_table(instances: list[dict], detailed: bool) -> None:
90
99
  )
91
100
  table.add_column("Id", style="green")
92
101
  table.add_column("Name", style="bold cyan")
102
+ table.add_column("Status", justify="center")
93
103
  if detailed:
94
104
  table.add_column("Associated services")
95
105
  table.add_column("Associated service policies")
@@ -102,6 +112,7 @@ def render_table(instances: list[dict], detailed: bool) -> None:
102
112
  row = [
103
113
  instance["id"],
104
114
  instance["name"],
115
+ "🟢" if instance["hasEdgeRouterConnection"] else "⚪",
105
116
  ]
106
117
  if detailed:
107
118
  row += [
@@ -142,6 +153,15 @@ def register(app: typer.Typer) -> None:
142
153
  show_default=True,
143
154
  ),
144
155
  ] = None,
156
+ online_only: Annotated[
157
+ bool,
158
+ typer.Option(
159
+ "--online-only",
160
+ "-o",
161
+ help="Show only connected instances",
162
+ show_default=True,
163
+ ),
164
+ ] = False,
145
165
  detailed: bool = typer.Option(
146
166
  False,
147
167
  "--detailed",
@@ -155,7 +175,7 @@ def register(app: typer.Typer) -> None:
155
175
  ),
156
176
  ):
157
177
  """List instances in OpenZiti (identities)."""
158
- instances = asyncio.run(get_instances(ctx.obj, detailed, extension, tags))
178
+ instances = asyncio.run(get_instances(ctx.obj, detailed, extension, tags, online_only))
159
179
 
160
180
  if len(instances) == 0:
161
181
  get_console().print("No instances found.")
@@ -8,14 +8,14 @@ from rich import print
8
8
  from mrok.cli.commands.admin.utils import parse_tags
9
9
  from mrok.conf import Settings
10
10
  from mrok.ziti.api import ZitiManagementAPI
11
- from mrok.ziti.services import register_extension
11
+ from mrok.ziti.services import register_service
12
12
 
13
13
  RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
14
14
 
15
15
 
16
16
  async def do_register(settings: Settings, extension_id: str, tags: list[str] | None):
17
17
  async with ZitiManagementAPI(settings) as api:
18
- await register_extension(settings, api, extension_id, tags=parse_tags(tags))
18
+ await register_service(settings, api, extension_id, tags=parse_tags(tags))
19
19
 
20
20
 
21
21
  def validate_extension_id(extension_id: str) -> str:
@@ -9,7 +9,7 @@ import typer
9
9
  from mrok.cli.commands.admin.utils import parse_tags
10
10
  from mrok.conf import Settings
11
11
  from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
12
- from mrok.ziti.identities import register_instance
12
+ from mrok.ziti.identities import register_identity
13
13
 
14
14
  RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
15
15
 
@@ -18,8 +18,8 @@ async def do_register(
18
18
  settings: Settings, extension_id: str, instance_id: str, tags: list[str] | None
19
19
  ):
20
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)
21
+ return await register_identity(
22
+ settings, mgmt_api, client_api, extension_id, instance_id, tags=parse_tags(tags)
23
23
  )
24
24
 
25
25
 
@@ -5,14 +5,14 @@ import typer
5
5
 
6
6
  from mrok.conf import Settings
7
7
  from mrok.ziti.api import ZitiManagementAPI
8
- from mrok.ziti.services import unregister_extension
8
+ from mrok.ziti.services import unregister_service
9
9
 
10
10
  RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
11
11
 
12
12
 
13
13
  async def do_unregister(settings: Settings, extension_id: str):
14
14
  async with ZitiManagementAPI(settings) as api:
15
- await unregister_extension(settings, api, extension_id)
15
+ await unregister_service(settings, api, extension_id)
16
16
 
17
17
 
18
18
  def validate_extension_id(extension_id: str):
@@ -5,14 +5,14 @@ import typer
5
5
 
6
6
  from mrok.conf import Settings
7
7
  from mrok.ziti.api import ZitiManagementAPI
8
- from mrok.ziti.identities import unregister_instance
8
+ from mrok.ziti.identities import unregister_identity
9
9
 
10
10
  RE_EXTENSION_ID = re.compile(r"(?i)EXT-\d{4}-\d{4}")
11
11
 
12
12
 
13
13
  async def do_unregister(settings: Settings, extension_id: str, instance_id: str):
14
14
  async with ZitiManagementAPI(settings) as api:
15
- await unregister_instance(api, extension_id, instance_id)
15
+ await unregister_identity(settings, api, extension_id, instance_id)
16
16
 
17
17
 
18
18
  def validate_extension_id(extension_id: str):
@@ -14,8 +14,8 @@ from mrok.ziti.errors import (
14
14
  ServiceAlreadyRegisteredError,
15
15
  ServiceNotFoundError,
16
16
  )
17
- from mrok.ziti.identities import register_instance, unregister_instance
18
- from mrok.ziti.services import register_extension, unregister_extension
17
+ from mrok.ziti.identities import register_identity, unregister_identity
18
+ from mrok.ziti.services import register_service, unregister_service
19
19
 
20
20
  logger = logging.getLogger("mrok.controller")
21
21
 
@@ -83,7 +83,7 @@ async def create_extension(
83
83
  ],
84
84
  ):
85
85
  try:
86
- service = await register_extension(settings, mgmt_api, data.extension.id, data.tags)
86
+ service = await register_service(settings, mgmt_api, data.extension.id, data.tags)
87
87
  return ExtensionRead(
88
88
  id=service["id"],
89
89
  name=service["name"],
@@ -149,7 +149,7 @@ async def delete_instance_by_id_or_extension_id(
149
149
  id_or_extension_id: str,
150
150
  ):
151
151
  try:
152
- await unregister_extension(settings, mgmt_api, id_or_extension_id)
152
+ await unregister_service(settings, mgmt_api, id_or_extension_id)
153
153
  except ServiceNotFoundError:
154
154
  raise HTTPException(
155
155
  status_code=status.HTTP_404_NOT_FOUND,
@@ -203,6 +203,7 @@ async def get_extensions(
203
203
  tags=["Instances"],
204
204
  )
205
205
  async def create_extension_instances(
206
+ settings: AppSettings,
206
207
  mgmt_api: ZitiManagementAPI,
207
208
  client_api: ZitiClientAPI,
208
209
  id_or_extension_id: str,
@@ -223,8 +224,8 @@ async def create_extension_instances(
223
224
  ],
224
225
  ):
225
226
  service = await fetch_extension_or_404(mgmt_api, id_or_extension_id)
226
- identity, identity_file = await register_instance(
227
- mgmt_api, client_api, service["name"], data.instance.id, data.tags
227
+ identity, identity_file = await register_identity(
228
+ settings, mgmt_api, client_api, service["name"], data.instance.id, data.tags
228
229
  )
229
230
  return InstanceRead(
230
231
  id=identity["id"],
@@ -299,10 +300,11 @@ async def get_instance_by_id_or_instance_id(
299
300
  tags=["Instances"],
300
301
  )
301
302
  async def delete_instance_by_id_or_instance_id(
303
+ settings: AppSettings,
302
304
  mgmt_api: ZitiManagementAPI,
303
305
  id_or_extension_id: str,
304
306
  id_or_instance_id: str,
305
307
  ):
306
308
  identity = await fetch_instance_or_404(mgmt_api, id_or_extension_id, id_or_instance_id)
307
309
  instance_id, extension_id = identity["name"].split(".")
308
- await unregister_instance(mgmt_api, extension_id, instance_id)
310
+ await unregister_identity(settings, mgmt_api, extension_id, instance_id)
@@ -1,8 +1,10 @@
1
+ import copy
1
2
  import logging
2
3
  from typing import Any
3
4
 
4
5
  import jwt
5
6
 
7
+ from mrok.conf import Settings
6
8
  from mrok.ziti import pki
7
9
  from mrok.ziti.api import TagsType, ZitiClientAPI, ZitiManagementAPI
8
10
  from mrok.ziti.constants import (
@@ -16,31 +18,37 @@ from mrok.ziti.errors import (
16
18
  ServiceNotFoundError,
17
19
  UserIdentityNotFoundError,
18
20
  )
21
+ from mrok.ziti.services import register_service, unregister_service
19
22
 
20
23
  logger = logging.getLogger("mrok.ziti")
21
24
 
22
25
 
23
- async def register_instance(
26
+ async def register_identity(
27
+ settings: Settings,
24
28
  mgmt_api: ZitiManagementAPI,
25
29
  client_api: ZitiClientAPI,
26
- extension_id: str,
27
- instance_id: str,
30
+ service_external_id: str,
31
+ identity_external_id: str,
28
32
  tags: TagsType | None = None,
29
33
  ):
30
- service_name = extension_id.lower()
31
- tags = tags or {}
32
- tags[MROK_SERVICE_TAG_NAME] = service_name
33
- tags[MROK_IDENTITY_TYPE_TAG_NAME] = MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE
34
+ service_name = service_external_id.lower()
35
+ identity_tags = copy.copy(tags or {})
36
+ identity_tags[MROK_SERVICE_TAG_NAME] = service_name
37
+ identity_tags[MROK_IDENTITY_TYPE_TAG_NAME] = MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE
34
38
  service = await mgmt_api.search_service(service_name)
35
39
  if not service:
36
- raise ServiceNotFoundError(f"A service with name `{extension_id}` does not exists.")
40
+ raise ServiceNotFoundError(f"A service with name `{service_external_id}` does not exists.")
37
41
 
38
- identity_name = f"{instance_id.lower()}.{service_name}"
42
+ identity_name = f"{identity_external_id.lower()}.{service_name}"
39
43
  service_policy_name = f"{identity_name}:bind"
44
+ self_service_policy_name = f"self.{service_policy_name}"
40
45
 
41
46
  identity = await mgmt_api.search_identity(identity_name)
42
47
  if identity:
43
48
  service_policy = await mgmt_api.search_service_policy(service_policy_name)
49
+ if service_policy:
50
+ await mgmt_api.delete_service_policy(service_policy["id"])
51
+ service_policy = await mgmt_api.search_service_policy(self_service_policy_name)
44
52
  if service_policy:
45
53
  await mgmt_api.delete_service_policy(service_policy["id"])
46
54
  router_policy = await mgmt_api.search_router_policy(identity_name)
@@ -48,7 +56,7 @@ async def register_instance(
48
56
  await mgmt_api.delete_router_policy(router_policy["id"])
49
57
  await mgmt_api.delete_identity(identity["id"])
50
58
 
51
- identity_id = await mgmt_api.create_user_identity(identity_name, tags=tags)
59
+ identity_id = await mgmt_api.create_user_identity(identity_name, tags=identity_tags)
52
60
  identity = await mgmt_api.get_identity(identity_id)
53
61
 
54
62
  identity_json = await _enroll_identity(
@@ -58,33 +66,55 @@ async def register_instance(
58
66
  identity,
59
67
  mrok={
60
68
  "identity": identity_name,
61
- "extension": extension_id,
62
- "instance": instance_id,
69
+ "extension": service_external_id,
70
+ "instance": identity_external_id,
71
+ "domain": settings.proxy.domain,
72
+ "tags": identity_tags,
63
73
  },
64
74
  )
65
75
 
76
+ self_service = await mgmt_api.search_service(identity_name)
77
+ if not self_service:
78
+ self_service = await register_service(settings, mgmt_api, identity_name, tags)
79
+
66
80
  await mgmt_api.create_bind_service_policy(service_policy_name, service["id"], identity_id)
81
+ await mgmt_api.create_bind_service_policy(
82
+ self_service_policy_name,
83
+ self_service["id"],
84
+ identity_id,
85
+ )
67
86
  await mgmt_api.create_router_policy(identity_name, identity_id)
68
87
 
69
88
  return identity, identity_json
70
89
 
71
90
 
72
- async def unregister_instance(
91
+ async def unregister_identity(
92
+ settings: Settings,
73
93
  mgmt_api: ZitiManagementAPI,
74
- extension_id: str,
75
- instance_id: str,
94
+ service_external_id: str,
95
+ identity_external_id: str,
76
96
  ):
77
- service_name = extension_id.lower()
97
+ service_name = service_external_id.lower()
78
98
  service = await mgmt_api.search_service(service_name)
79
99
  if not service:
80
- raise ServiceNotFoundError(f"A service with name `{extension_id}` does not exists.")
100
+ raise ServiceNotFoundError(f"A service with name `{service_external_id}` does not exists.")
81
101
 
82
- identity_name = f"{instance_id.lower()}.{service_name}"
102
+ identity_name = f"{identity_external_id.lower()}.{service_name}"
83
103
  service_policy_name = f"{identity_name}:bind"
84
104
 
85
105
  identity = await mgmt_api.search_identity(identity_name)
86
106
  if not identity:
87
- raise UserIdentityNotFoundError(f"Instance `{instance_id}` not found.")
107
+ raise UserIdentityNotFoundError(f"Identity `{identity_external_id}` not found.")
108
+
109
+ self_service_policy_name = f"self.{service_policy_name}"
110
+
111
+ service_policy = await mgmt_api.search_service_policy(self_service_policy_name)
112
+ if service_policy:
113
+ await mgmt_api.delete_service_policy(service_policy["id"])
114
+
115
+ self_service = await mgmt_api.search_service(identity_name)
116
+ if self_service:
117
+ await unregister_service(settings, mgmt_api, identity_name)
88
118
 
89
119
  service_policy = await mgmt_api.search_service_policy(service_policy_name)
90
120
  if service_policy:
@@ -120,7 +150,7 @@ async def _enroll_identity(
120
150
  client_api: ZitiClientAPI,
121
151
  identity_id: str,
122
152
  identity: dict[str, Any] | None = None,
123
- mrok: dict[str, str] | None = None,
153
+ mrok: dict[str, str | dict] | None = None,
124
154
  ):
125
155
  if identity is None:
126
156
  identity = await mgmt_api.get_identity(identity_id)
@@ -13,10 +13,10 @@ from mrok.ziti.errors import (
13
13
  logger = logging.getLogger(__name__)
14
14
 
15
15
 
16
- async def register_extension(
17
- settings: Settings, mgmt_api: ZitiManagementAPI, extension_id: str, tags: TagsType | None
16
+ async def register_service(
17
+ settings: Settings, mgmt_api: ZitiManagementAPI, external_id: str, tags: TagsType | None
18
18
  ) -> dict[str, Any]:
19
- service_name = extension_id.lower()
19
+ service_name = external_id.lower()
20
20
  registered = False
21
21
  proxy_identity = await mgmt_api.search_identity(settings.proxy.identity)
22
22
  if not proxy_identity:
@@ -58,17 +58,17 @@ async def register_extension(
58
58
  await mgmt_api.create_service_router_policy(service_name, service_id, tags=tags)
59
59
  registered = True
60
60
  if not registered:
61
- raise ServiceAlreadyRegisteredError(f"Extension `{extension_id}` already registered.")
61
+ raise ServiceAlreadyRegisteredError(f"Service `{external_id}` already registered.")
62
62
  return service
63
63
 
64
64
 
65
- async def unregister_extension(
66
- settings: Settings, mgmt_api: ZitiManagementAPI, extension_id: str
65
+ async def unregister_service(
66
+ settings: Settings, mgmt_api: ZitiManagementAPI, external_id: str
67
67
  ) -> None:
68
- service_name = extension_id.lower()
68
+ service_name = external_id.lower()
69
69
  service = await mgmt_api.search_service(service_name)
70
70
  if not service:
71
- raise ServiceNotFoundError(f"Extension `{extension_id}` not found.")
71
+ raise ServiceNotFoundError(f"Service `{external_id}` not found.")
72
72
 
73
73
  router_policy = await mgmt_api.search_service_router_policy(service_name)
74
74
  if router_policy:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mrok"
3
- version = "0.2.3"
3
+ version = "0.3.0"
4
4
  description = "MPT Extensions OpenZiti Orchestrator"
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  authors = [
@@ -5,8 +5,8 @@ proxy:
5
5
 
6
6
  ziti:
7
7
  api:
8
- client: https://ziti.exts.s1.today:80
9
- management: https://ziti.exts.s1.today:80
8
+ client: https://192.168.64.233:8444
9
+ management: https://192.168.64.233:8443
10
10
  read_timeout: 30
11
11
  ssl_verify: False
12
12
  auth:
@@ -162,8 +162,8 @@ def test_list_instances_command(
162
162
  ):
163
163
  settings = settings_factory()
164
164
  mocker.patch("mrok.cli.main.get_settings", return_value=settings)
165
-
166
- mock_get = mocker.AsyncMock(
165
+ mock_get = mocker.patch(
166
+ "mrok.cli.commands.admin.list.instances.get_instances",
167
167
  return_value=[
168
168
  {
169
169
  "id": "idt",
@@ -171,15 +171,12 @@ def test_list_instances_command(
171
171
  "tags": [],
172
172
  "services": [],
173
173
  "policies": [],
174
+ "hasEdgeRouterConnection": False,
174
175
  "createdAt": "2025-10-13T09:51:06.175Z",
175
176
  "updatedAt": "2025-10-13T09:51:06.175Z",
176
177
  },
177
178
  ],
178
179
  )
179
- mocker.patch(
180
- "mrok.cli.commands.admin.list.instances.get_instances",
181
- new=mock_get,
182
- )
183
180
 
184
181
  runner = CliRunner()
185
182
  # Run the command
@@ -187,9 +184,8 @@ def test_list_instances_command(
187
184
  app,
188
185
  shlex.split(f"admin list instances {output} {detailed}"),
189
186
  )
190
-
191
187
  assert result.exit_code == 0
192
- mock_get.assert_called_once_with(settings, detailed == "-d", None, None)
188
+ mock_get.assert_called_once_with(settings, detailed == "-d", None, None, False)
193
189
 
194
190
 
195
191
  def test_list_instances_command_with_tag(
@@ -213,11 +209,14 @@ def test_list_instances_command_with_tag(
213
209
  )
214
210
 
215
211
  assert "No instances found" in result.output
216
- mock_get.assert_called_once_with(settings, False, None, ["mytag=v1"])
212
+ mock_get.assert_called_once_with(settings, False, None, ["mytag=v1"], False)
217
213
 
218
214
 
219
215
  @pytest.mark.asyncio
220
- async def test_list_instances(settings_factory: SettingsFactory, httpx_mock: HTTPXMock):
216
+ async def test_list_instances_with_extension_filter(
217
+ settings_factory: SettingsFactory,
218
+ httpx_mock: HTTPXMock,
219
+ ):
221
220
  settings = settings_factory()
222
221
  tag_filter = f'tags.{MROK_IDENTITY_TYPE_TAG_NAME}="{MROK_IDENTITY_TYPE_TAG_VALUE_INSTANCE}"'
223
222
  url = f"{settings.ziti.api.management}/edge/management/v1/identities"
@@ -230,17 +229,35 @@ async def test_list_instances(settings_factory: SettingsFactory, httpx_mock: HTT
230
229
  "data": [{"id": "idt", "name": "identity"}],
231
230
  },
232
231
  )
232
+ httpx_mock.add_response(
233
+ method="GET",
234
+ url=f"{url}/idt/services?limit=5&offset=0",
235
+ json={
236
+ "meta": {"pagination": {"totalCount": 1, "limit": 5, "offset": 0}},
237
+ "data": [{"id": "svc", "name": "svc"}],
238
+ },
239
+ )
240
+ httpx_mock.add_response(
241
+ method="GET",
242
+ url=f"{url}/idt/service-policies?limit=5&offset=0",
243
+ json={
244
+ "meta": {"pagination": {"totalCount": 1, "limit": 5, "offset": 0}},
245
+ "data": [{"id": "plc", "name": "svc-policy"}],
246
+ },
247
+ )
233
248
 
234
- instances = await get_instances(settings, False)
249
+ instances = await get_instances(settings, False, "svc")
235
250
  assert len(instances) == 1
236
251
  assert instances[0] == {
237
252
  "id": "idt",
238
253
  "name": "identity",
254
+ "services": [{"id": "svc", "name": "svc"}],
255
+ "policies": [{"id": "plc", "name": "svc-policy"}],
239
256
  }
240
257
 
241
258
 
242
259
  @pytest.mark.asyncio
243
- async def test_list_instances_with_extension_filter(
260
+ async def test_list_instances_with_online_only_filter(
244
261
  settings_factory: SettingsFactory,
245
262
  httpx_mock: HTTPXMock,
246
263
  ):
@@ -252,8 +269,11 @@ async def test_list_instances_with_extension_filter(
252
269
  method="GET",
253
270
  url=f"{url}?filter={tag_filter}&limit=5&offset=0",
254
271
  json={
255
- "meta": {"pagination": {"totalCount": 1, "limit": 5, "offset": 0}},
256
- "data": [{"id": "idt", "name": "identity"}],
272
+ "meta": {"pagination": {"totalCount": 2, "limit": 5, "offset": 0}},
273
+ "data": [
274
+ {"id": "idt", "name": "identity", "hasEdgeRouterConnection": True},
275
+ {"id": "idt2", "name": "identity", "hasEdgeRouterConnection": False},
276
+ ],
257
277
  },
258
278
  )
259
279
  httpx_mock.add_response(
@@ -273,11 +293,12 @@ async def test_list_instances_with_extension_filter(
273
293
  },
274
294
  )
275
295
 
276
- instances = await get_instances(settings, False, "svc")
296
+ instances = await get_instances(settings, False, "svc", online_only=True)
277
297
  assert len(instances) == 1
278
298
  assert instances[0] == {
279
299
  "id": "idt",
280
300
  "name": "identity",
281
301
  "services": [{"id": "svc", "name": "svc"}],
282
302
  "policies": [{"id": "plc", "name": "svc-policy"}],
303
+ "hasEdgeRouterConnection": True,
283
304
  }
@@ -73,7 +73,7 @@ async def test_do_register_extension(mocker: MockerFixture, settings_factory: Se
73
73
  return_value=mocked_api,
74
74
  )
75
75
  mocked_register_service = mocker.patch(
76
- "mrok.cli.commands.admin.register.extensions.register_extension"
76
+ "mrok.cli.commands.admin.register.extensions.register_service"
77
77
  )
78
78
 
79
79
  await do_register_extension(settings, "EXT-1234", ["tag=value"])
@@ -162,12 +162,17 @@ async def test_do_register_instance(mocker: MockerFixture, settings_factory: Set
162
162
  return_value=mocked_cli_api,
163
163
  )
164
164
  mocked_register_instance = mocker.patch(
165
- "mrok.cli.commands.admin.register.instances.register_instance",
165
+ "mrok.cli.commands.admin.register.instances.register_identity",
166
166
  )
167
167
 
168
168
  await do_register_instance(settings, "EXT-1234", "INS-1234-0001", ["tag=value"])
169
169
  mocked_register_instance.assert_awaited_once_with(
170
- mocked_mgmt_api, mocked_cli_api, "EXT-1234", "INS-1234-0001", tags={"tag": "value"}
170
+ settings,
171
+ mocked_mgmt_api,
172
+ mocked_cli_api,
173
+ "EXT-1234",
174
+ "INS-1234-0001",
175
+ tags={"tag": "value"},
171
176
  )
172
177
  mocked_mgmt_api_ctor.assert_called_once_with(settings)
173
178
  mocked_cli_api_ctor.assert_called_once_with(settings)
@@ -69,7 +69,7 @@ async def test_do_unregister_extension(mocker: MockerFixture, settings_factory:
69
69
  return_value=mocked_api,
70
70
  )
71
71
  mocked_unregister_service = mocker.patch(
72
- "mrok.cli.commands.admin.unregister.extensions.unregister_extension"
72
+ "mrok.cli.commands.admin.unregister.extensions.unregister_service"
73
73
  )
74
74
 
75
75
  await do_unregister_extension(settings, "EXT-1234")
@@ -144,11 +144,12 @@ async def test_do_unregister_instance(mocker: MockerFixture, settings_factory: S
144
144
  return_value=mocked_api,
145
145
  )
146
146
  mocked_unregister_service = mocker.patch(
147
- "mrok.cli.commands.admin.unregister.instances.unregister_instance"
147
+ "mrok.cli.commands.admin.unregister.instances.unregister_identity"
148
148
  )
149
149
 
150
150
  await do_unregister_instance(settings, "EXT-1234", "INS-1234-0001")
151
151
  mocked_unregister_service.assert_awaited_once_with(
152
+ settings,
152
153
  mocked_api,
153
154
  "EXT-1234",
154
155
  "INS-1234-0001",