pagerduty 2.1.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,280 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module Pagerduty
6
+ # Trigger incidents via the PagerDuty Events API version 2.
7
+ #
8
+ # @see https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview
9
+ # PagerDuty Events API V2 documentation
10
+ #
11
+ # @see Pagerduty.build
12
+ #
13
+ # @see Pagerduty::EventsApiV2::Incident
14
+ #
15
+ class EventsApiV2
16
+ # Rather than using this directly, use the {Pagerduty.build} method to
17
+ # construct an instance.
18
+ #
19
+ # @option config [String] integration_key Authentication key for connecting
20
+ # to PagerDuty. A UUID expressed as a 32-digit hexadecimal number.
21
+ # Integration keys are generated by creating a new service, or creating a
22
+ # new integration for an existing service in PagerDuty, and can be found
23
+ # on a service's Integrations tab. This option is required.
24
+ #
25
+ # @option config [String] http_proxy.host The DNS name or IP address of the
26
+ # proxy host. If nil or unprovided an HTTP proxy will not be used.
27
+ #
28
+ # @option config [String] http_proxy.port The TCP port to use to access the
29
+ # proxy.
30
+ #
31
+ # @option config [String] http_proxy.username username if authorization is
32
+ # required to use the proxy.
33
+ #
34
+ # @option config [String] http_proxy.password password if authorization is
35
+ # required to use the proxy.
36
+ #
37
+ # @see Pagerduty.build
38
+ #
39
+ def initialize(config = {})
40
+ @config = config
41
+ end
42
+
43
+ # Send PagerDuty a trigger event to report a new or ongoing problem. When
44
+ # PagerDuty receives a trigger event, it will either open a new incident, or
45
+ # add a new trigger log entry to an existing incident, depending on the
46
+ # incident key.
47
+ #
48
+ # @example Trigger an incident, providing only required details
49
+ # incident = pagerduty.trigger(
50
+ # summary: "summary",
51
+ # source: "source",
52
+ # severity: "critical"
53
+ # )
54
+ #
55
+ # @example Trigger an incident providing full context
56
+ # incident = pagerduty.trigger(
57
+ # summary: "Example alert on host1.example.com",
58
+ # source: "monitoringtool:host1.example.com/prod-003",
59
+ # severity: %w[critical error warning info].sample,
60
+ # timestamp: Time.now,
61
+ # component: "postgres",
62
+ # group: "prod-datapipe",
63
+ # class: "deploy",
64
+ # custom_details: {
65
+ # ping_time: "1500ms",
66
+ # load_avg: 0.75
67
+ # },
68
+ # images: [
69
+ # {
70
+ # src: "https://chart.googleapis.com/chart.png",
71
+ # href: "https://example.com/",
72
+ # alt: "Example text",
73
+ # },
74
+ # ],
75
+ # links: [
76
+ # {
77
+ # href: "https://example.com/",
78
+ # text: "Link text",
79
+ # },
80
+ # ],
81
+ # client: "Sample Monitoring Service",
82
+ # client_url: "https://monitoring.example.com"
83
+ # )
84
+ #
85
+ # @option details [String] summary A brief text summary of the event,
86
+ # used to generate the summaries/titles of any associated alerts.
87
+ # The maximum permitted length of this property is 1024 characters.
88
+ #
89
+ # @option details [String] source The unique location of the affected
90
+ # system, preferably a hostname or FQDN.
91
+ #
92
+ # @option details [String] severity The perceived severity of the status
93
+ # the event is describing with respect to the affected system. This can
94
+ # be "critical", "error", "warning" or "info".
95
+ #
96
+ # @option details [Time] timestamp The time at which the emitting tool
97
+ # detected or generated the event.
98
+ #
99
+ # @option details [String] component Component of the source machine
100
+ # that is responsible for the event, for example "mysql" or "eth0".
101
+ #
102
+ # @option details [String] group Logical grouping of components of a
103
+ # service, for example "app-stack".
104
+ #
105
+ # @option details [String] class The class/type of the event, for
106
+ # example "ping failure" or "cpu load".
107
+ #
108
+ # @option details [Hash] custom_details Additional details about the
109
+ # event and affected system
110
+ #
111
+ # @option details [Array] images List of images to include.
112
+ #
113
+ # @option details [Array] links List of links to include.
114
+ #
115
+ # @return [Pagerduty::EventsApiV2::Incident] The triggered incident.
116
+ #
117
+ # @raise [PagerdutyException] If PagerDuty responds with a status that is
118
+ # not "success"
119
+ #
120
+ # @raise [ArgumentError] If details hash is nil
121
+ #
122
+ def trigger(details)
123
+ Incident.new(@config).trigger(details)
124
+ end
125
+
126
+ # @param [String] incident_key The unique identifier for the incident.
127
+ #
128
+ # @return [Pagerduty::EventsApiV2::Incident] The incident referenced by the
129
+ # provided key.
130
+ #
131
+ # @raise [ArgumentError] If incident_key is nil
132
+ #
133
+ def incident(incident_key)
134
+ raise ArgumentError, "incident_key is nil" if incident_key.nil?
135
+
136
+ Incident.new(@config.merge(incident_key: incident_key))
137
+ end
138
+
139
+ class Incident
140
+ attr_reader :incident_key
141
+
142
+ # @option (see Pagerduty::EventsApiV1#initialize)
143
+ #
144
+ # @option config [String] incident_key Identifies the incident to which
145
+ # this trigger event should be applied. If there's no open
146
+ # (i.e. unresolved) incident with this key, a new one will be created.
147
+ # If there's already an open incident with a matching key, this event
148
+ # will be appended to that incident's log. The event key provides an
149
+ # easy way to "de-dup" problem reports. If this field isn't provided,
150
+ # PagerDuty will automatically open a new incident with a unique key.
151
+ # The maximum length is 255 characters.
152
+ #
153
+ def initialize(config = {})
154
+ @integration_key = config.fetch(:integration_key) do
155
+ raise ArgumentError "integration_key not provided"
156
+ end
157
+ @incident_key = config[:incident_key]
158
+ @transport = Pagerduty::HttpTransport.new(
159
+ path: "/v2/enqueue",
160
+ proxy: config[:http_proxy],
161
+ )
162
+ end
163
+
164
+ # Send PagerDuty a trigger event to report a new or ongoing problem. When
165
+ # PagerDuty receives a trigger event, it will either open a new incident,
166
+ # or add a new trigger log entry to an existing incident, depending on
167
+ # the incident key.
168
+ #
169
+ # @example Trigger an incident, providing only required details
170
+ # incident = pagerduty.trigger(
171
+ # summary: "summary",
172
+ # source: "source",
173
+ # severity: "critical"
174
+ # )
175
+ #
176
+ # @example Trigger an incident providing full context
177
+ # incident = pagerduty.trigger(
178
+ # summary: "Example alert on host1.example.com",
179
+ # source: "monitoringtool:host1.example.com/prod-003",
180
+ # severity: %w[critical error warning info].sample,
181
+ # timestamp: Time.now,
182
+ # component: "postgres",
183
+ # group: "prod-datapipe",
184
+ # class: "deploy",
185
+ # custom_details: {
186
+ # ping_time: "1500ms",
187
+ # load_avg: 0.75
188
+ # },
189
+ # images: [
190
+ # {
191
+ # src: "https://chart.googleapis.com/chart.png",
192
+ # href: "https://example.com/",
193
+ # alt: "Example text",
194
+ # },
195
+ # ],
196
+ # links: [
197
+ # {
198
+ # href: "https://example.com/",
199
+ # text: "Link text",
200
+ # },
201
+ # ],
202
+ # client: "Sample Monitoring Service",
203
+ # client_url: "https://monitoring.example.com"
204
+ # )
205
+ #
206
+ # @param (see Pagerduty::EventsApiV2#trigger)
207
+ # @option (see Pagerduty::EventsApiV2#trigger)
208
+ def trigger(details)
209
+ if details.key?(:dedup_key) || details.key?(:incident_key)
210
+ raise ArgumentError, "incident_key or dedup_key provided, "\
211
+ "please use the EventsApiv2::incident method "\
212
+ "to specify an incident key"
213
+ end
214
+
215
+ response = api_call("trigger", trigger_request(details))
216
+ @incident_key = response["dedup_key"]
217
+ self
218
+ end
219
+
220
+ # Acknowledge the referenced incident. While an incident is acknowledged,
221
+ # it won't generate any additional notifications, even if it receives new
222
+ # trigger events. Send PagerDuty an acknowledge event when you know
223
+ # someone is presently working on the problem.
224
+ #
225
+ # @return [Pagerduty::EventsApiV2::Incident] self
226
+ #
227
+ # @raise [PagerdutyException] If PagerDuty responds with a status that is
228
+ # not "success"
229
+ #
230
+ def acknowledge
231
+ api_call("acknowledge")
232
+ self
233
+ end
234
+
235
+ # Resolve the referenced incident. Once an incident is resolved, it won't
236
+ # generate any additional notifications. New trigger events with the same
237
+ # incident_key as a resolved incident won't re-open the incident. Instead,
238
+ # a new incident will be created. Send PagerDuty a resolve event when the
239
+ # problem that caused the initial trigger event has been fixed.
240
+ #
241
+ # @return [Pagerduty::EventsApiV2::Incident] self
242
+ #
243
+ # @raise [PagerdutyException] If PagerDuty responds with a status that is
244
+ # not "success"
245
+ #
246
+ def resolve
247
+ api_call("resolve")
248
+ self
249
+ end
250
+
251
+ private
252
+
253
+ PAYLOAD_ATTR = %i[summary timestamp source severity
254
+ component group class custom_details].freeze
255
+ private_constant :PAYLOAD_ATTR
256
+
257
+ def trigger_request(details)
258
+ payload = details.select { |key| PAYLOAD_ATTR.include?(key) }
259
+ payload[:timestamp] &&= payload[:timestamp].iso8601
260
+ request = details.merge(payload: payload)
261
+ request.reject! { |key| PAYLOAD_ATTR.include?(key) }
262
+ request
263
+ end
264
+
265
+ def api_call(event_action, payload = {})
266
+ payload = payload.merge(
267
+ dedup_key: incident_key,
268
+ routing_key: @integration_key,
269
+ event_action: event_action,
270
+ )
271
+ response = @transport.send_payload(payload)
272
+ unless response["status"] == "success"
273
+ raise PagerdutyException.new(self, response, response["message"])
274
+ end
275
+
276
+ response
277
+ end
278
+ end
279
+ end
280
+ end
@@ -1,28 +1,30 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require "json"
3
4
  require "net/https"
