mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-09-22 01:14:52 +02:00
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:
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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))
|
||||
@@ -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 []
|
||||
@@ -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!")
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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))))
|
||||
@@ -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))
|
||||
@@ -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 '')",
|
||||
()
|
||||
)
|
||||
@@ -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))
|
||||
@@ -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
@@ -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!")
|
||||
@@ -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()))
|
||||
|
||||
# 获取监控容器资源并记录每分钟
|
||||
@@ -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)))
|
||||
Reference in New Issue
Block a user