Download bt-source/panel/mod/project/docker/composeMod.py from SZFSPC/apple: direct link, hf CLI and curl.
- Browser
- Download file 39.6 kB
-
https://huggingface.co/spaces/SZFSPC/apple/resolve/main/bt-source/panel/mod/project/docker/composeMod.py
- Command line
-
hf download hf://spaces/SZFSPC/apple/bt-source/panel/mod/project/docker/composeMod.py
-
curl -L -o composeMod.py https://huggingface.co/spaces/SZFSPC/apple/resolve/main/bt-source/panel/mod/project/docker/composeMod.py
39.6 kB
| # coding: utf-8 | |
| # ------------------------------------------------------------------- | |
| # 宝塔Linux面板 | |
| # ------------------------------------------------------------------- | |
| # Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. | |
| # ------------------------------------------------------------------- | |
| # Author: wzz <wzz@bt.cn> | |
| # ------------------------------------------------------------------- | |
| # ------------------------------ | |
| # docker模型 - docker compose | |
| # ------------------------------ | |
| import json | |
| 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 | |
| os.chdir("/www/server/panel") | |
| if "/www/server/panel" not in sys.path: | |
| sys.path.insert(0, "/www/server/panel") | |
| from mod.project.docker.docker_compose.base import Compose | |
| # 2024/6/25 下午2:16 检查相同传参的装饰器 | |
| def check_file(func): | |
| ''' | |
| @name 检查相同传参的装饰器 | |
| @author wzz <2024/6/25 下午2:30> | |
| @param get.path : 传docker-compose.yaml的绝对路劲; | |
| get.def_name : 传需要使用的函数名,如get_log | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| def wrapper(self, get, *args, **kwargs): | |
| try: | |
| get.path = get.get("path/s", None) | |
| if get.path is None: | |
| get._ws.send(json.dumps(self.wsResult(False, "path参数不能为空", code=1))) | |
| return | |
| if not os.path.exists(get.path): | |
| get._ws.send( | |
| json.dumps(self.wsResult(False, "[{}] 文件不存在".format(get.path), code=2))) | |
| return | |
| func(self, get, *args, **kwargs) | |
| if get.def_name in ("create", "up", "update", "start", "stop", "restart","rebuild"): | |
| get._ws.send( | |
| json.dumps(self.wsResult(True, " {}完成,如日志无异常再关闭此窗口!\r\n".format(get.option), data=-1, code=-1))) | |
| except Exception as e: | |
| return | |
| return wrapper | |
| class main(Compose): | |
| def __init__(self): | |
| super(main, self).__init__() | |
| # 2024/6/25 下午2:41 执行docker-compose命令获取实时输出 | |
| def exec_cmd(self, get, command): | |
| ''' | |
| @name 执行docker-compose命令获取实时输出 | |
| @author wzz <2024/6/25 下午2:41> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if self.def_name is None: self.set_def_name(get.def_name) | |
| import pty | |
| try: | |
| def read_output(fd, ws): | |
| while True: | |
| output = os.read(fd, 1024) | |
| if not output: | |
| break | |
| if hasattr(get, '_ws'): | |
| ws.send(json.dumps(self.wsResult( | |
| True, | |
| output.decode(), | |
| ))) | |
| pid, fd = pty.fork() | |
| if pid == 0: | |
| os.execvp(command[0], command) | |
| else: | |
| read_output(fd, get._ws) | |
| except: | |
| if self.def_name in ("get_logs", "get_project_container_logs"): | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| True, | |
| "", | |
| ))) | |
| return | |
| # 2024/6/25 下午2:44 更新指定docker-compose里面的镜像 | |
| def update(self, get): | |
| ''' | |
| @name 更新指定docker-compose里面的镜像 | |
| @param get | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| get.option = "更新" | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| True, | |
| "", | |
| ))) | |
| command = self.set_type(1).set_path(get.path).get_compose_pull() | |
| self.status_exec_logs(get, command) | |
| command = self.set_type(1).set_path(get.path).get_compose_up_remove_orphans() | |
| self.status_exec_logs(get, command) | |
| # 2024/6/28 下午2:19 重建指定docker-compose项目 | |
| def rebuild(self, get): | |
| ''' | |
| @name 重建指定docker-compose项目 | |
| @param get | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| get.option = "重建" | |
| command = self.set_type(1).set_path(get.path).get_compose_down() | |
| self.status_exec_logs(get, command) | |
| command = self.set_type(1).set_path(get.path).get_compose_up_remove_orphans() | |
| self.status_exec_logs(get, command) | |
| # 2024/6/24 下午10:54 停止指定docker-compose项目 | |
| def stop(self, get): | |
| ''' | |
| @name 停止指定docker-compose项目 | |
| @author wzz <2024/6/24 下午10:54> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| get.option = "停止" | |
| command = self.set_type(1).set_path(get.path).get_compose_stop() | |
| self.status_exec_logs(get, command) | |
| # 2024/6/24 下午10:54 启动指定docker-compose项目 | |
| def start(self, get): | |
| ''' | |
| @name 启动指定docker-compose项目 | |
| ''' | |
| get.option = "启动" | |
| command = self.set_type(1).set_path(get.path).get_compose_up_remove_orphans() | |
| self.status_exec_logs(get, command) | |
| # 2024/6/24 下午11:23 down指定docker-compose项目 | |
| def down(self, get): | |
| ''' | |
| @name 停止指定docker-compose项目,并删除容器、网络、镜像等 | |
| ''' | |
| get.option = "停止" | |
| command = self.set_type(1).set_path(get.path).get_compose_down() | |
| self.status_exec_logs(get, command) | |
| # 2024/6/24 下午11:23 部署指定docker-compose项目 | |
| def up(self, get): | |
| ''' | |
| @name 部署指定docker-compose项目 | |
| ''' | |
| get.option = "添加容器编排" | |
| command = self.set_type(1).set_path(get.path).get_compose_up_remove_orphans() | |
| self.status_exec_logs(get, command) | |
| # 2024/6/24 下午11:23 重启指定docker-compose项目 | |
| def restart(self, get): | |
| ''' | |
| @name 重启指定docker-compose项目 | |
| ''' | |
| get.option = "重启" | |
| command = self.set_type(1).set_path(get.path).get_compose_restart() | |
| # self.exec_logs(get, command) | |
| self.status_exec_logs(get, command) | |
| # 2024/6/26 下午4:28 获取docker-compose ls -a --format json | |
| def ls(self, get): | |
| ''' | |
| @name 获取docker-compose ls -a --format json | |
| ''' | |
| get.option = "获取编排列表" | |
| command = self.get_compose_ls() | |
| try: | |
| cmd_result = public.ExecShell(command)[0] | |
| if "Segmentation fault" in cmd_result: | |
| return [] | |
| return json.loads(cmd_result) | |
| except: | |
| return [] | |
| # 2024/6/26 下午8:38 获取指定compose.yaml的docker-compose ps | |
| def ps(self, get): | |
| ''' | |
| @name 获取指定compose.yaml的docker-compose ps | |
| @author wzz <2024/6/26 下午8:38> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| get.path = get.get("path/s", None) | |
| if get.path is None: | |
| get._ws.send(json.dumps(self.wsResult(False, "path参数不能为空", code=1))) | |
| return self.wsResult(False, "path参数不能为空", code=1) | |
| if not os.path.exists(get.path): | |
| get._ws.send( | |
| json.dumps(self.wsResult(False, "[{}] 文件不存在".format(get.path), code=2))) | |
| return self.wsResult(False, "[{}] 文件不存在".format(get.path), code=1) | |
| get.option = "获取指定编排的容器信息" | |
| command = self.set_path(get.path, rep=True).get_compose_ps() | |
| try: | |
| cmd_result = public.ExecShell(command)[0] | |
| if "Segmentation fault" in cmd_result: | |
| return [] | |
| if not cmd_result.startswith("["): | |
| return json.loads("[" + cmd_result.strip().replace("\n", ",") + "]") | |
| else: | |
| return json.loads(cmd_result.strip().replace("\n", ",")) | |
| except: | |
| self.ps_count += 1 | |
| if self.ps_count < 5: | |
| time.sleep(0.5) | |
| return self.ps(get) | |
| return [] | |
| # 2024/6/24 下午10:53 获取指定docker-compose的运行日志 | |
| def get_logs(self, get): | |
| ''' | |
| @name websocket接口,执行docker-compose命令,返回结果:执行self.get_compose_logs()命令 | |
| @param get | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| #每个compose容器获取10行日志 | |
| self.set_tail("10") | |
| get.option = "读取日志" | |
| command = self.set_type(1).set_path(get.path).get_compose_logs() | |
| self.exec_logs(get, command) | |
| # 2024/6/26 下午9:24 获取指定compose.yaml的内容 | |
| def get_config(self, get): | |
| ''' | |
| @name 获取指定compose.yaml的内容 | |
| @author wzz <2024/6/26 下午9:25> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if self.def_name is None: self.set_def_name(get.def_name) | |
| get.path = get.get("path/s", None) | |
| if get.path is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(False, "path参数不能为空", code=1))) | |
| return | |
| if not os.path.exists(get.path): | |
| if hasattr(get, '_ws'): | |
| get._ws.send( | |
| json.dumps(self.wsResult(False, "[{}] 文件不存在".format(get.path), code=2))) | |
| return | |
| try: | |
| config_body = public.readFile(get.path) | |
| # env_path = get.path.replace("docker-compose.yaml", ".env").replace("docker-compose.yml", ".env") | |
| # 获取文件路径 有些情况不是用标准文件名进行启动容器的 | |
| file_path = os.path.dirname(get.path) | |
| env_path = os.path.join(file_path, ".env") | |
| # 判断路径下.env 文件是否存在 | |
| env_body = public.readFile(env_path) if os.path.exists(env_path) else "" | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(True, "获取成功", data={ | |
| "config": config_body if config_body else "", | |
| "env": env_body if env_body else "", | |
| }))) | |
| return | |
| except: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(False, "获取失败", data={}, code=3))) | |
| return | |
| # 2024/6/26 下午9:31 保存指定compose.yaml的内容 | |
| def save_config(self, get): | |
| ''' | |
| @name 保存指定compose.yaml的内容 | |
| @param get | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if self.def_name is None: self.set_def_name(get.def_name) | |
| get.path = get.get("path/s", None) | |
| get.config = get.get("config/s", None) | |
| get.env = get.get("env/s", None) | |
| if get.path is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(False, "path参数不能为空", code=1))) | |
| return | |
| if not os.path.exists(get.path): | |
| if hasattr(get, '_ws'): | |
| get._ws.send( | |
| json.dumps(self.wsResult(False, "[{}] 文件不存在".format(get.path), code=2))) | |
| return | |
| if public.check_chinese(get.path): | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(False, "文件路径不能包含中文!", code=3))) | |
| return | |
| if get.config is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(False, "config参数不能为空", code=3))) | |
| return | |
| if get.env is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(False, "env参数不能为空", code=3))) | |
| return | |
| try: | |
| stdout, stderr = self.check_config(get) | |
| if stderr: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "保存失败,请检查compose.yaml文件格式是否正确: 【{}】".format(stderr), | |
| code=4, | |
| ))) | |
| return | |
| if "Segmentation fault" in stdout: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "保存失败,docker-compose 版本过低,请升级到最新版!", | |
| code=4, | |
| ))) | |
| return | |
| public.writeFile(get.path, get.config) | |
| env_path = os.path.join(os.path.dirname(get.path), ".env") | |
| public.writeFile(env_path,get.env) | |
| # self.up(get) | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| True, | |
| "保存成功", | |
| ))) | |
| return | |
| except: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "保存失败", | |
| ))) | |
| return | |
| # 2024/6/27 上午10:25 检查compose内容是否正确 | |
| def check_config(self, get): | |
| ''' | |
| @name 检查compose内容是否正确 | |
| @author wzz <2024/6/27 上午10:26> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if not os.path.exists("/tmp/btdk"): | |
| os.makedirs("/tmp/btdk", 0o755, True) | |
| tmp_path = "/tmp/btdk/{}".format(os.path.basename(public.GetRandomString(10).lower())) | |
| public.writeFile(tmp_path, get.config) | |
| public.writeFile("/tmp/btdk/.env", get.env) | |
| command = self.set_path(tmp_path, rep=True).get_compose_config() | |
| stdout, stderr = public.ExecShell(command) | |
| if "`version` is obsolete" in stderr: | |
| public.ExecShell("sed -i '/version/d' {}".format(tmp_path)) | |
| get.config = public.readFile(tmp_path) | |
| return self.check_config(get) | |
| public.ExecShell("rm -f {}".format(tmp_path)) | |
| return stdout, stderr | |
| # 2024/6/27 上午10:06 根据内容创建docker-compose编排 | |
| def create(self, get): | |
| ''' | |
| @name 根据内容创建docker-compose编排 | |
| @author wzz <2024/6/27 上午10:07> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if self.def_name is None: self.set_def_name(get.def_name) | |
| get.project_name = get.get("project_name/s", None) | |
| if get.project_name is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "project_name参数不能为空", | |
| code=1, | |
| ))) | |
| return | |
| get.config = get.get("config/s", None) | |
| if get.config is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "config参数不能为空", | |
| code=2, | |
| ))) | |
| return | |
| stdout, stderr = self.check_config(get) | |
| if stderr: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "创建失败,请检查compose.yaml文件格式是否正确:\r\n{}".format(stderr.replace("\n", "\r\n")), | |
| code=4, | |
| ))) | |
| return | |
| if "Segmentation fault" in stdout: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "创建失败,docker-compose 版本过低,请升级到最新版!", | |
| code=4, | |
| ))) | |
| return | |
| # 2024/2/20 下午 3:21 如果检测到是中文的compose,则自动转换为英文 | |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) | |
| try: | |
| name_map = json.loads(public.readFile(config_path)) | |
| import re | |
| if re.findall(r"[\u4e00-\u9fa5]", get.project_name): | |
| name_str = 'bt_compose_' + public.GetRandomString(10).lower() | |
| name_map[name_str] = get.project_name | |
| get.project_name = name_str | |
| public.writeFile(config_path, json.dumps(name_map)) | |
| except: | |
| pass | |
| if not os.path.exists(self.compose_project_path): os.makedirs(self.compose_project_path, 0o755, True) | |
| if not os.path.exists(os.path.join(self.compose_project_path, get.project_name)): | |
| os.makedirs(os.path.join(self.compose_project_path, get.project_name), 0o755, True) | |
| get.path = os.path.join(self.compose_project_path, "{}/docker-compose.yaml".format(get.project_name)) | |
| public.writeFile(get.path, get.config) | |
| public.writeFile(get.path.replace("docker-compose.yaml", ".env").replace("docker-compose.yml", ".env"), get.env) | |
| get.add_template = get.get("add_template/d", 0) | |
| template_id = None | |
| from btdockerModel import dk_public as dp | |
| if get.add_template == 1: | |
| get.template_name = get.get("template_name/s", None) | |
| if get.template_name is None: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "template_name参数不能为空", | |
| code=1, | |
| ))) | |
| return | |
| from btdockerModel import composeModel as cm | |
| template_list = cm.main().template_list(get) | |
| for template in template_list: | |
| if get.template_name == template['name']: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "模板名称已存在,请删除模板后再添加!", | |
| code=2, | |
| ))) | |
| return | |
| #添加编排模板 ---------- 可以直接引用composeModel.add_template | |
| template_path = os.path.join(self.compose_project_path, "{}".format(get.template_name)) | |
| compose_path = os.path.join(template_path,"docker-compose.yaml") | |
| env_path = os.path.join(template_path,".env") | |
| pdata = { | |
| "name": get.template_name, | |
| "remark": "", | |
| "path": template_path, | |
| "add_in_path":1 | |
| } | |
| template_id = dp.sql("templates").insert(pdata) | |
| if not os.path.exists(template_path): | |
| os.makedirs(template_path, 0o755, True) | |
| public.writeFile(compose_path, get.config) | |
| public.writeFile(env_path,get.env) | |
| get.remark = get.get("remark/s", "") | |
| 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": get.path, | |
| "template_id": template_id, | |
| "time": time.time(), | |
| "remark": public.xsssec(get.remark) | |
| } | |
| dp.sql("stacks").insert(pdata) | |
| else: | |
| check_status = public.ExecShell("docker-compose ls |grep {}".format(get.path))[0] | |
| if not check_status: | |
| dp.sql("stacks").where("name=?", (public.xsssec(get.project_name))).delete() | |
| else: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "项目名已经存在,请先删除后再添加!", | |
| code=3, | |
| ))) | |
| return | |
| self.up(get) | |
| # 2024/6/27 上午11:42 删除指定compose.yaml的docker-compose编排 | |
| def delete(self, get): | |
| ''' | |
| @name 删除指定compose.yaml的docker-compose编排 | |
| @author wzz <2024/6/27 上午11:42> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if self.def_name is None: self.set_def_name(get.def_name) | |
| get.project_name = get.get("project_name/s", None) | |
| if get.project_name is None: | |
| get._ws.send(json.dumps(self.wsResult(False, "project_name参数不能为空", code=1))) | |
| return | |
| get.path = get.get("path/s", None) | |
| if get.path is None: | |
| get._ws.send(json.dumps(self.wsResult(False, "path参数不能为空", code=1))) | |
| return | |
| from btdockerModel import dk_public as dp | |
| stacks_info = dp.sql("stacks").where("path=? or name=?", (get.path, get.project_name)).find() | |
| if stacks_info: | |
| dp.sql("stacks").where("path=? or name=?", (get.path, get.project_name)).delete() | |
| if "bt_compose_" in get.path: | |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) | |
| name_map = json.loads(public.readFile(config_path)) | |
| bt_compose_name = os.path.dirname(get.path).split("/")[-1] | |
| if bt_compose_name in name_map: | |
| name_map.pop(bt_compose_name) | |
| public.writeFile(config_path, json.dumps(name_map)) | |
| stacks_list = dp.sql("stacks").select() | |
| compose_list = self.ls(get) | |
| for i in stacks_list: | |
| for j in compose_list: | |
| if i['name'] == j['Name']: | |
| break | |
| if public.md5(i['name']) in j['Name']: | |
| break | |
| else: | |
| dp.sql("stacks").where("name=?", (i['name'])).delete() | |
| if not os.path.exists(get.path): | |
| command = self.set_type(0).set_compose_name(get.project_name).get_compose_delete_for_ps() | |
| else: | |
| command = self.set_type(0).set_path(get.path).get_compose_delete() | |
| stdout, stderr = public.ExecShell(command) | |
| if "invalid compose project" in stderr: | |
| command = self.set_type(0).set_compose_name(get.project_name).get_compose_delete_for_ps() | |
| stdout, stderr = public.ExecShell(command) | |
| if stderr and "Error" in stderr: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "删除失败,请检查compose.yaml文件格式是否正确:\r\n{}".format(stderr.replace("\n", "\r\n")), | |
| data=-1, | |
| code=4, | |
| ))) | |
| return | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| True, | |
| "删除容器编排", | |
| data=-1, | |
| code=0 | |
| ))) | |
| # 2024/6/27 下午8:39 批量删除指定compose.yaml的docker-compose编排 | |
| def batch_delete(self, get): | |
| ''' | |
| @name 批量删除指定compose.yaml的docker-compose编排 | |
| @param get | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if self.def_name is None: self.set_def_name(get.def_name) | |
| get.project_list = get.get("project_list", None) | |
| if get.project_list is None or len(get.project_list) == 0: | |
| return self.wsResult(False, "project_list参数不能为空", code=1) | |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) | |
| try: | |
| name_map = json.loads(public.readFile(config_path)) | |
| except: | |
| name_map = {} | |
| for project in get.project_list: | |
| if not isinstance(project, dict): | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "project_list参数格式错误: {}".format(project), | |
| code=1, | |
| ))) | |
| continue | |
| if project["project_name"] is None or project["project_name"] == "": | |
| get._ws.send( | |
| json.dumps(self.wsResult(False, "project_name参数不能为空", code=1))) | |
| continue | |
| if project["path"] is None or project["path"] == "": | |
| get._ws.send(json.dumps(self.wsResult(False, "path参数不能为空", code=1))) | |
| continue | |
| from btdockerModel import dk_public as dp | |
| stacks_info = dp.sql("stacks").where("path=? or name=?", (project["path"], project["project_name"])).find() | |
| if stacks_info: | |
| dp.sql("stacks").where("path=? or name=?", (project["path"], project["project_name"])).delete() | |
| if "bt_compose_" in project["path"]: | |
| bt_compose_name = os.path.dirname(project["path"]).split("/")[-1] | |
| if bt_compose_name in name_map: | |
| name_map.pop(bt_compose_name) | |
| if not os.path.exists(project["path"]): | |
| command = self.set_type(0).set_compose_name(project["project_name"]).get_compose_delete_for_ps() | |
| else: | |
| command = self.set_type(0).set_path(project["path"], rep=True).get_compose_delete() | |
| stdout, stderr = public.ExecShell(command) | |
| if "Segmentation fault" in stdout: | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| False, | |
| "删除失败,docker-compose 版本过低,请升级到最新版!", | |
| code=4, | |
| ))) | |
| return | |
| # public.ExecShell("rm -rf {}".format(os.path.dirname(project["path"]))) | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult( | |
| True, | |
| data={ | |
| "project_name": project["project_name"], | |
| "status": True | |
| } | |
| ))) | |
| public.writeFile(config_path, json.dumps(name_map)) | |
| if hasattr(get, '_ws'): | |
| get._ws.send(json.dumps(self.wsResult(True, data=-1))) | |
| # 2024/6/28 下午3:15 根据容器id获取指定容器的日志 | |
| def get_project_container_logs(self, get): | |
| ''' | |
| @name 根据容器id获取指定容器的日志 | |
| @author wzz <2024/6/28 下午3:16> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| get.container_id = get.get("container_id/s", None) | |
| if get.container_id is None: | |
| return public.returnResult(False, "container_id参数不能为空", code=1) | |
| self.set_tail("200") | |
| self.set_container_id(get.container_id) | |
| command = self.get_container_logs() | |
| stdout, stderr = public.ExecShell(command) | |
| if "invalid compose project" in stderr: | |
| return public.returnResult(False, "容器不存在", code=2) | |
| return public.returnResult(True, stdout.replace("\n", "\r\n"), code=0) | |
| # 2024/7/18 上午10:13 修改指定项目备注 | |
| def edit_remark(self, get): | |
| ''' | |
| @name 修改指定项目备注 | |
| ''' | |
| get.name = get.get("name", None) | |
| get.remark = get.get("remark", "") | |
| if get.name is None: | |
| return public.returnResult(False, "缺少参数name!", code=2) | |
| old_remark = "" | |
| from btdockerModel import dk_public as dp | |
| stacks_info = dp.sql("stacks").where("name=?", (public.xsssec(get.name))).find() | |
| if not stacks_info: | |
| get.path = get.get("path", None) | |
| if get.path is None: | |
| return public.returnResult(False, "缺少参数path!", code=2) | |
| pdata = { | |
| "name": public.xsssec(get.name), | |
| "status": "1", | |
| "path": get.path, | |
| "template_id": None, | |
| "time": time.time(), | |
| "remark": public.xsssec(get.remark) | |
| } | |
| dp.sql("stacks").insert(pdata) | |
| else: | |
| old_remark = stacks_info['remark'] | |
| dp.sql("stacks").where("name=?", (public.xsssec(get.name))).update({"remark": public.xsssec(get.remark)}) | |
| dp.write_log("项目 [{}] 的备注修改成功 [{}] --> [{}]!".format( | |
| get.name, | |
| old_remark, | |
| public.xsssec(get.remark))) | |
| return public.returnResult(True, "修改成功!") | |
| def compose_backup_list(self, get): | |
| ''' | |
| @name 获取 指定compose项目备份列表 | |
| @param p <int> 当前页码 默认1 | |
| @param limit <int> 每页数量 默认20 | |
| @param search <str> 搜索关键词 | |
| @param type <str> 备份类型 1:本机备份 2:上传备份 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| page = int(get.p) if hasattr(get, 'p') else 1 | |
| limit = int(get.limit) if hasattr(get, 'limit') else 20 | |
| query = get.get("search", "") | |
| _type = get.get("type", "") | |
| where_str = "1=?" | |
| where_param = [1] | |
| if query: | |
| where_str += "and name=?" | |
| where_param.append(query) | |
| if _type: | |
| where_str += "and type=?" | |
| where_param.append(_type) | |
| count = public.M("compose_backup").where(where_str,where_param).count() | |
| page_data = public.get_page(count=count,p=page,rows=limit) | |
| backup_list = public.M("compose_backup").where(where_str,where_param).limit(page_data['row'],page_data['shift']).order('time desc').select() | |
| page_data["data"] = backup_list | |
| for backup in backup_list: | |
| backup["time"] = public.format_date(times= backup["time"]) | |
| public.set_module_logs('composeBackup', 'backup_list', 1) | |
| return page_data | |
| def compose_backup_delete(self, get): | |
| ''' | |
| @name 删除 compose项目备份 | |
| @param id <int> 备份ID | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| # 1. 获取id参数 | |
| backup_id = get.get("id", None) | |
| if backup_id is None: | |
| return public.returnResult(False, "id参数不能为空") | |
| # 2. 检查备份是否存在 | |
| backup_info = public.M("compose_backup").where("id=?", (backup_id,)).find() | |
| if not backup_info: | |
| return public.returnResult(False, "备份不存在") | |
| # 3. 删除备份记录 | |
| public.M("compose_backup").where("id=?", (backup_id,)).delete() | |
| # 4. 删除备份文件 | |
| public.ExecShell(f"rm -rf {backup_info['path']}") | |
| return public.returnResult(True, "删除成功") | |
| def compose_backup(self, get): | |
| ''' | |
| @name 备份 compose项目 | |
| @param path <str> compose项目路径 | |
| @param name <str> 备份名称 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| #往TMP下写入锁文件,防止重复备份 | |
| lock_file = f"/tmp/compose_backup.pl" | |
| if os.path.exists(lock_file): | |
| return public.returnResult(False, "当前有应用正在备份中,请等待完成后再试...") | |
| public.writeFile(lock_file, "") | |
| # 1. 获取path参数 | |
| path = get.get("path", None) | |
| if path is None: | |
| return public.returnResult(False, "path参数不能为空") | |
| # 2. 检查path是否存在 | |
| if not os.path.exists(path): | |
| return public.returnResult(False, "path参数不存在") | |
| compose_name = get.get("name", "") | |
| if not compose_name: | |
| return public.returnResult(False, "name参数不能为空") | |
| #检查name包含特殊字符 | |
| if "&" in compose_name or "|" in compose_name or ";" in compose_name or " " in compose_name: | |
| return public.returnResult(False, "name参数不能包含特殊字符(& | ; 空格等)") | |
| panel_path = public.get_panel_path() | |
| exec_shell = '(btpython -u {panel_path}/script/docker_compose_backup.py "{compose_name}" "{path}";rm -rf {lock_file})'.format(panel_path=panel_path, compose_name=compose_name, path=path, lock_file=lock_file) | |
| import panelTask | |
| task_obj = panelTask.bt_task() | |
| task_id = task_obj.create_task('compose项目备份任务', 0, exec_shell) | |
| public.set_module_logs('composeBackup', 'backup', 1) | |
| return {'status': True, 'msg': 'compose项目备份任务已创建.', 'task_id': task_id} | |
| def compose_restore_config(self, get): | |
| ''' | |
| @name 恢复 compose项目配置 | |
| @param id <int> 备份ID | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| backup_id = get.get("id", None) | |
| if backup_id is None: | |
| return public.returnResult(False, "id参数不能为空") | |
| backup_info = public.M("compose_backup").where("id=?", (backup_id,)).find() | |
| if not backup_info: | |
| return public.returnResult(False, "备份不存在") | |
| file_path = backup_info.get("path", "") | |
| if not file_path or not os.path.exists(file_path): | |
| return public.returnResult(False, "备份文件不存在") | |
| back_config = {} | |
| try: | |
| base = os.path.splitext(os.path.basename(file_path))[0] | |
| target_path = f"{base}/config.json" | |
| cmd = f"tar -xf {file_path} -C /tmp {target_path}" | |
| public.ExecShell(cmd) | |
| config = public.ReadFile(f"/tmp/{target_path}") | |
| back_config = json.loads(config) | |
| except Exception as e: | |
| return public.returnResult(False, "读取备份失败: {}".format(str(e))) | |
| if not back_config: | |
| return public.returnResult(False, "备份配置文件读取失败...") | |
| try: | |
| from mod.project.docker.docker_compose.compose_utils import DockerComposeUtils | |
| # 检查网络是否存在 | |
| nets = back_config.get("networks", []) | |
| for net in nets: | |
| net_name = net.get('name') | |
| if net_name: | |
| net["exists"] = DockerComposeUtils.network_exists(net_name) | |
| except Exception as e: | |
| return public.returnResult(False, "恢复配置失败: {}".format(str(e))) | |
| return public.returnResult(True, "获取成功", back_config) | |
| def compose_restore(self, get): | |
| ''' | |
| @name 恢复 compose项目 | |
| @param id <int> 备份ID | |
| @param skip_volumes <str> 不恢复的卷,多个用逗号分隔 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| backup_id = get.get("id", None) | |
| if backup_id is None: | |
| return public.returnResult(False, "id参数不能为空") | |
| lock_file = f"/tmp/compose_restore.pl" | |
| if os.path.exists(lock_file): | |
| return public.returnResult(False, "当前有应用正在备份中,请等待完成后再试...") | |
| public.writeFile(lock_file, "") | |
| backup_info = public.M("compose_backup").where("id=?", (backup_id,)).find() | |
| if not backup_info: | |
| return public.returnResult(False, "备份不存在") | |
| file_path = backup_info.get("path", "") | |
| if not file_path or not os.path.exists(file_path): | |
| return public.returnResult(False, "备份文件不存在") | |
| skip_volumes = get.get("skip_volumes", "") | |
| panel_path = public.get_panel_path() | |
| exec_shell = '(btpython -u {panel_path}/script/docker_compose_restore.py "{path}" {skip_volumes};rm -rf {lock_file})'.format(panel_path=panel_path,path=file_path, skip_volumes=skip_volumes, lock_file=lock_file) | |
| # public.print_log(exec_shell) | |
| import panelTask | |
| task_obj = panelTask.bt_task() | |
| task_id = task_obj.create_task('compose项目恢复任务', 0, exec_shell) | |
| public.set_module_logs('composeBackup', 'restore', 1) | |
| return {'status': True, 'msg': 'compose项目恢复任务已创建.', 'task_id': task_id} | |
| def import_backup(self, get): | |
| ''' | |
| @name 导入compose项目备份 | |
| @param path <str> 备份文件路径 | |
| @param ps <str> 备注信息 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| path = get.get("path", "") | |
| if not path: | |
| return public.returnResult(False, "path参数不能为空") | |
| if not os.path.exists(path): | |
| return public.returnResult(False, "备份文件不存在") | |
| # 检查是否已存在 | |
| # if public.M("compose_backup").where("path=?", (path,)).count() > 0: | |
| # return public.returnResult(False, "该备份文件已存在记录中") | |
| try: | |
| # 读取备份配置 | |
| base = os.path.splitext(os.path.basename(path))[0] | |
| target_path = f"{base}/config.json" | |
| cmd = f"tar -xf {path} -C /tmp {target_path}" | |
| public.ExecShell(cmd) | |
| tmp_path = f"/tmp/{target_path}" | |
| config_content = public.ReadFile(tmp_path) | |
| if not config_content: | |
| return public.returnResult(False, "读取备份失败: 未能提取配置文件") | |
| config = json.loads(config_content) | |
| if os.path.exists(tmp_path): | |
| os.remove(tmp_path) | |
| except Exception as e: | |
| return public.returnResult(False, "读取备份文件失败: {}".format(str(e))) | |
| # 准备数据库数据 | |
| try: | |
| file_size = os.path.getsize(path) | |
| project_name = config.get("project_name", "") | |
| project_dir = config.get("project_dir", "") | |
| back_time = config.get("back_time", "") | |
| pdata = { | |
| "type": "2", # 上传备份 | |
| "name": project_name, | |
| "path": path, | |
| "file_size": file_size, | |
| "compose_path": project_dir, | |
| "time": back_time, | |
| "ps": get.get("ps", "手动导入") | |
| } | |
| public.M("compose_backup").insert(pdata) | |
| public.set_module_logs('composeBackup', 'import_backup', 1) | |
| return public.returnResult(True, "导入成功") | |
| except Exception as e: | |
| return public.returnResult(False, "导入失败: {}".format(str(e))) | |