| |
| |
| |
| |
| |
| |
| |
| |
| import datetime |
| import json |
| import os |
| import traceback |
|
|
| |
| |
| |
| import public |
| import crontab |
| import docker.errors |
| import projectModel.bt_docker.dk_public as dp |
|
|
|
|
| class main: |
|
|
| def __init__(self): |
| self.alter_table() |
| if public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count(): |
| p = crontab.crontab() |
| llist = p.GetCrontab(None) |
| for i in llist: |
| if i['name'] == '[勿删]docker日志切割': |
| return |
| args = { |
| "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(args) |
|
|
| 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 run(self, args): |
| """ |
| :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 args: |
| :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({})) |
| name_map = json.loads(public.readFile(config_path)) |
| name_str = 'q18q' + public.GetRandomString(10).lower() |
| name_map[name_str] = args.name |
| args.name = name_str |
| public.writeFile(config_path, json.dumps(name_map)) |
| if not hasattr(args, 'ports'): |
| args.ports = False |
| if not hasattr(args, 'volumes'): |
| args.volumes = False |
| |
| if args.ports: |
| for i in args.ports: |
| if dp.check_socket(args.ports[i]): |
| return public.returnMsg(False, "服务器端口[{}]已被占用,请更换为其他端口!".format(args.ports[i])) |
| if not args.image: |
| return public.returnMsg(False, "如果没有选择镜像,请到镜像标签拉取您需要的镜像!") |
| if args.restart_policy['Name'] == "always": |
| args.restart_policy = {"Name": "always"} |
| |
| |
| args.cpu_quota = float(args.cpuset_cpus) * 100000 |
| |
| |
| |
| |
| |
| try: |
| if not args.name: |
| args.name = "{}-{}".format(args.image, public.GetRandomString(8)) |
| if int(args.cpu_quota) / 100000 > dp.get_cpu_count(): |
| return public.returnMsg(False, "CPU 配额已超过可用内核数!") |
| mem_limit_byte = dp.byte_conversion(args.mem_limit) |
| if mem_limit_byte > dp.get_mem_info(): |
| return public.returnMsg(False, "内存配额已超过可用数量!") |
| res = self.docker_client(args.url).containers.run( |
| name=args.name, |
| image=args.image, |
| detach=True, |
| publish_all_ports=True if args.publish_all_ports == "1" else False, |
| ports=args.ports if args.ports else None, |
| command=args.command, |
| auto_remove=True if str(args.auto_remove) == "1" else False, |
| environment=dp.set_kv(args.environment), |
| volumes=args.volumes, |
| |
| |
| cpu_quota=int(args.cpu_quota), |
| mem_limit=args.mem_limit, |
| restart_policy=args.restart_policy, |
| labels=dp.set_kv(args.labels), |
| tty=True, |
| stdin_open=True, |
| privileged=True |
| ) |
| if res: |
| pdata = { |
| "cpu_limit": str(args.cpu_quota), |
| "container_name": args.name |
| } |
| dp.sql('container').insert(pdata) |
| public.set_module_logs('docker', 'run_container', 1) |
| dp.write_log("创建容器 [{}] 成功!".format(args.name)) |
| return public.returnMsg(True, "容器创建成功!") |
| return public.returnMsg(False, '创建失败!') |
| except docker.errors.APIError as e: |
| 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 args.ports: |
| if ":{}:".format(args.ports[i]) in str(e): |
| port = args.ports[i] |
| args.id = args.name |
| self.del_container(args) |
| return public.returnMsg(False, "服务器端口 {} 正在使用中! 请更改其他端口".format(port)) |
| return public.returnMsg(False, '创建失败! {}'.format(public.get_error_info())) |
|
|
| |
| def commit(self, args): |
| """ |
| :param repository 推送到的仓库 |
| :param tag 镜像标签 jose:v1 |
| :param message 提交的信息 |
| :param author 镜像作者 |
| :param changes |
| :param conf dict |
| :param path 导出路径 |
| :param name 导出文件名 |
| :param args: |
| :return: |
| """ |
| if not hasattr(args, 'conf') or not args.conf: |
| args.conf = None |
| if args.repository == "docker.io": |
| args.repository = "" |
| container = self.docker_client(args.url).containers.get(args.id) |
| container.commit( |
| repository=args.repository if args.repository else None, |
| tag=args.tag if args.tag else None, |
| message=args.message if args.message else None, |
| author=args.author if args.author else None, |
| |
| conf=args.conf |
| ) |
| if hasattr(args, "path") and args.path: |
| args.id = "{}:{}".format(args.name, args.tag) |
| import projectModel.bt_docker.dk_image as dk |
| return dk.main().save(args) |
| dp.write_log("提交容器 [{}] 作为图像 [{}] 成功!".format(container.attrs['Name'], args.tag)) |
| return public.returnMsg(True, "提交成功!") |
|
|
| |
| def docker_shell(self, args): |
| """ |
| :param container_id |
| :param args: |
| :return: |
| """ |
| try: |
| self.docker_client(args.url).containers.get(args.container_id) |
| cmd = 'docker container exec -it {} /bin/bash'.format(args.container_id) |
| return public.returnMsg(True, cmd) |
| except docker.errors.APIError as ex: |
| return public.returnMsg(False, '获取容器失败') |
|
|
| |
| def export(self, args): |
| """ |
| :param path 保存路径 |
| :param name 包名 |
| :param args: |
| :return: |
| """ |
| from os import path as ospath |
| from os import makedirs as makedirs |
| try: |
| if "tar" in args.name: |
| file_name = '{}/{}'.format(args.path, args.name) |
| else: |
| file_name = '{}/{}.tar'.format(args.path, args.name) |
| if not ospath.exists(args.path): |
| makedirs(args.path) |
| public.writeFile(file_name, '') |
| f = open(file_name, 'wb') |
| container = self.docker_client(args.url).containers.get(args.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, args): |
| """ |
| :return: |
| """ |
| import projectModel.bt_docker.dk_public as dp |
| container = self.docker_client(args.url).containers.get(args.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=?", (args.id,)).delete() |
| dp.sql("io_stats").where("container_id=?", (args.id,)).delete() |
| dp.sql("mem_stats").where("container_id=?", (args.id,)).delete() |
| dp.sql("net_stats").where("container_id=?", (args.id,)).delete() |
| dp.sql("container").where("container_nam=?", (container.attrs['Name'])).delete() |
| dp.write_log("删除容器 [{}] 成功!".format(container.attrs['Name'])) |
| return public.returnMsg(True, "成功删除!") |
|
|
| |
| def set_container_status(self, args): |
| import time |
| container = self.docker_client(args.url).containers.get(args.id) |
| if args.act == "start": |
| container.start() |
| elif args.act == "stop": |
| container.stop() |
| elif args.act == "pause": |
| container.pause() |
| elif args.act == "unpause": |
| container.unpause() |
| elif args.act == "reload": |
| container.reload() |
| else: |
| container.restart() |
| time.sleep(1) |
| tmp = self.docker_client(args.url).containers.get(args.id) |
| return {"name": container.attrs['Name'].replace('/', ''), "status": tmp.attrs['State']['Status']} |
|
|
| |
| def stop(self, args): |
| """ |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| try: |
| args.act = "stop" |
| data = self.set_container_status(args) |
| if data['status'] != "exited": |
| return public.returnMsg(False, "停止失败!") |
| dp.write_log("停止容器 [{}] 成功!".format(data['name'])) |
| 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, args): |
| """ |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| try: |
| args.act = "start" |
| data = self.set_container_status(args) |
| if data['status'] != "running": |
| return public.returnMsg(False, "启动失败!") |
| dp.write_log("启动容器 [{}] 成功!".format(data['name'])) |
| 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(args) |
|
|
| def pause(self, args): |
| """ |
| Pauses all processes within this container. |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| try: |
| args.act = "pause" |
| data = self.set_container_status(args) |
| if data['status'] != "paused": |
| return public.returnMsg(False, "容器暂停失败!") |
| dp.write_log("暂停容器 [{}] 成功!".format(data['name'])) |
| 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) |
|
|
| def unpause(self, args): |
| """ |
| unPauses all processes within this container. |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| try: |
| args.act = "unpause" |
| data = self.set_container_status(args) |
| if data['status'] != "running": |
| return public.returnMsg(False, "启动失败!") |
| dp.write_log("取消暂停容器 [{}] 成功!".format(data['name'])) |
| 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) |
|
|
| def reload(self, args): |
| """ |
| Load this object from the server again and update attrs with the new data. |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| args.act = "reload" |
| data = self.set_container_status(args) |
| if data['status'] != "running": |
| return public.returnMsg(False, "启动失败!") |
| dp.write_log("重新加载容器 [{}] 成功!".format(data['name'])) |
| return public.returnMsg(True, "重载容器成功!") |
|
|
| def restart(self, args): |
| """ |
| Restart this container. Similar to the docker restart command. |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| args.act = "restart" |
| data = self.set_container_status(args) |
| if data['status'] != "running": |
| return public.returnMsg(False, "启动失败!") |
| dp.write_log("重启容器 [{}] 成功!".format(data['name'])) |
| return public.returnMsg(True, "容器重启成功!") |
|
|
| def get_container_ip(self, container_networks): |
| data = list() |
| for network in container_networks: |
| data.append(container_networks[network]['IPAddress']) |
| return data |
|
|
| def get_container_path(self, detail): |
| 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_other_data_for_container_list(self, args): |
| import projectModel.bt_docker.dk_image as di |
| import projectModel.bt_docker.dk_volume as dv |
| import projectModel.bt_docker.dk_compose as dc |
| import projectModel.bt_docker.dk_setup as ds |
| |
| images = di.main().image_list(args) |
| if images['status']: |
| images = images['msg']['images_list'] |
| else: |
| images = list() |
| |
| volumes = dv.main().get_volume_list(args) |
| if volumes['status']: |
| volumes = volumes['msg']['volume'] |
| else: |
| volumes = list() |
| |
| template = dc.main().template_list(args) |
| if template['status']: |
| template = template['msg']['template'] |
| else: |
| template = list() |
| online_cpus = dp.get_cpu_count() |
| mem_total = dp.get_mem_info() |
| docker_setup = ds.main() |
| return { |
| "images": images, |
| "volumes": volumes, |
| "template": template, |
| "online_cpus": online_cpus, |
| "mem_total": mem_total, |
| "installed": docker_setup.check_docker_program(), |
| "service_status": docker_setup.get_service_status() |
| } |
|
|
| |
| def get_list(self, args): |
| """ |
| :param url |
| :return: |
| """ |
| |
| import projectModel.bt_docker.dk_setup as ds |
| data = self.get_other_data_for_container_list(args) |
| if not ds.main().check_docker_program(): |
| data['container_list'] = list() |
| return public.returnMsg(True, data) |
| client = self.docker_client(args.url) |
| if not client: |
| return public.returnMsg(True, data) |
| containers = client.containers |
| attr_list = self.get_container_attr(containers) |
| |
| container_detail = list() |
| for attr in attr_list: |
| cpu_usage = dp.sql("cpu_stats").where("container_id=?", (attr["Id"],)).select() |
| if cpu_usage and isinstance(cpu_usage, list): |
| cpu_usage = cpu_usage[-1]['cpu_usage'] |
| else: |
| cpu_usage = "0.0" |
| tmp = { |
| "id": attr["Id"], |
| "name": attr['Name'].replace("/", ""), |
| "status": attr["State"]["Status"], |
| "image": attr["Config"]["Image"], |
| "time": attr["Created"], |
| "merged": self.get_container_path(attr), |
| "ip": self.get_container_ip(attr["NetworkSettings"]['Networks']), |
| "ports": attr["NetworkSettings"]["Ports"], |
| "detail": attr, |
| "cpu_usage": cpu_usage if attr["State"]["Status"] == "running" else "" |
| } |
| container_detail.append(tmp) |
| if container_detail: |
| for i in container_detail: |
| i['name'] = self.rename(i['name']) |
| data['container_list'] = container_detail |
| return public.returnMsg(True, data) |
|
|
| def rename(self, name: str): |
| try: |
| if name[:4] != 'q18q': |
| return name |
| config_path = "{}/config/name_map.json".format(public.get_panel_path()) |
| config_data = json.loads(public.readFile(config_path)) |
| name_l = name.split('_') |
| if name_l[0] in config_data.keys(): |
| name_l[0] = config_data[name_l[0]] |
| return '_'.join(name_l) |
| except: |
| return name |
|
|
| |
| 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, args): |
| """ |
| :param url |
| :param id |
| :param args: |
| :return: |
| """ |
| try: |
| container = self.docker_client(args.url).containers.get(args.id) |
| if hasattr(args, 'time_search') and args.time_search != '': |
| time_search = json.loads(args.time_search) |
| since = int(time_search[0]) |
| until = int(time_search[1]) |
| res = container.logs(since=since, until=until).decode() |
| else: |
| res = container.logs().decode() |
| if hasattr(args, 'search') and args.search != '': |
| if args.search: |
| res = res.split("\n") |
| res = [i for i in res if args.search in i] |
| res = "\n".join(res) |
| return public.returnMsg(True, res) |
| except docker.errors.APIError as e: |
| if "configured logging driver does not support reading" in str(e): |
| return public.returnMsg(False, "容器没有日志文件!") |
| print(traceback.format_exc()) |
|
|
| def get_logs_all(self, args): |
| try: |
| client = self.docker_client(args.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': self.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 |
| if public.M('sqlite_master').where('type=? AND name=?', ('table', 'docker_log_split')).count(): |
| i['split_status'] = True if public.M('docker_log_split').where('pid=?', |
| (i['id'],)).count() else False |
| data = public.M('docker_log_split').where('pid=?', (i['id'],)).select() |
| if data: |
| i['split_type'] = data[0]['split_type'] |
| i['split_size'] = data[0]['split_size'] |
| i['split_hour'] = data[0]['split_hour'] |
| i['split_minute'] = data[0]['split_minute'] |
| i['save'] = data[0]['save'] |
| else: |
| i['split_type'] = 'day' |
| i['split_size'] = 10485760 |
| i['split_hour'] = 2 |
| i['split_minute'] = 0 |
| i['save'] = '180' |
| else: |
| i['split_status'] = False |
| i['split_type'] = 'day' |
| i['split_size'] = 10485760 |
| i['split_hour'] = 2 |
| i['split_minute'] = 0 |
| i['save'] = '180' |
| return clist |
| except: |
| return public.returnMsg(True, traceback.format_tb()) |
|
|
| def docker_split(self, args): |
| try: |
| client = self.docker_client(args.url) |
| if not client: |
| return public.returnMsg(True, 'docker连接失败') |
| containers = client.containers |
| clist = [i.attrs for i in containers.list(all=True)] |
| name = [self.rename(i['Name'][1:]) for i in clist if i['Id'] == args.pid] |
| if name: |
| name = name[0] |
| else: |
| name = '' |
| if not hasattr(args, 'type'): |
| return public.returnMsg(False, '参数错误') |
| 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 args.type == 'add': |
| if not (hasattr(args, 'pid') and hasattr(args, 'log_path') and |
| hasattr(args, 'split_type') and hasattr(args, 'split_size') and |
| hasattr(args, 'split_minute') and |
| hasattr(args,'split_hour') and hasattr(args, 'save')): |
| return public.returnMsg(False, '参数错误') |
| data = { |
| 'name': name, |
| 'pid': args.pid, |
| 'log_path': args.log_path, |
| 'split_type': args.split_type, |
| 'split_size': args.split_size, |
| 'split_hour': args.split_hour, |
| 'split_minute': args.split_minute, |
| 'save': args.save |
| } |
| if public.M('docker_log_split').where('pid=?', (args.pid,)).count(): |
| id = public.M('docker_log_split').where('pid=?', (args.pid,)).select() |
| public.M('docker_log_split').delete(id[0]['id']) |
| public.M('docker_log_split').insert(data) |
| return public.returnMsg(True, "开启成功!") |
| elif args.type == 'del': |
| id = public.M('docker_log_split').where('pid=?', (args.pid,)).getField('id') |
| public.M('docker_log_split').where('id=?', (id,)).delete() |
| return public.returnMsg(True, "关闭成功!") |
| except: |
| return public.returnMsg(False, traceback.format_exc()) |
|
|
| def clear_log(self, args): |
| if not hasattr(args, 'log_path'): |
| return public.returnMsg(False, '参数错误') |
| if not os.path.exists(args.log_path): |
| return public.returnMsg(False, '日志文件不存在') |
| public.writeFile(args.log_path, '') |
| return public.returnMsg(True, "日志清理成功成功!") |
|
|