appmesh 1.6.0__py3-none-any.whl → 1.6.2__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.
- appmesh/__init__.py +36 -6
- appmesh/client_http.py +30 -82
- appmesh/client_http_oauth.py +138 -0
- appmesh/tcp_transport.py +23 -16
- {appmesh-1.6.0.dist-info → appmesh-1.6.2.dist-info}/METADATA +3 -3
- {appmesh-1.6.0.dist-info → appmesh-1.6.2.dist-info}/RECORD +8 -7
- {appmesh-1.6.0.dist-info → appmesh-1.6.2.dist-info}/WHEEL +0 -0
- {appmesh-1.6.0.dist-info → appmesh-1.6.2.dist-info}/top_level.txt +0 -0
appmesh/__init__.py
CHANGED
@@ -9,10 +9,40 @@ Example:
|
|
9
9
|
client = AppMeshClient()
|
10
10
|
"""
|
11
11
|
|
12
|
-
|
13
|
-
from
|
14
|
-
from .client_tcp import AppMeshClientTCP
|
15
|
-
from .server_http import AppMeshServer
|
16
|
-
from .server_tcp import AppMeshServerTCP
|
12
|
+
import sys
|
13
|
+
from types import ModuleType
|
17
14
|
|
18
|
-
__all__ = ["App", "AppMeshClient", "AppMeshClientTCP", "AppMeshServer", "AppMeshServerTCP"]
|
15
|
+
__all__ = ["App", "AppMeshClient", "AppMeshClientTCP", "AppMeshClientOAuth", "AppMeshServer", "AppMeshServerTCP"]
|
16
|
+
|
17
|
+
_LAZY_IMPORTS = {
|
18
|
+
"App": ("app", "App"), # from .app import App
|
19
|
+
"AppMeshClient": ("client_http", "AppMeshClient"), # from .client_http import AppMeshClient
|
20
|
+
"AppMeshClientTCP": ("client_tcp", "AppMeshClientTCP"), # from .client_tcp import AppMeshClientTCP
|
21
|
+
"AppMeshClientOAuth": ("client_http_oauth", "AppMeshClientOAuth"), # from .client_http_oauth import AppMeshClientOAuth
|
22
|
+
"AppMeshServer": ("server_http", "AppMeshServer"), # from .server_http import AppMeshServer
|
23
|
+
"AppMeshServerTCP": ("server_tcp", "AppMeshServerTCP"), # from .server_tcp import AppMeshServerTCP
|
24
|
+
}
|
25
|
+
|
26
|
+
|
27
|
+
def _lazy_import(name):
|
28
|
+
"""Helper function for lazy importing."""
|
29
|
+
if name in _LAZY_IMPORTS:
|
30
|
+
module_name, attr_name = _LAZY_IMPORTS[name]
|
31
|
+
module = __import__(f"{__name__}.{module_name}", fromlist=[attr_name])
|
32
|
+
return getattr(module, attr_name)
|
33
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
34
|
+
|
35
|
+
|
36
|
+
if sys.version_info >= (3, 7):
|
37
|
+
|
38
|
+
def __getattr__(name):
|
39
|
+
return _lazy_import(name)
|
40
|
+
|
41
|
+
else:
|
42
|
+
# Python 3.6 compatibility
|
43
|
+
class _LazyModule(ModuleType):
|
44
|
+
def __getattr__(self, name):
|
45
|
+
return _lazy_import(name)
|
46
|
+
|
47
|
+
sys.modules[__name__] = _LazyModule(__name__)
|
48
|
+
sys.modules[__name__].__dict__.update(globals())
|
appmesh/client_http.py
CHANGED
@@ -107,7 +107,8 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
107
107
|
DURATION_ONE_WEEK_ISO = "P1W"
|
108
108
|
DURATION_TWO_DAYS_ISO = "P2D"
|
109
109
|
DURATION_TWO_DAYS_HALF_ISO = "P2DT12H"
|
110
|
-
TOKEN_REFRESH_INTERVAL =
|
110
|
+
TOKEN_REFRESH_INTERVAL = 300 # 5 min to refresh token
|
111
|
+
TOKEN_REFRESH_OFFSET = 30 # 30s before token expire to refresh token
|
111
112
|
|
112
113
|
# Platform-aware default SSL paths
|
113
114
|
_DEFAULT_SSL_DIR = "c:/local/appmesh/ssl" if os.name == "nt" else "/opt/appmesh/ssl"
|
@@ -140,7 +141,6 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
140
141
|
rest_ssl_client_cert=(DEFAULT_SSL_CLIENT_CERT_PATH, DEFAULT_SSL_CLIENT_KEY_PATH) if os.path.exists(DEFAULT_SSL_CLIENT_CERT_PATH) else None,
|
141
142
|
rest_timeout=(60, 300),
|
142
143
|
jwt_token=None,
|
143
|
-
oauth2=None,
|
144
144
|
auto_refresh_token=False,
|
145
145
|
):
|
146
146
|
"""Initialize an App Mesh HTTP client for interacting with the App Mesh server via secure HTTPS.
|
@@ -163,15 +163,6 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
163
163
|
The default is `(60, 300)`, where `60` seconds is the maximum time to establish a connection and `300` seconds for the maximum read duration.
|
164
164
|
|
165
165
|
jwt_token (str, optional): JWT token for API authentication, used in headers to authorize requests where required.
|
166
|
-
|
167
|
-
oauth2 (Dict[str, Any], optional): Keycloak configuration for oauth2 authentication:
|
168
|
-
- server_url: Keycloak server URL (e.g. "https://keycloak.example.com/auth/")
|
169
|
-
- realm: Keycloak realm
|
170
|
-
- client_id: Keycloak client ID
|
171
|
-
- client_secret: Keycloak client secret (optional)
|
172
|
-
Using this parameter enables Keycloak integration for authentication. The 'python-keycloak' package
|
173
|
-
will be imported on-demand only when this parameter is, make sure package is installed (pip3 install python-keycloak).
|
174
|
-
|
175
166
|
auto_refresh_token (bool, optional): Enable automatic token refresh before expiration.
|
176
167
|
When enabled, a background timer will monitor token expiration and attempt to refresh
|
177
168
|
the token before it expires. This works with both native App Mesh tokens and Keycloak tokens.
|
@@ -185,29 +176,10 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
185
176
|
self.rest_timeout = rest_timeout
|
186
177
|
self._forward_to = None
|
187
178
|
|
188
|
-
# Keycloak integration
|
189
|
-
self._keycloak_openid = None
|
190
|
-
if oauth2:
|
191
|
-
try:
|
192
|
-
from keycloak import KeycloakOpenID
|
193
|
-
|
194
|
-
self._keycloak_openid = KeycloakOpenID(
|
195
|
-
server_url=oauth2.get("auth_server_url"),
|
196
|
-
client_id=oauth2.get("client_id"),
|
197
|
-
realm_name=oauth2.get("realm"),
|
198
|
-
client_secret_key=oauth2.get("client_secret"),
|
199
|
-
verify=self.ssl_verify,
|
200
|
-
timeout=rest_timeout,
|
201
|
-
)
|
202
|
-
except ImportError:
|
203
|
-
logging.error("Keycloak package not installed. Install with: pip install python-keycloak")
|
204
|
-
raise Exception("Keycloak integration requested but python-keycloak package is not installed")
|
205
|
-
|
206
179
|
# Token auto-refresh
|
207
180
|
self._token_refresh_timer = None
|
208
181
|
self._auto_refresh_token = auto_refresh_token
|
209
|
-
|
210
|
-
self._schedule_token_refresh()
|
182
|
+
self.jwt_token = jwt_token # Set property last after all dependencies are initialized
|
211
183
|
|
212
184
|
@staticmethod
|
213
185
|
def _ensure_logging_configured():
|
@@ -215,6 +187,9 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
215
187
|
if not logging.root.handlers:
|
216
188
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
|
217
189
|
|
190
|
+
def _get_access_token(self) -> str:
|
191
|
+
return self.jwt_token
|
192
|
+
|
218
193
|
def _check_and_refresh_token(self):
|
219
194
|
"""Check and refresh token if needed, then schedule next check.
|
220
195
|
|
@@ -232,12 +207,12 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
232
207
|
|
233
208
|
# Check token expiration directly from JWT
|
234
209
|
try:
|
235
|
-
decoded_token = jwt.decode(self.
|
210
|
+
decoded_token = jwt.decode(self._get_access_token(), options={"verify_signature": False})
|
236
211
|
expiry = decoded_token.get("exp", 0)
|
237
212
|
current_time = time.time()
|
238
213
|
time_to_expiry = expiry - current_time
|
239
214
|
# Refresh if token expires within 5 minutes
|
240
|
-
needs_refresh = time_to_expiry <
|
215
|
+
needs_refresh = time_to_expiry < self.TOKEN_REFRESH_OFFSET
|
241
216
|
except Exception as e:
|
242
217
|
logging.debug("Failed to parse JWT token for expiration check: %s", str(e))
|
243
218
|
|
@@ -277,11 +252,11 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
277
252
|
|
278
253
|
# Calculate more precise check time if expiry is known
|
279
254
|
if time_to_expiry is not None:
|
280
|
-
if time_to_expiry <=
|
255
|
+
if time_to_expiry <= self.TOKEN_REFRESH_OFFSET: # Expires within 5 minutes
|
281
256
|
check_interval = 1 # Almost immediate refresh
|
282
257
|
else:
|
283
258
|
# Check at earlier of 5 minutes before expiry or regular interval
|
284
|
-
check_interval = min(time_to_expiry -
|
259
|
+
check_interval = min(time_to_expiry - self.TOKEN_REFRESH_OFFSET, self.TOKEN_REFRESH_INTERVAL)
|
285
260
|
|
286
261
|
# Create timer to execute refresh check
|
287
262
|
self._token_refresh_timer = threading.Timer(check_interval, self._check_and_refresh_token)
|
@@ -303,14 +278,9 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
303
278
|
self.session.close()
|
304
279
|
self.session = None
|
305
280
|
|
306
|
-
#
|
307
|
-
if hasattr(self, "
|
308
|
-
|
309
|
-
self._keycloak_openid.logout(self._jwt_token.get("refresh_token"))
|
310
|
-
except Exception as e:
|
311
|
-
logging.warning("Failed to logout from Keycloak: %s", str(e))
|
312
|
-
finally:
|
313
|
-
self._keycloak_openid = None
|
281
|
+
# Clean token
|
282
|
+
if hasattr(self, "_jwt_token") and self._jwt_token:
|
283
|
+
self._jwt_token = None
|
314
284
|
|
315
285
|
def __enter__(self):
|
316
286
|
"""Support for context manager protocol."""
|
@@ -370,6 +340,13 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
370
340
|
"""
|
371
341
|
self._jwt_token = token
|
372
342
|
|
343
|
+
# handle refresh
|
344
|
+
if self._jwt_token and self._auto_refresh_token:
|
345
|
+
self._schedule_token_refresh()
|
346
|
+
elif self._token_refresh_timer:
|
347
|
+
self._token_refresh_timer.cancel()
|
348
|
+
self._token_refresh_timer = None
|
349
|
+
|
373
350
|
@property
|
374
351
|
def forward_to(self) -> str:
|
375
352
|
"""Get the target host address for request forwarding in a cluster setup.
|
@@ -419,7 +396,14 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
419
396
|
########################################
|
420
397
|
# Security
|
421
398
|
########################################
|
422
|
-
def login(
|
399
|
+
def login(
|
400
|
+
self,
|
401
|
+
user_name: str,
|
402
|
+
user_pwd: str,
|
403
|
+
totp_code: Optional[str] = "",
|
404
|
+
timeout_seconds: Union[str, int] = DURATION_ONE_WEEK_ISO,
|
405
|
+
audience: Optional[str] = None,
|
406
|
+
) -> str:
|
423
407
|
"""Login with user name and password
|
424
408
|
|
425
409
|
Args:
|
@@ -432,20 +416,6 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
432
416
|
Returns:
|
433
417
|
str: JWT token.
|
434
418
|
"""
|
435
|
-
# Keycloak authentication if configured
|
436
|
-
if self._keycloak_openid:
|
437
|
-
self.jwt_token = self._keycloak_openid.token(
|
438
|
-
username=user_name,
|
439
|
-
password=user_pwd,
|
440
|
-
totp=totp_code if totp_code else None,
|
441
|
-
grant_type="password", # grant type for token request: "password" / "client_credentials" / "refresh_token"
|
442
|
-
scope="openid", # what information to include in the token, such as "openid profile email"
|
443
|
-
)
|
444
|
-
|
445
|
-
if self._auto_refresh_token:
|
446
|
-
self._schedule_token_refresh()
|
447
|
-
return self.jwt_token
|
448
|
-
|
449
419
|
# Standard App Mesh authentication
|
450
420
|
self.jwt_token = None
|
451
421
|
resp = self._request_http(
|
@@ -467,8 +437,6 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
467
437
|
else:
|
468
438
|
raise Exception(resp.text)
|
469
439
|
|
470
|
-
if self._auto_refresh_token:
|
471
|
-
self._schedule_token_refresh()
|
472
440
|
return self.jwt_token
|
473
441
|
|
474
442
|
def validate_totp(self, username: str, challenge: str, code: str, timeout: Union[int, str] = DURATION_ONE_WEEK_ISO) -> str:
|
@@ -510,24 +478,12 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
510
478
|
bool: logoff success or failure.
|
511
479
|
"""
|
512
480
|
result = False
|
513
|
-
# Handle Keycloak logout if configured
|
514
|
-
if self._keycloak_openid and self.jwt_token and isinstance(self.jwt_token, dict) and "refresh_token" in self.jwt_token:
|
515
|
-
refresh_token = self.jwt_token.get("refresh_token")
|
516
|
-
self._keycloak_openid.logout(refresh_token)
|
517
|
-
self.jwt_token = None
|
518
|
-
result = True
|
519
|
-
|
520
481
|
# Standard App Mesh logout
|
521
482
|
if self.jwt_token and isinstance(self.jwt_token, str):
|
522
483
|
resp = self._request_http(AppMeshClient.Method.POST, path="/appmesh/self/logoff")
|
523
484
|
self.jwt_token = None
|
524
485
|
result = resp.status_code == HTTPStatus.OK
|
525
486
|
|
526
|
-
# Cancel token refresh timer
|
527
|
-
if self._token_refresh_timer:
|
528
|
-
self._token_refresh_timer.cancel()
|
529
|
-
self._token_refresh_timer = None
|
530
|
-
|
531
487
|
return result
|
532
488
|
|
533
489
|
def authentication(self, token: str, permission: Optional[str] = None, audience: Optional[str] = None) -> bool:
|
@@ -577,13 +533,8 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
577
533
|
raise Exception("No token to renew")
|
578
534
|
|
579
535
|
try:
|
580
|
-
# Handle Keycloak token (dictionary format)
|
581
|
-
if self._keycloak_openid and isinstance(self.jwt_token, dict) and "refresh_token" in self.jwt_token:
|
582
|
-
new_token = self._keycloak_openid.refresh_token(self.jwt_token.get("refresh_token"))
|
583
|
-
self.jwt_token = new_token
|
584
|
-
|
585
536
|
# Handle App Mesh token (string format)
|
586
|
-
|
537
|
+
if isinstance(self.jwt_token, str):
|
587
538
|
resp = self._request_http(
|
588
539
|
AppMeshClient.Method.POST,
|
589
540
|
path="/appmesh/token/renew",
|
@@ -986,9 +937,6 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
986
937
|
Returns:
|
987
938
|
dict: user definition.
|
988
939
|
"""
|
989
|
-
if self._keycloak_openid and isinstance(self.jwt_token, dict) and "access_token" in self.jwt_token:
|
990
|
-
return self._keycloak_openid.userinfo(self.jwt_token.get("access_token"))
|
991
|
-
|
992
940
|
resp = self._request_http(method=AppMeshClient.Method.GET, path="/appmesh/user/self")
|
993
941
|
if resp.status_code != HTTPStatus.OK:
|
994
942
|
raise Exception(resp.text)
|
@@ -1383,7 +1331,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1383
1331
|
# Prepare headers
|
1384
1332
|
header = {} if header is None else header
|
1385
1333
|
if self.jwt_token:
|
1386
|
-
token = self.
|
1334
|
+
token = self._get_access_token()
|
1387
1335
|
header["Authorization"] = "Bearer " + token
|
1388
1336
|
if self.forward_to and len(self.forward_to) > 0:
|
1389
1337
|
if ":" in self.forward_to:
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# client_http_oauth.py
|
2
|
+
# pylint: disable=line-too-long,broad-exception-caught,too-many-lines, import-outside-toplevel, protected-access
|
3
|
+
import os
|
4
|
+
import logging
|
5
|
+
from typing import Optional, Union
|
6
|
+
from keycloak import KeycloakOpenID
|
7
|
+
from .client_http import AppMeshClient
|
8
|
+
|
9
|
+
|
10
|
+
class AppMeshClientOAuth(AppMeshClient):
|
11
|
+
"""
|
12
|
+
AppMeshClient extended with Keycloak authentication support.
|
13
|
+
|
14
|
+
Managing tokens using Keycloak as the identity provider.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(
|
18
|
+
self,
|
19
|
+
oauth2: dict, # Required for Keycloak
|
20
|
+
rest_url: str = "https://127.0.0.1:6060",
|
21
|
+
rest_ssl_verify=AppMeshClient.DEFAULT_SSL_CA_CERT_PATH if os.path.exists(AppMeshClient.DEFAULT_SSL_CA_CERT_PATH) else False,
|
22
|
+
rest_ssl_client_cert=(AppMeshClient.DEFAULT_SSL_CLIENT_CERT_PATH, AppMeshClient.DEFAULT_SSL_CLIENT_KEY_PATH) if os.path.exists(AppMeshClient.DEFAULT_SSL_CLIENT_CERT_PATH) else None,
|
23
|
+
rest_timeout=(60, 300),
|
24
|
+
jwt_token=None, # Keycloak dict
|
25
|
+
auto_refresh_token: bool = True, # Default to True for Keycloak
|
26
|
+
):
|
27
|
+
"""Initialize an App Mesh HTTP client with Keycloak support.
|
28
|
+
Args:
|
29
|
+
oauth2 (Dict[str, str]): Keycloak configuration for oauth2 authentication:
|
30
|
+
- auth_server_url: Keycloak server URL (e.g. "https://keycloak.example.com/auth/")
|
31
|
+
- realm: Keycloak realm
|
32
|
+
- client_id: Keycloak client ID
|
33
|
+
- client_secret: Keycloak client secret (optional)
|
34
|
+
Using this parameter enables Keycloak integration for authentication. The 'python-keycloak' package
|
35
|
+
will be imported on-demand only when this parameter is, make sure package is installed (pip3 install python-keycloak).
|
36
|
+
"""
|
37
|
+
# Initialize base class, disabling its Keycloak and auto-refresh logic
|
38
|
+
super().__init__(
|
39
|
+
rest_url=rest_url,
|
40
|
+
rest_ssl_verify=rest_ssl_verify,
|
41
|
+
rest_ssl_client_cert=rest_ssl_client_cert,
|
42
|
+
rest_timeout=rest_timeout,
|
43
|
+
jwt_token=jwt_token,
|
44
|
+
auto_refresh_token=auto_refresh_token,
|
45
|
+
)
|
46
|
+
|
47
|
+
# Keycloak integration
|
48
|
+
self._keycloak_openid = KeycloakOpenID(
|
49
|
+
server_url=oauth2.get("auth_server_url"),
|
50
|
+
client_id=oauth2.get("client_id"),
|
51
|
+
realm_name=oauth2.get("realm"),
|
52
|
+
client_secret_key=oauth2.get("client_secret"),
|
53
|
+
verify=self.ssl_verify,
|
54
|
+
timeout=rest_timeout,
|
55
|
+
)
|
56
|
+
|
57
|
+
# @override, from typing import override avialable from python3.12
|
58
|
+
def _get_access_token(self) -> str:
|
59
|
+
return self.jwt_token.get("access_token", "") if self.jwt_token else None
|
60
|
+
|
61
|
+
def login(
|
62
|
+
self,
|
63
|
+
user_name: str,
|
64
|
+
user_pwd: str,
|
65
|
+
totp_code: Optional[str] = "",
|
66
|
+
timeout_seconds: Union[str, int] = AppMeshClient.DURATION_ONE_WEEK_ISO,
|
67
|
+
) -> dict:
|
68
|
+
"""Login with user name and password using Keycloak.
|
69
|
+
Args:
|
70
|
+
user_name (str): the name of the user.
|
71
|
+
user_pwd (str): the password of the user.
|
72
|
+
totp_code (str, optional): the TOTP code if enabled for the user.
|
73
|
+
timeout_seconds (int | str, optional): token expire timeout of seconds. support ISO 8601 durations (e.g., 'P1Y2M3DT4H5M6S' 'P1W').
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
dict: Keycloak token.
|
77
|
+
"""
|
78
|
+
# Keycloak authentication
|
79
|
+
self.jwt_token = self._keycloak_openid.token(
|
80
|
+
username=user_name,
|
81
|
+
password=user_pwd,
|
82
|
+
totp=totp_code if totp_code else None,
|
83
|
+
grant_type="password", # grant type for token request: "password" / "client_credentials" / "refresh_token"
|
84
|
+
scope="openid", # what information to include in the token, such as "openid profile email"
|
85
|
+
)
|
86
|
+
|
87
|
+
return self.jwt_token
|
88
|
+
|
89
|
+
def logoff(self) -> bool:
|
90
|
+
"""Log out of the current session from Keycloak and clean up."""
|
91
|
+
result = False
|
92
|
+
if self._keycloak_openid and self.jwt_token:
|
93
|
+
try:
|
94
|
+
self._keycloak_openid.logout(self.jwt_token.get("refresh_token"))
|
95
|
+
result = True
|
96
|
+
except Exception as e:
|
97
|
+
logging.warning("Failed to logout from Keycloak: %s", str(e))
|
98
|
+
finally:
|
99
|
+
self.jwt_token = None
|
100
|
+
|
101
|
+
# Call super to handle base class cleanup (timers, session)
|
102
|
+
super_result = super().logoff()
|
103
|
+
|
104
|
+
return result and super_result
|
105
|
+
|
106
|
+
def renew_token(self) -> dict:
|
107
|
+
"""Renew the current Keycloak token."""
|
108
|
+
if not self.jwt_token or not isinstance(self.jwt_token, dict) or "refresh_token" not in self.jwt_token:
|
109
|
+
raise Exception("No Keycloak refresh token available to renew")
|
110
|
+
|
111
|
+
try:
|
112
|
+
# Handle Keycloak token (dictionary format)
|
113
|
+
new_token = self._keycloak_openid.refresh_token(self.jwt_token.get("refresh_token"))
|
114
|
+
self.jwt_token = new_token
|
115
|
+
return self.jwt_token
|
116
|
+
except Exception as e:
|
117
|
+
logging.error("Keycloak token renewal failed: %s", str(e))
|
118
|
+
raise Exception(f"Keycloak token renewal failed: {str(e)}") from e
|
119
|
+
|
120
|
+
def view_self(self) -> dict:
|
121
|
+
"""Get information about the current user, using Keycloak userinfo if applicable.
|
122
|
+
Returns:
|
123
|
+
dict: user definition.
|
124
|
+
"""
|
125
|
+
return self._keycloak_openid.userinfo(self._get_access_token())
|
126
|
+
|
127
|
+
def close(self):
|
128
|
+
"""Close the session and release resources, including Keycloak logout."""
|
129
|
+
# Logout from Keycloak if needed
|
130
|
+
if hasattr(self, "_keycloak_openid") and self._keycloak_openid and hasattr(self, "_jwt_token") and self._jwt_token and isinstance(self._jwt_token, dict) and "refresh_token" in self._jwt_token:
|
131
|
+
try:
|
132
|
+
self._keycloak_openid.logout(self._jwt_token.get("refresh_token"))
|
133
|
+
except Exception as e:
|
134
|
+
logging.warning("Failed to logout from Keycloak: %s", str(e))
|
135
|
+
finally:
|
136
|
+
self._keycloak_openid = None
|
137
|
+
# Close the base class session and resources (timers, etc.)
|
138
|
+
super().close()
|
appmesh/tcp_transport.py
CHANGED
@@ -108,7 +108,7 @@ class TCPTransport:
|
|
108
108
|
self._socket = ssl_socket
|
109
109
|
except (socket.error, ssl.SSLError) as e:
|
110
110
|
sock.close()
|
111
|
-
raise RuntimeError(f"Failed to connect to {self.tcp_address}: {e}")
|
111
|
+
raise RuntimeError(f"Failed to connect to {self.tcp_address}: {e}") from e
|
112
112
|
|
113
113
|
def close(self) -> None:
|
114
114
|
"""Close socket connection"""
|
@@ -137,7 +137,7 @@ class TCPTransport:
|
|
137
137
|
self._socket.sendall(data)
|
138
138
|
except (socket.error, ssl.SSLError) as e:
|
139
139
|
self.close()
|
140
|
-
raise RuntimeError(f"Error sending message: {e}")
|
140
|
+
raise RuntimeError(f"Error sending message: {e}") from e
|
141
141
|
|
142
142
|
def receive_message(self) -> Optional[bytearray]:
|
143
143
|
"""Receive a message with a prefixed header indicating its length and validate it"""
|
@@ -156,39 +156,46 @@ class TCPTransport:
|
|
156
156
|
return None
|
157
157
|
except (socket.error, ssl.SSLError) as e:
|
158
158
|
self.close()
|
159
|
-
raise RuntimeError(f"Error receiving message: {e}")
|
159
|
+
raise RuntimeError(f"Error receiving message: {e}") from e
|
160
160
|
|
161
|
-
def _recvall(self, length: int) ->
|
162
|
-
"""
|
161
|
+
def _recvall(self, length: int) -> bytes:
|
162
|
+
"""Receive exactly `length` bytes from the socket.
|
163
163
|
https://stackoverflow.com/questions/64466530/using-a-custom-socket-recvall-function-works-only-if-thread-is-put-to-sleep
|
164
|
+
|
164
165
|
Args:
|
165
|
-
length (int):
|
166
|
+
length (int): Number of bytes to receive.
|
166
167
|
|
167
168
|
Returns:
|
168
|
-
|
169
|
+
bytes: Received data.
|
169
170
|
|
170
171
|
Raises:
|
171
|
-
EOFError: If connection closes
|
172
|
-
ValueError: If length is
|
172
|
+
EOFError: If connection closes before receiving all data.
|
173
|
+
ValueError: If length is not positive.
|
174
|
+
socket.timeout: If socket operation times out.
|
173
175
|
"""
|
174
176
|
if length <= 0:
|
175
177
|
raise ValueError(f"Invalid length: {length}")
|
176
178
|
|
177
|
-
# Pre-allocate buffer
|
179
|
+
# Pre-allocate buffer
|
178
180
|
buffer = bytearray(length)
|
179
|
-
|
181
|
+
mv = memoryview(buffer)
|
180
182
|
bytes_received = 0
|
181
183
|
|
182
184
|
while bytes_received < length:
|
183
|
-
# Use recv_into to read directly into our buffer
|
184
185
|
try:
|
185
|
-
|
186
|
+
# Receive directly into buffer
|
187
|
+
remaining = length - bytes_received
|
188
|
+
chunk_size = self._socket.recv_into(mv, remaining)
|
186
189
|
|
187
190
|
if chunk_size == 0:
|
188
191
|
raise EOFError("Connection closed by peer")
|
189
192
|
|
193
|
+
mv = mv[chunk_size:] # advance memoryview
|
190
194
|
bytes_received += chunk_size
|
191
|
-
except socket.timeout:
|
192
|
-
raise socket.timeout(f"Socket operation timed out after receiving {bytes_received}/{length} bytes")
|
193
195
|
|
194
|
-
|
196
|
+
except InterruptedError:
|
197
|
+
continue
|
198
|
+
except socket.timeout as e:
|
199
|
+
raise socket.timeout(f"Socket operation timed out after receiving {bytes_received}/{length} bytes") from e
|
200
|
+
|
201
|
+
return bytes(buffer) # safer than bytearray
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: appmesh
|
3
|
-
Version: 1.6.
|
3
|
+
Version: 1.6.2
|
4
4
|
Summary: Client SDK for App Mesh
|
5
5
|
Home-page: https://github.com/laoshanxi/app-mesh
|
6
6
|
Author: laoshanxi
|
@@ -42,6 +42,7 @@ Dynamic: summary
|
|
42
42
|
|
43
43
|
App Mesh is an open-source, multi-tenant application management platform designed for cloud-native environments. It efficiently manages, schedules, and monitors both microservices and traditional applications, offering a lightweight alternative to Kubernetes. App Mesh bridges the gap between simple process managers and complex container orchestration systems, making it ideal for organizations seeking to modernize their infrastructure without adopting full container-native complexity. Supporting both containerized and native applications, it provides a versatile solution for diverse enterprise needs.
|
44
44
|
|
45
|
+
<div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/whatis.gif" align=center /></div>
|
45
46
|
<div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/diagram.png" align=center /></div>
|
46
47
|
|
47
48
|
## Features
|
@@ -54,7 +55,7 @@ Cloud native | Schedule cloud-level applications to run on multiple hosts with r
|
|
54
55
|
Micro service application | 🧱 [Consul micro-service cluster management](https://app-mesh.readthedocs.io/en/latest/CONSUL.html)
|
55
56
|
Extra Features | Collect host/app resource usage <br> Remote shell command execution <br> File upload/download interface <br> Hot-update support `systemctl reload appmesh` <br> Bash completion <br> Reverse proxy <br> 🌐[Web GUI](https://github.com/laoshanxi/app-mesh-ui)
|
56
57
|
Platform support | X86_64 <br> ARM32 <br> ARM64
|
57
|
-
SDK | [Python](https://app-mesh.readthedocs.io/en/latest/api/
|
58
|
+
SDK | [Python](https://app-mesh.readthedocs.io/en/latest/api/appmesh.html#module-appmesh.client_http) <br> [Golang](https://github.com/laoshanxi/app-mesh/blob/main/src/sdk/go/client_http.go) <br> [JavaScript](https://www.npmjs.com/package/appmesh) <br> [Java](https://github.com/laoshanxi/app-mesh/packages/2227502) <br> [Swagger OpenAPI Specification](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/laoshanxi/app-mesh/main/src/daemon/rest/openapi.yaml)
|
58
59
|
|
59
60
|
## Getting started
|
60
61
|
|
@@ -116,7 +117,6 @@ Refer to the [Installation doc](https://app-mesh.readthedocs.io/en/latest/Instal
|
|
116
117
|
|
117
118
|
- [Build a powerful monitor system with Grafana/Prometheus/Loki](https://app-mesh.readthedocs.io/en/latest/success/build_powerful_monitor_system_with_Grafana_Prometheus_Loki.html)
|
118
119
|
- [Customize application start behavior](https://app-mesh.readthedocs.io/en/latest/success/customize_app_startup_behavior.html)
|
119
|
-
- [Manage cluster-level microservice applications](https://app-mesh.readthedocs.io/en/latest/success/manage_cluster_level_microservice_applications.html)
|
120
120
|
- [Open service broker support local PV for Kubernetes](https://app-mesh.readthedocs.io/en/latest/success/open_service_broker_support_local_pv_for_K8S.html)
|
121
121
|
- [Promote native application to microservice application](https://app-mesh.readthedocs.io/en/latest/success/promote_native_app_to_microservice_app.html)
|
122
122
|
- [Secure REST file server](https://app-mesh.readthedocs.io/en/latest/success/secure_REST_file_server.html)
|
@@ -1,15 +1,16 @@
|
|
1
|
-
appmesh/__init__.py,sha256=
|
1
|
+
appmesh/__init__.py,sha256=TY1y5B5cE57uhraEzCFOZRWuo9SY1R-fYNRan8hCZOM,1670
|
2
2
|
appmesh/app.py,sha256=4lo66ob1ZFDgL8VYKxH4_sh-vbHcGkZNThURE0go-d4,10732
|
3
3
|
appmesh/app_output.py,sha256=vfn322AyixblI8DbXds08h6L_ybObiaRSifsA1-Xcoo,1035
|
4
4
|
appmesh/app_run.py,sha256=TJ9xVX8xqUN9-OOmr_8JlgXoyg3hTmDFNKvGf4w_oR4,1980
|
5
5
|
appmesh/appmesh_client.py,sha256=7yy9c_ygJbqMekrUsHWmRObwHLK82qP65xQLXxfGd8U,339
|
6
|
-
appmesh/client_http.py,sha256=
|
6
|
+
appmesh/client_http.py,sha256=rVmGAc_nWkntoZBL7BCaQZtH9QOZXUis38tW9OhT2ps,54707
|
7
|
+
appmesh/client_http_oauth.py,sha256=1d51o0JX_xtB8d2bEuM7_XJHcwMnhcjkbIq7GE1Zxm8,6120
|
7
8
|
appmesh/client_tcp.py,sha256=hE0T_2Z0OQZdF5zi--iuvu2_-ka0DfSSQ4BP3oHlg44,11243
|
8
9
|
appmesh/server_http.py,sha256=zr9sfS8g_mtP2kuAfmz-qU7rLSFTVt3srbFXaCbl8Y0,4253
|
9
10
|
appmesh/server_tcp.py,sha256=biBFF5IGWFOw2ru831cfmzn1DVXcBm9e-W6CP2VkfzE,1444
|
10
11
|
appmesh/tcp_messages.py,sha256=4XRv5lSm_8ElMg_37SuyIRrfxI7XFNNP_7SdO7us3PA,1023
|
11
|
-
appmesh/tcp_transport.py,sha256=
|
12
|
-
appmesh-1.6.
|
13
|
-
appmesh-1.6.
|
14
|
-
appmesh-1.6.
|
15
|
-
appmesh-1.6.
|
12
|
+
appmesh/tcp_transport.py,sha256=f28zfZNH46tUHfT8F1PrCM1wUXiSBIW7R3ipMsXJqIU,8946
|
13
|
+
appmesh-1.6.2.dist-info/METADATA,sha256=NljlBJBJxpevRRrgxFPyjNpxsYeu3FuUk7_klAhJCcs,11828
|
14
|
+
appmesh-1.6.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
15
|
+
appmesh-1.6.2.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
16
|
+
appmesh-1.6.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|