#!/usr/bin/env python

"""
#Demo webhook shims for Log Insight and vRealize Operations Manager

This is a demo shim, implemented using Flask, that accepts alert webhooks from:

1) VMware vRealize Log Insight 3.3 or newer

2) VMware vRealize Operations Manager 6.0 or newer (6.2 or newer recommended)

You can invoke `runserver.py [<port>]` directly on your development machine or run the Flask app under any WSGI webserver.
Don't run it on the Log Insight or vRealize Operations Manager virtual appliance, though.
Some shims require manually setting variables within the shim, while others work via URLs. Only modify variables BEFORE the app.route section in each shim.

As a demonstration, these shims are optimized for readability.
There is minimal error handling and no attempt to retransmit.
HTTP errors are passed back to the incoming system.
THESE SHIMS COME WITH NO SUPPORT - USE AT YOUR OWN RISK.
Please send feedback to https://github.com/vmw-loginsight/webhook-shims/issues or mailto:li-cord@vmware.com - pull requests welcome.

Known issues: 1) vRealize Operations Manager returns an error when testing a rest plugin to this shim though the test does work (cosmetic issue)

The following functions parse the webhook payload from the above products, translate and send it to other services.
Note that `<ALERTID>` is sent as part of vRealize Operations Manager webhooks and should not be specified when configuring incoming webhooks.
"""

from distutils.util import strtobool
from flask import Flask, Markup, request, json
import requests
import logging
import re
import base64


app = Flask(__name__)


__author__ = "Alan Castonguay and Steve Flanders"
__copyright__ = "Copyright 2016-2018"
__credits__ = ["Alan Castonguay", "Steve Flanders"]
__license__ = "Apache v2"
__maintainer__ = "Alan Castonguay and Steve Flanders"
__email__ = "li-cord@vmware.com"
__status__ = "Beta"
__version__ = "2.2"


def _minimal_markdown(markdown):
    """
    Given an html-safe Markdown object, replace some markdown with its html equivilant.
    This is the bare minimum necessary for example docstrings.
    For production use, leverage https://github.com/waylan/Python-Markdown instead.
    """
    assert(type(markdown) == Markup)
    s = str(markdown)
    s = re.sub(r'^#\s*(.*)\n*$', '<h1>\g<1></h1>', s, flags=re.MULTILINE)
    s = s.replace('\n\n', '<br><br>\n\n')
    s = re.sub(r'(\s)(https?://[^\s]+)(\s)', '\g<1><a href="\g<2>">\g<2></a>\g<3>', s, flags=re.IGNORECASE)
    s = re.sub(r'(\s)mailto:([^\s]+@[^\s]+)(\s)', '\g<1><a href="mailto:\g<2>">\g<2></a>\g<3>', s, flags=re.IGNORECASE)
    s = re.sub(r'`(.*?)`', '<code>\g<1></code>', s, flags=re.IGNORECASE)
    return Markup(s)


def parse(request):
    """
    Parse incoming JSON.
    Returns a dict or logs an exception.
    """
    try:
        payload = request.get_json()
        if (payload is None):
            logging.exception("Payload is empty, did you specify a Header in the request?")
            raise
        alert = {}
        alert = parseLI(payload, alert)
        alert = parsevROps(payload, alert)
    except:
        logging.info("Body=%s" % request.get_data())
        logging.exception("Unexpected payload, is it in proper JSON format?")
        raise
    logging.info("Parsed=%s" % alert)
    return alert


