mrok 0.1.9__tar.gz → 0.2.1__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 (121) hide show
  1. {mrok-0.1.9 → mrok-0.2.1}/PKG-INFO +2 -1
  2. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/app.py +3 -1
  3. mrok-0.2.1/mrok/controller/routes/instances.py +71 -0
  4. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/pki.py +10 -5
  5. {mrok-0.1.9 → mrok-0.2.1}/pyproject.toml +2 -1
  6. mrok-0.1.9/tests/controller/test_instances.py → mrok-0.2.1/tests/controller/test_extensions.py +228 -10
  7. mrok-0.2.1/tests/controller/test_instances.py +157 -0
  8. mrok-0.2.1/tests/ziti/__init__.py +0 -0
  9. {mrok-0.1.9 → mrok-0.2.1}/uv.lock +11 -0
  10. mrok-0.1.9/tests/controller/test_extensions.py +0 -221
  11. {mrok-0.1.9 → mrok-0.2.1}/.github/actions/setup-python-env/action.yml +0 -0
  12. {mrok-0.1.9 → mrok-0.2.1}/.github/workflows/assets/turing_team_pr_bot.png +0 -0
  13. {mrok-0.1.9 → mrok-0.2.1}/.github/workflows/notify-pr-closed.yaml +0 -0
  14. {mrok-0.1.9 → mrok-0.2.1}/.github/workflows/notify-pr-reviewed.yml +0 -0
  15. {mrok-0.1.9 → mrok-0.2.1}/.github/workflows/pr-build-merge.yaml +0 -0
  16. {mrok-0.1.9 → mrok-0.2.1}/.github/workflows/release.yml +0 -0
  17. {mrok-0.1.9 → mrok-0.2.1}/.gitignore +0 -0
  18. {mrok-0.1.9 → mrok-0.2.1}/.pre-commit-config.yaml +0 -0
  19. {mrok-0.1.9 → mrok-0.2.1}/.python-version +0 -0
  20. {mrok-0.1.9 → mrok-0.2.1}/LICENSE.txt +0 -0
  21. {mrok-0.1.9 → mrok-0.2.1}/README.md +0 -0
  22. {mrok-0.1.9 → mrok-0.2.1}/dev.Dockerfile +0 -0
  23. {mrok-0.1.9 → mrok-0.2.1}/docker-compose.yaml +0 -0
  24. {mrok-0.1.9 → mrok-0.2.1}/entrypoint.sh +0 -0
  25. {mrok-0.1.9 → mrok-0.2.1}/mrok/__init__.py +0 -0
  26. {mrok-0.1.9 → mrok-0.2.1}/mrok/agent/__init__.py +0 -0
  27. {mrok-0.1.9 → mrok-0.2.1}/mrok/agent/sidecar/__init__.py +0 -0
  28. {mrok-0.1.9 → mrok-0.2.1}/mrok/agent/sidecar/app.py +0 -0
  29. {mrok-0.1.9 → mrok-0.2.1}/mrok/agent/sidecar/main.py +0 -0
  30. {mrok-0.1.9 → mrok-0.2.1}/mrok/agent/ziticorn.py +0 -0
  31. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/__init__.py +0 -0
  32. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/__init__.py +0 -0
  33. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/__init__.py +0 -0
  34. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/bootstrap.py +0 -0
  35. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/list/__init__.py +0 -0
  36. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/list/extensions.py +0 -0
  37. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/list/instances.py +0 -0
  38. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/register/__init__.py +0 -0
  39. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/register/extensions.py +0 -0
  40. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/register/instances.py +0 -0
  41. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/unregister/__init__.py +0 -0
  42. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/unregister/extensions.py +0 -0
  43. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/unregister/instances.py +0 -0
  44. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/admin/utils.py +0 -0
  45. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/agent/__init__.py +0 -0
  46. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/agent/run/__init__.py +0 -0
  47. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/agent/run/asgi.py +0 -0
  48. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/agent/run/sidecar.py +0 -0
  49. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/controller/__init__.py +0 -0
  50. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/controller/openapi.py +0 -0
  51. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/commands/controller/run.py +0 -0
  52. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/main.py +0 -0
  53. {mrok-0.1.9 → mrok-0.2.1}/mrok/cli/rich.py +0 -0
  54. {mrok-0.1.9 → mrok-0.2.1}/mrok/conf.py +0 -0
  55. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/__init__.py +0 -0
  56. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/auth.py +0 -0
  57. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/dependencies/__init__.py +0 -0
  58. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/dependencies/conf.py +0 -0
  59. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/dependencies/ziti.py +0 -0
  60. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/openapi/__init__.py +0 -0
  61. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/openapi/examples.py +0 -0
  62. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/openapi/utils.py +0 -0
  63. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/pagination.py +0 -0
  64. {mrok-0.1.9/mrok/http → mrok-0.2.1/mrok/controller/routes}/__init__.py +0 -0
  65. /mrok-0.1.9/mrok/controller/routes.py → /mrok-0.2.1/mrok/controller/routes/extensions.py +0 -0
  66. {mrok-0.1.9 → mrok-0.2.1}/mrok/controller/schemas.py +0 -0
  67. {mrok-0.1.9 → mrok-0.2.1}/mrok/errors.py +0 -0
  68. {mrok-0.1.9/tests → mrok-0.2.1/mrok/http}/__init__.py +0 -0
  69. {mrok-0.1.9 → mrok-0.2.1}/mrok/http/config.py +0 -0
  70. {mrok-0.1.9 → mrok-0.2.1}/mrok/http/forwarder.py +0 -0
  71. {mrok-0.1.9 → mrok-0.2.1}/mrok/http/lifespan.py +0 -0
  72. {mrok-0.1.9 → mrok-0.2.1}/mrok/http/master.py +0 -0
  73. {mrok-0.1.9 → mrok-0.2.1}/mrok/http/protocol.py +0 -0
  74. {mrok-0.1.9 → mrok-0.2.1}/mrok/http/server.py +0 -0
  75. {mrok-0.1.9 → mrok-0.2.1}/mrok/logging.py +0 -0
  76. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/__init__.py +0 -0
  77. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/api.py +0 -0
  78. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/bootstrap.py +0 -0
  79. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/constants.py +0 -0
  80. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/errors.py +0 -0
  81. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/identities.py +0 -0
  82. {mrok-0.1.9 → mrok-0.2.1}/mrok/ziti/services.py +0 -0
  83. {mrok-0.1.9 → mrok-0.2.1}/prod.Dockerfile +0 -0
  84. {mrok-0.1.9 → mrok-0.2.1}/scripts/ziti.sh +0 -0
  85. {mrok-0.1.9 → mrok-0.2.1}/settings.yaml +0 -0
  86. {mrok-0.1.9 → mrok-0.2.1}/sonar-project.properties +0 -0
  87. {mrok-0.1.9/tests/agent → mrok-0.2.1/tests}/__init__.py +0 -0
  88. {mrok-0.1.9/tests/agent/sidecar → mrok-0.2.1/tests/agent}/__init__.py +0 -0
  89. {mrok-0.1.9/tests/cli → mrok-0.2.1/tests/agent/sidecar}/__init__.py +0 -0
  90. {mrok-0.1.9 → mrok-0.2.1}/tests/agent/sidecar/test_app.py +0 -0
  91. {mrok-0.1.9 → mrok-0.2.1}/tests/agent/sidecar/test_main.py +0 -0
  92. {mrok-0.1.9 → mrok-0.2.1}/tests/agent/test_ziticorn.py +0 -0
  93. {mrok-0.1.9/tests/cli/admin → mrok-0.2.1/tests/cli}/__init__.py +0 -0
  94. {mrok-0.1.9/tests/cli/agent → mrok-0.2.1/tests/cli/admin}/__init__.py +0 -0
  95. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/admin/test_bootstrap.py +0 -0
  96. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/admin/test_list.py +0 -0
  97. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/admin/test_register.py +0 -0
  98. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/admin/test_unregister.py +0 -0
  99. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/admin/test_utils.py +0 -0
  100. {mrok-0.1.9/tests/cli/controller → mrok-0.2.1/tests/cli/agent}/__init__.py +0 -0
  101. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/agent/test_run.py +0 -0
  102. {mrok-0.1.9/tests → mrok-0.2.1/tests/cli}/controller/__init__.py +0 -0
  103. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/controller/test_openapi.py +0 -0
  104. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/controller/test_run.py +0 -0
  105. {mrok-0.1.9 → mrok-0.2.1}/tests/cli/test_main.py +0 -0
  106. {mrok-0.1.9 → mrok-0.2.1}/tests/conftest.py +0 -0
  107. {mrok-0.1.9/tests/http → mrok-0.2.1/tests/controller}/__init__.py +0 -0
  108. {mrok-0.1.9 → mrok-0.2.1}/tests/controller/test_auth.py +0 -0
  109. {mrok-0.1.9 → mrok-0.2.1}/tests/controller/test_openapi.py +0 -0
  110. {mrok-0.1.9/tests/ziti → mrok-0.2.1/tests/http}/__init__.py +0 -0
  111. {mrok-0.1.9 → mrok-0.2.1}/tests/http/test_config.py +0 -0
  112. {mrok-0.1.9 → mrok-0.2.1}/tests/http/test_forwarder.py +0 -0
  113. {mrok-0.1.9 → mrok-0.2.1}/tests/http/test_lifespan.py +0 -0
  114. {mrok-0.1.9 → mrok-0.2.1}/tests/http/test_master.py +0 -0
  115. {mrok-0.1.9 → mrok-0.2.1}/tests/http/test_protocol.py +0 -0
  116. {mrok-0.1.9 → mrok-0.2.1}/tests/http/test_server.py +0 -0
  117. {mrok-0.1.9 → mrok-0.2.1}/tests/ziti/test_api.py +0 -0
  118. {mrok-0.1.9 → mrok-0.2.1}/tests/ziti/test_bootstrap.py +0 -0
  119. {mrok-0.1.9 → mrok-0.2.1}/tests/ziti/test_identities.py +0 -0
  120. {mrok-0.1.9 → mrok-0.2.1}/tests/ziti/test_pki.py +0 -0
  121. {mrok-0.1.9 → mrok-0.2.1}/tests/ziti/test_services.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mrok
