# coding: utf-8 # ------------------------------------------------------------------- # YakPanel # ------------------------------------------------------------------- # Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: wzz # ------------------------------------------------------------------- import json import os import traceback import time import crontab import docker.errors # ------------------------------ # Docker模型 # ------------------------------ import public from btdockerModelV2 import dk_public as dp from btdockerModelV2.dockerBase import dockerBase from public.validate import Param 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(): # if public.M('sqlite_master').where('type=? AND name=?',('table', 'docker_log_split')).count(): #if dp.sql('sqlite_master').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'] == "[Do not delete] Docker log cuts": add_crond = False break else: add_crond = True if add_crond: get = { "name": "[Do not delete] Docker log cuts", "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').db('container').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): # unix:///var/run/docker.sock return dp.docker_client(url) def get_cmd_log(self, get): """ 获取命令运行容器的日志,websocket @param get: @return: """ get.wsLogTitle = "Please wait to execute the command..." get._log_path = self._rCmd_log return self.get_ws_log(get) def run_cmd(self, get): """ 命令行创建运行容器(docker run),需要做危险命令校验,存在危险命令则不执行 @param get: @return: """ try: get.validate([ Param('cmd').Require().String(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) import re # if not hasattr(get, 'cmd'): # return public.return_message(-1, 0, public.lang("cmd parameter error")) if "docker run" not in get.cmd: return public.return_message(-1, 0, public.lang("Only the docker run command can be executed")) 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.return_message(-1, 0, public.lang( "Dangerous command exists: [ {}],Execution is not allowed!",d)) for d in danger_symbol: if d in get.cmd: return public.return_message(-1, 0, public.lang('There are danger symbols: [{}],Execution is not allowed!',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.return_message(0, 0, public.lang("The command has been executed!")) # 添加容器 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: """ # 校验参数 try: get.validate([ Param('name').Require().String(), Param('image').Require().String(), Param('publish_all_ports').Require().Integer(), Param('command').String(), Param('entrypoint').String(), Param('environment').String(), Param('auto_remove').String(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) 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.return_message(-1, 0, public.lang("The port format is wrong, this method is not supported!")) if "-" in cPorts.keys(): return public.return_message(-1, 0, public.lang("The port format is wrong, this method is not supported!")) 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.return_message(-1, 0, public.lang("The server port [{}] is already occupied, please replace it with a different port!",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.return_message(-1, 0, public.lang("Please pull the image you need")) if get.image == "" or "" in get.image: return public.return_message(-1, 0, public.lang("The image does not exist!")) 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.return_message(-1, 0, public.lang("The memory quota has exceeded the available amount!")) if mem_limit_byte < 6291456: return public.return_message(-1, 0, public.lang("The memory quota cannot be less than 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.return_message(-1, 0, public.lang("Image [{}] does not exist, You can try [Forced Pull]!",get.image)) except docker.errors.APIError as e: return public.return_message(-1, 0, public.lang("Image [{}] does not exist, You can try [Forced Pull]!",get.image)) # 2024/4/16 上午11:40 检查镜像是否存在并且处理镜像如果是非应用容器的情况 try: from btdockerModelV2.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.return_message(-1, 0, public.lang("cpu quota has exceeded available cores!")) 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.return_message(-1, 0, public.lang("Memory reservation cannot be less than 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.return_message(-1, 0, public.lang("The image name format is incorrect.such as:nginx:latest")) if "failed to create task for container" in str(e) or "failed to create shim task" in str(e): return public.return_message(-1, 0, public.lang("Container creation failed, details: {}!", str(e))) if "Minimum memory limit can not be less than memory reservation limit, see usage" in str(e): return public.return_message(-1, 0, public.lang("The memory quota cannot be less than the memory reserve!")) if "already exists in network bridge" in str(e): return public.return_message(-1, 0, public.lang("The container name or network bridge already exists. Please change the container name and try again!")) if "No command specified" in str(e): return public.return_message(-1, 0, public.lang("There is no startup command in this image, please specify the container startup command!")) if "permission denied" in str(e): return public.return_message(-1, 0, public.lang("Permission exception! Details:{}", str(e))) if "Internal Server Error" in str(e): return public.return_message(-1, 0, public.lang("Container creation failed! Please restart the docker service at the appropriate time!")) if "repository does not exist or may require 'docker login'" in str(e): return public.return_message(-1, 0, public.lang("Image [{}] does not exist!",get.image)) if "Minimum memory reservation allowed is 6MB" in str(e): return public.return_message(-1, 0, public.lang("Memory reservation cannot be less than 6MB!")) if "container to be able to reuse that name." in str(e): return public.return_message(-1, 0, public.lang("Container name already exists!")) if "Invalid container name" in str(e): return public.return_message(-1, 0, public.lang("The container name is invalid")) 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.return_message(-1, 0, public.lang("Server port {}in use! Change the other port", port)) return public.return_message(-1, 0, public.lang("Creation failure! {}", public.get_error_info())) except Exception as a: public.print_log(traceback.format_exc()) self.del_container(get) if "Read timed out" in str(a): return public.return_message(-1, 0, public.lang("The container creation failed and the connection to docker timed out!")) return public.return_message(-1, 0, public.lang("Container failed to run! {}", 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("Container creation [{}] successful!".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 net_result["status"] == -1: return net_result res.start() return public.return_message(0, 0, { "status": True, "result": "Successfully created!", "id": res.id, "name": dp.rename(res.name), }) return public.return_message(-1, 0, public.lang("Creation failure!")) # 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.return_message(-1, 0, public.lang("Editing failed, connection to docker timed out, please restart docker and try again!")) return public.return_message(-1, 0, public.lang("Update failed!{}",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 net_result["status"] == -1: 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 net_result["status"] == -1: get.new_container.remove() return net_result get.new_container.start() def upgrade_container(self, get): """ 更新正在运行的容器镜像(重建) @param get: @return: """ public.set_module_logs('docker', 'upgrade_container', 1) try: if "id" not in get: return public.return_message(-1, 0, public.lang("Container ID is abnormal")) 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.return_message(-1, 0, public.lang("The new image name cannot be empty!")) 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.return_message(-1, 0, public.lang("The mirror does not exist!")) except docker.errors.APIError as e: return public.return_message(-1, 0, public.lang("The mirror does not exist!")) # except Exception as e: # public.print_log(traceback.format_exc()) # return public.return_message(-1, 0, e) get.old_container_config = old_container_config new_container_config = self.structure_new_container_conf(get) if new_container_config['status']==-1: return public.return_message(-1, 0, public.lang( new_container_config['message'])) 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.return_message(-1, 0, public.lang("The image [{}] does not exist, please try to check [Force pull image] and try again!", get.new_container_config["image"])) return public.return_message(-1, 0, public.lang("The new container was not created successfully, and the old container has been restored. {}", str(result["msg"]))) from btdockerModelV2.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.return_message(0, 0, public.lang("Update successful!")) time.sleep(1) if is_break: break time.sleep(1) try: container = self.docker_client(self._url).containers.get(get.id) container.start() except: return public.return_message(-1, 0, public.lang("Update failed!{}", str(e))) return public.return_message(0, 0, public.lang("Update failed! The container state has been restored")) except docker.errors.NotFound as e: if "No such container" in str(e): return public.return_message(-1, 0, public.lang("Container does not exist!")) return public.return_message(-1, 0, public.lang("Update failed!{}", str(e))) except docker.errors.APIError as e: if "No such container" in str(e): return public.return_message(-1, 0, public.lang("Container does not exist!")) return public.return_message(-1, 0, public.lang("Update failed!{}", str(e))) except Exception as a: public.print_log(traceback.format_exc()) if "Read timed out" in str(a): return public.return_message(-1, 0, public.lang("Container editing failed and the connection to docker timed out.")) return public.return_message(-1, 0, public.lang("Update failed!{}", 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.return_message(0, 0, "") 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.return_message(-1, 0, public.lang("The container is successfully edited, and the subnet is not specified when the currently specified [{}] network is created, and the IP cannot be customized, and the IP has been automatically assigned to you!",str(get.net_name))) except Exception as e: return public.return_message(-1, 0, public.lang("The container is successfully edited, the network [{}] setting fails, and the IP address has been automatically assigned to you, error details:{}!",get.net_name, str(e))) return public.return_message(0, 0, "") 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 public.return_message(0, 0,{ "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.return_message(-1, 0, public.lang( "cpu quota has exceeded available cores!")) 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.return_message(-1, 0, public.lang( "The port format is incorrect")) if "-" in get_new_ports.keys(): return public.return_message(-1, 0, public.lang( "The port format is incorrect")) 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"] new_network = get.new_network if hasattr(get, "new_network") and get.new_network else get.old_container_config["network"] 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 public.return_message(0, 0, { "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("Submitted container [{}] as image [{}] successfully".format(container.attrs['Name'], get.tag)) if hasattr(get, "path") and get.path: get.id = "{}:{}".format(get.repository, get.tag) from btdockerModelV2 import imageModel as di result = di.main().save(get) if result['status'] == -1: return public.return_message(0, 0, public.lang("The image has been generated, and{}", result['message'])) return public.return_message(0, 0, result) return public.return_message(0, 0, public.lang("submit successfully!")) except Exception as e: return public.return_message(-1, 0, public.lang("Submission Failed!{}", str(e))) def docker_shell(self, get): """ 容器执行命令 :param get: :return: """ try: get.validate([ Param('id').Require().String(), Param('shell').Require().String('in', ['bash', 'sh']), # Param('sudo_i').Require().String(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) try: user_root = "-u root" if hasattr(get, "sudo_i") else "" cmd = 'docker container exec -it {} {} {}'.format(user_root, get.id, get.shell) return public.return_message(0, 0, cmd) except docker.errors.APIError as ex: return public.return_message(-1, 0, public.lang("Failed to get container")) 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.return_message(0, 0, public.lang("Successfully exported to:{}", file_name)) except: return public.return_message(-1, 0, public.lang( 'operation failure:' + str(public.get_error_info()))) def del_container(self, get): """ 删除指定容器 @param get: @return: """ try: get.validate([ Param('id').Require().String(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) import sys sys.path.insert(0, '/www/server/panel/class') from btdockerModelV2.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("Delete container [{}] successfully!".format(container.attrs['Name'])) get.container_id = get.id # todo 此处导入注意适配 info返回已改 info = main().get_proxy_info(get)['message'] 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() public.M('sites').where('name=?', (info['name'],)).delete() public.M('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'] == '[do not delete] Docker log cut': get.id = i['id'] p.DelCrontab(get) break return public.return_message(0, 0, public.lang("Successfully deleted!")) except Exception as e: if "operation not permitted" in str(e): return public.return_message(-1, 0, public.lang("Please turn off the [Tamper-proof for Enterprise] before trying!")) return public.return_message(-1, 0, public.lang("Delete failed! {}", str(e))) # 设置容器状态 def set_container_status(self, get): """ 设置容器状态 @param get: @return: """ try: get.validate([ Param('id').Require().String(), Param('status').Require().String(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) try: container = self.docker_client(self._url).containers.get(get.id) result = {"status": 0, "message": {"result": "Successfully set!"}} 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: # public.print_log(result) #{"status": 0, "timestamp": 1729561303, "message": {"result": "\u505c\u6b62\u6210\u529f\uff01"}} data = { "name": container.attrs['Name'].replace('/', ''), "result": result['message']['result'], # "msg": result['msg'], } if result['status'] == 0: return public.return_message(0, 0,data) else: return public.return_message(-1, 0, data) except: return public.return_message(-1, 0, { "name": container.attrs['Name'].replace('/', ''), # "status": False, "result": str(result['message']['result']), }) # return public.return_message(-1, 0, str(result['msg'])) except Exception as e: try: if "No such container" in str(e): return public.return_message(-1, 0, public.lang("The container has been deleted!")) 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.return_message(-1, 0, public.lang("ipv6 server port [ {}] is occupied!", str_port)) else: str_port = str(e).split("0.0.0.0")[1].split(":")[1].split(" ")[0] return public.return_message(-1, 0, public.lang("ipv4 server port [ {}] is occupied!", str_port)) return public.return_message(-1, 0, public.lang("Setup failed! {}", str(e))) except Exception as e: return public.return_message(-1, 0, public.lang("Setup failed! {}", 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.return_message(-1, 0, public.lang( "Stop failing!")) dp.write_log("Stopping container [{}] success!".format(data.attrs['Name'].replace('/', ''))) return public.return_message(0, 0, public.lang("Stop succeeding!")) except docker.errors.APIError as e: if "is already paused" in str(e): return public.return_message(-1, 0, public.lang("The container has paused.")) if "No such container" in str(e): return public.return_message(0, 0, public.lang( "Container stopped and deleted")) return public.return_message(-1, 0, public.lang("Stop failing!{}", 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.return_message(-1, 0, public.lang( "boot failed!")) dp.write_log("Starting container [{}] was successful!".format(data.attrs['Name'].replace('/', ''))) return public.return_message(0, 0, public.lang( "starting success!")) 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: print(traceback.format_exc()) raise Exception(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.return_message(-1, 0, public.lang( "Container pause failed!")) dp.write_log("Pause container [{}] success!".format(data.attrs['Name'].replace('/', ''))) return public.return_message(0, 0, public.lang( "Container pause successfully!")) except docker.errors.APIError as e: if "is already paused" in str(e): return public.return_message(-1, 0, public.lang( "The container has been suspended!")) if "is not running" in str(e): return public.return_message(-1, 0, public.lang( "The container is not started and cannot be paused!")) if "is not paused" in str(e): return public.return_message(-1, 0, public.lang( "The container is not paused or has been deleted. Check if the container has the option to delete immediately after stopping!")) return str(e) except Exception as a: print(traceback.format_exc()) raise Exception(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.return_message(-1, 0, public.lang( "boot failed!")) dp.write_log("Unpause container [{}] success!".format(data.attrs['Name'].replace('/', ''))) return public.return_message(0, 0, public.lang( "The container unpaused successfully")) except docker.errors.APIError as e: if "is already paused" in str(e): return public.return_message(-1, 0, public.lang( "The container has paused.")) if "is not running" in str(e): return public.return_message(-1, 0, public.lang( "The container is not started and cannot be paused!")) if "is not paused" in str(e): return public.return_message(-1, 0, public.lang( "The container is not paused or has been deleted. Check if the container has the option to delete immediately after stopping!")) return str(e) except Exception as a: print(traceback.format_exc()) raise Exception(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.return_message(-1, 0, public.lang( "boot failed!")) dp.write_log("Reloading container [{}] succeeded!".format(data.attrs['Name'].replace('/', ''))) return public.return_message(0, 0, public.lang( "The container was reloaded successfully!")) 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.return_message(-1, 0, public.lang( "boot failed!")) dp.write_log("Restart container [{}] successfully!".format(data.attrs['Name'].replace('/', ''))) return public.return_message(0, 0, public.lang( "Container restarts successfully!")) except docker.errors.APIError as e: if "container is marked for removal and cannot be started" in str(e): return public.return_message(-1, 0, public.lang( "The container has been stopped and deleted because containers have the option to automatically delete when stopped")) if "is already paused" in str(e): return public.return_message(-1, 0, public.lang( "The container has paused")) 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.return_message(-1, 0, public.lang("The container ID is empty")) from btdockerModelV2.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.return_message(-1, 0, public.lang("The container doesn't exist!")) 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 return public.return_message(0, 0, sk_container_info) except Exception as e: if "No such container" in str(e): return public.return_message(-1, 0, public.lang("Container does not exist!")) return public.return_message(-1, 0, public.lang("Failed to get container information!{}", 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(): data[str(port["PrivatePort"]) + "/" + port["Type"]] = [{ "HostIp": port["IP"], "HostPort": str(port.get("PublicPort", "")) }] if "IP" in port else None else: data[str(port["PrivatePort"]) + "/" + port["Type"]].append({ "HostIp": port["IP"], "HostPort": str(port.get("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":"提示信息"} ''' tmp = { "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, } return tmp # 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.return_message(-1, 0, public.lang("The container ID is empty")) return public.return_message(0, 0, { "path": public.ExecShell("docker inspect -f \"{{json .GraphDriver.Data.MergedDir}}\" " + get.id)[ 0].strip().strip('"')}) except Exception as e: return public.return_message(0, 0, {"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 btdockerModelV2.dockerSock import container sk_container = container.dockerContainer() container_list = sk_container.get_container() data = [] # 获取前先检测数据库是否存在 self.check_remark_table() dk_container_remark = dp.sql("dk_container_remark").select() self.check_table_dk_backup() 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 public.return_message(0, 0, data) except Exception as e: public.print_log(traceback.format_exc()) return public.return_message(0, 0, []) # 获取容器列表 def get_list(self, get): """ 获取所有容器列表 1 :param get :return: """ data = self._get_list(get) return public.return_message(0, 0, data) def _get_list(self, get): """ 获取所有容器列表 1 :param get :return: """ from btdockerModelV2.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_logs11(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 = self.docker_client(self._url).containers.get(get.id) if hasattr(get, 'time_search') and get.time_search != '': if not os.path.exists(container.attrs['LogPath']): return public.return_message(-1, 0, public.lang("No container logging")) # return public.return_message(0, 0, None) time_search = json.loads(str(get.time_search)) since = int(time_search[0]) until = int(time_search[1]) r_logs = container.logs(since=since, until=until).decode() else: if not os.path.exists(container.attrs['LogPath']): return public.return_message(-1, 0, public.lang("No container logging")) # return public.return_message(0, 0, None) size = os.stat(container.attrs['LogPath']).st_size if size < 1048576: r_logs = container.logs().decode() else: tail = int(get.tail) if "tail" in get else 3000 r_logs = container.logs(tail=tail).decode() if hasattr(get, 'search') and get.search != '': if get.search: r_logs = r_logs.split("\n") r_logs = [i for i in r_logs if get.search in i] r_logs = "\n".join(r_logs) res['logs'] = r_logs res['id'] = get.id res['name'] = dp.rename(container.attrs['Name'][1:]) res['logs_path'] = container.attrs['LogPath'] res['size'] = os.stat(container.attrs['LogPath']).st_size if public.M('sqlite_master').db('docker_log_split').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 public.return_message(0, 0, res) except Exception: return public.return_message(0, 0, res) def get_logs(self, get): """ 获取指定容器日志100行 (优化性能 ) :param get: :return: """ res = {"logs": ""} try: container_info = self.docker_client(self._url).containers.get(get.id) log_path = container_info.attrs['LogPath'] if not os.path.exists(log_path): return public.return_message(0, 0, "") size = os.stat(log_path).st_size # 处理时间范围 since = None until = None if hasattr(get, 'time_search') and get.time_search: try: time_search = json.loads(str(get.time_search)) since = int(time_search[0]) until = int(time_search[1]) except (json.JSONDecodeError, ValueError, TypeError, IndexError): pass # 忽略无效时间参数 # 处理 tail tail = 0 if size > 1048576: # >1MB default_tail = 10000 if since is not None else 1000 try: tail = int(get.tail) if hasattr(get, 'tail') and get.tail else default_tail if tail <= 0: tail = default_tail except (ValueError, TypeError): tail = default_tail # 构建 options options = {} if since is not None: options["since"] = since if until is not None: options["until"] = until if tail > 0: options["tail"] = tail from btdockerModelV2.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: lines = sk_container_logs.split("\n") lines = [line for line in lines if get.search in line] sk_container_logs = "\n".join(lines) res["logs"] = sk_container_logs res['id'] = get.id # 🔧 修复:移除未定义的 dp.rename res['name'] = container_info.attrs['Name'].lstrip('/') res['logs_path'] = log_path res['size'] = size return public.return_message(0, 0, res) except docker.errors.NotFound: return public.return_message(-1, 0, public.lang("The container doesn't exist!")) except Exception as e: public.print_log(traceback.format_exc()) return public.return_message(0, 0, res) def get_logs_all(self, get): """ 获取所有容器的日志 @param get: @return: """ try: client = self.docker_client(self._url) if not client: return public.return_message(-1, 0, public.lang("docker connection failed")) 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 public.return_message(0, 0, clist) except Exception as e: return public.return_message(-1, 0, e) def docker_split(self, get): """ 设置容器日志切割 @param get: @return: """ # {"pid": "3f225d2b41bcadb1b4bb73dc7521a35d1933cb1e1caa8ac61bd53cf718b9910c", "type": "del"} try: get.validate([ Param('pid').Require().String(), Param('type').Require().String(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) try: client = self.docker_client(self._url) if not client: return public.return_message(-1, 0, public.lang("docker connection failed")) 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.return_message(-1, 0, public.lang("parameter error,Pass: type")) if not public.M('sqlite_master').db('docker_log_split').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.return_message(-1, 0, public.lang("Container log directory does not exist, log cut cannot be set!")) 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.return_message(-1, 0, public.lang("parameter error")) 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.return_message(0, 0, public.lang("Opened successfully!")) 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.return_message(0, 0, public.lang("Closed successfully!")) except: return public.return_message(-1, 0, traceback.format_exc()) def clear_log(self, get): """ 清空日志 @param get: @return: """ if not hasattr(get, 'log_path'): return public.return_message(-1, 0, public.lang("parameter error")) if not os.path.exists(get.log_path): return public.return_message(-1, 0, public.lang("The log file does not exist")) public.writeFile(get.log_path, '') return public.return_message(0, 0, public.lang("Log cleaning was successful!")) # 清理无用已停止未使用的容器 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.return_message(-1, 0, public.lang("No useless containers!")) dp.write_log("Delete useless containers successfully!") return public.return_message(0, 0, public.lang("successfully deleted!")) else: import docker client = docker.from_env() containers = client.containers.list(all=True) for container in containers: container.remove(force=True) dp.write_log("Delete useless containers successfully!") return public.return_message(0, 0, public.lang("successfully deleted!")) except Exception as e: if "operation not permitted" in str(e): return public.return_message(-1, 0, public.lang("Please turn off enterprise tamper protection before trying again!")) return public.return_message(-1, 0, public.lang("failed to delete! {}", e)) def update_restart_policy(self, get): """ 更新容器重启策略 @param get: @return: """ try: get.validate([ Param('id').Require().String(), Param('restart_policy').Require(), ], [ public.validate.trim_filter(), ]) except Exception as ex: public.print_log("error info: {}".format(ex)) return public.return_message(-1, 0, ex) try: # if "restart_policy" not in get: # return public.return_message(-1, 0, public.lang("Parameter error, please pass in restart policy restart_policy!")) container = self.docker_client(self._url).containers.get(get.id) # 修复偶尔 go反序列化 报错 if isinstance(get.restart_policy, str): import json restart_policy = get.restart_policy.replace("'", "\"") restart_policy = json.loads(restart_policy) else: restart_policy = get.restart_policy container.update(restart_policy=restart_policy) # container.update(restart_policy=get.restart_policy) # container.update(restart_policy= json.dumps(get.restart_policy)) dp.write_log("Update container [{}] Restart policy successful!".format(container.attrs['Name'])) return public.return_message(0, 0, public.lang("Update successfully!")) # except docker.errors.APIError as e: except Exception as e: public.print_log(traceback.format_exc()) return public.return_message(-1, 0, public.lang("Update failed! {}", e)) ''' @name 重命名指定容器 @author wzz <2023/12/1 下午 3:13> @param 参数名<数据类型> 参数描述 @return 数据类型 ''' def rename_container(self, get): """ 重命名指定容器 @param get: @return: """ try: get.validate([ Param('id').Require().String(), 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, ex) try: # 2023/12/6 上午 10:54 容器未启动时,不允许重命名 container = self.docker_client(self._url).containers.get(get.id) if container.attrs['State']['Status'] != "running": return public.return_message(-1, 0, public.lang("The container is not started and cannot be renamed!")) 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("Renaming container [{}] succeeded!".format(get.name)) return public.return_message(0, 0, public.lang("Rename successfully!")) except docker.errors.APIError as e: return public.return_message(-1, 0, public.lang("Renaming failed! {}", e)) # 2024/2/23 上午 9:58 设置容器列表置顶 def set_container_to_top(self, get): """ 设置容器列表置顶 @param get: @return: """ # {"type": "add", "container_name": "dk_wordpress-wordpress-1"} 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.return_message(-1, 0, public.lang( 'The type only supports add/del')) if container_name is None: return public.return_message(-1, 0, public.lang( 'Please select a container')) _conf_path = "{}/class_v2/btdockerModelV2/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.return_message(0, 0, public.lang("Set top successfully!")) elif set_type == "del": container_top_conf.remove(container_name) public.writeFile(_conf_path, json.dumps(container_top_conf)) return public.return_message(0, 0, public.lang("Unpinned successfully!")) # 2024/2/23 上午 9:58 获取容器列表置顶 def _get_container_to_top(self): """ 获取容器列表置顶 @return: """ _conf_path = "{}/class_v2/btdockerModelV2/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: """ try: get.validate([ Param('id').Require().String(), 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, ex) # if not hasattr(get, 'remark'): # return public.return_message(-1, 0, public.lang("Parameter error, Please pass in: remark!")) # if not hasattr(get, 'id'): # return public.return_message(-1, 0, public.lang("Parameter error, Please pass in: 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.return_message(0, 0, public.lang("Setup successful!")) # 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.return_message(-1, 0, public.lang('Parameter error, Please pass in: ws_callback!')) if not hasattr(get, '_ws'): return public.return_message(-1, 0, public.lang('Parameter error, Please pass in: _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": "Start getting CPU and memory usage for all containers!", "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": "I haven't received any information about the running container resources!", "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": "Get CPU and memory usage for all containers successfully!", "status": True, "end": False, })) time.sleep(0.1) except Exception as ex: # import traceback # public.print_log(traceback.format_exc()) # public.print_log("error info: {}".format(ex)) get._ws.send(public.getJson( { "data": {}, "ws_callback": get.ws_callback, "msg": "Failed to get CPU and memory usage for all containers!", "status": True, "end": True, })) return def check_table_dk_backup(self): ''' @name 检查并创建表 @return dict{"status":True/False,"msg":"提示信息"} ''' if not dp.sql('sqlite_master').where('type=? AND name=?', ('table', 'dk_backup')).count(): dp.sql('dk_backup').execute( "CREATE TABLE `dk_backup` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `type` INTEGER, `name` TEXT, `container_id` TEXT, `container_name` TEXT, `filename` TEXT, `size` INTEGER, `addtime` TEXT, `ps` STRING DEFAULT 'none', `cron_id` INTEGER DEFAULT 0 )", () )