require 'faraday' require 'faraday_middleware' require 'active_support' require 'active_support/core_ext' require 'active_support/time_with_zone' module PagerDuty class Connection attr_accessor :connection API_VERSION = 2 class FileNotFoundError < RuntimeError end class ApiError < RuntimeError end class RateLimitError < RuntimeError end class RaiseFileNotFoundOn404 < Faraday::Middleware def call(env) response = @app.call env if response.status == 404 raise FileNotFoundError, response.env[:url].to_s else response end end end class RaiseApiErrorOnNon200 < Faraday::Middleware def call(env) response = @app.call env unless [200, 201, 204].include?(response.status) url = response.env[:url].to_s message = "Got HTTP #{response['status']} back for #{url}" if error = response.body['error'] # TODO May Need to check error.errors too message += "\n#{error.to_hash}" end raise ApiError, message else response end end end class RaiseRateLimitOn429 < Faraday::Middleware def call(env) response = @app.call env if response.status == 429 raise RateLimitError, response.env[:url].to_s end response end end class ConvertTimesParametersToISO8601 < Faraday::Middleware TIME_KEYS = [:since, :until] def call(env) body = env[:body] TIME_KEYS.each do |key| if body.has_key?(key) body[key] = body[key].iso8601 if body[key].respond_to?(:iso8601) end end @app.call env end end class ParseTimeStrings < Faraday::Response::Middleware TIME_KEYS = %w( at created_at created_on end end_time last_incident_timestamp last_status_change_on start started_at start_time ) OBJECT_KEYS = %w( alert entry incident log_entry maintenance_window note override service ) NESTED_COLLECTION_KEYS = %w( acknowledgers assigned_to pending_actions ) def parse(body) case body when Hash, ::Hashie::Mash OBJECT_KEYS.each do |key| object = body[key] parse_object_times(object) if object collection_key = key.pluralize collection = body[collection_key] parse_collection_times(collection) if collection end body else raise "Can't parse times of #{body.class}: #{body}" end end def parse_collection_times(collection) collection.each do |object| parse_object_times(object) NESTED_COLLECTION_KEYS.each do |key| object_collection = object[key] parse_collection_times(object_collection) if object_collection end end end def parse_object_times(object) time = Time.zone ? Time.zone : Time TIME_KEYS.each do |key| if object.has_key?(key) && object[key].present? object[key] = time.parse(object[key]) end end end end def initialize(token, debug: false) @connection = Faraday.new do |conn| conn.url_prefix = "https://api.pagerduty.com/" # use token authentication: http://developer.pagerduty.com/documentation/rest/authentication conn.token_auth token conn.use RaiseApiErrorOnNon200 conn.use RaiseFileNotFoundOn404 conn.use RaiseRateLimitOn429 conn.use ConvertTimesParametersToISO8601 # use json conn.request :json conn.headers[:accept] = "application/vnd.pagerduty+json;version=#{API_VERSION}" # json back, mashify it conn.use ParseTimeStrings conn.response :mashify conn.response :json conn.response :logger, ::Logger.new(STDOUT), bodies: true if debug conn.adapter Faraday.default_adapter end end def get(path, request = {}) # paginate anything being 'get'ed, because the offset/limit isn't intuitive request[:query_params] = {} if !request[:query_params] page = (request[:query_params].delete(:page) || 1).to_i limit = (request[:query_params].delete(:limit) || 100).to_i offset = (page - 1) * limit request[:query_params] = request[:query_params].merge(offset: offset, limit: limit) run_request(:get, path, request) end def put(path, request = {}) run_request(:put, path, request) end def post(path, request = {}) run_request(:post, path, request) end def delete(path, request = {}) run_request(:delete, path, request) end private def run_request(method, path, body: {}, headers: {}, query_params: {}) path = path.gsub(/^\//, '') # strip leading slash, to make sure relative things happen on the connection connection.params = query_params response = connection.run_request(method, path, body, headers) response.body end end end