3
- Version: 0.1.9
3
+ Version: 0.2.1
4
4
  Summary: MPT Extensions OpenZiti Orchestrator
5
5
  Author: SoftwareOne AG
6
6
  License: Apache License
@@ -206,6 +206,7 @@ License: Apache License
206
206
  limitations under the License.
207
207
  License-File: LICENSE.txt
208
208
  Requires-Python: <4,>=3.12
209
+ Requires-Dist: asn1crypto<2.0.0,>=1.5.1
209
210
  Requires-Dist: cryptography<46.0.0,>=45.0.7
210
211
  Requires-Dist: dynaconf<4.0.0,>=3.2.11
211
212
  Requires-Dist: fastapi-pagination<0.15.0,>=0.14.1
@@ -8,7 +8,8 @@ from fastapi.routing import APIRoute, APIRouter
8
8
  from mrok.conf import get_settings
9
9
  from mrok.controller.auth import authenticate
10
10
  from mrok.controller.openapi import generate_openapi_spec
11
- from mrok.controller.routes import router as extensions_router
11
+ from mrok.controller.routes.extensions import router as extensions_router
12
+ from mrok.controller.routes.instances import router as instances_router
12
13
 
13
14
  logger = logging.getLogger(__name__)
14
15
 
@@ -52,6 +53,7 @@ def setup_app():
52
53
  app.include_router(
53
54
  extensions_router, prefix="/extensions", dependencies=[Depends(authenticate)]
54
55
  )
