cpbox 1.6.859__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.
- cpbox/__init__.py +1 -0
- cpbox/_assets/docker/Dockerfile.j2 +41 -0
- cpbox/_assets/docker/debian.sources.tuna.j2 +11 -0
- cpbox/_assets/docker/pip.conf.j2 +8 -0
- cpbox/app/appconfig.py +27 -0
- cpbox/app/contextdata.py +76 -0
- cpbox/app/contracts.py +152 -0
- cpbox/app/devops.py +200 -0
- cpbox/app/env.py +34 -0
- cpbox/app/eventlog.py +40 -0
- cpbox/app/multiuser.py +183 -0
- cpbox/app/rootcontext.py +40 -0
- cpbox/app/tools/tibaselog.py +221 -0
- cpbox/app/xapp.py +200 -0
- cpbox/clis/aliyunoss.py +84 -0
- cpbox/clis/mysqlcli.py +99 -0
- cpbox/clis/pipcli.py +40 -0
- cpbox/clis/rediscli.py +60 -0
- cpbox/tool/array.py +21 -0
- cpbox/tool/cache.py +133 -0
- cpbox/tool/cls.py +20 -0
- cpbox/tool/concurrent.py +215 -0
- cpbox/tool/crontab.py +359 -0
- cpbox/tool/datatypes.py +259 -0
- cpbox/tool/dockerutil.py +51 -0
- cpbox/tool/file.py +109 -0
- cpbox/tool/functocli.py +481 -0
- cpbox/tool/http.py +40 -0
- cpbox/tool/id.py +50 -0
- cpbox/tool/local_cache.py +59 -0
- cpbox/tool/logger.py +227 -0
- cpbox/tool/net.py +39 -0
- cpbox/tool/pkgres.py +32 -0
- cpbox/tool/redistool.py +69 -0
- cpbox/tool/serde.py +63 -0
- cpbox/tool/spec.py +44 -0
- cpbox/tool/strings.py +50 -0
- cpbox/tool/strjson.py +6 -0
- cpbox/tool/system.py +96 -0
- cpbox/tool/template.py +43 -0
- cpbox/tool/testutils.py +40 -0
- cpbox/tool/timeutil.py +7 -0
- cpbox/tool/utils.py +115 -0
- cpbox/xenv/compose_manager.py +119 -0
- cpbox/xenv/conda_manager.py +211 -0
- cpbox/xenv/image_manager.py +268 -0
- cpbox/xenv/project_app.py +24 -0
- cpbox/xenv/sync_manager.py +84 -0
- cpbox/xenv/xenv_cli.py +155 -0
- cpbox/xkit/agent_assets_manager.py +351 -0
- cpbox/xkit/artifact_manager.py +314 -0
- cpbox/xkit/base_tools.py +35 -0
- cpbox/xkit/file_select.py +93 -0
- cpbox/xkit/objstore_manager.py +69 -0
- cpbox/xkit/pybuild_manager.py +417 -0
- cpbox/xkit/tools/__init__.py +0 -0
- cpbox/xkit/tools/objstore.py +180 -0
- cpbox/xkit/tools/profiles.py +23 -0
- cpbox/xkit/xkit_cli.py +104 -0
- cpbox/xkit/xkit_config.py +52 -0
- cpbox-1.6.859.dist-info/METADATA +20 -0
- cpbox-1.6.859.dist-info/RECORD +65 -0
- cpbox-1.6.859.dist-info/WHEEL +5 -0
- cpbox-1.6.859.dist-info/entry_points.txt +3 -0
- cpbox-1.6.859.dist-info/top_level.txt +1 -0
cpbox/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
FROM {% if XENV_DOCKER_BASE_IMAGE_PROXY_HOST %}{{ XENV_DOCKER_BASE_IMAGE_PROXY_HOST }}/{% endif %}continuumio/miniconda3:24.11.1-0
|
|
2
|
+
|
|
3
|
+
ARG TZ=Asia/Shanghai
|
|
4
|
+
ENV XENV_CONDA_ENV=xenv
|
|
5
|
+
|
|
6
|
+
WORKDIR /xmnt/app-working-root
|
|
7
|
+
|
|
8
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
9
|
+
ENV TZ=${TZ}
|
|
10
|
+
|
|
11
|
+
COPY output/xenv-storage/runtime/build-assets/docker/debian.sources.tuna /etc/apt/sources.list.d/debian.sources
|
|
12
|
+
|
|
13
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
14
|
+
build-essential \
|
|
15
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
16
|
+
|
|
17
|
+
RUN ln -snf /usr/share/zoneinfo/${TZ} /etc/localtime \
|
|
18
|
+
&& ln -snf /usr/share/zoneinfo/${TZ} /etc/timezone
|
|
19
|
+
|
|
20
|
+
COPY output/xenv-storage/runtime/build-assets/docker/pip.conf /root/.pip/pip.conf
|
|
21
|
+
|
|
22
|
+
COPY environment.yml /tmp/environment.yml
|
|
23
|
+
COPY requirements-prod.txt /tmp/requirements-prod.txt
|
|
24
|
+
COPY requirements-dev.txt /tmp/requirements-dev.txt
|
|
25
|
+
COPY pyproject.toml /tmp/pyproject.toml
|
|
26
|
+
|
|
27
|
+
RUN conda env create -n ${XENV_CONDA_ENV} -f /tmp/environment.yml \
|
|
28
|
+
&& conda clean -afy
|
|
29
|
+
|
|
30
|
+
RUN conda run -n ${XENV_CONDA_ENV} pip install --no-cache-dir -r /tmp/requirements-prod.txt \
|
|
31
|
+
&& conda run -n ${XENV_CONDA_ENV} pip install --no-cache-dir -r /tmp/requirements-dev.txt
|
|
32
|
+
|
|
33
|
+
RUN printf '%s\n' '#!/bin/bash' 'exec conda run -n "$XENV_CONDA_ENV" --no-capture-output "$@"' > /usr/local/bin/xenv-entrypoint \
|
|
34
|
+
&& chmod +x /usr/local/bin/xenv-entrypoint
|
|
35
|
+
|
|
36
|
+
ENV PYTHONPATH=/xmnt/app-working-root/src
|
|
37
|
+
ENV PATH=/opt/conda/envs/xenv/bin:/opt/conda/condabin:${PATH}
|
|
38
|
+
ENV CONDA_DEFAULT_ENV=${XENV_CONDA_ENV}
|
|
39
|
+
|
|
40
|
+
ENTRYPOINT ["/usr/local/bin/xenv-entrypoint"]
|
|
41
|
+
CMD ["python", "--version"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Types: deb
|
|
2
|
+
URIs: https://mirrors.tuna.tsinghua.edu.cn/debian
|
|
3
|
+
Suites: bookworm bookworm-updates
|
|
4
|
+
Components: main
|
|
5
|
+
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
|
6
|
+
|
|
7
|
+
Types: deb
|
|
8
|
+
URIs: https://security.debian.org/debian-security
|
|
9
|
+
Suites: bookworm-security
|
|
10
|
+
Components: main
|
|
11
|
+
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
[global]
|
|
2
|
+
{% if XENV_PIP_INDEX_HOST -%}
|
|
3
|
+
index=https://{{ XENV_PIP_INDEX_HOST }}/repository/pypi-all/pypi
|
|
4
|
+
index-url=https://{{ XENV_PIP_INDEX_HOST }}/repository/pypi-all/simple
|
|
5
|
+
{% else -%}
|
|
6
|
+
index = https://pypi.tuna.tsinghua.edu.cn/pypi
|
|
7
|
+
index-url = https://pypi.tuna.tsinghua.edu.cn/simple
|
|
8
|
+
{% endif %}
|
cpbox/app/appconfig.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from cpbox.tool.utils import Singleton
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ConfigData():
|
|
5
|
+
__metaclass__ = Singleton
|
|
6
|
+
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self._env = None
|
|
9
|
+
self._app_name = None
|
|
10
|
+
self._app_config = {}
|
|
11
|
+
|
|
12
|
+
def get_env(self):
|
|
13
|
+
return self._env
|
|
14
|
+
|
|
15
|
+
def get_app_name(self):
|
|
16
|
+
return self._app_name
|
|
17
|
+
|
|
18
|
+
def init(self, app_name, env = None, app_config = {}):
|
|
19
|
+
self._app_name = app_name
|
|
20
|
+
if env is not None:
|
|
21
|
+
self._env = env
|
|
22
|
+
self._app_config = app_config
|
|
23
|
+
|
|
24
|
+
def get_app_config(self):
|
|
25
|
+
return self._app_config
|
|
26
|
+
|
|
27
|
+
appconfig = ConfigData()
|
cpbox/app/contextdata.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from cpbox.tool import datatypes
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EnvDefaults(datatypes.TypedValueMap):
|
|
5
|
+
|
|
6
|
+
allowed_keys = (
|
|
7
|
+
'APP_ENV',
|
|
8
|
+
'LOG.LEVEL',
|
|
9
|
+
'LOG.DISABLE_STDOUT_LOGGER',
|
|
10
|
+
'LOG.NOISE_LOGGERS',
|
|
11
|
+
'LOG.NOISE_LEVEL',
|
|
12
|
+
'CONDA_DEFAULT_ENV_NAME',
|
|
13
|
+
'XENV_IMAGE_NAME',
|
|
14
|
+
'XENV_IMAGE_PUSH_REGISTRY_HOST',
|
|
15
|
+
'XENV_IMAGE_PULL_REGISTRY_HOST',
|
|
16
|
+
'XENV_DOCKER_BASE_IMAGE_PROXY_HOST',
|
|
17
|
+
'XENV_PIP_INDEX_HOST',
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__({})
|
|
22
|
+
self._used = False
|
|
23
|
+
|
|
24
|
+
def set(self, key, value):
|
|
25
|
+
self[key] = value
|
|
26
|
+
|
|
27
|
+
def __setitem__(self, key, value):
|
|
28
|
+
self._check_writable(key)
|
|
29
|
+
dict.__setitem__(self, key, value)
|
|
30
|
+
|
|
31
|
+
def update(self, *args, **kwargs):
|
|
32
|
+
data = dict(*args, **kwargs)
|
|
33
|
+
for key in data:
|
|
34
|
+
self._check_writable(key)
|
|
35
|
+
for key, value in data.items():
|
|
36
|
+
dict.__setitem__(self, key, value)
|
|
37
|
+
|
|
38
|
+
def setdefault(self, key, default=None):
|
|
39
|
+
self._check_writable(key)
|
|
40
|
+
return dict.setdefault(self, key, default)
|
|
41
|
+
|
|
42
|
+
def pop(self, key, default=None):
|
|
43
|
+
self._check_mutable()
|
|
44
|
+
return dict.pop(self, key, default)
|
|
45
|
+
|
|
46
|
+
def popitem(self):
|
|
47
|
+
self._check_mutable()
|
|
48
|
+
return dict.popitem(self)
|
|
49
|
+
|
|
50
|
+
def clear(self):
|
|
51
|
+
self._check_mutable()
|
|
52
|
+
dict.clear(self)
|
|
53
|
+
|
|
54
|
+
def mark_used(self):
|
|
55
|
+
self._used = True
|
|
56
|
+
|
|
57
|
+
def _check_mutable(self):
|
|
58
|
+
if self._used:
|
|
59
|
+
raise RuntimeError('gcontext.env_defaults has already been used to initialize env_config.')
|
|
60
|
+
|
|
61
|
+
def _check_writable(self, key):
|
|
62
|
+
self._check_mutable()
|
|
63
|
+
if key not in self.allowed_keys:
|
|
64
|
+
raise KeyError(f'env key is not allowed in gcontext.env_defaults: {key}')
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class RuntimeStore:
|
|
68
|
+
|
|
69
|
+
def __init__(self):
|
|
70
|
+
self._data = {}
|
|
71
|
+
|
|
72
|
+
def set(self, key, value):
|
|
73
|
+
self._data[key] = value
|
|
74
|
+
|
|
75
|
+
def get(self, key, default=None):
|
|
76
|
+
return self._data.get(key, default)
|
cpbox/app/contracts.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sys
|
|
4
|
+
import importlib
|
|
5
|
+
|
|
6
|
+
import dotenv
|
|
7
|
+
|
|
8
|
+
from cpbox.tool import pkgres
|
|
9
|
+
from cpbox.tool import datatypes
|
|
10
|
+
from cpbox.tool import serde
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_XAPP_CONFIG_DIR = 'xapp-config'
|
|
14
|
+
_XAPP_STORAGE_DIR = 'xapp-storage'
|
|
15
|
+
_XAPP_ENV_FILE = 'xapp.env'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _detect_start_dir():
|
|
19
|
+
# 启动目录判定规则:
|
|
20
|
+
# 1) 正常脚本方式启动(python xxx.py)时,优先使用入口脚本所在目录,后续向上查找 .env 会更符合项目预期。
|
|
21
|
+
# 2) 交互式场景(如 REPL / notebook / python -c)通常没有 __main__.__file__,回退到当前工作目录 cwd。
|
|
22
|
+
if hasattr(sys.modules['__main__'], '__file__'):
|
|
23
|
+
return Path(sys.argv[0]).resolve().parent
|
|
24
|
+
return Path.cwd()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def detect_working_root(start_dir=None):
|
|
28
|
+
if start_dir is None:
|
|
29
|
+
start_dir = _detect_start_dir()
|
|
30
|
+
else:
|
|
31
|
+
start_dir = Path(start_dir).resolve()
|
|
32
|
+
while True:
|
|
33
|
+
if (start_dir / '.env').exists():
|
|
34
|
+
return start_dir
|
|
35
|
+
if start_dir == start_dir.parent:
|
|
36
|
+
return None
|
|
37
|
+
start_dir = start_dir.parent
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_bundle_runtime():
|
|
41
|
+
return bool(getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def detect_bundle_root_dir():
|
|
45
|
+
if is_bundle_runtime():
|
|
46
|
+
return Path(sys._MEIPASS).resolve()
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _find_package_path(package):
|
|
51
|
+
module = importlib.import_module(package)
|
|
52
|
+
package_paths = list(getattr(module, '__path__', []))
|
|
53
|
+
if package_paths:
|
|
54
|
+
return Path(package_paths[0]).resolve()
|
|
55
|
+
if getattr(module, '__file__', None):
|
|
56
|
+
return Path(module.__file__).resolve().parent
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def package_resource_root(package, source_rel_path, resource_package):
|
|
61
|
+
package_path = _find_package_path(package)
|
|
62
|
+
if package_path is not None:
|
|
63
|
+
source_tree_root = detect_working_root(package_path)
|
|
64
|
+
if source_tree_root is not None:
|
|
65
|
+
source_path = source_tree_root / source_rel_path
|
|
66
|
+
if source_path.exists():
|
|
67
|
+
return pkgres.ResourceRoot.from_dir(source_path)
|
|
68
|
+
return pkgres.ResourceRoot.from_package(resource_package)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _resolve_env_path(env_key, fallback):
|
|
72
|
+
value = os.environ.get(env_key)
|
|
73
|
+
return Path(value).expanduser() if value else fallback
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def system_config_root():
|
|
77
|
+
if sys.platform == 'win32':
|
|
78
|
+
return _resolve_env_path('PROGRAMDATA', Path('C:/ProgramData')) / _XAPP_CONFIG_DIR
|
|
79
|
+
return Path('/etc') / _XAPP_CONFIG_DIR
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def user_config_home():
|
|
83
|
+
if sys.platform == 'win32':
|
|
84
|
+
return _resolve_env_path('APPDATA', Path.home() / 'AppData' / 'Roaming')
|
|
85
|
+
return _resolve_env_path('XDG_CONFIG_HOME', Path.home() / '.config')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def user_config_root():
|
|
89
|
+
return user_config_home() / _XAPP_CONFIG_DIR
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def system_storage_root():
|
|
93
|
+
if sys.platform == 'win32':
|
|
94
|
+
return _resolve_env_path('PROGRAMDATA', Path('C:/ProgramData')) / _XAPP_STORAGE_DIR
|
|
95
|
+
return Path('/var/lib') / _XAPP_STORAGE_DIR
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def user_data_home():
|
|
99
|
+
if sys.platform == 'win32':
|
|
100
|
+
return _resolve_env_path('LOCALAPPDATA', Path.home() / 'AppData' / 'Local')
|
|
101
|
+
return _resolve_env_path('XDG_DATA_HOME', Path.home() / '.local' / 'share')
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def user_storage_root():
|
|
105
|
+
return user_data_home() / _XAPP_STORAGE_DIR
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def xapp_default_system_config_file():
|
|
109
|
+
return system_config_root() / _XAPP_ENV_FILE
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def xapp_default_user_config_file():
|
|
113
|
+
return user_config_root() / _XAPP_ENV_FILE
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def load_system_user_config_file(config_file_path):
|
|
117
|
+
merged = {}
|
|
118
|
+
for config_root in (system_config_root(), user_config_root()):
|
|
119
|
+
path = config_root / config_file_path
|
|
120
|
+
if not path.exists():
|
|
121
|
+
continue
|
|
122
|
+
datatypes.merge(merged, serde.load_yaml_file(path, {}) or {})
|
|
123
|
+
return datatypes.TypedValueMap(merged)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _load_dotenv_values(env_file, fallback):
|
|
127
|
+
return dotenv.dotenv_values(env_file) if env_file.exists() else fallback
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def load_env_config_by_priority(working_root=None, env_defaults=None):
|
|
131
|
+
# Priority: process env > .env.local > .env > user config > system config > env_defaults > code fallbacks.
|
|
132
|
+
# env_defaults is a handy code-provided config source; it avoids forcing defaults into .env and stays overrideable by env files and process env.
|
|
133
|
+
# code fallbacks are caller-provided defaults used when reading env_config values.
|
|
134
|
+
# Env files are not exported to os.environ.
|
|
135
|
+
env_data = dict(env_defaults or {})
|
|
136
|
+
env_data.update(_load_dotenv_values(xapp_default_system_config_file(), {}))
|
|
137
|
+
env_data.update(_load_dotenv_values(xapp_default_user_config_file(), {}))
|
|
138
|
+
if working_root:
|
|
139
|
+
env_data.update(_load_dotenv_values(working_root / '.env', {}))
|
|
140
|
+
env_data.update(_load_dotenv_values(working_root / '.env.local', {}))
|
|
141
|
+
env_data = {key: value for key, value in env_data.items() if value is not None}
|
|
142
|
+
env_data.update(os.environ)
|
|
143
|
+
return datatypes.TypedValueMap(env_data)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def normalize_app_env(app_env=None):
|
|
147
|
+
# APP_ENV default: missing, empty, or invalid values resolve to prod.
|
|
148
|
+
app_env = app_env or 'prod'
|
|
149
|
+
app_env = str(app_env).lower()
|
|
150
|
+
if app_env not in ('dev', 'test', 'pre', 'prod'):
|
|
151
|
+
return 'prod'
|
|
152
|
+
return app_env
|
cpbox/app/devops.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from cpbox.app.appconfig import appconfig
|
|
7
|
+
from cpbox.tool import file
|
|
8
|
+
from cpbox.tool import functocli
|
|
9
|
+
from cpbox.tool import spec
|
|
10
|
+
from cpbox.tool import system
|
|
11
|
+
from cpbox.tool import template
|
|
12
|
+
from cpbox.tool import utils
|
|
13
|
+
from cpbox.app.tools import tibaselog
|
|
14
|
+
|
|
15
|
+
is_windows = os.name == 'nt'
|
|
16
|
+
|
|
17
|
+
class DevOpsAppConfig:
|
|
18
|
+
|
|
19
|
+
def __init__(self, app_name, provider):
|
|
20
|
+
appconfig.init(app_name, provider.get_env(), provider.get_app_config())
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DevOpsAppConfigContext:
|
|
24
|
+
|
|
25
|
+
def __init__(self, app_name, kwargs):
|
|
26
|
+
|
|
27
|
+
self.app_base_name = app_name
|
|
28
|
+
self.kwargs = kwargs
|
|
29
|
+
self.sandbox_version = kwargs.get('sandbox_version', 0)
|
|
30
|
+
|
|
31
|
+
self.env = self._get_env()
|
|
32
|
+
self.app_name = self._determine_app_name()
|
|
33
|
+
|
|
34
|
+
self.construct_baisc_dir()
|
|
35
|
+
|
|
36
|
+
def _determine_app_name(self):
|
|
37
|
+
app_name = self.app_base_name
|
|
38
|
+
if self.sandbox_version != 0:
|
|
39
|
+
app_name = '%s-sandbox-%03d' % (app_name, self.sandbox_version)
|
|
40
|
+
return app_name
|
|
41
|
+
|
|
42
|
+
def construct_baisc_dir(self):
|
|
43
|
+
default_data_dir = '/opt/data' if not is_windows else 'D:\\opt\\data'
|
|
44
|
+
data_dir = Path(os.environ.get('CPBOX_DATA_DIR', default_data_dir))
|
|
45
|
+
|
|
46
|
+
self.app_storage_dir = data_dir / self.app_name
|
|
47
|
+
|
|
48
|
+
self.app_persistent_storage_dir = self.app_storage_dir / 'persistent'
|
|
49
|
+
self.app_runtime_storage_dir = self.app_storage_dir / 'runtime'
|
|
50
|
+
self.app_logs_dir = self.app_runtime_storage_dir / 'logs'
|
|
51
|
+
|
|
52
|
+
def is_dev(self):
|
|
53
|
+
return self.env == 'dev' or self.env == 'test'
|
|
54
|
+
|
|
55
|
+
def _get_env(self):
|
|
56
|
+
env = 'dev'
|
|
57
|
+
if 'PUPPY_ENV' in os.environ:
|
|
58
|
+
env = os.environ['PUPPY_ENV']
|
|
59
|
+
if 'CPBOX_ENV' in os.environ:
|
|
60
|
+
env = os.environ['CPBOX_ENV']
|
|
61
|
+
if 'env' in self.kwargs:
|
|
62
|
+
env = self.kwargs['env']
|
|
63
|
+
return env
|
|
64
|
+
|
|
65
|
+
def get_sandbox_code_dir(self):
|
|
66
|
+
sandbox_code_dir = None
|
|
67
|
+
base_dir = self.app_persistent_storage_dir
|
|
68
|
+
if self.sandbox_version != 0:
|
|
69
|
+
sandbox_code_dir = '%s/sandbox-code-%03d' % (base_dir, self.sandbox_version)
|
|
70
|
+
sandbox_code_dir = Path(sandbox_code_dir)
|
|
71
|
+
return sandbox_code_dir
|
|
72
|
+
|
|
73
|
+
def get_root_dir(self):
|
|
74
|
+
if hasattr(sys.modules['__main__'], '__file__'):
|
|
75
|
+
root_dir = Path(sys.argv[0]).resolve().parent
|
|
76
|
+
return root_dir
|
|
77
|
+
else:
|
|
78
|
+
root_dir = Path.cwd()
|
|
79
|
+
return root_dir
|
|
80
|
+
|
|
81
|
+
def ensure_dir_and_write_permission(self):
|
|
82
|
+
file.ensure_dir(self.app_persistent_storage_dir)
|
|
83
|
+
file.ensure_dir(self.app_runtime_storage_dir)
|
|
84
|
+
file.ensure_dir(self.app_logs_dir)
|
|
85
|
+
file.ensure_dir(self.app_logs_dir / 'syslog')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class DevOpsApp(functocli.SimpleApp):
|
|
89
|
+
|
|
90
|
+
def __init__(self, app_name, **kwargs):
|
|
91
|
+
functocli.SimpleApp.__init__(self)
|
|
92
|
+
self.config_context = DevOpsAppConfigContext(app_name, kwargs)
|
|
93
|
+
self.env = self.config_context.env
|
|
94
|
+
|
|
95
|
+
self.construct_baisc_dir()
|
|
96
|
+
|
|
97
|
+
self.logger = self._make_logger(app_name, kwargs)
|
|
98
|
+
|
|
99
|
+
self.file_lock = None
|
|
100
|
+
|
|
101
|
+
def run_cmd_ret(self, cmd, log=True):
|
|
102
|
+
return self.run_cmd(cmd, log=log)[1]
|
|
103
|
+
|
|
104
|
+
def run_cmd(self, cmd, log=True):
|
|
105
|
+
if log:
|
|
106
|
+
self.logger.info('run_cmd: %s', cmd)
|
|
107
|
+
return system.run_cmd(cmd)
|
|
108
|
+
|
|
109
|
+
def shell_run(self, cmd, keep_pipeline=True, exit_on_error=True, dry_run=False, log=True):
|
|
110
|
+
if log:
|
|
111
|
+
self.logger.info('shell_run: %s', cmd)
|
|
112
|
+
if dry_run:
|
|
113
|
+
return 0
|
|
114
|
+
return system.shell_run(cmd, keep_pipeline=keep_pipeline, exit_on_error=exit_on_error)
|
|
115
|
+
|
|
116
|
+
def _make_logger(self, app_name, kwargs):
|
|
117
|
+
tibaselog.log_manager.add_file_logger(self.app_logs_dir, f'{app_name}.log')
|
|
118
|
+
import logging
|
|
119
|
+
return logging.getLogger(app_name)
|
|
120
|
+
|
|
121
|
+
def construct_baisc_dir(self):
|
|
122
|
+
|
|
123
|
+
context = self.config_context
|
|
124
|
+
context.construct_baisc_dir()
|
|
125
|
+
context.ensure_dir_and_write_permission()
|
|
126
|
+
|
|
127
|
+
hostname = socket.gethostname()
|
|
128
|
+
self.hostname_fqdn = hostname
|
|
129
|
+
self.hostname_short = hostname.split('.', 1)[0]
|
|
130
|
+
|
|
131
|
+
self.root_dir = context.get_sandbox_code_dir() if context.sandbox_version else context.get_root_dir()
|
|
132
|
+
self._roles_dir = self.root_dir / 'roles'
|
|
133
|
+
|
|
134
|
+
app_root_dir = self._roles_dir / context.app_base_name
|
|
135
|
+
|
|
136
|
+
self.app_root_dir = app_root_dir
|
|
137
|
+
self.app_config_dir = app_root_dir / 'config'
|
|
138
|
+
self.app_templates_dir = app_root_dir / 'templates'
|
|
139
|
+
self.app_scripts_dir = app_root_dir / 'scripts'
|
|
140
|
+
|
|
141
|
+
self.app_storage_dir = context.app_storage_dir
|
|
142
|
+
self.app_persistent_storage_dir = context.app_persistent_storage_dir
|
|
143
|
+
self.app_runtime_storage_dir = context.app_runtime_storage_dir
|
|
144
|
+
self.app_logs_dir = context.app_logs_dir
|
|
145
|
+
|
|
146
|
+
def remove_container(self, name, force=False, dry_run=False):
|
|
147
|
+
cmd = 'docker rm %s' % (name)
|
|
148
|
+
if force:
|
|
149
|
+
cmd = 'docker rm -f %s' % (name)
|
|
150
|
+
return self.shell_run(cmd, exit_on_error=False, dry_run=dry_run)
|
|
151
|
+
|
|
152
|
+
def container_is_running(self, name):
|
|
153
|
+
cmd = 'docker ps | grep %s' % (name)
|
|
154
|
+
return self.run_cmd(cmd, log=False)[0] == 0
|
|
155
|
+
|
|
156
|
+
def stop_container(self, name, timeout=300, dry_run=False):
|
|
157
|
+
cmd = 'docker stop --time %d %s' % (timeout, name)
|
|
158
|
+
return self.shell_run(cmd, exit_on_error=False, dry_run=dry_run)
|
|
159
|
+
|
|
160
|
+
def _check_lock(self):
|
|
161
|
+
filepath = self.app_runtime_storage_dir / 'locks' / file.compute_lock_filepath(sys.argv)
|
|
162
|
+
file_lock = file.obtain_lock(filepath)
|
|
163
|
+
if file_lock is None:
|
|
164
|
+
pid = 0
|
|
165
|
+
with open(filepath, 'r') as f:
|
|
166
|
+
pid = f.read()
|
|
167
|
+
self.logger.warning('lock file exists, pid: %s => %s', pid, filepath)
|
|
168
|
+
sys.exit(1)
|
|
169
|
+
else:
|
|
170
|
+
self.file_lock = file_lock
|
|
171
|
+
|
|
172
|
+
def template_to(self, template_filename, dst, payload, app_name=None):
|
|
173
|
+
template_payload = {'payload': payload}
|
|
174
|
+
src = self.app_templates_dir_for(app_name) / template_filename
|
|
175
|
+
template.render_to_file(src, template_payload, dst)
|
|
176
|
+
self.logger.info('template_to: %s => %s', src, dst)
|
|
177
|
+
|
|
178
|
+
def read_app_config(self, config_filebasename, app_name=None):
|
|
179
|
+
if '.yml' not in config_filebasename:
|
|
180
|
+
config_filebasename = config_filebasename + '.yml'
|
|
181
|
+
data = utils.load_yaml(self.app_config_dir_for(app_name) / config_filebasename, {})
|
|
182
|
+
return data
|
|
183
|
+
|
|
184
|
+
def app_templates_dir_for(self, app_name=None):
|
|
185
|
+
if app_name is None:
|
|
186
|
+
return self.app_templates_dir
|
|
187
|
+
return self._roles_dir / app_name / 'templates'
|
|
188
|
+
|
|
189
|
+
def app_config_dir_for(self, app_name=None):
|
|
190
|
+
if app_name is None:
|
|
191
|
+
return self.app_config_dir
|
|
192
|
+
return self._roles_dir / app_name / 'config'
|
|
193
|
+
|
|
194
|
+
@functocli.keep_method_for_inheritance
|
|
195
|
+
def check_health(self):
|
|
196
|
+
print('%s is good' % (sys.argv[0]))
|
|
197
|
+
|
|
198
|
+
@staticmethod
|
|
199
|
+
def run_app(app, log_level='info', default_method=None, common_args_option=None):
|
|
200
|
+
functocli.run_app(app, default_method=default_method)
|
cpbox/app/env.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from cpbox.tool import cache
|
|
2
|
+
from cpbox.tool import utils
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EnvTool(object):
|
|
6
|
+
|
|
7
|
+
def __init__(self, app):
|
|
8
|
+
self.app = app
|
|
9
|
+
fn = self.app.root_dir / '.box-config.yml'
|
|
10
|
+
self.box_config = utils.load_yaml(fn, {})
|
|
11
|
+
|
|
12
|
+
def docker_url_by_env(self, image_name):
|
|
13
|
+
group_names = self.app.get_group_names()
|
|
14
|
+
if 'nexus-genesis' in group_names:
|
|
15
|
+
return image_name
|
|
16
|
+
if 'firelighter-server' in group_names:
|
|
17
|
+
return self.docker_genesis_url(image_name)
|
|
18
|
+
else:
|
|
19
|
+
return self.docker_url(image_name)
|
|
20
|
+
|
|
21
|
+
def docker_url(self, image_name=''):
|
|
22
|
+
return self.box_config.get('docker_url_pre') + image_name
|
|
23
|
+
|
|
24
|
+
def docker_publish_url(self, image_name=''):
|
|
25
|
+
return self.box_config.get('docker_publish_url_pre') + image_name
|
|
26
|
+
|
|
27
|
+
def docker_genesis_url(self, image_name=''):
|
|
28
|
+
return self.box_config.get('docker_genesis_url_pre') + image_name
|
|
29
|
+
|
|
30
|
+
def setup_redis_for_cache(self):
|
|
31
|
+
config = self.box_config[self.app.env]
|
|
32
|
+
redis = config['redis']
|
|
33
|
+
redis_url = 'redis://:%s@%s:%s' % (redis['auth'], redis['host'], redis['port'])
|
|
34
|
+
cache.set_redis_url(redis_url)
|
cpbox/app/eventlog.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from cpbox.tool import timeutil
|
|
5
|
+
|
|
6
|
+
from cpbox.app.appconfig import appconfig
|
|
7
|
+
|
|
8
|
+
event_logger = logging.getLogger('event-log')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def add_event_log(event_key, payload):
|
|
12
|
+
payload['env'] = appconfig.get_env()
|
|
13
|
+
msg = 'cp %s %s' % (event_key, json.dumps(payload))
|
|
14
|
+
event_logger.info(msg)
|
|
15
|
+
|
|
16
|
+
def add_app_monitor_log(key, value, time=None):
|
|
17
|
+
time = time if time else timeutil.local_now_iso8601_str()
|
|
18
|
+
data = {
|
|
19
|
+
'event_key': key,
|
|
20
|
+
'event_value': value,
|
|
21
|
+
'time': time,
|
|
22
|
+
}
|
|
23
|
+
payload = {}
|
|
24
|
+
payload['env'] = appconfig.get_env()
|
|
25
|
+
payload['log'] = json.dumps(data)
|
|
26
|
+
msg = 'app-monitor monitor-log %s' % (json.dumps(payload))
|
|
27
|
+
event_logger.info(msg)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def log_func_call(func, *args, **kwargs):
|
|
31
|
+
def timed(*args, **kw):
|
|
32
|
+
start = time.time() * 1000
|
|
33
|
+
result = func(*args, **kw)
|
|
34
|
+
payload = {}
|
|
35
|
+
payload['name'] = func.__name__
|
|
36
|
+
payload['rt'] = time.time() * 1000 - start
|
|
37
|
+
add_event_log('func-call', payload)
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
return timed
|