Setting Jira Priority and Labels with OpsGenie Edge Connector

Insert Movie Pun

The default OpsGenie integration with Jira Service Management has a puzzling omission when it comes to their OpsGenie Edge Connector integration - it doesn't send Priority or Tags. This means that tags won't pass through as labels, and the priority will be at the default.

My ideal is that the Jira priority maps directly to the OpsGenie priority, especially when SLAs are set against those priorities. Tags and labels power automation, reporting, and documentation in our environment - so passing them through is a must.

Now obviously Atlassian is pivoting to retire their server products and offer "cloud only" by 2024, while working to more closely integrate OpsGenie with their Jira Service Management offering, but for those still using an on-premise server and OpsGenie, this might be of help. 

I haven't had cause to do a lot with Python before, but I modified the actionExecutor.py to perform a query against the OpsGenie Alerts API before creating the Jira ticket. It retrieves the priority and tags, maps OpsGenie priorities to the standard Jira priorities (eg. P1 = Highest), and adds the tags as Jira labels.

I also add a static "OpsGenie" label, because I have some Jira Automation rules that work against that label to fire off webhooks back to OpsGenie. 

In our environment, "P5" priorities don't exist - so I map a P5 to Low rather than Lowest. That's relatively trivial to change in the script if you need to.

The amended script is below, or you can grab it from my Github fork.


import argparse
import json
import logging
import sys

import requests
from requests.auth import HTTPBasicAuth

parser = argparse.ArgumentParser()
parser.add_argument('-payload', '--queuePayload', help='Payload from queue', required=True)
parser.add_argument('-apiKey', '--apiKey', help='The apiKey of the integration', required=True)
parser.add_argument('-opsgenieUrl', '--opsgenieUrl', help='The url', required=True)
parser.add_argument('-logLevel', '--logLevel', help='Level of log', required=True)
parser.add_argument('-url', '--url', help='URL', required=False)
parser.add_argument('-username', '--username', help='Username', required=False)
parser.add_argument('-password', '--password', help='Password', required=False)
parser.add_argument('-key', '--key', help='Project key', required=False)
parser.add_argument('-issueTypeName', '--issueTypeName', help='Issue Type', required=False)
args = vars(parser.parse_args())

logging.basicConfig(stream=sys.stdout, level=args['logLevel'])


def parse_field(key, mandatory):
    variable = queue_message.get(key)
    if not variable:
        variable = args.get(key)
    if mandatory and not variable:
        logging.error(LOG_PREFIX + " Skipping action, Mandatory conf item '" + key +
                      "' is missing. Check your configuration file.")
        raise ValueError(LOG_PREFIX + " Skipping action, Mandatory conf item '" + key +
                         "' is missing. Check your configuration file.")
    return variable


def parse_timeout():
    parsed_timeout = args.get('http.timeout')
    if not parsed_timeout:
        return 30000
    return int(parsed_timeout)


def get_transition_id(request_headers, jira_url, transition_name, token):
    transition_id = str()
    response = requests.get(jira_url, None, headers=request_headers, auth=token, timeout=timeout)
    body = response.json()
    if body != {} and response.status_code < 299:
        transition_list = body["transitions"]
        for transition in transition_list:
            to = transition['to']
            if transition_name == to['name']:
                transition_id = transition['id']
        logging.info(LOG_PREFIX + " Successfully executed at Jira Service Desk")
        logging.debug(
            LOG_PREFIX + " Jira Service Desk response: " + str(response.status_code) + " " + str(response.content))
    else:
        logging.error(
            LOG_PREFIX + " Could not execute at Jira Service Desk; response: " + str(
                response.content) + " status code: " + str(response.status_code))
    if transition_id:
        return transition_id
    else:
        logging.debug(LOG_PREFIX + " Transition id is empty")

def get_alert(opsgenieUrl, api_Key, alert_id):
    headers = {
        "Content-Type": "application/json",
        "Accept-Language": "application/json",
        "Authorization": "GenieKey " + args.get('apiKey')
    }
    
    alert_api_url = opsgenieUrl + "/v2/alerts/" + alert_id + '?identifierType=id'
    alert_response = requests.get(alert_api_url, headers=headers, timeout=timeout)
    if alert_response.status_code < 299:
        logging.info(LOG_PREFIX + " Successfully requested alert from Opsgenie")
        logging.debug(
            LOG_PREFIX + "OpsGenie response: " + str(alert_response.content) + " " + str(
                alert_response.status_code))
        return alert_response.json()
    else:
        logging.warning(
            LOG_PREFIX + " Could not execute at Opsgenie; response: " + str(
                alert_response.content) + " status code: " + str(alert_response.status_code))


def get_tags(alert):
        return alert['data']['tags']

def get_priority(alert):
    priorities = {"P1":"Highest","P2":"High","P3":"Medium","P4":"Low","P5":"Low"}
    priorityMap = "{" + alert["data"]["priority"] + "}"
    logging.debug(priorityMap + ' result: ' + priorityMap.format_map(priorities))
    return priorityMap.format_map(priorities)

