appmesh 1.6.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- appmesh-1.6.12/PKG-INFO +164 -0
- appmesh-1.6.12/appmesh/__init__.py +74 -0
- appmesh-1.6.12/appmesh/app.py +252 -0
- appmesh-1.6.12/appmesh/app_output.py +28 -0
- appmesh-1.6.12/appmesh/app_run.py +54 -0
- appmesh-1.6.12/appmesh/appmesh_client.py +10 -0
- appmesh-1.6.12/appmesh/client_http.py +1513 -0
- appmesh-1.6.12/appmesh/client_http_oauth.py +138 -0
- appmesh-1.6.12/appmesh/client_tcp.py +246 -0
- appmesh-1.6.12/appmesh/server_http.py +121 -0
- appmesh-1.6.12/appmesh/server_tcp.py +39 -0
- appmesh-1.6.12/appmesh/tcp_messages.py +69 -0
- appmesh-1.6.12/appmesh/tcp_transport.py +228 -0
- appmesh-1.6.12/appmesh.egg-info/PKG-INFO +164 -0
- appmesh-1.6.12/appmesh.egg-info/SOURCES.txt +21 -0
- appmesh-1.6.12/appmesh.egg-info/dependency_links.txt +1 -0
- appmesh-1.6.12/appmesh.egg-info/requires.txt +5 -0
- appmesh-1.6.12/appmesh.egg-info/top_level.txt +1 -0
- appmesh-1.6.12/pyproject.toml +6 -0
- appmesh-1.6.12/setup.cfg +4 -0
- appmesh-1.6.12/setup.py +46 -0
- appmesh-1.6.12/test/test_appmesh_client.py +346 -0
- appmesh-1.6.12/test/test_oauth2.py +152 -0
appmesh-1.6.12/PKG-INFO
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: appmesh
|
3
|
+
Version: 1.6.12
|
4
|
+
Summary: Client SDK for App Mesh
|
5
|
+
Home-page: https://github.com/laoshanxi/app-mesh
|
6
|
+
Author: laoshanxi
|
7
|
+
Author-email: 178029200@qq.com
|
8
|
+
License: MIT
|
9
|
+
Keywords: appmesh AppMesh app-mesh
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Programming Language :: Python :: 3.6
|
12
|
+
Classifier: Operating System :: OS Independent
|
13
|
+
Requires-Python: >=3
|
14
|
+
Description-Content-Type: text/markdown
|
15
|
+
Requires-Dist: requests
|
16
|
+
Requires-Dist: msgpack
|
17
|
+
Requires-Dist: requests_toolbelt
|
18
|
+
Requires-Dist: aniso8601
|
19
|
+
Requires-Dist: PyJWT
|
20
|
+
Dynamic: author
|
21
|
+
Dynamic: author-email
|
22
|
+
Dynamic: classifier
|
23
|
+
Dynamic: description
|
24
|
+
Dynamic: description-content-type
|
25
|
+
Dynamic: home-page
|
26
|
+
Dynamic: keywords
|
27
|
+
Dynamic: license
|
28
|
+
Dynamic: requires-dist
|
29
|
+
Dynamic: requires-python
|
30
|
+
Dynamic: summary
|
31
|
+
|
32
|
+
īģŋ[![language.badge]][language.url] [![standard.badge]][standard.url] [![unittest.badge]][unittest.url] [![docker.badge]][docker.url] [![cockpit.badge]][cockpit.url]
|
33
|
+
[](https://app-mesh.readthedocs.io/en/latest/?badge=latest) [](https://gitter.im/app-mesh/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
34
|
+
<a href="https://scan.coverity.com/projects/laoshanxi-app-mesh">
|
35
|
+
<img alt="Coverity Scan Build Status"
|
36
|
+
src="https://img.shields.io/coverity/scan/21528.svg"/>
|
37
|
+
</a>
|
38
|
+
[](https://api.securityscorecards.dev/projects/github.com/laoshanxi/app-mesh)
|
39
|
+
[![release.badge]][release.url] [![pypi.badge]][pypi.url] [![npm.badge]][npm.url]
|
40
|
+
|
41
|
+
# App Mesh: Advanced Application Management Platform
|
42
|
+
|
43
|
+
<div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/whatis.gif" align=center /></div>
|
44
|
+
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.
|
45
|
+
|
46
|
+
<div align=center><img src="https://github.com/laoshanxi/picture/raw/master/appmesh/diagram.png" align=center /></div>
|
47
|
+
|
48
|
+
## Features
|
49
|
+
|
50
|
+
Feature | Description
|
51
|
+
---|---
|
52
|
+
Application management | đ§Š <b>Application Management (CURD) with Full Remote Control</b> â including cgroup, OS user, environment variables, Docker, stdin, and stdout â along with comprehensive monitoring (start counts, exit codes, error messages, health checks). <br> đ§Š <b>Fine-Grained Application Behavior Control & Scheduling</b> â supports long- and short-running tasks, periodic jobs, cron schedules, custom timings, and robust error handling. <br> đ§Š <b>Multi-Tenancy</b> â built-in user ownership model and access controls. <br> đ§Š <b>Unified Access Interface</b> â interact via [CLI](https://app-mesh.readthedocs.io/en/latest/CLI.html), [REST](https://app-mesh.readthedocs.io/en/latest/Development.html#rest-apis), [SDK](https://github.com/laoshanxi/app-mesh/tree/main/src/sdk) or [WebGUI](https://github.com/laoshanxi/app-mesh-ui).<br>
|
53
|
+
Security | đ Authentication: [LDAP](https://app-mesh.readthedocs.io/en/latest/LDAP.html), [OAuth](src/sdk/python/test/test_oauth2.py), [2FA](https://app-mesh.readthedocs.io/en/latest/MFA.html), YAML-based storage (local file or Consul for clustering) <br> đ Authorization: [JWT](https://app-mesh.readthedocs.io/en/latest/JWT.html), [RBAC](https://app-mesh.readthedocs.io/en/latest/USER_ROLE.html), multi-tenant isolation <br> đ Protection: SSL/TLS for TCP/HTTP, CSRF tokens, HMAC with PSK for non-token verification
|
54
|
+
Cloud native | Schedule cloud-level applications to run on multiple hosts with resource size requests. <br> đŠī¸ [Prometheus Exporter (build-in)](https://app-mesh.readthedocs.io/en/latest/PROMETHEUS.html) <br> đŠī¸ [Grafana SimpleJson datasource](https://app-mesh.readthedocs.io/en/latest/GrafanaDataSource.html) <br> đŠī¸ [Grafana Loki](https://app-mesh.readthedocs.io/en/latest/Loki.html) <br>đŠī¸ [Dockerfile](https://github.com/laoshanxi/app-mesh/blob/main/Dockerfile)
|
55
|
+
Micro service application | đ§ą [Consul micro-service cluster management](https://app-mesh.readthedocs.io/en/latest/CONSUL.html)
|
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)
|
57
|
+
Platform support | X86_64 <br> ARM32 <br> ARM64
|
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)
|
59
|
+
|
60
|
+
## Getting started
|
61
|
+
|
62
|
+
Refer to the [Installation doc](https://app-mesh.readthedocs.io/en/latest/Install.html) to learn how to install App Mesh via Docker Compose or natively and set up an App Mesh cluster.
|
63
|
+
|
64
|
+
## Documentation
|
65
|
+
|
66
|
+
- [Read the Docs](https://app-mesh.readthedocs.io/)
|
67
|
+
- [REST API](https://app-mesh.readthedocs.io/en/latest/Development.html#rest-apis)
|
68
|
+
- [Command lines](https://app-mesh.readthedocs.io/en/latest/CLI.html)
|
69
|
+
- [Security](https://app-mesh.readthedocs.io/en/latest/Security.html)
|
70
|
+
|
71
|
+
## Comparison
|
72
|
+
|
73
|
+
### Standalone mode
|
74
|
+
|
75
|
+
| Feature | App Mesh | [Supervisor](http://supervisord.org/) | [crontab](https://crontab.guru/) |
|
76
|
+
| ------------------------ | -------- | ------------------------------------- | -------------------------------- |
|
77
|
+
| Accuracy | Seconds | Seconds | Minutes |
|
78
|
+
| Language | C++11 | Python | C |
|
79
|
+
| Web GUI | â | â |
|
80
|
+
| Command lines | â | â | â |
|
81
|
+
| SDK | â | |
|
82
|
+
| Cron schedule expression | â | | â |
|
83
|
+
| Manage daemon process | | | â |
|
84
|
+
| Manage docker app | â | |
|
85
|
+
| Start check (avoid leak) | â | â |
|
86
|
+
| Session login | | |
|
87
|
+
| Manage stdout/stderr | â | â |
|
88
|
+
| Health check | â | |
|
89
|
+
| Rich control options | â | |
|
90
|
+
| Authentication | â | â |
|
91
|
+
| Multi-tenant | â | | â |
|
92
|
+
|
93
|
+
### Cluster mode
|
94
|
+
|
95
|
+
| Feature | App Mesh | Kubernetes |
|
96
|
+
| ----------------- | -------- | ---------- |
|
97
|
+
| Easy deploy | â |
|
98
|
+
| More features | | â |
|
99
|
+
| Non-container app | â |
|
100
|
+
| Service expose | â | â |
|
101
|
+
| Scheduler | â | â |
|
102
|
+
| Definition file | YAML | YAML |
|
103
|
+
| GUI | â | â |
|
104
|
+
| Virtual Network | | â |
|
105
|
+
| Monitor tools | â | â |
|
106
|
+
| [Remote task](https://app-mesh.readthedocs.io/en/latest/RemoteTask.html) | â | |
|
107
|
+
|
108
|
+
---
|
109
|
+
|
110
|
+
### Mind diagram
|
111
|
+
|
112
|
+

|
113
|
+
|
114
|
+
---
|
115
|
+
|
116
|
+
## Success
|
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)
|
119
|
+
- [Customize application start behavior](https://app-mesh.readthedocs.io/en/latest/success/customize_app_startup_behavior.html)
|
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
|
+
- [Promote native application to microservice application](https://app-mesh.readthedocs.io/en/latest/success/promote_native_app_to_microservice_app.html)
|
122
|
+
- [Secure REST file server](https://app-mesh.readthedocs.io/en/latest/success/secure_REST_file_server.html)
|
123
|
+
- [Standalone JWT server](https://app-mesh.readthedocs.io/en/latest/success/standalone_JWT_server.html)
|
124
|
+
- [Kubernetes run none-container applications](https://app-mesh.readthedocs.io/en/latest/success/kubernetes_run_native_application.html)
|
125
|
+
- [Remote execute](https://app-mesh.readthedocs.io/en/latest/success/remote_run_cli_and_python.html)
|
126
|
+
- [Python parallel run](https://app-mesh.readthedocs.io/en/latest/success/python_parallel_run.html)
|
127
|
+
- [Secure consul cluster](https://app-mesh.readthedocs.io/en/latest/success/secure_consul_cluster.html)
|
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)
|
130
|
+
|
131
|
+
---
|
132
|
+
|
133
|
+
## Library dependency
|
134
|
+
|
135
|
+
- [MessagePack](https://msgpack.org/)
|
136
|
+
- [boostorg/boost](https://github.com/boostorg/boost)
|
137
|
+
- [ACE_TAO/ACE](https://github.com/DOCGroup/ACE_TAO)
|
138
|
+
- [Thalhammer/jwt-cpp](https://github.com/Thalhammer/jwt-cpp)
|
139
|
+
- [nlohmann/json](https://json.nlohmann.me)
|
140
|
+
- [yaml-cpp](https://github.com/jbeder/yaml-cpp)
|
141
|
+
- [nfpm](https://github.com/goreleaser/nfpm)
|
142
|
+
- [jupp0r/prometheus-cpp](https://github.com/jupp0r/prometheus-cpp)
|
143
|
+
- [zemasoft/wildcards](https://github.com/zemasoft/wildcards)
|
144
|
+
- [mariusbancila/croncpp](https://github.com/mariusbancila/croncpp)
|
145
|
+
- [log4cpp](http://log4cpp.sourceforge.net)
|
146
|
+
- [Crypto++](https://www.cryptopp.com)
|
147
|
+
- [ldap-cpp](https://github.com/AndreyBarmaley/ldap-cpp)
|
148
|
+
|
149
|
+
[language.url]: https://isocpp.org/
|
150
|
+
[language.badge]: https://img.shields.io/badge/language-C++-blue.svg
|
151
|
+
[standard.url]: https://en.wikipedia.org/wiki/C%2B%2B#Standardization
|
152
|
+
[standard.badge]: https://img.shields.io/badge/C%2B%2B-11%2F14%2F17-blue.svg
|
153
|
+
[release.url]: https://github.com/laoshanxi/app-mesh/releases
|
154
|
+
[release.badge]: https://img.shields.io/github/v/release/laoshanxi/app-mesh?label=Github%20package
|
155
|
+
[docker.url]: https://hub.docker.com/repository/docker/laoshanxi/appmesh
|
156
|
+
[docker.badge]: https://img.shields.io/docker/pulls/laoshanxi/appmesh.svg
|
157
|
+
[cockpit.url]: https://github.com/laoshanxi/app-mesh-ui
|
158
|
+
[cockpit.badge]: https://img.shields.io/badge/Cockpit-app--mesh--ui-blue?logo=appveyor
|
159
|
+
[unittest.url]: https://github.com/catchorg/Catch2
|
160
|
+
[unittest.badge]: https://img.shields.io/badge/UnitTest-Catch2-blue?logo=appveyor
|
161
|
+
[pypi.badge]: https://img.shields.io/pypi/v/appmesh?label=PyPI%3Aappmesh
|
162
|
+
[pypi.url]: https://pypi.org/project/appmesh/
|
163
|
+
[npm.badge]: https://img.shields.io/npm/v/appmesh?label=npm%3Aappmesh
|
164
|
+
[npm.url]: https://www.npmjs.com/package/appmesh
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# __init__.py
|
2
|
+
"""
|
3
|
+
App Mesh SDK package initializer with lazy loading support.
|
4
|
+
|
5
|
+
Example:
|
6
|
+
from appmesh import AppMeshClient
|
7
|
+
client = AppMeshClient()
|
8
|
+
"""
|
9
|
+
|
10
|
+
import sys
|
11
|
+
from types import ModuleType
|
12
|
+
from typing import TYPE_CHECKING
|
13
|
+
from importlib import import_module
|
14
|
+
|
15
|
+
__all__ = [
|
16
|
+
"App",
|
17
|
+
"AppMeshClient",
|
18
|
+
"AppMeshClientTCP",
|
19
|
+
"AppMeshClientOAuth",
|
20
|
+
"AppMeshServer",
|
21
|
+
"AppMeshServerTCP",
|
22
|
+
]
|
23
|
+
|
24
|
+
# Lazy import configuration
|
25
|
+
_LAZY_IMPORTS = {
|
26
|
+
"App": ("app", "App"), # from .app import App
|
27
|
+
"AppMeshClient": ("client_http", "AppMeshClient"), # from .client_http import AppMeshClient
|
28
|
+
"AppMeshClientTCP": ("client_tcp", "AppMeshClientTCP"), # from .client_tcp import AppMeshClientTCP
|
29
|
+
"AppMeshClientOAuth": ("client_http_oauth", "AppMeshClientOAuth"), # from .client_http_oauth import AppMeshClientOAuth
|
30
|
+
"AppMeshServer": ("server_http", "AppMeshServer"), # from .server_http import AppMeshServer
|
31
|
+
"AppMeshServerTCP": ("server_tcp", "AppMeshServerTCP"), # from .server_tcp import AppMeshServerTCP
|
32
|
+
}
|
33
|
+
|
34
|
+
if TYPE_CHECKING:
|
35
|
+
# Type checking imports (not executed at runtime)
|
36
|
+
from .app import App # noqa: F401
|
37
|
+
from .client_http import AppMeshClient # noqa: F401
|
38
|
+
from .client_tcp import AppMeshClientTCP # noqa: F401
|
39
|
+
from .client_http_oauth import AppMeshClientOAuth # noqa: F401
|
40
|
+
from .server_http import AppMeshServer # noqa: F401
|
41
|
+
from .server_tcp import AppMeshServerTCP # noqa: F401
|
42
|
+
|
43
|
+
|
44
|
+
def _lazy_import(name: str):
|
45
|
+
"""
|
46
|
+
Internal helper for lazy import resolution using PEP 562.
|
47
|
+
Only imports modules when accessed, improving startup time.
|
48
|
+
"""
|
49
|
+
if name in _LAZY_IMPORTS:
|
50
|
+
module_name, attr_name = _LAZY_IMPORTS[name]
|
51
|
+
module = import_module(f".{module_name}", __name__)
|
52
|
+
globals()[name] = getattr(module, attr_name)
|
53
|
+
return globals()[name]
|
54
|
+
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
55
|
+
|
56
|
+
|
57
|
+
def __dir__():
|
58
|
+
"""Provide tab-completion support for lazy-loaded attributes."""
|
59
|
+
return sorted(__all__ + list(globals().keys()))
|
60
|
+
|
61
|
+
|
62
|
+
if sys.version_info >= (3, 7):
|
63
|
+
__getattr__ = _lazy_import
|
64
|
+
else:
|
65
|
+
# Python 3.6 compatibility via module replacement
|
66
|
+
class _LazyModule(ModuleType):
|
67
|
+
def __getattr__(self, name):
|
68
|
+
return _lazy_import(name)
|
69
|
+
|
70
|
+
def __dir__(self):
|
71
|
+
return sorted(__all__ + list(globals().keys()))
|
72
|
+
|
73
|
+
sys.modules[__name__] = _LazyModule(__name__)
|
74
|
+
sys.modules[__name__].__dict__.update(globals())
|
@@ -0,0 +1,252 @@
|
|
1
|
+
# app.py
|
2
|
+
"""Application definition"""
|
3
|
+
|
4
|
+
import json
|
5
|
+
import copy
|
6
|
+
from datetime import datetime
|
7
|
+
from typing import Optional, Any, Dict
|
8
|
+
from enum import Enum, unique
|
9
|
+
|
10
|
+
|
11
|
+
def _get_str(data: Optional[dict], key: str) -> Optional[str]:
|
12
|
+
"""Retrieve a string value from a dictionary by key, if it exists and is a valid string."""
|
13
|
+
if not data or key not in data:
|
14
|
+
return None
|
15
|
+
value = data[key]
|
16
|
+
return value if value and isinstance(value, str) else None
|
17
|
+
|
18
|
+
|
19
|
+
def _get_int(data: Optional[dict], key: str) -> Optional[int]:
|
20
|
+
"""Retrieve an integer value from a dictionary by key, if it exists and is a valid integer."""
|
21
|
+
if not data or key not in data or data[key] is None:
|
22
|
+
return None
|
23
|
+
|
24
|
+
value = data[key]
|
25
|
+
if isinstance(value, int):
|
26
|
+
return value
|
27
|
+
if isinstance(value, str) and value.isdigit():
|
28
|
+
return int(value)
|
29
|
+
return None
|
30
|
+
|
31
|
+
|
32
|
+
def _get_bool(data: Optional[dict], key: str) -> Optional[bool]:
|
33
|
+
"""Retrieve a boolean value from a dictionary by key, if it exists and is boolean-like."""
|
34
|
+
if not data or key not in data or data[key] is None:
|
35
|
+
return None
|
36
|
+
return bool(data[key])
|
37
|
+
|
38
|
+
|
39
|
+
def _get_item(data: Optional[dict], key: str) -> Optional[Any]:
|
40
|
+
"""Retrieve a deep copy of a value from a dictionary by key, if it exists."""
|
41
|
+
if not data or key not in data or data[key] is None:
|
42
|
+
return None
|
43
|
+
return copy.deepcopy(data[key])
|
44
|
+
|
45
|
+
|
46
|
+
class App:
|
47
|
+
"""
|
48
|
+
An application in App Mesh, include all the process attributes,
|
49
|
+
resource limitations, behaviors, and permissions.
|
50
|
+
"""
|
51
|
+
|
52
|
+
@unique
|
53
|
+
class Permission(Enum):
|
54
|
+
"""Application permission levels."""
|
55
|
+
|
56
|
+
DENY = "1"
|
57
|
+
READ = "2"
|
58
|
+
WRITE = "3"
|
59
|
+
|
60
|
+
class Behavior:
|
61
|
+
"""
|
62
|
+
Application error handling behavior, including exit and control behaviors.
|
63
|
+
"""
|
64
|
+
|
65
|
+
@unique
|
66
|
+
class Action(Enum):
|
67
|
+
"""Actions for application exit behaviors."""
|
68
|
+
|
69
|
+
RESTART = "restart"
|
70
|
+
STANDBY = "standby"
|
71
|
+
KEEPALIVE = "keepalive"
|
72
|
+
REMOVE = "remove"
|
73
|
+
|
74
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
75
|
+
if isinstance(data, (str, bytes, bytearray)):
|
76
|
+
data = json.loads(data)
|
77
|
+
|
78
|
+
self.exit = _get_str(data, "exit")
|
79
|
+
"""Default exit behavior, options: 'restart', 'standby', 'keepalive', 'remove'."""
|
80
|
+
|
81
|
+
self.control = _get_item(data, "control") or {}
|
82
|
+
"""Exit code specific behavior (e.g, --control 0:restart --control 1:standby), higher priority than default exit behavior"""
|
83
|
+
|
84
|
+
def set_exit_behavior(self, action: "App.Behavior.Action") -> None:
|
85
|
+
"""Set default behavior for application exit."""
|
86
|
+
self.exit = action.value
|
87
|
+
|
88
|
+
def set_control_behavior(self, control_code: int, action: "App.Behavior.Action") -> None:
|
89
|
+
"""Define behavior for specific exit codes."""
|
90
|
+
self.control[str(control_code)] = action.value
|
91
|
+
|
92
|
+
class DailyLimitation:
|
93
|
+
"""
|
94
|
+
Application availability within a daily time range.
|
95
|
+
"""
|
96
|
+
|
97
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
98
|
+
if isinstance(data, (str, bytes, bytearray)):
|
99
|
+
data = json.loads(data)
|
100
|
+
|
101
|
+
self.daily_start = _get_int(data, "daily_start")
|
102
|
+
"""Start time for application availability (e.g., 09:00:00+08)."""
|
103
|
+
|
104
|
+
self.daily_end = _get_int(data, "daily_end")
|
105
|
+
"""End time for application availability (e.g., 09:00:00+08)."""
|
106
|
+
|
107
|
+
def set_daily_range(self, start: datetime, end: datetime) -> None:
|
108
|
+
"""Set the valid daily start and end times."""
|
109
|
+
self.daily_start = int(start.timestamp())
|
110
|
+
self.daily_end = int(end.timestamp())
|
111
|
+
|
112
|
+
class ResourceLimitation:
|
113
|
+
"""
|
114
|
+
Application resource limits, such as CPU and memory usage.
|
115
|
+
"""
|
116
|
+
|
117
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
118
|
+
if isinstance(data, (str, bytes, bytearray)):
|
119
|
+
data = json.loads(data)
|
120
|
+
|
121
|
+
self.cpu_shares = _get_int(data, "cpu_shares")
|
122
|
+
"""CPU shares, relative weight of CPU usage."""
|
123
|
+
|
124
|
+
self.memory_mb = _get_int(data, "memory_mb")
|
125
|
+
"""Physical memory limit in MB."""
|
126
|
+
|
127
|
+
self.memory_virt_mb = _get_int(data, "memory_virt_mb")
|
128
|
+
"""Virtual memory limit in MB."""
|
129
|
+
|
130
|
+
def __init__(self, data: Optional[dict] = None) -> None:
|
131
|
+
"""Initialize an App instance with optional configuration data."""
|
132
|
+
if isinstance(data, (str, bytes, bytearray)):
|
133
|
+
data = json.loads(data)
|
134
|
+
|
135
|
+
# Application configuration
|
136
|
+
self.name = _get_str(data, "name")
|
137
|
+
"""application name (unique)"""
|
138
|
+
self.command = _get_str(data, "command")
|
139
|
+
"""full command line with arguments"""
|
140
|
+
self.shell = _get_bool(data, "shell")
|
141
|
+
"""use shell mode, cmd can be more shell commands with string format"""
|
142
|
+
self.session_login = _get_bool(data, "session_login")
|
143
|
+
"""app run in session login mode"""
|
144
|
+
self.description = _get_str(data, "description")
|
145
|
+
"""application description string"""
|
146
|
+
self.metadata = _get_item(data, "metadata")
|
147
|
+
"""metadata string/JSON (input for application, pass to process stdin)"""
|
148
|
+
self.working_dir = _get_str(data, "working_dir")
|
149
|
+
"""working directory"""
|
150
|
+
self.status = _get_int(data, "status")
|
151
|
+
"""initial application status (true is enable, false is disabled)"""
|
152
|
+
self.docker_image = _get_str(data, "docker_image")
|
153
|
+
"""docker image which used to run command line (for docker container application)"""
|
154
|
+
self.stdout_cache_num = _get_int(data, "stdout_cache_num")
|
155
|
+
"""stdout file cache number"""
|
156
|
+
self.start_time = _get_int(data, "start_time")
|
157
|
+
"""start date time for app (ISO8601 time format, e.g., '2020-10-11T09:22:05')"""
|
158
|
+
self.end_time = _get_int(data, "end_time")
|
159
|
+
"""end date time for app (ISO8601 time format, e.g., '2020-10-11T10:22:05')"""
|
160
|
+
self.interval = _get_int(data, "interval")
|
161
|
+
"""start interval seconds for short running app, support ISO 8601 durations and cron expression (e.g., 'P1Y2M3DT4H5M6S' 'P5W' '* */5 * * * *')"""
|
162
|
+
self.cron = _get_bool(data, "cron")
|
163
|
+
"""indicate interval parameter use cron expression or not"""
|
164
|
+
self.daily_limitation = App.DailyLimitation(_get_item(data, "daily_limitation"))
|
165
|
+
self.retention = _get_str(data, "retention")
|
166
|
+
"""extra timeout seconds for stopping current process, support ISO 8601 durations (e.g., 'P1Y2M3DT4H5M6S' 'P5W')."""
|
167
|
+
self.health_check_cmd = _get_str(data, "health_check_cmd")
|
168
|
+
"""health check script command (e.g., sh -x 'curl host:port/health', return 0 is health)"""
|
169
|
+
self.permission = _get_int(data, "permission")
|
170
|
+
"""application user permission, value is 2 bit integer: [group & other], each bit can be deny:1, read:2, write: 3."""
|
171
|
+
self.behavior = App.Behavior(_get_item(data, "behavior"))
|
172
|
+
|
173
|
+
self.env = data.get("env", {}) if data else {}
|
174
|
+
"""environment variables (e.g., -e env1=value1 -e env2=value2, APP_DOCKER_OPTS is used to input docker run parameters)"""
|
175
|
+
self.sec_env = data.get("sec_env", {}) if data else {}
|
176
|
+
"""security environment variables, encrypt in server side with application owner's cipher"""
|
177
|
+
self.pid = _get_int(data, "pid")
|
178
|
+
"""process id used to attach to the running process"""
|
179
|
+
self.resource_limit = App.ResourceLimitation(_get_item(data, "resource_limit"))
|
180
|
+
|
181
|
+
# Read-only attributes
|
182
|
+
self.owner = _get_str(data, "owner")
|
183
|
+
"""owner name"""
|
184
|
+
self.user = _get_str(data, "pid_user")
|
185
|
+
"""process user name"""
|
186
|
+
self.pstree = _get_str(data, "pstree")
|
187
|
+
"""process tree"""
|
188
|
+
self.container_id = _get_str(data, "container_id")
|
189
|
+
"""container id"""
|
190
|
+
self.memory = _get_int(data, "memory")
|
191
|
+
"""memory usage"""
|
192
|
+
self.cpu = _get_int(data, "cpu")
|
193
|
+
"""cpu usage"""
|
194
|
+
self.fd = _get_int(data, "fd")
|
195
|
+
"""file descriptor usage"""
|
196
|
+
self.last_start_time = _get_int(data, "last_start_time")
|
197
|
+
"""last start time"""
|
198
|
+
self.last_exit_time = _get_int(data, "last_exit_time")
|
199
|
+
"""last exit time"""
|
200
|
+
self.health = _get_int(data, "health")
|
201
|
+
"""health status"""
|
202
|
+
self.version = _get_int(data, "version")
|
203
|
+
"""version number"""
|
204
|
+
self.return_code = _get_int(data, "return_code")
|
205
|
+
"""last exit code"""
|
206
|
+
self.task_id = _get_int(data, "task_id")
|
207
|
+
"""current task id"""
|
208
|
+
self.task_status = _get_str(data, "task_status")
|
209
|
+
"""task status"""
|
210
|
+
|
211
|
+
def set_valid_time(self, start: Optional[datetime], end: Optional[datetime]) -> None:
|
212
|
+
"""Define the valid time window for the application."""
|
213
|
+
self.start_time = int(start.timestamp()) if start else None
|
214
|
+
self.end_time = int(end.timestamp()) if end else None
|
215
|
+
|
216
|
+
def set_env(self, key: str, value: str, secure: bool = False) -> None:
|
217
|
+
"""Set an environment variable, marking it secure if specified."""
|
218
|
+
target = self.sec_env if secure else self.env
|
219
|
+
target[key] = value
|
220
|
+
|
221
|
+
def set_permission(self, group_user: Permission, others_user: Permission) -> None:
|
222
|
+
"""Define application permissions based on user roles."""
|
223
|
+
self.permission = int(group_user.value + others_user.value)
|
224
|
+
|
225
|
+
def __str__(self) -> str:
|
226
|
+
"""Return a JSON string representation of the application."""
|
227
|
+
return json.dumps(self.json())
|
228
|
+
|
229
|
+
def json(self) -> Dict[str, Any]:
|
230
|
+
"""Convert the application data into a JSON-compatible dictionary, removing empty items."""
|
231
|
+
output = copy.deepcopy(self.__dict__)
|
232
|
+
output["behavior"] = self.behavior.__dict__
|
233
|
+
output["daily_limitation"] = self.daily_limitation.__dict__
|
234
|
+
output["resource_limit"] = self.resource_limit.__dict__
|
235
|
+
|
236
|
+
self._clean_empty(output)
|
237
|
+
return output
|
238
|
+
|
239
|
+
@staticmethod
|
240
|
+
def _clean_empty(data: dict) -> None:
|
241
|
+
"""Recursively remove None, empty string, and empty dict values from nested dictionaries (except 'metadata')."""
|
242
|
+
keys_to_delete = []
|
243
|
+
for key, value in data.items():
|
244
|
+
if isinstance(value, dict) and key != "metadata":
|
245
|
+
App._clean_empty(value)
|
246
|
+
if not value:
|
247
|
+
keys_to_delete.append(key)
|
248
|
+
elif value in (None, "", {}):
|
249
|
+
keys_to_delete.append(key)
|
250
|
+
|
251
|
+
for key in keys_to_delete:
|
252
|
+
del data[key]
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# app_output.py
|
2
|
+
"""Application output information."""
|
3
|
+
|
4
|
+
from dataclasses import dataclass
|
5
|
+
from http import HTTPStatus
|
6
|
+
from typing import Optional
|
7
|
+
|
8
|
+
|
9
|
+
@dataclass(frozen=True)
|
10
|
+
class AppOutput:
|
11
|
+
"""
|
12
|
+
Output information returned by the `app_output()` API.
|
13
|
+
|
14
|
+
Includes the application's stdout, current read position,
|
15
|
+
HTTP status code, and process exit code.
|
16
|
+
"""
|
17
|
+
|
18
|
+
status_code: HTTPStatus
|
19
|
+
"""HTTP status code from the `app_output()` API request."""
|
20
|
+
|
21
|
+
output: str
|
22
|
+
"""Captured stdout content of the application."""
|
23
|
+
|
24
|
+
out_position: Optional[int]
|
25
|
+
"""Current read position in stdout stream, or None if not applicable."""
|
26
|
+
|
27
|
+
exit_code: Optional[int]
|
28
|
+
"""Exit code of the application, or None if still running."""
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# app_run.py
|
2
|
+
"""Application run object for remote application execution."""
|
3
|
+
|
4
|
+
from contextlib import contextmanager
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
6
|
+
|
7
|
+
if TYPE_CHECKING:
|
8
|
+
from .client_http import AppMeshClient
|
9
|
+
|
10
|
+
|
11
|
+
class AppRun:
|
12
|
+
"""
|
13
|
+
Application run object for monitoring and retrieving results
|
14
|
+
of a remote application run initiated by `run_async()`.
|
15
|
+
"""
|
16
|
+
|
17
|
+
def __init__(self, client: "AppMeshClient", app_name: str, process_id: str):
|
18
|
+
self.app_name = app_name
|
19
|
+
"""Name of the application associated with this run."""
|
20
|
+
|
21
|
+
self.proc_uid = process_id
|
22
|
+
"""Unique process ID from `run_async()`."""
|
23
|
+
|
24
|
+
self._client = client
|
25
|
+
self._forward_to = client.forward_to
|
26
|
+
|
27
|
+
@contextmanager
|
28
|
+
def forward_to(self):
|
29
|
+
"""
|
30
|
+
Context manager to temporarily override the client's `forward_to` setting.
|
31
|
+
|
32
|
+
Ensures operations during this run use the correct target server,
|
33
|
+
then restores the original setting.
|
34
|
+
"""
|
35
|
+
original_value = self._client.forward_to
|
36
|
+
self._client.forward_to = self._forward_to
|
37
|
+
try:
|
38
|
+
yield
|
39
|
+
finally:
|
40
|
+
self._client.forward_to = original_value
|
41
|
+
|
42
|
+
def wait(self, stdout_print: bool = True, timeout: int = 0) -> Optional[int]:
|
43
|
+
"""
|
44
|
+
Wait for the asynchronous run to complete.
|
45
|
+
|
46
|
+
Args:
|
47
|
+
stdout_print: If True, prints remote stdout to local console.
|
48
|
+
timeout: Maximum time to wait in seconds. 0 means wait indefinitely.
|
49
|
+
|
50
|
+
Returns:
|
51
|
+
Exit code if the process finishes successfully, or None on timeout.
|
52
|
+
"""
|
53
|
+
with self.forward_to():
|
54
|
+
return self._client.wait_for_async_run(self, stdout_print, timeout)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# appmesh_client.py
|
2
|
+
|
3
|
+
# Legacy Compatibility Layer
|
4
|
+
# These imports provide backward compatibility for older code that relies on
|
5
|
+
# AppMeshClient, App, and AppOutput classes. The updated implementation can be found
|
6
|
+
# in client_http.py, where these classes are now primarily maintained.
|
7
|
+
|
8
|
+
from .client_http import AppMeshClient
|
9
|
+
from .app import App
|
10
|
+
from .app_output import AppOutput
|