Initial YakPanel commit
This commit is contained in:
79
class_v2/btdockerModelV2/appModel.py
Normal file
79
class_v2/btdockerModelV2/appModel.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型 - Docker应用
|
||||
# ------------------------------
|
||||
import public
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
# 2024/2/20 下午 4:31 获取/搜索docker应用的列表
|
||||
def get_app_list(self, get=None):
|
||||
'''
|
||||
@name 获取docker应用的列表
|
||||
@author wzz <2024/2/20 下午 4:32>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
|
||||
try:
|
||||
from btdockerModelV2 import registryModel as dr
|
||||
dr.main().registry_list(get)
|
||||
|
||||
from panelPlugin import panelPlugin
|
||||
pp = panelPlugin()
|
||||
get.type = 10
|
||||
# get.type = 16 # dev docker
|
||||
# get.type = 14 # www docker
|
||||
get.force = get.force if "force" in get and get.force else 0
|
||||
if not hasattr(get, "query"):
|
||||
get.query = ""
|
||||
get.tojs = "soft.get_list"
|
||||
# softList = pp.get_soft_list(get)
|
||||
if get.query != "":
|
||||
get.row = 1000
|
||||
softList = pp.get_soft_list(get)
|
||||
softList['list'] = self.struct_list(softList['list'])
|
||||
softList['list'] = pp.get_page(softList['list']['data'], get)
|
||||
else:
|
||||
|
||||
softList = pp.get_soft_list(get)
|
||||
return public.return_message(0, 0, softList['list'])
|
||||
except Exception as e:
|
||||
# public.print_log("1111111111 进方法")
|
||||
return public.return_message(-1, 0, e)
|
||||
|
||||
# 2024/2/20 下午 4:47 处理云端软件列表,只需要list中type=13的数据
|
||||
def struct_list(self, softList: dict):
|
||||
'''
|
||||
@name 处理云端软件列表,只需要list中type=13的数据
|
||||
@param softList:
|
||||
@return:
|
||||
'''
|
||||
new_list = []
|
||||
for i in softList['data']:
|
||||
# if i['type'] == 14: # www docker
|
||||
# if i['type'] == 16: # dev docker
|
||||
if i['type'] == 10:
|
||||
new_list.append(i)
|
||||
|
||||
softList['data'] = new_list
|
||||
|
||||
return softList
|
||||
186
class_v2/btdockerModelV2/backupModel.py
Normal file
186
class_v2/btdockerModelV2/backupModel.py
Normal file
@@ -0,0 +1,186 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
import os
|
||||
import time
|
||||
import traceback
|
||||
import gettext
|
||||
_ = gettext.gettext
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import public
|
||||
from btdockerModelV2 import containerModel as dc
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
|
||||
# 2023/12/22 上午 9:56 备份指定容器的mount volume
|
||||
def backup_volume(self, get):
|
||||
'''
|
||||
@name 备份指定容器的mount volume
|
||||
@author wzz <2023/12/22 上午 11:19>
|
||||
@param "data":{"container_id":"容器ID"}
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('container_id').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
try:
|
||||
client = dp.docker_client()
|
||||
container = client.containers.get(get.container_id)
|
||||
volume_list = container.attrs["Mounts"]
|
||||
volume_list = [v["Source"] for v in volume_list]
|
||||
|
||||
if not volume_list:
|
||||
return public.return_message(-1, 0,
|
||||
public.lang("There is no volume to back up"))
|
||||
|
||||
backup_path = "/www/backup/btdocker/volumes/{}".format(container.name)
|
||||
if not os.path.exists(backup_path):
|
||||
os.makedirs(backup_path, 0o755)
|
||||
|
||||
import subprocess
|
||||
public.ExecShell("echo -n > {}".format(self._backup_log))
|
||||
|
||||
for v in volume_list:
|
||||
backup_name = os.path.basename(v)
|
||||
# 2023/12/22 上午 10:34 每个压缩包命名都用v的目录名,如果是文件则用文件名
|
||||
tar_name = "{}_{}_{}.tar.gz".format(
|
||||
container.name,
|
||||
backup_name,
|
||||
time.strftime("%Y%m%d_%H%M%S", time.localtime())
|
||||
)
|
||||
backup_file = os.path.join(backup_path, tar_name)
|
||||
source_path = os.path.dirname(v)
|
||||
cmd = "cd {} && tar zcvf {} {}".format(source_path, backup_file, backup_name)
|
||||
cmd = ("nohup echo 'To start backing up {} of container {}, it may take more than 1-5 minutes...' >> {};"
|
||||
"{} >> {} 2>&1 &&"
|
||||
"echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &"
|
||||
.format(
|
||||
container.name,
|
||||
tar_name,
|
||||
self._backup_log,
|
||||
cmd,
|
||||
self._backup_log,
|
||||
self._backup_log,
|
||||
self._backup_log,
|
||||
))
|
||||
subprocess.Popen(cmd, shell=True)
|
||||
|
||||
# 2023/12/22 下午 12:17 添加到数据库
|
||||
dp.sql('dk_backup').add(
|
||||
'type,name,container_id,container_name,filename,size,addtime',
|
||||
(3, tar_name, container.id, container.name, backup_file, 0, time.strftime(
|
||||
"%Y-%m-%d %H:%M:%S", time.localtime()
|
||||
))
|
||||
)
|
||||
public.WriteLog("Docker module", "The {} of the backup container {} succeeds!".format(container.name, tar_name))
|
||||
|
||||
return public.return_message(0, 0, public.lang("The backup task was created successfully."))
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
return public.return_message(-1, 0, public.lang("Failed to create a backup task {}", str(e)))
|
||||
|
||||
# 2023/12/22 上午 11:23 获取指定容器的备份列表
|
||||
def get_backup_list(self, get):
|
||||
'''
|
||||
@name 获取指定容器的备份列表
|
||||
@param "data":{"container_id":"容器ID"}
|
||||
@return list[dict{"":""}]
|
||||
'''
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('container_id').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
try:
|
||||
# 2023/12/22 下午 12:24 从数据库中获取已备份的指定容器
|
||||
backup_list = dp.sql('dk_backup').where('container_id=?', (get.container_id,)).field(
|
||||
'name,container_id,container_name,filename,size,addtime'
|
||||
).select()
|
||||
|
||||
for l in backup_list:
|
||||
if not os.path.exists(l['filename']):
|
||||
l['size'] = 0
|
||||
l['ps'] = 'file does not exist'
|
||||
continue
|
||||
|
||||
l['size'] = os.path.getsize(l['filename'])
|
||||
l['ps'] = 'local backup'
|
||||
|
||||
return public.return_message(0, 0, backup_list)
|
||||
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
return public.return_message(0, 0, [])
|
||||
|
||||
# 2023/12/22 下午 2:25 删除指定容器的备份
|
||||
def remove_backup(self, get):
|
||||
'''
|
||||
@name 删除指定容器的备份
|
||||
@param "data":{"container_id":"容器ID","container_name":"容器名","name":"文件名"}
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('container_id').Require().String(),
|
||||
Param('container_name').Require().String(),
|
||||
Param('name').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
|
||||
try:
|
||||
# 2023/12/22 下午 2:26 从数据库中删除指定容器的备份
|
||||
dp.sql('dk_backup').where('container_id=? and name=?', (get.container_id, get.name)).delete()
|
||||
|
||||
# 2023/12/22 下午 2:27 删除本地备份文件
|
||||
backup_path = "/www/backup/btdocker/volumes/{}".format(get.container_name)
|
||||
file_path = os.path.join(backup_path, get.name)
|
||||
if not os.path.exists(file_path):
|
||||
return public.return_message(0, 0, public.lang("successfully delete"))
|
||||
os.remove(file_path)
|
||||
return public.return_message(0, 0, public.lang("successfully delete"))
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
return public.return_message(-1, 0, public.lang("Failed to delete the file, reason: {}", get.name, str(e)))
|
||||
|
||||
def get_pull_log(self, get):
|
||||
"""
|
||||
获取镜像拉取日志,websocket
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
get.wsLogTitle = "Start container directory backup, please wait..."
|
||||
get._log_path = self._backup_log
|
||||
return self.get_ws_log(get)
|
||||
1150
class_v2/btdockerModelV2/composeModel.py
Normal file
1150
class_v2/btdockerModelV2/composeModel.py
Normal file
File diff suppressed because it is too large
Load Diff
3
class_v2/btdockerModelV2/config/com_reg_mirror.json
Normal file
3
class_v2/btdockerModelV2/config/com_reg_mirror.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"https://docker.m.daocloud.io": "Third party image accelerator"
|
||||
}
|
||||
11
class_v2/btdockerModelV2/config/com_registry.json
Normal file
11
class_v2/btdockerModelV2/config/com_registry.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"docker.io": "docker official mirror site",
|
||||
"swr.cn-north-4.myhuaweicloud.com": "Huawei Cloud Mirror Station (North China-Beijing 4)",
|
||||
"ccr.ccs.tencentyun.com": "Tencent Cloud Mirror Station",
|
||||
"registry.cn-hongkong.aliyuncs.com": "Alibaba Cloud Mirror Station (Hong Kong)",
|
||||
"registry.ap-southeast-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Singapore)",
|
||||
"registry.us-west-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Silicon Valley, USA)",
|
||||
"registry.eu-west-1.aliyuncs.com": "Alibaba Cloud Mirror Station (London, UK)",
|
||||
"registry.eu-central-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Frankfurt, Germany)",
|
||||
"registry.ap-northeast-1.aliyuncs.com": "Alibaba Cloud Mirror Station (Japan)"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
1710901654
|
||||
BIN
class_v2/btdockerModelV2/config/docker_hub_repos.db
Normal file
BIN
class_v2/btdockerModelV2/config/docker_hub_repos.db
Normal file
Binary file not shown.
1940
class_v2/btdockerModelV2/containerModel.py
Normal file
1940
class_v2/btdockerModelV2/containerModel.py
Normal file
File diff suppressed because it is too large
Load Diff
316
class_v2/btdockerModelV2/dk_public.py
Normal file
316
class_v2/btdockerModelV2/dk_public.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
import time
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import json
|
||||
import os
|
||||
|
||||
import db
|
||||
# from class_v2.dk_db import db
|
||||
import public
|
||||
from public.validate import Param
|
||||
|
||||
# db_path = "/www/server/panel/data/db/docker.db"
|
||||
db_path = "/www/server/panel/data/docker.db"
|
||||
|
||||
|
||||
def check_db():
|
||||
if not os.path.exists(db_path) or os.path.getsize(db_path) == 0:
|
||||
execstr = "wget -O {} {}/install/src/docker_en.db".format(db_path, public.get_url())
|
||||
public.ExecShell(execstr)
|
||||
|
||||
|
||||
def sql(table):
|
||||
check_db()
|
||||
with db.Sql() as sql:
|
||||
sql.dbfile(db_path)
|
||||
return sql.table(table)
|
||||
|
||||
|
||||
# 实例化docker
|
||||
def docker_client(url="unix:///var/run/docker.sock"):
|
||||
"""
|
||||
目前仅支持本地服务器
|
||||
:param url: unix:///var/run/docker.sock
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
import docker
|
||||
except:
|
||||
public.ExecShell("btpip install --upgrade docker")
|
||||
import docker
|
||||
|
||||
try:
|
||||
client = docker.DockerClient(base_url=url)
|
||||
if client:
|
||||
return client
|
||||
except Exception as e:
|
||||
#如果Docker服务是Active状态但连接失败,说明SDK版本不兼容,尝试自动升级
|
||||
if public.ExecShell("systemctl is-active docker")[0].strip() == "active":
|
||||
public.ExecShell("btpip install docker==7.1.0")
|
||||
# 留出升级时间
|
||||
time.sleep(5)
|
||||
|
||||
public.restart_panel()
|
||||
|
||||
public.print_log(public.get_error_info())
|
||||
return False
|
||||
|
||||
|
||||
def docker_client_low(url="unix:///var/run/docker.sock"):
|
||||
"""
|
||||
docker 低级接口
|
||||
:param url:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
import docker
|
||||
except:
|
||||
public.ExecShell("btpip install --upgrade docker")
|
||||
import docker
|
||||
|
||||
try:
|
||||
client = docker.APIClient(base_url=url)
|
||||
return client
|
||||
except docker.errors.DockerException:
|
||||
return False
|
||||
|
||||
|
||||
# 取CPU类型
|
||||
def get_cpu_count():
|
||||
import re
|
||||
with open('/proc/cpuinfo', 'r') as f:
|
||||
cpuinfo = f.read()
|
||||
rep = r"processor\s*:"
|
||||
tmp = re.findall(rep, cpuinfo)
|
||||
if not tmp:
|
||||
return 0
|
||||
return len(tmp)
|
||||
|
||||
|
||||
def set_kv(kv_str):
|
||||
"""
|
||||
将键值字符串转为对象
|
||||
:param data:
|
||||
:return:
|
||||
"""
|
||||
if not kv_str:
|
||||
return None
|
||||
res = kv_str.split('\n')
|
||||
data = dict()
|
||||
for i in res:
|
||||
i = i.strip()
|
||||
if not i:
|
||||
continue
|
||||
if i.find('=') == -1:
|
||||
continue
|
||||
if i.find('=') > 1:
|
||||
k, v = i.split('=', 1)
|
||||
|
||||
data[k] = v
|
||||
continue
|
||||
|
||||
k, v = i.split('=')
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
|
||||
def get_mem_info():
|
||||
# 取内存信息
|
||||
import psutil
|
||||
mem = psutil.virtual_memory()
|
||||
memInfo = int(mem.total)
|
||||
return memInfo
|
||||
|
||||
|
||||
def byte_conversion(data):
|
||||
data = data.lower() # 将数据转换为小写字母形式
|
||||
if "tib" in data:
|
||||
return float(data.replace('tib', '')) * 1024 * 1024 * 1024 *1024
|
||||
if "gib" in data:
|
||||
return float(data.replace('gib', '')) * 1024 * 1024 * 1024
|
||||
elif "mib" in data:
|
||||
return float(data.replace('mib', '')) * 1024 * 1024
|
||||
elif "kib" in data:
|
||||
return float(data.replace('kib', '')) * 1024
|
||||
elif "tb" in data:
|
||||
return float(data.replace('tb', '')) * 1024 * 1024 * 1024 *1024
|
||||
elif "gb" in data:
|
||||
return float(data.replace('gb', '')) * 1024 * 1024 * 1024
|
||||
elif "mb" in data:
|
||||
return float(data.replace('mb', '')) * 1024 * 1024
|
||||
elif "kb" in data:
|
||||
return float(data.replace('kb', '')) * 1024
|
||||
elif "b" in data:
|
||||
return float(data.replace('b', ''))
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def bytes_to_human_readable(bytes_num):
|
||||
"""
|
||||
将字节数转换为人类可读的格式(KB、MB、GB等)
|
||||
:param bytes_num: 字节数
|
||||
:return: 格式化后的字符串 xxx mb
|
||||
"""
|
||||
suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
index = 0
|
||||
while bytes_num >= 1024 and index < len(suffixes) - 1:
|
||||
bytes_num /= 1024.0
|
||||
index += 1
|
||||
return "{:.2f} {}".format(bytes_num, suffixes[index])
|
||||
|
||||
|
||||
def log_docker(generator, task_name):
|
||||
__log_path = '/tmp/dockertmp.log'
|
||||
while True:
|
||||
try:
|
||||
output = generator.__next__()
|
||||
try:
|
||||
output = json.loads(output)
|
||||
if 'status' in output:
|
||||
output_str = "{}\n".format(output['status'])
|
||||
public.writeFile(__log_path, output_str, 'a+')
|
||||
except:
|
||||
public.writeFile(__log_path, public.get_error_info(), 'a+')
|
||||
if 'stream' in output:
|
||||
output_str = output['stream']
|
||||
public.writeFile(__log_path, output_str, 'a+')
|
||||
except StopIteration:
|
||||
public.writeFile(__log_path, f'{task_name} complete.', 'a+')
|
||||
break
|
||||
except ValueError:
|
||||
public.writeFile(__log_path, f'Error parsing output from {task_name}: {output}', 'a+')
|
||||
except Exception as e:
|
||||
public.writeFile(__log_path, f'Error from {task_name}: {e}', 'a+')
|
||||
break
|
||||
|
||||
|
||||
def docker_conf():
|
||||
"""
|
||||
解析docker配置文件
|
||||
KEY=VAULE
|
||||
KEY1=VALUE1
|
||||
:return:
|
||||
"""
|
||||
docker_conf = public.readFile("{}/data/docker.conf".format(public.get_panel_path()))
|
||||
if not docker_conf:
|
||||
return {"SAVE": 30}
|
||||
data = dict()
|
||||
for i in docker_conf.split("\n"):
|
||||
if not i:
|
||||
continue
|
||||
k, v = i.split("=")
|
||||
if k == "SAVE":
|
||||
v = int(v)
|
||||
data[k] = v
|
||||
return data
|
||||
|
||||
|
||||
def get_process_id(pname, cmd_line):
|
||||
import psutil
|
||||
pids = psutil.pids()
|
||||
for pid in pids:
|
||||
try:
|
||||
p = psutil.Process(pid)
|
||||
if p.name() == pname and cmd_line in p.cmdline():
|
||||
return pid
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def write_log(log_data):
|
||||
public.WriteLog("Docker", log_data)
|
||||
|
||||
|
||||
def check_socket(port):
|
||||
import socket
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
location = ("127.0.0.1", int(port))
|
||||
result_of_check = s.connect_ex(location)
|
||||
s.close()
|
||||
if result_of_check == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def download_file(url, filename):
|
||||
'''
|
||||
下载方法
|
||||
@param url:
|
||||
@param filename:
|
||||
@return:
|
||||
'''
|
||||
return public.ExecShell(f"wget -O {filename} {url} --no-check-certificate")
|
||||
|
||||
def convert_timezone_str_to_iso8601(timestamp_str):
|
||||
# 解析时间字符串为 datetime 对象
|
||||
dt = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S %z %Z')
|
||||
|
||||
# 转换时区为 UTC
|
||||
dt_utc = dt.astimezone(timezone.utc)
|
||||
|
||||
# 格式化为 ISO 8601 格式
|
||||
iso8601_str = dt_utc.strftime('%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
|
||||
return iso8601_str
|
||||
|
||||
def timestamp_to_string(timestamp):
|
||||
# 将时间戳转换为 datetime 对象
|
||||
dt_object = datetime.fromtimestamp(timestamp)
|
||||
# 格式化为字符串
|
||||
formatted_string = dt_object.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
return formatted_string
|
||||
|
||||
def rename(name: str):
|
||||
"""
|
||||
重命名容器名,兼容中文命名
|
||||
@param name:
|
||||
@return:
|
||||
"""
|
||||
try:
|
||||
if name[:4] != 'q18q':
|
||||
return name
|
||||
config_path = "{}/config/name_map.json".format(public.get_panel_path())
|
||||
config_data = json.loads(public.readFile(config_path))
|
||||
name_l = name.split('_')
|
||||
if name_l[0] in config_data.keys():
|
||||
name_l[0] = config_data[name_l[0]]
|
||||
return '_'.join(name_l)
|
||||
except:
|
||||
return name
|
||||
|
||||
def convert_timezone_str_to_timestamp(timestamp_str: str):
|
||||
import re
|
||||
# 解析时间字符串为 2024-05-16T06:18:23.915547557-04:00 时间戳
|
||||
timestamp_str = re.sub(r'\.\d+', '', timestamp_str)
|
||||
|
||||
date_formats = ("%Y-%m-%dT%H:%M:%S%z", "%Y-%m-%d %H:%M:%S %z %Z")
|
||||
dt = None
|
||||
|
||||
for format_str in date_formats:
|
||||
try:
|
||||
dt = datetime.strptime(timestamp_str, format_str)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if dt is None:
|
||||
return None
|
||||
|
||||
# 转换时区为 UTC,然后转换为时间戳
|
||||
dt_utc = dt.astimezone(timezone.utc)
|
||||
timestamp = dt_utc.timestamp()
|
||||
|
||||
return timestamp
|
||||
297
class_v2/btdockerModelV2/dkgroupModel.py
Normal file
297
class_v2/btdockerModelV2/dkgroupModel.py
Normal file
@@ -0,0 +1,297 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author:
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# 容器启动顺序
|
||||
# ------------------------------
|
||||
import copy
|
||||
import json
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
import psutil
|
||||
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
|
||||
import public
|
||||
from btdockerModelV2.containerModel import main as docker
|
||||
from btdockerModelV2.dockerSock import container
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
|
||||
|
||||
class main():
|
||||
GROUP_PATH = "/www/server/panel/data/docker_groups.json"
|
||||
|
||||
def __init__(self):
|
||||
if not os.path.exists(self.GROUP_PATH):
|
||||
public.WriteFile(self.GROUP_PATH, "[]")
|
||||
|
||||
# 添加组信息
|
||||
def add_group(self, get):
|
||||
try:
|
||||
|
||||
if not hasattr(get, "group_name"):
|
||||
return public.return_message(-1, 0, public.lang("Missing parameters name"))
|
||||
|
||||
if not hasattr(get, "container_info"):
|
||||
return public.return_message(-1, 0, public.lang("Missing parameters container_info"))
|
||||
|
||||
interval = 30 if not get.interval else int(get.interval)
|
||||
|
||||
container_info = get.container_info.split(',') if get.container_info else []
|
||||
from uuid import uuid4
|
||||
group_id = uuid4().hex[::2]
|
||||
|
||||
file_data = self.__readFile()
|
||||
for group in file_data:
|
||||
if group["group_name"] == get.group_name.strip():
|
||||
return public.return_message(-1, 0, public.lang("The grouping already exists"))
|
||||
for temp in container_info:
|
||||
if temp in group["order"]:
|
||||
return public.return_message(-1, 0, public.lang("Container {} has been added by group {}!",temp, group['group_name']))
|
||||
|
||||
pdata = {
|
||||
"id": group_id,
|
||||
"group_name": get.group_name.strip(),
|
||||
"interval": interval,
|
||||
"order": container_info,
|
||||
}
|
||||
|
||||
file_data.append(pdata)
|
||||
public.writeFile(self.GROUP_PATH, json.dumps(file_data))
|
||||
|
||||
return public.return_message(0, 0, public.lang("Added successfully"))
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, public.lang("Add failed:{}",e))
|
||||
|
||||
# 编辑组信息
|
||||
def update_group(self, get):
|
||||
try:
|
||||
if not hasattr(get, "id"):
|
||||
return public.return_message(-1, 0, public.lang("Missing parameters id"))
|
||||
|
||||
interval = 30 if not get.interval else int(get.interval)
|
||||
container_info = get.container_info.split(",") if get.container_info else []
|
||||
file_data = self.__readFile()
|
||||
|
||||
for group in file_data:
|
||||
if group["id"] == get.id.strip():
|
||||
group["group_name"] = get.group_name.strip()
|
||||
group["interval"] = interval
|
||||
group["order"] = container_info
|
||||
break
|
||||
|
||||
public.writeFile(self.GROUP_PATH, json.dumps(file_data))
|
||||
|
||||
return public.return_message(0, 0, public.lang("Edit Success"))
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, public.lang("Edit failed:{}",e))
|
||||
|
||||
# 删除组信息
|
||||
def del_group(self, get):
|
||||
try:
|
||||
if not hasattr(get, "id"):
|
||||
return public.return_message(-1, 0, public.lang("Missing parameters id"))
|
||||
|
||||
file_data = self.__readFile()
|
||||
for index, data in enumerate(file_data):
|
||||
if data["id"] == get.id.strip():
|
||||
del file_data[index]
|
||||
break
|
||||
|
||||
public.writeFile(self.GROUP_PATH, json.dumps(file_data))
|
||||
return public.return_message(0, 0, public.lang("Deleted successfully"))
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, public.lang("Deletion failed:{}",e))
|
||||
|
||||
# 获取组信息
|
||||
def get_group(self, get):
|
||||
file_data = self.__readFile()
|
||||
|
||||
# 拿配置文件数据
|
||||
for group in file_data:
|
||||
group["status"] = 0
|
||||
# 标记文件
|
||||
status_file = "/tmp/{}.json".format(group["id"])
|
||||
try:
|
||||
info = json.loads(public.readFile(status_file))
|
||||
except:
|
||||
info = {
|
||||
"group_id": group["id"],
|
||||
"status": 0,
|
||||
"start_failed": ""
|
||||
}
|
||||
group["status"] = info["status"]
|
||||
group["start_failed"] = info["start_failed"]
|
||||
|
||||
return public.returnResult(True, data=file_data)
|
||||
|
||||
# 从组启动/停止容器
|
||||
def group_status(self, group_id, status):
|
||||
sk_container = container.dockerContainer()
|
||||
container_list = sk_container.get_container()
|
||||
|
||||
if status not in ["start", "stop"]:
|
||||
return public.return_message(-1, 0, public.lang("status Parameter error"))
|
||||
|
||||
# 状态启动中
|
||||
status_file = "/tmp/{}.json".format(group_id)
|
||||
|
||||
group_data = {
|
||||
"group_id": group_id,
|
||||
"status": 0,
|
||||
"start_failed": ""
|
||||
}
|
||||
|
||||
if status == "start":
|
||||
group_data["status"] = 2
|
||||
else:
|
||||
group_data["status"] = 4
|
||||
public.writeFile(status_file, json.dumps(group_data))
|
||||
|
||||
try:
|
||||
get = public.dict_obj()
|
||||
file_data = self.__readFile()
|
||||
# 获取对应ID的信息
|
||||
group_info = self.__group_info(group_id, file_data)
|
||||
if group_info:
|
||||
# 获取容器列表名称列表
|
||||
container_order = group_info["order"]
|
||||
# 依次启动容器
|
||||
for containers in container_order:
|
||||
get.id = containers
|
||||
get.status = status
|
||||
# 顺序开启并且容器已经开启了
|
||||
if status == "start" and self.__is_running(containers, container_list):
|
||||
continue
|
||||
result = docker().set_container_status(get)
|
||||
|
||||
if result["status"] == -1 and status == "start":
|
||||
# 写入失败标记文件 # 启动失败直接返回
|
||||
group_data["status"] = 3
|
||||
# 用于记录启动失败的容器
|
||||
group_data["start_failed"] = containers
|
||||
public.writeFile(status_file, json.dumps(group_data))
|
||||
return public.return_message(-1, 0, public.lang("Container [{}] operation failed",containers))
|
||||
# 启动时间间隔
|
||||
if len(container_order) > 1:
|
||||
time.sleep(int(group_info["interval"]))
|
||||
|
||||
if status == "start":
|
||||
group_data["status"] = 1
|
||||
elif status == "stop":
|
||||
group_data["status"] = 0
|
||||
public.writeFile(status_file, json.dumps(group_data))
|
||||
return public.return_message(0, 0, public.lang("Operation successful"))
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, public.lang("Operation failed:{}",e))
|
||||
|
||||
# 脚本调用
|
||||
def script_group(self, get):
|
||||
pid_file = "/tmp/docker_groups.pid"
|
||||
try:
|
||||
p = psutil.Process(public.readFile(pid_file))
|
||||
if p.is_running():
|
||||
return "The operation is running. Please wait for the previous operation to complete."
|
||||
except:
|
||||
pass
|
||||
|
||||
if os.path.exists(pid_file):
|
||||
os.remove(pid_file)
|
||||
|
||||
file_data = self.__readFile()
|
||||
for group in file_data:
|
||||
if group['id'] == get.id.strip():
|
||||
public.ExecShell(
|
||||
"nohup /www/server/panel/pyenv/bin/python3 /www/server/panel/script/set_docker_groups.py {} {} &> /tmp/docker_groups.log & \n"
|
||||
"echo $! > {} ".format(group['id'], get.status, pid_file)
|
||||
)
|
||||
|
||||
return public.return_message(0, 0, public.lang("Container start order {}",get.status))
|
||||
|
||||
# 顺序启动 / 停止 分组
|
||||
def modify_group_status(self, get):
|
||||
try:
|
||||
group_ids = [i for i in get.id.split(",")]
|
||||
|
||||
status = get.status.strip()
|
||||
if status not in ["start", "stop"]:
|
||||
return public.return_message(-1, 0, public.lang("Invalid state!"))
|
||||
|
||||
for group_id in group_ids:
|
||||
get.id = group_id
|
||||
return self.script_group(get)
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, public.lang("Operation failed:{}",e))
|
||||
|
||||
# 获取Json文件内容
|
||||
def __readFile(self):
|
||||
try:
|
||||
return json.loads(public.readFile(self.GROUP_PATH)) if public.readFile(self.GROUP_PATH) else []
|
||||
except:
|
||||
return []
|
||||
|
||||
# 返回指定组信息
|
||||
def get_info(self, get):
|
||||
if not hasattr(get, "id"):
|
||||
return public.return_message(-1, 0, public.lang("Missing parameters id"))
|
||||
return public.returnResult(True, data=self.__group_info(get.id, self.__readFile()))
|
||||
|
||||
# 获取指定组信息 内部使用
|
||||
def __group_info(self, group_id, filedata):
|
||||
result = None
|
||||
for result in filedata:
|
||||
if group_id == result["id"]:
|
||||
break
|
||||
return result
|
||||
|
||||
# 容器是否正在运行
|
||||
def __is_running(self, container_name, container_list):
|
||||
container_status = {dp.rename(temp['Names'][0].replace("/", "")): temp["State"] for temp in container_list}
|
||||
|
||||
if container_name in container_status and container_status[container_name] == "running":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def docker_list(self, get):
|
||||
try:
|
||||
# 获取容器列表和运行状态
|
||||
sk_container = container.dockerContainer()
|
||||
container_list = sk_container.get_container()
|
||||
|
||||
filedata = self.__readFile()
|
||||
|
||||
# 构建容器状态列表
|
||||
container_status_list = []
|
||||
|
||||
# 构建分组容器字典
|
||||
group_containers = {group_data["group_name"]: group_data["order"] for group_data in filedata}
|
||||
|
||||
# 遍历每个容器并获取状态
|
||||
for temp in container_list:
|
||||
container_name = dp.rename(temp['Names'][0].replace("/", "")) # 假设容器信息中包含名称字段
|
||||
container_status = {
|
||||
"name": container_name,
|
||||
"status": temp["State"],
|
||||
"group": ""
|
||||
}
|
||||
|
||||
|
||||
for group_name, group_order in group_containers.items():
|
||||
if container_name in group_order:
|
||||
container_status["group"] = group_name
|
||||
break
|
||||
container_status_list.append(container_status)
|
||||
|
||||
return public.returnResult(True, data=container_status_list)
|
||||
except Exception as e:
|
||||
return public.returnResult(True, data=[])
|
||||
459
class_v2/btdockerModelV2/dkgroupModel_.py
Normal file
459
class_v2/btdockerModelV2/dkgroupModel_.py
Normal file
@@ -0,0 +1,459 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: zhengweibiao
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# 容器项目编排
|
||||
# ------------------------------
|
||||
import os, sys, re, json, shutil, psutil, time
|
||||
import datetime
|
||||
import public
|
||||
from btdockerModelV2.containerModel import main as docker
|
||||
import subprocess
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class main():
|
||||
|
||||
next_id = 1 # 将 next_id 设为类变量,而不是实例变量
|
||||
def __init__(self):
|
||||
# self.next_id = 1
|
||||
pass
|
||||
|
||||
def load_project_data(self):
|
||||
json_file = "/www/server/panel/class_v2/btdockerModelV2/docker_project_groups.json"
|
||||
try:
|
||||
with open(json_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
data = []
|
||||
with open(json_file, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
return data
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def write_to_json(self, data):
|
||||
json_file = "/www/server/panel/class_v2/btdockerModelV2/docker_project_groups.json"
|
||||
try:
|
||||
with open(json_file, 'w') as f:
|
||||
json.dump(data, f)
|
||||
return True
|
||||
except Exception as e:
|
||||
print("写入失败!{}".format(e))
|
||||
return False
|
||||
|
||||
def get_project_groups(self, get):
|
||||
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
|
||||
# 获取项目列表
|
||||
project_list = docker().get_list(get)
|
||||
|
||||
# 更新每个项目组的状态
|
||||
for group in data:
|
||||
# 检查项目排序是否为空
|
||||
if not group['order']:
|
||||
group['status'] = 0 # 如果项目排序为空,状态为停止
|
||||
continue
|
||||
|
||||
all_running = True # 假设所有的项目都在运行
|
||||
for project in group['projects']:
|
||||
for p in project_list['container_list']:
|
||||
if p['name'] == project['project_name']:
|
||||
if p['status']!="running": # 如果项目没有运行
|
||||
all_running = False
|
||||
break
|
||||
if not all_running:
|
||||
break
|
||||
|
||||
if all_running:
|
||||
group['status'] = 1 # 如果所有的项目都在运行,状态为启动
|
||||
else:
|
||||
group['status'] = 0
|
||||
|
||||
return public.returnMsg(True, data)
|
||||
|
||||
def get_group_data(self, get):
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return None, public.returnMsg(False, "获取配置文件失败!")
|
||||
|
||||
project_list = docker().get_list(get)
|
||||
for group in data:
|
||||
if group['id'] == int(get.id):
|
||||
for project in group['projects']:
|
||||
|
||||
print(project)
|
||||
# print(project_list['container_list'])
|
||||
for p in project_list['container_list']:
|
||||
if p['name'] == project['project_name']:
|
||||
print(3333)
|
||||
print(p)
|
||||
project['status'] = p['status']
|
||||
return group, None
|
||||
return None, public.ReturnMsg(False, "项目不存在!")
|
||||
|
||||
except Exception as e:
|
||||
return None, public.returnMsg(False, "获取失败!" + str(e))
|
||||
|
||||
def get_project_details(self, get):
|
||||
print(33333)
|
||||
group, error_msg = self.get_group_data(get)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
|
||||
group['projects'].sort(
|
||||
key=lambda x: group['order'].index(x['project_name']))
|
||||
return public.returnMsg(True, group['projects'])
|
||||
|
||||
def get_project_group_details(self, get):
|
||||
group, error_msg = self.get_group_data(get)
|
||||
if error_msg:
|
||||
return error_msg
|
||||
return public.returnMsg(True, group)
|
||||
|
||||
def add_project_group(self, get):
|
||||
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
|
||||
# 检查是否已存在
|
||||
for group in data:
|
||||
if group['group_name'] == get.group_name:
|
||||
return public.returnMsg(False, "项目已存在!")
|
||||
|
||||
# 添加新的项目
|
||||
new_group = {
|
||||
"id": self.next_id, # 使用 next_id 作为新的 id
|
||||
"group_name": get.group_name,
|
||||
# "status": 0,
|
||||
"interval": 30,
|
||||
"projects": [],
|
||||
"order": [],
|
||||
}
|
||||
data.append(new_group)
|
||||
|
||||
# 更新 next_id
|
||||
|
||||
main.next_id += 1
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
|
||||
return public.returnMsg(True, "项目添加成功!")
|
||||
|
||||
except Exception as e:
|
||||
|
||||
return public.returnMsg(False, "添加失败!" + str(e))
|
||||
|
||||
def edit_project_order(self, get):
|
||||
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
# 找到指定的项目组
|
||||
for group in data:
|
||||
if group['id']==int(get.id):
|
||||
new_order=get.order.split(',')
|
||||
if sorted(new_order) != sorted(group['order']):
|
||||
return public.returnMsg(False, "无效的容器顺序!")
|
||||
group['order']=new_order
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
return public.returnMsg(True,"容器顺序修改成功!")
|
||||
return public.returnMsg(False,"项目不存在!")
|
||||
except Exception as e:
|
||||
return public.returnMsg(False,"修改失败:"+str(e))
|
||||
|
||||
def edit_group_interval(self, get):
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
|
||||
|
||||
# 找到指定的项目
|
||||
for group in data:
|
||||
if group['id'] == int(get.id):
|
||||
# 检查新的项目顺序是否有效
|
||||
group['interval'] = get.interval
|
||||
break
|
||||
else:
|
||||
return public.returnMsg(False, "项目不存在!")
|
||||
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
|
||||
return public.returnMsg(True, "项目时间间隔修改成功!")
|
||||
except Exception as e:
|
||||
return public.returnMsg(False, "修改失败!" + str(e))
|
||||
|
||||
def start_projects_in_order(self, get):
|
||||
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
project_list=docker().get_list(get)['container_list']
|
||||
|
||||
for group in data:
|
||||
if group['id']==int(get.id):
|
||||
# 如果pid存在并且进程仍在运行,那么就不允许用户再次启动项目
|
||||
if 'start_pid' in group and self.is_process_running(group['start_pid']):
|
||||
return public.returnMsg(False,"正在依次启动容器中!")
|
||||
|
||||
project_order=group['order']
|
||||
|
||||
running_projects=[project for project in project_order if self.is_project_running(project,project_list)]
|
||||
|
||||
if running_projects and not get.get("force_stop",False):
|
||||
return public.returnMsg(False,"以下容器正在运行:{}。是否允许先强制停止再启动?您也可以选择自己手动停止运行中的容器!".format(",".join(running_projects)))
|
||||
|
||||
with open('/dev/null','w') as devnull:
|
||||
process=subprocess.Popen(['btpython','/www/server/panel/script/set_docker_project_groups.py','--id',str(group['id']),"--action","start"],stdout=devnull,stderr=devnull)
|
||||
|
||||
|
||||
|
||||
pid=process.pid
|
||||
group['start_pid']=pid
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
|
||||
return public.returnMsg(True, "开始依次启动容器!")
|
||||
except Exception as e:
|
||||
return public.returnMsg(False, "启动失败!"+str(e))
|
||||
|
||||
def is_process_running(self,pid):
|
||||
try:
|
||||
os.kill(pid,0)
|
||||
except OSError:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
|
||||
def is_project_running(self,project_name,project_list):
|
||||
for project in project_list:
|
||||
if project['name']==project_name and project['status']=="running":
|
||||
return True
|
||||
return False
|
||||
|
||||
def stop_projects_in_order(self, get):
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
for group in data:
|
||||
if group['id']==int(get.id):
|
||||
# 如果pid存在并且进程仍在运行,那么就不允许用户再次停stop_pid止项目
|
||||
if 'stop_pid' in group and self.is_process_running(group['stop_pid']):
|
||||
return public.returnMsg(False,"正在依次停止容器中!")
|
||||
with open("/dev/null","w") as devnull:
|
||||
process=subprocess.Popen(['btpython','/www/server/panel/script/set_docker_project_groups.py','--id',str(group['id']),"--action","stop"],stdout=devnull,stderr=devnull)
|
||||
|
||||
pid=process.pid
|
||||
group['stop_pid']=pid
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
return public.returnMsg(True,"容器开始按顺序停止")
|
||||
|
||||
except Exception as e:
|
||||
return public.returnMsg(False, "停止失败!" + str(e))
|
||||
|
||||
|
||||
def start_group(self, args_id):
|
||||
|
||||
try:
|
||||
get = public.dict_obj()
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
# 找到指定的项目
|
||||
for group in data:
|
||||
if group['id'] == int(args_id):
|
||||
# 获取项目排序
|
||||
project_order = group['order']
|
||||
|
||||
# 停止所有项目
|
||||
for project_name in project_order:
|
||||
container_id = None
|
||||
for project in group['projects']:
|
||||
if project['project_name'] == project_name:
|
||||
container_id = project['project_id']
|
||||
break
|
||||
if container_id:
|
||||
# print()
|
||||
# docker().set_container_status(public.dict_obj({
|
||||
# "id": container_id,
|
||||
# "status": "stop"
|
||||
# }))
|
||||
print(33333333333333)
|
||||
get.status = "stop"
|
||||
get.id = container_id
|
||||
docker().set_container_status(get)
|
||||
# time.sleep(30)
|
||||
# 依次启动项目
|
||||
for project_name in project_order:
|
||||
container_id = None
|
||||
for project in group['projects']:
|
||||
if project['project_name'] == project_name:
|
||||
container_id = project['project_id']
|
||||
break
|
||||
if container_id:
|
||||
# print()
|
||||
# docker().set_container_status(public.dict_obj({
|
||||
# "id": container_id,
|
||||
# "status": "stop"
|
||||
# }))
|
||||
get.status = "start"
|
||||
get.id = container_id
|
||||
start_result=docker().set_container_status(get)
|
||||
|
||||
if not start_result['status']:
|
||||
return start_result # 如果启动失败,立即返回错误信息
|
||||
|
||||
# 暂停指定的时间间隔
|
||||
time.sleep(int(group['interval']))
|
||||
if not self.is_process_running(group['pid']):
|
||||
# 删除pid
|
||||
del group['pid']
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
except Exception as e:
|
||||
print("启动失败!" + str(e))
|
||||
|
||||
|
||||
def stop_group(self, args_id):
|
||||
|
||||
|
||||
try:
|
||||
get = public.dict_obj()
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
# 找到指定的项目
|
||||
for group in data:
|
||||
if group['id'] == int(args_id):
|
||||
# 获取项目排序
|
||||
project_order = group['order']
|
||||
|
||||
# 停止所有项目
|
||||
for project_name in project_order:
|
||||
container_id = None
|
||||
for project in group['projects']:
|
||||
if project['project_name'] == project_name:
|
||||
container_id = project['project_id']
|
||||
break
|
||||
if container_id:
|
||||
get.status = "stop"
|
||||
get.id = container_id
|
||||
docker().set_container_status(get)
|
||||
except Exception as e:
|
||||
print("启动失败!" + str(e))
|
||||
|
||||
def modify_group_status(self, get):
|
||||
group_ids=[int(id) for id in get.id.split(",")]
|
||||
|
||||
for group_id in group_ids:
|
||||
get.id=group_id
|
||||
if get.status=="1":
|
||||
return self.start_projects_in_order(get)
|
||||
elif get.status=="0":
|
||||
return self.stop_projects_in_order(get)
|
||||
else:
|
||||
return public.returnMsg(False,"无效的状态!")
|
||||
|
||||
|
||||
|
||||
def add_project_to_group(self, get):
|
||||
|
||||
try:
|
||||
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
# 检查项目是否已被其他项目组添加
|
||||
for group in data:
|
||||
for project in group['projects']:
|
||||
if project['project_name']==get.project_name:
|
||||
return public.returnMsg(False,"容器 {} 已经被项目组 {} 添加了!".format(get.project_name,group['group_name']))
|
||||
for group in data:
|
||||
if group['id']==int(get.id):
|
||||
new_project={
|
||||
"project_id":get.project_id,
|
||||
"project_name":get.project_name,
|
||||
|
||||
|
||||
}
|
||||
group['projects'].append(new_project)
|
||||
group['order'].append(get.project_name)
|
||||
break
|
||||
|
||||
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
|
||||
return public.returnMsg(True, "容器添加成功!")
|
||||
except Exception as e:
|
||||
return public.returnMsg(False, "添加失败!" + str(e))
|
||||
|
||||
def remove_project_from_group(self, get):
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
|
||||
# 将 get.project_name 分割成一个列表
|
||||
project_names=get.project_name.split(",")
|
||||
|
||||
# 找到指定的项目
|
||||
for group in data:
|
||||
if group['id']==int(get.id):
|
||||
# 删除指定的项目组
|
||||
group['projects']=[project for project in group['projects'] if project['project_name'] not in project_names]
|
||||
# 同时更新order列表,移除已删除的项目名称
|
||||
group['order'] = [project_name for project_name in group['order'] if project_name not in project_names]
|
||||
break
|
||||
else:
|
||||
return public.returnMsg(False,"项目不存在!")
|
||||
|
||||
# 将更新后的数据写回文件
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
|
||||
return public.returnMsg(True, "容器删除成功!")
|
||||
except Exception as e:
|
||||
return public.returnMsg(False, "删除失败!" + str(e))
|
||||
|
||||
def delete_project_group(self, get):
|
||||
|
||||
try:
|
||||
data = self.load_project_data()
|
||||
if data is None:
|
||||
return public.returnMsg(False, "获取配置文件失败")
|
||||
|
||||
# 将 get.id 分割成一个列表
|
||||
group_ids=[int(id) for id in get.id.split(",")]
|
||||
# 找到并删除指定的项目
|
||||
data=[group for group in data if group['id'] not in group_ids]
|
||||
# 将更新后的数据写回文件
|
||||
if not self.write_to_json(data):
|
||||
return public.returnMsg(False, "写入失败!")
|
||||
|
||||
return public.returnMsg(True, "项目删除成功!")
|
||||
except Exception as e:
|
||||
return public.returnMsg(False, "删除失败!" + str(e))
|
||||
117
class_v2/btdockerModelV2/dockerBase.py
Normal file
117
class_v2/btdockerModelV2/dockerBase.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import public, os
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
|
||||
|
||||
class dockerBase(object):
|
||||
|
||||
def __init__(self):
|
||||
# self._db_path = "/www/server/panel/data/db/docker.db"
|
||||
self._db_path = "/www/server/panel/data/docker.db"
|
||||
self._backup_log = '/tmp/backup.log'
|
||||
self._log_path = '/tmp/dockertmp.log'
|
||||
self._rCmd_log = '/tmp/dockerRun.log'
|
||||
self._url = "unix:///var/run/docker.sock"
|
||||
self.compose_path = "{}/data/compose".format(public.get_panel_path())
|
||||
self.aes_key = "btdockerModel_QWERAS"
|
||||
self.moinitor_lock = "/tmp/bt_docker_monitor.lock"
|
||||
|
||||
def get_ws_log(self, get):
|
||||
"""
|
||||
获取日志,websocket
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
if not hasattr(get, "_ws"):
|
||||
return True
|
||||
|
||||
import time
|
||||
sum = 0
|
||||
|
||||
with open(get._log_path, "r") as file:
|
||||
position = file.tell()
|
||||
get._ws.send(public.lang("{}\r\n",get.wsLogTitle))
|
||||
|
||||
while True:
|
||||
current_position = file.tell()
|
||||
line = file.readline()
|
||||
if current_position > position:
|
||||
file.seek(position)
|
||||
new_content = file.read(current_position - position)
|
||||
if "nohup" not in new_content:
|
||||
for i in new_content.split('\n'):
|
||||
if i == "": continue
|
||||
get._ws.send(i.strip("\n") + "\r\n")
|
||||
|
||||
position = current_position
|
||||
|
||||
if "bt_successful" in line:
|
||||
get._ws.send(public.lang("successful\r\n"))
|
||||
del get._ws
|
||||
break
|
||||
elif "bt_failed" in line:
|
||||
get._ws.send(public.lang("failed\r\n"))
|
||||
del get._ws
|
||||
break
|
||||
|
||||
if sum > 0:
|
||||
sum = 0
|
||||
else:
|
||||
sum += 1
|
||||
|
||||
if sum >= 6000:
|
||||
get._ws.send(public.lang("\r\nNo response for more than 10 minutes!\r\n"))
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
return True
|
||||
|
||||
# 命令行创建 拉取容器
|
||||
def run_cmd(self, get):
|
||||
"""
|
||||
命令行创建运行容器(docker run / docker pull),需要做危险命令校验,存在危险命令则不执行
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
import re
|
||||
if not hasattr(get, 'cmd'):
|
||||
return public.return_message(-1, 0, public.lang("Please pass in cmd"))
|
||||
|
||||
if "docker run" not in get.cmd and "docker pull" not in get.cmd:
|
||||
return public.return_message(-1, 0, public.lang("Only docker run or docker pull commands can be executed"))
|
||||
|
||||
danger_cmd = ['rm', 'rmi', 'kill', 'stop', 'pause', 'unpause', 'restart', 'update', 'exec', 'init',
|
||||
'shutdown', 'reboot', 'chmod', 'chown', 'dd', 'fdisk', 'killall', 'mkfs', 'mkswap', 'mount',
|
||||
'swapoff', 'swapon', 'umount', 'userdel', 'usermod', 'passwd', 'groupadd', 'groupdel',
|
||||
'groupmod', 'chpasswd', 'chage', 'usermod', 'useradd', 'userdel', 'pkill']
|
||||
|
||||
danger_symbol = ['&', '&&', '||', '|', ';']
|
||||
|
||||
for d in danger_cmd:
|
||||
if get.cmd.startswith(d) or re.search(r'\s{}\s'.format(d), get.cmd):
|
||||
return public.return_message(-1, 0, public.lang("Dangerous command exists: [ {}], execution is not allowed!", d))
|
||||
|
||||
for d in danger_symbol:
|
||||
if d in get.cmd:
|
||||
return public.return_message(-1, 0, public.lang("Dangerous symbol exists: [ {}], execution is not allowed!", d))
|
||||
|
||||
os.system("echo -n > {}".format(self._rCmd_log))
|
||||
os.system("nohup {} >> {} 2>&1 && echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &".format(
|
||||
get.cmd,
|
||||
self._rCmd_log,
|
||||
self._rCmd_log,
|
||||
self._rCmd_log,
|
||||
))
|
||||
return public.return_message(0, 0, public.lang("The command has been executed!"))
|
||||
0
class_v2/btdockerModelV2/dockerSock/__init__.py
Normal file
0
class_v2/btdockerModelV2/dockerSock/__init__.py
Normal file
143
class_v2/btdockerModelV2/dockerSock/container.py
Normal file
143
class_v2/btdockerModelV2/dockerSock/container.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# docker模型sock 封装库 容器库
|
||||
# -------------------------------------------------------------------
|
||||
import socket
|
||||
import select
|
||||
import json
|
||||
|
||||
import public
|
||||
from btdockerModelV2.dockerSock.sockBase import base
|
||||
|
||||
|
||||
class dockerContainer(base):
|
||||
def __init__(self):
|
||||
super(dockerContainer, self).__init__()
|
||||
|
||||
# 2024/3/13 上午 11:20 获取所有容器列表
|
||||
def get_container(self):
|
||||
'''
|
||||
@name 获取所有容器列表
|
||||
@author wzz <2024/3/13 上午 10:54>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/containers/json?all=1".format(self._sock, self.get_api_version()))[0])
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/containers/json?all=1".format(c, self._sock, self.get_api_version()))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
|
||||
# 2024/3/28 下午 11:37 获取指定容器的inspect
|
||||
def get_container_inspect(self, container_id: str):
|
||||
'''
|
||||
@name 获取指定容器的inspect
|
||||
@param container_id: 容器id
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/containers/{}/json"
|
||||
.format(self._sock, self.get_api_version(), container_id))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/containers/{}/json".format(c, self._sock, self.get_api_version(), container_id))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
|
||||
# 2025/1/18 11:05 构造返回日志
|
||||
def structure_docker_logs(self, log_data: str) -> list:
|
||||
"""
|
||||
@name: 清理 Docker 日志并转换为 JSON 格式
|
||||
@param: raw_logs: 原始日志字符串
|
||||
@return: 日志对象列表
|
||||
"""
|
||||
formatted_logs = []
|
||||
|
||||
# 逐行处理日志
|
||||
for line in log_data.split('\n'):
|
||||
line = line.strip()
|
||||
|
||||
if line:
|
||||
# 只处理包含日期时间戳的行,去除其它无用的字符
|
||||
parts = line.split(' ', 1) # 按第一个空格分割时间戳和日志内容
|
||||
if len(parts) > 1:
|
||||
timestamp = parts[0]
|
||||
message = parts[1]
|
||||
formatted_logs.append(message)
|
||||
|
||||
# 返回格式化后的日志
|
||||
return "\n".join(formatted_logs)
|
||||
|
||||
# 2025/1/18 10:26 获取容器日志
|
||||
def get_container_logs(self, container_id: str, options: dict) -> list:
|
||||
'''
|
||||
@name 获取容器日志
|
||||
@param container_id: 容器id
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
# 定义 Docker API 请求路径
|
||||
url = f"/containers/{container_id}/logs?stdout=true&stderr=true×tamps=true"
|
||||
|
||||
if options:
|
||||
if "since" in options and options["since"] != "":
|
||||
url += "&since={}".format(options['since'])
|
||||
if "until" in options and options["until"] != "":
|
||||
url += "&until={}".format(options['until'])
|
||||
if "tail" in options and options["tail"] != "":
|
||||
url += "&tail={}".format(options['tail'])
|
||||
|
||||
# 设置 Unix socket 地址
|
||||
unix_socket = "/var/run/docker.sock"
|
||||
|
||||
# 连接到 Docker 的 UNIX socket
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client_socket:
|
||||
client_socket.settimeout(1)
|
||||
client_socket.connect(unix_socket)
|
||||
|
||||
# 构建 HTTP 请求
|
||||
http_request = f"GET {url} HTTP/1.1\r\n"
|
||||
http_request += "Host: localhost\r\n"
|
||||
http_request += "Accept: application/json\r\n"
|
||||
http_request += "Content-Type: application/json\r\n"
|
||||
http_request += "Connection: close\r\n\r\n"
|
||||
|
||||
# 发送请求
|
||||
client_socket.sendall(http_request.encode())
|
||||
|
||||
# 接收响应
|
||||
response = b""
|
||||
while True:
|
||||
data = client_socket.recv(4096)
|
||||
if not data:
|
||||
break
|
||||
response += data
|
||||
|
||||
# 从响应中分割出 HTTP 头部和响应体
|
||||
headers, body = response.split(b"\r\n\r\n", 1)
|
||||
|
||||
# 只保留日志部分,解码并忽略无法解码的字符
|
||||
log_data = body.decode('utf-8', errors='replace')
|
||||
|
||||
result = self.structure_docker_logs(log_data)
|
||||
|
||||
# 返回日志内容
|
||||
return result
|
||||
108
class_v2/btdockerModelV2/dockerSock/image.py
Normal file
108
class_v2/btdockerModelV2/dockerSock/image.py
Normal file
@@ -0,0 +1,108 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# docker模型sock 封装库 镜像库
|
||||
# -------------------------------------------------------------------
|
||||
import json
|
||||
|
||||
import public
|
||||
from btdockerModelV2.dockerSock.sockBase import base
|
||||
|
||||
|
||||
class dockerImage(base):
|
||||
def __init__(self):
|
||||
super(dockerImage, self).__init__()
|
||||
|
||||
# 2024/3/13 上午 11:20 获取所有镜像列表
|
||||
def get_images(self):
|
||||
'''
|
||||
@name 获取所有镜像列表
|
||||
@author wzz <2024/3/13 上午 10:54>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/json?all=1"
|
||||
.format(self._sock, self.get_api_version()))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/images/json?all=1".format(c, self._sock, self.get_api_version()))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
|
||||
# 2023/12/13 上午 11:08 镜像搜索
|
||||
def search(self, name):
|
||||
'''
|
||||
@name 镜像搜索
|
||||
@author wzz <2023/12/13 下午 3:41>
|
||||
@param 参数名<数据类型> 参数描述
|
||||
@return 数据类型
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/search?term={}"
|
||||
.format(self._sock, self.get_api_version(), name))[0],)
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/images/search?term={}".format(c, self._sock, self.get_api_version(), name))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
|
||||
# 2024/4/1 下午 2:47 image load
|
||||
def load_image(self, path):
|
||||
'''
|
||||
@name 加载镜像
|
||||
@param path <str> 镜像名称
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} -X POST http:/{}/images/load -H \"Content-Type: application/x-tar\" --upload-file {}".format(self._sock, self.get_api_version(), path))[0])
|
||||
except Exception as e:
|
||||
return {}
|
||||
|
||||
# 2024/4/16 上午11:39 获取指定image的inspect信息
|
||||
def inspect(self, image):
|
||||
'''
|
||||
@name 获取指定image的inspect信息
|
||||
@param image <str> 镜像名称
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/images/{}/json"
|
||||
.format(self._sock, self.get_api_version(), image))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/images/{}/json".format(c, self._sock, self.get_api_version(), image))
|
||||
if not err: return json.loads(res)
|
||||
return {}
|
||||
except:
|
||||
return {}
|
||||
|
||||
# 2024/10/31 22:43 清理所有构建缓存
|
||||
def build_prune(self):
|
||||
'''
|
||||
@name 清理所有构建缓存
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} -X POST http:/{}/build/prune?all=1"
|
||||
.format(self._sock, self.get_api_version()))[0])
|
||||
except Exception as e:
|
||||
return False
|
||||
91
class_v2/btdockerModelV2/dockerSock/network.py
Normal file
91
class_v2/btdockerModelV2/dockerSock/network.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# docker模型sock 封装库 网络库
|
||||
# -------------------------------------------------------------------
|
||||
import json
|
||||
|
||||
import public
|
||||
from btdockerModelV2.dockerSock.sockBase import base
|
||||
|
||||
|
||||
class dockerNetWork(base):
|
||||
def __init__(self):
|
||||
super(dockerNetWork, self).__init__()
|
||||
|
||||
# 2024/11/27 14:31 list network
|
||||
def list_network(self):
|
||||
'''
|
||||
@name list network
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/networks".format(self._sock, self.get_api_version()))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/networks".format(c, self._sock, self.get_api_version()))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
|
||||
# 2024/11/27 14:33 inspect a network
|
||||
def inspect_network(self, get):
|
||||
'''
|
||||
@name inspect a network
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/networks/{}".format(self._sock, self.get_api_version(), get.id))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/networks/{}".format(c, self._sock, self.get_api_version(), get.id))
|
||||
if not err: return json.loads(res)
|
||||
return {}
|
||||
except:
|
||||
return {}
|
||||
|
||||
# 2024/11/27 14:34 remove network
|
||||
def remove_network(self, get):
|
||||
'''
|
||||
@name remove network
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s -X DELETE --unix-socket {} http:/{}/networks/{}".format(self._sock, self.get_api_version(), get.id))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s -X DELETE --unix-socket {} http:/{}/networks/{}".format(c, self._sock, self.get_api_version(), get.id))
|
||||
if not err: return json.loads(res)
|
||||
return {}
|
||||
except:
|
||||
return {}
|
||||
|
||||
# 2024/11/27 14:34 create network
|
||||
def create_network(self, get):
|
||||
'''
|
||||
@name create network
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s -X POST --unix-socket {} http:/{}/networks/create -H \"Content-Type: application/json\" -d '{}'".format(self._sock, self.get_api_version(), json.dumps(get.post_data)))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s -X POST --unix-socket {} http:/{}/networks/create -H \"Content-Type: application/json\" -d '{}'".format(c, self._sock, self.get_api_version(), json.dumps(get.post_data)))
|
||||
if not err: return json.loads(res)
|
||||
return {}
|
||||
except:
|
||||
return {}
|
||||
19
class_v2/btdockerModelV2/dockerSock/sockBase.py
Normal file
19
class_v2/btdockerModelV2/dockerSock/sockBase.py
Normal file
@@ -0,0 +1,19 @@
|
||||
class dockerSock(object):
|
||||
def __init__(self):
|
||||
self._sock = "/var/run/docker.sock"
|
||||
self._url = "unix://{}".format(self._sock)
|
||||
self._api_version = "/127.0.0.1"
|
||||
|
||||
def get_sock(self):
|
||||
return self._sock
|
||||
|
||||
def get_url(self):
|
||||
return self._url
|
||||
|
||||
def get_api_version(self):
|
||||
return self._api_version
|
||||
|
||||
|
||||
class base(dockerSock):
|
||||
def __init__(self):
|
||||
super(base, self).__init__()
|
||||
39
class_v2/btdockerModelV2/dockerSock/system.py
Normal file
39
class_v2/btdockerModelV2/dockerSock/system.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# docker模型sock 封装库 系统信息库
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
|
||||
import json
|
||||
import public
|
||||
from btdockerModelV2.dockerSock.sockBase import base
|
||||
|
||||
|
||||
class dockerSystem(base):
|
||||
def __init__(self):
|
||||
super(dockerSystem, self).__init__()
|
||||
|
||||
# 2024/12/16 17:47 获取系统信息
|
||||
def get_system_info(self):
|
||||
'''
|
||||
@name 获取系统信息
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/info".format(self._sock, self.get_api_version()))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/info".format(c, self._sock, self.get_api_version()))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
|
||||
41
class_v2/btdockerModelV2/dockerSock/volume.py
Normal file
41
class_v2/btdockerModelV2/dockerSock/volume.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# docker模型sock 封装库 存储库
|
||||
# -------------------------------------------------------------------
|
||||
import json
|
||||
|
||||
import public
|
||||
from btdockerModelV2.dockerSock.sockBase import base
|
||||
|
||||
|
||||
class dockerVolume(base):
|
||||
def __init__(self):
|
||||
super(dockerVolume, self).__init__()
|
||||
|
||||
# 2024/3/13 上午 11:20 获取所有存储卷列表
|
||||
def get_volumes(self):
|
||||
'''
|
||||
@name 获取所有存储卷列表
|
||||
@author wzz <2024/3/13 上午 10:54>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
return json.loads(public.ExecShell("curl -s --unix-socket {} http:/{}/volumes"
|
||||
.format(self._sock, self.get_api_version()))[0])
|
||||
except Exception as e:
|
||||
try:
|
||||
c_list = public.ExecShell("whereis curl | awk 'print {$1}'")[0].split(" ")
|
||||
for c in c_list:
|
||||
if not c.endswith("/curl"): continue
|
||||
res, err = public.ExecShell("{} -s --unix-socket {} http:/{}/volumes".format(c, self._sock, self.get_api_version()))
|
||||
if not err: return json.loads(res)
|
||||
return []
|
||||
except:
|
||||
return []
|
||||
60
class_v2/btdockerModelV2/host.py
Normal file
60
class_v2/btdockerModelV2/host.py
Normal file
@@ -0,0 +1,60 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: zouhw <zhw@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import public
|
||||
import dk_public as dp
|
||||
|
||||
class main:
|
||||
|
||||
# 获取docker主机列表
|
||||
def get_list(self,args=None):
|
||||
info = dp.sql("hosts").select()
|
||||
for i in info:
|
||||
if dp.docker_client(i['url']):
|
||||
i['status'] = True
|
||||
else:
|
||||
i['status'] = False
|
||||
return info
|
||||
|
||||
# 添加docker主机
|
||||
def add(self,args):
|
||||
"""
|
||||
:param url 连接主机的url
|
||||
:param remark 主机备注
|
||||
:return:
|
||||
"""
|
||||
import time
|
||||
host_lists = self.get_list()
|
||||
for h in host_lists:
|
||||
if h['url'] == args.url:
|
||||
return public.returnMsg(False, public.lang("The host already exists!"))
|
||||
# 测试连接
|
||||
if not dp.docker_client(args.url):
|
||||
return public.returnMsg(False, public.lang("Failed to connect to the server, please check if docker is started!"))
|
||||
pdata = {
|
||||
"url": args.url,
|
||||
"remark": public.xsssec(args.remark),
|
||||
"time": int(time.time())
|
||||
}
|
||||
dp.write_log("Add host [{}] successful!".format(args.url))
|
||||
dp.sql('hosts').insert(pdata)
|
||||
return public.returnMsg(True, public.lang("Add docker host successfully!"))
|
||||
|
||||
def delete(self,args):
|
||||
"""
|
||||
:param id 连接主机id
|
||||
:return:
|
||||
"""
|
||||
data = dp.sql('hosts').where('id=?',args(args.id,)).find()
|
||||
dp.sql('hosts').delete(id=args.id)
|
||||
dp.write_log("Delete host [{}] successful!".format(data['url']))
|
||||
return public.returnMsg(True, public.lang("Delete host successfully!"))
|
||||
1079
class_v2/btdockerModelV2/imageModel.py
Normal file
1079
class_v2/btdockerModelV2/imageModel.py
Normal file
File diff suppressed because it is too large
Load Diff
129
class_v2/btdockerModelV2/monitorModel.py
Normal file
129
class_v2/btdockerModelV2/monitorModel.py
Normal file
@@ -0,0 +1,129 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: zouhw <zhw@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
sys.path.insert(0, "/www/server/panel/class_v2/")
|
||||
sys.path.insert(1, "/www/server/panel/")
|
||||
import public
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2 import containerModel as dc
|
||||
from btdockerModelV2 import statusModel as ds
|
||||
from btdockerModelV2 import imageModel as di
|
||||
from public.validate import Param
|
||||
|
||||
class main:
|
||||
__save_date = None
|
||||
__day_sec = 86400
|
||||
|
||||
def __init__(self, save_date):
|
||||
if not save_date:
|
||||
self.__save_date = 30
|
||||
else:
|
||||
self.__save_date = save_date
|
||||
|
||||
def docker_client(self, url):
|
||||
return dp.docker_client(url)
|
||||
|
||||
def get_all_host_stats(self, fun):
|
||||
"""
|
||||
获取所有主机信息并获取该主机下的容器状态
|
||||
:param fun: 需要调用的方法,用于获取并记录容器状态
|
||||
:return:
|
||||
"""
|
||||
hosts = dp.sql('hosts').select()
|
||||
for i in hosts:
|
||||
t = threading.Thread(target=fun, args=(i,))
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
|
||||
# 获取所有docker容器的状态信息
|
||||
def container_status_for_all_hosts(self):
|
||||
"""
|
||||
获取所有服务器的容器数量
|
||||
:return:
|
||||
"""
|
||||
# while True:
|
||||
args = public.to_dict_obj({})
|
||||
container_list = dc.main().get_list(args)['message']
|
||||
for c in container_list['container_list']:
|
||||
args.id = c['id']
|
||||
args.write = 1
|
||||
args.save_date = self.__save_date
|
||||
ds.main().stats(args)
|
||||
# time.sleep(60)
|
||||
|
||||
# 获取所有服务器的容器数量
|
||||
def container_count(self):
|
||||
# while True:
|
||||
hosts = dp.sql('hosts').select()
|
||||
n = 0
|
||||
for i in hosts:
|
||||
args = public.to_dict_obj({})
|
||||
args.url = i['url']
|
||||
container_list = dc.main().get_list(args)['message']
|
||||
n += len(container_list)
|
||||
pdata = {
|
||||
"time": int(time.time()),
|
||||
"container_count": n
|
||||
}
|
||||
expired = time.time() - (self.__save_date * self.__day_sec)
|
||||
dp.sql("container_count").where("time<?", (expired,)).delete()
|
||||
dp.sql("container_count").insert(pdata)
|
||||
# time.sleep(60)
|
||||
|
||||
def image_for_all_host(self):
|
||||
# while True:
|
||||
hosts = dp.sql('hosts').select()
|
||||
num = 0
|
||||
size = 0
|
||||
for i in hosts:
|
||||
args = public.to_dict_obj({})
|
||||
args.url = i['url']
|
||||
res = di.main().image_for_host(args)['message']
|
||||
num += res['num']
|
||||
size += res['size']
|
||||
pdata = {
|
||||
"time": int(time.time()),
|
||||
"num": num,
|
||||
"size": int(size)
|
||||
}
|
||||
expired = time.time() - (self.__save_date * self.__day_sec)
|
||||
dp.sql("image_infos").where("time<?", (expired,)).delete()
|
||||
dp.sql("image_infos").insert(pdata)
|
||||
# time.sleep(60)
|
||||
|
||||
|
||||
def monitor():
|
||||
# 获取所有容器信息
|
||||
while True:
|
||||
save_date = dp.docker_conf()['SAVE']
|
||||
m = main(save_date)
|
||||
m.get_all_host_stats(m.container_status_for_all_hosts)
|
||||
# 开始获取容器总数
|
||||
t = threading.Thread(target=m.container_count)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
# 获取镜像详情
|
||||
t = threading.Thread(target=m.image_for_all_host)
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
time.sleep(60)
|
||||
# condition=threading.Condition()
|
||||
# condition.acquire()
|
||||
# condition.wait()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
monitor()
|
||||
384
class_v2/btdockerModelV2/networkModel.py
Normal file
384
class_v2/btdockerModelV2/networkModel.py
Normal file
@@ -0,0 +1,384 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
import docker.errors
|
||||
|
||||
import gettext
|
||||
_ = gettext.gettext
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import public
|
||||
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
|
||||
def docker_client(self, url):
|
||||
return dp.docker_client(url)
|
||||
|
||||
def get_network_id(self, get):
|
||||
"""
|
||||
asdf
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
networks = self.docker_client(self._url).networks
|
||||
network = networks.get(get.id)
|
||||
return network.attrs
|
||||
|
||||
def get_host_network(self, get):
|
||||
"""
|
||||
获取服务器的docker网络
|
||||
:param get:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
client = self.docker_client(self._url)
|
||||
if not client:
|
||||
return public.return_message(-1, 0, [])
|
||||
|
||||
networks = client.networks
|
||||
network_attr = self.get_network_attr(networks)
|
||||
|
||||
data = list()
|
||||
|
||||
for attr in network_attr:
|
||||
get.id = attr["Id"]
|
||||
c_result = self.get_network_id(get)
|
||||
subnet = ""
|
||||
gateway = ""
|
||||
subnetv6 = ""
|
||||
gatewayv6 = ""
|
||||
|
||||
if attr["IPAM"]["Config"]:
|
||||
if "Subnet" in attr["IPAM"]["Config"][0]:
|
||||
subnet = attr["IPAM"]["Config"][0]["Subnet"]
|
||||
if "Gateway" in attr["IPAM"]["Config"][0]:
|
||||
gateway = attr["IPAM"]["Config"][0]["Gateway"]
|
||||
|
||||
if len(attr["IPAM"]["Config"]) > 1:
|
||||
if "Subnet" in attr["IPAM"]["Config"][1]:
|
||||
subnetv6 = attr["IPAM"]["Config"][1]["Subnet"]
|
||||
if "Gateway" in attr["IPAM"]["Config"][1]:
|
||||
gatewayv6 = attr["IPAM"]["Config"][1]["Gateway"]
|
||||
|
||||
tmp = {
|
||||
"id": attr["Id"],
|
||||
"name": attr["Name"],
|
||||
"time": dp.convert_timezone_str_to_timestamp(attr["Created"]),
|
||||
"driver": attr["Driver"],
|
||||
"subnet": subnet,
|
||||
"gateway": gateway,
|
||||
"subnetv6": subnetv6,
|
||||
"gatewayv6": gatewayv6,
|
||||
"labels": attr["Labels"],
|
||||
"used": 1 if c_result["Containers"] else 0,
|
||||
"containers": c_result["Containers"],
|
||||
}
|
||||
data.append(tmp)
|
||||
|
||||
return public.return_message(0, 0, sorted(data, key=lambda x: x['time'], reverse=True))
|
||||
except Exception as e:
|
||||
err = str(e)
|
||||
if "Connection reset by peer" in err:
|
||||
return public.return_message(-1, 0, public.lang("The docker service is running abnormally, please restart and try again!"))
|
||||
return public.return_message(-1, 0, [])
|
||||
|
||||
def get_network_attr(self, networks):
|
||||
network = networks.list()
|
||||
return [i.attrs for i in network]
|
||||
|
||||
def add(self, get):
|
||||
"""
|
||||
:param name 网络名称
|
||||
:param driver bridge/ipvlan/macvlan/overlay
|
||||
:param options Driver options as a key-value dictionary
|
||||
:param subnet '124.42.0.0/16'
|
||||
:param gateway '124.42.0.254'
|
||||
:param iprange '124.42.0.0/24'
|
||||
:param labels Map of labels to set on the network. Default None.
|
||||
:param remarks 备注
|
||||
:param get:
|
||||
:return:
|
||||
"""
|
||||
# {"name": "23sdff223f", "driver": "overlay", "options": "", "subnet": "192.168.13.0/24",
|
||||
# "gateway": "192.168.13.1", "iprange": "192.168.13.0/24", "labels": ""}
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('name').Require().String(),
|
||||
Param('subnet').Require(),
|
||||
Param('gateway').Require(),
|
||||
Param('iprange').Require(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
import docker
|
||||
|
||||
# 传参 给默认值
|
||||
subnet = get.get("subnet", "")
|
||||
gateway = get.get("gateway", "")
|
||||
iprange = get.get("iprange", "")
|
||||
subnet_v6 = get.get("subnet_v6", "")
|
||||
gateway_v6 = get.get("gateway_v6", "")
|
||||
v6_status = get.get("status/d", 0)
|
||||
|
||||
ipam_pool4 = docker.types.IPAMPool(
|
||||
subnet=subnet,
|
||||
gateway=gateway,
|
||||
iprange=iprange
|
||||
)
|
||||
|
||||
if v6_status != 0:
|
||||
ipam_pool6 = docker.types.IPAMPool(
|
||||
subnet=subnet_v6,
|
||||
gateway=gateway_v6,
|
||||
)
|
||||
ipam_config = docker.types.IPAMConfig(
|
||||
pool_configs=[ipam_pool4, ipam_pool6]
|
||||
)
|
||||
else:
|
||||
ipam_config = docker.types.IPAMConfig(
|
||||
pool_configs=[ipam_pool4]
|
||||
)
|
||||
|
||||
try:
|
||||
self.docker_client(self._url).networks.create(
|
||||
name=get.name,
|
||||
options=dp.set_kv(get.options),
|
||||
driver=get.driver, # 使用用户指定的网络驱动类型
|
||||
ipam=ipam_config,
|
||||
enable_ipv6=v6_status,
|
||||
)
|
||||
except docker.errors.APIError as e:
|
||||
print(str(e))
|
||||
if "failed to allocate gateway" in str(e):
|
||||
return public.return_message(-1, 0,
|
||||
public.lang("The gateway setting is wrong, Please enter a gateway that matches the subnet: {}", get.subnet))
|
||||
if "invalid CIDR address" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Subnet address format error, please enter for example: 172.16.0.0/16"))
|
||||
if "invalid Address SubPool" in str(e):
|
||||
return public.return_message(-1, 0,
|
||||
public.lang("IP range format error, please enter the appropriate IP range for this subnet:", get.subnet))
|
||||
if "Pool overlaps with other one on this address space" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("IP range [ {}] already exists!", get.subnet))
|
||||
if "kernel version failed to meet the minimum ipvlan kernel requirement" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("The system kernel version is too low, please update the kernel or choose another network mode"))
|
||||
if "not a swarm manager" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("The current node is not a Swarm and needs to be configured before it can be used"))
|
||||
return public.return_message(-1, 0, public.lang("Failed to add network! {}", str(e)))
|
||||
|
||||
dp.write_log("Added network [{}] [{}] successful!".format(get.name, get.iprange))
|
||||
return public.return_message(0, 0, public.lang("Added network successfully!"))
|
||||
|
||||
def del_network(self, get):
|
||||
"""
|
||||
:param id
|
||||
:param get:
|
||||
:return:
|
||||
"""
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('id').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
try:
|
||||
networks = self.docker_client(self._url).networks.get(get.id)
|
||||
attrs = networks.attrs
|
||||
if attrs['Name'] in ["bridge", "none"]:
|
||||
return public.return_message(-1, 0, public.lang("The system default network cannot be deleted!"))
|
||||
|
||||
networks.remove()
|
||||
dp.write_log("Delete network [{}] successfully!".format(attrs['Name']))
|
||||
return public.return_message(0, 0, public.lang("successfully delete!"))
|
||||
|
||||
except docker.errors.APIError as e:
|
||||
if " has active endpoints" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("The network cannot be deleted while it is in use!"))
|
||||
return public.return_message(-1, 0, public.lang("Delete failed! {}", str(e)))
|
||||
|
||||
def prune(self, get):
|
||||
"""
|
||||
删除无用的网络
|
||||
:param get:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
res = self.docker_client(self._url).networks.prune()
|
||||
if not res['NetworksDeleted']:
|
||||
return public.return_message(-1, 0, public.lang("There are no useless networks!"))
|
||||
|
||||
dp.write_log("Delete useless network successfully!")
|
||||
return public.return_message(0, 0, public.lang("successfully delete!"))
|
||||
|
||||
except docker.errors.APIError as e:
|
||||
return public.return_message(-1, 0, public.lang("Delete failed! {}", str(e)))
|
||||
|
||||
def disconnect(self, get):
|
||||
"""
|
||||
断开某个容器的网络
|
||||
:param id
|
||||
:param container_id
|
||||
:param get:
|
||||
:return:
|
||||
"""
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('id').Require().String(),
|
||||
Param('container_id').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
try:
|
||||
get.id = get.get("id/s", "")
|
||||
get.container_id = get.get("container_id/s", "")
|
||||
if get.id == "":
|
||||
return public.return_message(-1, 0, public.lang("Network ID cannot be empty"))
|
||||
if get.container_id == "":
|
||||
return public.return_message(-1, 0, public.lang("Container ID cannot be empty"))
|
||||
|
||||
networks = self.docker_client(self._url).networks.get(get.id)
|
||||
networks.disconnect(get.container_id)
|
||||
dp.write_log("Network disconnection [{}] successful!".format(get.id))
|
||||
return public.return_message(0, 0, public.lang("Network disconnection was successful!"))
|
||||
except docker.errors.APIError as e:
|
||||
if "No such container" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Container ID: {}, does not exist!", get.container_id))
|
||||
if "network" in str(e) and "Not Found" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Network ID: {}, does not exist!", get.id))
|
||||
return public.return_message(-1, 0, public.lang("Network disconnection failed! {}", str(e)))
|
||||
|
||||
def connect(self, get):
|
||||
"""
|
||||
连接到指定网络
|
||||
:param id
|
||||
:param container_id
|
||||
:param get:
|
||||
:return:
|
||||
"""
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('id').Require().String(),
|
||||
Param('container_id').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
try:
|
||||
networks = self.docker_client(self._url).networks.get(get.id)
|
||||
networks.connect(get.container_id)
|
||||
dp.write_log("Network connection [{}] successful!".format(get.id))
|
||||
return public.return_message(0, 0, public.lang("Network connection successful!"))
|
||||
except docker.errors.APIError as e:
|
||||
if "No such container" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Container ID: {}, does not exist!", get.container_id))
|
||||
if "network" in str(e) and "Not Found" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Network ID: {}, does not exist!", get.id))
|
||||
return public.return_message(-1, 0, public.lang("Failed to connect to network! {}", str(e)))
|
||||
# 2024/11/27 14:37 创建网络
|
||||
def create_network(self, get):
|
||||
'''
|
||||
@name 创建网络
|
||||
'''
|
||||
get.subnet = get.get("subnet", "")
|
||||
get.gateway = get.get("gateway", "")
|
||||
get.iprange = get.get("iprange", "")
|
||||
get.subnet_v6 = get.get("subnet_v6", "")
|
||||
get.gateway_v6 = get.get("gateway_v6", "")
|
||||
get.v6_status = get.get("status", 0)
|
||||
|
||||
get.name = get.get("name", None)
|
||||
if get.name is None: return public.return_message(-1, 0, public.lang("The network name cannot be empty!"))
|
||||
|
||||
get.driver = get.get("driver", "bridge")
|
||||
get.options = get.get("options", "")
|
||||
|
||||
ipam_pool4 = {}
|
||||
ipam_pool6 = {}
|
||||
if get.subnet != "":
|
||||
if get.gateway == "": return public.return_message(-1, 0, public.lang("Gateways can't be empty!"))
|
||||
if get.iprange == "": return public.return_message(-1, 0, public.lang("IP ranges cannot be empty!"))
|
||||
|
||||
ipam_pool4 = {
|
||||
"subnet": get.subnet,
|
||||
"gateway": get.gateway,
|
||||
"iprange": get.iprange
|
||||
}
|
||||
|
||||
if get.v6_status != 0:
|
||||
ipam_pool6 = {
|
||||
"subnet": get.subnet_v6,
|
||||
"gateway": get.gateway_v6,
|
||||
}
|
||||
|
||||
if not ipam_pool4 and not ipam_pool6:
|
||||
get.ipam = None
|
||||
elif not ipam_pool6:
|
||||
get.ipam = {
|
||||
"Driver": "default",
|
||||
"Config": [{
|
||||
"Subnet": get.subnet,
|
||||
"Gateway": get.gateway,
|
||||
"IPRange": get.iprange
|
||||
}]
|
||||
}
|
||||
else:
|
||||
get.ipam = {
|
||||
"Driver": "default",
|
||||
"Config": [{
|
||||
"Subnet": get.subnet,
|
||||
"Gateway": get.gateway,
|
||||
"IPRange": get.iprange
|
||||
}, {
|
||||
"Subnet": get.subnet_v6,
|
||||
"Gateway": get.gateway_v6,
|
||||
}]
|
||||
}
|
||||
|
||||
get.post_data = {
|
||||
"name": get.name,
|
||||
"options": None,
|
||||
"driver": get.driver,
|
||||
"ipam": get.ipam,
|
||||
"enable_ipv6": bool(get.v6_status),
|
||||
}
|
||||
|
||||
from btdockerModelV2.dockerSock import network
|
||||
sk_network = network.dockerNetWork()
|
||||
create_network = sk_network.create_network(get)
|
||||
|
||||
if not create_network:
|
||||
return public.return_message(-1, 0, public.lang("Creating a network failed!"))
|
||||
if "message" in create_network:
|
||||
if "already exists" in create_network["message"]:
|
||||
return public.return_message(-1, 0, public.lang("Network name: [{}] already exists!".format(get.name)))
|
||||
return public.return_message(-1, 0, create_network["message"])
|
||||
return public.return_message(0, 0, public.lang("Create a network successfully!"))
|
||||
518
class_v2/btdockerModelV2/projectModel.py
Normal file
518
class_v2/btdockerModelV2/projectModel.py
Normal file
@@ -0,0 +1,518 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2014-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import public
|
||||
import os
|
||||
import time
|
||||
import json
|
||||
import re
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2 import setupModel as ds
|
||||
from btdockerModelV2 import volumeModel as dv
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
|
||||
compose_path = "{}/data/compose".format(public.get_panel_path())
|
||||
project_path = "/www/dk_project"
|
||||
templates_path = "{}/templates".format(project_path)
|
||||
config_path = "{}/config".format(public.get_panel_path())
|
||||
info_path = "{}/docker_project_info.json".format(config_path)
|
||||
__first_pl = "{}/first.pl".format(project_path)
|
||||
|
||||
def __init__(self):
|
||||
self.log_file = "/tmp/dk_project_run.log"
|
||||
self.docker_setup = ds.main()
|
||||
if not os.path.exists(self.templates_path): os.system("mkdir -p {}".format(self.templates_path))
|
||||
self.compose_cmd = "/usr/bin/docker-compose" if self.docker_setup.check_docker_compose_service()[0] \
|
||||
else "/usr/local/bin/docker-compose"
|
||||
|
||||
def __check_conf(self, filename):
|
||||
'''
|
||||
验证配置文件是否可执行
|
||||
@param filename: docker-compose.yml文件路劲
|
||||
@return:
|
||||
'''
|
||||
return public.ExecShell("{} -f {} config".format(self.compose_cmd, filename))
|
||||
|
||||
def sync_item(self, get):
|
||||
'''
|
||||
同步官方可以一键部署的项目
|
||||
@param get: 空对象
|
||||
@return:
|
||||
'''
|
||||
os.remove(self.info_path)
|
||||
project_info = self._get_project_list(get)
|
||||
failed_list = []
|
||||
successes_list = []
|
||||
for info in project_info:
|
||||
if info["server_name"]:
|
||||
down_project_yml = self.__download_project_yml(info["server_name"])
|
||||
if not down_project_yml["status"]:
|
||||
failed_list.append(info["server_name"])
|
||||
continue
|
||||
successes_list.append(info["server_name"])
|
||||
data = [{"successes": len(successes_list), "server_name": successes_list},
|
||||
{"failed": len(failed_list), "server_name": failed_list}]
|
||||
return public.return_message(0, 0, data)
|
||||
|
||||
def __first_sync_item(self, project_info):
|
||||
'''
|
||||
同步官方可以一键部署的项目
|
||||
@param get: 空对象
|
||||
@return:
|
||||
'''
|
||||
failed_list = []
|
||||
successes_list = []
|
||||
for info in project_info:
|
||||
if info["server_name"]:
|
||||
down_project_yml = self.__download_project_yml(info["server_name"])
|
||||
if not down_project_yml["status"]:
|
||||
failed_list.append(info["server_name"])
|
||||
continue
|
||||
successes_list.append(info["server_name"])
|
||||
data = [{"successes": len(successes_list), "server_name": successes_list},
|
||||
{"failed": len(failed_list), "server_name": failed_list}]
|
||||
return data
|
||||
|
||||
def get_project_list(self, get):
|
||||
'''
|
||||
获取支持一键部署的项目列表
|
||||
@param get:
|
||||
@return:
|
||||
'''
|
||||
project_info = self._get_project_list(get)
|
||||
|
||||
return public.return_message(0, 0, project_info)
|
||||
|
||||
def _get_project_list(self, get):
|
||||
'''
|
||||
获取支持一键部署的项目列表
|
||||
@param get:
|
||||
@return:
|
||||
'''
|
||||
project_info = []
|
||||
try:
|
||||
if not os.path.exists(self.info_path):
|
||||
down_info = self.__download_info(self.info_path)
|
||||
if not down_info["status"]:
|
||||
return project_info
|
||||
|
||||
project_info = json.loads(public.readFile(self.info_path))
|
||||
project_info.sort(key=lambda x: x["sort"])
|
||||
|
||||
if not os.path.exists(self.__first_pl):
|
||||
sync_result = self.__first_sync_item(project_info)
|
||||
for result in sync_result:
|
||||
if result.get("successes") and result["successes"] <= 0:
|
||||
return project_info
|
||||
public.ExecShell("echo \"first\" > {}".format(self.__first_pl))
|
||||
|
||||
except Exception as e:
|
||||
project_info = []
|
||||
|
||||
return project_info
|
||||
|
||||
def __get_docker_status(self, args):
|
||||
'''
|
||||
获取docker安装和启动状态
|
||||
@param args:
|
||||
@return:
|
||||
'''
|
||||
return {
|
||||
"installed": self.docker_setup.check_docker_compose_service(),
|
||||
"service_status": self.docker_setup.get_service_status()
|
||||
}
|
||||
|
||||
def __download_info(self, info_path):
|
||||
'''
|
||||
下载版本信息: info.json
|
||||
@param info_path: string info.json文件的路劲
|
||||
@return:
|
||||
'''
|
||||
url = "{}/install/lib/docker_project/docker_project_info.json".format(public.get_url())
|
||||
dp.download_file(url, info_path)
|
||||
if os.path.exists(info_path):
|
||||
return public.return_message(0, 0, public.lang("info.json is downloaded!"))
|
||||
return public.return_message(-1, 0, public.lang("The info.json download failed!"))
|
||||
|
||||
def __download_project_yml(self, server_name):
|
||||
'''
|
||||
下载指定项目压缩包
|
||||
@param server_name: string 模板名称,如nextcloud
|
||||
@return:
|
||||
'''
|
||||
try:
|
||||
path = "{}/{}".format(self.templates_path, server_name)
|
||||
filename = "{}/{}.tar.gz".format(self.templates_path, server_name)
|
||||
compose_file = "{}/docker-compose.yml".format(path)
|
||||
url = "{}/install/lib/docker_project/templates/{}.tar.gz".format(public.get_url(), server_name)
|
||||
dp.download_file(url, filename)
|
||||
if not os.path.exists(filename):
|
||||
return public.return_message(-1, 0, public.lang("{} Download failed, please resync!", server_name))
|
||||
if os.path.getsize(filename) == 0:
|
||||
os.remove(filename)
|
||||
return public.return_message(-1, 0, public.lang("{} Download failed, please resync!", server_name))
|
||||
self.__tar_x_yml(server_name, path, filename)
|
||||
if os.path.exists(compose_file):
|
||||
check_conf = self.__check_conf(compose_file)
|
||||
if check_conf[1]:
|
||||
return public.return_message(-1, 0, public.lang("{}yml file test failed,{}", server_name, check_conf[1]))
|
||||
return public.return_message(0, 0, public.lang("{} Download completed!", server_name))
|
||||
except:
|
||||
return public.return_message(-1, 0, public.lang("{} Download failed, please resync!", server_name))
|
||||
|
||||
def __tar_x_yml(self, server_name, path=None, filename=None):
|
||||
'''
|
||||
解压项目模板方法
|
||||
@param server_name: 模板名称,如nextcloud
|
||||
@param path: 项目模板路劲,如/www/dk_project/templates/nextcloud
|
||||
@param filename: 项目模板压缩包,如/www/dk_project/templates/nextcloud.tar.gz
|
||||
@return:
|
||||
'''
|
||||
tar_result = public.ExecShell("tar xvf {} -C {}".format(filename, self.templates_path))
|
||||
if tar_result[1]:
|
||||
os.remove(path)
|
||||
os.remove(filename)
|
||||
return public.return_message(-1, 0, public.lang("{} Decompression failed", server_name))
|
||||
return public.return_message(0, 0, public.lang("{} extracted successfully", server_name))
|
||||
|
||||
def create_project_volume(self, server_name, project_name, dir_names, volume_path):
|
||||
'''
|
||||
创建指定项目的数据存储卷
|
||||
@param volume_path:
|
||||
@param project_name: string
|
||||
@param dir_names: list [dir_name,dir_name,...]
|
||||
@return:
|
||||
'''
|
||||
args = public.dict_obj()
|
||||
args.url = "unix:///var/run/docker.sock"
|
||||
# volumes = dv.main().get_volume_list(args)
|
||||
# {'status': True, 'msg': {'volume': [], 'installed': True, 'service_status': True}}
|
||||
# if volumes['status']:
|
||||
# volumes = volumes['msg']['volume']
|
||||
# else:
|
||||
# volumes = list()
|
||||
# volume的值,一个list: []
|
||||
for dir_name in dir_names:
|
||||
# # 如果已经存在就跳过
|
||||
# for volume in volumes:
|
||||
# if dir_name == volume["Name"]:
|
||||
# continue
|
||||
if volume_path == "":
|
||||
path = "{}/projects/{}/data/{}".format(self.project_path, project_name, dir_name)
|
||||
else:
|
||||
path = "{}/data/{}".format(volume_path, dir_name)
|
||||
is_mkdir = public.ExecShell("mkdir -p {}".format(path))
|
||||
if is_mkdir[1]: return public.return_message(-1, 0, public.lang("Directory creation failed for the following reasons: {}", is_mkdir[1]))
|
||||
args.name = "{}_{}_{}".format(project_name, server_name, dir_name)
|
||||
args.driver = "local"
|
||||
args.driver_opts = {'type': 'none', 'device': path, 'o': 'bind'}
|
||||
args.labels = {}
|
||||
dv.main().add(args)
|
||||
return public.return_message(0, 0, public.lang("The storage volume has been created"))
|
||||
|
||||
def get_project(self, get):
|
||||
'''
|
||||
获取指定一键部署项目的配置信息
|
||||
@param get: get.server_name
|
||||
@return:
|
||||
'''
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('server_name').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
try:
|
||||
server_name = getattr(get, "server_name")
|
||||
info_path = "{}/{}/conf.json".format(self.templates_path, server_name)
|
||||
project_info = json.loads(public.readFile(info_path))
|
||||
volume_placeholder = "Default: {}/projects/ your project name /data/".format(self.project_path)
|
||||
total_sum = len(project_info)
|
||||
volume_path = {"id": total_sum + 1, "sort": total_sum + 1, "type": "string",
|
||||
"key": "VOLUME_PATH", "value": "", "placeholder": volume_placeholder,
|
||||
"ps": "Data storage directory"}
|
||||
project_info.append(volume_path)
|
||||
except:
|
||||
project_info = []
|
||||
return public.return_message(0, 0, project_info)
|
||||
|
||||
def _get_project(self, get):
|
||||
'''
|
||||
获取指定一键部署项目的配置信息
|
||||
@param get: get.server_name
|
||||
@return:
|
||||
'''
|
||||
try:
|
||||
server_name = getattr(get, "server_name")
|
||||
info_path = "{}/{}/conf.json".format(self.templates_path, server_name)
|
||||
project_info = json.loads(public.readFile(info_path))
|
||||
volume_placeholder = "Default: {}/projects/ your project name /data/".format(self.project_path)
|
||||
total_sum = len(project_info)
|
||||
volume_path = {"id": total_sum + 1, "sort": total_sum + 1, "type": "string",
|
||||
"key": "VOLUME_PATH", "value": "", "placeholder": volume_placeholder,
|
||||
"ps": "Data storage directory"}
|
||||
project_info.append(volume_path)
|
||||
except:
|
||||
project_info = []
|
||||
return project_info
|
||||
|
||||
def __get_server_ps(self, project_conf, conf_key):
|
||||
'''
|
||||
获取对应服务名的标题
|
||||
@param project_conf:
|
||||
@param conf_key:
|
||||
@return:
|
||||
'''
|
||||
get = public.dict_obj()
|
||||
for conf in project_conf:
|
||||
if conf["key"] == "SERVER_NAME":
|
||||
get.server_name = conf["value"]
|
||||
server_conf = self._get_project(get)
|
||||
for server in server_conf:
|
||||
if conf_key == server["key"]:
|
||||
return server["ps"]
|
||||
return conf_key
|
||||
|
||||
def get_project_logs(self, get):
|
||||
"""
|
||||
获取一键部署日志,websocket
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
get.wsLogTitle = "Please wait to execute the command..."
|
||||
print(self.log_file)
|
||||
get._log_path = self.log_file
|
||||
return self.get_ws_log(get)
|
||||
def create_project(self, get):
|
||||
'''
|
||||
创建一键部署的项目
|
||||
@param get:
|
||||
@return:
|
||||
'''
|
||||
|
||||
# {"project_conf": [{"key": "PROJECT_NAME", "value": "sdfasdf"}, {"key": "PORT", "value": "8180"},
|
||||
# {"key": "DB_ROOT_PASS", "value": "bt_nextcloud"}, {"key": "DB_NAME", "value": "nextcloud"},
|
||||
# {"key": "DB_USER", "value": "nextcloud"}, {"key": "DB_PASS", "value": "bt_nextcloud"},
|
||||
# {"key": "VOLUME_PATH", "value": "/www/dk_project/projects/sdfasdf"},
|
||||
# {"key": "REMARK", "value": "SDFADSF"}, {"key": "SERVER_NAME", "value": "nextcloud"},
|
||||
# {"key": "VOLUMES", "value": ["nextcloud", "db"]}]}
|
||||
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('project_conf').Require().List(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
project_conf = getattr(get, "project_conf")
|
||||
remark = ""
|
||||
for conf in project_conf:
|
||||
if conf["key"] != "REMARK" and type(conf["value"]) != list:
|
||||
if re.search(r'\s', conf["value"]):
|
||||
server_ps = self.__get_server_ps(project_conf, conf["key"])
|
||||
return public.return_message(-1, 0, public.lang("{} cannot contain Spaces", server_ps))
|
||||
if conf["key"] != "VOLUME_PATH" and conf["key"] != "REMARK":
|
||||
if conf["value"] == "":
|
||||
server_ps = self.__get_server_ps(project_conf, conf["key"])
|
||||
return public.return_message(-1, 0, public.lang("{} cannot be null!", server_ps))
|
||||
if conf["key"].upper() == "PROJECT_NAME": project_name = conf["value"].strip()
|
||||
if conf["key"].upper() == "VOLUME_PATH": project_volume = conf["value"].strip()
|
||||
if conf["key"].upper() == "SERVER_NAME": server_name = conf["value"].strip()
|
||||
if conf["key"].upper() == "VOLUMES": # VOLUMES = list
|
||||
volumes = conf["value"]
|
||||
if conf["key"].upper() == "PORT":
|
||||
if dp.check_socket(conf["value"]):
|
||||
return public.return_message(-1, 0, public.lang("Server port [ {}] is occupied, please change to another port!", conf['value']))
|
||||
project_port = conf["value"]
|
||||
if conf["key"] == "REMARK": remark = conf["value"]
|
||||
|
||||
config_path = "{}/config/name_map.json".format(public.get_panel_path())
|
||||
if not os.path.exists(config_path):
|
||||
public.writeFile(config_path, json.dumps({}))
|
||||
|
||||
if public.readFile(config_path) == '':
|
||||
public.writeFile(config_path, json.dumps({}))
|
||||
|
||||
name_map = json.loads(public.readFile(config_path))
|
||||
name_str = 'q18q' + public.GetRandomString(10).lower()
|
||||
name_map[name_str] = project_name
|
||||
project_name = name_str
|
||||
public.writeFile(config_path, json.dumps(name_map))
|
||||
server_dir = "{}/{}".format(self.templates_path, server_name)
|
||||
project_dir = "{}/projects/{}/{}_{}".format(self.project_path, project_name, project_name, server_name)
|
||||
public.set_module_logs('docker_project', 'create_project', 1)
|
||||
check_result = self.__create_dir(project_dir, project_name, server_name, server_dir)
|
||||
# todo 修改返回内容 只取msg 测试是否取到
|
||||
if not check_result["status"]:
|
||||
return public.return_message(-1, 0, check_result["msg"])
|
||||
|
||||
self.__write_config(project_dir, project_name, server_name, project_conf)
|
||||
self.create_project_volume(server_name, project_name, volumes, project_volume)
|
||||
run_result = self.__project_run(project_dir, project_name)
|
||||
|
||||
if run_result["status"]:
|
||||
self.__add_sql(project_dir, project_name, server_name, remark)
|
||||
dp.write_log("One-click deployment project [{}] successful!".format(server_name))
|
||||
return public.return_message(-1, 0, self.__return_msg(project_port))
|
||||
return public.return_message(-1, 0, run_result)
|
||||
# return public.return_message(-1, 0, run_result["msg"])
|
||||
|
||||
def __project_run(self, project_dir, project_name):
|
||||
'''
|
||||
运行项目
|
||||
@param project_dir: 项目运行目录
|
||||
@param server_name: 服务名称
|
||||
@return:
|
||||
'''
|
||||
filename = "{}/docker-compose.yml".format(project_dir)
|
||||
check_result = self.__check_conf(filename)
|
||||
if check_result[1]:
|
||||
return public.return_message(-1, 0, public.lang("Project startup failed {}", check_result[1]))
|
||||
|
||||
public.ExecShell("echo -n > {}".format(self.log_file))
|
||||
public.ExecShell("nohup {} -f {}/docker-compose.yml up -d >> {} 2>&1 &&"
|
||||
" echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &"
|
||||
.format(
|
||||
self.compose_cmd,
|
||||
project_dir,
|
||||
self.log_file,
|
||||
self.log_file,
|
||||
self.log_file
|
||||
))
|
||||
return public.return_message(0, 0, public.lang("Start creating the project"))
|
||||
|
||||
def __create_dir(self, project_dir, project_name, server_name, server_dir):
|
||||
'''
|
||||
创建项目目录
|
||||
@param project_dir: 项目目录
|
||||
@param project_name: 项目名称
|
||||
@param server_dir: 服务源目录
|
||||
@return:
|
||||
'''
|
||||
if self.__check_repeat(project_dir, project_name, server_name):
|
||||
return public.return_message(-1, 0, public.lang("{} already exists, please change the project name", project_name))
|
||||
mk_result = public.ExecShell("mkdir -p {}".format(project_dir))
|
||||
if mk_result[1]: return public.return_message(-1, 0, public.lang("User project directory failed to create,details: {}", mk_result[1]))
|
||||
cp_result = public.ExecShell("cp -a {}/. {}/".format(server_dir, project_dir))
|
||||
if cp_result[1]: return public.return_message(-1, 0, public.lang("Failed to copy project directory. Details: {}", cp_result[1]))
|
||||
return public.return_message(0, 0, public.lang(""))
|
||||
|
||||
def __add_sql(self, project_dir, project_name, server_name, remark):
|
||||
'''
|
||||
添加项目到docker数据库中
|
||||
@param project_dir: 项目路劲
|
||||
@param project_name: 项目名称
|
||||
@return:
|
||||
'''
|
||||
pdata = {
|
||||
"name": public.xsssec("{}_{}".format(project_name, server_name)),
|
||||
"status": "1",
|
||||
"path": "{}/docker-compose.yml".format(project_dir),
|
||||
"template_id": "",
|
||||
"time": time.time(),
|
||||
"remark": public.xsssec(remark)
|
||||
}
|
||||
dp.sql("stacks").insert(pdata)
|
||||
|
||||
def __return_msg(self, project_port):
|
||||
'''
|
||||
创建成功后返回给用户的数据
|
||||
@param project_port:
|
||||
@return:
|
||||
'''
|
||||
server_ip = public.get_server_ip()
|
||||
local_ip = public.GetLocalIp()
|
||||
data = {"protocol": "http", "server_ip": server_ip, "local_ip": local_ip,
|
||||
"port": project_port}
|
||||
return public.return_message(0, 0, data)
|
||||
|
||||
def __check_repeat(self, project_dir, project_name, server_name):
|
||||
'''
|
||||
检查是否存在相同项目
|
||||
@param project_dir: 项目路劲
|
||||
@return:
|
||||
'''
|
||||
# if os.path.exists(project_dir):
|
||||
# return True
|
||||
stacks_info = dp.sql("stacks").where("name=?", ("{}_{}".format(project_name, server_name),)).find()
|
||||
if stacks_info:
|
||||
return True
|
||||
return False
|
||||
|
||||
def __write_config(self, project_dir, project_name, server_name, project_conf):
|
||||
'''
|
||||
写配置文件
|
||||
@param project_dir: 用户项目目录
|
||||
@param project_name: 项目名称
|
||||
@param server_name: 服务名称,如nextcloud
|
||||
@param project_conf: 新的配置文件内容
|
||||
@return:
|
||||
'''
|
||||
old_env_path = "{}/{}/.env".format(self.templates_path, server_name)
|
||||
new_env_path = "{}/.env".format(project_dir)
|
||||
env_conf = ""
|
||||
if not os.path.exists(old_env_path):
|
||||
public.ExecShell("echo > {}".format(old_env_path))
|
||||
with open(old_env_path) as env:
|
||||
lines = env.readlines()
|
||||
# 取旧文件转字典
|
||||
old_dict = {}
|
||||
for line in lines:
|
||||
if "=" in line:
|
||||
temp = line.split("=")
|
||||
old_dict[temp[0]] = temp[1]
|
||||
# 新数据转字典
|
||||
new_dict = {}
|
||||
for conf in project_conf:
|
||||
if conf["key"] == "VOLUME_PATH":
|
||||
project_volume = conf["value"]
|
||||
if "Default path" in project_volume:
|
||||
conf["value"] = "{}/{}/data/".format(self.project_path, project_name)
|
||||
continue
|
||||
if conf["key"] == "VOLUMES": continue
|
||||
new_dict[conf["key"].upper()] = conf["value"]
|
||||
# 旧字典更新新字典的内容
|
||||
old_dict.update(new_dict)
|
||||
# 拼接成新的环境变量文件
|
||||
for key, value in old_dict.items():
|
||||
env_conf += "{}={}\n".format(key, value.strip())
|
||||
public.writeFile(new_env_path, env_conf)
|
||||
return True
|
||||
|
||||
def sync_compose_template(self, server_name):
|
||||
'''
|
||||
同步模板到项目模板页面
|
||||
@param server_name: 模板名称
|
||||
@return:
|
||||
'''
|
||||
data = dp.sql("templates").where("name=?", (server_name,)).find()
|
||||
# if data: dp.sql("templates").delete(id=data["id"])
|
||||
if data: return
|
||||
pdata = {
|
||||
"name": server_name,
|
||||
"remark": "YakPanel Docker Quick Deployment templates only [Do not delete them and use them separately to create projects]",
|
||||
"path": "{}/{}/docker-compose.yml".format(self.templates_path, server_name)
|
||||
}
|
||||
dp.sql("templates").insert(pdata)
|
||||
dp.write_log("Add template [{}] successful!".format(server_name))
|
||||
307
class_v2/btdockerModelV2/proxyModel.py
Normal file
307
class_v2/btdockerModelV2/proxyModel.py
Normal file
@@ -0,0 +1,307 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
import json
|
||||
import os
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
|
||||
import public
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
class main(dockerBase):
|
||||
|
||||
# 2023/12/27 下午 2:56 创建容器反向代理
|
||||
def create_proxy(self, get):
|
||||
'''
|
||||
@name 创建容器反向代理
|
||||
@author wzz <2023/12/27 下午 2:57>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('domain').Require(),
|
||||
Param('container_port').Require(),
|
||||
Param('container_name').Require(),
|
||||
Param('container_id').Require(),
|
||||
Param('privateKey').Require(),
|
||||
Param('certPem').Require(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, ex)
|
||||
|
||||
# try:
|
||||
if not (os.path.exists('/etc/init.d/nginx') or os.path.exists('/etc/init.d/httpd')):
|
||||
return public.return_message(-1, 0, public.lang("nginx or apache server was not detected, please install one first!"))
|
||||
|
||||
# if not hasattr(get, 'domain'):
|
||||
# return public.return_message(-1, 0, public.lang("parameter error"))
|
||||
#
|
||||
# if not hasattr(get, 'container_port'):
|
||||
# return public.return_message(-1, 0, public.lang("parameter error"))
|
||||
|
||||
self.siteName = get.domain.strip()
|
||||
self.check_table_dk_sites()
|
||||
if dp.sql('dk_sites').where('container_id=?', (get.container_id,)).order('id desc').find():
|
||||
self.close_proxy(get)
|
||||
# 2024/2/23 下午 12:05 如果其他地方有这个域名,则禁止添加
|
||||
newpid = public.M('domain').where("name=? and port=?", (self.siteName, 80)).getField('pid')
|
||||
if newpid:
|
||||
result = public.M('sites').where("id=?", (newpid,)).find()
|
||||
if result:
|
||||
return public.return_message(-1, 0, public.lang("Project Type [{}] Existing Domain: {}", result['project_type'],self.siteName))
|
||||
|
||||
self.container_port = get.container_port
|
||||
if not dp.check_socket(self.container_port):
|
||||
return public.return_message(-1, 0, public.lang("Server port [ {}] is not used, please enter the port in use to reverse!", self.container_port))
|
||||
# todo mod的反向代理
|
||||
from mod.project.proxy.comMod import main as proxyMod
|
||||
pMod = proxyMod()
|
||||
|
||||
try:
|
||||
args = public.to_dict_obj({
|
||||
"proxy_pass": "http://127.0.0.1:{}".format(self.container_port),
|
||||
"proxy_type": "http",
|
||||
"domains": self.siteName,
|
||||
"proxy_host": "$http_host",
|
||||
"remark": "Reverse proxy for container [{}]".format(get.container_name),
|
||||
})
|
||||
# 改返回
|
||||
create_result = pMod.create(args)
|
||||
if create_result['status'] == -1:
|
||||
return create_result
|
||||
# if not create_result['status']:
|
||||
# return public.returnResult(False, create_result['msg'])
|
||||
|
||||
if hasattr(get, "privateKey") and hasattr(get, "certPem") and get.privateKey != "" and get.certPem != "":
|
||||
args.site_name = self.siteName
|
||||
args.key = get.privateKey
|
||||
args.csr = get.certPem
|
||||
# 改返回
|
||||
ssl_result = pMod.set_ssl(args)
|
||||
# if not ssl_result['status']:
|
||||
if ssl_result['status'] == -1:
|
||||
result = public.M('sites').where("name=?", (self.siteName,)).find()
|
||||
args.id = result['id']
|
||||
args.siteName = self.siteName
|
||||
args.remove_path = 1
|
||||
pMod.delete(args)
|
||||
return ssl_result
|
||||
# return public.returnResult(False, ssl_result['msg'])
|
||||
|
||||
self.sitePath = '/www/wwwroot/' + self.siteName
|
||||
|
||||
site_pid = dp.sql('dk_sites').add(
|
||||
'name,path,ps,addtime,container_id,container_name,container_port',
|
||||
(self.siteName, self.sitePath, 'Reverse proxy for container [{}]'.format(get.container_name),
|
||||
datetime.now().strftime("%Y-%m-%d %H:%M:%S"), get.container_id, get.container_name, self.container_port)
|
||||
)
|
||||
if not site_pid:
|
||||
return public.return_message(-1, 0, public.lang("Add failure, database cannot be written!"))
|
||||
|
||||
domain_id = dp.sql('dk_domain').where('id=?', (site_pid,)).find()
|
||||
if not domain_id:
|
||||
dp.sql('dk_domain').add(
|
||||
'pid,name,addtime',
|
||||
(site_pid, self.siteName, datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
|
||||
)
|
||||
|
||||
return public.return_message(0, 0, public.lang("successfully added!"))
|
||||
except Exception as e:
|
||||
result = public.M('sites').where("name=?", (self.siteName,)).find()
|
||||
args = public.to_dict_obj({
|
||||
"id": result['id'],
|
||||
"siteName": self.siteName,
|
||||
"remove_path": 1,
|
||||
})
|
||||
pMod.delete(args)
|
||||
return public.return_message(-1, 0, public.lang('Add failed with error: {}',str(e)))
|
||||
|
||||
# 2024/1/2 下午 5:34 获取容器的反向代理信息
|
||||
def get_proxy_info(self, get):
|
||||
'''
|
||||
@name 获取容器的反向代理信息
|
||||
@author wzz <2024/1/2 下午 5:34>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('container_id').Require(),
|
||||
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, ex)
|
||||
try:
|
||||
# if not hasattr(get, 'container_id'):
|
||||
# return public.return_message(-1, 0, public.lang("parameter error"))
|
||||
self.check_table_dk_sites()
|
||||
container_id = get.container_id
|
||||
from btdockerModelV2 import containerModel as dc
|
||||
get.id = container_id
|
||||
container_info = dc.main().get_container_info(get)
|
||||
proxy_port = []
|
||||
proxy_info = {
|
||||
"proxy_port": proxy_port,
|
||||
"ssl": False,
|
||||
"status": False,
|
||||
}
|
||||
# public.print_log("container_info: {}".format(container_info))
|
||||
|
||||
if container_info["status"] == -1:
|
||||
proxy_port = []
|
||||
else:
|
||||
container_info = container_info['message']
|
||||
for key, value in container_info['NetworkSettings']['Ports'].items():
|
||||
|
||||
if value:
|
||||
proxy_port.append(value[0]['HostPort'])
|
||||
|
||||
try:
|
||||
proxy_info_data = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find()
|
||||
if not proxy_info_data:
|
||||
return public.return_message(0, 0, proxy_info)
|
||||
|
||||
proxy_info = proxy_info_data
|
||||
|
||||
site_result = public.M('sites').where("name=?", (proxy_info['name'],)).find()
|
||||
if not site_result:
|
||||
dp.sql('dk_sites').where('container_id=?', (container_id,)).delete()
|
||||
dp.sql('dk_domain').where('pid=?', (proxy_info['id'],)).delete()
|
||||
return public.return_message(0, 0, proxy_info)
|
||||
|
||||
path = '/www/server/panel/vhost/cert/' + proxy_info['name']
|
||||
conf_file = '/www/server/panel/vhost/nginx/' + proxy_info['name'] + '.conf'
|
||||
csrpath = path + "/fullchain.pem"
|
||||
keypath = path + "/privkey.pem"
|
||||
proxy_info["ssl"] = False
|
||||
if os.path.exists(csrpath) and os.path.exists(keypath):
|
||||
try:
|
||||
conf = public.readFile(conf_file)
|
||||
if conf:
|
||||
if (conf.find("ssl_certificate") != -1):
|
||||
proxy_info["ssl"] = True
|
||||
proxy_info['cert'] = public.readFile(csrpath)
|
||||
proxy_info['key'] = public.readFile(keypath)
|
||||
except:
|
||||
proxy_info['cert'] = ""
|
||||
proxy_info['key'] = ""
|
||||
|
||||
proxy_info["status"] = True
|
||||
proxy_info['proxy_port'] = proxy_port
|
||||
except:
|
||||
pass
|
||||
|
||||
return public.return_message(0, 0, proxy_info)
|
||||
except Exception as ex:
|
||||
public.print_log(traceback.format_exc())
|
||||
public.print_log("error: {}".format(ex))
|
||||
return public.return_message(-1, 0, ex)
|
||||
|
||||
# 2024/1/2 下午 5:43 关闭容器的反向代理
|
||||
def close_proxy(self, get):
|
||||
'''
|
||||
@name 关闭容器的反向代理
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
get.validate([
|
||||
Param('container_id').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, ex)
|
||||
try:
|
||||
# if not hasattr(get, 'container_id'):
|
||||
# return public.return_message(-1, 0, public.lang("parameter error!"))
|
||||
|
||||
container_id = get.container_id
|
||||
proxy_info = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find()
|
||||
|
||||
if not proxy_info:
|
||||
return public.return_message(-1, 0, public.lang("No reverse proxy information was detected!"))
|
||||
|
||||
newpid = public.M('domain').where("name=? and port=?", (proxy_info["name"], 80)).getField('pid')
|
||||
if not newpid:
|
||||
return public.return_message(-1, 0, public.lang("No reverse proxy information was detected!"))
|
||||
|
||||
# 删除站点
|
||||
public.M('sites').where("name=?", (proxy_info['name'],)).delete()
|
||||
public.M('domain').where("name=?", (proxy_info['name'],)).delete()
|
||||
|
||||
# 删除数据库记录
|
||||
dp.sql('dk_sites').where('container_id=?', (container_id,)).delete()
|
||||
dp.sql('dk_domain').where('pid=?', (proxy_info['id'],)).delete()
|
||||
|
||||
return public.return_message(0, 0, public.lang("successfully delete!"))
|
||||
except:
|
||||
return public.return_message(-1, 0, traceback.format_exc())
|
||||
|
||||
# 2024/1/2 下午 5:57 获取指定域名的证书内容
|
||||
def get_cert_info(self, get):
|
||||
'''
|
||||
@name 获取指定域名的证书内容
|
||||
@author wzz <2024/1/2 下午 5:58>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
if not hasattr(get, 'cert_name'): return public.return_message(-1, 0, public.lang("parameter error!"))
|
||||
cert_name = get.cert_name
|
||||
# 2024/1/3 下午 4:50 处理通配符域名,将*.spider.com替换成spider.com
|
||||
if cert_name.startswith('*.'):
|
||||
cert_name = cert_name.replace('*.', '')
|
||||
if not os.path.exists('/www/server/panel/vhost/ssl/{}'.format(cert_name)):
|
||||
return public.return_message(-1, 0, public.lang("Certificate does not exist!"))
|
||||
cert_data = {}
|
||||
cert_data['cert_name'] = cert_name
|
||||
cert_data['cert'] = public.readFile('/www/server/panel/vhost/ssl/{}/fullchain.pem'.format(cert_name))
|
||||
cert_data['key'] = public.readFile('/www/server/panel/vhost/ssl/{}/privkey.pem'.format(cert_name))
|
||||
cert_data['info'] = json.loads(
|
||||
public.readFile('/www/server/panel/vhost/ssl/{}/info.json'.format(cert_name)))
|
||||
return public.return_message(0, 0, cert_data)
|
||||
except:
|
||||
return public.return_message(-1, 0, traceback.format_exc())
|
||||
|
||||
def check_table_dk_domain(self):
|
||||
'''
|
||||
@name 检查并创建表
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_domain')).count():
|
||||
dp.sql('dk_domain').execute(
|
||||
"CREATE TABLE `dk_domain` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `pid` INTEGER, `name` TEXT, `addtime` TEXT )",
|
||||
()
|
||||
)
|
||||
|
||||
def check_table_dk_sites(self):
|
||||
'''
|
||||
@name 检查并创建表
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_sites')).count():
|
||||
dp.sql('dk_sites').execute(
|
||||
"CREATE TABLE `dk_sites` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `path` TEXT, `status` TEXT DEFAULT 1, `ps` TEXT, `addtime` TEXT, `type_id` integer DEFAULT 111, `edate` integer DEFAULT '0000-00-00', `project_type` STRING DEFAULT 'dk_proxy', `container_id` TEXT DEFAULT '', `container_name` TEXT DEFAULT '', `container_port` TEXT DEFAULT '')",
|
||||
()
|
||||
)
|
||||
323
class_v2/btdockerModelV2/registryModel.py
Normal file
323
class_v2/btdockerModelV2/registryModel.py
Normal file
@@ -0,0 +1,323 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: zouhw <zhw@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import json
|
||||
|
||||
import public
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
|
||||
def docker_client(self, url):
|
||||
return dp.docker_client(url)
|
||||
|
||||
def add(self, args):
|
||||
"""
|
||||
添加仓库
|
||||
:param registry 仓库URL docker.io
|
||||
:param name
|
||||
:parma username
|
||||
:parma password
|
||||
:param namespace 仓库命名空间
|
||||
:param remark 备注
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
# {"registry": "docker.io", "name": "wzznb", "username": "akaishuichi", "password": "xiuyi999..",
|
||||
# "namespace": "akaishuichi", "remark": "wzz_docker_io"}
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
args.validate([
|
||||
Param('name').Require().String(),
|
||||
Param('username').Require().String(),
|
||||
Param('password').Require().String(),
|
||||
Param('namespace').Require().String(),
|
||||
Param('remark').String(),
|
||||
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
# 验证登录
|
||||
if not args.registry:
|
||||
args.registry = "docker.io"
|
||||
res = self.login(self._url, args.registry, args.username, args.password)
|
||||
|
||||
if not res['status']:
|
||||
return public.return_message(-1, 0, res['msg'])
|
||||
r_list = self.registry_list(args)
|
||||
if len(r_list) > 0:
|
||||
for r in r_list:
|
||||
if "reg_name" in r:
|
||||
if r['reg_name'] == args.name and r["reg_url"] == args.registry and r['username'] == args.username:
|
||||
return public.return_message(-1, 0, public.lang("Repository information already exists!"))
|
||||
|
||||
if r['name'] == args.name and r["reg_url"] == args.registry and r['username'] == args.username:
|
||||
return public.return_message(-1, 0, public.lang("Repository information already exists!"))
|
||||
pdata = {
|
||||
"reg_name": args.name,
|
||||
"url": args.registry,
|
||||
"namespace": args.namespace,
|
||||
"username": public.aes_encrypt(args.username, self.aes_key),
|
||||
"password": public.aes_encrypt(args.password, self.aes_key),
|
||||
"remark": public.xsssec(args.remark)
|
||||
}
|
||||
aa = dp.sql("registry").insert(pdata)
|
||||
|
||||
dp.write_log("Added repository [{}] [{}] success!".format(args.name, args.registry))
|
||||
return public.return_message(0, 0, public.lang("successfully added!"))
|
||||
|
||||
def edit(self, args):
|
||||
"""
|
||||
编辑仓库
|
||||
:param registry 仓库URL docker.io
|
||||
:param id 仓库id
|
||||
:parma username
|
||||
:parma password
|
||||
:param namespace
|
||||
:param remark
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
args.validate([
|
||||
Param('id').Require().Integer(),
|
||||
Param('username').Require().String(),
|
||||
Param('password').Require().String(),
|
||||
Param('namespace').Require().String(),
|
||||
Param('remark').String(),
|
||||
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
# 验证登录
|
||||
# if str(args.id) == "1":
|
||||
# return public.return_message(-1, 0, public.lang("[Official Docker repository] Not editable!"))
|
||||
if not args.registry:
|
||||
args.registry = "docker.io"
|
||||
|
||||
# 2023/12/13 上午 11:40 处理加密的编辑
|
||||
try:
|
||||
is_encrypt = False
|
||||
res = self.login(self._url, args.registry, args.username, args.password)
|
||||
if not res['status']:
|
||||
res = self.login(
|
||||
self._url,
|
||||
args.registry,
|
||||
public.aes_decrypt(args.username, self.aes_key),
|
||||
public.aes_decrypt(args.password, self.aes_key)
|
||||
)
|
||||
if not res['status']:
|
||||
return public.return_message(-1, 0, res['msg'])
|
||||
is_encrypt = True
|
||||
except Exception as e:
|
||||
if "binascii.Error: Incorrect padding" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Editing failed! Reason: Account password decryption failed! Please delete the repository and add it again"))
|
||||
return public.return_message(-1, 0, public.lang("Editing failed! Reason:{}", e))
|
||||
|
||||
res = dp.sql("registry").where("id=?", (args.id,)).find()
|
||||
if not res:
|
||||
return public.return_message(-1, 0, public.lang("This repository could not be found"))
|
||||
pdata = {
|
||||
"reg_name": args.name,
|
||||
"url": args.registry,
|
||||
"username": public.aes_encrypt(args.username, self.aes_key) if is_encrypt is False else args.username,
|
||||
"password": public.aes_encrypt(args.password, self.aes_key) if is_encrypt is False else args.password,
|
||||
"namespace": args.namespace,
|
||||
"remark": args.remark
|
||||
}
|
||||
dp.sql("registry").where("id=?", (args.id,)).update(pdata)
|
||||
dp.write_log("Edit repository [{}][{}] Success!".format(args.name, args.registry))
|
||||
return public.return_message(0, 0, public.lang("Edit success!"))
|
||||
|
||||
def remove(self, args):
|
||||
"""
|
||||
删除某个仓库
|
||||
:param id
|
||||
:param rags:
|
||||
:return:
|
||||
"""
|
||||
# 校验参数
|
||||
try:
|
||||
args.validate([
|
||||
Param('id').Require().Integer(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
|
||||
# if str(args.id) == "1":
|
||||
# return public.return_message(-1, 0, public.lang("[Official Docker repository] can not be removed!"))
|
||||
|
||||
data = dp.sql("registry").where("id=?", (args.id)).find()
|
||||
|
||||
if len(data) < 1:
|
||||
return public.return_message(0, 0, public.lang("Delete failed,The repository id may not exist!"))
|
||||
|
||||
dp.sql("registry").where("id=?", (args.id,)).delete()
|
||||
|
||||
dp.write_log("Delete repository [{}][{}] Success!".format(data['name'], data['url']))
|
||||
return public.return_message(0, 0, public.lang("Successfully deleted!"))
|
||||
def registry_list(self, get):
|
||||
"""
|
||||
获取仓库列表
|
||||
:return:
|
||||
"""
|
||||
self.check_table_dk_registry()
|
||||
db_obj = dp.sql("registry")
|
||||
# 2024/1/3 下午 6:00 检测数据库是否存在并且表健康
|
||||
search_result = db_obj.where('id=? or name=?', (1, "Docker public repository")).select()
|
||||
|
||||
# if db_obj.ERR_INFO:
|
||||
# return []
|
||||
|
||||
|
||||
if len(search_result) == 0:
|
||||
dp.sql("registry").insert({
|
||||
"name": "Docker public repository",
|
||||
"url": "docker.io",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"namespace": "",
|
||||
"remark": "Docker public repository"
|
||||
})
|
||||
if "error: no such table: registry" in search_result or len(search_result) == 0:
|
||||
# public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db")
|
||||
public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db")
|
||||
dp.check_db()
|
||||
|
||||
res = dp.sql("registry").select()
|
||||
if not isinstance(res, list):
|
||||
res = []
|
||||
|
||||
for r in res:
|
||||
if "reg_name" not in r: continue
|
||||
if r["name"] == "" or not r["name"] or r["name"] is None:
|
||||
r["name"] = r["reg_name"]
|
||||
|
||||
return res
|
||||
# 改返回
|
||||
def registry_listV2(self, get):
|
||||
"""
|
||||
获取仓库列表
|
||||
:return:
|
||||
"""
|
||||
res = self.registry_list(get)
|
||||
return public.return_message(0, 0, res)
|
||||
# 2024/5/24 下午6:09 设置备注
|
||||
def set_remark(self, get):
|
||||
'''
|
||||
@name 设置备注
|
||||
@param "data":{"id":"仓库ID","remark":"备注"}
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
get.remark = get.get("remark/s", "")
|
||||
if get.remark != "":
|
||||
get.remark = public.xssencode2(get.remark)
|
||||
|
||||
dp.sql("registry").where("id=?", (get.id,)).setField("remark", get.remark)
|
||||
return public.return_message(0, 0, public.lang("Setup Successful!"))
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, public.lang("Setup failed!{}",str(e)))
|
||||
|
||||
def get_com_registry(self, get):
|
||||
"""
|
||||
获取常用仓库列表
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
com_registry_file = "{}/class/btdockerModelV2/config/com_registry.json".format(public.get_panel_path())
|
||||
try:
|
||||
com_registry = json.loads(public.readFile(com_registry_file))
|
||||
except:
|
||||
com_registry = {
|
||||
"docker.io": "Docker public repository",
|
||||
"swr.cn-north-4.myhuaweicloud.com": "Huawei Cloud mirror station",
|
||||
"ccr.ccs.tencentyun.com": "Tencent cloud mirror station",
|
||||
"registry.cn-hangzhou.aliyuncs.com": "Alibaba Cloud Mirror Station (Hangzhou)"
|
||||
}
|
||||
|
||||
return public.return_message(0, 0, com_registry)
|
||||
# todo 检查字段新增reg_name
|
||||
def registry_info(self, get):
|
||||
'''
|
||||
@name 获取指定仓库的信息
|
||||
@author wzz <2024/5/24 下午3:16>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
registry_info = dp.sql("registry").where("reg_name=? and url=?", (get.name, get.url)).find()
|
||||
if not registry_info:
|
||||
registry_info = dp.sql("registry").where("name=? and url=?", (get.name, get.url)).find()
|
||||
|
||||
return registry_info
|
||||
|
||||
def login(self, url, registry, username, password):
|
||||
"""
|
||||
仓库登录测试
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
|
||||
try:
|
||||
res = self.docker_client(url).login(
|
||||
registry=registry,
|
||||
username=username,
|
||||
password=password,
|
||||
reauth=False
|
||||
)
|
||||
|
||||
return public.returnMsg(True, str(res))
|
||||
except:
|
||||
error_info = public.get_error_info()
|
||||
return public.returnMsg(False, public.lang("Login test failed! Reason: {}", error_info))
|
||||
# except docker.errors.APIError as e:
|
||||
# if "authentication required" in str(e):
|
||||
# return public.returnMsg(False, public.lang("Login test failed! Reason: May be account password error, please check!"))
|
||||
# if "unauthorized: incorrect username or password" in str(e):
|
||||
# return public.returnMsg(False, public.lang("Login test failed! Reason: May be account password error, please check!"))
|
||||
# return public.returnMsg(False, public.lang("Login test failed! Reason: {}", e))
|
||||
|
||||
|
||||
def check_table_dk_registry(self):
|
||||
'''
|
||||
@name 检查表registry 字段 reg_name remark是否存在
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
|
||||
# if public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count():
|
||||
# create_table_str = public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).getField('sql')
|
||||
#
|
||||
# if dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count():
|
||||
# create_table_str = dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).getField('sql')
|
||||
|
||||
|
||||
create_table_str = dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'registry')).getField('sql')
|
||||
if create_table_str and 'reg_name' not in create_table_str:
|
||||
dp.sql('registry').execute('ALTER TABLE `registry` ADD COLUMN `reg_name` VARCHAR default "";')
|
||||
|
||||
if create_table_str and 'remark' not in create_table_str:
|
||||
dp.sql('registry').execute('ALTER TABLE `registry` ADD COLUMN `remark` VARCHAR default "";')
|
||||
84
class_v2/btdockerModelV2/screen.py
Normal file
84
class_v2/btdockerModelV2/screen.py
Normal file
@@ -0,0 +1,84 @@
|
||||
#coding: utf-8
|
||||
#-------------------------------------------------------------------
|
||||
# YakPanel
|
||||
#-------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
#-------------------------------------------------------------------
|
||||
# Author: zouhw <zhw@yakpanel.com>
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
#------------------------------
|
||||
# Docker模型
|
||||
#------------------------------
|
||||
import dk_public as dp
|
||||
import time
|
||||
|
||||
class main:
|
||||
|
||||
def get_status(self,args):
|
||||
"""
|
||||
start_time
|
||||
stop_time
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
data = dict()
|
||||
# 容器总数
|
||||
data['container_count'] = self.__get_container_count(args)
|
||||
# 镜像信息,镜像总数,占用空间大小
|
||||
data['image_info'] = dp.sql("image_infos").where("time>=? and time<=?",(args.start_time,args.stop_time)).select()
|
||||
# 主机信息
|
||||
data['host'] = len(dp.sql('hosts').select())
|
||||
# 1小时内容器占用资源前三平均值
|
||||
data['container_top'] = {"cpu":self.__get_cpu_avg(),"mem":self.__get_mem_avg()}
|
||||
return data
|
||||
|
||||
def __get_container_count(self,args):
|
||||
count = dp.sql('container_count').where("time>=? and time<=?", (args.start_time, args.stop_time)).select()
|
||||
if not count:
|
||||
return 0
|
||||
return count[-1]
|
||||
|
||||
def __get_mem_avg(self):
|
||||
now = int(time.time())
|
||||
start_time = now - 3600
|
||||
data = dp.sql("mem_stats").where("time>=? and time<=?",(start_time,now)).select()
|
||||
containers = list()
|
||||
info = dict()
|
||||
# 获取容器ID
|
||||
for d in data:
|
||||
containers.append(d['container_id'])
|
||||
# 获取每个容器1小时内的cpu使用率总和
|
||||
containers = set(containers)
|
||||
for c in containers:
|
||||
num = 0
|
||||
usage = 0
|
||||
for d in data:
|
||||
if d['container_id'] == c:
|
||||
num += 1
|
||||
usage += float(d['usage'])
|
||||
if num != 0:
|
||||
info[c] = usage / num
|
||||
return info
|
||||
|
||||
def __get_cpu_avg(self):
|
||||
now = int(time.time())
|
||||
start_time = now - 3600
|
||||
data = dp.sql("cpu_stats").where("time>=? and time<=?",(start_time,now)).select()
|
||||
containers = list()
|
||||
info = dict()
|
||||
# 获取容器ID
|
||||
for d in data:
|
||||
containers.append(d['container_id'])
|
||||
# 获取每个容器1小时内的cpu使用率总和
|
||||
containers = set(containers)
|
||||
for c in containers:
|
||||
num = 0
|
||||
cpu_usage = 0
|
||||
for d in data:
|
||||
if d['container_id'] == c:
|
||||
num += 1
|
||||
cpu_usage += float(0 if d['cpu_usage'] == '0.0' else d['cpu_usage'])
|
||||
if num != 0:
|
||||
info[c] = cpu_usage / num
|
||||
return info
|
||||
Binary file not shown.
136
class_v2/btdockerModelV2/script/flush_plugin_.py
Normal file
136
class_v2/btdockerModelV2/script/flush_plugin_.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# coding: utf-8
|
||||
import sys, os
|
||||
|
||||
os.chdir('/www/server/panel/')
|
||||
sys.path.insert(0, "class/")
|
||||
import PluginLoader
|
||||
import public
|
||||
import time
|
||||
|
||||
|
||||
def clear_hosts():
|
||||
"""
|
||||
@name 清理 hosts 中面板官网节点记录
|
||||
@return:
|
||||
"""
|
||||
if public.is_self_hosted():
|
||||
return
|
||||
remove = 0
|
||||
try:
|
||||
import requests
|
||||
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
url = 'https://wafapi2.yakpanel.com/api/ip/info_json'
|
||||
res = requests.post(url, verify=False)
|
||||
|
||||
if res.status_code == 404:
|
||||
remove = 1
|
||||
elif res.status_code == 200 or res.status_code == 400:
|
||||
res = res.json()
|
||||
if res != "[]":
|
||||
remove = 1
|
||||
except:
|
||||
result = public.ExecShell("curl -sS --connect-timeout 3 -m 60 -k https://wafapi2.yakpanel.com/api/ip/info_json")[0]
|
||||
if result != "[]":
|
||||
remove = 1
|
||||
|
||||
hosts_file = '/etc/hosts'
|
||||
if remove == 1 and os.path.exists(hosts_file):
|
||||
public.ExecShell('sed -i "/www\\.yakpanel\\.com/d" /etc/hosts && sed -i "/api\\.yakpanel\\.com/d" /etc/hosts && sed -i "/bt\\.cn/d" /etc/hosts')
|
||||
|
||||
def flush_cache():
|
||||
'''
|
||||
@name 更新缓存
|
||||
@author hwliang
|
||||
@return void
|
||||
'''
|
||||
try:
|
||||
# start_time = time.time()
|
||||
res = PluginLoader.get_plugin_list(1)
|
||||
spath = '{}/data/pay_type.json'.format(public.get_panel_path())
|
||||
public.downloadFile(public.get_url() + '/install/lib/pay_type.json', spath)
|
||||
import plugin_deployment
|
||||
plugin_deployment.plugin_deployment().GetCloudList(None)
|
||||
|
||||
# timeout = time.time() - start_time
|
||||
if 'ip' in res and res['ip']:
|
||||
pass
|
||||
else:
|
||||
if isinstance(res, dict) and not 'msg' in res: res['msg'] = 'Connection failure!'
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def flush_php_order_cache():
|
||||
"""
|
||||
更新软件商店php顺序缓存
|
||||
@return:
|
||||
"""
|
||||
spath = '{}/data/php_order.json'.format(public.get_panel_path())
|
||||
public.downloadFile(public.get_url() + '/install/lib/php_order.json', spath)
|
||||
|
||||
|
||||
def flush_msg_json():
|
||||
"""
|
||||
@name 更新消息json
|
||||
"""
|
||||
try:
|
||||
spath = '{}/data/msg.json'.format(public.get_panel_path())
|
||||
public.downloadFile(public.get_url() + '/linux/panel/msg/msg.json', spath)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def flush_docker_project_info():
|
||||
'''
|
||||
@name 更新docker_project版本信息
|
||||
@author wzz
|
||||
@return void
|
||||
'''
|
||||
msg = "docker_projcet version information"
|
||||
try:
|
||||
# start_time = time.time()
|
||||
res = PluginLoader.get_plugin_list(1)
|
||||
config_path = f"{public.get_panel_path()}/config"
|
||||
spath = f"{config_path}/docker_project_info.json"
|
||||
url = "/install/lib/docker_project/docker_project_info.json"
|
||||
public.downloadFile(f"{public.get_url()}{url}", spath)
|
||||
import plugin_deployment
|
||||
plugin_deployment.plugin_deployment().GetCloudList(None)
|
||||
|
||||
# timeout = time.time() - start_time
|
||||
if 'ip' in res and res['ip']:
|
||||
pass
|
||||
else:
|
||||
if isinstance(res, dict) and not 'msg' in res: res['msg'] = 'Connection failure!'
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
# 2024/3/20 上午 11:09 更新docker_hub镜像排行数据
|
||||
def flush_docker_hub_repos():
|
||||
'''
|
||||
@name 更新docker_hub镜像排行数据
|
||||
@author wzz <2024/3/20 上午 11:09>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
public.ExecShell("/www/server/panel/pyenv/bin/python3 /www/server/panel/class_v2/btdockerModelV2/script/syncreposdb.py")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
tip_date_tie = '/tmp/.fluah_time'
|
||||
if os.path.exists(tip_date_tie):
|
||||
last_time = int(public.readFile(tip_date_tie))
|
||||
timeout = time.time() - last_time
|
||||
if timeout < 600:
|
||||
print("Execution interval is too short, exit - {}!".format(timeout))
|
||||
sys.exit()
|
||||
clear_hosts()
|
||||
flush_cache()
|
||||
flush_php_order_cache()
|
||||
flush_msg_json()
|
||||
flush_docker_project_info()
|
||||
flush_docker_hub_repos()
|
||||
|
||||
public.writeFile(tip_date_tie, str(int(time.time())))
|
||||
92
class_v2/btdockerModelV2/script/syncreposdb.py
Normal file
92
class_v2/btdockerModelV2/script/syncreposdb.py
Normal file
@@ -0,0 +1,92 @@
|
||||
#!/www/server/panel/pyenv/bin/python3
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
# docker模型sock 封装库 镜像库
|
||||
# -------------------------------------------------------------------
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
if "/www/server/panel/class" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class")
|
||||
import public
|
||||
|
||||
if "/www/server/panel/class_v2" not in sys.path:
|
||||
sys.path.insert(0, "/www/server/panel/class_v2")
|
||||
import btdockerModelV2.dk_public as dp
|
||||
|
||||
db_file = '{}/class_v2/btdockerModelV2/config/docker_hub_repos.db'.format(public.get_panel_path())
|
||||
last_update_pl = "{}/class_v2/btdockerModelV2/config/docker_hub_last_update.pl".format(public.get_panel_path())
|
||||
|
||||
|
||||
# 2024/3/20 上午 9:47 获取docker hub最新的镜像排行数据
|
||||
def get_docker_hub_repos():
|
||||
'''
|
||||
@name 获取docker hub最新的镜像排行数据
|
||||
@author wzz <2024/3/20 上午 9:47>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
url = "{}/src/docker/docker_hub_repos.db".format(public.get_url())
|
||||
dp.download_file(url, db_file)
|
||||
if not os.path.exists(db_file):
|
||||
return public.returnMsg(False, "info.json download failed")
|
||||
|
||||
# 写一个最后更新的标记文件,里面有时间戳
|
||||
public.writeFile(last_update_pl, str(int(time.time())))
|
||||
|
||||
return
|
||||
except Exception as e:
|
||||
if os.path.exists('data/debug.pl'):
|
||||
print(public.get_error_info())
|
||||
public.print_log(public.get_error_info())
|
||||
|
||||
|
||||
# 2024/3/20 上午 9:34 如果当前时间减去这个时间戳大于30天,就执行 get_docker_hub_repos
|
||||
def check_last_update():
|
||||
'''
|
||||
@name 如果当前时间减去这个时间戳大于30天,就执行 get_docker_hub_repos
|
||||
@author wzz <2024/3/20 上午 9:46>
|
||||
@param "data":{"参数名":""} <数据类型> 参数描述
|
||||
@return dict{"status":True/False,"msg":"提示信息"}
|
||||
'''
|
||||
try:
|
||||
if os.path.exists(last_update_pl):
|
||||
last_update_time = int(public.readFile(last_update_pl))
|
||||
if int(time.time()) - last_update_time > 2592000:
|
||||
public.ExecShell("rm -f {}".format(db_file))
|
||||
public.ExecShell("rm -f {}".format(last_update_pl))
|
||||
get_docker_hub_repos()
|
||||
|
||||
if os.path.exists(db_file) and (os.path.getsize(db_file) == 0 or os.path.getsize(db_file) < 80):
|
||||
public.ExecShell("rm -f {}".format(db_file))
|
||||
public.ExecShell("rm -f {}".format(last_update_pl))
|
||||
get_docker_hub_repos()
|
||||
|
||||
if not os.path.exists(db_file):
|
||||
public.ExecShell("rm -f {}".format(last_update_pl))
|
||||
get_docker_hub_repos()
|
||||
else:
|
||||
if not os.path.exists(db_file):
|
||||
get_docker_hub_repos()
|
||||
|
||||
if os.path.exists(db_file) and (os.path.getsize(db_file) == 0 or os.path.getsize(db_file) < 80):
|
||||
public.ExecShell("rm -f {}".format(db_file))
|
||||
public.ExecShell("rm -f {}".format(last_update_pl))
|
||||
get_docker_hub_repos()
|
||||
except Exception as e:
|
||||
public.ExecShell("rm -f {}".format(db_file))
|
||||
public.ExecShell("rm -f {}".format(last_update_pl))
|
||||
if os.path.exists('data/debug.pl'):
|
||||
print(public.get_error_info())
|
||||
public.print_log(public.get_error_info())
|
||||
|
||||
|
||||
check_last_update()
|
||||
1275
class_v2/btdockerModelV2/securityModel.py
Normal file
1275
class_v2/btdockerModelV2/securityModel.py
Normal file
File diff suppressed because it is too large
Load Diff
1372
class_v2/btdockerModelV2/setupModel.py
Normal file
1372
class_v2/btdockerModelV2/setupModel.py
Normal file
File diff suppressed because it is too large
Load Diff
317
class_v2/btdockerModelV2/statusModel.py
Normal file
317
class_v2/btdockerModelV2/statusModel.py
Normal file
@@ -0,0 +1,317 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
import gettext
|
||||
import json
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
import time
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import public
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
__stats_tmp = dict()
|
||||
__docker = None
|
||||
|
||||
def docker_client(self, url):
|
||||
if not self.__docker:
|
||||
self.__docker = dp.docker_client(url)
|
||||
return self.__docker
|
||||
|
||||
def io_stats(self, stats, write=None):
|
||||
drive_io = stats['blkio_stats']['io_service_bytes_recursive']
|
||||
if drive_io:
|
||||
if len(drive_io) <= 2:
|
||||
try:
|
||||
now = drive_io[0]['value']
|
||||
self.__stats_tmp['read_total'] = now
|
||||
except:
|
||||
self.__stats_tmp['read_total'] = 0
|
||||
try:
|
||||
now = drive_io[1]['value']
|
||||
self.__stats_tmp['write_total'] = now
|
||||
except:
|
||||
self.__stats_tmp['write_total'] = 0
|
||||
else:
|
||||
try:
|
||||
now = drive_io[0]['value'] + drive_io[2]['value']
|
||||
self.__stats_tmp['read_total'] = now
|
||||
except:
|
||||
self.__stats_tmp['read_total'] = 0
|
||||
try:
|
||||
now = drive_io[1]['value'] + drive_io[3]['value']
|
||||
self.__stats_tmp['write_total'] = now
|
||||
except:
|
||||
self.__stats_tmp['write_total'] = 0
|
||||
if write:
|
||||
self.__stats_tmp['container_id'] = stats['id']
|
||||
self.write_io(self.__stats_tmp)
|
||||
|
||||
def net_stats(self, stats, cache, write=None):
|
||||
try:
|
||||
net_io = stats['networks']['eth0']
|
||||
net_io_old = cache['networks']['eth0']
|
||||
except:
|
||||
self.__stats_tmp['rx_total'] = 0
|
||||
self.__stats_tmp['rx'] = 0
|
||||
self.__stats_tmp['tx_total'] = 0
|
||||
self.__stats_tmp['tx'] = 0
|
||||
if write:
|
||||
self.__stats_tmp['container_id'] = stats['id']
|
||||
self.write_net(self.__stats_tmp)
|
||||
return
|
||||
time_now = stats["time"]
|
||||
time_old = cache["time"]
|
||||
try:
|
||||
now = net_io["rx_bytes"]
|
||||
self.__stats_tmp['rx_total'] = now
|
||||
old = net_io_old["rx_bytes"]
|
||||
self.__stats_tmp['rx'] = int((now - old) / (time_now - time_old))
|
||||
except:
|
||||
self.__stats_tmp['rx_total'] = 0
|
||||
self.__stats_tmp['rx'] = 0
|
||||
try:
|
||||
now = net_io["tx_bytes"]
|
||||
old = net_io_old["tx_bytes"]
|
||||
self.__stats_tmp['tx_total'] = now
|
||||
self.__stats_tmp['tx'] = int((now - old) / (time_now - time_old))
|
||||
except:
|
||||
self.__stats_tmp['tx_total'] = 0
|
||||
self.__stats_tmp['tx'] = 0
|
||||
if write:
|
||||
self.__stats_tmp['container_id'] = stats['id']
|
||||
self.write_net(self.__stats_tmp)
|
||||
# return data
|
||||
|
||||
def mem_stats(self, stats, write=None):
|
||||
mem = stats['memory_stats']
|
||||
try:
|
||||
self.__stats_tmp['limit'] = mem['limit']
|
||||
self.__stats_tmp['usage_total'] = mem['usage']
|
||||
if 'cache' not in mem['stats']:
|
||||
mem['stats']['cache'] = 0
|
||||
self.__stats_tmp['usage'] = mem['usage'] - mem['stats']['cache']
|
||||
self.__stats_tmp['cache'] = mem['stats']['cache']
|
||||
# data['mem_useage'] = round(mem['usage'] * 100 / data['limit'],2)
|
||||
except:
|
||||
# return public.get_error_info()
|
||||
self.__stats_tmp['limit'] = 0
|
||||
self.__stats_tmp['usage'] = 0
|
||||
self.__stats_tmp['cache'] = 0
|
||||
self.__stats_tmp['usage_total'] = 0
|
||||
# data['mem_useage'] = 0
|
||||
if write:
|
||||
self.__stats_tmp['container_id'] = stats['id']
|
||||
self.write_mem(self.__stats_tmp)
|
||||
# return data
|
||||
|
||||
def cpu_stats(self, stats, write=None):
|
||||
# cpu_limit = dp.sql('container').where("c_id=?",(stats['id'],)).find()
|
||||
# if cpu_limit:
|
||||
# cpu_limit = cpu_limit['cpu_limit']
|
||||
# else:
|
||||
# cpu_limit = 1
|
||||
try:
|
||||
cpu = stats['cpu_stats']['cpu_usage']['total_usage'] - stats[
|
||||
'precpu_stats']['cpu_usage']['total_usage']
|
||||
except:
|
||||
cpu = 0
|
||||
try:
|
||||
system = stats['cpu_stats']['system_cpu_usage'] - stats[
|
||||
'precpu_stats']['system_cpu_usage']
|
||||
except:
|
||||
system = 0
|
||||
try:
|
||||
self.__stats_tmp['online_cpus'] = stats['cpu_stats']['online_cpus']
|
||||
except:
|
||||
self.__stats_tmp['online_cpus'] = 0
|
||||
if cpu > 0 and system > 0:
|
||||
self.__stats_tmp['cpu_usage'] = round(
|
||||
(cpu / system) * 100 * self.__stats_tmp['online_cpus'], 2)
|
||||
else:
|
||||
self.__stats_tmp['cpu_usage'] = 0.0
|
||||
if write:
|
||||
self.__stats_tmp['container_id'] = stats['id']
|
||||
self.write_cpu(self.__stats_tmp)
|
||||
# return data
|
||||
|
||||
def stats(self, args):
|
||||
"""
|
||||
获取某个容器的cpu,内存,网络io,磁盘io.
|
||||
:param url
|
||||
:param id
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
# {"id": "d58097084d43324643efde5cc8d30643901c27366d35238801a2119509352ab7", "dk_status": "running"}
|
||||
|
||||
try:
|
||||
args.validate([
|
||||
Param('id').Require().String(),
|
||||
Param('dk_status').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, ex)
|
||||
try:
|
||||
container = self.docker_client(self._url).containers.get(args.id)
|
||||
stats = container.stats(decode=None, stream=False)
|
||||
stats['time'] = time.time()
|
||||
cache = public.cache_get('stats')
|
||||
if not cache:
|
||||
cache = stats
|
||||
public.cache_set('stats', stats)
|
||||
write = None
|
||||
if hasattr(args, "write"):
|
||||
write = args.write
|
||||
self.__stats_tmp['expired'] = time.time() - (args.save_date * 86400)
|
||||
stats['id'] = args.id
|
||||
import json
|
||||
from pygments import highlight, lexers, formatters
|
||||
formatted_json = json.dumps(stats, indent=3)
|
||||
colorful_json = highlight(formatted_json.encode('utf-8'), lexers.JsonLexer(), formatters.TerminalFormatter())
|
||||
# print(colorful_json)
|
||||
self.io_stats(stats, write)
|
||||
self.net_stats(stats, cache, write)
|
||||
self.cpu_stats(stats, write)
|
||||
self.mem_stats(stats, write)
|
||||
public.cache_set('stats', stats)
|
||||
self.__stats_tmp['detail'] = stats
|
||||
if 'dk_status' in args and args.dk_status != 'running':
|
||||
self.__stats_tmp['read_total'] = 0
|
||||
self.__stats_tmp['write_total'] = 0
|
||||
return public.return_message(0, 0, self.__stats_tmp)
|
||||
except Exception as ex:
|
||||
if "No such container" in str(ex):
|
||||
return public.return_message(-1, 0, public.lang("The container does not exist, please refresh the browser and try again!"))
|
||||
return public.return_message(-1, 0, public.lang('Failed to get container status: ' + str(ex)))
|
||||
|
||||
def top(self, get):
|
||||
"""
|
||||
获取容器内进程信息
|
||||
@param get:
|
||||
@return:
|
||||
"""
|
||||
container = self.docker_client(self._url).containers.get(get.id)
|
||||
return public.return_message(0, 0, container.top())
|
||||
|
||||
def write_cpu(self, data):
|
||||
pdata = {
|
||||
"time": time.time(),
|
||||
"cpu_usage": data['cpu_usage'],
|
||||
"online_cpus": data['online_cpus'],
|
||||
"container_id": data['container_id']
|
||||
}
|
||||
dp.sql("cpu_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
|
||||
dp.sql("cpu_stats").insert(pdata)
|
||||
|
||||
def write_io(self, data):
|
||||
pdata = {
|
||||
"time": time.time(),
|
||||
"write_total": data['write_total'],
|
||||
"read_total": data['read_total'],
|
||||
"container_id": data['container_id']
|
||||
}
|
||||
dp.sql("io_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
|
||||
dp.sql("io_stats").insert(pdata)
|
||||
|
||||
def write_net(self, data):
|
||||
pdata = {
|
||||
"time": time.time(),
|
||||
"tx_total": data['tx_total'],
|
||||
"rx_total": data['rx_total'],
|
||||
"tx": data['tx'],
|
||||
"rx": data['rx'],
|
||||
"container_id": data['container_id']
|
||||
}
|
||||
dp.sql("net_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
|
||||
dp.sql("net_stats").insert(pdata)
|
||||
|
||||
def write_mem(self, data):
|
||||
pdata = {
|
||||
"time": time.time(),
|
||||
"mem_limit": data['limit'],
|
||||
"cache": data['cache'],
|
||||
"usage": data['usage'],
|
||||
"usage_total": data['usage_total'],
|
||||
"container_id": data['container_id']
|
||||
}
|
||||
dp.sql("mem_stats").where("time<?", (self.__stats_tmp['expired'],)).delete()
|
||||
dp.sql("mem_stats").insert(pdata)
|
||||
|
||||
# 获取某服务器容器总数
|
||||
def get_container_count(self, args):
|
||||
return public.return_message(0, 0, len(self.docker_client(self._url).containers.list()))
|
||||
|
||||
# 获取监控容器资源并记录每分钟
|
||||
|
||||
# 获取Docker总览信息
|
||||
def get_docker_system_info(self, get):
|
||||
result = {
|
||||
"containers": {"usage": "-", "total": "-", "size": "-"},
|
||||
"images": {"usage": "-", "total": "-", "size": "-"},
|
||||
"volumes": {"usage": "-", "total": "-", "size": "-"},
|
||||
"networks": {"usage": 0, "total": 0},
|
||||
"composes": {"usage": 0, "total": 0},
|
||||
"mirrors": 0,
|
||||
}
|
||||
|
||||
try:
|
||||
# 获取 mirrors
|
||||
system_info = self.docker_client(self._url).info()
|
||||
result["mirrors"] = len(system_info.get("RegistryConfig", {}).get("IndexConfigs", {}))
|
||||
|
||||
# 解析 docker system df
|
||||
dfs, err = public.ExecShell("docker system df --format json")
|
||||
|
||||
for line in dfs.strip().split('\n'):
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
df = json.loads(line)
|
||||
df_type = df.get("Type")
|
||||
if df_type == "Images":
|
||||
result["images"]["usage"] = df.get("Active", 0)
|
||||
result["images"]["total"] = df.get("TotalCount", 0)
|
||||
result["images"]["size"] = df.get("Size", "0B")
|
||||
elif df_type == "Containers":
|
||||
result["containers"]["usage"] = df.get("Active", 0)
|
||||
result["containers"]["total"] = df.get("TotalCount", 0)
|
||||
result["containers"]["size"] = df.get("Size", "0B")
|
||||
elif df_type == "Local Volumes":
|
||||
result["volumes"]["usage"] = df.get("Active", 0)
|
||||
result["volumes"]["total"] = df.get("TotalCount", 0)
|
||||
result["volumes"]["size"] = df.get("Size", "0B")
|
||||
except Exception as e:
|
||||
public.print_log('Docker', 'Parse docker system df failed: {}, error: {}'.format(line, str(e)))
|
||||
continue
|
||||
|
||||
# Networks
|
||||
network_info = self.docker_client(self._url).networks.list()
|
||||
result["networks"]["total"] = len(network_info)
|
||||
|
||||
# Compose
|
||||
from mod.project.docker.composeMod import main as Compose
|
||||
dk_compose = Compose()
|
||||
compose_list = dk_compose.ls(get)
|
||||
result["composes"]["total"] = len(compose_list)
|
||||
|
||||
except Exception as e:
|
||||
public.print_log('Docker', 'Get system info failed: {}'.format(str(e)))
|
||||
|
||||
return public.return_message(0, 0, result)
|
||||
158
class_v2/btdockerModelV2/volumeModel.py
Normal file
158
class_v2/btdockerModelV2/volumeModel.py
Normal file
@@ -0,0 +1,158 @@
|
||||
# coding: utf-8
|
||||
# -------------------------------------------------------------------
|
||||
# YakPanel
|
||||
# -------------------------------------------------------------------
|
||||
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# -------------------------------------------------------------------
|
||||
# Author: wzz <wzz@yakpanel.com>
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# ------------------------------
|
||||
# Docker模型
|
||||
# ------------------------------
|
||||
import docker.errors
|
||||
import public
|
||||
from btdockerModelV2 import dk_public as dp
|
||||
from btdockerModelV2.dockerBase import dockerBase
|
||||
from public.validate import Param
|
||||
|
||||
class main(dockerBase):
|
||||
|
||||
def docker_client(self, url):
|
||||
return dp.docker_client(url)
|
||||
|
||||
def get_volume_container_name(self, volume_detail, container_list):
|
||||
'''
|
||||
拼接对应的容器名与卷名
|
||||
@param volume_detail: 卷字典
|
||||
@param container_list: 容器详情列表
|
||||
@return:
|
||||
'''
|
||||
try:
|
||||
for container in container_list:
|
||||
if not container['Mounts']:
|
||||
continue
|
||||
for mount in container['Mounts']:
|
||||
if "Name" not in mount:
|
||||
continue
|
||||
if volume_detail['Name'] == mount['Name']:
|
||||
volume_detail['container'] = container['Names'][0].replace("/", "")
|
||||
if 'container' not in volume_detail:
|
||||
volume_detail['container'] = ''
|
||||
except:
|
||||
volume_detail['container'] = ''
|
||||
|
||||
return volume_detail
|
||||
|
||||
def get_volume_list(self, args):
|
||||
"""
|
||||
:param self._url: 链接docker的URL
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
data = list()
|
||||
from btdockerModelV2.dockerSock import volume
|
||||
sk_volume = volume.dockerVolume()
|
||||
volume_list = sk_volume.get_volumes()
|
||||
|
||||
from btdockerModelV2.dockerSock import container
|
||||
sk_container = container.dockerContainer()
|
||||
container_list = sk_container.get_container()
|
||||
|
||||
if "Volumes" in volume_list and type(volume_list["Volumes"]) == list:
|
||||
for v in volume_list["Volumes"]:
|
||||
v["CreatedAt"] = dp.convert_timezone_str_to_timestamp(v["CreatedAt"])
|
||||
data.append(self.get_volume_container_name(v, container_list))
|
||||
|
||||
return public.return_message(0, 0, sorted(data, key=lambda x: x['CreatedAt'], reverse=True))
|
||||
else:
|
||||
return public.return_message(0, 0, [])
|
||||
except Exception as e:
|
||||
return public.return_message(-1, 0, [])
|
||||
|
||||
def add(self, args):
|
||||
"""
|
||||
添加一个卷
|
||||
:param name
|
||||
:param driver local
|
||||
:param driver_opts (dict) – Driver options as a key-value dictionary
|
||||
:param labels str
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
args.driver_opts = args.get("driver_opts", "")
|
||||
args.labels = args.get("labels", "")
|
||||
if args.driver_opts != "":
|
||||
args.driver_opts = dp.set_kv(args.driver_opts)
|
||||
if args.labels != "":
|
||||
args.labels = dp.set_kv(args.labels)
|
||||
|
||||
if len(args.name) < 2:
|
||||
return public.return_message(-1, 0, public.lang("Volume names can be no less than 2 characters long!"))
|
||||
|
||||
self.docker_client(self._url).volumes.create(
|
||||
name=args.name,
|
||||
driver=args.driver,
|
||||
driver_opts=args.driver_opts if args.driver_opts else None,
|
||||
labels=args.labels if args.labels != "" else None
|
||||
)
|
||||
dp.write_log("Add storage volume [{}] success!".format(args.name))
|
||||
return public.return_message(0, 0, public.lang("successfully added!"))
|
||||
except docker.errors.APIError as e:
|
||||
if "volume name is too short, names should be at least two alphanumeric characters" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Volume names can be no less than 2 characters long!"))
|
||||
if "volume name" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Volume name already exists!"))
|
||||
return public.return_message(-1, 0, public.lang("addition failed {}", e))
|
||||
|
||||
except Exception as e:
|
||||
if "driver_opts must be a dictionary" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("Driver option tags must be dictionary/key-value pairs!"))
|
||||
return public.return_message(-1, 0, public.lang("Add failed! {}", e))
|
||||
|
||||
def remove(self, args):
|
||||
"""
|
||||
删除一个卷
|
||||
:param name volume name
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
|
||||
# 校验参数
|
||||
try:
|
||||
args.validate([
|
||||
Param('name').Require().String(),
|
||||
], [
|
||||
public.validate.trim_filter(),
|
||||
])
|
||||
except Exception as ex:
|
||||
public.print_log("error info: {}".format(ex))
|
||||
return public.return_message(-1, 0, str(ex))
|
||||
try:
|
||||
obj = self.docker_client(self._url).volumes.get(args.name)
|
||||
obj.remove()
|
||||
dp.write_log("Delete volume [{}] successful!".format(args.name))
|
||||
return public.return_message(0, 0, public.lang("successfully delete"))
|
||||
|
||||
except docker.errors.APIError as e:
|
||||
if "volume is in use" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("The storage volume is in use and cannot be deleted!"))
|
||||
if "no such volume" in str(e):
|
||||
return public.return_message(-1, 0, public.lang("The storage volume does not exist!"))
|
||||
return public.return_message(-1, 0, public.lang("Delete failed! {}", e))
|
||||
|
||||
def prune(self, args):
|
||||
"""
|
||||
删除无用的卷
|
||||
:param args:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
res = self.docker_client(self._url).volumes.prune()
|
||||
if not res['VolumesDeleted']:
|
||||
return public.return_message(-1, 0, public.lang("No useless storage volumes!"))
|
||||
|
||||
dp.write_log("Delete useless storage volume successfully!")
|
||||
return public.return_message(0, 0, public.lang("successfully delete!"))
|
||||
except docker.errors.APIError as e:
|
||||
return public.return_message(-1, 0, public.lang("Delete failed! {}", e))
|
||||
Reference in New Issue
Block a user