Download bt-source/panel/class/btdockerModel/containerModel.py from SZFSPC/apple: direct link, hf CLI and curl.
- Browser
- Download file 77.1 kB
-
https://huggingface.co/spaces/SZFSPC/apple/resolve/main/bt-source/panel/class/btdockerModel/containerModel.py
- Command line
-
hf download hf://spaces/SZFSPC/apple/bt-source/panel/class/btdockerModel/containerModel.py
-
curl -L -o containerModel.py https://huggingface.co/spaces/SZFSPC/apple/resolve/main/bt-source/panel/class/btdockerModel/containerModel.py
77.1 kB
| # coding: utf-8 | |
| # ------------------------------------------------------------------- | |
| # 宝塔Linux面板 | |
| # ------------------------------------------------------------------- | |
| # Copyright (c) 2015-2099 宝塔软件(http://bt.cn) All rights reserved. | |
| # ------------------------------------------------------------------- | |
| # Author: wzz <wzz@bt.cn> | |
| # ------------------------------------------------------------------- | |
| import json | |
| import os | |
| import time | |
| import crontab | |
| import docker.errors | |
| # ------------------------------ | |
| # Docker模型 | |
| # ------------------------------ | |
| import public | |
| from btdockerModel import dk_public as dp | |
| from btdockerModel.dockerBase import dockerBase | |
| class main(dockerBase): | |
| def __init__(self): | |
| super().__init__() | |
| self.alter_table() | |
| if public.M('sqlite_master').db('docker_log_split').where('type=? AND name=?', ('table', 'docker_log_split')).count(): | |
| p = crontab.crontab() | |
| llist = p.GetCrontab(None) | |
| add_crond = True | |
| if type(llist) == list: | |
| for i in llist: | |
| if i['name'] == '[勿删]docker日志切割': | |
| add_crond = False | |
| break | |
| else: | |
| add_crond = True | |
| if add_crond: | |
| get = { | |
| "name": "[勿删]docker日志切割", | |
| "type": "minute-n", | |
| "where1": 5, | |
| "hour": "", | |
| "minute": "", | |
| "week": "", | |
| "sType": "toShell", | |
| "sName": "", | |
| "backupTo": "localhost", | |
| "save": '', | |
| "sBody": "btpython /www/server/panel/script/dk_log_split.py", | |
| "urladdress": "undefined" | |
| } | |
| p.AddCrontab(get) | |
| def alter_table(self): | |
| if not dp.sql('sqlite_master').where('type=? AND name=? AND sql LIKE ?', | |
| ('table', 'container', '%sid%')).count(): | |
| dp.sql('container').execute("alter TABLE container add container_name VARCHAR DEFAULT ''", ()) | |
| def docker_client(self, url): | |
| return dp.docker_client(url) | |
| def get_cmd_log(self, get): | |
| """ | |
| 获取命令运行容器的日志,websocket | |
| @param get: | |
| @return: | |
| """ | |
| get.wsLogTitle = "开始执行命令,请等待..." | |
| get._log_path = self._rCmd_log | |
| return self.get_ws_log(get) | |
| def run_cmd(self, get): | |
| """ | |
| 命令行创建运行容器(docker run),需要做危险命令校验,存在危险命令则不执行 | |
| @param get: | |
| @return: | |
| """ | |
| import re | |
| if not hasattr(get, 'cmd'): | |
| return public.returnMsg(False, '请传入cmd参数错误') | |
| if "docker run" not in get.cmd: | |
| return public.returnMsg(False, '只能执行docker run命令') | |
| danger_cmd = ['rm', 'rmi', 'kill', 'stop', 'pause', 'unpause', 'restart', 'update', 'exec', 'init', | |
| 'shutdown', 'reboot', 'chmod', 'chown', 'dd', 'fdisk', 'killall', 'mkfs', 'mkswap', 'mount', | |
| 'swapoff', 'swapon', 'umount', 'userdel', 'usermod', 'passwd', 'groupadd', 'groupdel', | |
| 'groupmod', 'chpasswd', 'chage', 'usermod', 'useradd', 'userdel', 'pkill'] | |
| danger_symbol = ['&', '&&', '||', '|', ';'] | |
| for d in danger_cmd: | |
| if get.cmd.startswith(d) or re.search(r'\s{}\s'.format(d), get.cmd): | |
| return public.returnMsg(False, '存在危险命令: [{}],不允许执行!'.format(d)) | |
| for d in danger_symbol: | |
| if d in get.cmd: | |
| return public.returnMsg(False, '存在危险符号: [{}],不允许执行!'.format(d)) | |
| get.cmd = get.cmd.replace("\n", "").replace("\r", "").replace("\\", " ") | |
| os.system("echo -n > {}".format(self._rCmd_log)) | |
| os.system("nohup {} >> {} 2>&1 && echo 'bt_successful' >> {} || echo 'bt_failed' >> {} &".format( | |
| get.cmd, | |
| self._rCmd_log, | |
| self._rCmd_log, | |
| self._rCmd_log, | |
| )) | |
| return public.returnMsg(True, "命令已执行完毕!") | |
| # 添加容器 | |
| def run(self, get): | |
| """ | |
| :param name:容器名 | |
| :param image: 镜像 | |
| :param publish_all_ports 暴露所有端口 1/0 | |
| :param ports 暴露某些端口 {'1111/tcp': ('127.0.0.1', 1111)} | |
| :param command 命令 | |
| :param entrypoint 配置容器启动后执行的命令 | |
| :param environment 环境变量 xxx=xxx 一行一条 | |
| :param auto_remove 当容器进程退出时,在守护进程端启用自动移除容器。 0/1 | |
| :param get: | |
| :return: | |
| """ | |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) | |
| if not os.path.exists(config_path): | |
| public.writeFile(config_path, json.dumps({})) | |
| if public.readFile(config_path) == '': | |
| public.writeFile(config_path, json.dumps({})) | |
| # 2024/2/20 下午 3:21 如果检测到是中文的容器名,则自动转换为英文 | |
| name_map = json.loads(public.readFile(config_path)) | |
| import re | |
| if re.findall(r"[\u4e00-\u9fa5]", get.name): | |
| name_str = 'q18q' + public.GetRandomString(10).lower() | |
| name_map[name_str] = get.name | |
| get.name = name_str | |
| public.writeFile(config_path, json.dumps(name_map)) | |
| cPorts = get.ports if "ports" in get and get.ports != "" else False | |
| nPorts = {} | |
| if not cPorts is False: | |
| if ":" in cPorts.keys(): | |
| return public.returnMsg(False, "端口格式错误,暂不支持此种方式!") | |
| if "-" in cPorts.keys(): | |
| return public.returnMsg(False, "端口格式错误,暂不支持此种方式!") | |
| for i in cPorts.keys(): | |
| if cPorts[i] == "": continue | |
| if isinstance(cPorts[i], list): | |
| cPorts[i] = tuple(cPorts[i]) | |
| check_port = cPorts[i] | |
| if isinstance(cPorts[i], tuple): | |
| check_port = cPorts[i][1] | |
| if dp.check_socket(check_port): | |
| return public.returnMsg(False, "服务器端口[{}]已被占用,请更换为其他端口!".format(cPorts[i])) | |
| if "tcp/udp" in i: | |
| cPort = i.split('/')[0] | |
| nPorts[str(cPort) + "/tcp"] = cPorts[i] | |
| nPorts[str(cPort) + "/udp"] = cPorts[i] | |
| else: | |
| nPorts[i] = cPorts[i] | |
| del cPorts | |
| if "image" not in get or not get.image: | |
| return public.returnMsg(False, "如果没有选择镜像,请到镜像标签拉取您需要的镜像!") | |
| if get.image == "<none>" or "<none>" in get.image: | |
| return public.returnMsg(False, "镜像不存在,请检查镜像名称是否正确!") | |
| mem_limit = get.mem_limit if "mem_limit" in get and get.mem_limit != "0" else None | |
| if not mem_limit is None: | |
| mem_limit_byte = dp.byte_conversion(get.mem_limit) | |
| if mem_limit_byte > dp.get_mem_info(): | |
| return public.returnMsg(False, "内存配额已超过可用数量!") | |
| if mem_limit_byte < 6291456: | |
| return public.returnMsg(False, "内存配额不能小于6MB!") | |
| try: | |
| if "force_pull" in get and get.force_pull == "0": | |
| self.docker_client(self._url).images.get(get.image) | |
| except docker.errors.ImageNotFound as e: | |
| return public.returnMsg(False, "镜像[{}]不存在,如需尝试强制拉取请勾选【强制拉取】按钮!".format(get.image)) | |
| except docker.errors.APIError as e: | |
| return public.returnMsg(False, "镜像[{}]不存在,如需尝试强制拉取请勾选【强制拉取】按钮!".format(get.image)) | |
| # 2024/4/16 上午11:40 检查镜像是否存在并且处理镜像如果是非应用容器的情况 | |
| try: | |
| from btdockerModel.dockerSock import image | |
| sk_image = image.dockerImage() | |
| check_image = get.image if ":" in get.image else get.image + ":latest" | |
| image_inspect = sk_image.inspect(check_image) | |
| if type(image_inspect) != dict or "message" in image_inspect: | |
| self.docker_client(self._url).images.pull(check_image) | |
| if "Config" in image_inspect and "Cmd" in image_inspect["Config"]: | |
| sh_list = ("bash", "sh", "dash", "/bin/sh", "/bin/bash", "/bin/dash") | |
| if len(image_inspect["Config"]["Cmd"]) == 1 and image_inspect["Config"]["Cmd"][0] in sh_list: | |
| get.tty = "1" | |
| get.stdin_open = "1" | |
| except Exception as e: | |
| pass | |
| cpu_quota = get.cpu_quota if "cpu_quota" in get and get.cpu_quota != "0" else 0 | |
| if int(cpu_quota) != 0: | |
| cpu_quota = float(get.cpu_quota) * 100000 | |
| if int(cpu_quota) / 100000 > dp.get_cpu_count(): | |
| return public.returnMsg(False, "CPU 配额已超过可用内核数!") | |
| df_restart_policy = {"Name": "unless-stopped", "MaximumRetryCount": 0} | |
| restart_policy = get.restart_policy if "restart_policy" in get and get.restart_policy else df_restart_policy | |
| if restart_policy['Name'] == "always": | |
| restart_policy = {"Name": "always"} | |
| mem_reservation = get.mem_reservation if "mem_reservation" in get and get.mem_reservation != "" else None | |
| # 2023/12/19 下午 3:08 检测如果小于6MB则报错 | |
| if not mem_reservation is None and mem_reservation != "0": | |
| mem_reservation_byte = dp.byte_conversion(mem_reservation) | |
| if mem_reservation_byte < 6291456: | |
| return public.returnMsg(False, "内存预留不能小于6MB!") | |
| network_info = get.network_info if hasattr(get, "network_info") and get.network_info != "" else [] | |
| device_request = [] | |
| gpus = str(get.get("gpus", "0")) | |
| if gpus != "0": | |
| count = 0 | |
| if gpus == "all": | |
| count = -1 | |
| else: | |
| count = int(gpus) | |
| from docker.types import DeviceRequest | |
| device_request.append(DeviceRequest(driver="nvidia",count=count,capabilities=[["gpu"]])) | |
| try: | |
| res = self.docker_client(self._url).containers.create( | |
| name=get.name, | |
| image=get.image, | |
| detach=True, | |
| # cpuset_cpus=get.cpuset_cpus ,#指定容器使用的cpu个数 | |
| tty=True if "tty" in get and get.tty == "1" else False, | |
| stdin_open=True if "stdin_open" in get and get.stdin_open == "1" else False, | |
| publish_all_ports=True if "publish_all_ports" in get and get.publish_all_ports != "0" else False, | |
| ports=nPorts if len(nPorts) > 0 else None, | |
| cpu_quota=int(cpu_quota) or 0, | |
| mem_reservation=mem_reservation, # b,k,m,g | |
| mem_limit=mem_limit, # b,k,m,g | |
| restart_policy=restart_policy, | |
| command=get.command if "command" in get and get.command != "" else None, | |
| volume_driver=get.volume_driver if "volume_driver" in get and get.volume_driver != "" else None, | |
| volumes=get.volumes if "volumes" in get and get.volumes != "" else None, | |
| auto_remove=True if "auto_remove" in get and get.auto_remove != "0" else False, | |
| privileged=True if "privileged" in get and get.privileged != "0" else False, | |
| environment=dp.set_kv(get.environment), # "HOME=/value\nHOME11=value1" | |
| labels=dp.set_kv(get.labels), # "key=value\nkey1=value1" | |
| device_requests=device_request | |
| ) | |
| except docker.errors.APIError as e: | |
| if "invalid reference format" in str(e): | |
| return public.returnMsg(False, "镜像名称格式不正确,请输入正确的镜像名,如:nginx:latest") | |
| if "failed to create task for container" in str(e) or "failed to create shim task" in str(e): | |
| return public.returnMsg(False, "容器创建失败,详情:{}!".format(str(e))) | |
| if "Minimum memory limit can not be less than memory reservation limit, see usage" in str(e): | |
| return public.returnMsg(False, "内存配额不能小于内存保留量!") | |
| if "already exists in network bridge" in str(e): | |
| return public.returnMsg(False, "容器名称或网络桥接器已存在,请更换容器名后再试!") | |
| if "No command specified" in str(e): | |
| return public.returnMsg(False, "该镜像中无启动命令,请指定容器启动命令!") | |
| if "permission denied" in str(e): | |
| return public.returnMsg(False, "权限异常!详情:{}".format(str(e))) | |
| if "Internal Server Error" in str(e): | |
| return public.returnMsg(False, "容器创建失败!docker主进程或iptables异常,请在合适的时候重启docker服务!") | |
| if "repository does not exist or may require 'docker login'" in str(e): | |
| return public.returnMsg(False, "镜像[{}]不存在,请检查镜像名称是否正确!".format(get.image)) | |
| if "Minimum memory reservation allowed is 6MB" in str(e): | |
| return public.returnMsg(False, "内存预留不能小于6MB!") | |
| if "container to be able to reuse that name." in str(e): | |
| return public.returnMsg(False, "容器名称已存在!") | |
| if "Invalid container name" in str(e): | |
| return public.returnMsg(False, "容器名称不合法,") | |
| if "bind: address already in use" in str(e): | |
| port = "" | |
| for i in get.ports: | |
| if ":{}:".format(get.ports[i]) in str(e): | |
| port = get.ports[i] | |
| get.id = get.name | |
| self.del_container(get) | |
| return public.returnMsg(False, "服务器端口 {} 正在使用中! 请更改其他端口".format(port)) | |
| return public.returnMsg(False, '创建失败! {}'.format(str(e))) | |
| except Exception as a: | |
| self.del_container(get) | |
| if "Read timed out" in str(a): | |
| return public.returnMsg(False, "容器创建失败,连接docker超时,请尝试重启docker后再试!") | |
| return public.returnMsg(False, '容器运行失败! {}'.format(str(a))) | |
| if res: | |
| # 将容器的ip改成用户指定的ip | |
| pdata = { | |
| "cpu_limit": str(get.cpu_quota), | |
| "container_name": get.name | |
| } | |
| dp.sql('container').insert(pdata) | |
| public.set_module_logs('docker', 'run_container', 1) | |
| dp.write_log("创建容器 [{}] 成功!".format(get.name)) | |
| # 2024/2/26 下午 6:00 添加备注 | |
| self.check_remark_table() | |
| dp.sql('dk_container_remark').insert({ | |
| "container_id": res.id, | |
| "container_name": get.name, | |
| "remark": public.xssencode2(get.remark), | |
| "addtime": int(time.time()) | |
| }) | |
| if len(network_info) > 0: | |
| self.docker_client(self._url).networks.get("bridge").disconnect(res.id) | |
| for network in network_info: | |
| get.net_name = network["network"] | |
| get.new_container_id = res.id | |
| get.tmp_ip_address = network["ip_address"] | |
| get.tmp_ip_addressv6 = network["ip_addressv6"] | |
| net_result = self.connent_network(get) | |
| if not net_result["status"]: | |
| return net_result | |
| res.start() | |
| return { | |
| "status": True, | |
| "msg": "创建成功!", | |
| "id": res.id, | |
| "name": dp.rename(res.name), | |
| } | |
| # return public.returnMsg(True, "容器创建成功!") | |
| return public.returnMsg(False, '创建失败!') | |
| # 2024/11/12 17:15 创建并启动容器 | |
| def create_some_container(self, get, new_name=None): | |
| ''' | |
| @name 创建并启动容器 | |
| ''' | |
| container = new_name if not new_name is None else get.new_container_config["name"] | |
| network_info = get.network_info if "network_info" in get and get.network_info != "" else [] | |
| try: | |
| get.new_container = self.docker_client(self._url).containers.create( | |
| name=container, | |
| image=get.new_container_config["image"], | |
| detach=get.new_container_config["detach"], | |
| cpu_quota=get.new_container_config["cpu_quota"], | |
| mem_limit=get.new_container_config["mem_limit"], | |
| tty=get.new_container_config["tty"], | |
| stdin_open=get.new_container_config["stdin_open"], | |
| publish_all_ports=True if get.new_container_config["publish_all_ports"] == "1" else False, | |
| ports=get.new_container_config["ports"], | |
| command=get.new_container_config["command"], | |
| entrypoint=get.new_container_config["entrypoint"], | |
| environment=get.new_container_config["environment"], | |
| labels=get.new_container_config["labels"], | |
| auto_remove=get.new_container_config["auto_remove"], | |
| privileged=get.new_container_config["privileged"], | |
| volumes=get.new_container_config["volumes"], | |
| volume_driver=get.new_container_config["volume_driver"], | |
| mem_reservation=get.new_container_config["mem_reservation"], | |
| restart_policy=get.new_container_config["restart_policy"], | |
| device_requests=get.new_container_config["device_requests"] | |
| ) | |
| except Exception as e: | |
| if "Read timed out" in str(e): | |
| return public.returnMsg(False, "容器编辑失败,连接docker超时,请尝试重启docker后再试!") | |
| return public.returnMsg(False, "更新失败!{}".format(str(e))) | |
| if not "upgrade" in get or get.upgrade != "1": | |
| if len(network_info) > 0: | |
| self.docker_client(self._url).networks.get("bridge").disconnect(get.new_container.id) | |
| for temp in network_info: | |
| get.net_name = temp["network"] | |
| get.new_container_id = get.new_container.id | |
| get.tmp_ip_address = temp["ip_address"] | |
| get.tmp_ip_addressv6 = temp["ip_addressv6"] | |
| net_result = self.connent_network(get) | |
| if not net_result["status"]: | |
| get.new_container.remove() | |
| return net_result | |
| else: | |
| self.docker_client(self._url).networks.get("bridge").disconnect(get.new_container.id) | |
| for net_name, net_settings in get.old_container_config["networking_config"].items(): | |
| get.net_name = net_name | |
| get.new_container_id = get.new_container.id | |
| get.tmp_ip_address = net_settings["IPAddress"] | |
| get.tmp_ip_addressv6 = net_settings["GlobalIPv6Address"] | |
| net_result = self.connent_network(get) | |
| if not net_result["status"]: | |
| get.new_container.remove() | |
| return net_result | |
| get.new_container.start() | |
| def upgrade_container(self, get): | |
| """ | |
| 更新正在运行的容器镜像(重建) | |
| @param get: | |
| @return: | |
| """ | |
| public.set_module_logs('编辑容器', 'upgrade_container', 1) | |
| try: | |
| if "id" not in get: | |
| return public.returnMsg(False, "容器ID异常,请刷新页面后重试!") | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| old_container_config = self.save_container_config(container) | |
| new_image = get.new_image if "new_image" in get and get.new_image else "latest" | |
| if new_image is None: | |
| return public.returnMsg(False, "新镜像名称不能为空!") | |
| if "upgrade" in get and get.upgrade == "1": | |
| get.new_image = "{}:{}".format(old_container_config["image"].split(':')[0], new_image) | |
| try: | |
| if "force_pull" in get and get.force_pull == "1": | |
| public.ExecShell("docker pull {}".format(get.new_image)) | |
| except docker.errors.ImageNotFound as e: | |
| return public.returnMsg(False, "镜像不存在!") | |
| except docker.errors.APIError as e: | |
| return public.returnMsg(False, "镜像不存在!") | |
| get.old_container_config = old_container_config | |
| new_container_config = self.structure_new_container_conf(get) | |
| if type(new_container_config) != dict: | |
| return new_container_config | |
| get.new_container_config = new_container_config | |
| # 2024/11/12 17:05 先讲旧的容器停止掉,以便后面创建新的容器 | |
| container.stop() | |
| # 2024/11/12 17:04 创建一个当前时间戳的新容器,测试是否可以正常运行 | |
| new_name_time = int(time.time()) | |
| new_name = "{}_{}".format(new_container_config["name"], new_name_time) | |
| result = self.create_some_container(get, new_name=new_name) | |
| if result: | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.start() | |
| if "No such image" in result["msg"]: | |
| return public.returnMsg(False, "镜像【{}】不存在,请尝试勾选【强制拉取镜像】再试!".format(get.new_container_config["image"])) | |
| return public.returnMsg(False, msg=result["msg"] + ",新容器没有创建成功,已恢复旧容器!") | |
| from btdockerModel.dockerSock import container | |
| sk_container = container.dockerContainer() | |
| # 2024/11/12 17:05 循环检测10次,每次1秒,看是否能获取到新容器的状态,并且必须是running运行中,否则判定为新容器创建失败,然后回滚旧容器 | |
| is_break = False | |
| for i in range(10): | |
| sk_container_info = sk_container.get_container_inspect(get.new_container.attrs["Id"]) | |
| state_n = sk_container_info["State"] | |
| if "running" in state_n["Status"] and state_n["Running"]: | |
| is_break = True | |
| get.new_container.stop() | |
| get.new_container.remove() | |
| time.sleep(1) | |
| # 2024/11/12 17:06 验证了新的配置文件没有问题,可以正常启动,删掉旧容器,开始创建新的容器 | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.remove() | |
| result = self.create_some_container(get) | |
| if result: return result | |
| # 2024/11/12 17:07 再次检查新的容器状态,如果已经是运行中,则返回编辑成功,否则失败 | |
| for i in range(10): | |
| new_sk_container_info = sk_container.get_container_inspect(get.new_container.attrs["Id"]) | |
| if "message" in new_sk_container_info: | |
| time.sleep(1) | |
| continue | |
| state_n = new_sk_container_info["State"] | |
| if "running" in state_n["Status"] and state_n["Running"]: | |
| return public.returnMsg(True, "更新成功!") | |
| time.sleep(1) | |
| if is_break: break | |
| time.sleep(1) | |
| try: | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.start() | |
| except: | |
| import traceback | |
| return public.returnMsg(False, "更新失败!{}".format(traceback.format_exc())) | |
| return public.returnMsg(True, "更新失败!已经还原容器状态") | |
| except docker.errors.NotFound as e: | |
| if "No such container" in str(e): | |
| return public.returnMsg(False, "容器不存在!") | |
| return public.returnMsg(False, "更新失败!{}".format(str(e))) | |
| except docker.errors.APIError as e: | |
| if "No such container" in str(e): | |
| return public.returnMsg(False, "容器不存在!") | |
| return public.returnMsg(False, "更新失败!{}".format(str(e))) | |
| except Exception as a: | |
| if "Read timed out" in str(a): | |
| return public.returnMsg(False, "容器编辑失败,连接docker超时,请尝试重启docker后再试!") | |
| return public.returnMsg(False, "更新失败!{}".format(str(a))) | |
| # 2024/5/28 下午3:30 编辑容器连接网络 | |
| def connent_network(self, get): | |
| ''' | |
| @name 编辑容器连接网络 | |
| @author wzz <2024/5/28 下午3:30> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if get.net_name == "bridge": | |
| self.docker_client(self._url).networks.get(get.net_name).connect(get.new_container_id) | |
| return public.returnMsg(True, "") | |
| try: | |
| self.docker_client(self._url).networks.get(get.net_name).connect( | |
| get.new_container_id, | |
| ipv4_address=get.tmp_ip_address, | |
| ipv6_address=get.tmp_ip_addressv6, | |
| ) | |
| except docker.errors.APIError as e: | |
| if ("user specified IP address is supported only when " | |
| "connecting to networks with user configured subnets") in str(e): | |
| return public.returnMsg( | |
| False, "容器编辑成功,当前指定的【{}】网络创建时未指定子网,无法自定义IP,已为您自动分配IP!" | |
| .format(str(get.net_name))) | |
| except Exception as e: | |
| return public.returnMsg( | |
| False, "容器编辑成功,网络【{}】设置失败,已为您自动分配IP,错误详情:{}!" | |
| .format(get.net_name, str(e)) | |
| ) | |
| return public.returnMsg(True, "") | |
| def save_container_config(self, container): | |
| """ | |
| 保存容器的配置信息 | |
| """ | |
| network_config = {} | |
| for net_name, net_settings in container.attrs["NetworkSettings"]["Networks"].items(): | |
| network_config[net_name] = { | |
| "IPAMConfig": net_settings["IPAMConfig"], | |
| "Links": net_settings["Links"], | |
| "MacAddress": net_settings["MacAddress"], | |
| "Aliases": net_settings["Aliases"], | |
| "NetworkID": net_settings["NetworkID"], | |
| "EndpointID": net_settings["EndpointID"], | |
| "Gateway": net_settings["Gateway"], | |
| "IPAddress": net_settings["IPAddress"], | |
| "IPPrefixLen": net_settings["IPPrefixLen"], | |
| "IPv6Gateway": net_settings["IPv6Gateway"], | |
| "GlobalIPv6Address": net_settings["GlobalIPv6Address"], | |
| "GlobalIPv6PrefixLen": net_settings["GlobalIPv6PrefixLen"], | |
| "DriverOpts": net_settings["DriverOpts"], | |
| } | |
| if "DNSNames" in net_settings: network_config[net_name]["DNSNames"] = net_settings["DNSNames"] | |
| return { | |
| "image": container.attrs['Config']['Image'], | |
| "name": container.attrs['Name'], | |
| "detach": True, | |
| "cpu_quota": container.attrs['HostConfig']['CpuQuota'], | |
| "mem_limit": container.attrs['HostConfig']['Memory'], | |
| "tty": container.attrs['Config']['Tty'], | |
| "stdin_open": container.attrs['Config']['OpenStdin'], | |
| "publish_all_ports": container.attrs['HostConfig']['PublishAllPorts'], | |
| "ports": container.attrs['NetworkSettings']['Ports'], | |
| "command": container.attrs['Config']['Cmd'], | |
| "entrypoint": container.attrs['Config']['Entrypoint'], | |
| "environment": container.attrs['Config']['Env'], | |
| "labels": container.attrs['Config']['Labels'], | |
| "auto_remove": container.attrs['HostConfig']['AutoRemove'], | |
| "privileged": container.attrs['HostConfig']['Privileged'], | |
| "volumes": container.attrs['HostConfig']['Binds'], | |
| "volume_driver": container.attrs['HostConfig']['VolumeDriver'], | |
| "mem_reservation": container.attrs['HostConfig']['MemoryReservation'], | |
| "restart_policy": container.attrs['HostConfig']['RestartPolicy'], | |
| "networking_config": network_config, | |
| } | |
| def structure_new_container_conf(self, get): | |
| """ | |
| 构造新的容器配置 | |
| @param get: | |
| @return: | |
| """ | |
| new_image = get.new_image if hasattr(get, "new_image") and get.new_image else get.old_container_config["image"] | |
| new_name = get.new_name if hasattr(get, "new_name") and get.new_name else get.old_container_config[ | |
| "name"].replace("/", "") | |
| new_cpu_quota = get.new_cpu_quota if hasattr(get, "new_cpu_quota") and get.new_cpu_quota != 0 else get.old_container_config["cpu_quota"] | |
| if int(new_cpu_quota) != 0: | |
| new_cpu_quota = float(new_cpu_quota) * 100000 | |
| if int(new_cpu_quota) / 100000 > dp.get_cpu_count(): | |
| return public.returnMsg(False, "CPU 配额已超过可用内核数!") | |
| new_mem_limit = get.new_mem_limit if hasattr(get, "new_mem_limit") and get.new_mem_limit else get.old_container_config["mem_limit"] | |
| new_tty = get.new_tty if hasattr(get, "new_tty") and get.new_tty else get.old_container_config["tty"] | |
| new_stdin_open = get.new_stdin_open if hasattr(get, "new_stdin_open") and get.new_stdin_open else get.old_container_config["stdin_open"] | |
| new_publish_all_ports = get.new_publish_all_ports if hasattr(get, "new_publish_all_ports") and get.new_publish_all_ports != '0' else get.old_container_config["publish_all_ports"] | |
| get_new_ports = get.new_ports if hasattr(get, "new_ports") and get.new_ports else False | |
| new_ports = {} | |
| if get_new_ports: | |
| if ":" in get_new_ports.keys(): | |
| return public.returnMsg(False, "端口格式错误,暂不支持此种方式!") | |
| if "-" in get_new_ports.keys(): | |
| return public.returnMsg(False, "端口格式错误,暂不支持此种方式!") | |
| for i in get_new_ports.keys(): | |
| if get_new_ports[i] == "": continue | |
| if isinstance(get_new_ports[i], list): | |
| get_new_ports[i] = tuple(get_new_ports[i]) | |
| if "tcp/udp" in i: | |
| cPort = i.split('/')[0] | |
| new_ports[str(cPort) + "/tcp"] = get_new_ports[i] | |
| new_ports[str(cPort) + "/udp"] = get_new_ports[i] | |
| else: | |
| new_ports[i] = get_new_ports[i] | |
| del get_new_ports | |
| new_ports = new_ports if new_ports else get.old_container_config["ports"] | |
| new_command = get.new_command if hasattr(get, "new_command") else get.old_container_config["command"] | |
| new_entrypoint = get.new_entrypoint if hasattr(get, "new_entrypoint") else get.old_container_config[ | |
| "entrypoint"] | |
| new_environment = get.new_environment if hasattr(get, "new_environment") and get.new_environment != '' else get.old_container_config["environment"] | |
| new_labels = get.new_labels if hasattr(get, "new_labels") and get.new_labels != '' else get.old_container_config["labels"] | |
| new_auto_remove = True if hasattr(get, "new_auto_remove") and get.new_auto_remove != '0' else get.old_container_config["auto_remove"] | |
| new_privileged = True if hasattr(get, "new_privileged") and get.new_privileged != '0' else get.old_container_config["privileged"] | |
| new_volumes = get.new_volumes if hasattr(get, "new_volumes") and get.new_volumes else get.old_container_config[ | |
| "volumes"] | |
| new_volume_driver = get.new_volume_driver if hasattr(get, "new_volume_driver") and get.new_volume_driver else get.old_container_config["volume_driver"] | |
| new_mem_reservation = get.new_mem_reservation if hasattr(get, "new_mem_reservation") and get.new_mem_reservation else get.old_container_config["mem_reservation"] | |
| new_restart_policy = get.new_restart_policy if hasattr(get, "new_restart_policy") and get.new_restart_policy else get.old_container_config["restart_policy"] | |
| device_request = [] | |
| gpus = str(get.get("gpus", "0")) | |
| if gpus != "0": | |
| count = 0 | |
| if gpus == "all": | |
| count = -1 | |
| else: | |
| count = int(gpus) | |
| from docker.types import DeviceRequest | |
| device_request.append(DeviceRequest(driver="nvidia", count=count, capabilities=[["gpu"]])) | |
| return { | |
| "image": new_image, | |
| "name": new_name, | |
| "detach": True, | |
| "cpu_quota": int(new_cpu_quota), | |
| "mem_limit": new_mem_limit, | |
| "tty": new_tty, | |
| "stdin_open": new_stdin_open, | |
| "publish_all_ports": new_publish_all_ports, | |
| "ports": new_ports, | |
| "command": new_command, | |
| "entrypoint": new_entrypoint, | |
| "environment": dp.set_kv(new_environment) if type(new_environment) != list else new_environment, | |
| "labels": dp.set_kv(new_labels) if type(new_labels) != dict else new_labels, | |
| "auto_remove": new_auto_remove, | |
| "privileged": new_privileged, | |
| "volumes": new_volumes, | |
| "volume_driver": new_volume_driver, | |
| "mem_reservation": new_mem_reservation, | |
| "restart_policy": new_restart_policy, | |
| "device_requests":device_request | |
| } | |
| def commit(self, get): | |
| """ | |
| 保存为镜像 | |
| :param repository 推送到的仓库 | |
| :param tag 镜像标签 jose:v1 | |
| :param message 提交的信息 | |
| :param author 镜像作者 | |
| :param changes | |
| :param conf dict | |
| :param path 导出路径 | |
| :param name 导出文件名 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| if not hasattr(get, 'conf') or not get.conf: | |
| get.conf = None | |
| if get.repository == "docker.io": | |
| get.repository = "" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.commit( | |
| repository=get.repository if "repository" in get else None, | |
| tag=get.tag if "tag" in get else None, | |
| message=get.message if "message" in get else None, | |
| author=get.author if "author" in get else None, | |
| # changes=get.changes if get.changes else None, | |
| conf=get.conf | |
| ) | |
| dp.write_log("提交容器 [{}] 作为图像 [{}] 成功!".format(container.attrs['Name'], get.tag)) | |
| if hasattr(get, "path") and get.path: | |
| get.id = "{}:{}".format(get.repository, get.tag) | |
| from btdockerModel import imageModel as di | |
| result = di.main().save(get) | |
| if result['status']: | |
| return public.returnMsg(True, "镜像已生成,并{}".format(result['msg'])) | |
| return result | |
| return public.returnMsg(True, "提交成功!") | |
| except Exception as e: | |
| return public.returnMsg(False, "提交失败!{}".format(str(e))) | |
| def docker_shell(self, get): | |
| """ | |
| 容器执行命令 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| if not hasattr(get, "id"): | |
| return public.returnMsg(False, "容器ID异常,请刷新页面后重试!") | |
| shell_list = ('bash', 'sh') | |
| if not hasattr(get, "shell"): | |
| return public.returnMsg(False, "请选择shell类型!") | |
| if get.shell not in shell_list: | |
| return public.returnMsg(False, "不支持此种shell,请选择bash或sh!") | |
| user_root = "-u root" if hasattr(get, "sudo_i") else "" | |
| cmd = 'docker container exec -it {} {} {}'.format(user_root, get.id, get.shell) | |
| return public.returnMsg(True, cmd) | |
| except docker.errors.APIError as ex: | |
| return public.returnMsg(False, '获取容器失败') | |
| def export(self, get): | |
| """ | |
| 导出容器为tar 没有导入方法,目前弃用 | |
| :param get: | |
| :return: | |
| """ | |
| from os import path as ospath | |
| from os import makedirs as makedirs | |
| try: | |
| if "tar" in get.name: | |
| file_name = '{}/{}'.format(get.path, get.name) | |
| else: | |
| file_name = '{}/{}.tar'.format(get.path, get.name) | |
| if not ospath.exists(get.path): | |
| makedirs(get.path) | |
| public.writeFile(file_name, '') | |
| f = open(file_name, 'wb') | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| data = container.export() | |
| for i in data: | |
| f.write(i) | |
| f.close() | |
| return public.returnMsg(True, "成功导出到:{}".format(file_name)) | |
| except: | |
| return public.returnMsg(False, '操作失败:' + str(public.get_error_info())) | |
| def del_container(self, get): | |
| """ | |
| 删除指定容器 | |
| @param get: | |
| @return: | |
| """ | |
| import sys | |
| sys.path.insert(0, '/www/server/panel/class') | |
| from btdockerModel.proxyModel import main | |
| from panelSite import panelSite | |
| try: | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) | |
| if not os.path.exists(config_path): | |
| public.writeFile(config_path, json.dumps({})) | |
| if public.readFile(config_path) == '': | |
| public.writeFile(config_path, json.dumps({})) | |
| config_data = json.loads(public.readFile(config_path)) | |
| if container.name in config_data.keys(): | |
| config_data.pop(container.name) | |
| public.writeFile(config_path, json.dumps(config_data)) | |
| container.remove(force=True) | |
| dp.sql("cpu_stats").where("container_id=?", (get.id,)).delete() | |
| dp.sql("io_stats").where("container_id=?", (get.id,)).delete() | |
| dp.sql("mem_stats").where("container_id=?", (get.id,)).delete() | |
| dp.sql("net_stats").where("container_id=?", (get.id,)).delete() | |
| dp.sql("container").where("container_nam=?", (container.attrs['Name'])).delete() | |
| dp.write_log("删除容器 [{}] 成功!".format(container.attrs['Name'])) | |
| get.container_id = get.id | |
| info = main().get_proxy_info(get) | |
| if info and 'name' in info and 'id' in info: | |
| args = public.to_dict_obj({ | |
| 'id': info['id'], | |
| 'webname': info['name'] | |
| }) | |
| panelSite().DeleteSite(args) | |
| # domain_id = dp.sql('dk_domain').where('id=?', (info['id'],)).find() | |
| # 删除站点 | |
| dp.sql('sites').where('name=?', (info['name'],)).delete() | |
| dp.sql('domain').where("name=?", (info['name'],)).delete() | |
| # 删除数据库记录 | |
| dp.sql('dk_domain').where('id=?', (info['id'],)).delete() | |
| dp.sql('dk_sites').where('container_id=?', (get.id,)).delete() | |
| if public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count(): | |
| id = public.M('docker_log_split').where('pid=?', (get.id,)).getField('id') | |
| public.M('docker_log_split').where('id=?', (id,)).delete() | |
| all_data = public.M('docker_log_split').field('pid').select() | |
| if not all_data: | |
| public.M("docker_log_split").execute('drop table if exists docker_log_split') | |
| containers_list = self.get_list(get) | |
| if not containers_list["container_list"]: | |
| public.M("docker_log_split").execute('drop table if exists docker_log_split') | |
| for i in all_data: | |
| for cc in containers_list["container_list"]: | |
| if i['pid'] in cc['id']: | |
| break | |
| else: | |
| public.M('docker_log_split').where('pid=?', (i['pid'],)).delete() | |
| if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count(): | |
| p = crontab.crontab() | |
| llist = p.GetCrontab(None) | |
| if type(llist) == list: | |
| for i in llist: | |
| if i['name'] == '[勿删]docker日志切割': | |
| get.id = i['id'] | |
| p.DelCrontab(get) | |
| break | |
| return public.returnMsg(True, "成功删除!") | |
| except Exception as e: | |
| if "operation not permitted" in str(e): | |
| return public.returnMsg(False, "请先关闭企业防篡改后再试!") | |
| return public.returnMsg(False, "删除失败!" + str(e)) | |
| # 设置容器状态 | |
| def set_container_status(self, get): | |
| """ | |
| 设置容器状态 | |
| @param get: | |
| @return: | |
| """ | |
| try: | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| result = {"status": True, "msg": "设置成功!"} | |
| if get.status == "start": | |
| result = self.start(get) | |
| elif get.status == "stop": | |
| result = self.stop(get) | |
| elif get.status == "pause": | |
| result = self.pause(get) | |
| elif get.status == "unpause": | |
| result = self.unpause(get) | |
| elif get.status == "reload": | |
| result = self.reload(get) | |
| elif get.status == "kill": | |
| container.kill() | |
| else: | |
| container.restart() | |
| try: | |
| return { | |
| "name": container.attrs['Name'].replace('/', ''), | |
| "status": result['status'], | |
| "msg": result['msg'], | |
| } | |
| except: | |
| return { | |
| "name": container.attrs['Name'].replace('/', ''), | |
| "status": False, | |
| "msg": str(result), | |
| } | |
| except Exception as e: | |
| try: | |
| if "No such container" in str(e): | |
| return public.returnMsg(False, "容器已被删除!") | |
| if "port is already allocated" in str(e) or "address already in use" in str(e): | |
| if "[::]" in str(e): | |
| str_port = str(e).split("[::]:")[1].split(":")[0] | |
| return public.returnMsg(False, "ipv6服务器端口[{}] 已被占用!".format(str_port)) | |
| else: | |
| str_port = str(e).split("0.0.0.0")[1].split(":")[1].split(" ")[0] | |
| return public.returnMsg(False, "ipv4服务器端口[{}] 已被占用!".format(str_port)) | |
| return public.returnMsg(False, "设置失败!" + str(e)) | |
| except Exception as e: | |
| return public.returnMsg(False, "设置失败!" + str(e)) | |
| # 停止容器 | |
| def stop(self, get): | |
| """ | |
| 停止指定容器 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| get.status = "stop" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.stop() | |
| time.sleep(1) | |
| data = self.docker_client(self._url).containers.get(get.id) | |
| if data.attrs['State']['Status'] != "exited": | |
| return public.returnMsg(False, "停止失败!") | |
| dp.write_log("停止容器 [{}] 成功!".format(data.attrs['Name'].replace('/', ''))) | |
| return public.returnMsg(True, "停止成功!") | |
| except docker.errors.APIError as e: | |
| if "is already paused" in str(e): | |
| return public.returnMsg(False, "容器已暂停!") | |
| if "No such container" in str(e): | |
| return public.returnMsg(True, "容器已停止并删除,因为容器有停止后自动删除的选项!") | |
| return public.returnMsg(False, "停止失败!{}".format(e)) | |
| def start(self, get): | |
| """ | |
| 启动指定容器 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| get.status = "start" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.start() | |
| time.sleep(1) | |
| data = self.docker_client(self._url).containers.get(get.id) | |
| if data.attrs['State']['Status'] != "running": | |
| return public.returnMsg(False, "启动失败!") | |
| dp.write_log("启动容器 [{}] 成功!".format(data.attrs['Name'].replace('/', ''))) | |
| return public.returnMsg(True, "启动成功!") | |
| except docker.errors.APIError as e: | |
| if "cannot start a paused container, try unpause instead" in str(e): | |
| return self.unpause(get) | |
| except Exception as a: | |
| raise Exception(str(a)) | |
| def pause(self, get): | |
| """ | |
| 暂停此容器内的所有进程 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| get.status = "pause" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.pause() | |
| time.sleep(1) | |
| data = self.docker_client(self._url).containers.get(get.id) | |
| if data.attrs['State']['Status'] != "paused": | |
| return public.returnMsg(False, "容器暂停失败!") | |
| dp.write_log("暂停容器 [{}] 成功!".format(data.attrs['Name'].replace('/', ''))) | |
| return public.returnMsg(True, "容器暂停成功!") | |
| except docker.errors.APIError as e: | |
| if "is already paused" in str(e): | |
| return public.returnMsg(False, "容器已被挂起!") | |
| if "is not running" in str(e): | |
| return public.returnMsg(False, "容器未启动,无法暂停!") | |
| if "is not paused" in str(e): | |
| return public.returnMsg(False, "容器没有被暂停或已被删除,请检查容器是否有停止后立即删除的选项!") | |
| return str(e) | |
| except Exception as a: | |
| raise Exception(str(a)) | |
| def unpause(self, get): | |
| """ | |
| 取消暂停该容器内的所有进程 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| get.status = "unpause" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.unpause() | |
| time.sleep(1) | |
| data = self.docker_client(self._url).containers.get(get.id) | |
| if data.attrs['State']['Status'] != "running": | |
| return public.returnMsg(False, "启动失败!") | |
| dp.write_log("取消暂停容器 [{}] 成功!".format(data.attrs['Name'].replace('/', ''))) | |
| return public.returnMsg(True, "容器取消暂停成功") | |
| except docker.errors.APIError as e: | |
| if "is already paused" in str(e): | |
| return public.returnMsg(False, "容器已暂停!") | |
| if "is not running" in str(e): | |
| return public.returnMsg(False, "容器未启动,无法暂停!") | |
| if "is not paused" in str(e): | |
| return public.returnMsg(False, "容器没有被暂停或已被删除,请检查容器是否有停止后立即删除的选项!") | |
| return str(e) | |
| except Exception as a: | |
| raise Exception(str(a)) | |
| def reload(self, get): | |
| """ | |
| 再次从服务器加载此对象并使用新数据更新 attrs | |
| :param get: | |
| :return: | |
| """ | |
| get.status = "reload" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.reload() | |
| time.sleep(1) | |
| data = self.docker_client(self._url).containers.get(get.id) | |
| if data.attrs['State']['Status'] != "running": | |
| return public.returnMsg(False, "启动失败!") | |
| dp.write_log("重新加载容器 [{}] 成功!".format(data.attrs['Name'].replace('/', ''))) | |
| return public.returnMsg(True, "重载容器成功!") | |
| def restart(self, get): | |
| """ | |
| 重新启动这个容器。类似于 docker restart 命令 | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| get.status = "restart" | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.restart() | |
| time.sleep(1) | |
| data = self.docker_client(self._url).containers.get(get.id) | |
| if data.attrs['State']['Status'] != "running": | |
| return public.returnMsg(False, "启动失败!") | |
| dp.write_log("重启容器 [{}] 成功!".format(data.attrs['Name'].replace('/', ''))) | |
| return public.returnMsg(True, "容器重启成功!") | |
| except docker.errors.APIError as e: | |
| if "container is marked for removal and cannot be started" in str(e): | |
| return public.returnMsg(False, "容器已停止并删除,因为容器有停止后自动删除的选项!") | |
| if "is already paused" in str(e): | |
| return public.returnMsg(False, "容器已暂停!") | |
| return str(e) | |
| def get_container_ip(self, container_networks): | |
| """ | |
| 获取容器IP | |
| @param container_networks: | |
| @return: | |
| """ | |
| ipv4_list = [] | |
| ipv6_list = [] | |
| for network in container_networks: | |
| if container_networks[network]['IPAddress'] != "": | |
| ipv4_list.append(container_networks[network]['IPAddress']) | |
| if container_networks[network]['GlobalIPv6Address'] != "": | |
| ipv6_list.append(container_networks[network]['GlobalIPv6Address']) | |
| return {"ipv4": ipv4_list, "ipv6": ipv6_list} | |
| def get_container_path(self, detail): | |
| """ | |
| 获取容器路径 | |
| @param detail: | |
| @return: | |
| """ | |
| try: | |
| import os | |
| if not "GraphDriver" in detail: | |
| return False | |
| if "Data" not in detail["GraphDriver"]: | |
| return False | |
| if "MergedDir" not in detail["GraphDriver"]["Data"]: | |
| return False | |
| path = detail["GraphDriver"]["Data"]["MergedDir"] | |
| if not os.path.exists(path): | |
| return "" | |
| return path | |
| except: | |
| return False | |
| def get_container_info(self, get): | |
| """ | |
| 获取容器信息 | |
| @param get: | |
| @return: | |
| """ | |
| try: | |
| if "id" not in get or get.id == "": | |
| return public.returnMsg(False, "容器ID为空,请刷新浏览器后再试!") | |
| from btdockerModel.dockerSock import container | |
| sk_container = container.dockerContainer() | |
| sk_container_info = sk_container.get_container_inspect(get.id) | |
| if "No such container" in str(sk_container_info): | |
| return public.returnMsg(False, "容器不存在!") | |
| info_path = "/var/lib/docker/containers/{}/container_info.json".format(sk_container_info["Id"]) | |
| public.writeFile(info_path, json.dumps(sk_container_info, indent=3)) | |
| sk_container_info['container_info'] = info_path | |
| # 计算GPU数量 | |
| device_requests = sk_container_info["HostConfig"].get('DeviceRequests', []) | |
| sk_container_info["gpu_count"] = 0 | |
| if device_requests is not None and len(device_requests) != 0: | |
| for item in device_requests: | |
| if item["Driver"] == "nvidia": | |
| sk_container_info["gpu_count"] = item["Count"] | |
| break | |
| sk_container_info['from_compose'] = 1 if sk_container_info.get("Config",{}).get("Labels", {}).get("com.docker.compose.project", "") != "" else 0 | |
| return sk_container_info | |
| except Exception as e: | |
| if "No such container" in str(e): | |
| return public.returnMsg(False, "容器不存在!") | |
| return public.returnMsg(False, "获取容器信息失败!{}".format(str(e))) | |
| def struct_container_ports(self, ports): | |
| """ | |
| 构造容器ports | |
| @param ports: | |
| @return: | |
| """ | |
| data = dict() | |
| for port in ports: | |
| key = str(port["PrivatePort"]) + "/" + port["Type"] | |
| if key not in data.keys(): | |
| ddata = None | |
| if port.get("IP",""): | |
| ddata = [{"HostIp": port["IP"],"HostPort": str(port["PublicPort"])}] | |
| data[str(port["PrivatePort"]) + "/" + port["Type"]] = ddata | |
| else: | |
| data[str(port["PrivatePort"]) + "/" + port["Type"]].append({ | |
| "HostIp": port["IP"], | |
| "HostPort": str(port["PublicPort"]) | |
| }) | |
| return data | |
| def struct_container_list(self, container, container_to_top=None): | |
| ''' | |
| @name 构造容器列表 | |
| @author wzz <2024/3/13 下午 5:32> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| return { | |
| "id": container["Id"], | |
| "name": dp.rename(container['Names'][0].replace("/", "")), | |
| "status": container["State"], | |
| "image": container["Image"], | |
| "created_time": container["Created"], | |
| "ip": self.get_container_ip(container["NetworkSettings"]['Networks'])["ipv4"], | |
| "ipv6": self.get_container_ip(container["NetworkSettings"]['Networks'])["ipv6"], | |
| "ports": self.struct_container_ports(container["Ports"]), | |
| "is_top": 0 if container_to_top is None else 1 if container['Names'][0].replace("/", "") in container_to_top else 0, | |
| } | |
| # 2024/4/11 下午2:46 获取 merged 目录 | |
| def get_container_merged(self, get): | |
| ''' | |
| @name 获取容器 merged 目录 | |
| @author wzz <2024/4/11 下午2:47> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| try: | |
| get.id = get.get("id", "") | |
| if get.id == "": | |
| return public.returnMsg(False, "容器ID为空,请刷新浏览器后再试!") | |
| return {"path": public.ExecShell("docker inspect -f \"{{json .GraphDriver.Data.MergedDir}}\" " + get.id)[ | |
| 0].strip().strip('"')} | |
| except Exception as e: | |
| return {"path": ""} | |
| # 2024/4/11 下午3:44 获取其他容器列表的数据 | |
| def get_other_container_data(self, get): | |
| ''' | |
| @name 获取其他容器列表的数据 | |
| @author wzz <2024/4/11 下午3:45> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| try: | |
| from btdockerModel.dockerSock import container | |
| sk_container = container.dockerContainer() | |
| container_list = sk_container.get_container() | |
| data = [] | |
| dk_container_remark = dp.sql("dk_container_remark").select() | |
| dk_backup = dp.sql("dk_backup").select() | |
| for sk_c in container_list: | |
| try: | |
| remark = [i['remark'] for i in dk_container_remark if i['container_id'] == sk_c["Id"]][0] | |
| except Exception as e: | |
| remark = "" | |
| # 计算备份数量 | |
| backup_count = 0 | |
| for i in dk_backup: | |
| if i['container_id'] == sk_c["Id"]: | |
| backup_count += 1 | |
| data.append({ | |
| "id": sk_c["Id"], | |
| "name": dp.rename(sk_c['Names'][0].replace("/", "")), | |
| "backup_count": backup_count, | |
| "remark": remark, | |
| }) | |
| return data | |
| except Exception as e: | |
| return [] | |
| # 获取容器列表 | |
| def get_list(self, get): | |
| """ | |
| 获取所有容器列表 | |
| :param get | |
| :return: | |
| """ | |
| from btdockerModel.dockerSock import container | |
| sk_container = container.dockerContainer() | |
| sk_container_list = sk_container.get_container() | |
| container_to_top = self._get_container_to_top() | |
| #获取gpu数量 | |
| try: | |
| from mod.project.docker.app.gpu.nvidia import NVIDIA | |
| gpu_count = NVIDIA().device_count | |
| except: | |
| gpu_count = 0 | |
| data = { | |
| "online_cpus": dp.get_cpu_count(), | |
| "mem_total": dp.get_mem_info(), | |
| "container_list": [], | |
| "gpu": gpu_count | |
| } | |
| container_detail = list() | |
| grouped_by_status = dict() | |
| for sk_c in sk_container_list: | |
| struct_container = self.struct_container_list(sk_c, container_to_top) | |
| status = struct_container['status'] | |
| grouped_by_status.setdefault(status, []).append(struct_container) | |
| container_detail.append(struct_container) | |
| if container_to_top: | |
| container_detail = sorted(container_detail, key=lambda x: (x['is_top'], x['created_time']), reverse=True) | |
| for key in grouped_by_status: | |
| grouped_by_status[key] = sorted(grouped_by_status[key], key=lambda x: (x['is_top'], x['created_time']), reverse=True) | |
| else: | |
| container_detail = sorted(container_detail, key=lambda x: x['created_time'], reverse=True) | |
| for key in grouped_by_status: | |
| grouped_by_status[key] = sorted(grouped_by_status[key], key=lambda x: x['created_time'], reverse=True) | |
| data['grouped_by_status'] = grouped_by_status | |
| data['container_list'] = container_detail | |
| return data | |
| # 获取容器的attr | |
| def get_container_attr(self, containers): | |
| c_list = containers.list(all=True) | |
| return [container_info.attrs for container_info in c_list] | |
| # 获取容器日志 | |
| def get_logs(self, get): | |
| """ | |
| 获取指定容器日志 | |
| :param get: | |
| :return: | |
| """ | |
| res = { | |
| "logs": "", | |
| # 'split_status': False, | |
| # 'split_type': 'day', | |
| # 'split_size': 1000, | |
| # 'split_hour': 2, | |
| # 'split_minute': 0, | |
| # 'save': '180' | |
| } | |
| try: | |
| # 获取容器信息 名称 日志路径 大小 | |
| container_info = self.docker_client(self._url).containers.get(get.id) | |
| if not os.path.exists(container_info.attrs['LogPath']): | |
| return "" | |
| since = "" | |
| until = "" | |
| tail = "" | |
| if hasattr(get, 'time_search') and get.time_search != '': | |
| time_search = json.loads(str(get.time_search)) | |
| since = int(time_search[0]) | |
| until = int(time_search[1]) | |
| size = os.stat(container_info.attrs['LogPath']).st_size | |
| if size > 1048576: | |
| tail = int(get.tail) if "tail" in get else 10000 | |
| options = { | |
| "since": since, | |
| "until": until, | |
| "tail": tail | |
| } | |
| from btdockerModel.dockerSock import container | |
| sk_container = container.dockerContainer() | |
| sk_container_logs = sk_container.get_container_logs(get.id,options) | |
| if hasattr(get, 'search') and get.search != '': | |
| if get.search: | |
| sk_container_logs = sk_container_logs.split("\n") | |
| sk_container_logs = [i for i in sk_container_logs if get.search in i] | |
| sk_container_logs = "\n".join(sk_container_logs) | |
| res["logs"] = sk_container_logs | |
| res['id'] = get.id | |
| res['name'] = dp.rename(container_info.attrs['Name'][1:]) | |
| res['logs_path'] = container_info.attrs['LogPath'] | |
| res['size'] = os.stat(container_info.attrs['LogPath']).st_size | |
| # if public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count(): | |
| # res['split_status'] = True if public.M('docker_log_split').where('pid=?', (get.id,)).count() else False | |
| # data = public.M('docker_log_split').where('pid=?', (get.id,)).select() | |
| # if data: | |
| # res['split_type'] = data[0]['split_type'] | |
| # res['split_size'] = data[0]['split_size'] | |
| # res['split_hour'] = data[0]['split_hour'] | |
| # res['split_minute'] = data[0]['split_minute'] | |
| # res['save'] = data[0]['save'] | |
| # else: | |
| # res['split_type'] = 'day' | |
| # res['split_size'] = 1000 | |
| # res['split_hour'] = 2 | |
| # res['split_minute'] = 0 | |
| # res['save'] = '180' | |
| return res | |
| except Exception: | |
| return res | |
| def get_logs_all(self, get): | |
| """ | |
| 获取所有容器的日志 | |
| @param get: | |
| @return: | |
| """ | |
| try: | |
| client = self.docker_client(self._url) | |
| if not client: | |
| return public.returnMsg(True, 'docker连接失败') | |
| containers = client.containers | |
| clist = [i.attrs for i in containers.list(all=True)] | |
| clist = [{'id': i['Id'], 'name': dp.rename(i['Name'][1:]), 'log_path': i['LogPath']} for i in clist] | |
| for i in clist: | |
| if os.path.exists(i['log_path']): | |
| i['size'] = os.stat(i['log_path']).st_size | |
| else: | |
| i['size'] = 0 | |
| return clist | |
| except Exception as e: | |
| return public.returnMsg(True, e) | |
| def docker_split(self, get): | |
| """ | |
| 设置容器日志切割 | |
| @param get: | |
| @return: | |
| """ | |
| try: | |
| client = self.docker_client(self._url) | |
| if not client: | |
| return public.returnMsg(True, 'docker连接失败') | |
| containers = client.containers | |
| clist = [i.attrs for i in containers.list(all=True)] | |
| name = [dp.rename(i['Name'][1:]) for i in clist if i['Id'] == get.pid] | |
| if name: | |
| name = name[0] | |
| else: | |
| name = '' | |
| if not hasattr(get, 'type'): | |
| return public.returnMsg(False, '参数错误,请传入:type') | |
| if not public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count(): | |
| public.M('docker_log_split').execute('''CREATE TABLE IF NOT EXISTS docker_log_split ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| name text default '', | |
| pid text default '', | |
| log_path text default '', | |
| split_type text default '', | |
| split_size INTEGER default 0, | |
| split_hour INTEGER default 2, | |
| split_minute INTEGER default 0, | |
| save INTEGER default 180)''', ()) | |
| if get.type == 'add': | |
| if "log_path" not in get or not get.log_path: | |
| return public.returnMsg(False, '容器日志目录不存在,无法设置日志切割!') | |
| if not (hasattr(get, 'pid') and hasattr(get, 'log_path') and | |
| hasattr(get, 'split_type') and hasattr(get, 'split_size') and | |
| hasattr(get, 'split_minute') and | |
| hasattr(get, 'split_hour') and hasattr(get, 'save')): | |
| return public.returnMsg(False, '参数错误') | |
| data = { | |
| 'name': name, | |
| 'pid': get.pid, | |
| 'log_path': get.log_path, | |
| 'split_type': get.split_type, | |
| 'split_size': get.split_size, | |
| 'split_hour': get.split_hour, | |
| 'split_minute': get.split_minute, | |
| 'save': get.save | |
| } | |
| if public.M('docker_log_split').where('pid=?', (get.pid,)).count(): | |
| id = public.M('docker_log_split').where('pid=?', (get.pid,)).select() | |
| public.M('docker_log_split').delete(id[0]['id']) | |
| public.M('docker_log_split').insert(data) | |
| return public.returnMsg(True, "开启成功!") | |
| elif get.type == 'del': | |
| id = public.M('docker_log_split').where('pid=?', (get.pid,)).getField('id') | |
| public.M('docker_log_split').where('id=?', (id,)).delete() | |
| return public.returnMsg(True, "关闭成功!") | |
| except: | |
| return public.returnMsg(False, "") | |
| def clear_log(self, get): | |
| """ | |
| 清空日志 | |
| @param get: | |
| @return: | |
| """ | |
| if not hasattr(get, 'log_path'): | |
| return public.returnMsg(False, '参数错误') | |
| if not os.path.exists(get.log_path): | |
| return public.returnMsg(False, '日志文件不存在') | |
| public.writeFile(get.log_path, '') | |
| return public.returnMsg(True, "日志清理成功!") | |
| def prune(self, get): | |
| """ | |
| :param get: | |
| :return: | |
| """ | |
| try: | |
| type = get.get("type/d", 0) | |
| if type == 0: | |
| res = self.docker_client(self._url).containers.prune() | |
| if not res['ContainersDeleted']: | |
| return public.returnMsg(False, "没有无用的容器!") | |
| dp.write_log("删除无用的容器成功!") | |
| return public.returnMsg(True, "删除成功!") | |
| else: | |
| import docker | |
| client = docker.from_env() | |
| containers = client.containers.list(all=True) | |
| for container in containers: | |
| container.remove(force=True) | |
| dp.write_log("删除所有容器成功!") | |
| return public.returnMsg(True, "删除成功!") | |
| except Exception as e: | |
| if "operation not permitted" in str(e): | |
| return public.returnMsg(False, "请先关闭企业防篡改后再试!") | |
| return public.returnMsg(False, "删除失败! {}".format(e)) | |
| def update_restart_policy(self, get): | |
| """ | |
| 更新容器重启策略 | |
| @param get: | |
| @return: | |
| """ | |
| try: | |
| if "restart_policy" not in get: | |
| return public.returnMsg(False, "参数错误,请传入重启策略restart_policy!") | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| container.update(restart_policy=get.restart_policy) | |
| dp.write_log("更新容器 [{}] 重启策略成功!".format(container.attrs['Name'])) | |
| return public.returnMsg(True, "更新成功!") | |
| except docker.errors.APIError as e: | |
| return public.returnMsg(False, "更新失败! {}".format(e)) | |
| # 2024/2/23 上午 9:57 重命名指定容器 | |
| def rename_container(self, get): | |
| """ | |
| 重命名指定容器 | |
| @param get: | |
| @return: | |
| """ | |
| try: | |
| # 2023/12/6 上午 10:54 容器未启动时,不允许重命名 | |
| container = self.docker_client(self._url).containers.get(get.id) | |
| if container.attrs['State']['Status'] != "running": | |
| return public.returnMsg(False, "容器未启动,无法重命名!") | |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) | |
| if not os.path.exists(config_path): | |
| public.writeFile(config_path, json.dumps({})) | |
| if public.readFile(config_path) == '': | |
| public.writeFile(config_path, json.dumps({})) | |
| name_map = json.loads(public.readFile(config_path)) | |
| name_str = 'q18q' + public.GetRandomString(10).lower() | |
| name_map[name_str] = get.name | |
| get.name = name_str | |
| public.writeFile(config_path, json.dumps(name_map)) | |
| container.rename(get.name) | |
| dp.write_log("重命名容器 [{}] 成功!".format(get.name)) | |
| return public.returnMsg(True, "重命名成功!") | |
| except docker.errors.APIError as e: | |
| return public.returnMsg(False, "重命名失败! {}".format(e)) | |
| # 2024/2/23 上午 9:58 设置容器列表置顶 | |
| def set_container_to_top(self, get): | |
| """ | |
| 设置容器列表置顶 | |
| @param get: | |
| @return: | |
| """ | |
| set_type = get.type if "type" in get else "" | |
| container_name = get.container_name if "container_name" in get else None | |
| if set_type not in ['add', 'del']: return public.returnMsg(False, '类型仅支持add/del') | |
| if container_name is None: return public.returnMsg(False, '请选择容器') | |
| _conf_path = "{}/class/btdockerModel/config/container_top.json".format(public.get_panel_path()) | |
| if os.path.exists(_conf_path): | |
| container_top_conf = json.loads(public.readFile(_conf_path)) | |
| else: | |
| container_top_conf = [] | |
| if set_type == "add": | |
| container_top_conf = [i for i in container_top_conf if i != container_name] | |
| container_top_conf.insert(0, container_name) | |
| public.writeFile(_conf_path, json.dumps(container_top_conf)) | |
| return public.returnMsg(True, '设置置顶成功!') | |
| elif set_type == "del": | |
| container_top_conf.remove(container_name) | |
| public.writeFile(_conf_path, json.dumps(container_top_conf)) | |
| return public.returnMsg(True, '取消置顶成功!') | |
| # 2024/2/23 上午 9:58 获取容器列表置顶 | |
| def _get_container_to_top(self): | |
| """ | |
| 获取容器列表置顶 | |
| @return: | |
| """ | |
| _conf_path = "{}/class/btdockerModel/config/container_top.json".format(public.get_panel_path()) | |
| if os.path.exists(_conf_path): | |
| container_top_conf = json.loads(public.readFile(_conf_path)) | |
| else: | |
| container_top_conf = [] | |
| return container_top_conf | |
| # 2024/3/13 下午 5:46 检查并创建备注的表 | |
| def check_remark_table(self): | |
| ''' | |
| @name 检查并创建备注的表 | |
| @author wzz <2024/2/26 下午 5:59> | |
| @param "data":{"参数名":""} <数据类型> 参数描述 | |
| @return dict{"status":True/False,"msg":"提示信息"} | |
| ''' | |
| if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_container_remark')).count(): | |
| dp.sql('dk_container_remark').execute( | |
| "CREATE TABLE `dk_container_remark` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `container_id` TEXT, `container_name` TEXT, `remark` TEXT, `addtime` TEXT)", | |
| () | |
| ) | |
| # 2024/2/26 下午 6:11 修改备注 | |
| def set_container_remark(self, get): | |
| """ | |
| 设置容器备注 | |
| @param get: | |
| @return: | |
| """ | |
| if not hasattr(get, 'remark'): | |
| return public.returnMsg(False, '参数错误, 请传入remark!') | |
| if not hasattr(get, 'id'): | |
| return public.returnMsg(False, '参数错误, 请传入id!') | |
| container_id = get.id | |
| container_remark = public.xssencode2(get.remark) | |
| if not dp.sql("dk_container_remark").where("container_id=?", (container_id,)).count(): | |
| dp.sql("dk_container_remark").insert({ | |
| "container_id": container_id, | |
| "remark": container_remark, | |
| }) | |
| else: | |
| dp.sql("dk_container_remark").where("container_id=?", (container_id,)).setField("remark", container_remark) | |
| return public.returnMsg(True, "设置成功!") | |
| # 2024/2/27 上午 11:03 通过cgroup获取所有容器的cpu和内存使用情况 | |
| def get_all_stats(self, get): | |
| """ | |
| # 通过cgroup获取所有容器的cpu和内存使用情况 | |
| @param get: | |
| @return: | |
| """ | |
| if not hasattr(get, 'ws_callback'): | |
| return public.returnMsg(False, '参数错误, 请传入ws_callback!') | |
| if not hasattr(get, '_ws'): | |
| return public.returnMsg(False, '参数错误, 请传入_ws!') | |
| try: | |
| result = { | |
| "container_list": {}, | |
| } | |
| # 获取所有容器列表 | |
| container_list = self.get_list(get)["container_list"] | |
| for container in container_list: | |
| result["container_list"][container["name"]] = { | |
| "id": container["id"], | |
| "name": container["name"], | |
| "image": container["image"], | |
| "created_time": container["created_time"], | |
| "status": container["status"], | |
| "memory_usage": "", | |
| "cpu_usage": "", | |
| "pids": "", | |
| } | |
| get._ws.send(public.getJson( | |
| { | |
| "data": result, | |
| "ws_callback": get.ws_callback, | |
| "msg": "开始获取所有容器的cpu和内存使用情况!", | |
| "status": True, | |
| "end": False, | |
| })) | |
| while True: | |
| docker_stats_result = public.ExecShell( | |
| "docker stats --all --no-stream --format " | |
| "'{{.ID}},{{.Name}},{{.CPUPerc}},{{.MemUsage}},{{.MemPerc}},{{.NetIO}},{{.BlockIO}},{{.PIDs}};'" | |
| )[0] | |
| if not docker_stats_result: | |
| get._ws.send(public.getJson( | |
| { | |
| "data": {}, | |
| "ws_callback": get.ws_callback, | |
| "msg": "暂时没有获取到正在运行中的容器资源信息!", | |
| "status": True, | |
| "end": True, | |
| })) | |
| return | |
| for i in docker_stats_result.split(";"): | |
| if not i: continue | |
| tmp = i.strip().split(",") | |
| if len(tmp) == 0: continue | |
| if len(tmp) == 1 and not tmp[0]: continue | |
| container_name = dp.rename(tmp[1]) | |
| if container_name not in result["container_list"]: | |
| continue | |
| result["container_list"][container_name]["cpu_usage"] = tmp[2].strip("%") | |
| result["container_list"][container_name]["mem_percent"] = tmp[4].strip("%") | |
| result["container_list"][container_name]["memory_usage"] = { | |
| "mem_usage": dp.byte_conversion(tmp[3].split("/")[0]), | |
| "mem_limit": dp.byte_conversion(tmp[3].split("/")[1]), | |
| } | |
| result["container_list"][container_name]["pids"] = tmp[7] | |
| get._ws.send(public.getJson( | |
| { | |
| "data": result, | |
| "ws_callback": get.ws_callback, | |
| "msg": "获取所有容器的cpu和内存使用情况成功!", | |
| "status": True, | |
| "end": False, | |
| })) | |
| time.sleep(0.1) | |
| except: | |
| get._ws.send(public.getJson( | |
| { | |
| "data": {}, | |
| "ws_callback": get.ws_callback, | |
| "msg": "获取所有容器的cpu和内存使用情况失败!", | |
| "status": True, | |
| "end": True, | |
| })) | |
| return | |