56
+ app.include_router(instances_router, prefix="/instances", dependencies=[Depends(authenticate)])
55
57
 
56
58
  settings = get_settings()
57
59
 
@@ -0,0 +1,71 @@
1
+ import logging
2
+
3
+ from fastapi import APIRouter, HTTPException, status
4
+
5
+ from mrok.controller.dependencies import ZitiManagementAPI
6
+ from mrok.controller.openapi import examples
7
+ from mrok.controller.pagination import LimitOffsetPage, paginate
8
+ from mrok.controller.schemas import InstanceRead
9
+
10
+ logger = logging.getLogger("mrok.controller")
11
+
12
+ router = APIRouter()
13
+
14
+
15
+ async def fetch_instance_or_404(mgmt_api: ZitiManagementAPI, id_or_instance_id: str):
16
+ identity = await mgmt_api.search_identity(id_or_instance_id)
17
+ if not identity:
18
+ raise HTTPException(
19
+ status_code=status.HTTP_404_NOT_FOUND,
20
+ )
21
+ return identity
22
+
23
+
24
+ @router.get(
25
+ "/{id_or_instance_id}",
26
+ response_model=InstanceRead,
27
+ responses={
28
+ 200: {
29
+ "description": "Instance",
30
+ "content": {"application/json": {"example": examples.INSTANCE_RESPONSE}},
31
+ },
32
+ },
33
+ dependencies=[],
34
+ tags=["Instances"],
35
+ )
36
+ async def get_instance_by_id_or_instance_id(
37
+ mgmt_api: ZitiManagementAPI,
38
+ id_or_instance_id: str,
39
+ ):
40
+ identity = await fetch_instance_or_404(mgmt_api, id_or_instance_id)
41
+ return InstanceRead(**identity)
42
+
43
+
44
+ @router.get(
45
+ "",
46
+ response_model=LimitOffsetPage[InstanceRead],
47
+ responses={
48
+ 200: {
49
+ "description": "List of Instances",
50
+ "content": {
51
+ "application/json": {
52
+ "example": {
53
+ "data": [examples.INSTANCE_RESPONSE],
54
+ "$meta": {
55
+ "pagination": {
56
+ "total": 1,
57
+ "limit": 10,
58
+ "offset": 0,
59
+ },
60
+ },
61
+ },
62
+ },
63
+ },
64
+ },
65
+ },
66
+ tags=["Instances"],
67
+ )
68
+ async def get_instances(
69
+ mgmt_api: ZitiManagementAPI,
70
+ ):
71
+ return await paginate(mgmt_api, "/identities", InstanceRead)
@@ -1,9 +1,9 @@
1
1
  import base64
2
2
 