def parseLI(payload, alert):
    """
    Parse LI JSON from alert webhook.
    Returns a dict.
    """
    if (not 'AlertName' in payload):
        return alert
    alert.update({
        "hookName": "Log Insight",
        "color": "red",
        "AlertName": payload['AlertName']                   if ('AlertName' in payload) else "<None>",
        "info": payload['Info']                             if ('Info' in payload and payload['Info'] is not None) else "",
        "Messages": payload['messages']                     if ('messages' in payload) else "",
        "url": payload['Url']                               if ('Url' in payload and payload['Url'] is not None) else "",
        "editurl": payload['EditUrl']                       if ('EditUrl' in payload and payload['EditUrl'] is not None) else "",
        "HasMoreResults": str(payload['HasMoreResults'])    if 'HasMoreResults' in payload else False,
        # may be less than length of messages, if there's more events
        "NumHits": str(payload['NumHits'])                  if 'NumHits' in payload else False,
        "icon": "http://blogs.vmware.com/management/files/2015/04/li-logo.png",
    })
    if ('Info' in payload and payload['Info'] is not None):
        alert.update({"info": payload['Info']})
    elif ('messages' in payload and payload['messages'] is not None and len(payload['messages']) > 0):
        alert.update({"info": payload['messages'][0]['text']})
    else:
        alert.update({"info": ""})
    if ('Messages' in alert and not alert['Messages']): # If a test alert
        alert.update({
            "moreinfo": ("Hello from the webhook shim! This is a test webhook alert.\n\n") + \
                ("Alert Name: ") + alert['AlertName'] + \
                ("\nAlert Info: ") + alert['info'] + \
                ("\nAlert Details: ") + str(alert['Messages']),
        })
    else:
        alert.update({
            "moreinfo": ("Alert Name: ") + alert['AlertName'] + \
                ("\nAlert Info: ") + alert['info'] + \
                ("\nAlert Details: ") + str(alert['Messages']) + \
                ("\n\nYou can view this alert by clicking: %s" % alert['url'] if alert['url'] else "") + \
                ("\nYou can edit this alert by clicking: %s" % alert['editurl'] if alert['editurl'] else ""),
        })
    if alert['HasMoreResults']:
        alert.update({
            "fields": [
                { "name": 'HasMoreResults',     "content": alert['HasMoreResults'], },
                { "name": 'NumHits',            "content": alert['NumHits'], }
            ]
        })
    else:
        alert.update({"fields": []})
    return alert


def parsevROps(payload, alert):
    """
    Parse vROps JSON from alert webhook.
    Returns a dict.
    """
    if (not 'alertId' in payload):
        return alert
    alert.update({
        "hookName": "vRealize Operations Manager",
        "AlertName": payload['alertName']       if ('alertName' in payload and payload['alertName'] != "") else "<None>",
        "alertId": payload['alertId']           if ('alertId' in payload) else "",
        "info": payload['info']                 if ('info' in payload and payload['info'] is not None) else "",
        "criticality": payload['criticality']   if ('criticality' in payload) else "",
        "status": payload['status']             if ('status' in payload) else "",
        "type": payload['type']                 if ('type' in payload) else "",
        "subType": payload['subType']           if ('subType' in payload) else "",
        "Risk": payload['Risk']                 if ('Risk' in payload) else "<None>",
        "Efficiency": payload['Efficiency']     if ('Efficiency' in payload) else "<None>",
        "Health": payload['Health']             if ('Health' in payload) else "<None>",
        "resourceName": payload['resourceName'] if ('resourceName' in payload) else "",
        "resourceId": payload['resourceId']     if ('resourceId' in payload) else "",
        "adapterKind": payload['adapterKind']   if ('adapterKind' in payload) else "",
        "startDate": payload['startDate']       if ('startDate' in payload) else "",
        "updateDate": payload['updateDate']     if ('updateDate' in payload) else "",
        "icon": "http://blogs.vmware.com/management/files/2016/09/vrops-256.png",
        "Messages":"",
        "url":"",
        "editurl":"",
    })
    if (alert['status'] == "ACTIVE"):
        if (alert['criticality'] == "ALERT_CRITICALITY_LEVEL_CRITICAL" or
            alert['criticality'] == "ALERT_CRITICALITY_LEVEL_IMMEDIATE"):
                color = "red"
        elif (alert['criticality'] == "ALERT_CRITICALITY_LEVEL_WARNING"):
            color = "yellow"
        else:
            color = "gray"
    elif (alert['status'] != "ACTIVE" and alert['status'] != ""):
        if (alert['criticality'] == "ALERT_CRITICALITY_LEVEL_CRITICAL" or
            alert['criticality'] == "ALERT_CRITICALITY_LEVEL_IMMEDIATE" or
            alert['criticality'] == "ALERT_CRITICALITY_LEVEL_WARNING"):
                color = "green"
        else:
            color = "gray"
    else:
        color = "red"
    alert.update({
        "color": color,
        "fields": [
            { "name": 'Health',         "content": str(alert['Health']), },
            { "name": 'Risk',           "content": str(alert['Risk']), },
            { "name": 'Efficiency',     "content": str(alert['Efficiency']), },
            { "name": 'Resouce Name',   "content": alert['resourceName'], },
            { "name": 'Adapter Kind',   "content": alert['adapterKind'], },
            { "name": 'Type',           "content": alert['type'], },
            { "name": 'Sub Type',       "content": alert['subType'], },
        ]
    })
    if (alert['adapterKind'] == 'sample-adapter-type'): # If a test alert
        alert.update({
            "moreinfo": "Hello from the webhook shim! This is a test webhook alert.\n\n" + \
                    ("Alert Name: ") + alert['AlertName'] + \
                    ("\nAlert Info: ") + alert['info'] + \
                    ("\nAlert Details: ") + str(alert['fields']),
        })
    else:
        alert.update({
            "moreinfo": ("Alert Name: ") + alert['AlertName'] + \
                    ("\nAlert Info: ") + alert['info'] + \
                    ("\nAlert Details: ") + str(alert['fields']),
        })
    return alert


