# coding: utf-8 # ------------------------------------------------------------------- # YakPanel # ------------------------------------------------------------------- # Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved. # ------------------------------------------------------------------- # Author: wzz # ------------------------------------------------------------------- # ------------------------------ # docker模型 - docker compose 基类 # ------------------------------ import sys import time from typing import List if "/www/server/panel/class" not in sys.path: sys.path.insert(0, "/www/server/panel/class") import public class Compose(): def __init__(self): self.cmd = 'docker-compose' self.path = None self.tail = "100" self.type = 0 self.compose_name = None self.compose_project_path = "{}/data/compose".format(public.get_panel_path()) self.grep_version = 'grep -v "\`version\` is obsolete"' self.def_name = None self.container_id = None self.ps_count = 0 def set_container_id(self, container_id: str) -> 'Compose': self.container_id = container_id return self def get_cmd(self) -> str: return self.cmd def set_cmd(self, cmd: str) -> 'Compose': self.cmd = cmd return self def set_path(self, path: str, rep: bool = False) -> 'Compose': if rep: self.path = path.replace("\'", "\\'").replace("\"", "\\\"").replace(" ", "\\ ").replace("|", "\\|") else: self.path = path return self def set_tail(self, tail: str) -> 'Compose': self.tail = tail return self def set_type(self, type: int) -> 'Compose': self.type = type return self def set_compose_name(self, compose_name: str) -> 'Compose': self.compose_name = compose_name return self def set_def_name(self, def_name: str) -> 'Compose': self.def_name = def_name return self def get_compose_up(self) -> List[str] or str: return self.cmd + ' -f {} up -d| {}'.format(self.path, self.grep_version) def get_compose_up_remove_orphans(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} up -d --remove-orphans'.format(self.path) else: return [self.cmd, '-f', self.path, 'up', '-d', '--remove-orphans'] def get_compose_down(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} down'.format(self.path) else: return [self.cmd, '-f', self.path, 'down'] def kill_compose(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} kill'.format(self.path) else: return [self.cmd, '-f', self.path, 'kill'] def rm_compose(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} rm -f'.format(self.path) else: return [self.cmd, '-f', self.path, 'rm', '-f'] def get_compose_delete(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} down --volumes --remove-orphans'.format(self.path) else: return [self.cmd, '-f', self.path, 'down', '--volumes', '--remove-orphans'] def get_compose_delete_for_ps(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -p {} down --volumes --remove-orphans'.format(self.compose_name) else: return [self.cmd, '-p', self.compose_name, 'down', '--volumes', '--remove-orphans'] def get_compose_restart(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} restart'.format(self.path) else: return [self.cmd, '-f', self.path, 'restart'] def get_compose_stop(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} stop'.format(self.path) else: return [self.cmd, '-f', self.path, 'stop'] def get_compose_start(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} start'.format(self.path) else: return [self.cmd, '-f', self.path, 'start'] def get_compose_pull(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} pull'.format(self.path) else: return [self.cmd, '-f', self.path, 'pull'] def get_compose_logs(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} logs -f --tail {}'.format(self.path, self.tail) else: return [self.cmd, '-f', self.path, 'logs', '-f', '--tail', self.tail] def get_tail_compose_log(self) -> List[str] or str: if self.type == 0: return self.cmd + ' -f {} logs --tail {}'.format(self.path, self.tail) else: return [self.cmd, '-f', self.path, 'logs', '--tail', self.tail] def get_compose_ls(self) -> List[str] or str: return self.cmd + ' ls -a --format json| {}'.format(self.grep_version) def get_compose_ps(self) -> List[str] or str: return self.cmd + ' -f {} ps -a --format json| {}'.format(self.path, self.grep_version) def get_compose_config(self) -> List[str] or str: return self.cmd + ' -f {} config| {}'.format(self.path, self.grep_version) def get_container_logs(self) -> List[str] or str: # return ['docker', 'logs', '-f', self.container_id] return "docker logs {} --tail {} 2>&1".format(self.container_id, self.tail) def wsResult(self, status: bool = True, msg: str = "", data: any = None, timestamp: int = None, code: int = 0, args: any = None): # public.print_log("wsResult code -- {} status--{}".format(code, status)) # rs = public.returnResult(status, msg, data, timestamp, code, args) import time if timestamp is None: timestamp = int(time.time()) if msg is None: msg = "OK" rs = {"code": code, "status": status, "msg": msg, "data": data, "timestamp": timestamp, "def_name": self.def_name} # public.print_log("wsResult rs -- {} ".format(rs)) return rs # 2024/8/1 下午3:29 构造分页数据 def get_page(self, data, get): get.row = get.get("row", 20) # get.row = 20000 get.p = get.get("p", 1) import page page = page.Page() info = {'count': len(data), 'row': int(get.row), 'p': int(get.p), 'uri': {}, 'return_js': ''} result = {'page': page.GetPage(info)} n = 0 result['data'] = [] for i in range(info['count']): if n >= page.ROW: break if i < page.SHIFT: continue n += 1 result['data'].append(data[i]) return result # 2024/7/29 下午4:22 检查web服务是否正常 def check_web_status(self): ''' @name 检查web服务是否正常 @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' from mod.base.web_conf import util webserver = util.webserver() if webserver != "nginx" or webserver is None: return public.returnResult(status=False, msg="Domain name access only supports Nginx. Please go to the software store to install Nginx or choose not to use domain name access!") from panelSite import panelSite site_obj = panelSite() site_obj.check_default() wc_err = public.checkWebConfig() if not wc_err: return public.returnResult( status=False, msg='ERROR: An error in the configuration file has been detected. Please eliminate it before proceeding.