3
+ from asn1crypto import cms
3
4
  from cryptography import x509
4
5
  from cryptography.hazmat.primitives import hashes, serialization
5
6
  from cryptography.hazmat.primitives.asymmetric import rsa
6
- from cryptography.hazmat.primitives.serialization.pkcs7 import load_der_pkcs7_certificates
7
7
  from cryptography.x509.oid import NameOID
8
8
 
9
9
  from mrok.ziti.api import ZitiManagementAPI
@@ -16,11 +16,16 @@ async def get_ca_certificates(mgmt_api: ZitiManagementAPI) -> str:
16
16
  if not _ca_certificates:
17
17
  cas_pkcs7 = await mgmt_api.fetch_ca_certificates()
18
18
  pkcs7_bytes = base64.b64decode(cas_pkcs7)
19
- pkcs7_certs = load_der_pkcs7_certificates(pkcs7_bytes)
19
+
20
+ content_info = cms.ContentInfo.load(pkcs7_bytes)
21
+ certs = content_info["content"]["certificates"]
22
+
20
23
  ca_certificates = []
21
- for cert in pkcs7_certs:
22
- cert_pem = cert.public_bytes(serialization.Encoding.PEM)
23
- ca_certificates.append(cert_pem.decode("utf-8"))
24
+ for cert in certs:
25
+ crypt_cert = x509.load_der_x509_certificate(cert.dump())
26
+ pem = crypt_cert.public_bytes(serialization.Encoding.PEM).decode("utf-8")
27
+ ca_certificates.append(pem)
28
+
24
29
  _ca_certificates = "\n".join(ca_certificates)
25
30
  return _ca_certificates
