appmesh 1.5.8__py3-none-any.whl → 1.6.0__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 +5 -7
- appmesh/app.py +20 -12
- appmesh/app_output.py +4 -3
- appmesh/app_run.py +15 -6
- appmesh/appmesh_client.py +2 -2
- appmesh/{http_client.py → client_http.py} +23 -11
- appmesh/{tcp_client.py → client_tcp.py} +53 -33
- appmesh/{http_server.py → server_http.py} +2 -2
- appmesh/{tcp_server.py → server_tcp.py} +5 -4
- appmesh/tcp_messages.py +3 -3
- appmesh/tcp_transport.py +2 -1
- {appmesh-1.5.8.dist-info → appmesh-1.6.0.dist-info}/METADATA +3 -2
- appmesh-1.6.0.dist-info/RECORD +15 -0
- appmesh-1.5.8.dist-info/RECORD +0 -15
- {appmesh-1.5.8.dist-info → appmesh-1.6.0.dist-info}/WHEEL +0 -0
- {appmesh-1.5.8.dist-info → appmesh-1.6.0.dist-info}/top_level.txt +0 -0
appmesh/__init__.py
CHANGED
@@ -5,16 +5,14 @@ App Mesh SDK package initializer.
|
|
5
5
|
This module exports the main client classes used to interact with the App Mesh API.
|
6
6
|
|
7
7
|
Example:
|
8
|
-
from appmesh import AppMeshClient
|
9
|
-
|
8
|
+
from appmesh import AppMeshClient
|
10
9
|
client = AppMeshClient()
|
11
|
-
client_tcp = AppMeshClientTCP()
|
12
10
|
"""
|
13
11
|
|
14
12
|
from .app import App
|
15
|
-
from .
|
16
|
-
from .
|
17
|
-
from .
|
18
|
-
from .
|
13
|
+
from .client_http import AppMeshClient
|
14
|
+
from .client_tcp import AppMeshClientTCP
|
15
|
+
from .server_http import AppMeshServer
|
16
|
+
from .server_tcp import AppMeshServerTCP
|
19
17
|
|
20
18
|
__all__ = ["App", "AppMeshClient", "AppMeshClientTCP", "AppMeshServer", "AppMeshServerTCP"]
|
appmesh/app.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# app.py
|
1
2
|
"""Application definition"""
|
2
3
|
|
3
4
|
import json
|
@@ -10,9 +11,9 @@ from enum import Enum, unique
|
|
10
11
|
# pylint: disable=line-too-long
|
11
12
|
|
12
13
|
|
13
|
-
class App
|
14
|
+
class App:
|
14
15
|
"""
|
15
|
-
|
16
|
+
An application in App Mesh, include all the process attributes, resource limitations, behaviors, and permissions.
|
16
17
|
"""
|
17
18
|
|
18
19
|
@staticmethod
|
@@ -23,17 +24,24 @@ class App(object):
|
|
23
24
|
@staticmethod
|
24
25
|
def _get_int_item(data: dict, key: str) -> Optional[int]:
|
25
26
|
"""Retrieve an integer value from a dictionary by key, if it exists and is a valid integer."""
|
26
|
-
|
27
|
+
if data and key in data and data[key] is not None:
|
28
|
+
if isinstance(data[key], int):
|
29
|
+
return data[key]
|
30
|
+
elif isinstance(data[key], str) and data[key].isdigit():
|
31
|
+
return int(data[key])
|
32
|
+
return None
|
27
33
|
|
28
34
|
@staticmethod
|
29
35
|
def _get_bool_item(data: dict, key: str) -> Optional[bool]:
|
30
36
|
"""Retrieve a boolean value from a dictionary by key, if it exists and is boolean-like."""
|
31
|
-
|
37
|
+
if data and key in data and data[key] is not None:
|
38
|
+
return bool(data[key])
|
39
|
+
return None
|
32
40
|
|
33
41
|
@staticmethod
|
34
42
|
def _get_native_item(data: dict, key: str) -> Optional[object]:
|
35
43
|
"""Retrieve a deep copy of a value from a dictionary by key, if it exists."""
|
36
|
-
return copy.deepcopy(data[key]) if (data and key in data and data[key]) else None
|
44
|
+
return copy.deepcopy(data[key]) if (data and key in data and data[key] is not None) else None
|
37
45
|
|
38
46
|
@unique
|
39
47
|
class Permission(Enum):
|
@@ -43,7 +51,7 @@ class App(object):
|
|
43
51
|
READ = "2"
|
44
52
|
WRITE = "3"
|
45
53
|
|
46
|
-
class Behavior
|
54
|
+
class Behavior:
|
47
55
|
"""
|
48
56
|
Manages application error handling behavior, including exit and control behaviors.
|
49
57
|
"""
|
@@ -67,15 +75,15 @@ class App(object):
|
|
67
75
|
self.control = App._get_native_item(data, "control") or {}
|
68
76
|
"""Exit code specific behavior (e.g, --control 0:restart --control 1:standby), higher priority than default exit behavior"""
|
69
77
|
|
70
|
-
def set_exit_behavior(self, action: Action) -> None:
|
78
|
+
def set_exit_behavior(self, action: "App.Behavior.Action") -> None:
|
71
79
|
"""Set default behavior for application exit."""
|
72
80
|
self.exit = action.value
|
73
81
|
|
74
|
-
def set_control_behavior(self, control_code: int, action: Action) -> None:
|
82
|
+
def set_control_behavior(self, control_code: int, action: "App.Behavior.Action") -> None:
|
75
83
|
"""Define behavior for specific exit codes."""
|
76
84
|
self.control[str(control_code)] = action.value
|
77
85
|
|
78
|
-
class DailyLimitation
|
86
|
+
class DailyLimitation:
|
79
87
|
"""
|
80
88
|
Defines application availability within a daily time range.
|
81
89
|
"""
|
@@ -88,14 +96,14 @@ class App(object):
|
|
88
96
|
"""Start time for application availability (e.g., 09:00:00+08)."""
|
89
97
|
|
90
98
|
self.daily_end = App._get_int_item(data, "daily_end")
|
91
|
-
"""End time for application availability (e.g.,
|
99
|
+
"""End time for application availability (e.g., 09:00:00+08)."""
|
92
100
|
|
93
101
|
def set_daily_range(self, start: datetime, end: datetime) -> None:
|
94
102
|
"""Set the valid daily start and end times."""
|
95
103
|
self.daily_start = int(start.timestamp())
|
96
104
|
self.daily_end = int(end.timestamp())
|
97
105
|
|
98
|
-
class ResourceLimitation
|
106
|
+
class ResourceLimitation:
|
99
107
|
"""
|
100
108
|
Defines application resource limits, such as CPU and memory usage.
|
101
109
|
"""
|
@@ -217,7 +225,7 @@ class App(object):
|
|
217
225
|
keys_to_delete = []
|
218
226
|
for key, value in data.items():
|
219
227
|
if isinstance(value, dict) and key != "metadata":
|
220
|
-
clean_empty(value) # Recursive call (without
|
228
|
+
clean_empty(value) # Recursive call (without checking user metadata)
|
221
229
|
if data[key] in [None, "", {}]:
|
222
230
|
keys_to_delete.append(key) # Mark keys for deletion
|
223
231
|
|
appmesh/app_output.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# app_output.py
|
1
2
|
"""Application output information"""
|
2
3
|
|
3
4
|
from http import HTTPStatus
|
@@ -8,13 +9,13 @@ from typing import Optional
|
|
8
9
|
|
9
10
|
class AppOutput(object):
|
10
11
|
"""
|
11
|
-
Represents the output information returned by the `app_output()` API
|
12
|
-
stdout
|
12
|
+
Represents the output information returned by the `app_output()` API.
|
13
|
+
including the application's stdout , current read position, http status code, and process exit code.
|
13
14
|
"""
|
14
15
|
|
15
16
|
def __init__(self, status_code: HTTPStatus, output: str, out_position: Optional[int], exit_code: Optional[int]) -> None:
|
16
17
|
self.status_code = status_code
|
17
|
-
"""HTTP status code from the `app_output()` API request, indicating the
|
18
|
+
"""HTTP status code from the `app_output()` API request, indicating the http status."""
|
18
19
|
|
19
20
|
self.output = output
|
20
21
|
"""Captured stdout content of the application as returned by the `app_output()` API."""
|
appmesh/app_run.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
|
-
|
1
|
+
# app_run.py
|
2
|
+
"""Application run object for remote application execution."""
|
2
3
|
|
3
4
|
from contextlib import contextmanager
|
4
5
|
|
5
6
|
# pylint: disable=line-too-long
|
6
7
|
|
7
8
|
|
8
|
-
class AppRun
|
9
|
+
class AppRun:
|
9
10
|
"""
|
10
11
|
Represents an application run object initiated by `run_async()` for monitoring and retrieving
|
11
12
|
the result of a remote application run.
|
@@ -26,7 +27,14 @@ class AppRun(object):
|
|
26
27
|
|
27
28
|
@contextmanager
|
28
29
|
def forward_to(self):
|
29
|
-
"""Context manager to override the `forward_to`
|
30
|
+
"""Context manager to temporarily override the client's `forward_to` setting.
|
31
|
+
|
32
|
+
This ensures that operations during this run use the correct target server,
|
33
|
+
then restores the original setting when done.
|
34
|
+
|
35
|
+
Yields:
|
36
|
+
None: Context for the overridden forward_to setting.
|
37
|
+
"""
|
30
38
|
original_value = self._client.forward_to
|
31
39
|
self._client.forward_to = self._forward_to
|
32
40
|
try:
|
@@ -38,11 +46,12 @@ class AppRun(object):
|
|
38
46
|
"""Wait for the asynchronous run to complete.
|
39
47
|
|
40
48
|
Args:
|
41
|
-
stdout_print
|
42
|
-
timeout
|
49
|
+
stdout_print: If `True`, prints remote stdout to local console. Defaults to `True`.
|
50
|
+
timeout: Maximum time to wait in seconds. If `0`, waits indefinitely until completion.
|
51
|
+
Defaults to `0`.
|
43
52
|
|
44
53
|
Returns:
|
45
|
-
|
54
|
+
Exit code if the process finishes successfully, or `None` on timeout or exception.
|
46
55
|
"""
|
47
56
|
with self.forward_to():
|
48
57
|
return self._client.wait_for_async_run(self, stdout_print, timeout)
|
appmesh/appmesh_client.py
CHANGED
@@ -3,6 +3,6 @@
|
|
3
3
|
# Legacy Compatibility Layer
|
4
4
|
# These imports provide backward compatibility for older code that relies on
|
5
5
|
# AppMeshClient, App, and AppOutput classes. The updated implementation can be found
|
6
|
-
# in
|
6
|
+
# in client_http.py, where these classes are now primarily maintained.
|
7
7
|
|
8
|
-
from .
|
8
|
+
from .client_http import AppMeshClient, App, AppOutput
|
@@ -1,10 +1,11 @@
|
|
1
|
-
#
|
1
|
+
# client_http.py
|
2
2
|
# pylint: disable=broad-exception-raised,line-too-long,broad-exception-caught,too-many-lines, import-outside-toplevel, protected-access
|
3
3
|
import abc
|
4
4
|
import base64
|
5
5
|
import json
|
6
6
|
import logging
|
7
7
|
import os
|
8
|
+
import time
|
8
9
|
from datetime import datetime
|
9
10
|
from enum import Enum, unique
|
10
11
|
from http import HTTPStatus
|
@@ -14,7 +15,6 @@ from urllib import parse
|
|
14
15
|
import aniso8601
|
15
16
|
import jwt
|
16
17
|
import requests
|
17
|
-
import time
|
18
18
|
from .app import App
|
19
19
|
from .app_run import AppRun
|
20
20
|
from .app_output import AppOutput
|
@@ -71,6 +71,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
71
71
|
- run_app_async()
|
72
72
|
- wait_for_async_run()
|
73
73
|
- run_app_sync()
|
74
|
+
- run_task()
|
74
75
|
|
75
76
|
# System Management
|
76
77
|
- forward_to
|
@@ -108,9 +109,11 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
108
109
|
DURATION_TWO_DAYS_HALF_ISO = "P2DT12H"
|
109
110
|
TOKEN_REFRESH_INTERVAL = 60
|
110
111
|
|
111
|
-
|
112
|
-
|
113
|
-
|
112
|
+
# Platform-aware default SSL paths
|
113
|
+
_DEFAULT_SSL_DIR = "c:/local/appmesh/ssl" if os.name == "nt" else "/opt/appmesh/ssl"
|
114
|
+
DEFAULT_SSL_CA_CERT_PATH = os.path.join(_DEFAULT_SSL_DIR, "ca.pem")
|
115
|
+
DEFAULT_SSL_CLIENT_CERT_PATH = os.path.join(_DEFAULT_SSL_DIR, "client.pem")
|
116
|
+
DEFAULT_SSL_CLIENT_KEY_PATH = os.path.join(_DEFAULT_SSL_DIR, "client-key.pem")
|
114
117
|
|
115
118
|
DEFAULT_JWT_AUDIENCE = "appmesh-service"
|
116
119
|
|
@@ -322,7 +325,7 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
322
325
|
try:
|
323
326
|
self.close()
|
324
327
|
except Exception:
|
325
|
-
pass #
|
328
|
+
pass # Never raise in __del__
|
326
329
|
|
327
330
|
@property
|
328
331
|
def jwt_token(self) -> str:
|
@@ -1396,9 +1399,9 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1396
1399
|
|
1397
1400
|
try:
|
1398
1401
|
if method is AppMeshClient.Method.GET:
|
1399
|
-
|
1402
|
+
resp = self.session.get(url=rest_url, params=query, headers=header, cert=self.ssl_client_cert, verify=self.ssl_verify, timeout=self.rest_timeout)
|
1400
1403
|
elif method is AppMeshClient.Method.POST:
|
1401
|
-
|
1404
|
+
resp = self.session.post(
|
1402
1405
|
url=rest_url,
|
1403
1406
|
params=query,
|
1404
1407
|
headers=header,
|
@@ -1408,12 +1411,21 @@ class AppMeshClient(metaclass=abc.ABCMeta):
|
|
1408
1411
|
timeout=self.rest_timeout,
|
1409
1412
|
)
|
1410
1413
|
elif method is AppMeshClient.Method.POST_STREAM:
|
1411
|
-
|
1414
|
+
resp = self.session.post(url=rest_url, params=query, headers=header, data=body, cert=self.ssl_client_cert, verify=self.ssl_verify, stream=True, timeout=self.rest_timeout)
|
1412
1415
|
elif method is AppMeshClient.Method.DELETE:
|
1413
|
-
|
1416
|
+
resp = self.session.delete(url=rest_url, headers=header, cert=self.ssl_client_cert, verify=self.ssl_verify, timeout=self.rest_timeout)
|
1414
1417
|
elif method is AppMeshClient.Method.PUT:
|
1415
|
-
|
1418
|
+
resp = self.session.put(url=rest_url, params=query, headers=header, data=body, cert=self.ssl_client_cert, verify=self.ssl_verify, timeout=self.rest_timeout)
|
1416
1419
|
else:
|
1417
1420
|
raise Exception("Invalid http method", method)
|
1421
|
+
|
1422
|
+
# Ensure response text decoding uses UTF-8 by default
|
1423
|
+
try:
|
1424
|
+
resp.encoding = "utf-8"
|
1425
|
+
except Exception:
|
1426
|
+
# If setting encoding fails for any reason, ignore and return the response as-is
|
1427
|
+
pass
|
1428
|
+
|
1429
|
+
return resp
|
1418
1430
|
except requests.exceptions.RequestException as e:
|
1419
1431
|
raise Exception(f"HTTP request failed: {str(e)}")
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# client_tcp.py
|
2
2
|
# pylint: disable=line-too-long,broad-exception-raised,broad-exception-caught,import-outside-toplevel,protected-access
|
3
3
|
|
4
4
|
import json
|
@@ -6,7 +6,7 @@ import os
|
|
6
6
|
import socket
|
7
7
|
import uuid
|
8
8
|
import requests
|
9
|
-
from .
|
9
|
+
from .client_http import AppMeshClient
|
10
10
|
from .tcp_transport import TCPTransport
|
11
11
|
from .tcp_messages import RequestMessage, ResponseMessage
|
12
12
|
|
@@ -28,10 +28,10 @@ class AppMeshClientTCP(AppMeshClient):
|
|
28
28
|
|
29
29
|
Usage:
|
30
30
|
- Import the client module:
|
31
|
-
from appmesh import
|
31
|
+
from appmesh import AppMeshClientTCP
|
32
32
|
|
33
33
|
Example:
|
34
|
-
client =
|
34
|
+
client = AppMeshClientTCP()
|
35
35
|
client.login("your-name", "your-password")
|
36
36
|
client.file_download("/tmp/os-release", "os-release")
|
37
37
|
|
@@ -83,20 +83,34 @@ class AppMeshClientTCP(AppMeshClient):
|
|
83
83
|
self.tcp_transport = TCPTransport(address=tcp_address, ssl_verify=rest_ssl_verify, ssl_client_cert=rest_ssl_client_cert)
|
84
84
|
super().__init__(rest_ssl_verify=rest_ssl_verify, rest_ssl_client_cert=rest_ssl_client_cert, jwt_token=jwt_token)
|
85
85
|
|
86
|
+
def close(self):
|
87
|
+
"""Close the connection and release resources."""
|
88
|
+
if hasattr(self, "tcp_transport") and self.tcp_transport:
|
89
|
+
self.tcp_transport.close()
|
90
|
+
self.tcp_transport = None
|
91
|
+
return super().close()
|
92
|
+
|
93
|
+
def __del__(self):
|
94
|
+
"""Ensure resources are properly released when the object is garbage collected."""
|
95
|
+
try:
|
96
|
+
self.close()
|
97
|
+
except Exception:
|
98
|
+
pass # Never raise in __del__
|
99
|
+
super().__del__()
|
100
|
+
|
86
101
|
def _request_http(self, method: AppMeshClient.Method, path: str, query: dict = None, header: dict = None, body=None) -> requests.Response:
|
87
|
-
"""TCP
|
102
|
+
"""Send HTTP request over TCP transport.
|
88
103
|
|
89
104
|
Args:
|
90
|
-
method (Method):
|
91
|
-
path (str): URI
|
92
|
-
query (dict, optional):
|
105
|
+
method (Method): HTTP method.
|
106
|
+
path (str): URI path.
|
107
|
+
query (dict, optional): Query parameters.
|
93
108
|
header (dict, optional): HTTP headers.
|
94
|
-
body
|
109
|
+
body: Request body.
|
95
110
|
|
96
111
|
Returns:
|
97
|
-
requests.Response: HTTP response
|
112
|
+
requests.Response: Simulated HTTP response.
|
98
113
|
"""
|
99
|
-
|
100
114
|
if not self.tcp_transport.connected():
|
101
115
|
self.tcp_transport.connect()
|
102
116
|
|
@@ -110,8 +124,9 @@ class AppMeshClientTCP(AppMeshClient):
|
|
110
124
|
appmesh_request.http_method = method.value
|
111
125
|
appmesh_request.request_uri = path
|
112
126
|
appmesh_request.client_addr = socket.gethostname()
|
127
|
+
|
113
128
|
if body:
|
114
|
-
if isinstance(body, dict
|
129
|
+
if isinstance(body, (dict, list)):
|
115
130
|
appmesh_request.body = bytes(json.dumps(body, indent=2), self.ENCODING_UTF8)
|
116
131
|
elif isinstance(body, str):
|
117
132
|
appmesh_request.body = bytes(body, self.ENCODING_UTF8)
|
@@ -119,20 +134,23 @@ class AppMeshClientTCP(AppMeshClient):
|
|
119
134
|
appmesh_request.body = body
|
120
135
|
else:
|
121
136
|
raise Exception(f"UnSupported body type: {type(body)}")
|
137
|
+
|
122
138
|
if header:
|
123
139
|
for k, v in header.items():
|
124
140
|
appmesh_request.headers[k] = v
|
125
141
|
if query:
|
126
142
|
for k, v in query.items():
|
127
143
|
appmesh_request.querys[k] = v
|
144
|
+
|
128
145
|
data = appmesh_request.serialize()
|
129
146
|
self.tcp_transport.send_message(data)
|
130
147
|
|
131
148
|
resp_data = self.tcp_transport.receive_message()
|
132
|
-
if resp_data
|
149
|
+
if not resp_data: # Covers None and empty bytes
|
133
150
|
self.tcp_transport.close()
|
134
151
|
raise Exception("socket connection broken")
|
135
|
-
|
152
|
+
|
153
|
+
appmesh_resp = ResponseMessage().deserialize(resp_data)
|
136
154
|
response = requests.Response()
|
137
155
|
response.status_code = appmesh_resp.http_status
|
138
156
|
response.encoding = self.ENCODING_UTF8
|
@@ -140,24 +158,27 @@ class AppMeshClientTCP(AppMeshClient):
|
|
140
158
|
response.headers = appmesh_resp.headers
|
141
159
|
if appmesh_resp.body_msg_type:
|
142
160
|
response.headers["Content-Type"] = appmesh_resp.body_msg_type
|
161
|
+
|
143
162
|
return response
|
144
163
|
|
145
164
|
########################################
|
146
165
|
# File management
|
147
166
|
########################################
|
148
167
|
def download_file(self, remote_file: str, local_file: str, apply_file_attributes: bool = True) -> None:
|
149
|
-
"""Copy a remote file to local,
|
168
|
+
"""Copy a remote file to local, preserving file attributes if requested.
|
150
169
|
|
151
170
|
Args:
|
152
|
-
remote_file (str):
|
153
|
-
local_file (str):
|
154
|
-
apply_file_attributes (bool):
|
171
|
+
remote_file (str): Remote file path.
|
172
|
+
local_file (str): Local destination path.
|
173
|
+
apply_file_attributes (bool): Apply remote file permissions/ownership locally.
|
155
174
|
"""
|
156
|
-
header = {
|
157
|
-
|
175
|
+
header = {
|
176
|
+
AppMeshClient.HTTP_HEADER_KEY_X_FILE_PATH: remote_file,
|
177
|
+
self.HTTP_HEADER_KEY_X_RECV_FILE_SOCKET: "true",
|
178
|
+
}
|
158
179
|
resp = self._request_http(AppMeshClient.Method.GET, path="/appmesh/file/download", header=header)
|
159
|
-
|
160
180
|
resp.raise_for_status()
|
181
|
+
|
161
182
|
if self.HTTP_HEADER_KEY_X_RECV_FILE_SOCKET not in resp.headers:
|
162
183
|
raise ValueError(f"Server did not respond with socket transfer option: {self.HTTP_HEADER_KEY_X_RECV_FILE_SOCKET}")
|
163
184
|
|
@@ -180,23 +201,22 @@ class AppMeshClientTCP(AppMeshClient):
|
|
180
201
|
print(f"Warning: Unable to change owner/group of {local_file}. Operation requires elevated privileges.")
|
181
202
|
|
182
203
|
def upload_file(self, local_file: str, remote_file: str, apply_file_attributes: bool = True) -> None:
|
183
|
-
"""Upload a local file to
|
184
|
-
|
185
|
-
Dependency:
|
186
|
-
sudo apt install python3-pip
|
187
|
-
pip3 install requests_toolbelt
|
204
|
+
"""Upload a local file to remote server, preserving file attributes if requested.
|
188
205
|
|
189
206
|
Args:
|
190
|
-
local_file (str):
|
191
|
-
remote_file (str):
|
192
|
-
apply_file_attributes (bool):
|
207
|
+
local_file (str): Local file path.
|
208
|
+
remote_file (str): Remote destination path.
|
209
|
+
apply_file_attributes (bool): Upload file permissions/ownership metadata.
|
193
210
|
"""
|
194
211
|
if not os.path.exists(local_file):
|
195
212
|
raise FileNotFoundError(f"Local file not found: {local_file}")
|
196
213
|
|
197
214
|
with open(file=local_file, mode="rb") as fp:
|
198
|
-
header = {
|
199
|
-
|
215
|
+
header = {
|
216
|
+
AppMeshClient.HTTP_HEADER_KEY_X_FILE_PATH: remote_file,
|
217
|
+
"Content-Type": "text/plain",
|
218
|
+
self.HTTP_HEADER_KEY_X_SEND_FILE_SOCKET: "true",
|
219
|
+
}
|
200
220
|
|
201
221
|
if apply_file_attributes:
|
202
222
|
file_stat = os.stat(local_file)
|
@@ -205,8 +225,8 @@ class AppMeshClientTCP(AppMeshClient):
|
|
205
225
|
header["X-File-Group"] = str(file_stat.st_gid)
|
206
226
|
|
207
227
|
resp = self._request_http(AppMeshClient.Method.POST, path="/appmesh/file/upload", header=header)
|
208
|
-
|
209
228
|
resp.raise_for_status()
|
229
|
+
|
210
230
|
if self.HTTP_HEADER_KEY_X_SEND_FILE_SOCKET not in resp.headers:
|
211
231
|
raise ValueError(f"Server did not respond with socket transfer option: {self.HTTP_HEADER_KEY_X_SEND_FILE_SOCKET}")
|
212
232
|
|
@@ -214,6 +234,6 @@ class AppMeshClientTCP(AppMeshClient):
|
|
214
234
|
while True:
|
215
235
|
chunk_data = fp.read(chunk_size)
|
216
236
|
if not chunk_data:
|
217
|
-
self.tcp_transport.send_message([])
|
237
|
+
self.tcp_transport.send_message([]) # EOF signal
|
218
238
|
break
|
219
239
|
self.tcp_transport.send_message(chunk_data)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# server_http.py
|
2
2
|
# pylint: disable=line-too-long,broad-exception-raised,broad-exception-caught,import-outside-toplevel,protected-access
|
3
3
|
|
4
4
|
import abc
|
@@ -7,7 +7,7 @@ import os
|
|
7
7
|
import time
|
8
8
|
from typing import Optional, Tuple
|
9
9
|
from http import HTTPStatus
|
10
|
-
from .
|
10
|
+
from .client_http import AppMeshClient
|
11
11
|
|
12
12
|
logger = logging.getLogger(__name__)
|
13
13
|
|
@@ -1,12 +1,12 @@
|
|
1
|
-
#
|
1
|
+
# server_tcp.py
|
2
2
|
# pylint: disable=line-too-long,broad-exception-raised,broad-exception-caught,import-outside-toplevel,protected-access
|
3
3
|
|
4
4
|
import os
|
5
5
|
import logging
|
6
6
|
from typing import Optional, Tuple
|
7
|
-
from .
|
8
|
-
from .
|
9
|
-
from .
|
7
|
+
from .client_http import AppMeshClient
|
8
|
+
from .client_tcp import AppMeshClientTCP
|
9
|
+
from .server_http import AppMeshServer
|
10
10
|
|
11
11
|
logger = logging.getLogger(__name__)
|
12
12
|
|
@@ -31,5 +31,6 @@ class AppMeshServerTCP(AppMeshServer):
|
|
31
31
|
"""
|
32
32
|
# Deliberately avoid calling super().__init__ to inject a TCP client while keeping the same public API.
|
33
33
|
object.__init__(self)
|
34
|
+
# super().__init__(rest_ssl_verify=rest_ssl_verify, rest_ssl_client_cert=rest_ssl_client_cert)
|
34
35
|
self._client = AppMeshClientTCP(rest_ssl_verify=rest_ssl_verify, rest_ssl_client_cert=rest_ssl_client_cert, tcp_address=tcp_address)
|
35
36
|
self._logger = logger_ or logger
|
appmesh/tcp_messages.py
CHANGED
@@ -4,7 +4,7 @@ import msgpack
|
|
4
4
|
|
5
5
|
|
6
6
|
class RequestMessage:
|
7
|
-
"""
|
7
|
+
"""TCP request message for HTTP-like communication"""
|
8
8
|
|
9
9
|
uuid: str = ""
|
10
10
|
request_uri: str = ""
|
@@ -24,7 +24,7 @@ class RequestMessage:
|
|
24
24
|
|
25
25
|
|
26
26
|
class ResponseMessage:
|
27
|
-
"""
|
27
|
+
"""TCP response message for HTTP-like communication"""
|
28
28
|
|
29
29
|
uuid: str = ""
|
30
30
|
request_uri: str = ""
|
@@ -33,7 +33,7 @@ class ResponseMessage:
|
|
33
33
|
body: str = ""
|
34
34
|
headers: dict = {}
|
35
35
|
|
36
|
-
def
|
36
|
+
def deserialize(self, buf: bytes):
|
37
37
|
"""Deserialize response message"""
|
38
38
|
dic = msgpack.unpackb(buf)
|
39
39
|
for k, v in dic.items():
|
appmesh/tcp_transport.py
CHANGED
@@ -67,6 +67,7 @@ class TCPTransport:
|
|
67
67
|
context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
|
68
68
|
# Configure SSL verification
|
69
69
|
if not self.ssl_verify:
|
70
|
+
context.check_hostname = False
|
70
71
|
context.verify_mode = ssl.CERT_NONE
|
71
72
|
else:
|
72
73
|
context.verify_mode = ssl.CERT_REQUIRED # Require certificate verification
|
@@ -94,7 +95,7 @@ class TCPTransport:
|
|
94
95
|
# Create a TCP socket
|
95
96
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
96
97
|
sock.setblocking(True)
|
97
|
-
sock.settimeout(30) # Connection timeout set to 30 seconds
|
98
|
+
# sock.settimeout(30) # Connection timeout set to 30 seconds
|
98
99
|
try:
|
99
100
|
# Wrap the socket with SSL/TLS
|
100
101
|
ssl_socket = context.wrap_socket(sock, server_hostname=self.tcp_address[0])
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: appmesh
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.6.0
|
4
4
|
Summary: Client SDK for App Mesh
|
5
5
|
Home-page: https://github.com/laoshanxi/app-mesh
|
6
6
|
Author: laoshanxi
|
@@ -102,7 +102,7 @@ Refer to the [Installation doc](https://app-mesh.readthedocs.io/en/latest/Instal
|
|
102
102
|
| GUI | √ | √ |
|
103
103
|
| Virtual Network | | √ |
|
104
104
|
| Monitor tools | √ | √ |
|
105
|
-
| [Remote
|
105
|
+
| [Remote task](https://app-mesh.readthedocs.io/en/latest/RemoteTask.html) | √ | |
|
106
106
|
|
107
107
|
---
|
108
108
|
|
@@ -126,6 +126,7 @@ Refer to the [Installation doc](https://app-mesh.readthedocs.io/en/latest/Instal
|
|
126
126
|
- [Python parallel run](https://app-mesh.readthedocs.io/en/latest/success/python_parallel_run.html)
|
127
127
|
- [Secure consul cluster](https://app-mesh.readthedocs.io/en/latest/success/secure_consul_cluster.html)
|
128
128
|
- [JWT service with REST and UI](https://github.com/laoshanxi/app-mesh/blob/main/script/docker-compose-auth-service.yaml)
|
129
|
+
- [Remote task execute](https://app-mesh.readthedocs.io/en/latest/RemoteTask.html)
|
129
130
|
|
130
131
|
---
|
131
132
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
appmesh/__init__.py,sha256=_I-kLcLy7DM3gG2JJTRM_hUxBu3zvIPm2wCsz3NYes4,493
|
2
|
+
appmesh/app.py,sha256=4lo66ob1ZFDgL8VYKxH4_sh-vbHcGkZNThURE0go-d4,10732
|
3
|
+
appmesh/app_output.py,sha256=vfn322AyixblI8DbXds08h6L_ybObiaRSifsA1-Xcoo,1035
|
4
|
+
appmesh/app_run.py,sha256=TJ9xVX8xqUN9-OOmr_8JlgXoyg3hTmDFNKvGf4w_oR4,1980
|
5
|
+
appmesh/appmesh_client.py,sha256=7yy9c_ygJbqMekrUsHWmRObwHLK82qP65xQLXxfGd8U,339
|
6
|
+
appmesh/client_http.py,sha256=jUIZJHrnu2-r6N6Cd9lQNPeQcZefB7ENl6DUn8Mugck,57896
|
7
|
+
appmesh/client_tcp.py,sha256=hE0T_2Z0OQZdF5zi--iuvu2_-ka0DfSSQ4BP3oHlg44,11243
|
8
|
+
appmesh/server_http.py,sha256=zr9sfS8g_mtP2kuAfmz-qU7rLSFTVt3srbFXaCbl8Y0,4253
|
9
|
+
appmesh/server_tcp.py,sha256=biBFF5IGWFOw2ru831cfmzn1DVXcBm9e-W6CP2VkfzE,1444
|
10
|
+
appmesh/tcp_messages.py,sha256=4XRv5lSm_8ElMg_37SuyIRrfxI7XFNNP_7SdO7us3PA,1023
|
11
|
+
appmesh/tcp_transport.py,sha256=3B0w8AHXnb-E49Odxm5bPNBve1P-YiMHNDxFO2YYYVE,8690
|
12
|
+
appmesh-1.6.0.dist-info/METADATA,sha256=VKKRa3mUsX0oahVUosSDJCR4mlsqTX-ua51-JskhSwk,11847
|
13
|
+
appmesh-1.6.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
+
appmesh-1.6.0.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
15
|
+
appmesh-1.6.0.dist-info/RECORD,,
|
appmesh-1.5.8.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
appmesh/__init__.py,sha256=fQHuLWBaMZmbs5i5AV4KIXhv9YlIWkhYIv9WSLGNLm4,548
|
2
|
-
appmesh/app.py,sha256=9Q-SOOej-MH13BU5Dv2iTa-p-sECCJQp6ZX9DjWWmwE,10526
|
3
|
-
appmesh/app_output.py,sha256=JK_TMKgjvaw4n_ys_vmN5S4MyWVZpmD7NlKz_UyMIM8,1015
|
4
|
-
appmesh/app_run.py,sha256=9ISKGZ3k3kkbQvSsPfRfkOLqD9xhbqNOM7ork9F4w9c,1712
|
5
|
-
appmesh/appmesh_client.py,sha256=0ltkqHZUq094gKneYmC0bEZCP0X9kHTp9fccKdWFWP0,339
|
6
|
-
appmesh/http_client.py,sha256=j7OIHiRKeFs8o4zTT-tOcCwaYfeACzL86lmz1E2UP_M,57437
|
7
|
-
appmesh/http_server.py,sha256=HOE6DMzmo6FR16aSUQjSDLua_Q8o8807MNhPaAt-abs,4264
|
8
|
-
appmesh/tcp_client.py,sha256=yi0n2NpBBCA-JHYWQPPE_1euE7Oz6JHBe1op7lypTXY,10999
|
9
|
-
appmesh/tcp_messages.py,sha256=w1Kehz_aX4X2CYAUsy9mFVJRhxnLQwwc6L58W4YkQqs,969
|
10
|
-
appmesh/tcp_server.py,sha256=42YW3-hU82AwhE32DF4Gn6GucSg5qNJavP7xGSrpBVc,1353
|
11
|
-
appmesh/tcp_transport.py,sha256=-XDTQbsKL3yCbguHeW2jNqXpYgnCyHsH4rwcaJ46AS8,8645
|
12
|
-
appmesh-1.5.8.dist-info/METADATA,sha256=iSbvWio_TfOfwA-xXU4ZsSWENhl-FDoAUkJrSZRyaz4,11768
|
13
|
-
appmesh-1.5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
14
|
-
appmesh-1.5.8.dist-info/top_level.txt,sha256=-y0MNQOGJxUzLdHZ6E_Rfv5_LNCkV-GTmOBME_b6pg8,8
|
15
|
-
appmesh-1.5.8.dist-info/RECORD,,
|
File without changes
|
File without changes
|