' + wc_err.replace("\n", '
') + '
' ) return public.return_message(0, 0, '') def pageResult(self, status: bool = True, msg: str = "", data: any = None, timestamp: int = None, code: int = 0, args: any = None, page: any = None, cpu: any = None, mem: any = None): # rs = public.returnResult(status, msg, data, timestamp, code, args) # public.print_log("re 列表 --{}".format(rs['msg'])) # import time # if timestamp is None: # timestamp = int(time.time()) if msg is None: msg = "OK" # rs = {"code": code, "status": status, "msg": msg, "data": data, "timestamp": timestamp} rs = {"msg": msg, "data": data} if not self.def_name is None: rs["def_name"] = self.def_name if not cpu is None: rs["maximum_cpu"] = cpu if not mem is None: rs["maximum_memory"] = mem if not page is None: rs["page"] = page st = 0 if status else -1 return public.return_message(st, 0, rs) # 2024/6/25 下午2:40 获取日志类型的websocket返回值 def exec_logs(self, get, command, cwd=None): ''' @name 获取日志类型的websocket返回值 @author wzz <2024/6/25 下午2:41> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' import json,select if self.def_name is None: self.set_def_name(get.def_name) from subprocess import Popen, PIPE, STDOUT if not hasattr(get, '_ws'): return p = Popen(command, stdout=PIPE, stderr=STDOUT, cwd=cwd) try: while True: # 优先检查连接状态,如果断开则立即停止 if not get._ws.connected: break # 检查是否有数据可读 readable, _, _ = select.select([p.stdout], [], [], 0.01) if p.stdout in readable: line = p.stdout.readline() if line: try: get._ws.send(json.dumps(self.wsResult(True, "{}".format(line.decode('utf-8').rstrip())))) except: break elif p.poll() is not None: # 没有数据可读,且进程已结束,则退出 break finally: if p.poll() is None: p.kill() # 2024/6/25 下午2:40 获取日志类型的websocket返回值 def status_exec_logs(self, get, command, cwd=None): ''' @name 获取日志类型的websocket返回值 @author wzz <2024/6/25 下午2:41> @param "data":{"参数名":""} <数据类型> 参数描述 @return dict{"status":True/False,"msg":"提示信息"} ''' import json if self.def_name is None: self.set_def_name(get.def_name) from subprocess import Popen, PIPE, STDOUT p = Popen(command, stdout=PIPE, stderr=STDOUT, cwd=cwd) while True: if p.poll() is not None: break line = p.stdout.readline() # 非阻塞读取 if line: try: if hasattr(get, '_ws'): get._ws.send(json.dumps(self.wsResult( True, "{}\r\n".format(line.decode('utf-8').rstrip()), ))) except: continue else: break