4
5
 
5
- class Pagerduty
6
- # @api private
6
+ module Pagerduty
7
+ # @private
7
8
  class HttpTransport
8
- HOST = "events.pagerduty.com".freeze
9
+ HOST = "events.pagerduty.com"
9
10
  PORT = 443
10
- PATH = "/generic/2010-04-15/create_event.json".freeze
11
+ private_constant :HOST, :PORT
11
12
 
12
- def initialize(options = {})
13
- @options = options
13
+ def initialize(config)
14
+ @path = config.fetch(:path)
15
+ @proxy = config[:proxy] || {}
14
16
  end
15
17
 
16
- def send_payload(payload = {})
17
- response = post payload.to_json
18
+ def send_payload(payload)
19
+ response = post(payload.to_json)
18
20
  response.error! unless transported?(response)
19
21
  JSON.parse(response.body)
20
22
  end
21
23
 
22
- private
24
+ private
23
25
 
24
26
  def post(payload)
25
- post = Net::HTTP::Post.new(PATH)
27
+ post = Net::HTTP::Post.new(@path)
26
28
  post.body = payload
27
29
  http.request(post)
28
30
  end
@@ -38,10 +40,10 @@ class Pagerduty
38
40
 
39
41
  def http_proxy
40
42
  Net::HTTP.Proxy(
41
- @options[:proxy_host],
42
- @options[:proxy_port],
43
- @options[:proxy_username],
44
- @options[:proxy_password],
43
+ @proxy[:host],
44
+ @proxy[:port],
45
+ @proxy[:username],
46
+ @proxy[:password],
45
47
  )
