feishu_with_pic

#!/usr/bin/env python
# -*- coding=utf-8 -*-
import os, time, json, requests, re, urllib3, sys, configparser, datetime, hashlib, logging, collections, traceback
from pyzabbix import ZabbixAPI

if sys.version_info[0] == 2:
    from urlparse import urljoin

    reload(sys)
    sys.setdefaultencoding('utf-8')
elif sys.version_info[0] == 3:
    from urllib.parse import urljoin

# 获取配置
#parent_dir = os.path.dirname(os.path.abspath(__file__))
config = configparser.ConfigParser()
config.read('/usr/local/zabbix-5.2.4/share/zabbix/alertscripts/feishu_with_pic.conf', encoding='utf-8')
#config.read(parent_dir + "/feishu_with_pic.conf", encoding='utf-8')
log_file = config.get('config', 'log')
RobotApi = config.get('config', 'webhook')
AppId = config.get('config', 'app_id')
AppSecret = config.get('config', 'app_secret')
TokenCacheFile = config.get('config', 'token_cache_file')
ZabbixUrl = config.get('config', 'zabbix_url')
ZabbixUser = config.get('config', 'zabbix_user')
ZabbixPass = config.get('config', 'zabbix_pass')
ZabbixPicPath = config.get('config', 'zabbix_pic_path')
log_file = config.get('config', 'log')

# debug config
Debug = int(config.get('config', 'debug_mode'))
DebugRobotApi = config.get('config', 'debug_webhook')

logger = logging.getLogger('feishu')
logger.setLevel(logging.INFO)
# create console handler
ch = logging.FileHandler(filename=log_file)
# ch = logging.StreamHandler()
# create formatter
formatter = logging.Formatter("%(asctime)s %(name)s [%(process)d][{0}:%(lineno)s] [%(levelname)s] %(message)s",
                              "%Y-%m-%d %H:%M:%S")
ch.setFormatter(formatter)
logger.addHandler(ch)


