Files
yakpanel-core/class_v2/btdockerModelV2/composeModel.py

1151 lines
45 KiB
Python
Raw Normal View History

2026-04-07 02:04:22 +05:30
# coding: utf-8
# -------------------------------------------------------------------
# YakPanel
# -------------------------------------------------------------------
# Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# -------------------------------------------------------------------
# Author: wzz <wzz@yakpanel.com>
# -------------------------------------------------------------------
# ------------------------------
# Docker模型
# ------------------------------
import json
import os
import time
import gettext
_ = gettext.gettext
import public
import traceback
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):
def get_docker_compose_version(self):
try:
import subprocess
result = subprocess.run(["docker-compose", "version", "--short"], capture_output=True, text=True)
version_str = result.stdout.strip()
major, minor, patch = map(int, version_str.split('.'))
return major, minor, patch
except Exception as e:
print("Error:", e)
return None
# 验证配置文件
def check_conf(self, path):
# 2024/3/21 下午 5:58 检测path是否存在中文如果有就false return
if public.check_chinese(path):
return public.return_message(-1, 0, public.lang("The file path cannot contain Chinese characters!"))
# 2024/3/20 下午 4:33 获取docker-compose的版本如果大于v2.24.7则不需要检测比如是v2.25解决高版本的docker-compose
version = self.get_docker_compose_version()
if version and version > (2, 24, 7):
tmpfile = public.md5(path)
public.ExecShell(r"\cp -r {} /tmp/{}.yml".format(path, public.md5(path)))
public.ExecShell("sed -i '/version:/d' /tmp/{}.yml".format(tmpfile))
path = "/tmp/{}.yml".format(tmpfile)
shell = "/usr/bin/docker-compose -f {} config".format(path)
a, e = public.ExecShell(shell)
if e and "setlocale: LC_ALL: cannot change locale (en_US.UTF-8)" not in e:
return public.return_message(-1, 0, public.lang("Detection failed: {}", e))
return public.return_message(0, 0, public.lang("Detection passes!"))
# 用引导方式创建模板
def add_template_gui(self, get):
"""
用引导方式创建模板
:param name 模板名
:param description 模板描述
:param data 模板内容 {"version":3,"services":{...}...}
:param get:
模板文件参数
version 2/3version
2: 仅支持单机
3支持单机和多机模式
services:
多个容器的集合
下一层执行服务名
如web1,服务名下面指定服务的变量
web1:
build: . 基于dockerfile构建一个镜像
image: nginx 服务所使用的镜像为nginx
container_name: "web" 容器名
depends_on: 该服务在db服务启动后再启动
- db
ports:
- "6061:80" 将容器的80端口映射到主机的6061端口
networks:
- frontend 该容器所在的网络
deploy: 指定与部署和运行服务相关的配置(在使用 swarm时才会生效)
replicas: 6 6个副本
update_config:
parallelism: 2
delay: 10s
restart_policy:
condition: on-failure
其他详细描述可以参考 https://docs.docker.com/compose/compose-file/compose-file-v3
:return:
"""
import yaml
path = "{}/template".format(self.compose_path)
file = "{}/{}.yaml".format(path, get.name)
if not os.path.exists(path):
os.makedirs(path)
data = json.loads(get.data)
yaml.dump(data, file)
def get_template_kw(self, get):
data = {
"version": "",
"services": {
"server_name_str": { # 用户输入
"build": {
"context": "str",
"dockerfile": "str",
"get": [],
"cache_from": [],
"labels": [],
"network": "str",
"shm_size": "str",
"target": "str"
},
"cap_add": "",
"cap_drop": "",
"cgroup_parent": "str",
"command": "str",
"configs": {
"my_config_str": []
},
"container_name": "str",
"credential_spec": {
"file": "str",
"registry": "str"
},
"depends_on": [],
"deploy": {
"endpoint_mode": "str",
"labels": {
"key": "value"
},
"mode": "str",
"placement": [{"key": "value"}],
"max_replicas_per_node": "int",
"replicas": "int",
"resources": {
"limits": {
"cpus": "str",
"memory": "str",
},
"reservations": {
"cpus": "str",
"memory": "str",
},
"restart_policy": {
"condition": "str",
"delay": "str",
"max_attempts": "int",
"window": "str"
}
}
}
}
}
}
# 创建项目配置文件
def add_template(self, get):
"""
添加一个模板文件
:param name 模板名
:param remark 模板描述
:param data 模板内容
:param get:
:return:
"""
import re
name = get.name
if not re.search(r"^[\w\.\-]+$", name):
return public.return_message(-1, 0, public.lang("Template names cannot contain special characters; only letters, numbers, underscores, dots, and underscores are supported"))
template_list = self._template_list(get)
for template in template_list:
if name == template['name']:
return public.return_message(-1, 0, public.lang("This template name already exists!"))
path = "{}/{}/template".format(self.compose_path, name)
file = "{}/{}.yaml".format(path, name)
if not os.path.exists(path):
os.makedirs(path)
public.writeFile(file, get.data)
check_res = self.check_conf(file)
if check_res['status'] == -1:
if os.path.exists(file):
os.remove(file)
return public.return_message(-1, 0, check_res['message'])
pdata = {
"name": name,
"remark": public.xsssec(get.remark),
"path": file
}
dp.sql("templates").insert(pdata)
dp.write_log("Added template [{}] successfully!".format(name))
public.set_module_logs('docker', 'add_template', 1)
return public.return_message(0, 0, public.lang("Template added successfully!"))
def edit_template(self, get):
"""
:param id 模板id
:param data 模板内容
:param remark 模板描述
:param get:
:return:
"""
template_info = dp.sql("templates").where("id=?", (get.id,)).find()
if not template_info:
return public.return_message(-1, 0, public.lang("Did not change the template!"))
# if "data" not in get:
# return public.return_message(-1, 0, public.lang("Template content format error, please enter a valid docker-compose template!"))
#
# if "version" not in get.data:
# return public.return_message(-1, 0, public.lang("Template content format error, please enter a valid docker-compose template!"))
public.writeFile(template_info['path'], get.data)
check_res = self.check_conf(template_info['path'])
if check_res['status'] == -1:
return public.return_message(-1, 0, check_res['message'])
pdata = {
"name": get.name,
"remark": public.xsssec(get.remark),
"path": template_info['path']
}
dp.sql("templates").where("id=?", (get.id,)).update(pdata)
dp.write_log("Edit template [{}] successful!".format(template_info['name']))
return public.return_message(0, 0, public.lang("Modified template successfully!"))
def get_template(self, get):
"""
id 模板ID
获取模板内容
:return:
"""
# 校验参数
try:
get.validate([
Param('template_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))
template_info = dp.sql("templates").where("id=?", (get.template_id,)).find()
if not template_info:
return public.return_message(-1, 0, public.lang("This template was not found!"))
return public.return_message(0, 0, public.readFile(template_info['path']))
def template_list(self, get):
"""
获取所有模板
:param get:
:return:
"""
template = dp.sql("templates").select()[::-1]
if not isinstance(template, list):
template = []
return public.return_message(0, 0, template)
# 内部调用 不改响应格式
def _template_list(self, get):
"""
获取所有模板
:param get:
:return:
"""
template = dp.sql("templates").select()[::-1]
if not isinstance(template, list):
template = []
return template
# 检查模板是否被使用
def check_use_template(self, get):
"""
检查模板是否被使用
:param get:
:return:
"""
template_info = dp.sql("stacks").where("template_id=?", (get.id,)).find()
if not template_info:
return public.return_message(-1, 0, "")
return public.return_message(0, 0, template_info["name"])
def remove_template(self, get):
"""
删除模板
:param template_id
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('template_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))
data = dp.sql("templates").where("id=?", (get.template_id,)).find()
if not data:
return public.return_message(-1, 0, public.lang("This template was not found!"))
if os.path.exists(data['path']):
# 删除模板文件 目录不处理
try:
os.remove(data['path'])
except:
pass
get.id = get.template_id
if self.check_use_template(get)['status']==0:
# 模板已被使用
if hasattr(get, "status"):
template_info = dp.sql("stacks").where("template_id=?", (get.template_id,)).find()
if os.path.exists(template_info["path"]):
public.ExecShell("/usr/bin/docker-compose -f {} down".format(template_info["path"]))
else:
stdout, stderr = public.ExecShell("docker-compose ls --format json")
try:
info = json.loads(stdout)
except:
info = []
for i in info:
if i['Name'] == public.md5(template_info['name']):
public.ExecShell("/usr/bin/docker-compose -p {} down".format(i['Name']))
break
dp.sql("stacks").delete(id=template_info["id"])
dp.sql("templates").delete(id=get.template_id)
dp.write_log("Delete template [{}] successfully!".format(data['name']))
return public.return_message(0, 0, public.lang("successfully delete!"))
def edit_project_remark(self, get):
"""
编辑项目
:param project_id 项目
:param remark备注
:param get:
:return:
"""
stacks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not stacks_info:
return public.return_message(-1, 0, public.lang("The item was not found!"))
pdata = {
"remark": public.xsssec(get.remark)
}
dp.write_log("Comment for project [{}] changed successfully [{}] --> [{}]!".format(stacks_info['name'],
stacks_info['remark'],
public.xsssec(get.remark)))
dp.sql("stacks").where("id=?", (get.project_id,)).update(pdata)
def edit_template_remark(self, get):
"""
编辑项目
:param templates_id 项目
:param remark备注
:param get:
:return:
"""
stacks_info = dp.sql("templates").where("id=?", (get.templates_id,)).find()
if not stacks_info:
return public.return_message(-1, 0, public.lang("The template was not found!"))
pdata = {
"remark": public.xsssec(get.remark)
}
dp.write_log(
"Modify template [{}] Remark successful [{}] --> [{}]!".format(stacks_info['name'], stacks_info['remark'],
public.xsssec(get.remark)))
dp.sql("templates").where("id=?", (get.templates_id,)).update(pdata)
def create_project_in_path(self, name, path):
shell = "cd {} && /usr/bin/docker-compose -p {} up -d &> {}".format("/".join(path.split("/")[:-1]), name,
self._log_path)
public.ExecShell(shell)
def create_project_in_file(self, project_name, file):
project_path = "{}/{}".format(self.compose_path, project_name)
project_file = "{}/docker-compose.yaml".format(project_path)
if not os.path.exists(project_path):
os.makedirs(project_path)
template_content = public.readFile(file)
public.writeFile(project_file, template_content)
shell = "/usr/bin/docker-compose -p {} -f {} up -d &> {}".format(project_name, project_file, self._log_path)
public.ExecShell(shell)
def check_project_container_name(self, template_data, get):
"""
检测模板文件中的容器名是否已经存在
:return:
"""
import re
data = []
template_container_name = re.findall("container_name\s*:\s*[\"\']+(.*)[\'\"]", template_data)
# 调用容器列表接口 选择不改统一返回的
container_list = dc.main()._get_list(get)
container_list = container_list['container_list']
for container in container_list:
if container['name'] in template_container_name:
data.append(container['name'])
if data:
return public.return_message(-1, 0, public.lang("The container name already exists!: <br>[{}]", ", ".join(data)))
# 获取模板所使用的端口
rep = r"(\d+):\d+"
port_list = re.findall(rep, template_data)
for port in port_list:
if dp.check_socket(port):
return public.return_message(-1, 0, public.lang("This port [{}] is already used by other templates", port))
# 创建项目
def create(self, get):
"""
:param project_name 项目名
:param remark 描述
:param template_id 模板ID
:param rags:
:return:
"""
# {"template_id": "13", "project_name": "dedf2f", "remark": "从本地添加asd"}
# 校验参数
try:
get.validate([
Param('template_id').Require().Integer(),
Param('project_name').Require().String().Xss(),
Param('remark').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:
project_name = public.md5(public.xsssec(get.project_name))
if "template_id" not in get:
return public.return_message(-1, 0, public.lang("Parameter error, please pass in template_id!"))
template_id = get.template_id
template_info = dp.sql("templates").where("id=?", template_id).find()
if len(template_info) < 1:
return public.return_message(-1, 0, public.lang("This template was not found, or file is corrupt!"))
if not os.path.exists(template_info['path']):
return public.return_message(-1, 0, public.lang("Template file does not exist"))
template_exist = dp.sql("stacks").where("template_id=?", (template_id,)).find()
if template_exist:
return public.return_message(-1, 0,
public.lang("Template [{}] has been deployed by project: [{}], please change a template and try again!",
template_info['name'], template_exist['name']))
name_exist = self.check_project_container_name(public.readFile(template_info['path']), get)
if name_exist:
return public.return_message(-1, 0, name_exist['message'])
stacks_info = dp.sql("stacks").where("name=?", (public.xsssec(get.project_name))).find()
if not stacks_info:
pdata = {
"name": public.xsssec(get.project_name),
"status": "1",
"path": template_info['path'],
"template_id": template_id,
"time": time.time(),
"remark": public.xsssec(get.remark)
}
dp.sql("stacks").insert(pdata)
else:
return public.return_message(-1, 0, public.lang("The project name already exists!"))
if template_info['add_in_path'] == 1:
self.create_project_in_path(
project_name,
template_info['path']
)
else:
self.create_project_in_file(
project_name,
template_info['path']
)
# 检查容器是否成功执行
check_shell = "/usr/bin/docker-compose -p {} ps -q".format(project_name)
container_ids, _ = public.ExecShell(check_shell)
if not container_ids.strip():
raise Exception("Docker deployment failed. Please check logs at: {}".format(self._log_path))
dp.write_log("Project [{}] deployed successfully!".format(public.xsssec(get.project_name)))
public.set_module_logs('docker', 'add_project', 1)
return public.return_message(0, 0, public.lang("Successful deployment!"))
except Exception as ex:
dp.sql("stacks").where("name=?", (public.xsssec(get.project_name),)).delete()
public.print_log(traceback.format_exc())
return public.return_message(-1, 0, str(ex))
def compose_project_list(self, get):
"""
获取所有已部署的项目列表
@param get:
"""
compose_project = dp.sql("stacks").select()
# public.print_log("部署项目 {}".format(compose_project))
try:
cmd_result = public.ExecShell("/usr/bin/docker-compose ls -a --format json")[0]
if "Segmentation fault" in cmd_result:
return public.return_message(-1, 0, public.lang("docker-compose is too low, please upgrade to the latest version!"))
result = json.loads(cmd_result)
except:
result = []
for i in compose_project:
for j in result:
if public.md5(i['name']) in j['Name']:
i['run_status'] = j['Status'].split("(")[0].lower()
break
else:
i['run_status'] = "exited"
return public.return_message(0, 0, compose_project)
def project_container_count(self, get):
"""
获取项目容器数量
@param get:
@return:
"""
from btdockerModelV2.dockerSock import container
sk_container = container.dockerContainer()
sk_container_list = sk_container.get_container()
stacks_info = dp.sql("stacks").select()
net_info = []
for i in stacks_info:
count = 0
for c in sk_container_list:
if public.md5(i['name']) in c["Names"][0].replace("/", ""):
count += 1
continue
if 'com.docker.compose.project' in c.keys():
if "com.docker.compose.project.config_files" in c:
if public.md5(i['name']) in c['com.docker.compose.project.config_files']:
count += 1
continue
if public.md5(i['name']) in public.md5(c['com.docker.compose.project.config_files']):
count += 1
continue
if 'com.docker.compose.project' in c['Labels'].keys():
if public.md5(i['name']) in c['Labels']['com.docker.compose.project.config_files']:
count += 1
continue
if public.md5(i['name']) in public.md5(c['Labels']['com.docker.compose.project.config_files']):
count += 1
continue
net_info.append(count)
return public.return_message(0, 0, net_info)
def get_compose_container(self, get):
"""
目前仅支持本地 url: unix:///var/run/docker.sock
"""
# 校验参数
try:
get.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))
from btdockerModelV2.dockerSock import container
sk_container = container.dockerContainer()
sk_container_list = sk_container.get_container()
project_container_list = []
for c in sk_container_list:
if public.md5(get.name) in dp.rename(c["Names"][0].replace("/", "")):
project_container_list.append(dc.main().struct_container_list(c))
continue
if 'com.docker.compose.project' in c.keys():
if "com.docker.compose.project.config_files" in c:
if public.md5(get.name) in c['com.docker.compose.project.config_files']:
project_container_list.append(dc.main().struct_container_list(c))
if public.md5(get.name) in public.md5(c['com.docker.compose.project.config_files']):
project_container_list.append(dc.main().struct_container_list(c))
if 'com.docker.compose.project' in c['Labels'].keys():
if "com.docker.compose.project.config_files" in c['Labels']:
if public.md5(get.name) in c['Labels']['com.docker.compose.project.config_files']:
project_container_list.append(dc.main().struct_container_list(c))
if public.md5(get.name) in public.md5(c['Labels']['com.docker.compose.project.config_files']):
project_container_list.append(dc.main().struct_container_list(c))
return public.return_message(0, 0, project_container_list)
# 删除项目
def remove(self, get):
"""
project_id 数据库记录的项目ID
:param get:
:return:
"""
# 校验参数
try:
get.validate([
Param('project_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))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("The project name was not found!"))
container_name = public.ExecShell("docker ps --format \"{{.Names}}\"")
if statcks_info['name'] in container_name[0]:
shell = f"/usr/bin/docker-compose -p {statcks_info['name']} -f {statcks_info['path']} down &> {self._log_path}"
else:
shell = f"/usr/bin/docker-compose -p {public.md5(statcks_info['name'])} -f" \
f" {statcks_info['path']} down &> {self._log_path}"
public.ExecShell(shell)
dp.sql("stacks").delete(id=get.project_id)
dp.write_log("Delete project [{}] success!".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("successfully delete!"))
def prune(self, get):
"""
删除所有没有容器的项目
@param get:
@return:
"""
stacks_info = dp.sql("stacks").select()
container_name = public.ExecShell("docker ps --format \"{{.Names}}\"")[0]
# 正在运行的容器为空 直接返回结果
if container_name == "":
return public.return_message(0, 0, public.lang("The cleanup was successful!"))
container_name = container_name.split("\n")
# 数据库列表信息
for i in stacks_info:
# 2024/3/21 下午 6:26 如果i['name']在container_name[0]中,说明容器还在运行,不删除
is_run = False
docker_name = public.ExecShell("grep 'container_name' {}".format(i["path"]))[0]
# 实际运行列表
for j in container_name:
if j == "": continue
if public.md5(i['name']) in j or j in docker_name:
is_run = True
break
if is_run: continue
shell = "/usr/bin/docker-compose -f {} down &> {}".format(i['path'], self._log_path)
public.ExecShell(shell)
dp.sql("stacks").delete(id=i['id'])
dp.write_log("Cleanup project [{}] successful!".format(i['name']))
return public.return_message(0, 0, public.lang("Clean up successfully!"))
def set_compose_status(self, get):
"""
设置项目状态
@param get:
@return:
"""
try:
get.validate([
Param('status').Require().String('in', ['start', 'stop','restart','pause','unpause','kill']),
], [
public.validate.trim_filter(),
])
except Exception as ex:
public.print_log("error info: {}".format(ex))
return public.return_message(-1, 0, str(ex))
if get.status == 'start':
data = self.start(get)
elif get.status == 'stop':
data = self.stop(get)
elif get.status == 'restart':
data = self.restart(get)
elif get.status == 'pause':
data = self.pause(get)
elif get.status == 'unpause':
data = self.unpause(get)
else:
data = self.kill(get)
if data["status"]== -1:
return public.return_message(0, 0, data['message'])
else:
return public.return_message(-1, 0, data['message'])
# 项目状态执行命令调用
def __host_temp(self, statcks_info, status):
file_path = "{}/data/compose/{}/docker-compose.yaml".format(public.get_panel_path(), public.md5(statcks_info['name']))
status_info = {
"start": "start",
"stop": "stop",
"restart": "restart",
"pause": "pause",
"unpause": "Unpause",
"kill": "kill",
}
# 本地添加模版
if not os.path.exists(file_path):
if os.path.exists(statcks_info["path"]):
public.ExecShell("/usr/bin/docker-compose -f {} {} &> {}".format(statcks_info["path"], status, self._log_path))
else:
stdout, stderr = public.ExecShell("docker-compose ls --format json")
try:
info = json.loads(stdout)
except:
info = []
if stdout:
for i in info:
if i['Name'] == statcks_info['name']:
public.ExecShell("docker-compose -p {} {} &> {}".format(i['Name'], status, self._log_path))
break
else:
shell = "/usr/bin/docker-compose -f {compose_path} {status} &> {_log_path}".format(
compose_path="{}/data/compose/{}/docker-compose.yaml".format(
public.get_panel_path(),
public.md5(statcks_info['name'])),
status=status,
_log_path=self._log_path
)
a, e = public.ExecShell(shell)
if e:
return public.return_message(-1, 0, public.lang("{}The project failed: {}",status_info[status], e))
def kill(self, get):
"""
强制停止项目
@param get:
@return:
"""
if not hasattr(get, "project_id"):
return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("Project configuration not found!"))
if not hasattr(get, "status"):
return public.return_message(-1, 0, public.lang("The status parameter is missing!"))
self.__host_temp(statcks_info, get.status)
dp.write_log("Stopping project [{}] succeeded".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Setup successful!"))
def stop(self, get):
"""
停止项目
project_id 数据库记录的项目ID
kill 强制停止项目 0/1
:param get:
:return:
"""
if not hasattr(get, "project_id"):
return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("Project configuration not found!"))
if not hasattr(get, "status"):
return public.return_message(-1, 0, public.lang("The status parameter is missing!"))
self.__host_temp(statcks_info, get.status)
dp.write_log("Stopping project [{}] succeeded!".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Setup successful!"))
def start(self, get):
"""
启动项目
project_id 数据库记录的项目ID
:param get:
:return:
"""
if not hasattr(get, "project_id"):
return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("Project configuration not found!"))
if not hasattr(get, "status"):
return public.return_message(-1, 0, public.lang("The status parameter is missing!"))
self.__host_temp(statcks_info, get.status)
dp.write_log("Start project [{}] successful!".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Setup successful!"))
def restart(self, get):
"""
拉取项目内需要的镜像
project_id 数据库记录的项目ID
:param get:
:return:
"""
if not hasattr(get, "project_id"):
return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("Project configuration not found!"))
if not hasattr(get, "status"):
return public.return_message(-1, 0, public.lang("The status parameter is missing!"))
self.__host_temp(statcks_info, get.status)
dp.write_log("Restart project [{}] successfully!".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Successfully set!"))
def pull(self, get):
"""
拉取模板内需要的镜像
template_id 数据库记录的项目ID
:param get:
:return:
"""
get.name = get.get("name", "")
get.url = get.get("url", "docker.io")
get.template_id = get.get("template_id", "")
if get.template_id == "":
return public.return_message(-1, 0, public.lang("Missing parameter template_id!"))
statcks_info = dp.sql("templates").where("id=?", (get.template_id,)).find()
if not statcks_info:
return public.return_message(0, 0, public.lang("Didn't find the template!"))
cmd = "/usr/bin/docker-compose -f {} pull >> {} 2>&1".format(statcks_info['path'], self._log_path)
if get.name != "" and get.url != "docker.io":
from btdockerModelV2 import registryModel as dr
r_info = dr.main().registry_info(get)
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_result = dr.main().login(self._url, r_info['url'], r_info['username'], r_info['password'])
if not login_result["status"]:
return login_result
cmd = "docker login --username {} --password {} {} >> {} 2>&1 &&".format(
r_info['username'],
r_info['password'],
r_info['url'],
self._log_path
) + cmd
os.system(
"echo > {};nohup {} && echo 'bt_successful' >> {} "
"|| echo 'bt_failed' >> {} &".format(
self._log_path,
cmd,
self._log_path,
self._log_path,
))
dp.write_log("The image inside the template [{}] was pulled successfully !".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Pull successfully!"))
def pause(self, get):
"""
暂停项目
project_id 数据库记录的项目ID
:param get:
:return:
"""
if not hasattr(get, "project_id"):
return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("Project configuration not found!"))
if not hasattr(get, "status"):
return public.return_message(-1, 0, public.lang("The status parameter is missing!"))
self.__host_temp(statcks_info, get.status)
dp.write_log("Pause [{}] success!".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Successfully set!"))
def unpause(self, get):
"""
取消暂停项目
project_id 数据库记录的项目ID
:param get:
:return:
"""
if not hasattr(get, "project_id"):
return public.return_message(-1, 0, public.lang("Missing parameter project_id!"))
statcks_info = dp.sql("stacks").where("id=?", (get.project_id,)).find()
if not statcks_info:
return public.return_message(-1, 0, public.lang("Project configuration not found!"))
if not hasattr(get, "status"):
return public.return_message(-1, 0, public.lang("The status parameter is missing!"))
self.__host_temp(statcks_info, get.status)
dp.write_log("Unpause [{}] success!".format(statcks_info['name']))
return public.return_message(0, 0, public.lang("Successfully set!"))
def scan_compose_file(self, path, data):
"""
递归扫描目录下的compose文件
:param path 需要扫描的目录
:param data 需要返回的数据 一个字典
:param get:
:return:
"""
try:
if not os.path.isdir(path):
return data
file_list = os.listdir(path)
for file in file_list:
current_path = os.path.join(path, file)
# 判断是否是文件夹
if os.path.isdir(current_path):
self.scan_compose_file(current_path, data)
else:
if file == "docker-compose.yaml" or file == "docker-compose.yam" or file == "docker-compose.yml":
if "/www/server/panel/data/compose" in current_path:
continue
data.append(current_path)
if ".yaml" in file or ".yam" in file or ".yml" in file:
if "/www/server/panel/data/compose" in current_path:
continue
data.append(current_path)
except:
pass
return data
def get_compose_project(self, get):
"""
:param path 需要获取的路径 是一个目录
:param sub_dir 扫描子目录
:param get:
:return:
"""
get.exists(["path", "sub_dir"])
data = list()
suffix = ["yaml", "yam", "yml"]
if get.path == "/":
return public.return_message(-1, 0, public.lang("Unable to scan the root directory"))
if get.path[-1] == "/":
get.path = get.path[:-1]
if not os.path.exists(get.path):
return public.return_message(-1, 0, public.lang("The path does not exist!"))
if str(get.sub_dir) == "1":
res = self.scan_compose_file(get.path, data)
if not res:
res = []
else:
tmp = list()
p_name_tmp = list()
for i in res:
if i.split(".")[1] not in suffix:
continue
project_name = i.split("/")[-1].split(".")[0]
if project_name in p_name_tmp:
project_name = "{}_{}".format(project_name, i.split("/")[-2])
tmp_data = {
"project_name": project_name,
"conf_file": "/".join(i.split("/")),
"remark": "Add locally"
}
tmp.append(tmp_data)
p_name_tmp.append(tmp_data['project_name'])
res = tmp
p_name_tmp.clear()
else:
yaml = "{}/docker-compose.yaml".format(get.path)
yam = "{}/docker-compose.yam".format(get.path)
yml = "{}/docker-compose.yml".format(get.path)
if os.path.exists(yaml):
res = [{
"project_name": get.path.split("/")[-1],
"conf_file": yaml,
"remark": "Add locally"
}]
elif os.path.exists(yam):
res = [{
"project_name": get.path.split("/")[-1],
"conf_file": yam,
"remark": "Add locally"
}]
elif os.path.exists(yml):
res = [{
"project_name": get.path.split("/")[-1],
"conf_file": yml,
"remark": "Add locally"
}]
else:
res = list()
if not os.path.isdir(get.path):
return public.return_message(0, 0, res)
dir_list = os.listdir(get.path)
for i in dir_list:
if i.rsplit(".")[-1] in suffix:
res.append({
"project_name": i.rsplit(".")[0],
"conf_file": "/".join(get.path.split("/") + [i]),
"remark": "Add locally"
})
# 去重
res = list({item['conf_file']: item for item in res}.values())
return public.return_message(0, 0, res)
# 从现有目录中添加模板
def add_template_in_path(self, get):
"""
:param template_list list [{"project_name":"pathtest_template","conf_file":"/www/dockerce/mysecent-project/docker-compose.yaml","remark":"描述描述"}]
:param get:
:return:
"""
create_failed = dict()
create_successfully = dict()
for template in get.template_list:
path = template['conf_file']
name = template['project_name']
remark = template['remark']
exists = self._template_list(get)
is_continue = False
for i in exists:
if name.strip() == i['name'].strip():
create_failed[name] = "Template already exists!"
is_continue = True
break
if is_continue: continue
if not os.path.exists(path):
create_failed[name] = "This template was not found!"
continue
check_res = self.check_conf(path)
if check_res['status'] == -1:
create_failed[name] = "Template validation failed, possibly malformed!"
continue
pdata = {
"name": name,
"remark": remark,
"path": path,
"add_in_path": 1
}
dp.sql("templates").insert(pdata)
create_successfully[name] = "Template added successfully!"
for i in create_failed:
if i in create_successfully:
del (create_successfully[i])
else:
dp.write_log("Template added successfully from path [{}]!".format(i))
if not create_failed and create_successfully:
return public.return_message(0, 0,
public.lang("Template added successfully: [{}]", ','.join(create_successfully)))
elif not create_successfully and create_failed:
return public.return_message(-1, 0,
public.lang("Failed to add template: template name already exists or is incorrectly formatted [{}],Use docker-compose -f [specify compose.yml file] config to check", ','.join(create_failed)))
return public.return_message(-1, 0,
public.lang('These templates succeed: [{}]<br> These templates fail: the template name already exists or is incorrectly formatted [{}]',','.join(create_successfully), ','.join(create_failed)))
def get_pull_log(self, get):
"""
获取镜像拉取日志websocket
@param get:
@return:
"""
get.wsLogTitle = "Start to pull the template image, please wait..."
get._log_path = self._log_path
return self.get_ws_log(get)
# 编辑项目 todo 根据删除适配
def edit(self, get):
"""
:param project_id: 要编辑的项目的ID
:param project_name: 新的项目名
:param remark: 新的描述
:param template_id: 新的模板ID
:return:
"""
# {"project_id": 1, "template_id": 2, "project_name": "福达坊", "remark": ""}
# 校验参数
try:
get.validate([
Param('project_id').Require().Integer(),
Param('project_name').Require().String(),
Param('remark').String(),
Param('template_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))
# 删除旧的项目
remove_result = self.remove(get)
# if not remove_result['status']:
# return public.return_message(-1, 0, public.lang("Fail to modify!"))
# 创建新的项目
self.create(get)
return public.return_message(0, 0, public.lang("Modify successfully!"))