46
48
  end
47
49
 
@@ -1,3 +1,5 @@
1
- class Pagerduty
2
- VERSION = "2.1.1".freeze
1
+ # frozen_string_literal: true
2
+
3
+ module Pagerduty
4
+ VERSION = "4.0.0"
3
5
  end
data/lib/pagerduty.rb CHANGED
@@ -1,5 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "pagerduty/version"
2
4
  require "pagerduty/http_transport"
5
+ require "pagerduty/events_api_v1"
6
+ require "pagerduty/events_api_v2"
3
7
 
4
8
  class PagerdutyException < StandardError
5
9
  attr_reader :pagerduty_instance, :api_response
@@ -11,178 +15,73 @@ class PagerdutyException < StandardError
11
15
  end
12
16
  end
13
17
 
14
- class Pagerduty
15
- attr_reader :service_key
16
-
17
- # @param [String] service_key The GUID of one of your "Generic API" services.
18
- # This is the "service key" listed on a Generic API's service detail page.
19
- #
20
- # @option options [String] :proxy_host The DNS name or IP address of the
21
- # proxy host. If nil or unprovided a proxy will not be used.
22
- #
23
- # @option options [String] :proxy_port The port to use to access the proxy.
24
- #
25
- # @option options [String] :proxy_username username if authorization is
18
+ module Pagerduty
19
+ # Build an instance that will send API calls to the specified Pagerduty
20
+ # Events API version.
21
+ #
22
+ # @example Build an instance for the Events API version 1
23
+ # pagerduty = Pagerduty.build(
24
+ # integration_key: "<integration-key>",
25
+ # api_version: 1,
26
+ # )
27
+ #
28
+ # @example Build an instance using an HTTP proxy for API requests
29
+ # pagerduty = Pagerduty.build(
30
+ # integration_key: "<integration-key>",
31
+ # api_version: 1,
32
+ # http_proxy: {
33
+ # host: "my.http.proxy.local",
34
+ # port: 3128,
35
+ # username: "<my-proxy-username>",
36
+ # password: "<my-proxy-password>",
37
+ # }
38
+ # )
39
+ #
40
+ # @option config [String] integration_key Authentication key for connecting
41
+ # to PagerDuty. A UUID expressed as a 32-digit hexadecimal number.
42
+ # Integration keys are generated by creating a new service, or creating a
43
+ # new integration for an existing service in PagerDuty, and can be found on
44
+ # a service's Integrations tab. This option is required.
45
+ #
46
+ # @option config [String] api_version The version of the Pagerduty events API.
47
+ # The gem currently supports version 1 (`1`). This option is required.
48
+ #
49
+ # @option config [String] http_proxy.host The DNS name or IP address of the
50
+ # proxy host. If nil or unprovided an HTTP proxy will not be used.
51
+ #
52
+ # @option config [String] http_proxy.port The TCP port to use to access the
53
+ # proxy.
54
+ #
55
+ # @option config [String] http_proxy.username username if authorization is
26
56
  # required to use the proxy.