26
31
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mrok"
3
- version = "0.1.9"
3
+ version = "0.2.1"
4
4
  description = "MPT Extensions OpenZiti Orchestrator"
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  authors = [
@@ -9,6 +9,7 @@ authors = [
9
9
  license = { file = "LICENSE.txt" }
10
10
  requires-python = ">=3.12,<4"
11
11
  dependencies = [
12
+ "asn1crypto>=1.5.1,<2.0.0",
12
13
  "cryptography>=45.0.7,<46.0.0",
13
14
  "dynaconf>=3.2.11,<4.0.0",
14
15
  "fastapi-pagination>=0.14.1,<0.15.0",
@@ -1,15 +1,227 @@
1
1
  from urllib.parse import quote
2
2
 
3
3
  import pytest
4
+ from dynaconf.base import LazySettings
4
5
  from httpx import AsyncClient
5
6
  from pytest_httpx import HTTPXMock
6
7
  from pytest_mock import MockerFixture
7
8
 
8
9
  from mrok.ziti.api import ZitiClientAPI, ZitiManagementAPI
9
10
  from mrok.ziti.constants import MROK_SERVICE_TAG_NAME, MROK_VERSION_TAG_NAME
11
+ from mrok.ziti.errors import (
12
+ ConfigTypeNotFoundError,
13
+ MrokError,
14
+ ProxyIdentityNotFoundError,
15
+ ServiceAlreadyRegisteredError,
16
+ ServiceNotFoundError,
17
+ )
10
18
  from tests.conftest import SettingsFactory
11
19
 
12
20
 
21
+ @pytest.mark.asyncio
22
+ async def test_list_extensions(
23
+ api_client: AsyncClient, settings_factory: SettingsFactory, httpx_mock: HTTPXMock
24
+ ):
25
+ settings = settings_factory()
26
+ httpx_mock.add_response(
27
+ method="GET",
28
+ url=f"{settings.ziti.api.management}/edge/management/v1/services?limit=5&offset=0",
29
+ json={
30
+ "meta": {"pagination": {"totalCount": 10, "limit": 5, "offset": 0}},
31
+ "data": [{"id": f"svc{i}", "name": "svc"} for i in range(5)],
32
+ },
33
+ )
34
+ httpx_mock.add_response(
35
+ method="GET",
36
+ url=f"{settings.ziti.api.management}/edge/management/v1/services?limit=5&offset=5",
37
+ json={
38
+ "meta": {"pagination": {"totalCount": 10, "limit": 5, "offset": 5}},
39
+ "data": [{"id": f"svc{5 + i}", "name": "svc"} for i in range(5)],
40
+ },
41
+ )
42
+ response = await api_client.get("/extensions?limit=5&offset=0")
43
+ assert response.status_code == 200
44
+ page_1 = response.json()
45
+ assert len(page_1["data"]) == 5
46
+ assert page_1["$meta"]["pagination"]["offset"] == 0
47
+
48
+ response = await api_client.get("/extensions?limit=5&offset=5")
49
+ assert response.status_code == 200
50
+ page_2 = response.json()
51
+ assert len(page_2["data"]) == 5
52
+ assert page_2["$meta"]["pagination"]["offset"] == 5
53
+
54
+ assert sorted(page_1["data"], key=lambda x: x["id"]) != sorted(
55
+ page_2["data"], key=lambda x: x["id"]
56
+ )
57
+
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_register_extension(mocker: MockerFixture, api_client: AsyncClient):
61
+ mocked_register = mocker.patch(
62
+ "mrok.controller.routes.extensions.register_extension",
63
+ return_value={
64
+ "id": "a1b2cd",
65
+ "name": "ext-1234-5678",
66
+ "tags": {
67
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
68
+ "account": "ACC-1234-5678",
69
+ },
70
+ },
71
+ )
72
+
73
+ response = await api_client.post(
74
+ "/extensions",
75
+ json={
76
+ "extension": {"id": "EXT-1234-5678"},
77
+ "tags": {"account": "ACC-1234-5678"},
78
+ },
79
+ )
80
+ assert response.status_code == 201
81
+ assert response.json() == {
82
+ "id": "a1b2cd",
83
+ "extension": {"id": "EXT-1234-5678"},
84
+ "name": "ext-1234-5678",
85
+ "tags": {
86
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
87
+ "account": "ACC-1234-5678",
88
+ },
89
+ }
90
+ assert mocked_register.call_count == 1
91
+ assert isinstance(mocked_register.mock_calls[0].args[0], LazySettings)
92
+ assert isinstance(mocked_register.mock_calls[0].args[1], ZitiManagementAPI)
93
+ assert mocked_register.mock_calls[0].args[2] == "EXT-1234-5678"
94
+ assert mocked_register.mock_calls[0].args[3] == {"account": "ACC-1234-5678"}
95
+
96
+
97
+ @pytest.mark.asyncio
98
+ async def test_register_extension_already_exists(mocker: MockerFixture, api_client: AsyncClient):
99
+ mocker.patch(
100
+ "mrok.controller.routes.extensions.register_extension",
101
+ side_effect=ServiceAlreadyRegisteredError("Extension `EXT-1234-5678` already registered."),
102
+ )
103
+
104
+ response = await api_client.post(
105
+ "/extensions",
106
+ json={
107
+ "extension": {"id": "EXT-1234-5678"},
108
+ "tags": {"account": "ACC-1234-5678"},
109
+ },
110
+ )
111
+ assert response.status_code == 400
112
+ assert response.json() == {"detail": "Extension `EXT-1234-5678` already registered."}
113
+
114
+
115
+ @pytest.mark.asyncio
116
+ @pytest.mark.parametrize("exc_type", [ProxyIdentityNotFoundError, ConfigTypeNotFoundError])
117
+ async def test_register_extension_mrok_not_configured(
118
+ mocker: MockerFixture,
119
+ api_client: AsyncClient,
120
+ exc_type: MrokError,
121
+ ):
122
+ mocker.patch(
123
+ "mrok.controller.routes.extensions.register_extension",
124
+ side_effect=exc_type("this is the error."), # type: ignore
125
+ )
126
+
127
+ response = await api_client.post(
128
+ "/extensions",
129
+ json={
130
+ "extension": {"id": "EXT-1234-5678"},
131
+ "tags": {"account": "ACC-1234-5678"},
132
+ },
133
+ )
134
+ assert response.status_code == 400
135
+ assert response.json() == {"detail": "OpenZiti not configured properly: this is the error."}
136
+
137
+
138
+ @pytest.mark.asyncio
139
+ async def test_get_extension(
140
+ settings_factory: SettingsFactory,
141
+ api_client: AsyncClient,
142
+ httpx_mock: HTTPXMock,
143
+ ):
144
+ settings = settings_factory()
145
+ query = quote(
146
+ f'(id="EXT-1234-5678" or name="ext-1234-5678") and tags.{MROK_VERSION_TAG_NAME} != null'
147
+ )
148
+ httpx_mock.add_response(
149
+ method="GET",
150
+ url=f"{settings.ziti.api.management}/edge/management/v1/services?filter={query}",
151
+ json={
152
+ "meta": {"pagination": {"totalCount": 1}},
153
+ "data": [
154
+ {
155
+ "id": "svc1",
156
+ "name": "ext-1234-5678",
157
+ "tags": {MROK_VERSION_TAG_NAME: "0.0.0.dev0"},
158
+ }
159
+ ],
160
+ },
161
+ )
162
+
163
+ response = await api_client.get("/extensions/EXT-1234-5678")
164
+ assert response.status_code == 200
165
+ assert response.json() == {
166
+ "id": "svc1",
167
+ "extension": {"id": "EXT-1234-5678"},
168
+ "name": "ext-1234-5678",
169
+ "tags": {
170
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
171
+ },
172
+ }
173
+
174
+
175
+ @pytest.mark.asyncio
176
+ async def test_get_extension_not_found(
177
+ settings_factory: SettingsFactory,
178
+ api_client: AsyncClient,
179
+ httpx_mock: HTTPXMock,
180
+ ):
181
+ settings = settings_factory()
182
+ query = quote(
183
+ f'(id="EXT-1234-5678" or name="ext-1234-5678") and tags.{MROK_VERSION_TAG_NAME} != null'
184
+ )
185
+ httpx_mock.add_response(
186
+ method="GET",
187
+ url=f"{settings.ziti.api.management}/edge/management/v1/services?filter={query}",
188
+ json={
189
+ "meta": {"pagination": {"totalCount": 0}},
190
+ "data": [],
191
+ },
192
+ )
193
+
194
+ response = await api_client.get("/extensions/EXT-1234-5678")
195
+ assert response.status_code == 404
196
+
197
+
198
+ @pytest.mark.asyncio
199
+ async def test_delete_extension(
200
+ mocker: MockerFixture,
201
+ api_client: AsyncClient,
202
+ ):
203
+ mocker.patch(
204
+ "mrok.controller.routes.extensions.unregister_extension",
205
+ )
206
+
207
+ response = await api_client.delete("/extensions/EXT-1234-5678")
208
+ assert response.status_code == 204
209
+
210
+
211
+ @pytest.mark.asyncio
212
+ async def test_delete_extension_not_found(
213
+ mocker: MockerFixture,
214
+ api_client: AsyncClient,
215
+ ):
216
+ mocker.patch(
217
+ "mrok.controller.routes.extensions.unregister_extension",
218
+ side_effect=ServiceNotFoundError("not found"),
219
+ )
220
+
221
+ response = await api_client.delete("/extensions/EXT-1234-5678")
222
+ assert response.status_code == 404
223
+
224
+
13
225
  @pytest.mark.asyncio
14
226
  async def test_list_instances(
15
227
  mocker: MockerFixture,
@@ -19,7 +231,8 @@ async def test_list_instances(
19
231
  ):
20
232
  settings = settings_factory()
21
233
  mocker.patch(
22
- "mrok.controller.routes.fetch_extension_or_404", return_value={"name": "ext-1234-5678"}
234
+ "mrok.controller.routes.extensions.fetch_extension_or_404",
235
+ return_value={"name": "ext-1234-5678"},
23
236
  )
24
237
  query = quote(f'tags.{MROK_SERVICE_TAG_NAME} = "ext-1234-5678"')
25
238
  httpx_mock.add_response(
@@ -58,10 +271,11 @@ async def test_list_instances(
58
271
  @pytest.mark.asyncio
59
272
  async def test_register_instance(mocker: MockerFixture, api_client: AsyncClient):
60
273
  mocker.patch(
61
- "mrok.controller.routes.fetch_extension_or_404", return_value={"name": "ext-1234-5678"}
274
+ "mrok.controller.routes.extensions.fetch_extension_or_404",
275
+ return_value={"name": "ext-1234-5678"},
62
276
  )
63
277
  mocked_register = mocker.patch(
64
- "mrok.controller.routes.register_instance",
278
+ "mrok.controller.routes.extensions.register_instance",
65
279
  return_value=(
66
280
  {
67
281
  "id": "a1b2cd",
@@ -114,7 +328,8 @@ async def test_get_instance(
114
328
  httpx_mock: HTTPXMock,
115
329
  ):
116
330
  mocker.patch(
117
- "mrok.controller.routes.fetch_extension_or_404", return_value={"name": "ext-1234-5678"}
331
+ "mrok.controller.routes.extensions.fetch_extension_or_404",
332
+ return_value={"name": "ext-1234-5678"},
118
333
  )
119
334
  settings = settings_factory()
120
335
  query = quote(
@@ -161,7 +376,8 @@ async def test_get_instance_by_instance_id(
161
376
  httpx_mock: HTTPXMock,
162
377
  ):
163
378
  mocker.patch(
164
- "mrok.controller.routes.fetch_extension_or_404", return_value={"name": "ext-1234-5678"}
379
+ "mrok.controller.routes.extensions.fetch_extension_or_404",
380
+ return_value={"name": "ext-1234-5678"},
165
381
  )
166
382
  settings = settings_factory()
167
383
  query = quote(f'(id="ins1" or name="ins1") and tags.{MROK_VERSION_TAG_NAME} != null')
@@ -205,7 +421,8 @@ async def test_get_instance_not_found(
205
421
  httpx_mock: HTTPXMock,
206
422
  ):
207
423
  mocker.patch(
208
- "mrok.controller.routes.fetch_extension_or_404", return_value={"name": "ext-1234-5678"}
424
+ "mrok.controller.routes.extensions.fetch_extension_or_404",
425
+ return_value={"name": "ext-1234-5678"},
209
426
  )
210
427
  settings = settings_factory()
211
428
  query = quote(
@@ -231,11 +448,11 @@ async def test_delete_instance(
231
448
  api_client: AsyncClient,
232
449
  ):
233
450
  mocker.patch(
234
- "mrok.controller.routes.fetch_instance_or_404",
451
+ "mrok.controller.routes.extensions.fetch_instance_or_404",
235
452
  return_value={"name": "ins-1234-5678.ext-1234-5678"},
236
453
  )
237
454
  mocker.patch(
238
- "mrok.controller.routes.unregister_instance",
455
+ "mrok.controller.routes.extensions.unregister_instance",
239
456
  )
240
457
 
241
458
  response = await api_client.delete("/extensions/EXT-1234-5678/instances/INS-1234-5678-0001")
@@ -243,7 +460,7 @@ async def test_delete_instance(
243
460
 
244
461
 
245
462
  @pytest.mark.asyncio
246
- async def test_delete_extension_not_found(
463
+ async def test_delete_instance_extension_not_found(
247
464
  mocker: MockerFixture,
248
465
  api_client: AsyncClient,
249
466
  settings_factory: SettingsFactory,
@@ -251,7 +468,8 @@ async def test_delete_extension_not_found(
251
468
  ):
252
469
  settings = settings_factory()
253
470
  mocker.patch(
254
- "mrok.controller.routes.fetch_extension_or_404", return_value={"name": "ext-1234-5678"}
471
+ "mrok.controller.routes.extensions.fetch_extension_or_404",
472
+ return_value={"name": "ext-1234-5678"},
255
473
  )
256
474
 
257
475
  query = quote(
@@ -0,0 +1,157 @@
1
+ from urllib.parse import quote
2
+
3
+ import pytest
4
+ from httpx import AsyncClient
5
+ from pytest_httpx import HTTPXMock
6
+
7
+ from mrok.ziti.constants import MROK_SERVICE_TAG_NAME, MROK_VERSION_TAG_NAME
8
+ from tests.conftest import SettingsFactory
9
+
10
+
11
+ @pytest.mark.asyncio
12
+ async def test_list_instances(
13
+ api_client: AsyncClient,
14
+ settings_factory: SettingsFactory,
15
+ httpx_mock: HTTPXMock,
16
+ ):
17
+ settings = settings_factory()
18
+ httpx_mock.add_response(
19
+ method="GET",
20
+ url=f"{settings.ziti.api.management}/edge/management/v1/identities?&limit=10&offset=0",
21
+ json={
22
+ "meta": {"pagination": {"totalCount": 15, "limit": 10, "offset": 0}},
23
+ "data": [{"id": f"ins{i}", "name": "ins.svc"} for i in range(10)],
24
+ },
25
+ )
26
+ httpx_mock.add_response(
27
+ method="GET",
28
+ url=f"{settings.ziti.api.management}/edge/management/v1/identities?&limit=10&offset=10",
29
+ json={
30
+ "meta": {"pagination": {"totalCount": 15, "limit": 10, "offset": 10}},
31
+ "data": [{"id": f"ins{i}", "name": "ins.svc"} for i in range(11, 16)],
32
+ },
33
+ )
34
+ response = await api_client.get("/instances?limit=10&offset=0")
35
+ assert response.status_code == 200
36
+ page_1 = response.json()
37
+ assert len(page_1["data"]) == 10
38
+ assert page_1["$meta"]["pagination"]["offset"] == 0
39
+
40
+ response = await api_client.get("/instances?limit=10&offset=10")
41
+ assert response.status_code == 200
42
+ page_2 = response.json()
43
+ assert len(page_2["data"]) == 5
44
+ assert page_2["$meta"]["pagination"]["offset"] == 10
45
+
46
+ assert sorted(page_1["data"], key=lambda x: x["id"]) != sorted(
47
+ page_2["data"], key=lambda x: x["id"]
48
+ )
49
+
50
+
51
+ @pytest.mark.asyncio
52
+ async def test_get_instance(
53
+ settings_factory: SettingsFactory,
54
+ api_client: AsyncClient,
55
+ httpx_mock: HTTPXMock,
56
+ ):
57
+ settings = settings_factory()
58
+ query = quote(
59
+ '(id="INS-1234-1234-0001.ext-1234-1234" or name="ins-1234-1234-0001.ext-1234-1234") '
60
+ f"and tags.{MROK_VERSION_TAG_NAME} != null"
61
+ )
62
+ httpx_mock.add_response(
63
+ method="GET",
64
+ url=f"{settings.ziti.api.management}/edge/management/v1/identities?filter={query}",
65
+ json={
66
+ "meta": {"pagination": {"totalCount": 1}},
67
+ "data": [
68
+ {
69
+ "id": "ins1",
70
+ "name": "ins-1234-1234-0001.ext-1234-1234",
71
+ "tags": {
72
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
73
+ MROK_SERVICE_TAG_NAME: "ext-1234-1234",
74
+ },
75
+ }
76
+ ],
77
+ },
78
+ )
79
+
80
+ response = await api_client.get("/instances/INS-1234-1234-0001.ext-1234-1234")
81
+ assert response.status_code == 200
82
+ assert response.json() == {
83
+ "id": "ins1",
84
+ "identity": None,
85
+ "extension": {"id": "EXT-1234-1234"},
86
+ "instance": {"id": "INS-1234-1234-0001"},
87
+ "name": "ins-1234-1234-0001.ext-1234-1234",
88
+ "tags": {
89
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
90
+ MROK_SERVICE_TAG_NAME: "ext-1234-1234",
91
+ },
92
+ }
93
+
94
+
95
+ @pytest.mark.asyncio
96
+ async def test_get_instance_by_instance_id(
97
+ settings_factory: SettingsFactory,
98
+ api_client: AsyncClient,
99
+ httpx_mock: HTTPXMock,
100
+ ):
101
+ settings = settings_factory()
102
+ query = quote(f'(id="ins1" or name="ins1") and tags.{MROK_VERSION_TAG_NAME} != null')
103
+ httpx_mock.add_response(
104
+ method="GET",
105
+ url=f"{settings.ziti.api.management}/edge/management/v1/identities?filter={query}",
106
+ json={
107
+ "meta": {"pagination": {"totalCount": 1}},
108
+ "data": [
109
+ {
110
+ "id": "ins1",
111
+ "name": "ins-1234-1234-0001.ext-1234-1234",
112
+ "tags": {
113
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
114
+ MROK_SERVICE_TAG_NAME: "ext-1234-1234",
115
+ },
116
+ }
117
+ ],
118
+ },
119
+ )
120
+
121
+ response = await api_client.get("/instances/ins1")
122
+ assert response.status_code == 200
123
+ assert response.json() == {
124
+ "id": "ins1",
125
+ "extension": {"id": "EXT-1234-1234"},
126
+ "instance": {"id": "INS-1234-1234-0001"},
127
+ "name": "ins-1234-1234-0001.ext-1234-1234",
128
+ "tags": {
129
+ MROK_VERSION_TAG_NAME: "0.0.0.dev0",
130
+ MROK_SERVICE_TAG_NAME: "ext-1234-1234",
131
+ },
132
+ "identity": None,
133
+ }
134
+
135
+
136
+ @pytest.mark.asyncio
137
+ async def test_get_instance_not_found(
138
+ settings_factory: SettingsFactory,
139
+ api_client: AsyncClient,
140
+ httpx_mock: HTTPXMock,
141
+ ):
142
+ settings = settings_factory()
143
+ query = quote(
144
+ '(id="INS-1234-1234-0001.ext-1234-1234" or name="ins-1234-1234-0001.ext-1234-1234") '
145
+ f"and tags.{MROK_VERSION_TAG_NAME} != null"
146
+ )
147
+ httpx_mock.add_response(
148
+ method="GET",
149
+ url=f"{settings.ziti.api.management}/edge/management/v1/identities?filter={query}",
150
+ json={
151
+ "meta": {"pagination": {"totalCount": 10}},
152
+ "data": [],
153
+ },
154
+ )
155
+
156
+ response = await api_client.get("/instances/INS-1234-1234-0001.ext-1234-1234")
157
+ assert response.status_code == 404
File without changes
@@ -37,6 +37,15 @@ wheels = [
37
37
  { url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895, upload-time = "2023-03-28T17:35:47.772Z" },
38
38
  ]
39
39
 
40
+ [[package]]
41
+ name = "asn1crypto"
42
+ version = "1.5.1"
43
+ source = { registry = "https://pypi.org/simple" }
44
+ sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080, upload-time = "2022-03-15T14:46:52.889Z" }
45
+ wheels = [
46
+ { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045, upload-time = "2022-03-15T14:46:51.055Z" },
47
+ ]
48
+
40
49
  [[package]]
41
50
  name = "asttokens"
42
51
  version = "3.0.0"
@@ -672,6 +681,7 @@ name = "mrok"
672
681
  version = "0.0.0.dev0"
673
682
  source = { editable = "." }
674
683
  dependencies = [
684
+ { name = "asn1crypto" },
675
685
  { name = "cryptography" },
676
686
  { name = "dynaconf" },
677
687
  { name = "fastapi", extra = ["standard"] },
@@ -708,6 +718,7 @@ dev = [
708
718
 
709
719
  [package.metadata]
710
720
  requires-dist = [
721
+ { name = "asn1crypto", specifier = ">=1.5.1,<2.0.0" },
711
722
  { name = "cryptography", specifier = ">=45.0.7,<46.0.0" },
712
723
  { name = "dynaconf", specifier = ">=3.2.11,<4.0.0" },
713
724
  { name = "fastapi", extras = ["standard"], specifier = ">=0.119.0,<0.120.0" },