Update to 7.7.0

Since version 7.7.0, we recommend yours update python to 3.12.

[+] Using nginx technology to load static files improves access speed
[+] Refactor homepage, website, FTP, and database using vue3
[+] Table loading changed to skeleton screen
[+] Add Website statistics-v2 professional plug-in
[+] Add Home page - top 5 resource occupancy
[+] Add protection for Files management (requires Tamper-proof for Enterprise 3.7)
[+] Website, FTP, Databases page add program status
[+] Add FTP log analysis (only supports Centos)
[+] Add password-free login to phpMyAdmin
[+] Add Proxy Project in Website (Supported when web service uses Nginx)
[+] Add WP Toolkit (Pro version only)
[+] Redesigned Docker module
[+] Add WP Toolkit Protection
[+] Add WP Toolkit Backup and Restore
[+] Add WP Toolkit Migrated
[+] Add WP Toolkit Clone site (supports new domain and subdomain)
[+] Add WP Toolkit Create site from backup of other panel
[+] Add WP Toolkit support for Cron automatic backup (only save Local disk)
[+] Add WP Toolkit operation log
[+] Add Integrity check for WP Toolkit
[+] Add WP Toolkit plug-in management and themes management

[*] Optimize phpMyAdmin formula access method
[*] Optimize Home page PHP display problem
[*] Optimize jump to the login interface after the login expires
[*] Optimize automatic renewal of SSL at some times
[*] Optimize Let's Encrypt to increase application success rate

