Skip to content

Instantly share code, notes, and snippets.

@baskarp
Created November 2, 2012 08:28
Show Gist options
  • Save baskarp/3999485 to your computer and use it in GitHub Desktop.
Save baskarp/3999485 to your computer and use it in GitHub Desktop.
Ruby script to mirror an email incident as an API incident in PagerDuty
#!/usr/bin/env ruby
# Ruby script to mirror an email incident as an API incident
#
# Copyright (c) 2011, PagerDuty, Inc. <info@pagerduty.com>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of PagerDuty Inc nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL PAGERDUTY INC BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Tested with Ruby 1.9.3
# Requires rubygems (Ruby 1.9.3 comes with Rubygems)
# Just run the following from a terminal to install the necessary gems
#
# sudo gem install json
# sudo gem install faraday
# sudo gem install mail
# sudo gem install nokogiri
#
# Once this script is installed (i.e. in ~/user/mirror_incident.rb)
# Then edit the user's ctron tab to run this script every minute.
#
# crontab -u <user-name> -e
#
# Add the following line to the crontab:
# * * * * * ~/user/mirror_incident.rb
#
require 'rubygems'
require 'faraday'
require 'json'
require 'mail'
require 'nokogiri'
EVENTS_URL = "https://events.pagerduty.com"
EVENTS_PATH = "/generic/2010-04-15/create_event.json"
# Unfortunately HTTP Basic Authentication is not supported by PagerDuty, thus
# we need to use cookie based authentication
class PagerDutyAgent
attr_reader :email
attr_reader :connection
attr_reader :cookies
def initialize(subdomain, email, password)
@connection = Faraday.new(:url => "https://#{subdomain}.pagerduty.com",
:ssl => {:verify => false}) do |c|
c.request :url_encoded
# c.response :logger
c.adapter :net_http
end
@cookies = login(email, password)
end
def login(email, password)
response = connection.get("/sign_in")
authenticity_token = rails_auth_token(response.body)
cookies = response.headers['Set-Cookie']
# Retrieve and store a cookie
params = {
"user[email]" => email,
"user[password]" => password,
"user[remember_me]" => 1,
"authenticity_token" => authenticity_token
}
response = connection.post("/sign_in", params, {'Cookie' => cookies})
response.headers['Set-Cookie']
end
def rails_auth_token(body)
doc = Nokogiri::HTML::Document.parse(body)
doc.css("form#login_form input[name='authenticity_token']").attribute("value")
end
def get_incident_id(incident_number)
response = connection.get("/api/beta/incidents") do |request|
request.params[:status] = "triggered,acknowledged"
end
doc = JSON.parse(response.body)
# TODO support for pagination
incident = doc["incidents"].find do |incident|
incident["incident_number"] == incident_number
end
incident["id"] if incident
end
def sumbit_event(service_key, event_type, description,
incident_key = nil, details = {})
event = {
:service_key => service_key,
:event_type => event_type,
:description => description,
:details => details
}
event[:incident_key] = incident_key if incident_key
events_api_connection = Faraday.new(:url => EVENTS_URL,
:ssl => {:verify => false}) do |c|
# c.response :logger
c.adapter :net_http
end
events_api_connection.post(EVENTS_PATH, JSON.generate(event))
end
def trigger_event(service_key, description, incident_key = nil, details = {})
sumbit_event(service_key, "trigger", description, incident_key, details)
end
def ack_event(service_key, description, incident_key = nil, details = {})
sumbit_event(service_key, "acknowledged", description, incident_key, details)
end
def resolve_event(service_key, description, incident_key = nil, details = {})
sumbit_event(service_key, "resolve", description, incident_key, details)
end
def get(path, query = {})
response = connection.get(path, query, {'Cookie' => cookies}).body
end
def open_incidents(service_id)
json_body = get("/api/beta/incidents", {"service" => service_id,
"status" => "triggered"})
JSON.parse(json_body)["incidents"]
end
def get_service_key(service_id)
JSON.parse(get("/api/v1/services/#{service_id}"))["service"]["service_key"]
end
def get_raw_ile(incident_id, ile_id)
raw_ile_path = "/incidents/#{incident_id}/log_entries/#{ile_id}/show_raw_details"
get(raw_ile_path)
end
def change_status(incident_id, status)
body = JSON.generate({
:incidents => [{:id => incident_id, :status => status}]})
response = connection.put("/api/v1/incidents", body,
{'Cookie' => cookies, 'Content-Type' => 'application/json'})
doc = JSON.parse(response.body)
incident = doc["incidents"].find do |incident|
incident["id"] == incident_id
end
return response.status == 200
end
def ack(incident_id)
change_status(incident_id, "acknowledged")
end
def resolve(incident_id)
change_status(incident_id, "resolved")
end
end
class IncidentMirror
attr_reader :agent
attr_reader :original_service_id
attr_reader :new_service_id
def initialize(pagerduty_agent, original_service_id, new_service_id)
@agent = pagerduty_agent
@original_service_id = original_service_id
@new_service_id = new_service_id
end
def mirror
puts "Mirroring open incidents from service: #{original_service_id}"
open_incidents = agent.open_incidents(original_service_id)
puts "Found #{open_incidents.size} open incidents"
open_incidents.each do |incident|
incident_id = incident["id"]
trigger_details = incident["trigger_details"]
ile_id = trigger_details["id"]
if trigger_details["type"] == "email_trigger"
puts "Mirroring incident #{incident_id}"
raw_email = agent.get_raw_ile(incident_id, ile_id)
open_new_incident_with(incident_id, raw_email)
end
end
end
def open_new_incident_with(original_incident_id, email_text)
mail = Mail.new(email_text)
description = description_out_of_email(mail.body)
service_key = agent.get_service_key(new_service_id)
# Open a new incident in the API service using the description from the email
# incident
agent.trigger_event(service_key, description, original_incident_id)
# Resolve the original incident. However, you don't have to, since PagerDuty
# can also be configured to resolve incidents automatically.
agent.resolve(original_incident_id)
end
# Compose a description using the body of the email. Feel free to modify
# this method, if you know the structure of the email body
#
# mail - The String representation of the mail body
#
# Returns a description that can be used to create an API incident
def description_out_of_email(email)
# Replace all new line characters with space
description = email.to_s.strip.gsub(/\n|\r/, ' ')
# Truncate if the description is over 1024 chars (PagerDuty API Limit)
description = description[0..1024] if description.length > 1024
description
end
end
# Fill in your account subdomain and user credentials here.
# If you don't want to share your password with anyone, we recommend that you
# create an API user with a shared password.
# Your subdomain is: http://<subdomain>.pagerduty.com
#
# Unfortunately we can't use the API tokens here as not all of PagerDuty is
# accessible via the tokens yet.
CREDENTIALS = {
:subdomain => "YOUR_SUBDOMAIN",
:email => "YOUR_EMAIL_ADDRESS",
:password => "PASSWORD"
}
# The service id can be obtained from here
# https://<subdomain>.pagerduty.com/services/<serivce-id>
ORIGINAL_SERVICE_ID = "PK2PZMF" # This is the Email Service that collects the original email
NEW_SERVICE_ID = "PVME5TH" # This is the API Service that new incident would be created on
agent = PagerDutyAgent.new(CREDENTIALS[:subdomain],
CREDENTIALS[:email],
CREDENTIALS[:password])
im = IncidentMirror.new(agent, ORIGINAL_SERVICE_ID, NEW_SERVICE_ID)
im.mirror
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment