利用Flask封装sqladvisor服务

2 篇文章 0 订阅
1 篇文章 0 订阅

利用Flask封装sqladvisor服务

使用sqladvisor需要预装很多系统组件,比如percona等,在不连外网的情况下,安装起来还是非常困难的。于是采用镜像的方式安装,这样就存在一个远程调用sqladvisor的问题,于是需要封装一个sqladvisor服务。此服务借鉴了一些archery开源项目的内容,特此说明。

from  flask import Flask
from flask import request
import json
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
app = Flask(__name__)
import logging
import subprocess
import traceback
import pymysql
pymysql.install_as_MySQLdb()
logger = logging.getLogger('default')

class Plugin:
    def __init__(self, path):
        self.path = path
        self.required_args = []  # must param
        self.disable_args = []  # forbiden param

    def check_args(self, args):
        """
        check param
        :return: {'status': 0, 'msg': 'ok', 'data': {}}
        """
        args_check_result = {'status': 0, 'msg': 'ok', 'data': {}}
        # check path
        if self.path is None:
            return {'status': 1, 'msg': 'exec path must not be null!', 'data': {}}
        # check  forbiden param
        for arg in args.keys():
            if arg in self.disable_args:
                return {'status': 1, 'msg': '{arg}param forbiden'.format(arg=arg), 'data': {}}
        # check must param
        for req_arg in self.required_args:
            if req_arg not in args.keys():
                return {'status': 1, 'msg': 'need {arg} param'.format(arg=req_arg), 'data': {}}
            elif args[req_arg] is None or args[req_arg] == '':
                return {'status': 1, 'msg': '{arg} param cannot be null'.format(arg=req_arg), 'data': {}}
        return args_check_result

    def generate_args2cmd(self, args, shell):
        """
        :return:
        """

    @staticmethod
    def execute_cmd(cmd_args, shell):
        """
        :return:
        """
        try:
            p = subprocess.Popen(cmd_args,
                                 shell=shell,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 #universal_newlines=True
                                 )
            return p
        except Exception as e:
            logger.error("exec failed \n{}".format(traceback.format_exc()))
            raise RuntimeError('exec failed,reason:%s' % str(e))

class SQLAdvisor(Plugin):
    def __init__(self):
        self.path = "/usr/local/bin/sqladvisor"
        self.required_args = ['q']
        self.disable_args = []
        super(Plugin, self).__init__()

    def generate_args2cmd(self, args, shell):
        """
        :param args:
        :param shell:
        :return:
        """
        if shell:
            cmd_args = self.path if self.path else ''
            for name, value in args.items():
                cmd_args += f' -{name} "{value}"'
        else:
            cmd_args = [self.path]
            for name, value in args.items():
                cmd_args.append(f'-{name}')
                cmd_args.append(f'{value}')
        return cmd_args


def user_instaces(ip,port):
    dsnstr = "mysql://test:test@127.0.0.1:3307/archery?charset=utf8"
    sql="SELECT USER,PASSWORD FROM ARCHERY.SQL_INSTANCE WHERE HOST='%s' AND PORT=%s"%(ip,port)
    engine = create_engine(dsnstr)
    DBsession = sessionmaker(bind=engine)
    session = DBsession()
    try:
        ret = session.execute(sql).fetchone()
        user=ret[0]
        password=ret[1]
    except Exception as e :
        user="null"
        password="null"
    finally:
        session.close()
    return user,password


@app.route('/sqladvisor/', methods=['POST', 'GET'])
def sqladvisor():
    req=request.json
    host=req["ip"]
    port=req["port"]
    db_name=req["db_name"]
    sql=req["sql"]
    verbose=1
    result = {'status': 0, 'msg': 'ok', 'data': []}

    if sql is None or db_name is None:
        result['status'] = 1
        result['msg'] = 'sql or instance must not be null'
        return json.dumps(result)

    user, password=user_instaces(host,port)
    if user =="null" or password=="null":
        result['status'] = 1
        result['msg'] = 'cannot find param in ARCHERY.SQL_INSTANCE'
        return json.dumps(result)

    sqladvisor_path = "/usr/local/bin/sqladvisor"
    if sqladvisor_path is None:
        result['status'] = 1
        result['msg'] = 'please config SQLAdvisor path!'
        return json.dumps(result)

    sqladvisor = SQLAdvisor()
    args = {"h": host,
            "P": port,
            "u": user,
            "p": password,
            "d": db_name,
            "v": verbose,
            "q": sql.strip().replace('"', '\\"').replace('`', '').replace('\n', ' ')
            }

    args_check_result = sqladvisor.check_args(args)
    if args_check_result['status'] == 1:
        return json.dumps(args_check_result)
    cmd_args = sqladvisor.generate_args2cmd(args, shell=True)
    try:
        stdout, stderr = sqladvisor.execute_cmd(cmd_args, shell=True).communicate()
        #print(stdout.decode('utf-8'),stderr.decode('utf-8'))
        result['data'] = f"{stdout}{stderr}"
        #print(result)
    except RuntimeError as e:
        result['status'] = 1
        result['msg'] = str(e)
    return json.dumps(result)

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080)
    #app.run(debug=True)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Archery 定位于 SQL 审核查询平台,旨在提升 DBA 的工作效率,支持多种数据库的 SQL 上线和查询,同时支持丰富的MySQL运维功能,所有功能都兼容手机端操作。 启动: 下载 Releases文件,解压后进入docker-compose文件夹 #启动 docker-compose -f docker-compose.yml up -d #表结构初始化 docker exec -ti archery /bin/bash cd /opt/archery source /opt/venv4archery/bin/activate python3 manage.py makemigrations sql   python3 manage.py migrate #数据初始化 python3 manage.py dbshellpython3 manage.py dbshell #创建管理用户 python3 manage.py createsuperuser #重启服务 docker restart archery #日志查看和问题排查 docker logs archery -f --tail=10 logs/archery.log 访问 http://127.0.0.1:9123/     Archery SQL审核查询平台 更新日志: v1.8.1 变更说明 移除对 Inception 的审核支持,仅作为脱敏语句解析使用 在线查询,pg数据库增加会话超时设定,支持查询脱敏 慢查日志和明细列表支持按表头字段进行服务端排序 默认资源组、权限组支持多选,优化系统配置下拉选项 修复说明 解决数据库区分大小写时查看事务信息报错的问题 解决 memoryview is not JSON serializable PG脱敏-查询语句中带有别名脱敏处理 解决查询结果不展示json对象的问题 调整启动方式为wsgi,解决上版本出现访问阻塞的问题 企业微信消息推送,如果消息接受者ID为空,则不会调用企业微信官方API. fix(sendmsg/feishu): fix #1016 支持新版飞书 webhook 接口 ) Bump django from 3.1.2 to 3.1.6 add pycryptodome to requirements 易用性调整 手动执行按钮改名为“已手动完成” 安全性调整 对接受入参的SQL拼接增加参数转义,规避注入风险 使用shlex.quote()对插件参数进行过滤,规避注入风险

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值