lita-interrupt 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.travis.yml +8 -0
- data/Gemfile +3 -0
- data/LICENSE +26 -0
- data/README.md +91 -0
- data/Rakefile +6 -0
- data/lib/interrupt_helper/pager_duty.rb +36 -0
- data/lib/lita-interrupt.rb +12 -0
- data/lib/lita/handlers/interrupt.rb +309 -0
- data/lita-interrupt.gemspec +30 -0
- data/locales/en.yml +51 -0
- data/spec/lita/handlers/interrupt_spec.rb +143 -0
- data/spec/spec_helper.rb +15 -0
- metadata +201 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b15811f72a7f29073b61d5f8bcdcd7cea23e7ed6
|
4
|
+
data.tar.gz: d67e7c6d51bf17ebd58946b48e94e8c02ab21d34
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fb320ff975a01d83543a859c2c906c470f6b8f8f577e3896213b948ab5e04b714b09f8aefa3d654790da6f87dc72bff3b14c95e12e836f869bea9f94b28c2910
|
7
|
+
data.tar.gz: b265df0952d50b4423fb09eeec78de07b6c3c2f834440ee9f0b37bfb9d920fd646a7ac77cab6bd29548a9b839e05165e6796534ddbf8186cc90d0205d1d2ff66
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2018 Noel R. Cower. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
4
|
+
modification, are permitted provided that the following conditions are
|
5
|
+
met:
|
6
|
+
|
7
|
+
1. Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
11
|
+
documentation and/or other materials provided with the distribution.
|
12
|
+
3. Neither the name of the copyright holder nor the names of its
|
13
|
+
contributors may be used to endorse or promote products derived from
|
14
|
+
this software without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
17
|
+
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
18
|
+
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
20
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
22
|
+
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
# lita-interrupt
|
2
|
+
|
3
|
+
[](https://travis-ci.org/nilium/lita-interrupt)
|
4
|
+
[](https://coveralls.io/r/nilium/lita-interrupt)
|
5
|
+
|
6
|
+
lita-interrupt is a plugin that enables paging of individual services in
|
7
|
+
PagerDuty using an alias. It is primarily intended for use in on-call situations
|
8
|
+
and is geared towards use in Slack (hasn't been tested on other chat platforms.)
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Add lita-interrupt to your Lita instance's Gemfile:
|
13
|
+
|
14
|
+
``` ruby
|
15
|
+
gem "lita-interrupt", :git => 'https://github.com/nilium/lita-interrupt', :branch => 'master'
|
16
|
+
```
|
17
|
+
|
18
|
+
Currently, there are no published releases, so you have to use the Git
|
19
|
+
repository.
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
lita-interrupt you to set `config.handlers.interrupt.pagerduty_teams` to a hash
|
24
|
+
of team names to PagerDuty API tokens. For example:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
Lita.configure do |config|
|
28
|
+
|
29
|
+
config.handlers.interrupt.pagerduty_teams = {
|
30
|
+
'team' => 'API-TOKEN',
|
31
|
+
}
|
32
|
+
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
For now, team names must be a lowercase string.
|
37
|
+
|
38
|
+
Users in the `pagerduty_admins` auth group can list services, teams, and create
|
39
|
+
and remove aliases.
|
40
|
+
|
41
|
+
To override the name required for PagerDuty integrations, you can set the
|
42
|
+
`config.handlers.interrupt.integration_name` config value. By default, it is set
|
43
|
+
to `lita-interrupt`.
|
44
|
+
|
45
|
+
### PagerDuty Integrations
|
46
|
+
|
47
|
+
lita-interrupt looks for Events v2 API integrations under PagerDuty services
|
48
|
+
with the configured integration name (default: `lita-interrupt`). In order to be
|
49
|
+
able to create an alias for a service, you must first add an integration with
|
50
|
+
the required name to the service you want to create a notification for.
|
51
|
+
|
52
|
+
## Usage
|
53
|
+
|
54
|
+
### `call ALIAS [MESSAGE]`
|
55
|
+
|
56
|
+
Send a PagerDuty notification to the service identified by the alias. Optionally
|
57
|
+
includes a message with the alert.
|
58
|
+
|
59
|
+
All alerts attempt to include the caller's name and the channel the notification
|
60
|
+
was sent from.
|
61
|
+
|
62
|
+
### `int service ls [TEAM [QUERY]]`
|
63
|
+
|
64
|
+
List all service IDs configured with a lita-interrupt integration. You can use
|
65
|
+
the IDs returned by this to create an alias for a particular team.
|
66
|
+
|
67
|
+
Requires `pagerduty_admins` auth.
|
68
|
+
|
69
|
+
### `int alias new NAME TEAM SERVICE_ID`
|
70
|
+
|
71
|
+
Create an alias with the given name for the team and service ID. Once created,
|
72
|
+
the alias can be used to send a PagerDuty alert to the service.
|
73
|
+
|
74
|
+
Requires `pagerduty_admins` auth.
|
75
|
+
|
76
|
+
### `int alias ls` or `who can I call?`
|
77
|
+
|
78
|
+
(Also accepts `who can I call` minus the question mark -- matching is
|
79
|
+
case-insensitive.)
|
80
|
+
|
81
|
+
List call-able aliases.
|
82
|
+
|
83
|
+
### `int alias rm NAME`
|
84
|
+
|
85
|
+
Remove an alias with the given name.
|
86
|
+
|
87
|
+
Requires `pagerduty_admins` auth.
|
88
|
+
|
89
|
+
### `int team ls`
|
90
|
+
|
91
|
+
List team IDs.
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
require 'pager_duty/connection'
|
4
|
+
|
5
|
+
module InterruptHelper
|
6
|
+
module PagerDuty
|
7
|
+
def pagerduty(token)
|
8
|
+
@pagerduty_clients ||= Hash.new { |h, k|
|
9
|
+
h[k] = ::PagerDuty::Connection.new(k)
|
10
|
+
}
|
11
|
+
@pagerduty_clients[token]
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [::Faraday]
|
15
|
+
def events_v2
|
16
|
+
@events_v2_client ||= Faraday.new do |conn|
|
17
|
+
conn.url_prefix = "https://events.pagerduty.com/v2/"
|
18
|
+
conn.use(RaiseNon202)
|
19
|
+
conn.request(:json)
|
20
|
+
conn.response(:json)
|
21
|
+
conn.headers[:accept] = "application/json"
|
22
|
+
conn.adapter(Faraday.default_adapter)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class APIError < RuntimeError; end
|
27
|
+
|
28
|
+
class RaiseNon202 < Faraday::Middleware
|
29
|
+
def call(env)
|
30
|
+
response = @app.call env
|
31
|
+
raise APIError, "bad response code: #{response.status}" unless response.status == 202
|
32
|
+
response
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "lita"
|
2
|
+
|
3
|
+
Lita.load_locales Dir[File.expand_path(
|
4
|
+
File.join("..", "..", "locales", "*.yml"), __FILE__
|
5
|
+
)]
|
6
|
+
|
7
|
+
require "lita/handlers/interrupt"
|
8
|
+
|
9
|
+
Lita::Handlers::Interrupt.template_root File.expand_path(
|
10
|
+
File.join("..", "..", "templates"),
|
11
|
+
__FILE__
|
12
|
+
)
|
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'interrupt_helper/pager_duty'
|
3
|
+
|
4
|
+
module Lita
|
5
|
+
module Handlers
|
6
|
+
class Interrupt < Handler
|
7
|
+
config :pagerduty_teams, required: true, type: Hash # { lowercase('team') => 'token' }
|
8
|
+
config :integration_name, type: String, default: 'lita-interrupt'.freeze
|
9
|
+
|
10
|
+
# Keys in Redis
|
11
|
+
CALL_ALIASES = 'call_alias'.freeze
|
12
|
+
|
13
|
+
# Constants used by the PagerDuty API
|
14
|
+
EVENTS_API_V2_INBOUND = 'events_api_v2_inbound_integration'.freeze
|
15
|
+
|
16
|
+
include ::InterruptHelper::PagerDuty
|
17
|
+
|
18
|
+
## ROUTES
|
19
|
+
|
20
|
+
route(
|
21
|
+
/^call\s+(?<alias_id>[^\s]+)(?:\s+(?<message>.+))?\s*$/i,
|
22
|
+
:call_alias,
|
23
|
+
command: true,
|
24
|
+
help: { t('help.call.syntax') => t('help.call.desc') }
|
25
|
+
)
|
26
|
+
|
27
|
+
route(
|
28
|
+
/^int\s+team\s+ls\s*$/i,
|
29
|
+
:list_teams,
|
30
|
+
restrict_to: %w[pagerduty_admins],
|
31
|
+
command: true,
|
32
|
+
help: { t('help.team_ls.syntax') => t('help.team_ls.desc') }
|
33
|
+
)
|
34
|
+
|
35
|
+
route(
|
36
|
+
/^int\s+alias\s+new\s+(?<alias_id>\S+)\s+(?<team_id>\S+)\s+(?<service_id>\S+)\s*$/i,
|
37
|
+
:create_alias,
|
38
|
+
command: true,
|
39
|
+
restrict_to: %w[pagerduty_admins],
|
40
|
+
help: { t('help.alias_new.syntax') => t('help.alias_new.desc') }
|
41
|
+
)
|
42
|
+
|
43
|
+
route(
|
44
|
+
/^int\s+alias\s+rm\s+(?<alias_id>\S+)\s*$/i,
|
45
|
+
:remove_alias,
|
46
|
+
command: true,
|
47
|
+
restrict_to: %w[pagerduty_admins],
|
48
|
+
help: { t('help.alias_rm.syntax') => t('help.alias_rm.desc') }
|
49
|
+
)
|
50
|
+
|
51
|
+
route(
|
52
|
+
/^(int\s+alias\s+ls|who\s+can\s+i\s+call\??)\s*$/i,
|
53
|
+
:list_aliases,
|
54
|
+
command: true,
|
55
|
+
help: { t('help.alias_ls.syntax') => t('help.alias_ls.desc') }
|
56
|
+
)
|
57
|
+
|
58
|
+
route(
|
59
|
+
/^int\s+service\s+ls(\s+(?<team_id>\S+)(\s+(?<query>.+))?)?\s*$/i,
|
60
|
+
:list_services,
|
61
|
+
command: true,
|
62
|
+
restrict_to: %w[pagerduty_admins],
|
63
|
+
help: { t('help.service_ls.syntax') => t('help.service_ls.desc') }
|
64
|
+
)
|
65
|
+
|
66
|
+
## CALLBACKS
|
67
|
+
|
68
|
+
# @param [Lita::Response] response
|
69
|
+
def call_alias(response)
|
70
|
+
alias_id = response.match_data['alias_id'].downcase
|
71
|
+
|
72
|
+
callee = get_call_alias(alias_id)
|
73
|
+
return response.reply(t('call.no_alias', name: alias_id)) unless callee
|
74
|
+
|
75
|
+
request = gen_call_request(response, callee['integration']['integration_key'])
|
76
|
+
events_v2.post('enqueue', request)
|
77
|
+
response.reply(t('call.ok', name: alias_id))
|
78
|
+
|
79
|
+
rescue APIError => ae
|
80
|
+
log_exception(__callee__, ex)
|
81
|
+
response.reply(t('call.api_error', id: alias_id, message: ae.message))
|
82
|
+
rescue Exception => ex
|
83
|
+
send_exception(response, __callee__, ex)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Lita::Response] response
|
87
|
+
def list_teams(response)
|
88
|
+
names = config.pagerduty_teams.keys
|
89
|
+
if names.empty?
|
90
|
+
response.reply(t('team_ls.no_teams'))
|
91
|
+
return
|
92
|
+
end
|
93
|
+
response.reply(
|
94
|
+
names.sort.map do |team_id|
|
95
|
+
t('team_ls.entry', team_id: team_id)
|
96
|
+
end.join("\n")
|
97
|
+
)
|
98
|
+
rescue Exception => ex
|
99
|
+
send_exception(response, __callee__, ex)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @param [Lita::Response] response
|
103
|
+
def create_alias(response)
|
104
|
+
alias_id = response.match_data['alias_id'].downcase
|
105
|
+
team_id = response.match_data['team_id'].downcase
|
106
|
+
service_id = response.match_data['service_id']
|
107
|
+
|
108
|
+
pd = team(team_id)
|
109
|
+
if pd.nil?
|
110
|
+
response.reply(t('alias_new.no_team', team_id: team_id))
|
111
|
+
return
|
112
|
+
end
|
113
|
+
|
114
|
+
integration = get_events_api_integration(pd, service_id)
|
115
|
+
unless integration
|
116
|
+
return response.reply(t('alias_new.no_integration', service_id: service_id, integration_name: config.integration_name))
|
117
|
+
end
|
118
|
+
|
119
|
+
set_call_alias(alias_id, team_id, service_id, integration)
|
120
|
+
response.reply(t('alias_new.ok', id: alias_id))
|
121
|
+
|
122
|
+
rescue Exception => ex
|
123
|
+
send_exception(response, __callee__, ex)
|
124
|
+
end
|
125
|
+
|
126
|
+
# @param [Lita::Response] response
|
127
|
+
def list_aliases(response)
|
128
|
+
aliases = redis.hgetall(CALL_ALIASES)
|
129
|
+
if aliases.empty?
|
130
|
+
response.reply(t('alias_ls.no_aliases'))
|
131
|
+
return
|
132
|
+
end
|
133
|
+
|
134
|
+
response.reply(
|
135
|
+
aliases.keys.sort.map do |alias_id|
|
136
|
+
obj = MultiJson.load(aliases[alias_id])
|
137
|
+
t(
|
138
|
+
'alias_ls.entry',
|
139
|
+
alias_id: alias_id,
|
140
|
+
service_name: obj['integration']['service']['summary'],
|
141
|
+
team_id: obj['team_id']
|
142
|
+
)
|
143
|
+
end.join("\n")
|
144
|
+
)
|
145
|
+
|
146
|
+
rescue Exception => ex
|
147
|
+
send_exception(response, __callee__, ex)
|
148
|
+
end
|
149
|
+
|
150
|
+
# @param [Lita::Response] response
|
151
|
+
def remove_alias(response)
|
152
|
+
alias_id = response.match_data['alias_id'].downcase
|
153
|
+
if redis.hdel(CALL_ALIASES, alias_id) > 0 then
|
154
|
+
response.reply(t('alias_rm.ok', id: alias_id))
|
155
|
+
else
|
156
|
+
response.reply(t('alias_rm.no_alias', id: alias_id))
|
157
|
+
end
|
158
|
+
|
159
|
+
rescue Exception => ex
|
160
|
+
send_exception(response, __callee__, ex)
|
161
|
+
end
|
162
|
+
|
163
|
+
# @param [Lita::Response] response
|
164
|
+
def list_services(response)
|
165
|
+
team_id = response.match_data['team_id']
|
166
|
+
unless team_id.nil? || team_id.empty?
|
167
|
+
response.reply(list_team_services(team_id, response).join("\n"))
|
168
|
+
return
|
169
|
+
end
|
170
|
+
|
171
|
+
response.reply(
|
172
|
+
config.pagerduty_teams.keys.sort.flat_map do |team_id|
|
173
|
+
[
|
174
|
+
t('service_ls.team', team_id: team_id),
|
175
|
+
*list_team_services(team_id, response)
|
176
|
+
]
|
177
|
+
end.join("\n")
|
178
|
+
)
|
179
|
+
|
180
|
+
rescue APIError => ae
|
181
|
+
log_exception(__callee__, ex)
|
182
|
+
response.reply(t('call.api_error', id: alias_id, message: ae.message))
|
183
|
+
rescue Exception => ex
|
184
|
+
send_exception(response, __callee__, ex)
|
185
|
+
end
|
186
|
+
|
187
|
+
def list_team_services(team_id, response)
|
188
|
+
pd = team(team_id)
|
189
|
+
if pd.nil?
|
190
|
+
response.reply(t('service_ls.no_team', team_id: team_id))
|
191
|
+
return
|
192
|
+
end
|
193
|
+
|
194
|
+
query = (response.match_data['query'] || '').strip
|
195
|
+
params = {
|
196
|
+
query: query || '',
|
197
|
+
include: %w[integrations]
|
198
|
+
}
|
199
|
+
|
200
|
+
results = pd.get('services', query_params: params)
|
201
|
+
|
202
|
+
# Only list Events API 2-integrated services with suitable integrations
|
203
|
+
services = (results['services'] || []).select do |svc|
|
204
|
+
(svc['integrations'] || []).any? { |i| is_suitable_integration?(i) }
|
205
|
+
end
|
206
|
+
return response.reply(t('service_ls.no_services')) if services.empty?
|
207
|
+
|
208
|
+
services.sort_by { |svc| svc['name'] }.map { |svc|
|
209
|
+
t('service_ls.entry', service_name: svc['name'], service_id: svc['id'])
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
## IMPLEMENTATION
|
214
|
+
|
215
|
+
def team(team_id)
|
216
|
+
pagerduty(config.pagerduty_teams[team_id])
|
217
|
+
end
|
218
|
+
|
219
|
+
def send_exception(response, callee, ex)
|
220
|
+
log_exception(callee, ex)
|
221
|
+
response.reply(t('exception', handler: callee, class: ex.class.name, message: ex.message))
|
222
|
+
end
|
223
|
+
|
224
|
+
def log_exception(callee, ex)
|
225
|
+
log.warn("Exception occurred #{ex.class}: #{ex.message}:\n#{ex.backtrace.join("\n")}")
|
226
|
+
end
|
227
|
+
|
228
|
+
def get_room_name(response)
|
229
|
+
room = Lita::Room.find_by_id(response.message.source.room_object.id)
|
230
|
+
room.name || room.mention_name if room
|
231
|
+
end
|
232
|
+
|
233
|
+
def gen_call_request(response, routing_key)
|
234
|
+
# Build summary message sent in the event
|
235
|
+
room_name = get_room_name(response) || ''
|
236
|
+
room_note = room_name.empty? ? '' : " in ##{room_name}"
|
237
|
+
msg = "You've been called by #{response.user.name}#{room_note}."
|
238
|
+
|
239
|
+
sub_msg = (response.match_data['message'] || '').strip
|
240
|
+
unless sub_msg.empty?
|
241
|
+
sub_msg = "#{sub_msg}." unless /\p{Terminal_Punctuation}["']?$/ =~ sub_msg
|
242
|
+
msg = "#{msg}\n#{sub_msg}"
|
243
|
+
end
|
244
|
+
|
245
|
+
source = 'Slack'
|
246
|
+
source = "##{room_name}" unless room_name.empty?
|
247
|
+
|
248
|
+
{
|
249
|
+
routing_key: routing_key,
|
250
|
+
event_action: 'trigger',
|
251
|
+
payload: {
|
252
|
+
summary: msg,
|
253
|
+
source: source,
|
254
|
+
severity: 'critical',
|
255
|
+
component: 'Human',
|
256
|
+
group: 'On-Call',
|
257
|
+
class: 'Notification'
|
258
|
+
}
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
# @return [Hash] A hash describing the policy alias. If no policy alias is defined, returns nil.
|
263
|
+
def get_call_alias(alias_id)
|
264
|
+
callee_json = redis.hget(CALL_ALIASES, alias_id)
|
265
|
+
return nil if callee_json.nil?
|
266
|
+
MultiJson.load(callee_json)
|
267
|
+
end
|
268
|
+
|
269
|
+
def set_call_alias(alias_id, team_id, service_id, integration)
|
270
|
+
obj = {
|
271
|
+
version: 1,
|
272
|
+
team_id: team_id.to_s,
|
273
|
+
alias_id: alias_id.to_s,
|
274
|
+
service_id: service_id,
|
275
|
+
integration: integration
|
276
|
+
}
|
277
|
+
redis.hset(CALL_ALIASES, alias_id.downcase, MultiJson.dump(obj))
|
278
|
+
end
|
279
|
+
|
280
|
+
def get_events_api_integration(pd, service_id)
|
281
|
+
service = pd.get(
|
282
|
+
"services/#{service_id}",
|
283
|
+
query_params: { include: %w[integrations] }
|
284
|
+
)&.fetch('service')
|
285
|
+
return nil unless service
|
286
|
+
index = service['integrations'].index do |int|
|
287
|
+
int['type'] == EVENTS_API_V2_INBOUND &&
|
288
|
+
int['name'].downcase.gsub(/[_.\s]/, '-') == config.integration_name
|
289
|
+
end
|
290
|
+
index && service['integrations'][index]
|
291
|
+
end
|
292
|
+
|
293
|
+
def required_integration_name
|
294
|
+
@integration_name ||= normalize_name(config.integration_name)
|
295
|
+
end
|
296
|
+
|
297
|
+
def normalize_name(name)
|
298
|
+
name.downcase.gsub(/[\s\._]/, '-')
|
299
|
+
end
|
300
|
+
|
301
|
+
def is_suitable_integration?(integration)
|
302
|
+
integration['type'] == EVENTS_API_V2_INBOUND &&
|
303
|
+
normalize_name(integration['name']) == required_integration_name
|
304
|
+
end
|
305
|
+
|
306
|
+
Lita.register_handler(self)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = 'lita-interrupt'
|
3
|
+
spec.version = '0.1.0'
|
4
|
+
spec.authors = ['Noel Cower']
|
5
|
+
spec.email = ['ncower@gmail.com']
|
6
|
+
spec.description = 'lita-interrupt interrupts a user by assigning them to a new incident in PagerDuty'
|
7
|
+
spec.summary = 'Interrupts people through the power of PagerDuty'
|
8
|
+
spec.homepage = 'https://github.com/nilium/lita-interrupt'
|
9
|
+
spec.license = 'BSD-3-Clause'
|
10
|
+
spec.metadata = { 'lita_plugin_type' => 'handler' }
|
11
|
+
|
12
|
+
spec.files = `git ls-files`.split($/)
|
13
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
14
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
15
|
+
spec.require_paths = ['lib']
|
16
|
+
|
17
|
+
spec.required_ruby_version = '~> 2.3'
|
18
|
+
|
19
|
+
spec.add_runtime_dependency 'lita', '>= 4.7'
|
20
|
+
spec.add_runtime_dependency 'pager_duty-connection', '~> 1.0'
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
23
|
+
spec.add_development_dependency 'coveralls'
|
24
|
+
spec.add_development_dependency 'pry-byebug'
|
25
|
+
spec.add_development_dependency 'rack-test'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rspec', '>= 3.0.0'
|
28
|
+
spec.add_development_dependency 'rubocop'
|
29
|
+
spec.add_development_dependency 'simplecov'
|
30
|
+
end
|
data/locales/en.yml
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
en:
|
2
|
+
lita:
|
3
|
+
handlers:
|
4
|
+
interrupt:
|
5
|
+
team_ls:
|
6
|
+
no_teams: "No teams are configured."
|
7
|
+
entry: "• `%{team_id}`"
|
8
|
+
service_ls:
|
9
|
+
no_services: ":failed: No services found."
|
10
|
+
no_team: ":failed: Team %{team_id} not found."
|
11
|
+
api_error: ":failed: Unable to look up services: %{message}"
|
12
|
+
entry: "• `%{service_id}` - _%{service_name}_"
|
13
|
+
team: "*%{team_id}*"
|
14
|
+
alias_new:
|
15
|
+
no_integration: ":failed: Service `%{service_id}` does not exist or does not have an Events v2 integration named `%{integration_name}`."
|
16
|
+
no_team: ":failed: Team %{team_id} not found."
|
17
|
+
ok: ":success: Alias %{id} created."
|
18
|
+
alias_rm:
|
19
|
+
no_alias: ":failed: Alias %{id} not found."
|
20
|
+
ok: ":success: Alias %{id} deleted."
|
21
|
+
alias_ls:
|
22
|
+
entry: "• `%{alias_id}` → %{service_name} (%{team_id})"
|
23
|
+
no_aliases: "No aliases defined."
|
24
|
+
call:
|
25
|
+
no_alias: ":failed: No alias found with the name %{name}"
|
26
|
+
api_error: ":failed: Unable to call %{id}: %{message}"
|
27
|
+
ok: ":notify: %{name} notified."
|
28
|
+
|
29
|
+
exception: |-
|
30
|
+
:failed: Exception in handler %{handler} (%{class}):
|
31
|
+
%{message}
|
32
|
+
|
33
|
+
help:
|
34
|
+
call:
|
35
|
+
syntax: 'call <NAME> [MESSAGE]'
|
36
|
+
desc: 'Notify the recipient with an optional message via PagerDuty.'
|
37
|
+
team_ls:
|
38
|
+
syntax: 'int team ls'
|
39
|
+
desc: 'List team names.'
|
40
|
+
alias_new:
|
41
|
+
syntax: 'int alias new <NAME> <TEAM> <SERVICE-ID>'
|
42
|
+
desc: 'Create a service call alias (requires the service to have a correctly-named Events v2 integration).'
|
43
|
+
alias_ls:
|
44
|
+
syntax: 'int alias ls | who can i call'
|
45
|
+
desc: 'List aliases.'
|
46
|
+
alias_rm:
|
47
|
+
syntax: 'int alias rm <NAME>'
|
48
|
+
desc: 'Delete an existing service alias.'
|
49
|
+
service_ls:
|
50
|
+
syntax: 'int service ls [TEAM [QUERY]]'
|
51
|
+
desc: 'List services, optionally filtering them by team and with a query (by title).'
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Lita::Handlers::Interrupt, lita_handler: true do
|
4
|
+
|
5
|
+
routes = {
|
6
|
+
# :method => ["MESSAGE", ...]
|
7
|
+
|
8
|
+
:call_alias => [
|
9
|
+
'call dev the tirefire seems unhappy today',
|
10
|
+
'call ops we forgot to add more tires',
|
11
|
+
'call dev ',
|
12
|
+
'call ops ',
|
13
|
+
'call dev',
|
14
|
+
'call ops'
|
15
|
+
],
|
16
|
+
|
17
|
+
:list_teams => [
|
18
|
+
'int team ls ',
|
19
|
+
'int team ls'
|
20
|
+
],
|
21
|
+
|
22
|
+
:create_alias => [
|
23
|
+
'int alias new alias1 team1 serviceid',
|
24
|
+
'int alias new alias1 team1 serviceid'
|
25
|
+
],
|
26
|
+
|
27
|
+
:remove_alias => [
|
28
|
+
'int alias rm alias1'
|
29
|
+
],
|
30
|
+
|
31
|
+
:list_aliases => [
|
32
|
+
'int alias ls ',
|
33
|
+
'int alias ls',
|
34
|
+
'who can I call?',
|
35
|
+
'who can i call?',
|
36
|
+
'WHO CAN i CALL ' # does it pass the Scott check?
|
37
|
+
],
|
38
|
+
|
39
|
+
:list_services => [
|
40
|
+
'int service ls',
|
41
|
+
'int service ls ',
|
42
|
+
'int service ls team1',
|
43
|
+
'int service ls team1 query text'
|
44
|
+
]
|
45
|
+
|
46
|
+
}
|
47
|
+
|
48
|
+
describe "Basic user access" do
|
49
|
+
%i[
|
50
|
+
list_aliases
|
51
|
+
call_alias
|
52
|
+
].each do |method_name|
|
53
|
+
it "routes basic commands" do
|
54
|
+
routes[method_name].each { |cmd| is_expected.to route_command(cmd).to(method_name) }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "Unauthorized user access" do
|
60
|
+
%i[
|
61
|
+
list_teams
|
62
|
+
create_alias
|
63
|
+
remove_alias
|
64
|
+
list_services
|
65
|
+
].each do |method_name|
|
66
|
+
it "does not route admin commands" do
|
67
|
+
routes[method_name].each { |cmd| is_expected.not_to route_command(cmd).to(method_name) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe "pagerduty_admins access" do
|
73
|
+
before do
|
74
|
+
robot.auth.add_user_to_group!(user, :pagerduty_admins)
|
75
|
+
end
|
76
|
+
|
77
|
+
routes.each do |method_name, cmds|
|
78
|
+
it "routes all commands" do
|
79
|
+
cmds.each { |cmd| is_expected.to route_command(cmd).to(method_name) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
after do
|
84
|
+
robot.auth.remove_user_from_group!(user, :pagerduty_admins)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
let(:success) do
|
89
|
+
pd_client = double
|
90
|
+
events_client = double
|
91
|
+
|
92
|
+
allow(pd_client).to receive(:get).with('services') do
|
93
|
+
# This is not a full response for getting services since there's a lot
|
94
|
+
# more metadata in those.
|
95
|
+
{
|
96
|
+
"limit" => 100,
|
97
|
+
"offset" => 0,
|
98
|
+
"total" => nil,
|
99
|
+
"more" => false,
|
100
|
+
"services" => [
|
101
|
+
{
|
102
|
+
"id" => "PT2JFN8",
|
103
|
+
"name" => "PagerDuty Service",
|
104
|
+
"status" => "active",
|
105
|
+
"type" => "service",
|
106
|
+
"self" => "https://api.pagerduty.com/services/PT2JFN8",
|
107
|
+
"html_url" => "https://teamname.pagerduty.com/services/PT2JFN8",
|
108
|
+
|
109
|
+
"teams" => [
|
110
|
+
{
|
111
|
+
"id" => "PSODFXI",
|
112
|
+
"type" => "team_reference",
|
113
|
+
"summary" => "Team",
|
114
|
+
"self" => "https://api.pagerduty.com/teams/PSODFXI",
|
115
|
+
"html_url" => "https://teamname.pagerduty.com/teams/PSODFXI",
|
116
|
+
},
|
117
|
+
],
|
118
|
+
|
119
|
+
"escalation_policy" =>
|
120
|
+
{
|
121
|
+
"id" => "PEN4ZAY",
|
122
|
+
"type" => "escalation_policy_reference",
|
123
|
+
"summary" => "First Responder",
|
124
|
+
"self" => "https://api.pagerduty.com/escalation_policies/PEN4ZAY",
|
125
|
+
"html_url" => "https://teamname.pagerduty.com/escalation_policies/PEN4ZAY",
|
126
|
+
},
|
127
|
+
|
128
|
+
"integrations" => [
|
129
|
+
{
|
130
|
+
"id" => "PS1XI4R",
|
131
|
+
"type" => "generic_email_inbound_integration_reference",
|
132
|
+
"summary" => "Email",
|
133
|
+
"self" => "https://api.pagerduty.com/services/PT2JFN8/integrations/PS1XI4R",
|
134
|
+
"html_url" => "https://teamname.pagerduty.com/services/PT2JFN8/integrations/PS1XI4R",
|
135
|
+
},
|
136
|
+
],
|
137
|
+
}
|
138
|
+
],
|
139
|
+
}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
require "coveralls"
|
3
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(
|
4
|
+
[
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter
|
7
|
+
])
|
8
|
+
SimpleCov.start { add_filter "/spec/" }
|
9
|
+
|
10
|
+
require "lita-interrupt"
|
11
|
+
require "lita/rspec"
|
12
|
+
|
13
|
+
# A compatibility mode is provided for older plugins upgrading from Lita 3. Since this plugin
|
14
|
+
# was generated with Lita 4, the compatibility mode should be left disabled.
|
15
|
+
Lita.version_3_compatibility_mode = false
|
metadata
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lita-interrupt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Noel Cower
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-07-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: lita
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.7'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pager_duty-connection
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: coveralls
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-byebug
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rack-test
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 3.0.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 3.0.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: simplecov
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
description: lita-interrupt interrupts a user by assigning them to a new incident
|
154
|
+
in PagerDuty
|
155
|
+
email:
|
156
|
+
- ncower@gmail.com
|
157
|
+
executables: []
|
158
|
+
extensions: []
|
159
|
+
extra_rdoc_files: []
|
160
|
+
files:
|
161
|
+
- ".gitignore"
|
162
|
+
- ".travis.yml"
|
163
|
+
- Gemfile
|
164
|
+
- LICENSE
|
165
|
+
- README.md
|
166
|
+
- Rakefile
|
167
|
+
- lib/interrupt_helper/pager_duty.rb
|
168
|
+
- lib/lita-interrupt.rb
|
169
|
+
- lib/lita/handlers/interrupt.rb
|
170
|
+
- lita-interrupt.gemspec
|
171
|
+
- locales/en.yml
|
172
|
+
- spec/lita/handlers/interrupt_spec.rb
|
173
|
+
- spec/spec_helper.rb
|
174
|
+
homepage: https://github.com/nilium/lita-interrupt
|
175
|
+
licenses:
|
176
|
+
- BSD-3-Clause
|
177
|
+
metadata:
|
178
|
+
lita_plugin_type: handler
|
179
|
+
post_install_message:
|
180
|
+
rdoc_options: []
|
181
|
+
require_paths:
|
182
|
+
- lib
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - "~>"
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '2.3'
|
188
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
requirements: []
|
194
|
+
rubyforge_project:
|
195
|
+
rubygems_version: 2.5.1
|
196
|
+
signing_key:
|
197
|
+
specification_version: 4
|
198
|
+
summary: Interrupts people through the power of PagerDuty
|
199
|
+
test_files:
|
200
|
+
- spec/lita/handlers/interrupt_spec.rb
|
201
|
+
- spec/spec_helper.rb
|