# coding: utf-8 # ------------------------------------------------------------------- # YakPanel # ------------------------------------------------------------------- # Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: wzz # ------------------------------------------------------------------- # ------------------------------ # 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!:
[{}]", ", ".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: [{}]
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!"))