1151 lines
45 KiB
Python
1151 lines
45 KiB
Python
|
|
# 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!"))
|