27
57
  #
28
- # @option options [String] :proxy_password password if authorization is
58
+ # @option config [String] http_proxy.password password if authorization is
29
59
  # required to use the proxy.
30
60
  #
31
- def initialize(service_key, options = {})
32
- @service_key = service_key
33
- @transport = transport_from_options(options)
34
- end
35
-
36
- # Send PagerDuty a trigger event to report a new or ongoing problem. When
37
- # PagerDuty receives a trigger event, it will either open a new incident, or
38
- # add a new trigger log entry to an existing incident, depending on the
39
- # provided incident_key.
40
- #
41
- # @param [String] description A short description of the problem that led to
42
- # this trigger. This field (or a truncated version) will be used when
43
- # generating phone calls, SMS messages and alert emails. It will also appear
44
- # on the incidents tables in the PagerDuty UI. The maximum length is 1024
45
- # characters.
46
- #
47
- # @option options [String] :incident_key Identifies the incident to which
48
- # this trigger event should be applied. If there's no open (i.e. unresolved)
49
- # incident with this key, a new one will be created. If there's already an
50
- # open incident with a matching key, this event will be appended to that
51
- # incident's log. The event key provides an easy way to "de-dup" problem
52
- # reports. If this field isn't provided, PagerDuty will automatically open a
53
- # new incident with a unique key.
54
- #
55
- # @option options [String] :client The name of the monitoring client that is
56
- # triggering this event.
57
- #
58
- # @option options [String] :client_url The URL of the monitoring client that
59
- # is triggering this event.
60
- #
61
- # @option options [Hash] :details An arbitrary hash containing any data you'd
62
- # like included in the incident log.
63
- #
64
- # @return [PagerdutyIncident] The triggered incident.
61
+ # @return [Pagerduty::EventsApiV1] the built instance.
65
62
  #
66
- # @raise [PagerdutyException] If PagerDuty responds with a status that is not
67
- # "success"
63
+ # @raise [ArgumentError] If integration_key or api_version options are not
64
+ # provided. Or if the provided api_version is unsupported.
68
65
  #