def main():
    global LOG_PREFIX
    global queue_message
    global timeout

    queue_message_string = args['queuePayload']
    queue_message = json.loads(queue_message_string)

    logging.debug(str(queue_message))

    alert_id = queue_message["alert"]["alertId"]
    mapped_action = queue_message["mappedActionV2"]["name"]

    LOG_PREFIX = "[" + mapped_action + "]"
    logging.info("Will execute " + mapped_action + " for alertId " + alert_id)

    timeout = parse_timeout()
    url = parse_field('url', True)
    username = parse_field('username', True)
    password = parse_field('password', True)
    project_key = parse_field('key', False)
    issue_type_name = parse_field('issueTypeName', False)

    issue_key = queue_message.get("IssueKey")

    logging.debug("Url: " + str(url))
    logging.debug("Username: " + str(username))
    logging.debug("Project Key: " + str(project_key))
    logging.debug("Issue Type: " + str(issue_type_name))
    logging.debug("Issue Key: " + str(issue_key))

    content_params = dict()

    token = HTTPBasicAuth(username, password)
    headers = {
        "Content-Type": "application/json",
        "Accept-Language": "application/json"
    }

    result_url = url + "/rest/api/2/issue"

    if mapped_action == "addComment":
        content_params = {
            "body": queue_message.get('body')
        }
        result_url += "/" + str(issue_key) + "/comment"
    elif mapped_action == "createIssue":
        toLabel = queue_message.get("alias")
        alert = get_alert(args.get('opsgenieUrl'), args.get('apiKey'), alert_id)
        priority = get_priority(alert)
        labels = get_tags(alert)
        labels.append(toLabel)
        labels.append("OpsGenie")
        content_params = {
            "fields": {
                "project": {
                    "key": project_key
                },
                "issuetype": {
                    "name": issue_type_name
                },
                "summary": queue_message.get("summary"),
                "description": queue_message.get("description"),
                "priority": { "name": priority },
                "labels": labels
            }
        }
    elif mapped_action == "resolveIssue":
        result_url += "/" + str(issue_key) + "/transitions"
        content_params = {
            "transition": {
                "id": get_transition_id(headers, result_url, "Resolved", token)
            },
            "fields": {
                "resolution": {
                    "name": "Done"
                }
            }
        }

    logging.debug(str(content_params))    
    response = requests.post(result_url, data=json.dumps(content_params), headers=headers, auth=token, timeout=timeout)
    if response.status_code < 299:
        logging.info("Successfully executed at Jira Service Desk")
        if mapped_action == "createIssue":
            if response.json():
                issue_key_from_response = response.json()['key']
                if issue_key_from_response:
                    alert_api_url = args.get('opsgenieUrl') + "/v2/alerts/" + alert_id + "/details"
                    content = {
                        "details":
                            {
                                "issueKey": issue_key_from_response
                            }
                    }
                    headers = {
                        "Content-Type": "application/json",
                        "Accept-Language": "application/json",
                        "Authorization": "GenieKey " + args.get('apiKey')
                    }
                    alert_response = requests.post(alert_api_url,
                                                   data=json.dumps(content), headers=headers, timeout=timeout)
                    if alert_response.status_code < 299:
                        logging.info(LOG_PREFIX + " Successfully sent to Opsgenie")
                        logging.debug(
                            LOG_PREFIX + " Jira Service Desk response: " + str(alert_response.content) + " " + str(
                                alert_response.status_code))
                    else:
                        logging.warning(
                            LOG_PREFIX + " Could not execute at Opsgenie; response: " + str(
                                alert_response.content) + " status code: " + str(alert_response.status_code))
            else:
                logging.warning(
                    LOG_PREFIX + " Jira Service Desk response is empty")
    else:
        logging.warning(
            LOG_PREFIX + " Could not execute at Jira Service Desk; response: " + str(
                response.content) + " status code: " + str(response.status_code))


if __name__ == '__main__':
    main()

Comments

You may also like:

Seq Reporter v1.0.3 - Who needs email when you can raise a Jira issue?

Reporting all the things Seq Reporter is the command-line client that can be used to schedule reporting from your Seq structured logs. It drives a number of daily and monthly reports for us and overall, it works well. We just set our query config and time range, schedule it, and...

Performing OpsGenie Heartbeats with Seq

When we investigated OpsGenie, one feature I was attracted to was Heartbeat Monitoring. This is a feature that can help to answer a fundamental problem - "How do you know if you have a major site or infrastructure outage?" There are plenty of ways that you could go about this,...

Passing Priority, Responder, and Tags from Seq to OpsGenie!

Building up the Seq app for OpsGenie Over the past few weeks, I've worked with Nicholas Blumhardt to enhance the Seq.App.OpsGenie application for Seq. Nicholas is the founder and CEO of Datalust, the company behind Seq, and is very active in the community - which is awesome, and has meant that...