# frozen_string_literal: true require "time" module Pagerduty # Trigger incidents via the PagerDuty Events API version 2. # # @see https://v2.developer.pagerduty.com/docs/events-api-v2 PagerDuty Events # API V2 documentation # # @see Pagerduty.build # # @see Pagerduty::EventsApiV2::Incident # class EventsApiV2 # Rather than using this directly, use the {Pagerduty.build} method to # construct an instance. # # @option config [String] integration_key Authentication key for connecting # to PagerDuty. A UUID expressed as a 32-digit hexadecimal number. # Integration keys are generated by creating a new service, or creating a # new integration for an existing service in PagerDuty, and can be found # on a service's Integrations tab. This option is required. # # @option config [String] http_proxy.host The DNS name or IP address of the # proxy host. If nil or unprovided an HTTP proxy will not be used. # # @option config [String] http_proxy.port The TCP port to use to access the # proxy. # # @option config [String] http_proxy.username username if authorization is # required to use the proxy. # # @option config [String] http_proxy.password password if authorization is # required to use the proxy. # # @see Pagerduty.build # def initialize(config = {}) @config = config end # Send PagerDuty a trigger event to report a new or ongoing problem. When # PagerDuty receives a trigger event, it will either open a new incident, or # add a new trigger log entry to an existing incident, depending on the # incident key. # # @example Trigger an incident, providing only required details # incident = pagerduty.trigger( # summary: "summary", # source: "source", # severity: "critical" # ) # # @example Trigger an incident providing full context # incident = pagerduty.trigger( # summary: "Example alert on host1.example.com", # source: "monitoringtool:host1.example.com/prod-003", # severity: %w[critical error warning info].sample, # timestamp: Time.now, # component: "postgres", # group: "prod-datapipe", # class: "deploy", # custom_details: { # ping_time: "1500ms", # load_avg: 0.75 # }, # images: [ # { # src: "https://chart.googleapis.com/chart.png", # href: "https://example.com/", # alt: "Example text", # }, # ], # links: [ # { # href: "https://example.com/", # text: "Link text", # }, # ], # client: "Sample Monitoring Service", # client_url: "https://monitoring.example.com" # ) # # @option details [String] summary A brief text summary of the event, # used to generate the summaries/titles of any associated alerts. # The maximum permitted length of this property is 1024 characters. # # @option details [String] source The unique location of the affected # system, preferably a hostname or FQDN. # # @option details [String] severity The perceived severity of the status # the event is describing with respect to the affected system. This can # be "critical", "error", "warning" or "info". # # @option details [Time] timestamp The time at which the emitting tool # detected or generated the event. # # @option details [String] component Component of the source machine # that is responsible for the event, for example "mysql" or "eth0". # # @option details [String] group Logical grouping of components of a # service, for example "app-stack". # # @option details [String] class The class/type of the event, for # example "ping failure" or "cpu load". # # @option details [Hash] custom_details Additional details about the # event and affected system # # @option details [Array] images List of images to include. # # @option details [Array] links List of links to include. # # @return [Pagerduty::EventsApiV2::Incident] The triggered incident. # # @raise [PagerdutyException] If PagerDuty responds with a status that is # not "success" # # @raise [ArgumentError] If details hash is nil # def trigger(details) Incident.new(@config).trigger(details) end # @param [String] incident_key The unique identifier for the incident. # # @return [Pagerduty::EventsApiV2::Incident] The incident referenced by the # provided key. # # @raise [ArgumentError] If incident_key is nil # def incident(incident_key) raise ArgumentError, "incident_key is nil" if incident_key.nil? Incident.new(@config.merge(incident_key: incident_key)) end class Incident attr_reader :incident_key # @option (see Pagerduty::EventsApiV1#initialize) # # @option config [String] incident_key Identifies the incident to which # this trigger event should be applied. If there's no open # (i.e. unresolved) incident with this key, a new one will be created. # If there's already an open incident with a matching key, this event # will be appended to that incident's log. The event key provides an # easy way to "de-dup" problem reports. If this field isn't provided, # PagerDuty will automatically open a new incident with a unique key. # The maximum length is 255 characters. # def initialize(config = {}) @integration_key = config.fetch(:integration_key) do raise ArgumentError "integration_key not provided" end @incident_key = config[:incident_key] @transport = Pagerduty::HttpTransport.new( path: "/v2/enqueue", proxy: config[:http_proxy], ) end # Send PagerDuty a trigger event to report a new or ongoing problem. When # PagerDuty receives a trigger event, it will either open a new incident, # or add a new trigger log entry to an existing incident, depending on # the incident key. # # @example Trigger an incident, providing only required details # incident = pagerduty.trigger( # summary: "summary", # source: "source", # severity: "critical" # ) # # @example Trigger an incident providing full context # incident = pagerduty.trigger( # summary: "Example alert on host1.example.com", # source: "monitoringtool:host1.example.com/prod-003", # severity: %w[critical error warning info].sample, # timestamp: Time.now, # component: "postgres", # group: "prod-datapipe", # class: "deploy", # custom_details: { # ping_time: "1500ms", # load_avg: 0.75 # }, # images: [ # { # src: "https://chart.googleapis.com/chart.png", # href: "https://example.com/", # alt: "Example text", # }, # ], # links: [ # { # href: "https://example.com/", # text: "Link text", # }, # ], # client: "Sample Monitoring Service", # client_url: "https://monitoring.example.com" # ) # # @param (see Pagerduty::EventsApiV2#trigger) # @option (see Pagerduty::EventsApiV2#trigger) def trigger(details) if details.key?(:dedup_key) || details.key?(:incident_key) raise ArgumentError, "incident_key or dedup_key provided, "\ "please use the EventsApiv2::incident method "\ "to specify an incident key" end response = api_call("trigger", trigger_request(details)) @incident_key = response["dedup_key"] self end # Acknowledge the referenced incident. While an incident is acknowledged, # it won't generate any additional notifications, even if it receives new # trigger events. Send PagerDuty an acknowledge event when you know # someone is presently working on the problem. # # @return [Pagerduty::EventsApiV2::Incident] self # # @raise [PagerdutyException] If PagerDuty responds with a status that is # not "success" # def acknowledge api_call("acknowledge") self end # Resolve the referenced incident. Once an incident is resolved, it won't # generate any additional notifications. New trigger events with the same # incident_key as a resolved incident won't re-open the incident. Instead, # a new incident will be created. Send PagerDuty a resolve event when the # problem that caused the initial trigger event has been fixed. # # @return [Pagerduty::EventsApiV2::Incident] self # # @raise [PagerdutyException] If PagerDuty responds with a status that is # not "success" # def resolve api_call("resolve") self end private PAYLOAD_ATTR = %i[summary timestamp source severity component group class custom_details].freeze private_constant :PAYLOAD_ATTR def trigger_request(details) payload = details.select { |key| PAYLOAD_ATTR.include?(key) } payload[:timestamp] &&= payload[:timestamp].iso8601 request = details.merge(payload: payload) request.reject! { |key| PAYLOAD_ATTR.include?(key) } request end def api_call(event_action, payload = {}) payload = payload.merge( dedup_key: incident_key, routing_key: @integration_key, event_action: event_action, ) response = @transport.send_payload(payload) unless response["status"] == "success" raise PagerdutyException.new(self, response, response["message"]) end response end end end end