pagerduty 2.1.1 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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