appmesh 1.3.5__py3-none-any.whl → 1.3.7__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 +18 -1
- appmesh/app.py +226 -0
- appmesh/app_output.py +26 -0
- appmesh/app_run.py +48 -0
- appmesh/appmesh_client.py +167 -700
- appmesh/appmesh_client_tcp.py +362 -0
- {appmesh-1.3.5.dist-info → appmesh-1.3.7.dist-info}/METADATA +1 -1
- appmesh-1.3.7.dist-info/RECORD +10 -0
- appmesh-1.3.5.dist-info/RECORD +0 -6
- {appmesh-1.3.5.dist-info → appmesh-1.3.7.dist-info}/WHEEL +0 -0
- {appmesh-1.3.5.dist-info → appmesh-1.3.7.dist-info}/top_level.txt +0 -0
appmesh/__init__.py
CHANGED
@@ -1 +1,18 @@
|
|
1
|
-
|
1
|
+
# __init__.py
|
2
|
+
"""
|
3
|
+
App Mesh SDK package initializer.
|
4
|
+
|
5
|
+
This module exports the main client classes used to interact with the App Mesh API.
|
6
|
+
|
7
|
+
Example:
|
8
|
+
from appmesh import AppMeshClient, AppMeshClientTCP
|
9
|
+
|
10
|
+
client = AppMeshClient()
|
11
|
+
client_tcp = AppMeshClientTCP()
|
12
|
+
"""
|
13
|
+
|
14
|
+
from .app import App
|
15
|
+
from .appmesh_client import AppMeshClient
|
16
|
+
from .appmesh_client_tcp import AppMeshClientTCP
|
17
|
+
|
18
|
+
__all__ = ["App", "AppMeshClient", "AppMeshClientTCP"]
|
appmesh/app.py
ADDED
@@ -0,0 +1,226 @@
|
|
1
|
+
"""Application definition"""
|
2
|
+
|
3
|
+
import json
|
4
|
+
import copy
|
5
|
+
|
6
|
+
from datetime import datetime
|
7
|
+
from typing import Optional
|
8
|
+
from enum import Enum, unique
|
9
|
+
|
10
|
+
# pylint: disable=line-too-long
|
11
|
+
|
12
|
+
|
13
|
+
class App(object):
|
14
|
+
"""
|
15
|
+
Represents an application in App Mesh, including configuration, resource limitations, behaviors, and permissions.
|
16
|
+
"""
|
17
|
+
|
18
|
+
@staticmethod
|
19
|
+
def _get_str_item(data: dict, key: str) -> Optional[str]:
|
20
|
+
"""Retrieve a string value from a dictionary by key, if it exists and is a valid string."""
|
21
|
+
return data[key] if (data and key in data and data[key] and isinstance(data[key], str)) else None
|
22
|
+
|
23
|
+
@staticmethod
|
24
|
+
def _get_int_item(data: dict, key: str) -> Optional[int]:
|
25
|
+
"""Retrieve an integer value from a dictionary by key, if it exists and is a valid integer."""
|
26
|
+
return int(data[key]) if (data and key in data and data[key] and isinstance(data[key], int)) else None
|
27
|
+
|
28
|
+
@staticmethod
|
29
|
+
def _get_bool_item(data: dict, key: str) -> Optional[bool]:
|
30
|
+
"""Retrieve a boolean value from a dictionary by key, if it exists and is boolean-like."""
|
31
|
+
return bool(data[key]) if (data and key in data and data[key]) else None
|
32
|
+
|
33
|
+
@staticmethod
|
34
|
+
def _get_native_item(data: dict, key: str) -> Optional[object]:
|
35
|
+
"""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
|
37
|
+
|
38
|
+
@unique
|
39
|
+
class Permission(Enum):
|
40
|
+
"""Defines application permission levels."""
|
41
|
+
|
42
|
+
DENY = "1"
|
43
|
+
READ = "2"
|
44
|
+
WRITE = "3"
|
45
|
+
|
46
|
+
class Behavior(object):
|
47
|
+
"""
|
48
|
+
Manages application error handling behavior, including exit and control behaviors.
|
49
|
+
"""
|
50
|
+
|
51
|
+
@unique
|
52
|
+
class Action(Enum):
|
53
|
+
"""Defines actions for application exit behaviors."""
|
54
|
+
|
55
|
+
RESTART = "restart"
|
56
|
+
STANDBY = "standby"
|
57
|
+
KEEPALIVE = "keepalive"
|
58
|
+
REMOVE = "remove"
|
59
|
+
|
60
|
+
def __init__(self, data=None) -> None:
|
61
|
+
if isinstance(data, (str, bytes, bytearray)):
|
62
|
+
data = json.loads(data)
|
63
|
+
|
64
|
+
self.exit = App._get_str_item(data, "exit")
|
65
|
+
"""Default exit behavior, options: 'restart', 'standby', 'keepalive', 'remove'."""
|
66
|
+
|
67
|
+
self.control = App._get_native_item(data, "control") or {}
|
68
|
+
"""Exit code specific behavior (e.g, --control 0:restart --control 1:standby), higher priority than default exit behavior"""
|
69
|
+
|
70
|
+
def set_exit_behavior(self, action: Action) -> None:
|
71
|
+
"""Set default behavior for application exit."""
|
72
|
+
self.exit = action.value
|
73
|
+
|
74
|
+
def set_control_behavior(self, control_code: int, action: Action) -> None:
|
75
|
+
"""Define behavior for specific exit codes."""
|
76
|
+
self.control[str(control_code)] = action.value
|
77
|
+
|
78
|
+
class DailyLimitation(object):
|
79
|
+
"""
|
80
|
+
Defines application availability within a daily time range.
|
81
|
+
"""
|
82
|
+
|
83
|
+
def __init__(self, data=None) -> None:
|
84
|
+
if isinstance(data, (str, bytes, bytearray)):
|
85
|
+
data = json.loads(data)
|
86
|
+
|
87
|
+
self.daily_start = App._get_int_item(data, "daily_start")
|
88
|
+
"""Start time for application availability (e.g., 09:00:00+08)."""
|
89
|
+
|
90
|
+
self.daily_end = App._get_int_item(data, "daily_end")
|
91
|
+
"""End time for application availability (e.g., 20:00:00+08)."""
|
92
|
+
|
93
|
+
def set_daily_range(self, start: datetime, end: datetime) -> None:
|
94
|
+
"""Set the valid daily start and end times."""
|
95
|
+
self.daily_start = int(start.timestamp())
|
96
|
+
self.daily_end = int(end.timestamp())
|
97
|
+
|
98
|
+
class ResourceLimitation(object):
|
99
|
+
"""
|
100
|
+
Defines application resource limits, such as CPU and memory usage.
|
101
|
+
"""
|
102
|
+
|
103
|
+
def __init__(self, data=None) -> None:
|
104
|
+
if isinstance(data, (str, bytes, bytearray)):
|
105
|
+
data = json.loads(data)
|
106
|
+
|
107
|
+
self.cpu_shares = App._get_int_item(data, "cpu_shares")
|
108
|
+
"""CPU shares, relative weight of CPU usage."""
|
109
|
+
|
110
|
+
self.memory_mb = App._get_int_item(data, "memory_mb")
|
111
|
+
"""Physical memory limit in MB."""
|
112
|
+
|
113
|
+
self.memory_virt_mb = App._get_int_item(data, "memory_virt_mb")
|
114
|
+
"""Virtual memory limit in MB."""
|
115
|
+
|
116
|
+
def __init__(self, data=None) -> None:
|
117
|
+
"""Initialize an App instance with optional configuration data."""
|
118
|
+
if isinstance(data, (str, bytes, bytearray)):
|
119
|
+
data = json.loads(data)
|
120
|
+
|
121
|
+
self.name = App._get_str_item(data, "name")
|
122
|
+
"""application name (unique)"""
|
123
|
+
self.command = App._get_str_item(data, "command")
|
124
|
+
"""full command line with arguments"""
|
125
|
+
self.shell = App._get_bool_item(data, "shell")
|
126
|
+
"""use shell mode, cmd can be more shell commands with string format"""
|
127
|
+
self.session_login = App._get_bool_item(data, "session_login")
|
128
|
+
"""app run in session login mode"""
|
129
|
+
self.description = App._get_str_item(data, "description")
|
130
|
+
"""application description string"""
|
131
|
+
self.metadata = App._get_native_item(data, "metadata")
|
132
|
+
"""metadata string/JSON (input for application, pass to process stdin)"""
|
133
|
+
self.working_dir = App._get_str_item(data, "working_dir")
|
134
|
+
"""working directory"""
|
135
|
+
self.status = App._get_int_item(data, "status")
|
136
|
+
"""initial application status (true is enable, false is disabled)"""
|
137
|
+
self.docker_image = App._get_str_item(data, "docker_image")
|
138
|
+
"""docker image which used to run command line (for docker container application)"""
|
139
|
+
self.stdout_cache_num = App._get_int_item(data, "stdout_cache_num")
|
140
|
+
"""stdout file cache number"""
|
141
|
+
self.start_time = App._get_int_item(data, "start_time")
|
142
|
+
"""start date time for app (ISO8601 time format, e.g., '2020-10-11T09:22:05')"""
|
143
|
+
self.end_time = App._get_int_item(data, "end_time")
|
144
|
+
"""end date time for app (ISO8601 time format, e.g., '2020-10-11T10:22:05')"""
|
145
|
+
self.interval = App._get_int_item(data, "interval")
|
146
|
+
"""start interval seconds for short running app, support ISO 8601 durations and cron expression (e.g., 'P1Y2M3DT4H5M6S' 'P5W' '* */5 * * * *')"""
|
147
|
+
self.cron = App._get_bool_item(data, "cron")
|
148
|
+
"""indicate interval parameter use cron expression or not"""
|
149
|
+
self.daily_limitation = App.DailyLimitation(App._get_native_item(data, "daily_limitation"))
|
150
|
+
self.retention = App._get_str_item(data, "retention")
|
151
|
+
"""extra timeout seconds for stopping current process, support ISO 8601 durations (e.g., 'P1Y2M3DT4H5M6S' 'P5W')."""
|
152
|
+
self.health_check_cmd = App._get_str_item(data, "health_check_cmd")
|
153
|
+
"""health check script command (e.g., sh -x 'curl host:port/health', return 0 is health)"""
|
154
|
+
self.permission = App._get_int_item(data, "permission")
|
155
|
+
"""application user permission, value is 2 bit integer: [group & other], each bit can be deny:1, read:2, write: 3."""
|
156
|
+
self.behavior = App.Behavior(App._get_native_item(data, "behavior"))
|
157
|
+
|
158
|
+
self.env = data.get("env", {}) if data else {}
|
159
|
+
"""environment variables (e.g., -e env1=value1 -e env2=value2, APP_DOCKER_OPTS is used to input docker run parameters)"""
|
160
|
+
self.sec_env = data.get("sec_env", {}) if data else {}
|
161
|
+
"""security environment variables, encrypt in server side with application owner's cipher"""
|
162
|
+
self.pid = App._get_int_item(data, "pid")
|
163
|
+
"""process id used to attach to the running process"""
|
164
|
+
self.resource_limit = App.ResourceLimitation(App._get_native_item(data, "resource_limit"))
|
165
|
+
|
166
|
+
# Read-only attributes
|
167
|
+
self.owner = App._get_str_item(data, "owner")
|
168
|
+
"""owner name"""
|
169
|
+
self.pstree = App._get_str_item(data, "pstree")
|
170
|
+
"""process tree"""
|
171
|
+
self.container_id = App._get_str_item(data, "container_id")
|
172
|
+
"""container id"""
|
173
|
+
self.memory = App._get_int_item(data, "memory")
|
174
|
+
"""memory usage"""
|
175
|
+
self.cpu = App._get_int_item(data, "cpu")
|
176
|
+
"""cpu usage"""
|
177
|
+
self.fd = App._get_int_item(data, "fd")
|
178
|
+
"""file descriptor usage"""
|
179
|
+
self.last_start_time = App._get_int_item(data, "last_start_time")
|
180
|
+
"""last start time"""
|
181
|
+
self.last_exit_time = App._get_int_item(data, "last_exit_time")
|
182
|
+
"""last exit time"""
|
183
|
+
self.health = App._get_int_item(data, "health")
|
184
|
+
"""health status"""
|
185
|
+
self.version = App._get_int_item(data, "version")
|
186
|
+
"""version number"""
|
187
|
+
self.return_code = App._get_int_item(data, "return_code")
|
188
|
+
"""last exit code"""
|
189
|
+
|
190
|
+
def set_valid_time(self, start: datetime, end: datetime) -> None:
|
191
|
+
"""Define the valid time window for the application."""
|
192
|
+
self.start_time = int(start.timestamp()) if start else None
|
193
|
+
self.end_time = int(end.timestamp()) if end else None
|
194
|
+
|
195
|
+
def set_env(self, key: str, value: str, secure: bool = False) -> None:
|
196
|
+
"""Set an environment variable, marking it secure if specified."""
|
197
|
+
(self.sec_env if secure else self.env)[key] = value
|
198
|
+
|
199
|
+
def set_permission(self, group_user: Permission, others_user: Permission) -> None:
|
200
|
+
"""Define application permissions based on user roles."""
|
201
|
+
self.permission = int(group_user.value + others_user.value)
|
202
|
+
|
203
|
+
def __str__(self) -> str:
|
204
|
+
"""Return a JSON string representation of the application."""
|
205
|
+
return json.dumps(self.json())
|
206
|
+
|
207
|
+
def json(self) -> dict:
|
208
|
+
"""Convert the application data into a JSON-compatible dictionary, removing empty items."""
|
209
|
+
output = copy.deepcopy(self.__dict__)
|
210
|
+
output["behavior"] = self.behavior.__dict__
|
211
|
+
output["daily_limitation"] = self.daily_limitation.__dict__
|
212
|
+
output["resource_limit"] = self.resource_limit.__dict__
|
213
|
+
|
214
|
+
def clean_empty(data: dict) -> None:
|
215
|
+
keys_to_delete = []
|
216
|
+
for key, value in data.items():
|
217
|
+
if isinstance(value, dict) and key != "metadata":
|
218
|
+
clean_empty(value) # Recursive call (without check user metadata)
|
219
|
+
if data[key] in [None, "", {}]:
|
220
|
+
keys_to_delete.append(key) # Mark keys for deletion
|
221
|
+
|
222
|
+
for key in keys_to_delete: # Delete keys after the loop to avoid modifying dict during iteration
|
223
|
+
del data[key]
|
224
|
+
|
225
|
+
clean_empty(output)
|
226
|
+
return output
|
appmesh/app_output.py
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
"""Application output information"""
|
2
|
+
|
3
|
+
from http import HTTPStatus
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
# pylint: disable=line-too-long
|
7
|
+
|
8
|
+
|
9
|
+
class AppOutput(object):
|
10
|
+
"""
|
11
|
+
Represents the output information returned by the `app_output()` API, including the application's
|
12
|
+
stdout content, current read position, status code, and exit code.
|
13
|
+
"""
|
14
|
+
|
15
|
+
def __init__(self, status_code: HTTPStatus, output: str, out_position: Optional[int], exit_code: Optional[int]) -> None:
|
16
|
+
self.status_code = status_code
|
17
|
+
"""HTTP status code from the `app_output()` API request, indicating the result status."""
|
18
|
+
|
19
|
+
self.output = output
|
20
|
+
"""Captured stdout content of the application as returned by the `app_output()` API."""
|
21
|
+
|
22
|
+
self.out_position = out_position
|
23
|
+
"""Current read position in the application's stdout stream, or `None` if not applicable."""
|
24
|
+
|
25
|
+
self.exit_code = exit_code
|
26
|
+
"""Exit code of the application, or `None` if the process is still running or hasn't exited."""
|
appmesh/app_run.py
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
"""Application run object"""
|
2
|
+
|
3
|
+
from contextlib import contextmanager
|
4
|
+
|
5
|
+
# pylint: disable=line-too-long
|
6
|
+
|
7
|
+
|
8
|
+
class AppRun(object):
|
9
|
+
"""
|
10
|
+
Represents an application run object initiated by `run_async()` for monitoring and retrieving
|
11
|
+
the result of a remote application run.
|
12
|
+
"""
|
13
|
+
|
14
|
+
def __init__(self, client, app_name: str, process_id: str):
|
15
|
+
self.app_name = app_name
|
16
|
+
"""Name of the application associated with this run."""
|
17
|
+
|
18
|
+
self.proc_uid = process_id
|
19
|
+
"""Unique process ID from `run_async()`."""
|
20
|
+
|
21
|
+
self._client = client
|
22
|
+
"""Instance of `AppMeshClient` used to manage this application run."""
|
23
|
+
|
24
|
+
self._forwarding_host = client.forwarding_host
|
25
|
+
"""Target server for the application run, used for forwarding."""
|
26
|
+
|
27
|
+
@contextmanager
|
28
|
+
def forwarding_host(self):
|
29
|
+
"""Context manager to override the `forwarding_host` for the duration of the run."""
|
30
|
+
original_value = self._client.forwarding_host
|
31
|
+
self._client.forwarding_host = self._forwarding_host
|
32
|
+
try:
|
33
|
+
yield
|
34
|
+
finally:
|
35
|
+
self._client.forwarding_host = original_value
|
36
|
+
|
37
|
+
def wait(self, stdout_print: bool = True, timeout: int = 0) -> int:
|
38
|
+
"""Wait for the asynchronous run to complete.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
stdout_print (bool, optional): If `True`, prints remote stdout to local. Defaults to `True`.
|
42
|
+
timeout (int, optional): Maximum time to wait in seconds. If `0`, waits until completion. Defaults to `0`.
|
43
|
+
|
44
|
+
Returns:
|
45
|
+
int: Exit code if the process finishes successfully. Returns `None` on timeout or exception.
|
46
|
+
"""
|
47
|
+
with self.forwarding_host():
|
48
|
+
return self._client.run_async_wait(self, stdout_print, timeout)
|