def make_feishu_req_data(zbx_url, event, alert_map, zbx_image_key):
    """
    文档:https://ding-doc.dingtalk.com/doc#/serverapi3/iydd5h
    :param zbx_image_key:
    :param zbx_url:
    :param event:
    :param alert_map: zabbix传入的message,已处理为dict
    :return: {}
    """
    # 字段展示顺序
    field_map = collections.OrderedDict([('ip_addr', 'IP地址'), ('failed_time', '故障时间'), ('recovery_time', '恢复时间'),
                                         ('duration', '持续时间'), ('level', '故障级别'),('trigger','故障信息')])
    if 'failed_time' in alert_map.keys():
        color = 'red'
        card_title = '故障卡'
    else:
        color = 'green'
        card_title = '恢复卡'
    true = True
    false = False
    card = {
        "config": {
            "wide_screen_mode": true
        },
        "header": {
            "title": {
                "tag": "plain_text",
                "content": card_title
            },
            "template": color
        },
        "elements": [
            {
                "tag": "div",
                "text": {
                    "tag": "lark_md",
                    "content": "**{host}: {title}**".format(host=alert_map.get('host', None), title=event)
                },
                "fields": []
            },
            {
                "tag": "markdown",
                "content": "![{title}]({img_key})".format(title=event, img_key=zbx_image_key)
            },
            {
                "tag": "hr"
            },
            {
                "tag": "note",
                "elements": [
                    {
                        "tag": "plain_text",
                        "content": "Send at: {cur_time}".format(
                            cur_time=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
                    },
                    {
                        "tag": "lark_md",
                        "content": "| [最新数据]({zbx_url}/history.php?action=showgraph&itemids[]={item_id})".format(
                            zbx_url=zbx_url, item_id=alert_map.get('item_id', None))
                    },
                    {
                        "tag": "lark_md",
                        "content": "| [配置主机]({zbx_url}/hosts.php?form=update&hostid={host_id})".format(zbx_url=zbx_url,
                                                                                                       host_id=alert_map.get(
                                                                                                           'host_id',
                                                                                                           None))
                    }
                ]
            }
        ]
    }
    for k, v in field_map.items():
        if k in alert_map.keys():
            tp = {
                "is_short": False,
                "text": {
                    "tag": "lark_md",
                    "content": "{sub}:{val}".format(sub=v, val=alert_map[k])
                }
            }
            card['elements'][0]['fields'].append(tp)
    req_dict = {
        "msg_type": "interactive",
        "card": card
    }
    return json.dumps(req_dict, ensure_ascii=False)

class Zabbix_Graph(object):
    """ Zabbix_Graph """
    def __init__(self, url=None, user=None, pwd=None, logger=None, timeout=None):
        urllib3.disable_warnings()
        if timeout == None:
            self.timeout = 1
        else:
            self.timeout = timeout
        self.url = url
        self.user = user
        self.pwd = pwd
        self.cookies = {}
        self.zapi = None
        self.logger = logger

    def _do_login(self):
        """ do_login """
        if self.url == None or self.user == None or self.pwd == None:
            self.logger.error("url or user or u_pwd can not None")
            return None
        if self.zapi is not None:
            return self.zapi
        try:
            zapi = ZabbixAPI(self.url)
            zapi.session.verify = False
            zapi.session.auth = (self.user, self.pwd)
            zapi.login(self.user, self.pwd)
            #self.cookies["zbx_sessionid"] = str(zapi.auth)
            self.cookies["zbx_session"] = "eyJzZXNzaW9uaWQiOiJhNzc5YjhlMWVkNWQwYjZiNDNkZjExMzRiYWVmZDE5YiIsInNlcnZlckNoZWNrUmVzdWx0Ijp0cnVlLCJzZXJ2ZXJDaGVja1RpbWUiOjE2MjI0NTc4MDgsInNpZ24iOiJrWDhDcXUrbVNYMGNVQk4wSjRadW5Oa3l4ZDlSXC9KVDhyalgyN21LYjliWUZHam51OGVlRzlqVHdJcXFtY2o2RHJWdTNqR1lKUGhwZGNXamhOTWZZWEpta2xtWnNQejNWdkhNNjRXd3pxSGlLajVwSWh4VVg4ZWp0ZU85K0tcL1krWm1uVDEyYnF0M1wvYldVajRPZkVIWmc9PSJ9"
            self.zapi = zapi
            logger.info('zbx api version:%s' % self.zapi.api_version())
            return zapi
        except Exception as e:
            self.logger.error("auth failed:\t%s " % (e))
            return None

    def _is_can_graph(self, itemid=None):
        self.zapi = self._do_login()
        if self.zapi is None:
            self.logger.error("zabbix login fail, self.zapi is None:")
            return False
        if itemid is not None:
            """
            0 - numeric float; 
            1 - character; 
            2 - log; 
            3 - numeric unsigned; 
            4 - text.
            """
            item_info = self.zapi.item.get(
                filter={"itemid": itemid}, output=["value_type"])
            if len(item_info) > 0:
                if item_info[0]["value_type"] in [u'0', u'3']:
                    return True
            else:
                self.logger.error("get itemid fail")
        return False

    def get_graph_in_binary(self, itemid=None):
        """ get_graph """
        if itemid == None:
            self.logger.error("itemid %s is None" % itemid)
            return "ERROR"

        if self._is_can_graph(itemid=itemid) is False or self.zapi is None:
            self.logger.error("itemid %s can't graph" % itemid)
            return "ERROR"

        if len(re.findall('4.0', self.zapi.api_version())) == 1:
            graph_url = "%s/chart.php?from=now-1h&to=now&itemids[]=%s&width=360&height=60" % (
                self.url, itemid)
        else:
            graph_url = "%s/chart.php?period=3600&itemids[]=%s&width=480&height=60" % (
                self.url, itemid)
        try:
            ses = self.zapi.session
            # rq = requests.get(graph_url, cookies=self.cookies,
            #                   timeout=self.timeout, stream=True, verify=False)
            rq = ses.get(graph_url, timeout=self.timeout, cookies=self.cookies, stream=True, verify=False)
            if rq.status_code == 200:
                # 直接返回二进制数据
                return rq.content
            rq.close()
        except Exception as e:
            self.logger.error(e)
            return False

    def download_graph(self, save_path, itemid=None, ):
        """ get_graph """
        save_path = os.path.join(save_path, datetime.datetime.today().strftime('%Y-%m-%d'))
        if not os.path.isdir(save_path):
            os.makedirs(save_path, mode=0755)
        if itemid == None:
            self.logger.error("itemid %s is None" % itemid)
            return False

        if self._is_can_graph(itemid=itemid) is False or self.zapi is None:
            self.logger.error("itemid %s can't graph" % itemid)
            return False

        if len(re.findall('4.0', self.zapi.api_version())) == 1:
            graph_url = "%s/chart.php?from=now-1h&to=now&itemids[]=%s" % (
                self.url, itemid)
        else:
            graph_url = "%s/chart.php?period=3600&itemids[]=%s" % (
                self.url, itemid)
        self.logger.info('graph_url: %s' % graph_url)
        try:
            ses = self.zapi.session
            # rq = requests.get(graph_url, cookies=self.cookies,
            #                   timeout=self.timeout, stream=True, verify=False)
            rq = ses.get(graph_url, timeout=self.timeout, cookies=self.cookies, stream=True, verify=False)
            if rq.status_code == 200 or rq.status_code == 500:
                imgpath = os.path.join(save_path,
                                       '%s-%s.png' % (itemid, hashlib.md5(str(time.time()) + itemid).hexdigest()))
                with open(imgpath, 'wb') as f:
                    for chunk in rq.iter_content(1024):
                        f.write(chunk)
                return imgpath
            else:
                self.logger.error('failed to download graph, graph_url: %s return content: %s' % (graph_url, rq.content))
                rq.status_code
            rq.close()
        except Exception as e:
            self.logger.error(e)
            return False

def get_token(app_id, app_secret):
    url = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/'
    body = {"app_id": app_id, "app_secret": app_secret}
    if os.path.isfile(TokenCacheFile):
        with open(TokenCacheFile, 'r') as f:
            d = json.loads(f.read())
            gen_ts = d['gen_ts']
        # 过期时间是7200s
        if time.time() - gen_ts < 7000:
            return d['tenant_access_token']
    resp = requests.post(url, data=body)
    data = resp.json()
    try:
        ret = data['tenant_access_token']
        with open(TokenCacheFile, 'w') as f:
            f.write(json.dumps({'tenant_access_token': ret, 'gen_ts': time.time()}))
        return ret
    except:
        raise Exception("Call Api Error, errorCode is %s" % data["code"])


def upload_image(image_path, tenant_access_token):
    """
    :param image_path:
    :param tenant_access_token:
    :return:
    """
    with open(image_path, 'rb') as f:
        image = f.read()
    resp = requests.post(
        url='https://open.feishu.cn/open-apis/image/v4/put/',
        headers={'Authorization': "Bearer %s" % tenant_access_token},
        files={
            "image": image
        },
        data={
            "image_type": "message"
        },
        stream=True)

    resp.raise_for_status()
    content = resp.json()
    print(content)
    if content.get("code") == 0:
        return content['data']['image_key']
    else:
        raise Exception("Call Api Error, errorCode is %s" % content["code"])


def _main():
    send_to = sys.argv[1]
    event = sys.argv[2]
    alert_message = sys.argv[3]
    #send_to = ''
    #event = 'cpu使用率超过99%'
    #alert_message = """level::average;host::XX-01 ;ip_addr::192.168.1.1;failed_time::11:20:10 ;item_id::39169;host_id::10385"""
    #alert_message = """level::average;host::XX-01 ;ip_addr::192.168.1.1;recovery_time::11:20:10;duration::21m;item_id::39169;host_id::10385"""

    """zbx传入alert_message格式:
        故障message:level::{TRIGGER.SEVERITY};host::{HOSTNAME1} ;ip_addr::{HOST.CONN};failed_time::{EVENT.TIME} ;item_id::{ITEM.ID};host_id::{HOST.ID}
        恢复message:host::{HOSTNAME1}; ip_addr::{HOST.CONN}; recovery_time::{EVENT.RECOVERY.TIME}; duration::{EVENT.AGE}; item_id::{ITEM.ID};host_id::{HOST.ID}
    """
    # 注意分隔符是 "::"
    alert_map = dict([tuple(tmp.split('::')) for tmp in alert_message.split(';')])
    itemid = alert_map.get('item_id', None)
    zbx = Zabbix_Graph(url=ZabbixUrl, user=ZabbixUser, pwd=ZabbixPass, logger=logger, timeout=3)
    if Debug:
        robot_url = DebugRobotApi
    else:
        robot_url = RobotApi
    pic_path = zbx.download_graph(save_path=ZabbixPicPath, itemid=itemid)
    tenant_access_token = get_token(app_id=AppId, app_secret=AppSecret)
    if not pic_path:
        logger.error('zbx download error')
	raise(Exception('zbx download error'))
    image_key = upload_image(pic_path, tenant_access_token)
    req_data_str = make_feishu_req_data(zbx_url=ZabbixUrl, event=event, alert_map=alert_map,
                                        zbx_image_key=image_key)
    req = requests.post(url=robot_url, data=req_data_str, headers={'Content-Type': 'application/json'})
    res = req.json()
    if 'StatusCode' in res.keys() and res['StatusCode'] == 0:
        logger.info("sendto: %s||title:%s||content:%s" % (send_to, event, alert_map))
    else:
        logger.error('api error:%s' % req.content)


_main()

[config]
#此文件注意权限
#log=/tmp/zabbix_dingding.log
log=zabbix_dingding.log
webhook=https://open.feishu.cn/open-apis/bot/v2/hook/d923f171-0b08-40df-8e2d-d9f11d12ee5d
#app_id=cli_a0b6d8418478d00e
#app_secret=ZjDIU8c1RKBZIITGdBGovdC2HsvGjWJS
app_id=cli_a0bca8e484b95014
app_secret=c5IMmAqXK0CSQDv9wTnq3bJ3Ats2wxri
token_cache_file=token.json
zabbix_url=http://192.168.102.72/zabbix/
zabbix_user=Admin
zabbix_pass=hrEtHJ9QkdwIHl2PDpqo
zabbix_pic_path=2019-10-31/
#配置nginx访问zabbix_pic_path目录(公网需能访问,钉钉才能正常显示)
# debug开启:1 关闭:0
debug_mode=0
debug_webhook=https://open.feishu.cn/open-apis/bot/v2/hook/741fbe82-ceb7-4336-8c04-69177ed2250c

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值