appmesh 1.3.1__py3-none-any.whl → 1.3.3__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/appmesh_client.py +242 -127
- {appmesh-1.3.1.dist-info → appmesh-1.3.3.dist-info}/METADATA +5 -6
- appmesh-1.3.3.dist-info/RECORD +6 -0
- {appmesh-1.3.1.dist-info → appmesh-1.3.3.dist-info}/WHEEL +1 -1
- appmesh-1.3.1.dist-info/RECORD +0 -6
- {appmesh-1.3.1.dist-info → appmesh-1.3.3.dist-info}/top_level.txt +0 -0
appmesh/appmesh_client.py
CHANGED
@@ -24,59 +24,48 @@ import requests
|
|
24
24
|
DEFAULT_TOKEN_EXPIRE_SECONDS = "P1W" # default 7 day(s)
|
25
25
|
DEFAULT_RUN_APP_TIMEOUT_SECONDS = "P2D" # 2 days
|
26
26
|
DEFAULT_RUN_APP_LIFECYCLE_SECONDS = "P2DT12H" # 2.5 days
|
27
|
+
|
27
28
|
REST_TEXT_MESSAGE_JSON_KEY = "message"
|
28
29
|
MESSAGE_ENCODING_UTF8 = "utf-8"
|
29
30
|
TCP_MESSAGE_HEADER_LENGTH = 4
|
31
|
+
|
30
32
|
_SSL_CA_PEM_FILE = "/opt/appmesh/ssl/ca.pem"
|
31
33
|
_SSL_CLIENT_PEM_FILE = "/opt/appmesh/ssl/client.pem"
|
32
34
|
_SSL_CLIENT_PEM_KEY_FILE = "/opt/appmesh/ssl/client-key.pem"
|
33
|
-
|
35
|
+
|
34
36
|
HTTP_USER_AGENT = "appmesh/python"
|
35
37
|
HTTP_USER_AGENT_TCP = "appmesh/python/tcp"
|
38
|
+
HTTP_HEADER_KEY_USER_AGENT = "User-Agent"
|
36
39
|
HTTP_HEADER_KEY_X_SEND_FILE_SOCKET = "X-Send-File-Socket"
|
37
40
|
HTTP_HEADER_KEY_X_RECV_FILE_SOCKET = "X-Recv-File-Socket"
|
38
41
|
HTTP_HEADER_KEY_X_TARGET_HOST = "X-Target-Host"
|
39
42
|
|
40
43
|
|
41
|
-
def _get_str_item(data: dict, key):
|
42
|
-
return data[key] if (data and key in data and data[key] and isinstance(data[key], str)) else None
|
43
|
-
|
44
|
-
|
45
|
-
def _get_int_item(data: dict, key):
|
46
|
-
return int(data[key]) if (data and key in data and data[key] and isinstance(data[key], int)) else None
|
47
|
-
|
48
|
-
|
49
|
-
def _get_bool_item(data: dict, key):
|
50
|
-
return bool(data[key]) if (data and key in data and data[key]) else None
|
51
|
-
|
52
|
-
|
53
|
-
def _get_native_item(data: dict, key):
|
54
|
-
return copy.deepcopy(data[key]) if (data and key in data and data[key]) else None
|
55
|
-
|
56
|
-
|
57
|
-
class AppOutput(object):
|
58
|
-
"""App output object for app_output() method"""
|
59
|
-
|
60
|
-
def __init__(self, status_code: HTTPStatus, output: str, out_position: Optional[int], exit_code: Optional[int]) -> None:
|
61
|
-
|
62
|
-
self.status_code = status_code
|
63
|
-
"""HTTP status code"""
|
64
|
-
|
65
|
-
self.output = output
|
66
|
-
"""HTTP response text"""
|
67
|
-
|
68
|
-
self.out_position = out_position
|
69
|
-
"""Current read position (int or None)"""
|
70
|
-
|
71
|
-
self.exit_code = exit_code
|
72
|
-
"""Process exit code (int or None)"""
|
73
|
-
|
74
|
-
|
75
44
|
class App(object):
|
76
45
|
"""
|
77
46
|
App object present an application in App Mesh
|
78
47
|
"""
|
79
48
|
|
49
|
+
@staticmethod
|
50
|
+
def _get_str_item(data: dict, key) -> Optional[str]:
|
51
|
+
"""Retrieve a string value from a dictionary by key, if it exists and is a valid string."""
|
52
|
+
return data[key] if (data and key in data and data[key] and isinstance(data[key], str)) else None
|
53
|
+
|
54
|
+
@staticmethod
|
55
|
+
def _get_int_item(data: dict, key) -> Optional[int]:
|
56
|
+
"""Retrieve an integer value from a dictionary by key, if it exists and is a valid integer."""
|
57
|
+
return int(data[key]) if (data and key in data and data[key] and isinstance(data[key], int)) else None
|
58
|
+
|
59
|
+
@staticmethod
|
60
|
+
def _get_bool_item(data: dict, key) -> Optional[bool]:
|
61
|
+
"""Retrieve a boolean value from a dictionary by key, if it exists."""
|
62
|
+
return bool(data[key]) if (data and key in data and data[key]) else None
|
63
|
+
|
64
|
+
@staticmethod
|
65
|
+
def _get_native_item(data: dict, key) -> Optional[object]:
|
66
|
+
"""Retrieve a deep copy of a value from a dictionary by key, if it exists."""
|
67
|
+
return copy.deepcopy(data[key]) if (data and key in data and data[key]) else None
|
68
|
+
|
80
69
|
@unique
|
81
70
|
class Permission(Enum):
|
82
71
|
"""Application permission definition"""
|
@@ -103,10 +92,10 @@ class App(object):
|
|
103
92
|
if isinstance(data, (str, bytes, bytearray)):
|
104
93
|
data = json.loads(data)
|
105
94
|
|
106
|
-
self.exit = _get_str_item(data, "exit")
|
95
|
+
self.exit = App._get_str_item(data, "exit")
|
107
96
|
"""default exit behavior [restart,standby,keepalive,remove]"""
|
108
97
|
|
109
|
-
self.control = _get_native_item(data, "control") if _get_native_item(data, "control") else dict()
|
98
|
+
self.control = App._get_native_item(data, "control") if App._get_native_item(data, "control") else dict()
|
110
99
|
"""exit code behavior (e.g, --control 0:restart --control 1:standby), higher priority than default exit behavior"""
|
111
100
|
|
112
101
|
def set_exit_behavior(self, a: Action) -> None:
|
@@ -126,10 +115,10 @@ class App(object):
|
|
126
115
|
if isinstance(data, (str, bytes, bytearray)):
|
127
116
|
data = json.loads(data)
|
128
117
|
|
129
|
-
self.daily_start = _get_int_item(data, "daily_start")
|
118
|
+
self.daily_start = App._get_int_item(data, "daily_start")
|
130
119
|
"""daily start time (e.g., '09:00:00+08')"""
|
131
120
|
|
132
|
-
self.daily_end = _get_int_item(data, "daily_end")
|
121
|
+
self.daily_end = App._get_int_item(data, "daily_end")
|
133
122
|
"""daily end time (e.g., '20:00:00+08')"""
|
134
123
|
|
135
124
|
def set_daily_range(self, start: datetime, end: datetime) -> None:
|
@@ -146,13 +135,13 @@ class App(object):
|
|
146
135
|
if isinstance(data, (str, bytes, bytearray)):
|
147
136
|
data = json.loads(data)
|
148
137
|
|
149
|
-
self.cpu_shares = _get_int_item(data, "cpu_shares")
|
138
|
+
self.cpu_shares = App._get_int_item(data, "cpu_shares")
|
150
139
|
"""CPU shares (relative weight)"""
|
151
140
|
|
152
|
-
self.memory_mb = _get_int_item(data, "memory_mb")
|
141
|
+
self.memory_mb = App._get_int_item(data, "memory_mb")
|
153
142
|
"""physical memory limit in MByte"""
|
154
143
|
|
155
|
-
self.memory_virt_mb = _get_int_item(data, "memory_virt_mb")
|
144
|
+
self.memory_virt_mb = App._get_int_item(data, "memory_virt_mb")
|
156
145
|
"""virtual memory limit in MByte"""
|
157
146
|
|
158
147
|
def __init__(self, data=None):
|
@@ -165,59 +154,59 @@ class App(object):
|
|
165
154
|
if isinstance(data, (str, bytes, bytearray)):
|
166
155
|
data = json.loads(data)
|
167
156
|
|
168
|
-
self.name = _get_str_item(data, "name")
|
157
|
+
self.name = App._get_str_item(data, "name")
|
169
158
|
"""application name (unique)"""
|
170
159
|
|
171
|
-
self.command = _get_str_item(data, "command")
|
160
|
+
self.command = App._get_str_item(data, "command")
|
172
161
|
"""full command line with arguments"""
|
173
162
|
|
174
|
-
self.shell = _get_bool_item(data, "shell")
|
163
|
+
self.shell = App._get_bool_item(data, "shell")
|
175
164
|
"""use shell mode, cmd can be more shell commands with string format"""
|
176
165
|
|
177
|
-
self.session_login = _get_bool_item(data, "session_login")
|
166
|
+
self.session_login = App._get_bool_item(data, "session_login")
|
178
167
|
"""app run in session login mode"""
|
179
168
|
|
180
|
-
self.description = _get_str_item(data, "description")
|
169
|
+
self.description = App._get_str_item(data, "description")
|
181
170
|
"""application description string"""
|
182
171
|
|
183
|
-
self.metadata = _get_native_item(data, "metadata")
|
172
|
+
self.metadata = App._get_native_item(data, "metadata")
|
184
173
|
"""metadata string/JSON (input for application, pass to process stdin)"""
|
185
174
|
|
186
|
-
self.working_dir = _get_str_item(data, "working_dir")
|
175
|
+
self.working_dir = App._get_str_item(data, "working_dir")
|
187
176
|
"""working directory"""
|
188
177
|
|
189
|
-
self.status = _get_int_item(data, "status")
|
178
|
+
self.status = App._get_int_item(data, "status")
|
190
179
|
"""initial application status (true is enable, false is disabled)"""
|
191
180
|
|
192
|
-
self.docker_image = _get_str_item(data, "docker_image")
|
181
|
+
self.docker_image = App._get_str_item(data, "docker_image")
|
193
182
|
"""docker image which used to run command line (for docker container application)"""
|
194
183
|
|
195
|
-
self.stdout_cache_num = _get_int_item(data, "stdout_cache_num")
|
184
|
+
self.stdout_cache_num = App._get_int_item(data, "stdout_cache_num")
|
196
185
|
"""stdout file cache number"""
|
197
186
|
|
198
|
-
self.start_time = _get_int_item(data, "start_time")
|
187
|
+
self.start_time = App._get_int_item(data, "start_time")
|
199
188
|
"""start date time for app (ISO8601 time format, e.g., '2020-10-11T09:22:05')"""
|
200
189
|
|
201
|
-
self.end_time = _get_int_item(data, "end_time")
|
190
|
+
self.end_time = App._get_int_item(data, "end_time")
|
202
191
|
"""end date time for app (ISO8601 time format, e.g., '2020-10-11T10:22:05')"""
|
203
192
|
|
204
|
-
self.interval = _get_int_item(data, "interval")
|
193
|
+
self.interval = App._get_int_item(data, "interval")
|
205
194
|
"""start interval seconds for short running app, support ISO 8601 durations and cron expression (e.g., 'P1Y2M3DT4H5M6S' 'P5W' '* */5 * * * *')"""
|
206
195
|
|
207
|
-
self.cron = _get_bool_item(data, "cron")
|
196
|
+
self.cron = App._get_bool_item(data, "cron")
|
208
197
|
"""indicate interval parameter use cron expression or not"""
|
209
198
|
|
210
|
-
self.daily_limitation = App.DailyLimitation(_get_native_item(data, "daily_limitation"))
|
199
|
+
self.daily_limitation = App.DailyLimitation(App._get_native_item(data, "daily_limitation"))
|
211
200
|
|
212
|
-
self.retention = _get_str_item(data, "retention")
|
201
|
+
self.retention = App._get_str_item(data, "retention")
|
213
202
|
"""extra timeout seconds for stopping current process, support ISO 8601 durations (e.g., 'P1Y2M3DT4H5M6S' 'P5W')."""
|
214
203
|
|
215
|
-
self.health_check_cmd = _get_str_item(data, "health_check_cmd")
|
204
|
+
self.health_check_cmd = App._get_str_item(data, "health_check_cmd")
|
216
205
|
"""health check script command (e.g., sh -x 'curl host:port/health', return 0 is health)"""
|
217
206
|
|
218
|
-
self.permission = _get_int_item(data, "permission")
|
207
|
+
self.permission = App._get_int_item(data, "permission")
|
219
208
|
"""application user permission, value is 2 bit integer: [group & other], each bit can be deny:1, read:2, write: 3."""
|
220
|
-
self.behavior = App.Behavior(_get_native_item(data, "behavior"))
|
209
|
+
self.behavior = App.Behavior(App._get_native_item(data, "behavior"))
|
221
210
|
|
222
211
|
self.env = dict()
|
223
212
|
"""environment variables (e.g., -e env1=value1 -e env2=value2, APP_DOCKER_OPTS is used to input docker run parameters)"""
|
@@ -231,32 +220,32 @@ class App(object):
|
|
231
220
|
for k, v in data["sec_env"].items():
|
232
221
|
self.sec_env[k] = v
|
233
222
|
|
234
|
-
self.pid = _get_int_item(data, "pid")
|
223
|
+
self.pid = App._get_int_item(data, "pid")
|
235
224
|
"""process id used to attach to the running process"""
|
236
|
-
self.resource_limit = App.ResourceLimitation(_get_native_item(data, "resource_limit"))
|
225
|
+
self.resource_limit = App.ResourceLimitation(App._get_native_item(data, "resource_limit"))
|
237
226
|
|
238
227
|
# readonly attributes
|
239
|
-
self.owner = _get_str_item(data, "owner")
|
228
|
+
self.owner = App._get_str_item(data, "owner")
|
240
229
|
"""owner name"""
|
241
|
-
self.pstree = _get_str_item(data, "pstree")
|
230
|
+
self.pstree = App._get_str_item(data, "pstree")
|
242
231
|
"""process tree"""
|
243
|
-
self.container_id = _get_str_item(data, "container_id")
|
232
|
+
self.container_id = App._get_str_item(data, "container_id")
|
244
233
|
"""container id"""
|
245
|
-
self.memory = _get_int_item(data, "memory")
|
234
|
+
self.memory = App._get_int_item(data, "memory")
|
246
235
|
"""memory usage"""
|
247
|
-
self.cpu = _get_int_item(data, "cpu")
|
236
|
+
self.cpu = App._get_int_item(data, "cpu")
|
248
237
|
"""cpu usage"""
|
249
|
-
self.fd = _get_int_item(data, "fd")
|
238
|
+
self.fd = App._get_int_item(data, "fd")
|
250
239
|
"""file descriptor usage"""
|
251
|
-
self.last_start_time = _get_int_item(data, "last_start_time")
|
240
|
+
self.last_start_time = App._get_int_item(data, "last_start_time")
|
252
241
|
"""last start time"""
|
253
|
-
self.last_exit_time = _get_int_item(data, "last_exit_time")
|
242
|
+
self.last_exit_time = App._get_int_item(data, "last_exit_time")
|
254
243
|
"""last exit time"""
|
255
|
-
self.health = _get_int_item(data, "health")
|
244
|
+
self.health = App._get_int_item(data, "health")
|
256
245
|
"""health status"""
|
257
|
-
self.version = _get_int_item(data, "version")
|
246
|
+
self.version = App._get_int_item(data, "version")
|
258
247
|
"""version number"""
|
259
|
-
self.return_code = _get_int_item(data, "return_code")
|
248
|
+
self.return_code = App._get_int_item(data, "return_code")
|
260
249
|
"""last exit code"""
|
261
250
|
|
262
251
|
def set_valid_time(self, start: datetime, end: datetime) -> None:
|
@@ -300,6 +289,24 @@ class App(object):
|
|
300
289
|
return output
|
301
290
|
|
302
291
|
|
292
|
+
class AppOutput(object):
|
293
|
+
"""App output information"""
|
294
|
+
|
295
|
+
def __init__(self, status_code: HTTPStatus, output: str, out_position: Optional[int], exit_code: Optional[int]) -> None:
|
296
|
+
|
297
|
+
self.status_code = status_code
|
298
|
+
"""HTTP status code"""
|
299
|
+
|
300
|
+
self.output = output
|
301
|
+
"""HTTP response text"""
|
302
|
+
|
303
|
+
self.out_position = out_position
|
304
|
+
"""Current read position (int or None)"""
|
305
|
+
|
306
|
+
self.exit_code = exit_code
|
307
|
+
"""Process exit code (int or None)"""
|
308
|
+
|
309
|
+
|
303
310
|
class AppRun(object):
|
304
311
|
"""
|
305
312
|
Application run object indicate to a remote run from run_async()
|
@@ -312,18 +319,18 @@ class AppRun(object):
|
|
312
319
|
"""process_uuid from run_async()"""
|
313
320
|
self._client = client
|
314
321
|
"""AppMeshClient object"""
|
315
|
-
self.
|
316
|
-
"""
|
322
|
+
self._forwarding_host = client.forwarding_host
|
323
|
+
"""forward host indicates the target server for this app run"""
|
317
324
|
|
318
325
|
@contextmanager
|
319
|
-
def
|
320
|
-
"""context manager for
|
321
|
-
original_value = self._client.
|
322
|
-
self._client.
|
326
|
+
def forwarding_host(self):
|
327
|
+
"""context manager for forward host override to self._client"""
|
328
|
+
original_value = self._client.forwarding_host
|
329
|
+
self._client.forwarding_host = self._forwarding_host
|
323
330
|
try:
|
324
331
|
yield
|
325
332
|
finally:
|
326
|
-
self._client.
|
333
|
+
self._client.forwarding_host = original_value
|
327
334
|
|
328
335
|
def wait(self, stdout_print: bool = True, timeout: int = 0) -> int:
|
329
336
|
"""Wait for an async run to be finished
|
@@ -335,15 +342,86 @@ class AppRun(object):
|
|
335
342
|
Returns:
|
336
343
|
int: return exit code if process finished, return None for timeout or exception.
|
337
344
|
"""
|
338
|
-
with self.
|
345
|
+
with self.forwarding_host():
|
339
346
|
return self._client.run_async_wait(self, stdout_print, timeout)
|
340
347
|
|
341
348
|
|
342
349
|
class AppMeshClient(metaclass=abc.ABCMeta):
|
343
|
-
"""
|
344
|
-
|
345
|
-
|
346
|
-
|
350
|
+
"""
|
351
|
+
Client SDK for interacting with the App Mesh service via REST API.
|
352
|
+
|
353
|
+
The `AppMeshClient` class provides a comprehensive interface for managing and monitoring distributed applications
|
354
|
+
within the App Mesh ecosystem. It enables communication with the App Mesh REST API for operations such as
|
355
|
+
application lifecycle management, monitoring, and configuration.
|
356
|
+
|
357
|
+
This client is designed for direct usage in applications that require access to App Mesh services over HTTP-based REST.
|
358
|
+
|
359
|
+
Usage:
|
360
|
+
- Install the App Mesh Python package:
|
361
|
+
python3 -m pip install --upgrade appmesh
|
362
|
+
- Import the client module:
|
363
|
+
from appmesh import appmesh_client
|
364
|
+
|
365
|
+
Example:
|
366
|
+
client = appmesh_client.AppMeshClient()
|
367
|
+
client.login("your-name", "your-password")
|
368
|
+
response = client.app_view(app_name='ping')
|
369
|
+
|
370
|
+
Attributes:
|
371
|
+
- TLS (Transport Layer Security): Supports secure connections between the client and App Mesh service,
|
372
|
+
ensuring encrypted communication.
|
373
|
+
- JWT (JSON Web Token) and RBAC (Role-Based Access Control): Provides secure API access with
|
374
|
+
token-based authentication and authorization to enforce fine-grained permissions.
|
375
|
+
|
376
|
+
Methods:
|
377
|
+
- login()
|
378
|
+
- logoff()
|
379
|
+
- authentication()
|
380
|
+
- renew()
|
381
|
+
- totp_disable()
|
382
|
+
- totp_secret()
|
383
|
+
- totp_setup()
|
384
|
+
|
385
|
+
- app_add()
|
386
|
+
- app_delete()
|
387
|
+
- app_disable()
|
388
|
+
- app_enable()
|
389
|
+
- app_health()
|
390
|
+
- app_output()
|
391
|
+
- app_view()
|
392
|
+
- app_view_all()
|
393
|
+
|
394
|
+
- run_async()
|
395
|
+
- run_async_wait()
|
396
|
+
- run_sync()
|
397
|
+
|
398
|
+
- config_set()
|
399
|
+
- config_view()
|
400
|
+
- log_level_set()
|
401
|
+
- host_resource()
|
402
|
+
- forwarding_host
|
403
|
+
- metrics()
|
404
|
+
|
405
|
+
- tag_add()
|
406
|
+
- tag_delete()
|
407
|
+
- tag_view()
|
408
|
+
|
409
|
+
- file_download()
|
410
|
+
- file_upload()
|
411
|
+
|
412
|
+
- user_add()
|
413
|
+
- user_delete()
|
414
|
+
- user_lock()
|
415
|
+
- user_passwd_update()
|
416
|
+
- user_self()
|
417
|
+
- user_unlock()
|
418
|
+
- users_view()
|
419
|
+
- permissions_for_user()
|
420
|
+
- permissions_view()
|
421
|
+
- role_delete()
|
422
|
+
- role_update()
|
423
|
+
- roles_view()
|
424
|
+
- groups_view()
|
347
425
|
"""
|
348
426
|
|
349
427
|
@unique
|
@@ -380,7 +458,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
380
458
|
self.ssl_verify = rest_ssl_verify
|
381
459
|
self.ssl_client_cert = rest_ssl_client_cert
|
382
460
|
self.rest_timeout = rest_timeout
|
383
|
-
self.
|
461
|
+
self._forwarding_host = None
|
384
462
|
|
385
463
|
@property
|
386
464
|
def jwt_token(self) -> str:
|
@@ -401,22 +479,22 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
401
479
|
self._jwt_token = token
|
402
480
|
|
403
481
|
@property
|
404
|
-
def
|
405
|
-
"""property for
|
482
|
+
def forwarding_host(self) -> str:
|
483
|
+
"""property for forwarding_host
|
406
484
|
|
407
485
|
Returns:
|
408
|
-
str:
|
486
|
+
str: forward request to target host (host:port)
|
409
487
|
"""
|
410
|
-
return self.
|
488
|
+
return self._forwarding_host
|
411
489
|
|
412
|
-
@
|
413
|
-
def
|
414
|
-
"""setter for
|
490
|
+
@forwarding_host.setter
|
491
|
+
def forwarding_host(self, host: str) -> None:
|
492
|
+
"""setter for forwarding_host
|
415
493
|
|
416
494
|
Args:
|
417
|
-
host (str):
|
495
|
+
host (str): forward request to target host (host:port)
|
418
496
|
"""
|
419
|
-
self.
|
497
|
+
self._forwarding_host = host
|
420
498
|
|
421
499
|
########################################
|
422
500
|
# Security
|
@@ -475,6 +553,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
475
553
|
resp = self._request_http(AppMeshClient.Method.POST, path="/appmesh/self/logoff")
|
476
554
|
if resp.status_code != HTTPStatus.OK:
|
477
555
|
raise Exception(resp.text)
|
556
|
+
self.jwt_token = None
|
478
557
|
return resp.status_code == HTTPStatus.OK
|
479
558
|
|
480
559
|
def authentication(self, token: str, permission=None) -> bool:
|
@@ -662,7 +741,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
662
741
|
return AppOutput(status_code=resp.status_code, output=resp.text, out_position=out_position, exit_code=exit_code)
|
663
742
|
|
664
743
|
def app_health(self, app_name: str) -> bool:
|
665
|
-
"""Get application health status
|
744
|
+
"""Get application health status
|
666
745
|
|
667
746
|
Args:
|
668
747
|
app_name (str): the application name.
|
@@ -1142,12 +1221,13 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1142
1221
|
########################################
|
1143
1222
|
# File management
|
1144
1223
|
########################################
|
1145
|
-
def file_download(self, file_path: str, local_file: str) -> bool:
|
1146
|
-
"""Copy a remote file to local, the local file will have the same permission as the remote file
|
1224
|
+
def file_download(self, file_path: str, local_file: str, apply_file_attributes: bool = True) -> bool:
|
1225
|
+
"""Copy a remote file to local. Optionally, the local file will have the same permission as the remote file.
|
1147
1226
|
|
1148
1227
|
Args:
|
1149
1228
|
file_path (str): the remote file path.
|
1150
1229
|
local_file (str): the local file path to be downloaded.
|
1230
|
+
apply_file_attributes (bool): whether to apply file attributes (permissions, owner, group) to the local file.
|
1151
1231
|
|
1152
1232
|
Returns:
|
1153
1233
|
bool: success or failure.
|
@@ -1156,23 +1236,27 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1156
1236
|
if resp.status_code != HTTPStatus.OK:
|
1157
1237
|
raise Exception(resp.text)
|
1158
1238
|
|
1239
|
+
# Write the file content locally
|
1159
1240
|
with open(local_file, "wb") as fp:
|
1160
1241
|
for chunk in resp.iter_content(chunk_size=512):
|
1161
1242
|
if chunk:
|
1162
1243
|
fp.write(chunk)
|
1163
|
-
|
1164
|
-
|
1165
|
-
if
|
1166
|
-
|
1167
|
-
|
1168
|
-
|
1169
|
-
|
1170
|
-
|
1171
|
-
|
1244
|
+
|
1245
|
+
# Apply file attributes (permissions, owner, group) if requested
|
1246
|
+
if apply_file_attributes:
|
1247
|
+
if "File-Mode" in resp.headers:
|
1248
|
+
os.chmod(path=local_file, mode=int(resp.headers["File-Mode"]))
|
1249
|
+
if "File-User" in resp.headers and "File-Group" in resp.headers:
|
1250
|
+
file_uid = int(resp.headers["File-User"])
|
1251
|
+
file_gid = int(resp.headers["File-Group"])
|
1252
|
+
try:
|
1253
|
+
os.chown(path=local_file, uid=file_uid, gid=file_gid)
|
1254
|
+
except Exception as ex:
|
1255
|
+
print(ex)
|
1172
1256
|
return resp.status_code == HTTPStatus.OK
|
1173
1257
|
|
1174
|
-
def file_upload(self, local_file: str, file_path: str) -> bool:
|
1175
|
-
"""Upload a local file to the remote server, the remote file will have the same permission as the local file
|
1258
|
+
def file_upload(self, local_file: str, file_path: str, apply_file_attributes: bool = True) -> bool:
|
1259
|
+
"""Upload a local file to the remote server. Optionally, the remote file will have the same permission as the local file.
|
1176
1260
|
|
1177
1261
|
Dependency:
|
1178
1262
|
sudo apt install python3-pip
|
@@ -1181,6 +1265,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1181
1265
|
Args:
|
1182
1266
|
local_file (str): the local file path.
|
1183
1267
|
file_path (str): the target remote file to be uploaded.
|
1268
|
+
apply_file_attributes (bool): whether to upload file attributes (permissions, owner, group) along with the file.
|
1184
1269
|
|
1185
1270
|
Returns:
|
1186
1271
|
bool: success or failure.
|
@@ -1189,13 +1274,16 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1189
1274
|
|
1190
1275
|
with open(file=local_file, mode="rb") as fp:
|
1191
1276
|
encoder = MultipartEncoder(fields={"filename": os.path.basename(file_path), "file": ("filename", fp, "application/octet-stream")})
|
1192
|
-
|
1193
|
-
|
1194
|
-
|
1195
|
-
|
1196
|
-
|
1197
|
-
|
1198
|
-
|
1277
|
+
header = {"File-Path": file_path, "Content-Type": encoder.content_type}
|
1278
|
+
|
1279
|
+
# Include file attributes (permissions, owner, group) if requested
|
1280
|
+
if apply_file_attributes:
|
1281
|
+
file_stat = os.stat(local_file)
|
1282
|
+
header["File-Mode"] = str(file_stat.st_mode)
|
1283
|
+
header["File-User"] = str(file_stat.st_uid)
|
1284
|
+
header["File-Group"] = str(file_stat.st_gid)
|
1285
|
+
|
1286
|
+
# Upload file with or without attributes
|
1199
1287
|
# https://stackoverflow.com/questions/22567306/python-requests-file-upload
|
1200
1288
|
resp = self._request_http(
|
1201
1289
|
AppMeshClient.Method.POST_STREAM,
|
@@ -1335,12 +1423,12 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1335
1423
|
header = {} if header is None else header
|
1336
1424
|
if self.jwt_token:
|
1337
1425
|
header["Authorization"] = "Bearer " + self.jwt_token
|
1338
|
-
if self.
|
1339
|
-
if ":" in self.
|
1340
|
-
header[HTTP_HEADER_KEY_X_TARGET_HOST] = self.
|
1426
|
+
if self.forwarding_host and len(self.forwarding_host) > 0:
|
1427
|
+
if ":" in self.forwarding_host:
|
1428
|
+
header[HTTP_HEADER_KEY_X_TARGET_HOST] = self.forwarding_host
|
1341
1429
|
else:
|
1342
|
-
header[HTTP_HEADER_KEY_X_TARGET_HOST] = self.
|
1343
|
-
header[
|
1430
|
+
header[HTTP_HEADER_KEY_X_TARGET_HOST] = self.forwarding_host + ":" + str(parse.urlsplit(self.server_url).port)
|
1431
|
+
header[HTTP_HEADER_KEY_USER_AGENT] = HTTP_USER_AGENT
|
1344
1432
|
|
1345
1433
|
if method is AppMeshClient.Method.GET:
|
1346
1434
|
return requests.get(url=rest_url, params=query, headers=header, cert=self.ssl_client_cert, verify=self.ssl_verify, timeout=self.rest_timeout)
|
@@ -1359,10 +1447,37 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1359
1447
|
|
1360
1448
|
|
1361
1449
|
class AppMeshClientTCP(AppMeshClient):
|
1362
|
-
"""
|
1450
|
+
"""
|
1451
|
+
Client SDK for interacting with the App Mesh service over TCP, with enhanced support for large file transfers.
|
1452
|
+
|
1453
|
+
The `AppMeshClientTCP` class extends the functionality of `AppMeshClient` by offering a TCP-based communication layer
|
1454
|
+
for the App Mesh REST API. It overrides the file download and upload methods to support large file transfers with
|
1455
|
+
improved performance, leveraging TCP for lower latency and higher throughput compared to HTTP.
|
1456
|
+
|
1457
|
+
This client is suitable for applications requiring efficient data transfers and high-throughput operations within the
|
1458
|
+
App Mesh ecosystem, while maintaining compatibility with all other attributes and methods from `AppMeshClient`.
|
1363
1459
|
|
1364
1460
|
Dependency:
|
1365
|
-
|
1461
|
+
- Install the required package for message serialization:
|
1462
|
+
pip3 install msgpack
|
1463
|
+
|
1464
|
+
Usage:
|
1465
|
+
- Import the client module:
|
1466
|
+
from appmesh import appmesh_client
|
1467
|
+
|
1468
|
+
Example:
|
1469
|
+
client = appmesh_client.AppMeshClientTCP()
|
1470
|
+
client.login("your-name", "your-password")
|
1471
|
+
client.file_download("/tmp/os-release", "os-release")
|
1472
|
+
|
1473
|
+
Attributes:
|
1474
|
+
- Inherits all attributes from `AppMeshClient`, including TLS secure connections and JWT-based authentication.
|
1475
|
+
- Optimized for TCP-based communication to provide better performance for large file transfers.
|
1476
|
+
|
1477
|
+
Methods:
|
1478
|
+
- file_download()
|
1479
|
+
- file_upload()
|
1480
|
+
- Inherits all other methods from `AppMeshClient`, providing a consistent interface for managing applications within App Mesh.
|
1366
1481
|
"""
|
1367
1482
|
|
1368
1483
|
def __init__(
|
@@ -1503,9 +1618,9 @@ class AppMeshClientTCP(AppMeshClient):
|
|
1503
1618
|
appmesh_requst = RequestMsg()
|
1504
1619
|
if super().jwt_token:
|
1505
1620
|
appmesh_requst.headers["Authorization"] = "Bearer " + super().jwt_token
|
1506
|
-
if super().
|
1507
|
-
raise Exception("Not support
|
1508
|
-
appmesh_requst.headers[
|
1621
|
+
if super().forwarding_host and len(super().forwarding_host) > 0:
|
1622
|
+
raise Exception("Not support forward request in TCP mode")
|
1623
|
+
appmesh_requst.headers[HTTP_HEADER_KEY_USER_AGENT] = HTTP_USER_AGENT_TCP
|
1509
1624
|
appmesh_requst.uuid = str(uuid.uuid1())
|
1510
1625
|
appmesh_requst.http_method = method.value
|
1511
1626
|
appmesh_requst.request_uri = path
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: appmesh
|
3
|
-
Version: 1.3.
|
3
|
+
Version: 1.3.3
|
4
4
|
Summary: Client SDK for App Mesh
|
5
5
|
Home-page: https://github.com/laoshanxi/app-mesh
|
6
6
|
Author: laoshanxi
|
@@ -25,11 +25,9 @@ Requires-Dist: aniso8601
|
|
25
25
|
</a>
|
26
26
|
[](https://api.securityscorecards.dev/projects/github.com/laoshanxi/app-mesh)
|
27
27
|
|
28
|
-
# App Mesh
|
28
|
+
# App Mesh: Advanced Application Management Platform
|
29
29
|
|
30
|
-
App Mesh is
|
31
|
-
|
32
|
-
App Mesh is similar to Kubernetes but is much more lightweight, supporting both containerized and native applications.
|
30
|
+
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.
|
33
31
|
|
34
32
|
<div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/diagram.png" align=center /></div>
|
35
33
|
|
@@ -87,7 +85,7 @@ Refer to the [Installation doc](https://app-mesh.readthedocs.io/en/latest/Instal
|
|
87
85
|
| Non-container app | √ |
|
88
86
|
| Service expose | √ | √ |
|
89
87
|
| Scheduler | √ | √ |
|
90
|
-
| Definition file |
|
88
|
+
| Definition file | YAML | YAML |
|
91
89
|
| GUI | √ | √ |
|
92
90
|
| Virtual Network | | √ |
|
93
91
|
| Monitor tools | √ | √ |
|
@@ -112,6 +110,7 @@ Refer to the [Installation doc](https://app-mesh.readthedocs.io/en/latest/Instal
|
|
112
110
|
- [Kubernetes run none-container applications](https://app-mesh.readthedocs.io/en/latest/success/kubernetes_run_native_application.html)
|
113
111
|
- [Remote execute](https://app-mesh.readthedocs.io/en/latest/success/remote_run_cli_and_python.html)
|
114
112
|
- [Python parallel run](https://app-mesh.readthedocs.io/en/latest/success/python_parallel_run.html)
|
113
|
+
- [Secure consul cluster](https://app-mesh.readthedocs.io/en/latest/success/secure_consul_cluster.html)
|
115
114
|
|
116
115
|
---
|
117
116
|
|
@@ -0,0 +1,6 @@
|
|
1
|
+
appmesh/__init__.py,sha256=xRdXeFHEieRauuJZElbEBASgXG0ZzU1a5_0isAhM7Gw,11
|
2
|
+
appmesh/appmesh_client.py,sha256=Hsp4at6YCLcAoz8eRx928amyCTqN4ZAIFnVHxEbzDrI,67720
|
3
|
+
appmesh-1.3.3.dist-info/METADATA,sha256=73_wYNYMLoHLoew35YGTd2bGKWhfScEObKhr07yyHx0,11191
|
4
|
+
appmesh-1.3.3.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
|
5
|
+
appmesh-1.3.3.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
6
|
+
appmesh-1.3.3.dist-info/RECORD,,
|
appmesh-1.3.1.dist-info/RECORD
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
appmesh/__init__.py,sha256=xRdXeFHEieRauuJZElbEBASgXG0ZzU1a5_0isAhM7Gw,11
|
2
|
-
appmesh/appmesh_client.py,sha256=BHgPST7m4vFbGCzHfg_kaqFZZyP9dW-h2M8mEurfyt0,62882
|
3
|
-
appmesh-1.3.1.dist-info/METADATA,sha256=qtqT4-GC1-PwliFwBHf-7Ixbt-UjgHiZ5DxO2JGkGfQ,11031
|
4
|
-
appmesh-1.3.1.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
|
5
|
-
appmesh-1.3.1.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
6
|
-
appmesh-1.3.1.dist-info/RECORD,,
|
File without changes
|