[-] Fix Logs Audit cannot be opened
[-] Fix apache URL rewrite issue
[-] Fix phpmyadmin installation problem
[-] Fix the problem that some servers cannot install software
[-] Fix upload file error
[-] Fix left menu hiding problem
[-] Fix aaPanel Mobile QR code display problem
[-] Fix problem that third-party plug-ins are not displayed in the App Store
[-] Fix issue where the menu bar is blank when opening new tabs
[-] Fixed panel not being accessible in some cases
[-] Fix the issue where Curl warning caused the inability to apply for SSL
[-] Fix Quota issues for Website, FTP, Databases
[-] Fix file interface display problem on mobile terminal
This commit is contained in:
Jack
2024-07-19 11:25:10 +08:00
parent ed34994f45
commit ed55fa708d
1949 changed files with 329210 additions and 26644 deletions
+79
View File
@@ -0,0 +1,79 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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
+185
View File
@@ -0,0 +1,185 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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 v11olume
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, _("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, _("The backup task was created successfully."))
except Exception as e:
print(traceback.format_exc())
return public.return_message(-1, 0, _("Failed to create a backup task {}".format(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, _("successfully delete"))
os.remove(file_path)
return public.return_message(0, 0, _("successfully delete"))
except Exception as e:
print(traceback.format_exc())
return public.return_message(-1, 0, _("{} Failed to delete the file, reason: {}".format(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)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,3 @@
{
"https://docker.m.daocloud.io": "Third party image accelerator"
}
@@ -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
Binary file not shown.
File diff suppressed because it is too large Load Diff
+293
View File
@@ -0,0 +1,293 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
# 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 docker")
import docker
try:
client = docker.DockerClient(base_url=url)
if client:
return client
return False
except:
return False
def docker_client_low(url="unix:///var/run/docker.sock"):
"""
docker 低级接口
:param url:
:return:
"""
try:
import docker
except:
public.ExecShell("btpip install 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 "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 "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 module", 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)
dt = datetime.strptime(timestamp_str, "%Y-%m-%dT%H:%M:%S%z")
# 转换时区为 UTC
dt_utc = dt.astimezone(timezone.utc)
# 转换为时间戳
timestamp = dt_utc.timestamp()
return timestamp
+459
View File
@@ -0,0 +1,459 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.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
View File
@@ -0,0 +1,117 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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("{}\r\n".format(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("bt_successful\r\n")
del get._ws
break
elif "bt_failed" in line:
get._ws.send("bt_failed\r\n")
del get._ws
break
if sum > 0:
sum = 0
else:
sum += 1
if sum >= 6000:
get._ws.send("\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, _("Please pass in cmd"))
if "docker run" not in get.cmd and "docker pull" not in get.cmd:
return public.return_message(-1, 0, _('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, _( 'Dangerous command exists: [{}], execution is not allowed!'.format(d)))
for d in danger_symbol:
if d in get.cmd:
return public.return_message(-1, 0, _( 'Dangerous symbol exists: [{}], execution is not allowed!'.format(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, _("The command has been executed!"))
@@ -0,0 +1,48 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
# docker模型sock 封装库 容器库
# -------------------------------------------------------------------
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:
print(public.get_error_info())
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:
print(public.get_error_info())
return []
@@ -0,0 +1,80 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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:
print(public.get_error_info())
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:
# if os.path.exists('data/debug.pl'):
# print(public.get_error_info())
# public.print_log(public.get_error_info())
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\" --data-binary @{}"
.format(self._sock, self.get_api_version(), path))[0])
except Exception as e:
print(public.get_error_info())
return False
# 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:
print(public.get_error_info())
return {}
@@ -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__()
@@ -0,0 +1,34 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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:
print(public.get_error_info())
return []
+60
View File
@@ -0,0 +1,60 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@aapanel.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,"The host already exists")
# 测试连接
if not dp.docker_client(args.url):
return public.returnMsg(False,"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,"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,"Delete host successfully")
+733
View File
@@ -0,0 +1,733 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import os
import json
import traceback
import docker.errors
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
import gettext
_ = gettext.gettext
class main(dockerBase):
def docker_client(self, url):
return dp.docker_client(url)
# 导出
def save(self, get):
"""
:param path 要镜像tar要存放的路径
:param name 包名
:param id 镜像
:param
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('path').Require().SafePath(),
Param('name').Require().String(),
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:
# if "name" not in get or get.name == "":
# return public.returnMsg(False, "Image name cannot be empty")
# if "path" not in get or get.path == "":
# return public.returnMsg(False, "Mirror path cannot be empty")
# if "id" not in get or get.id == "":
# return public.returnMsg(False, "Image ID cannot be empty")
if "/" in get.name:
return public.return_message(-1, 0, _("The image name cannot contain /"))
if "tar" in get.name:
filename = '{}/{}'.format(get.path, get.name)
else:
filename = '{}/{}.tar'.format(get.path, get.name)
if not os.path.exists(get.path): os.makedirs(get.path)
public.writeFile(filename, "")
with open(filename, 'wb') as f:
image = self.docker_client(self._url).images.get(get.id)
print(image)
for chunk in image.save(named=True):
f.write(chunk)
dp.write_log("Image [{}] exported to [{}] successfully".format(get.id, filename))
return public.return_message(0, 0, "Successfully saved to:{}".format(filename))
except docker.errors.APIError as e:
if "empty export - not implemented" in str(e):
return public.return_message(-1, 0, "Cannot export image!")
return public.get_error_info()
except Exception as e:
if "Read timed out" in str(e):
return public.return_message(-1, 0,
"Exporting the image failed and the connection to docker timed out. Please try restarting docker and try again!")
return public.return_message(-1, 0, "Failed to export image!<br> {}".format(e))
# 导入
def load(self, get):
"""
:param path: 需要导入的镜像路径具体到文件名
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('path').Require().SafePath(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
try:
if "path" not in get and get.path == "":
return public.return_message(-1, 0, "Please enter the image path!")
# 2023/12/20 下午 4:12 判断如果path后缀不为.tar则返回错误
if not get.path.endswith(".tar"):
return public.return_message(-1, 0, "Failed to import the image. The file extension must be.tar!")
from btdockerModelV2.dockerSock import image
sk_image = image.dockerImage()
sk_image.load_image(get.path)
dp.write_log("Image [{}] imported successfully!".format(get.path))
return public.return_message(0, 0, "Image import was successful!{}".format(get.path))
except Exception as e:
if "Read timed out" in str(e):
return public.return_message(-1, 0,
"Exporting the image failed and the connection to docker timed out. Please try restarting docker and try again!")
if "no such file or directory" in str(e):
return public.return_message(-1, 0,
"The image import failed and the temporary directory of the container failed to be created. Please check whether the protection software has an interception record!")
return public.return_message(-1, 0, "Failed to import image!<br> {}".format(e))
# 列出所有镜像
def image_list(self, get):
"""
:param url
:param get:
:return:
"""
try:
from btdockerModelV2.dockerSock import image
sk_image = image.dockerImage()
sk_images_list = sk_image.get_images()
from btdockerModelV2.dockerSock import container
sk_container = container.dockerContainer()
container_list = sk_container.get_container()
# if not container_list:
# return public.return_message(0, 0, data)
data = list()
# public.print_log("data000 : {}".format(data))
# public.print_log("sk_images_镜像列表 sk_images_list: {}".format(sk_images_list))
for image in sk_images_list:
# public.print_log("image if111111: {}".format(image))
# {'Containers': -1, 'Created': 1717026901,
# 'Id': 'sha256:4f67c83422ec747235357c04556616234e66fc3fa39cb4f40b2d4441ddd8f100',
# 'Labels': {'maintainer': 'NGINX Docker Maintainers <docker-maint@nginx.com>'}, 'ParentId': '',
# 'RepoDigests': ['nginx@sha256:0f04e4f646a3f14bf31d8bc8d885b6c951fdcf42589d06845f64d18aec6a3c4d'],
# 'RepoTags': ['nginx:latest'], 'SharedSize': -1, 'Size': 187667860}
if image is None:
continue
if image['RepoTags'] is not None and len(image['RepoTags']) != 0:
# public.print_log("data2 if111111: {}".format(data))
for tag in image['RepoTags']:
tmp = {
"id": image['Id'],
"tags": tag,
"name": tag,
"digest": image['RepoDigests'][0].split("@")[1] if image['RepoDigests'] else "",
"time": image['Created'] if type(image['Created']) == int else None,
"size": image['Size'],
"created_at": image['Created'],
"used": 0,
"containers": [],
}
# public.print_log("tmp tmp tmp: {}".format(tmp))
# {'id': 'sha256:4f67c83422ec747235357c04556616234e66fc3fa39cb4f40b2d4441ddd8f100',
# 'tags': 'nginx:latest', 'name': 'nginx:latest',
# 'digest': 'sha256:0f04e4f646a3f14bf31d8bc8d885b6c951fdcf42589d06845f64d18aec6a3c4d',
# 'time': 1717026901, 'size': 187667860, 'created_at': 1717026901, 'used': 0, 'containers': []}
# public.print_log("container_list if: {}".format(container_list))
self.structure_images_list(container_list, tmp)
# public.print_log("data jhshs哈666666 if: {}".format(data))
data.append(tmp)
# public.print_log("data2 if: {}".format(data))
else:
# public.print_log("data2 if: {}".format(data))
tmp = {
"id": image['Id'],
"tags": "<none>",
"name": "<none>",
"digest": image['RepoDigests'][0].split("@")[1] if image['RepoDigests'] else "",
"time": image['Created'] if type(image['Created']) == int else None,
"size": image['Size'],
"created_at": image['Created'],
"used": 0,
"containers": [],
}
self.structure_images_list(container_list, tmp)
# public.print_log("data2333 if: {}".format(data))
data.append(tmp)
# public.print_log("data2 else : {}".format(data))
# public.print_log("data2kjefa : {}".format(type(data)))
return public.return_message(0, 0, data)
except Exception as ex:
import traceback
# public.print_log("尺码个| info: {}".format(ex))
public.print_log(traceback.format_exc())
return public.return_message(0, 0, data)
def structure_images_list(self, container_list, image_info):
'''
@name
@author wzz <2024/5/22 下午5:53>
@param "data":{"参数名":""} <数据类型> 参数描述
@return dict{"status":True/False,"msg":"提示信息"}
'''
try:
for container in container_list:
if image_info['id'] in container['ImageID']:
image_info['used'] = 1
image_info['containers'].append({
"container_id": container['Id'],
"container_name": dp.rename(container['Names'][0].replace("/", "")),
})
except:
pass
def get_image_attr(self, images):
image = images.list()
return [i.attrs for i in image]
def get_logs(self, get):
# 校验参数
try:
get.validate([
Param('logs_file').Require().SafePath(),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
import files
logs_file = get.logs_file
return public.return_message(0, 0, files.files().GetLastLine(logs_file, 20))
# 构建镜像
def build(self, get):
"""
:param path dockerfile dir
:param pull 如果引用的镜像有更新自动拉取
:param tag 标签 jose:v1
:param data 在线编辑配置
:param get:
:return:
"""
# # 校验参数
# try:
# get.validate([
# Param('path').Require().SafePath(),
# ], [
# public.validate.trim_filter(),
# ])
# except Exception as ex:
# public.print_log("error info: {}".format(ex))
# return public.return_message(-1, 0, str(ex))
public.writeFile(self._log_path, "Start building the image!")
if not hasattr(get, "pull"):
get.pull = False
min_time = None
if hasattr(get, "data") and get.data:
min_time = public.format_date("%Y%m%d%H%M")
get.path = "/tmp/{}/Dockerfile".format(min_time)
os.makedirs("/tmp/{}".format(min_time), exist_ok=True)
public.writeFile(get.path, get.data)
if not os.path.exists(get.path):
return public.return_message(-1, 0, "Please enter the correct DockerFile path!")
try:
# 2024/1/18 下午 12:05 取get.path的目录
get.path = os.path.dirname(get.path)
image_obj, generator = self.docker_client(self._url).images.build(
path=get.path,
pull=True if get.pull == "1" else False,
tag=get.tag,
forcerm=True
)
if min_time is not None:
public.ExecShell("rm -rf {}".format(get.path))
dp.log_docker(generator, "Docker Build tasks!")
dp.write_log("Build image [{}] successful!".format(get.tag))
return public.return_message(0, 0, "Build image successfully!")
except docker.errors.BuildError as e:
if "TLS handshake timeout" in str(e):
return public.return_message(-1, 0, "Build failed, connection timed out")
return public.return_message(-1, 0, "Build failed! {}".format(e))
except docker.errors.APIError as e:
if "Cannot locate specified Dockerfile" in str(e):
return public.return_message(-1, 0, "Build failed!The specified Dockerfile was not found")
return public.return_message(-1, 0, "Build failed!{}".format(e))
except Exception as e:
return public.return_message(-1, 0, "Build failed!{}".format(e))
# 删除镜像
def remove(self, get):
"""
:param url
:param id 镜像id
:param name 镜像tag
:force 0/1 强制删除镜像
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('force').Require().Integer(),
Param('name').Require().String(),
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:
from btdockerModelV2.dockerSock import image
sk_image = image.dockerImage()
image_inspect = sk_image.inspect(get.name)
if not image_inspect:
self.docker_client(self._url).images.remove(get.id)
else:
self.docker_client(self._url).images.remove(get.name)
dp.write_log("Deletion of image【{}】successful!".format(get.name))
return public.return_message(0, 0, "Mirror deleted successfully!")
except docker.errors.ImageNotFound as e:
return public.return_message(-1, 0, "The delete failed and the image may not exist!")
except docker.errors.APIError as e:
if "image is referenced in multiple repositories" in str(e):
return public.return_message(-1, 0,
"The image ID is used in more than one image, force the image to be deleted!")
if ("using its referenced image" in str(e) or
"image is being used by stopped container" in str(e) or
"image is being used by running container" in str(e)):
return public.return_message(-1, 0,
"The image is in use. Please delete the container before deleting the image!")
return public.return_message(-1, 0, "Failed to delete image!<br> {}".format(e))
except Exception as e:
if "Read timed out" in str(e):
return public.return_message(-1, 0,
"Failed to delete image,The connection to docker timed out, please restart and try again!")
return public.return_message(-1, 0, "Failed to delete image!<br> {}".format(e))
# 拉取指定仓库镜像
def pull_from_some_registry(self, get):
"""
:param name 仓库名11
:param url
:param image
:param get:
:return:
"""
if not hasattr(get, "_ws"):
return True
from btdockerModelV2 import registryModel as dr
try:
if get.name == "Docker public repository":
login = dr.main().login(self._url, "docker.io", None, None)['status']
if not login:
get._ws.send(
"bt_failed, Login to the repository [docker.io] failed, please try to log in to this repository again!\r\n")
return login
r_info = {
"url": "docker.io",
"username": None,
"password": None,
"namespace": "library"
}
else:
r_info = dr.main().registry_info(get.name)
r_info['username'] = public.aes_decrypt(r_info['username'], self.aes_key)
r_info['password'] = public.aes_decrypt(r_info['password'], self.aes_key)
login = dr.main().login(self._url, r_info['url'], r_info['username'], r_info['password'])['status']
if not login:
get._ws.send("bt_failed, {}\r\n".format(login['msg']))
return login
except Exception as e:
get._ws.send(
"bt_failed, Login to repository [{}] failed, please try to log in to this repository again!\r\n".format(
get.name))
return public.returnMsg(False,
"bt_failed, Login to repository [{}] failed, please try to log in to this repository again!".format(
get.name))
get.username = r_info['username']
get.password = r_info['password']
get.registry = r_info['url']
get.namespace = r_info['namespace']
# public.print_log('准备拉取镜像 123--')
return self.pull(get)
# 推送镜像到指定仓库
def push(self, get):
"""
:param id 镜像ID
:param url 连接docker的url
:param tag 标签 镜像名+版本号v1
:param name 仓库名
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('tag').Require().String(),
Param('name').Require().String(),
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))
if "/" in get.tag:
return public.return_message(-1, 0, "The pushed image cannot contain [/], please use the following "
"format: image:v1 (image name: version)")
if ":" not in get.tag:
get.tag = "{}:latest".format(get.tag)
public.writeFile(self._log_path, "Start pushing the image!\n")
from btdockerModelV2 import registryModel as dr
r_info = dr.main().registry_info(get.name)
r_info['username'] = public.aes_decrypt(r_info['username'], self.aes_key)
r_info['password'] = public.aes_decrypt(r_info['password'], self.aes_key)
if get.name == "docker official" and r_info['url'] == "docker.io":
public.writeFile(self._log_path, "The image cannot be pushed to the Docker public repository!\n")
return public.return_message(-1, 0, "Unable to push to Docker public repository!")
try:
login = dr.main().login(self._url, r_info['url'], r_info['username'], r_info['password'])['status']
if not login:
return public.return_message(-1, 0, "Repository [{}] Login failed!".format(r_info['url']))
auth_conf = {
"username": r_info['username'],
"password": r_info['password'],
"registry": r_info['url']
}
# repository namespace/image
repository = r_info['url']
image = "{}/{}/{}".format(repository, r_info['namespace'], get.tag)
self.tag(self._url, get.id, image)
ret = self.docker_client(self._url).images.push(
repository=image.split(":")[0],
tag=image.split(":")[1],
auth_config=auth_conf,
stream=True
)
dp.log_docker(ret, "Image push task")
# 删除自动打标签的镜像
get.name = image
self.remove(get)
except docker.errors.APIError as e:
if "invalid reference format" in str(e):
return public.return_message(-1, 0, "Push failed, image label error, please enter such as: v1.0.1")
if "denied: requested access to the resource is denied" in str(e):
return public.return_message(-1, 0, "Push failed, do not have permission to push to this repository!")
return public.return_message(-1, 0, "Push failure!{}".format(e))
dp.write_log("Image [{}] pushed successfully!".format(image))
return public.return_message(0, 0, "Push successfully, mirror:{}".format(image))
def tag(self, url, image_id, tag):
"""
为镜像打标签
:param repository 仓库namespace/images
:param image_id: 镜像ID
:param tag: 镜像标签jose:v1
:return:
"""
image = tag.split(":")[0]
tag_ver = tag.split(":")[1]
self.docker_client(url).images.get(image_id).tag(
repository=image,
tag=tag_ver
)
return public.returnMsg(True, "Successfully set!")
def pull(self, get):
"""
:param image
:param url
:param registry
:param username 拉取私有镜像时填写 1
:param password 拉取私有镜像时填写
:param get:
:return:
"""
get._ws.send("Pulling the image, please wait...\r\n")
try:
get._ws.send("Pulling the image, please wait...\r\n")
import docker.errors
import time
time.sleep(0.1)
get._ws.send("Pull or search for images...\r\n")
try:
get.image = '{}:latest'.format(get.image) if ':' not in get.image else get.image
auth_data = {
"username": get.username,
"password": get.password,
"registry": get.registry if get.registry else None
}
auth_conf = auth_data if get.username else None
if not hasattr(get, "tag"): get.tag = get.image.split(":")[-1]
if get.registry != "docker.io":
get.image = "{}/{}/{}".format(get.registry, get.namespace, get.image)
ret = dp.docker_client_low(self._url).pull(
repository=get.image.split(":")[0],
auth_config=auth_conf,
tag=get.tag,
stream=True
)
if not ret:
get._ws.send("bt_failed, pull failed!\r\n")
return
while True:
try:
output = next(ret)
output = json.loads(output)
if 'status' in output:
output_str = output['status']
get._ws.send(output_str + "\r\n")
time.sleep(0.1)
except StopIteration:
get._ws.send("bt_successful, Image pull [{}] successful\r\n".format(get.image))
return public.returnMsg(True, "Image pulled successfully!")
except ValueError:
get._ws.send("bt_failed, Failed to pull image!\r\n")
return public.returnMsg(False, "Failed to pull image!")
except docker.errors.ImageNotFound as e:
if "pull access denied for" in str(e):
get._ws.send(
"bt_failed, pull failed,The image does not exist, or the image may be a private image. You need to enter your dockerhub account password!\r\n")
return
get._ws.send("bt_failed, pull failed!{}\r\n".format(e))
return
except docker.errors.NotFound as e:
if "not found: manifest unknown" in str(e):
get._ws.send("bt_failed, pull failed,There is no such image in the repository!\r\n")
return
get._ws.send("bt_failed, pull failed!{}\r\n".format(e))
return
except docker.errors.APIError as e:
if "invalid tag format" in str(e):
get._ws.send("bt_failed, pull failed, The image format is wrong, such as: nginx:v 1!\r\n")
return
get._ws.send("bt_failed, pull failed!{}\r\n".format(e))
return
except Exception as e:
# public.print_log("拉取镜像 -- {}".format(e))
public.print_log(traceback.format_exc())
# 拉取镜像
def pull_high_api(self, get):
"""
:param image
:param url
:param registry
:param username 拉取私有镜像时填写
:param password 拉取私有镜像时填写
:param get:
:return:
"""
import docker.errors
try:
if ':' not in get.image:
get.image = '{}:latest'.format(get.image)
auth_data = {
"username": get.username,
"password": get.password,
"registry": get.registry if get.registry else None
}
auth_conf = auth_data if get.username else None
if get.registry != "docker.io":
get.image = "{}/{}/{}".format(get.registry, get.namespace, get.image)
ret = self.docker_client(get.url).images.pull(repository=get.image, auth_config=auth_conf)
if ret:
return public.returnMsg(True, 'The image was pulled successfully.')
else:
return public.returnMsg(False, 'There may not be this mirror image.')
except docker.errors.ImageNotFound as e:
if "pull access denied for" in str(e):
return public.returnMsg(False,
"Failed to pull the image, this is a private image, please enter the account password!")
return public.returnMsg(False, "Pull image failure <br><br> Reason: {}".format(e))
def image_for_host(self, get):
"""
获取镜像大小和获取镜像数量
:param get:
:return:
"""
res = self.image_list(get)
if not res['status']: return res
num = len(res['msg']['images_list'])
size = 0
for i in res['msg']['images_list']:
size += i['size']
return public.returnMsg(True, {'num': num, 'size': size})
def prune(self, get):
"""
删除无用的镜像
:param get:
:return:
"""
dang_ling = True if "filters" in get and get.filters == "0" else False
try:
res = self.docker_client(self._url).images.prune(filters={'dangling': dang_ling})
if not res['ImagesDeleted']:
return public.return_message(0, 0, "No useless images!")
dp.write_log("Delete useless image successfully!")
return public.return_message(0, 0, "successfully delete!")
except docker.errors.APIError as e:
return public.return_message(-1, 0, "failed to delete!{}".format(e))
except Exception as e:
if error.find("Read timed out") != -1:
return public.return_message(-1, 0,
"Deletion of useless images failed and the connection to docker timed"
" out. Please try restarting the docker service and try again!")
return public.return_message(-1, 0, "failed to delete!{}".format(e))
# 2023/12/13 上午 11:08 镜像搜索 todo 关键字查询调用ws接口 暂时没查到
def search(self, get):
'''
@name 镜像搜索,docker hub官方镜像列表
从docker hub官方镜像列表获取最新排序镜像
数据库在/www/server/panel/class_v2/btdockerModelV2/config/docker_hub_repos.db
每隔1个月从官网同步一次
脚本在/www/server/panel/class_v2/btdockerModelV2/script/syncreposdb.py
@author wzz <2023/12/13 下午 3:41>
@param 参数名<数据类型> 参数描述
@return 数据类型
'''
try:
get.name = get.get("name/s", "")
if get.name == "":
# 2024/3/20 上午 10:10 如果get.name是空,则返回docker_hub_repos.db中results表的所有镜像
import db, os
sql = db.Sql()
sql.dbfile('{}/class_v2/btdockerModelV2/config/docker_hub_repos.db'.format(public.get_panel_path()))
# 2024/3/20 上午 10:24 按照star_count排序
results = sql.table('results').field('name,description,star_count,is_official').order(
'star_count desc').select()
if not results:
return public.return_message(0, 0, [])
return public.return_message(0, 0, results)
from btdockerModelV2.dockerSock import image
sk_image = image.dockerImage()
return public.return_message(0, 0, sk_image.search(get.name))
except Exception as e:
# if os.path.exists('data/debug.pl'):
# print(public.get_error_info())
public.print_log(public.get_error_info())
return public.return_message(-1, 0, [])
# 拉取容器日志
def get_cmd_log(self, get):
"""
拉取容器日志
@param get:
@return:
"""
get.wsLogTitle = "Start executing the command, please wait..."
get._log_path = self._rCmd_log
return self.get_ws_log(get)
+129
View File
@@ -0,0 +1,129 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@aapanel.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)
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)
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)
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()
+276
View File
@@ -0,0 +1,276 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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 = ""
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"]
tmp = {
"id": attr["Id"],
"name": attr["Name"],
"time": dp.convert_timezone_str_to_timestamp(attr["Created"]),
"driver": attr["Driver"],
"subnet": subnet,
"gateway": gateway,
"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, _(
"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
ipam_pool = docker.types.IPAMPool(
subnet=get.subnet,
gateway=get.gateway,
iprange=get.iprange
)
ipam_config = docker.types.IPAMConfig(
pool_configs=[ipam_pool]
)
try:
self.docker_client(self._url).networks.create(
name=get.name,
options=dp.set_kv(get.options),
driver="bridge",
ipam=ipam_config,
labels=dp.set_kv(get.labels)
)
except docker.errors.APIError as e:
print(str(e))
if "failed to allocate gateway" in str(e):
return public.return_message(-1, 0, _(
"The gateway setting is wrong, Please enter a gateway that matches the subnet: {}".format(
get.subnet)))
if "invalid CIDR address" in str(e):
return public.return_message(-1, 0, _(
"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, _(
"IP range format error, please enter the appropriate IP range for this subnet:".format(
get.subnet)))
if "Pool overlaps with other one on this address space" in str(e):
return public.return_message(-1, 0, _( "IP range [{}] already exists!".format(get.subnet)))
return public.return_message(-1, 0, _( "Failed to add network! {}".format(str(e))))
dp.write_log("Added network [{}] [{}] successful!".format(get.name, get.iprange))
return public.return_message(0, 0, _( "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, _( "The system default network cannot be deleted!"))
networks.remove()
dp.write_log("Delete network [{}] successfully!".format(attrs['Name']))
return public.return_message(0, 0, _( "successfully delete!"))
except docker.errors.APIError as e:
if " has active endpoints" in str(e):
return public.return_message(-1, 0, _( "The network cannot be deleted while it is in use!"))
return public.return_message(-1, 0, _( "Delete failed! {}".format(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, _( "There are no useless networks!"))
dp.write_log("Delete useless network successfully!")
return public.return_message(0, 0, _( "successfully delete!"))
except docker.errors.APIError as e:
return public.return_message(-1, 0, _( "Delete failed! {}".format(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, _( "Network ID cannot be empty"))
if get.container_id == "":
return public.return_message(-1, 0, _( "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, _( "Network disconnection was successful!"))
except docker.errors.APIError as e:
if "No such container" in str(e):
return public.return_message(-1, 0, _( "Container ID: {}, does not exist!".format(get.container_id)))
if "network" in str(e) and "Not Found" in str(e):
return public.return_message(-1, 0, _( "Network ID: {}, does not exist!".format(get.id)))
return public.return_message(-1, 0, _( "Network disconnection failed! {}".format(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, _( "Network connection successful!"))
except docker.errors.APIError as e:
if "No such container" in str(e):
return public.return_message(-1, 0, _( "Container ID: {}, does not exist!".format(get.container_id)))
if "network" in str(e) and "Not Found" in str(e):
return public.return_message(-1, 0, _( "Network ID: {}, does not exist!".format(get.id)))
return public.return_message(-1, 0, _( "Failed to connect to network! {}".format(str(e))))
+540
View File
@@ -0,0 +1,540 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2014-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
import gettext
_ = gettext.gettext
# ------------------------------
# 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 = []
try:
if not os.path.exists(self.info_path):
down_info = self.__download_info(self.info_path)
if not down_info["status"]:
return public.return_message(0, 0, 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 public.return_message(0, 0, project_info)
public.ExecShell("echo \"first\" > {}".format(self.__first_pl))
except Exception as e:
project_info = []
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.returnMsg(True, "info.json is downloaded!")
return public.returnMsg(False, "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.returnMsg(False, "{} Download failed, please resync!".format(server_name))
if os.path.getsize(filename) == 0:
os.remove(filename)
return public.returnMsg(False, "{} Download failed, please resync!".format(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.returnMsg(False, "{}yml file test failed,{}".format(server_name, check_conf[1]))
return public.returnMsg(True, "{} Download completed!".format(server_name))
except:
return public.returnMsg(False, "{} Download failed, please resync!".format(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.returnMsg(False, "{} Decompression failed".format(server_name))
return public.returnMsg(True, "{} extracted successfully".format(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.returnMsg(False, "Directory creation failed for the following reasons: {}".format(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.returnMsg(True, "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, _( "{} cannot contain Spaces".format(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, _( "{} cannot be null!".format(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, _( "Server port [{}] is occupied, please change to another port!".format(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.returnMsg(False, "Project startup failed {}".format(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.returnMsg(True, "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.returnMsg(False, "{} already exists, please change the project name".format(project_name))
mk_result = public.ExecShell("mkdir -p {}".format(project_dir))
if mk_result[1]: return public.returnMsg(False, "User project directory failed to create,details: {}".format(mk_result[1]))
cp_result = public.ExecShell("cp -a {}/. {}/".format(server_dir, project_dir))
if cp_result[1]: return public.returnMsg(False, "Failed to copy project directory. Details: {}".format(cp_result[1]))
return public.returnMsg(True, "")
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.returnMsg(True, 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": "aaPanel 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))
+301
View File
@@ -0,0 +1,301 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
import json
import os
import traceback
from datetime import datetime
import gettext
_ = gettext.gettext
# 未处理关键字
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, 'nginx or apache server was not detected, please install one first!')
# if not hasattr(get, 'domain'):
# return public.return_message(-1, 0, 'parameter error')
#
# if not hasattr(get, 'container_port'):
# return public.return_message(-1, 0, '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=? and ps!=?",
(newpid, 'Reverse proxy for the container [{}]'.format(get.container_name))).find()
if result:
return public.return_message(-1, 0, _(
'Project Type [{}] Existing Domain: {}'.format(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, _( "Server port [{}] is not used, please enter the port in use to reverse!".format(self.container_port)))
self.sitePath = '/www/wwwroot/' + self.siteName
from panelSite import panelSite
args = public.to_dict_obj({
'webname': '{"domain":"'+ self.siteName +'","domainlist":[],"count":0}',
'type': 'docker',
'port': "80",
'ps': self.siteName,
'path': self.sitePath,
'type_id': 111,
'version': "00",
'ftp': False,
'sql': False,
})
panelSite().AddSite(args)
args = public.to_dict_obj({
'type': 1,
'proxyname': get.container_name + '_dk_proxy',
'cachetime': 1,
'proxydir': '/',
'cache': 0,
'subfilter': '[{"sub1":"","sub2":""},{"sub1":"","sub2":""},{"sub1":"","sub2":""}]',
'sitename': self.siteName,
'advanced': 0,
'proxysite': 'http://127.0.0.1:' + self.container_port,
'todomain': '$host',
})
import projectModel.proxyModel as proxyModel
proxyModel = proxyModel.main()
proxyModel.CreateProxy(args)
# 设置面板SSL
if hasattr(get, "privateKey") and hasattr(get, "certPem"):
args = public.to_dict_obj({
'type': '1',
'siteName': self.siteName,
'key': get.privateKey,
'csr': get.certPem,
})
panelSite().SetSSL(args)
# 写入数据库
newpid = public.M('domain').where("name=? and port=?", (self.siteName, 80)).getField('pid')
if newpid:
# 更新ps和project_type字段
public.M('sites').where("id=?", (newpid,)).save('ps,project_type', (
'Reverse proxy for the container [{}]'.format(get.container_name),
'proxy'))
site_pid = dp.sql('dk_sites').add(
'name,path,ps,addtime,container_id,container_name,container_port',
(self.siteName, self.sitePath, 'Reverse proxy for the 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, _( 'Add failure, database cannot be written!'))
# 检查数据库是否存在
self.check_table_dk_domain()
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, _( 'successfully added!'))
except Exception as e:
return public.return_message(0, 0, _( 'Add failed, error {}!'.format(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, 'parameter error')
container_id = get.container_id
self.check_table_dk_sites()
proxy_info = dp.sql('dk_sites').where('container_id=?', (container_id,)).order('id desc').find()
# 没找到表
if isinstance(proxy_info, dict):
return public.return_message(-1, 0, proxy_info)
path = '/www/server/panel/vhost/cert/' + proxy_info['name']
csrpath = path + "/fullchain.pem"
keypath = path + "/privkey.pem"
if os.path.exists(csrpath) and os.path.exists(keypath):
try:
proxy_info['cert'] = public.readFile(csrpath)
proxy_info['key'] = public.readFile(keypath)
except:
proxy_info['cert'] = ""
proxy_info['key'] = ""
if not proxy_info:
return public.return_message(-1, 0, _( 'No reverse proxy information was detected!'))
return public.return_message(0, 0, proxy_info)
except Exception as ex:
print(traceback.format_exc())
public.print_log("error: {}".format(ex))
return public.return_message(-1, 0, {})
# 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, '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, _( '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, _( 'No reverse proxy information was detected!'))
result = public.M('sites').where("id=? and ps=?", (newpid, 'Reverse proxy for the container [{}]'.format(proxy_info["container_name"]))).find()
# 删除反向代理
import projectModel.proxyModel as proxyModel
proxyModel = proxyModel.main()
args = public.to_dict_obj({
'id': result['id'],
'webname': proxy_info['name'],
'type': 1,
})
proxyModel.DeleteSite(args)
# 删除站点
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, _( 'successfully delete!'))
except:
return 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, _( '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, _( '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(-1, 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_backup` (`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_backup` (`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 '')",
()
)
+293
View File
@@ -0,0 +1,293 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: zouhw <zhw@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import json
import gettext
_ = gettext.gettext
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)
r_list = self.registry_list(args)
if len(r_list) > 0:
for r in r_list:
if r['name'] == args.name:
return public.return_message(-1, 0, _( "The name already exists! <br><br>name: {}".format(args.name)))
if r['username'] == args.username and args.registry == r['url']:
return public.return_message(-1, 0, _( "Repository information already exists!"))
pdata = {
"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)
}
dp.sql("registry").insert(pdata)
dp.write_log("Added repository [{}] [{}] success!".format(args.name, args.registry))
return public.return_message(0, 0, _( "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, "[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, _(
"Editing failed! Reason: Account password decryption failed! Please delete the repository and add it again"))
return public.return_message(-1, 0, _( "Editing failed! Reason:{}".format(e)))
res = dp.sql("registry").where("id=?", (args.id,)).find()
if not res:
return public.return_message(-1, 0, _( "This repository could not be found"))
pdata = {
"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, _( "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, "[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, _( "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, _( "Successfully deleted!"))
def registry_list(self, get):
"""
获取仓库列表
:return:
"""
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 = []
return res
# 改返回
def registry_listV2(self, get):
"""
获取仓库列表
:return:
"""
db_obj = dp.sql("registry")
# 2024/1/3 下午 6:00 检测数据库是否存在并且表健康
search_result = db_obj.where('id=? or name=?', (1, "Docker public repository")).select()
# search_result = db_obj.where('id=? ', (1)).select()
# if db_obj.ERR_INFO:
# return public.return_message(0, 0, [])
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 = []
return public.return_message(0, 0, res)
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)
def registry_info(self, name):
return dp.sql("registry").where("name=?", (name,)).find()
def login(self, url, registry, username, password):
"""
仓库登录测试
:param args:
:return:
"""
import docker.errors
try:
res = self.docker_client(url).login(
registry=registry,
username=username,
password=password,
reauth=False
)
return public.returnMsg(True, str(res))
except docker.errors.APIError as e:
if "authentication required" in str(e):
return public.returnMsg(False,
"Login test failed! Reason: May be account password error, please check!")
if "unauthorized: incorrect username or password" in str(e):
return public.returnMsg(False,
"Login test failed! Reason: May be account password error, please check!")
return public.returnMsg(False, "Login test failed! Reason: {}".format(e))
+84
View File
@@ -0,0 +1,84 @@
#coding: utf-8
#-------------------------------------------------------------------
# aaPanel
#-------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
#-------------------------------------------------------------------
# Author: zouhw <zhw@aapanel.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
@@ -0,0 +1,134 @@
# 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文件中的bt.cn记录
@return:
"""
remove = 0
try:
import requests
requests.packages.urllib3.disable_warnings(requests.packages.urllib3.exceptions.InsecureRequestWarning)
url = 'https://www.bt.cn/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://www.bt.cn/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.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())))
@@ -0,0 +1,92 @@
#!/www/server/panel/pyenv/bin/python3
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.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()
File diff suppressed because it is too large Load Diff
+686
View File
@@ -0,0 +1,686 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import gettext
_ = gettext.gettext
import json
import os
import public
from btdockerModelV2 import dk_public as dp
from btdockerModelV2.dockerBase import dockerBase
from public.validate import Param
class main(dockerBase):
def get_config(self, get):
"""
获取设置配置信息
@param get:
@return:
"""
check_docker_compose = self.check_docker_compose_service()
try:
installing = public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count()
if not installing:
installing = public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count()
except:
installing = 0
# if not os.path.exists("/www/server/panel/data/db/docker.db"):
# public.ExecShell("mv -f /www/server/panel/data/docker.db /www/server/panel/data/db/docker.db")
if not os.path.exists("/www/server/panel/data/docker.db"):
public.ExecShell("mv -f /www/server/panel/data/db/docker.db /www/server/panel/data/docker.db")
service_status = self.get_service_status()
if not service_status:
service_status = self.get_service_status()
data = {
"service_status": service_status,
"docker_installed": self.check_docker_service(),
"docker_compose_installed": check_docker_compose[0],
"docker_compose_path": check_docker_compose[1],
"monitor_status": self.get_monitor_status(),
"monitor_save_date": dp.docker_conf()['SAVE'],
"daemon_path": "/etc/docker/daemon.json",
"installing": installing,
}
return public.return_message(0, 0, data)
@staticmethod
def _get_com_registry_mirrors():
"""
获取常用加速配置
@return:
"""
com_reg_mirror_file = "{}/class_v2/btdockerModelV2/config/com_reg_mirror.json".format(public.get_panel_path())
try:
com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file))
except:
com_reg_mirror = {
"https://docker.m.daocloud.io": "Third party image accelerator",
}
return com_reg_mirror
def set_monitor_save_date(self, get):
"""
:param save_date: int 例如30 表示 30天
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('save_date').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))
import re
conf_path = "{}/data/docker.conf".format(public.get_panel_path())
docker_conf = public.readFile(conf_path)
try:
save_date = int(get.save_date)
except:
return public.return_message(-1, 0, _( "The monitoring save time needs to be a positive integer!"))
if save_date > 999:
return public.return_message(-1, 0, _( "Monitoring data cannot be retained for more than 999 days!"))
if not docker_conf:
docker_conf = "SAVE={}".format(save_date)
public.writeFile(conf_path, docker_conf)
return public.return_message(0, 0, _( "Successfully set!"))
docker_conf = re.sub(r"SAVE\s*=\s*\d+", "SAVE={}".format(save_date),
docker_conf)
public.writeFile(conf_path, docker_conf)
dp.write_log("et the monitoring time to [{}] days!".format(save_date))
return public.return_message(0, 0, _( "Successfully set!"))
def get_service_status(self):
sock = '/var/run/docker.pid'
if os.path.exists(sock):
try:
client = dp.docker_client()
if client:
return True
return False
except:
return False
else:
return False
# docker服务状态设置
def docker_service(self, get):
"""
:param act start/stop/restart
:param get:
:return:
"""
import public
act_dict = {'start': 'start', 'stop': 'stop', 'restart': 'restart'}
if get.act not in act_dict:
return public.return_message(-1, 0, _( "There's no way to do that"))
exec_str = 'systemctl {} docker'.format(get.act)
if get.act == "stop":
exec_str += ";systemctl {} docker.socket".format(get.act)
stdout, stderr = public.ExecShell(exec_str)
if stderr and not "but it can still be activated by:\n docker.socket\n" in stderr:
dp.write_log("Setting the Docker service status to [{}] failed, failure reason:{}".format(act_dict[get.act], stderr))
jou_stdout, jou_stderr = public.ExecShell("journalctl -xe -u docker -n 100 --no-pager|grep libusranalyse.so")
if jou_stdout != "":
return public.return_message(-1, 0, _("Docker service setup failed, please turn off aapanel anti-intrusion and try again!"))
return public.return_message(-1, 0, _("Setup failed! Reason for failure:{}".format(stderr)))
if get.act != "stop":
service_status = self.get_service_status()
if not service_status:
import time
public.ExecShell("systemctl stop docker")
public.ExecShell("systemctl stop docker.socket")
time.sleep(1)
public.ExecShell("systemctl start docker")
dp.write_log("Set the Docker service status to [{}]".format(act_dict[get.act]))
return public.return_message(0, 0, _("{} success".format(act_dict[get.act])))
# 获取加速配置
def get_registry_mirrors(self, get):
"""
获取镜像加速信息
@param get:
@return:
"""
try:
if not os.path.exists('/etc/docker/daemon.json'):
reg_mirrors = []
else:
conf = json.loads(public.readFile('/etc/docker/daemon.json'))
if "registry-mirrors" not in conf:
reg_mirrors = []
else:
reg_mirrors = conf['registry-mirrors']
except:
reg_mirrors = []
com_reg_mirrors = self._get_com_registry_mirrors()
# return {
# "registry_mirrors": reg_mirrors,
# "com_reg_mirrors": com_reg_mirrors
# }
data = {
"registry_mirrors": reg_mirrors,
"com_reg_mirrors": com_reg_mirrors
}
return public.return_message(0, 0, data)
# 设置加速配置
def set_registry_mirrors(self, get):
"""
:param registry_mirrors_address registry.docker-cn.com\nhub-mirror.c.163.com
:param get:
:return:
"""
# {"registry_mirrors_address": "https://wzz1sdf11nb.com", "remarks": ""}
# 校验参数
try:
get.validate([
Param('registry_mirrors_address').Require().String(),
Param('remarks').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 os.path.exists('/etc/docker/'):
os.makedirs('/etc/docker', 755, True)
import re
try:
get.registry_mirrors_address = get.get("registry_mirrors_address/s", "")
conf = {}
if os.path.exists('/etc/docker/daemon.json'):
try:
conf = json.loads(public.readFile('/etc/docker/daemon.json'))
except Exception as e:
return public.return_message(-1, 0, _( "Global configuration file error, please check {}!".format(str(e))))
if not get.registry_mirrors_address.strip():
if "registry-mirrors" in conf:
del (conf['registry-mirrors'])
else:
registry_mirrors = get.registry_mirrors_address.strip()
if registry_mirrors == "":
# 2024/4/16 下午12:10 双重保险
if 'registry-mirrors' in conf:
del (conf['registry-mirrors'])
else:
if not re.search('https?://', registry_mirrors):
return public.return_message(-1, 0, _( 'Speedup address [{}] Format error <br> Reference: https://mirror.ccs.tencentyun.com'.format(registry_mirrors)))
conf['registry-mirrors'] = public.xsssec2(registry_mirrors)
if isinstance(conf['registry-mirrors'], str):
conf['registry-mirrors'] = [conf['registry-mirrors']]
public.writeFile('/etc/docker/daemon.json', json.dumps(conf, indent=2))
if get.registry_mirrors_address != "":
self.update_com_registry_mirrors(get)
dp.write_log("Setup Docker acceleration successful!")
return public.return_message(0, 0, _( 'successfully set'))
except:
return public.return_message(-1, 0, _('Setup failed! Failure reason :{}'.format(public.get_error_info())))
def update_com_registry_mirrors(self, get):
"""
更新常用加速配置
@param get:
@return:
"""
import time
com_reg_mirror_file = "{}/class_v2/btdockerModelV2/config/com_reg_mirror.json".format(public.get_panel_path())
try:
com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file))
except:
com_reg_mirror = {
"https://docker.m.daocloud.io": "Third party image accelerator",
}
if get.registry_mirrors_address in com_reg_mirror:
return public.return_message(0, 0, _( "Successfully set!"))
remarks = get.remarks if "remarks" in get and get.remarks != "" else ("Custom mirrors" + str(int(time.time())))
com_reg_mirror.update({"{}".format(get.registry_mirrors_address): remarks})
public.writeFile(com_reg_mirror_file, json.dumps(com_reg_mirror, indent=2))
dp.write_log("Updated common acceleration configuration successfully!")
return public.return_message(0, 0, _( "Update successfully!"))
def del_com_registry_mirror(self, get):
"""
删除常用加速配置
@param get:
@return:
"""
com_reg_mirror_file = "{}/class_v2/btdockerModelV2/config/com_reg_mirror.json".format(public.get_panel_path())
try:
com_reg_mirror = json.loads(public.readFile(com_reg_mirror_file))
except:
com_reg_mirror = {
"https://docker.m.daocloud.io": "Third party image accelerator",
}
if get.registry_mirrors_address not in com_reg_mirror:
return public.return_message(0, 0, _( "successfully delete!"))
del com_reg_mirror["{}".format(get.registry_mirrors_address)]
public.writeFile(com_reg_mirror_file, json.dumps(com_reg_mirror, indent=2))
dp.write_log("Remove common acceleration configuration successfully!")
return public.return_message(0, 0, _( "successfully delete!"))
def get_monitor_status(self):
"""
获取docker监控状态
@return:
"""
try:
from BTPanel import cache
except:
from cachelib import SimpleCache
cache = SimpleCache()
skey = "docker_monitor_status"
result = cache.get(skey)
if isinstance(result, bool):
return result
import psutil
is_monitor = False
for proc in psutil.process_iter():
try:
pinfo = proc.as_dict(attrs=['pid', 'name'])
if "monitorModel.py" in pinfo['name']:
is_monitor = True
except psutil.NoSuchProcess:
pass
cache.set(skey, is_monitor, 86400)
return is_monitor
def set_docker_monitor(self, get):
"""
开启docker监控获取docker相取资源信息
:param act: start/stop
:return:
"""
# 校验参数
try:
get.validate([
Param('act').Require().String('in', ['start', 'stop']),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
import time
python = "/www/server/panel/pyenv/bin/python"
if not os.path.exists(python):
python = "/www/server/panel/pyenv/bin/python3"
cmd_line = "/www/server/panel/class_v2/btdockerModelV2/monitorModel.py"
if get.act == "start":
self.stop_monitor(get)
if not os.path.exists(self.moinitor_lock):
public.writeFile(self.moinitor_lock, "1")
shell = "nohup {} {} &".format(python, cmd_line)
public.ExecShell(shell)
time.sleep(1)
if self.get_monitor_status():
dp.write_log("Docker started monitoring successfully!")
self.add_monitor_cron(get)
return public.return_message(0, 0, _( "Start monitoring successfully!"))
return public.return_message(-1, 0, _( "Failed to start monitoring!"))
else:
from BTPanel import cache
skey = "docker_monitor_status"
cache.set(skey, False)
if os.path.exists(self.moinitor_lock):
os.remove(self.moinitor_lock)
self.stop_monitor(get)
return public.return_message(0, 0, _( "Docker monitoring stopped successfully!"))
# 2024/1/4 上午 9:32 停止容器监控进程
def stop_monitor(self, get):
'''
@name 名称/描述
@param 参数名<数据类型> 参数描述
@return 数据类型
'''
cmd_line = [
"/www/server/panel/class_v2/btdockerModelV2/monitorModel.py",
"/www/server/panel/class/projectModel/bt_docker/dk_monitor.py"
]
for cmd in cmd_line:
in_pid = True
sum = 0
while in_pid:
in_pid = False
pid = dp.get_process_id(
"python",
"{}".format(cmd))
if pid:
in_pid = True
if not pid:
pid = dp.get_process_id(
"python3",
"{}".format(cmd)
)
if pid:
in_pid = True
public.ExecShell("kill -9 {}".format(pid))
sum += 1
if sum > 100:
break
import os
# 指定目录路径
directory = "/www/server/cron/"
if not os.path.exists(directory):
os.makedirs(directory)
# 遍历目录下的所有非.log结尾的文件
for filename in os.listdir(directory):
if not filename.endswith(".log"):
filepath = os.path.join(directory, filename)
if os.path.isdir(filepath):
continue
# 检查文件内容是否包含 "monitorModel.py"
with open(filepath, 'r') as file:
content = file.read()
if "monitorModel.py" in content or "dk_monitor.py" in content:
# 删除原文件和对应的.log文件
if os.path.exists(filepath):
os.remove(filepath)
if os.path.exists(os.path.join(directory, "{}.log".format(filename))):
os.remove(os.path.join(directory, "{}.log".format(filename)))
public.ExecShell("crontab -l | sed '/{}/d' | crontab -".format(filename))
dp.write_log("Docker monitoring stopped successfully!")
public.M('crontab').where('name=?', ("[Do not delete] docker monitoring daemon",)).delete()
return public.returnMsg(True, "Docker monitoring stopped successfully!")
# 2023/12/7 下午 6:24 创建计划任务,监听监控进程是否存在,如果不存在则添加
def add_monitor_cron(self, get):
'''
@name 名称/描述
@author wzz <2023/12/7 下午 6:24>
@param 参数名<数据类型> 参数描述
@return 数据类型
'''
try:
import crontab
if public.M('crontab').where('name', ("[Do not delete] docker monitoring daemon",)).count() == 0:
p = crontab.crontab()
llist = p.GetCrontab(None)
if type(llist) == list:
for i in llist:
if i['name'] == '[Do not delete] docker monitoring daemon':
return
get = {
"name": "[Do not delete] docker monitoring daemon",
"type": "minute-n",
"where1": 5,
"hour": "",
"minute": "",
"week": "",
"sType": "toShell",
"sName": "",
"backupTo": "localhost",
"save": '',
"sBody": """
if [ -f {} ]; then
new_mt=`ps aux|grep monitorModel.py|grep -v grep`
old_mt=`ps aux|grep dk_monitor.py|grep -v grep`
if [ -z "$new_mt" ] && [ -z "$old_mt" ]; then
nohup /www/server/panel/pyenv/bin/python /www/server/panel/class_v2/btdockerModelV2/monitorModel.py &
fi
fi
""".format(self.moinitor_lock),
"urladdress": "undefined"
}
p.AddCrontab(get)
except Exception as e:
return False
def check_docker_compose_service(self):
"""
检查docker-compose是否已经安装
:return:
"""
docker_compose = "/usr/bin/docker-compose"
docker_compose_path = "{}/class_v2/btdockerModelV2/config/docker_compose_path.pl".format(public.get_panel_path())
if os.path.exists(docker_compose_path):
docker_compose = public.readFile(docker_compose_path).strip()
if not os.path.exists(docker_compose):
# public.print_log("mwmwmwmwm 没文件")
dk_compose_list = ["/usr/libexec/docker/cli-plugins/docker-compose", "/usr/local/docker-compose"]
for i in dk_compose_list:
if os.path.exists(i):
public.ExecShell("ln -sf {} {}".format(i, "/usr/bin/docker-compose"))
break
if not os.path.exists(docker_compose):
return False, ""
return True, docker_compose
def check_docker_service(self):
"""
检查docker是否安装
@return:
"""
docker = "/usr/bin/docker"
if not os.path.exists(docker):
return False
return True
def set_docker_compose_path(self, get):
"""
设置docker-compose的路径
@param get:
@return:
"""
docker_compose_file = get.docker_compose_path if "docker_compose_path" in get else ""
if docker_compose_file == "":
return public.return_message(-1, 0, _( "docker-compose file path cannot be empty!"))
if not os.path.exists(docker_compose_file):
return public.return_message(-1, 0, _( "docker-compose file does not exist!"))
public.ExecShell("chmod +x {}".format(docker_compose_file))
cmd_result = public.ExecShell("{} --version".format(docker_compose_file))
if not cmd_result[0]:
return public.return_message(-1, 0, _( "docker-compose file is not executable or is not a docker-compose file!"))
docker_compose_path = "{}/class_v2/btdockerModelV2/config/docker_compose_path.pl".format(public.get_panel_path())
public.writeFile(docker_compose_path, docker_compose_file)
dp.write_log("Set docker-compose path successfully!")
return public.return_message(0, 0, _( "Successfully set!"))
def install_docker_program(self, get):
"""
安装docker和docker-compose
:param get:
:return:
"""
import time
url = get.get("url/s", "")
type = get.get("type/d", 0)
# 2024/3/28 上午 10:36 检测是否已存在安装任务
if public.M('tasks').where('name=? and status=?', ("Install Docker Service", "-1")).count():
return public.return_message(-1, 0, _( "The installation task already exists, please do not add it again!"))
mmsg = "Install Docker Service"
if type == 0 and url == "":
# 默认安装
execstr = ("wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && "
"bash /tmp/docker_install.sh install ").format(public.get_url())
elif type == 0 and url != "":
# 选择镜像源安装
execstr = ("wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && "
"bash /tmp/docker_install.sh install {} ").format(public.get_url(), url.strip('"'))
else:
# 二进制安装
execstr = "/bin/bash /www/server/panel/install/install_soft.sh 0 install docker_bin "
public.M('tasks').add('id,name,type,status,addtime,execstr',
(None, mmsg, 'execshell', '0',
time.strftime('%Y-%m-%d %H:%M:%S'), execstr))
public.httpPost(
public.GetConfigValue('home') + '/api/panel/plugin_total', {
"pid": "1111111",
'p_name': "Docker commercial module"
}, 3)
return public.return_message(0, 0, _( "The installation task has been added to the queue!"))
def repair_docker(self, get):
"""
修复docker
@param get:
@return:
"""
import time
mmsg = "Repair Docker service"
execstr = "curl -fsSL https://get.docker.com -o /tmp/get-docker.sh && sed -i '/sleep 20/d' /tmp/get-docker.sh && /bin/bash /tmp/get-docker.sh"
public.M('tasks').add('id,name,type,status,addtime,execstr',
(None, mmsg, 'execshell', '0',
time.strftime('%Y-%m-%d %H:%M:%S'), execstr))
public.httpPost(
public.GetConfigValue('home') + '/api/panel/plugin_total', {
"pid": "1111111",
'p_name': "Docker commercial module"
}, 3)
return public.return_message(0, 0, _( "The repair task has been added to the queue!"))
def get_daemon_json(self, get):
"""
获取daemon.json配置信息
@param get:
@return:
"""
daemon_json = "/etc/docker/daemon.json"
if not os.path.exists(daemon_json):
return public.return_message(0, 0, "")
try:
return public.return_message(0, 0, json.loads(public.readFile(daemon_json)))
except Exception as e:
print(e)
return public.return_message(-1, 0, "")
def save_daemon_json(self, get):
"""
保存daemon.json配置信息,保存前备份,验证可以成功执行后再替换
@param get:
@return:
"""
daemon_json = "/etc/docker/daemon.json"
if getattr(get, "daemon_json", "") == "":
public.ExecShell("rm -f {}".format(daemon_json))
return public.return_message(0, 0, _( "Saved successfully!"))
try:
conf = json.loads(get.daemon_json)
public.writeFile(daemon_json, json.dumps(conf, indent=2))
dp.write_log("Save daemon.json configuration successfully!")
return public.return_message(0, 0, _( "Saved successfully!"))
except Exception as e:
public.print_log("err: {}".format(e))
if "Expecting property name enclosed in double quotes" in str(e):
return public.return_message(-1, 0, _( "Saving failed, reason: daemon.json configuration file format error!"))
return public.return_message(-1, 0, _( "Save failed, reason: {}".format(e)))
def uninstall_status(self, get):
"""
检测docker是否可以卸载
:param get:
:return:
"""
from btdockerModelV2 import containerModel
docker_list = containerModel.main().get_list(get)
from btdockerModelV2 import imageModel
images_list = imageModel.main().image_list(get)
if len(images_list) > 0 or len(docker_list["container_list"]) > 0:
return public.return_message(0, 0, {"status": False,
"msg": "Please manually delete all containers and images before uninstalling!"})
return public.return_message(0, 0, "Allow uninstallation")
def uninstall_status1(self, get):
"""
检测docker是否可以卸载
:param get:
:return:
"""
from btdockerModelV2 import containerModel
docker_list = containerModel.main().get_list(get)
from btdockerModelV2 import imageModel
images_list = imageModel.main().image_list(get)
if len(images_list) > 0 or len(docker_list["container_list"]) > 0:
return False
return True
def uninstall_docker_program(self, get):
"""
卸载docker和docker-compose
:param get:
:return:
"""
type = get.get("type/d", 0)
if type == 0:
uninstall_status = self.uninstall_status1(get)
if not uninstall_status["status"]:
return public.return_message(-1, 0, _( "Please manually delete all containers and images before uninstalling!"))
public.ExecShell(
"wget -O /tmp/docker_install.sh {}/install/0/docker_install.sh && bash /tmp/docker_install.sh uninstall"
.format(public.get_url()
))
public.ExecShell("rm -rf /usr/bin/docker-compose")
return public.return_message(0, 0, "Uninstall successfully!")
+258
View File
@@ -0,0 +1,258 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
import gettext
_ = 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, _('The container does not exist, please refresh the browser and try again!'))
return public.return_message(-1, 0, _('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()))
# 获取监控容器资源并记录每分钟
+159
View File
@@ -0,0 +1,159 @@
# coding: utf-8
# -------------------------------------------------------------------
# aaPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 aaPanel(www.aapanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@aapanel.com>
# -------------------------------------------------------------------
import gettext
_ = gettext.gettext
# ------------------------------
# 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"]:
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, _( "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, _( "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, _( "Volume names can be no less than 2 characters long!"))
if "volume name" in str(e):
return public.return_message(-1, 0, _( "Volume name already exists!"))
return public.return_message(-1, 0, _( "addition failed {}".format(e)))
except Exception as e:
if "driver_opts must be a dictionary" in str(e):
return public.return_message(-1, 0, _( "Driver option tags must be dictionary/key-value pairs!"))
return public.return_message(-1, 0, _( "Add failed! {}".format(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, _( "successfully delete"))
except docker.errors.APIError as e:
if "volume is in use" in str(e):
return public.return_message(-1, 0, _( "The storage volume is in use and cannot be deleted!"))
if "no such volume" in str(e):
return public.return_message(-1, 0, _( "The storage volume does not exist!"))
return public.return_message(-1, 0, _( "Delete failed! {}".format(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, _( "No useless storage volumes!"))
dp.write_log("Delete useless storage volume successfully!")
return public.return_message(0, 0, _( "successfully delete!"))
except docker.errors.APIError as e:
return public.return_message(-1, 0, _( "Delete failed! {}".format(e)))