def callapi(url, method='post', payload=None, headers=None, auth=None, check=True):
    """
    Simple wrapper around `requests.post`, with excessive logging.
    Returns a Flask-friendly tuple on success or failure.
    Logs and re-raises any exceptions.
    """
    if not headers:
        headers = {'Content-type': 'application/json'}
    try:
        logging.info("URL=%s" % url)
        logging.info("Auth=%s" % str(auth))
        logging.info("Headers=%s" % headers)
        logging.info("Body=%s" % payload)
        logging.info("Check=%s" % check)
        if (auth is not None):
            r = requests.request(method, url, auth=auth, headers=headers, data=payload, verify=bool(strtobool(str(check))))
        else:
            r = requests.request(method, url, headers=headers, data=payload, verify=check)
        if r.status_code >= 200 and r.status_code < 300:
            if (payload is None):
                return r.text
            else:
                return ("OK", r.status_code, None)
    except:
        logging.exception("Can't create new payload. Check code and try again.")
        raise
    return ("%s" % r.text, r.status_code, None)


@app.route("/")
def _introduction():
    """This help text."""
    ret = _minimal_markdown(Markup("<p>%s</p>") % __doc__)
    ret += Markup("<dl>")

    for f in sorted(app.view_functions):
        if f != 'static':
            ret += Markup("<dt><b>%s()</b></dt>\n") % f
            for r in app.url_map.iter_rules():
                if r.endpoint == f:
                    ret += Markup(" <dd><code>%s</code></dd>\n") % str(r)
            ret += Markup(" <dd>%s</dd>\n") % _minimal_markdown(Markup.escape(str(app.view_functions[f].__doc__)))
    ret += Markup("</dl>")
    return ret


@app.route("/endpoint/test", methods=['POST'])
@app.route("/endpoint/test/<ALERTID>", methods=['POST'])
def test(ALERTID=None):
    """Log the auth header, request, and parsed moreinfo field. Respond with success. Don't send the payload anywhere."""
    try:
        logging.info(request.headers['Authorization'])
    except KeyError:
        pass
    if request.get_data():
        logging.info(request.get_data())
        a = parse(request)
        try:
            logging.info(a['moreinfo'])
        except KeyError:
            pass
    return "OK"


# Import individual shims
import loginsightwebhookdemo.bigpanda
import loginsightwebhookdemo.bugzilla
import loginsightwebhookdemo.groove
import loginsightwebhookdemo.hipchat
import loginsightwebhookdemo.jenkins
import loginsightwebhookdemo.jira
import loginsightwebhookdemo.kafkatopic
import loginsightwebhookdemo.opsgenie
import loginsightwebhookdemo.pagerduty
import loginsightwebhookdemo.pivotaltracker
import loginsightwebhookdemo.pushbullet
import loginsightwebhookdemo.servicenow
import loginsightwebhookdemo.slack
import loginsightwebhookdemo.socialcast
#import loginsightwebhookdemo.template
import loginsightwebhookdemo.travisci
import loginsightwebhookdemo.vrealizeorchestrator
import loginsightwebhookdemo.zendesk
import loginsightwebhookdemo.moogsoft
import loginsightwebhookdemo.msteams