69
- def trigger(description, options = {})
70
- resp = api_call("trigger", options.merge(description: description))
71
- ensure_success(resp)
72
- PagerdutyIncident.new(
73
- service_key,
74
- resp["incident_key"],
75
- transport: @transport,
76
- )
77
- end
78
-
79
- # @param [String] incident_key The unique identifier for the incident.
80
- #
81
- # @return [PagerdutyIncident] The incident referenced by the key.
82
- #
83
- # @raise [ArgumentError] If incident_key is nil
84
- #
85
- def get_incident(incident_key)
86
- raise ArgumentError, "incident_key is nil" if incident_key.nil?
87
- PagerdutyIncident.new(
88
- service_key,
89
- incident_key,
90
- transport: @transport,
91
- )
92
- end
93
-
94
- protected
95
-
96
- def api_call(event_type, args)
97
- args = args.merge(
98
- service_key: service_key,
99
- event_type: event_type,
100
- )
101
- @transport.send_payload(args)
102
- end
103
-
104
- def ensure_success(response)
105
- unless response["status"] == "success"
106
- raise PagerdutyException.new(self, response, response["message"])
66
+ def self.build(config)
67
+ unless config.key?(:integration_key)
68
+ raise ArgumentError, "integration_key not provided"
107
69
  end
108
- end
109
-
110
- private
70
+ raise ArgumentError, "incident_key provided" if config.key?(:incident_key)
111
71
 
112
- # @api private
113
- def transport_from_options(options = {})
114
- options[:transport] || Pagerduty::HttpTransport.new(options)
115
- end
116
- end
117
-
118
- class PagerdutyIncident < Pagerduty
119
- attr_reader :incident_key
120
-
121
- # @param [String] service_key The GUID of one of your "Generic API" services.
122
- # This is the "service key" listed on a Generic API's service detail page.
123
- #
124
- # @param [String] incident_key The unique identifier for the incident.
125
- #
126
- def initialize(service_key, incident_key, options = {})
127
- super service_key, options
128
- @incident_key = incident_key
129
- end
130
-
131
- # @param (see Pagerduty#trigger)
132
- # @option (see Pagerduty#trigger)
133
- def trigger(description, options = {})
134
- super(description, { incident_key: incident_key }.merge(options))
135
- end
136
-
137
- # Acknowledge the referenced incident. While an incident is acknowledged, it
138
- # won't generate any additional notifications, even if it receives new
139
- # trigger events. Send PagerDuty an acknowledge event when you know someone
140
- # is presently working on the problem.
141
- #
142
- # @param [String] description Text that will appear in the incident's log
143
- # associated with this event.
144
- #
145
- # @param [Hash] details An arbitrary hash containing any data you'd like
146
- # included in the incident log.
147
- #
148
- # @return [PagerdutyIncident] self
149
- #
150
- # @raise [PagerdutyException] If PagerDuty responds with a status that is not
151
- # "success"
152
- #
153
- def acknowledge(description = nil, details = nil)
154
- modify_incident("acknowledge", description, details)
155
- end
156
-
157
- # Resolve the referenced incident. Once an incident is resolved, it won't
158
- # generate any additional notifications. New trigger events with the same
159
- # incident_key as a resolved incident won't re-open the incident. Instead, a
160
- # new incident will be created. Send PagerDuty a resolve event when the
161
- # problem that caused the initial trigger event has been fixed.
162
- #
163
- # @param [String] description Text that will appear in the incident's log
164
- # associated with this event.
165
- #
166
- # @param [Hash] details An arbitrary hash containing any data you'd like
167
- # included in the incident log.
168
- #
169
- # @return [PagerdutyIncident] self
170
- #
171
- # @raise [PagerdutyException] If PagerDuty responds with a status that is not
172
- # "success"
173
- #
174
- def resolve(description = nil, details = nil)
175
- modify_incident("resolve", description, details)
72
+ version = config.fetch(:api_version) do
73
+ raise ArgumentError, "api_version not provided"
74
+ end
75
+ events_api_class(version).new(config)
176
76
  end
177
77
 
178
- private
179
-
180
- def modify_incident(event_type, description, details)
181
- options = { incident_key: incident_key }
182
- options[:description] = description if description
183
- options[:details] = details if details
184
- resp = api_call(event_type, options)
185
- ensure_success(resp)
186
- self
78
+ def self.events_api_class(version)
79
+ class_name = "Pagerduty::EventsApiV#{version}"
80
+ if const_defined?(class_name)
81
+ const_get(class_name)
82
+ else
83
+ raise ArgumentError, "api_version #{version.inspect} not supported"
84
+ end
187
85
  end
86
+ private_class_method :events_api_class
188
87
  end