lita-locker 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e5c32b7506039ce73526e9483adfd4243e1aa36d
4
+ data.tar.gz: 745a609c8595fe3bd6cb8caab9de837b439f9049
5
+ SHA512:
6
+ metadata.gz: 3849f4ad7eec61bc12027688a35ff8002cac08dbcc8b0f0e478b7b3a31ea4f3e928ab33414c2e69fa28687dd005fb0cc0e9c3d7f986fb1b4a26de6fa871fcd84
7
+ data.tar.gz: 8408eff2c472f5d9f9141d91bed7bf6fa2a3c8e6d5a80e54d38a3eec20bba820d4d81ce78ea68fd2cbc886f7e3066b795e495fc733c9e5e14c934a39d1323ca0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rubocop.yml ADDED
@@ -0,0 +1,19 @@
1
+ BlockNesting:
2
+ Max: 5
3
+
4
+ ClassLength:
5
+ Max: 500
6
+
7
+ CyclomaticComplexity:
8
+ Max: 10
9
+
10
+ Documentation:
11
+ Exclude:
12
+ - lib/lita/handlers/locker.rb
13
+
14
+ FileName:
15
+ Exclude:
16
+ - lib/lita-locker.rb
17
+
18
+ MethodLength:
19
+ Max: 200
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ script: bundle exec rake
5
+ before_install:
6
+ - gem update --system
7
+ services:
8
+ - redis-server
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2014 Eric Sigler
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # lita-locker
2
+
3
+ [![Build Status](https://img.shields.io/travis/esigler/lita-locker/master.svg)](https://travis-ci.org/esigler/lita-locker)
4
+ [![MIT License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://tldrlegal.com/license/mit-license)
5
+ [![RubyGems :: RMuh Gem Version](http://img.shields.io/gem/v/lita-locker.svg)](https://rubygems.org/gems/lita-locker)
6
+ [![Coveralls Coverage](https://img.shields.io/coveralls/esigler/lita-locker/master.svg)](https://coveralls.io/r/esigler/lita-locker)
7
+ [![Code Climate](https://img.shields.io/codeclimate/github/esigler/lita-locker.svg)](https://codeclimate.com/github/esigler/lita-locker)
8
+ [![Gemnasium](https://img.shields.io/gemnasium/esigler/lita-locker.svg)](https://gemnasium.com/esigler/lita-locker)
9
+
10
+ Locking, unlocking shared resource handler for [lita.io](https://github.com/jimmycuadra/lita).
11
+
12
+ ## Installation
13
+
14
+ Add lita-locker to your Lita instance's Gemfile:
15
+
16
+ ``` ruby
17
+ gem "lita-locker"
18
+ ```
19
+
20
+ ## Configuration
21
+
22
+ None
23
+
24
+ ## Usage
25
+
26
+ lita-locker allows you to define resources (such as a server, or Git repo),
27
+ and labels (such as "production"). Labels can have multiple resources, and
28
+ resources can be referenced by multiple labels. A label can only be locked
29
+ if all of the resources it uses are available.
30
+
31
+ ### Examples
32
+ ```
33
+ lock web - Make something unavailable to others
34
+ lock web 30m - Make something unavailable to others for 30 minutes
35
+ unlock web - Make something available to others
36
+ locker reserve web - Make yourself the next owner of something after it is unlocked
37
+ locker status - Show what is and isn't available
38
+ ```
39
+
40
+ ### Locking, Unlocking, State
41
+ ```
42
+ lock <subject> - A basic reservation, with no time limit. <subject> can be a resource or a label.
43
+ unlock <subject> - Remove a reservation. This can only be done by whomever made the request.
44
+ unlock <subject> force - Force removal of a reservation. This can be done by anyone.
45
+ ```
46
+
47
+ ### Time-based locking - Not implemented yet!
48
+ ```
49
+ lock <subject> <time> - A time-limited reservation. <time> must be a number with a "s", "m", or "h" postfix.
50
+ ```
51
+
52
+ ### Reservations - Not implemented yet!
53
+ ```
54
+ reserve <subject> - Add yourself to a FIFO queue of pending reservations for <subject>
55
+ unreserve <subject> - Remove yourself from the queue for <subject>
56
+ ```
57
+
58
+ ### Labels
59
+ ```
60
+ locker label list - List all labels
61
+ locker label create <name> - Create a label with <name>. <name> must be A-Z, a-z, 0-9, no spaces or punctuation.
62
+ locker label delete <name> - Delete the label with <name>. Clears all locks associated.
63
+ locker label add <resource> to <name> - Adds <resource> to the list of things to lock/unlock for <name>
64
+ locker label remove <resource> from <name> - Removes <resource> from <name>
65
+ locker label show <name> - Show all resources for <name>
66
+ ```
67
+
68
+ ### Resources
69
+ ```
70
+ locker resource list - List all resources
71
+ locker resource create <name> - Create a resource with <name>. <name> must be A-Z, a-z, 0-9, no spaces or punctuation.
72
+ locker resource delete <name> - Delete the resource with <name>. Clears all locks associated.
73
+ locker resource show <name> - Show the state of <name>
74
+ ```
75
+
76
+ ### HTTP access
77
+ ```
78
+ curl http://lita.example.com/locker/label/<name> - Get current <name> status
79
+ curl http://lita.example.com/locker/resource/<name> - Get current <name> status
80
+ ```
81
+
82
+ ## License
83
+
84
+ [MIT](http://opensource.org/licenses/MIT)
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+ Rubocop::RakeTask.new(:rubocop)
7
+
8
+ task default: [:spec, :rubocop]
@@ -0,0 +1,9 @@
1
+ require 'lita'
2
+
3
+ Lita.load_locales Dir[File.expand_path(
4
+ File.join('..', '..', 'locales', '*.yml'), __FILE__
5
+ )]
6
+
7
+ require 'JSON'
8
+
9
+ require 'lita/handlers/locker'
@@ -0,0 +1,502 @@
1
+ module Lita
2
+ module Handlers
3
+ class Locker < Handler
4
+ http.get '/locker/label/:name', :http_label_show
5
+ http.get '/locker/resource/:name', :http_resource_show
6
+
7
+ # route(
8
+ # /^\(lock\)\s([a-zA-Z0-9_-]+)$/,
9
+ # :lock
10
+ # )
11
+
12
+ # route(
13
+ # /^\(unlock\)\s([a-zA-Z0-9_-]+)$/,
14
+ # :unlock
15
+ # )
16
+
17
+ route(
18
+ /^lock\s([a-zA-Z0-9_-]+)$/,
19
+ :lock,
20
+ command: true,
21
+ help: {
22
+ t('help.lock_key') => t('help.lock_value')
23
+ }
24
+ )
25
+
26
+ # route(
27
+ # /^lock\s([a-zA-Z0-9_-]+)\s(\d+)(s|m|h)$/,
28
+ # :lock,
29
+ # command: true,
30
+ # help: {
31
+ # t('help.lock_time_key') => t('help.lock_time_value')
32
+ # }
33
+ # )
34
+
35
+ route(
36
+ /^unlock\s([a-zA-Z0-9_-]+)$/,
37
+ :unlock,
38
+ command: true,
39
+ help: {
40
+ t('help.unlock_key') => t('help.unlock_value')
41
+ }
42
+ )
43
+
44
+ route(
45
+ /^unlock\s([a-zA-Z0-9_-]+)\sforce$/,
46
+ :unlock_force,
47
+ command: true,
48
+ help: {
49
+ t('help.unlock_force_key') => t('help.unlock_force_value')
50
+ }
51
+ )
52
+
53
+ route(
54
+ /^locker\sresource\slist$/,
55
+ :resource_list,
56
+ command: true,
57
+ help: {
58
+ t('help.resource_list_key') =>
59
+ t('help.resource_list_value')
60
+ }
61
+ )
62
+
63
+ route(
64
+ /^locker\sresource\screate\s([a-zA-Z0-9_-]+)$/,
65
+ :resource_create,
66
+ command: true,
67
+ help: {
68
+ t('help.resource_create_key') =>
69
+ t('help.resource_create_value')
70
+ }
71
+ )
72
+
73
+ route(
74
+ /^locker\sresource\sdelete\s([a-zA-Z0-9_-]+)$/,
75
+ :resource_delete,
76
+ command: true,
77
+ help: {
78
+ t('help.resource_delete_key') =>
79
+ t('help.resource_delete_value')
80
+ }
81
+ )
82
+
83
+ route(
84
+ /^locker\sresource\sshow\s([a-zA-Z0-9_-]+)$/,
85
+ :resource_show,
86
+ command: true,
87
+ help: {
88
+ t('help.resource_show_key') =>
89
+ t('help.resource_show_value')
90
+ }
91
+ )
92
+
93
+ route(
94
+ /^locker\slabel\slist$/,
95
+ :label_list,
96
+ command: true,
97
+ help: {
98
+ t('help.label_list_key') =>
99
+ t('help.label_list_value')
100
+ }
101
+ )
102
+
103
+ route(
104
+ /^locker\slabel\screate\s([a-zA-Z0-9_-]+)$/,
105
+ :label_create,
106
+ command: true,
107
+ help: {
108
+ t('help.label_create_key') =>
109
+ t('help.label_create_value')
110
+ }
111
+ )
112
+
113
+ route(
114
+ /^locker\slabel\sdelete\s([a-zA-Z0-9_-]+)$/,
115
+ :label_delete,
116
+ command: true,
117
+ help: {
118
+ t('help.label_delete_key') =>
119
+ t('help.label_delete_value')
120
+ }
121
+ )
122
+
123
+ route(
124
+ /^locker\slabel\sshow\s([a-zA-Z0-9_-]+)$/,
125
+ :label_show,
126
+ command: true,
127
+ help: {
128
+ t('help.label_show_key') =>
129
+ t('help.label_show_value')
130
+ }
131
+ )
132
+
133
+ route(
134
+ /^locker\slabel\sadd\s([a-zA-Z0-9_-]+)\sto\s([a-zA-Z0-9_-]+)$/,
135
+ :label_add,
136
+ command: true,
137
+ help: {
138
+ t('help.label_add_key') =>
139
+ t('help.label_add_value')
140
+ }
141
+ )
142
+
143
+ route(
144
+ /^locker\slabel\sremove\s([a-zA-Z0-9_-]+)\sfrom\s([a-zA-Z0-9_-]+)$/,
145
+ :label_remove,
146
+ command: true,
147
+ help: {
148
+ t('help.label_remove_key') =>
149
+ t('help.label_remove_value')
150
+ }
151
+ )
152
+
153
+ def http_label_show(request, response)
154
+ name = request.env['router.params'][:name]
155
+ response.headers['Content-Type'] = 'application/json'
156
+ result = label(name)
157
+ response.write(result.to_json)
158
+ end
159
+
160
+ def http_resource_show(request, response)
161
+ name = request.env['router.params'][:name]
162
+ response.headers['Content-Type'] = 'application/json'
163
+ result = resource(name)
164
+ response.write(result.to_json)
165
+ end
166
+
167
+ def lock(response)
168
+ name = response.matches[0][0]
169
+ timeamt = response.matches[0][1]
170
+ timeunit = response.matches[0][2]
171
+ case timeunit
172
+ when 's'
173
+ time_until = Time.now.utc + timeamt.to_i
174
+ when 'm'
175
+ time_until = Time.now.utc + (timeamt.to_i * 60)
176
+ when 'h'
177
+ time_until = Time.now.utc + (timeamt.to_i * 3600)
178
+ else
179
+ time_until = nil
180
+ end
181
+
182
+ if resource_exists?(name)
183
+ if lock_resource!(name, response.user, time_until)
184
+ response.reply(t('resource.lock', name: name))
185
+ else
186
+ response.reply(t('resource.is_locked', name: name))
187
+ end
188
+ elsif label_exists?(name)
189
+ m = label_membership(name)
190
+ if m.count > 0
191
+ if lock_label!(name, response.user, time_until)
192
+ response.reply(t('label.lock', name: name))
193
+ else
194
+ response.reply(t('label.unable_to_lock', name: name))
195
+ end
196
+ else
197
+ response.reply(t('label.no_resources', name: name))
198
+ end
199
+ else
200
+ response.reply(t('subject.does_not_exist', name: name))
201
+ end
202
+ end
203
+
204
+ def unlock(response)
205
+ name = response.matches[0][0]
206
+ if resource_exists?(name)
207
+ res = resource(name)
208
+ if res['state'] == 'unlocked'
209
+ response.reply(t('resource.is_unlocked', name: name))
210
+ else
211
+ # FIXME: NOT SECURE
212
+ if response.user.name == res['owner']
213
+ unlock_resource!(name)
214
+ response.reply(t('resource.unlock', name: name))
215
+ # FIXME: Handle the case where things can't be unlocked?
216
+ else
217
+ response.reply(t('resource.owned', name: name,
218
+ owner: res['owner']))
219
+ end
220
+ end
221
+ elsif label_exists?(name)
222
+ l = label(name)
223
+ if l['state'] == 'unlocked'
224
+ response.reply(t('label.is_unlocked', name: name))
225
+ else
226
+ # FIXME: NOT SECURE
227
+ if response.user.name == l['owner']
228
+ unlock_label!(name)
229
+ response.reply(t('label.unlock', name: name))
230
+ # FIXME: Handle the case where things can't be unlocked?
231
+ else
232
+ response.reply(t('label.owned', name: name,
233
+ owner: l['owner']))
234
+ end
235
+ end
236
+ else
237
+ response.reply(t('subject.does_not_exist', name: name))
238
+ end
239
+ end
240
+
241
+ def unlock_force(response)
242
+ name = response.matches[0][0]
243
+ if resource_exists?(name)
244
+ unlock_resource!(name)
245
+ response.reply(t('resource.unlock', name: name))
246
+ # FIXME: Handle the case where things can't be unlocked?
247
+ elsif label_exists?(name)
248
+ unlock_label!(name)
249
+ response.reply(t('label.unlock', name: name))
250
+ # FIXME: Handle the case where things can't be unlocked?
251
+ else
252
+ response.reply(t('subject.does_not_exist', name: name))
253
+ end
254
+ end
255
+
256
+ def label_list(response)
257
+ labels.each do |l|
258
+ response.reply(t('label.desc', name: l.sub('label_', '')))
259
+ end
260
+ end
261
+
262
+ def label_create(response)
263
+ name = response.matches[0][0]
264
+ if create_label(name)
265
+ response.reply(t('label.created', name: name))
266
+ else
267
+ response.reply(t('label.exists', name: name))
268
+ end
269
+ end
270
+
271
+ def label_delete(response)
272
+ name = response.matches[0][0]
273
+ if delete_label(name)
274
+ response.reply(t('label.deleted', name: name))
275
+ else
276
+ response.reply(t('label.does_not_exist', name: name))
277
+ end
278
+ end
279
+
280
+ def label_show(response)
281
+ name = response.matches[0][0]
282
+ if label_exists?(name)
283
+ members = label_membership(name)
284
+ if members.count > 0
285
+ response.reply(t('label.resources', name: name,
286
+ resources: members.join(', ')))
287
+ else
288
+ response.reply(t('label.has_no_resources', name: name))
289
+ end
290
+ else
291
+ response.reply(t('label.does_not_exist', name: name))
292
+ end
293
+ end
294
+
295
+ def label_add(response)
296
+ resource_name = response.matches[0][0]
297
+ label_name = response.matches[0][1]
298
+ if label_exists?(label_name)
299
+ if resource_exists?(resource_name)
300
+ add_resource_to_label(label_name, resource_name)
301
+ response.reply(t('label.resource_added', label: label_name,
302
+ resource: resource_name))
303
+ else
304
+ response.reply(t('resource.does_not_exist', name: resource_name))
305
+ end
306
+ else
307
+ response.reply(t('label.does_not_exist', name: label_name))
308
+ end
309
+ end
310
+
311
+ def label_remove(response)
312
+ resource_name = response.matches[0][0]
313
+ label_name = response.matches[0][1]
314
+ if label_exists?(label_name)
315
+ if resource_exists?(resource_name)
316
+ members = label_membership(label_name)
317
+ if members.include?(resource_name)
318
+ remove_resource_from_label(label_name, resource_name)
319
+ response.reply(t('label.resource_removed',
320
+ label: label_name, resource: resource_name))
321
+ else
322
+ response.reply(t('label.does_not_have_resource',
323
+ label: label_name, resource: resource_name))
324
+ end
325
+ else
326
+ response.reply(t('resource.does_not_exist', name: resource_name))
327
+ end
328
+ else
329
+ response.reply(t('label.does_not_exist', name: label_name))
330
+ end
331
+ end
332
+
333
+ def resource_list(response)
334
+ resources.each do |r|
335
+ r_name = r.sub('resource_', '')
336
+ res = resource(r_name)
337
+ response.reply(t('resource.desc', name: r_name, state: res['state']))
338
+ end
339
+ end
340
+
341
+ def resource_create(response)
342
+ name = response.matches[0][0]
343
+ if create_resource(name)
344
+ response.reply(t('resource.created', name: name))
345
+ else
346
+ response.reply(t('resource.exists', name: name))
347
+ end
348
+ end
349
+
350
+ def resource_delete(response)
351
+ name = response.matches[0][0]
352
+ if delete_resource(name)
353
+ response.reply(t('resource.deleted', name: name))
354
+ else
355
+ response.reply(t('resource.does_not_exist', name: name))
356
+ end
357
+ end
358
+
359
+ def resource_show(response)
360
+ name = response.matches[0][0]
361
+ if resource_exists?(name)
362
+ r = resource(name)
363
+ response.reply(t('resource.desc', name: name, state: r['state']))
364
+ else
365
+ response.reply(t('resource.does_not_exist', name: name))
366
+ end
367
+ end
368
+
369
+ private
370
+
371
+ def create_label(name)
372
+ label_key = "label_#{name}"
373
+ redis.hset(label_key, 'state', 'unlocked') unless
374
+ resource_exists?(name) || label_exists?(name)
375
+ end
376
+
377
+ def delete_label(name)
378
+ label_key = "label_#{name}"
379
+ redis.del(label_key) if label_exists?(name)
380
+ end
381
+
382
+ def label_exists?(name)
383
+ redis.exists("label_#{name}")
384
+ end
385
+
386
+ def label_membership(name)
387
+ redis.smembers("membership_#{name}")
388
+ end
389
+
390
+ def add_resource_to_label(label, resource)
391
+ if label_exists?(label) && resource_exists?(resource)
392
+ redis.sadd("membership_#{label}", resource)
393
+ end
394
+ end
395
+
396
+ def remove_resource_from_label(label, resource)
397
+ if label_exists?(label) && resource_exists?(resource)
398
+ redis.srem("membership_#{label}", resource)
399
+ end
400
+ end
401
+
402
+ def create_resource(name)
403
+ resource_key = "resource_#{name}"
404
+ redis.hset(resource_key, 'state', 'unlocked') unless
405
+ resource_exists?(name) || label_exists?(name)
406
+ end
407
+
408
+ def delete_resource(name)
409
+ resource_key = "resource_#{name}"
410
+ redis.del(resource_key) if resource_exists?(name)
411
+ end
412
+
413
+ def resource_exists?(name)
414
+ redis.exists("resource_#{name}")
415
+ end
416
+
417
+ def lock_resource!(name, owner, time_until)
418
+ if resource_exists?(name)
419
+ resource_key = "resource_#{name}"
420
+ value = redis.hget(resource_key, 'state')
421
+ if value == 'unlocked'
422
+ # FIXME: Race condition!
423
+ # FIXME: Store something better than name
424
+ redis.hset(resource_key, 'state', 'locked')
425
+ redis.hset(resource_key, 'owner', owner.name)
426
+ redis.hset(resource_key, 'until', time_until)
427
+ true
428
+ else
429
+ false
430
+ end
431
+ else
432
+ false
433
+ end
434
+ end
435
+
436
+ def lock_label!(name, owner, time_until)
437
+ if label_exists?(name)
438
+ key = "label_#{name}"
439
+ members = label_membership(name)
440
+ members.each do |m|
441
+ r = resource(m)
442
+ return false if r['state'] == 'locked'
443
+ end
444
+ # FIXME: No, really, race condition.
445
+ members.each do |m|
446
+ lock_resource!(m, owner, time_until)
447
+ end
448
+ redis.hset(key, 'state', 'locked')
449
+ redis.hset(key, 'owner', owner.name)
450
+ redis.hset(key, 'until', time_until)
451
+ true
452
+ else
453
+ false
454
+ end
455
+ end
456
+
457
+ def unlock_resource!(name)
458
+ if resource_exists?(name)
459
+ # FIXME: Tracking here?
460
+ key = "resource_#{name}"
461
+ redis.hset(key, 'state', 'unlocked')
462
+ redis.hset(key, 'owner', '')
463
+ else
464
+ false
465
+ end
466
+ end
467
+
468
+ def unlock_label!(name)
469
+ if label_exists?(name)
470
+ key = "label_#{name}"
471
+ members = label_membership(name)
472
+ members.each do |m|
473
+ unlock_resource!(m)
474
+ end
475
+ redis.hset(key, 'state', 'unlocked')
476
+ redis.hset(key, 'owner', '')
477
+ true
478
+ else
479
+ false
480
+ end
481
+ end
482
+
483
+ def resource(name)
484
+ redis.hgetall("resource_#{name}")
485
+ end
486
+
487
+ def resources
488
+ redis.keys('resource_*')
489
+ end
490
+
491
+ def label(name)
492
+ redis.hgetall("label_#{name}")
493
+ end
494
+
495
+ def labels
496
+ redis.keys('label_*')
497
+ end
498
+ end
499
+
500
+ Lita.register_handler(Locker)
501
+ end
502
+ end
@@ -0,0 +1,25 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'lita-locker'
3
+ spec.version = '0.3.0'
4
+ spec.authors = ['Eric Sigler']
5
+ spec.email = ['me@esigler.com']
6
+ spec.description = %q("lock" and "unlock" arbitrary subjects)
7
+ spec.summary = %q("lock" and "unlock" arbitrary subjects)
8
+ spec.homepage = 'https://github.com/esigler/lita-locker'
9
+ spec.license = 'MIT'
10
+ spec.metadata = { 'lita_plugin_type' => 'handler' }
11
+
12
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
13
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(/^(test|spec|features)\//)
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_runtime_dependency 'lita', '>= 3.2'
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.3'
20
+ spec.add_development_dependency 'rake'
21
+ spec.add_development_dependency 'rspec', '>= 3.0.0.beta2'
22
+ spec.add_development_dependency 'simplecov'
23
+ spec.add_development_dependency 'coveralls'
24
+ spec.add_development_dependency 'rubocop'
25
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,61 @@
1
+ en:
2
+ lita:
3
+ handlers:
4
+ locker:
5
+ help:
6
+ lock_key: lock <subject>
7
+ lock_value: Make something unavailable to others
8
+ unlock_key: unlock <subject>
9
+ unlock_value: Make something available to others
10
+ unlock_force_key: unlock <subject> force
11
+ unlock_force_value: Force removal of a reservation.
12
+ resource_list_key: locker resource list
13
+ resource_list_value: List all resources
14
+ resource_create_key: locker resource create <name>
15
+ resource_create_value: Create a resource with <name>
16
+ resource_delete_key: locker resource delete <name>
17
+ resource_delete_value: Delete the resource with <name>
18
+ resource_show_key: locker resource show <name>
19
+ resource_show_value: Show the state of <name>
20
+ label_list_key: locker label list
21
+ label_list_value: List all labels
22
+ label_create_key: locker label create <name>
23
+ label_create_value: Create a label with <name>
24
+ label_delete_key: locker label delete <name>
25
+ label_delete_value: Delete the label with <name>
26
+ label_show_key: locker label show <name>
27
+ label_show_value: Show all resources for <name>
28
+ label_add_key: locker label add <resource> to <name>
29
+ label_add_value: Adds <resource> to the list of things to lock/unlock for <name>
30
+ label_remove_key: locker label remove <resource> from <name>
31
+ label_remove_value: Removes <resource> from <name>
32
+ resource:
33
+ created: "Resource %{name} created"
34
+ desc: "Resource: %{name}, state: %{state}"
35
+ exists: "%{name} already exists"
36
+ deleted: "Resource %{name} deleted"
37
+ does_not_exist: "Resource %{name} does not exist"
38
+ lock: "%{name} locked"
39
+ is_locked: "%{name} is locked"
40
+ unlock: "%{name} unlocked"
41
+ is_unlocked: "%{name} is unlocked"
42
+ owned: "%{name} is locked by %{owner}"
43
+ subject:
44
+ does_not_exist: "%{name} does not exist"
45
+ label:
46
+ unlock: "%{name} unlocked"
47
+ owned: "%{name} is locked by %{owner}"
48
+ is_unlocked: "%{name} is unlocked"
49
+ unable_to_lock: "%{name} unable to be locked"
50
+ lock: "%{name} locked"
51
+ desc: "Label: %{name}"
52
+ created: "Label %{name} created"
53
+ exists: "%{name} already exists"
54
+ deleted: "Label %{name} deleted"
55
+ does_not_exist: "Label %{name} does not exist"
56
+ has_no_resources: "Label %{name} has no resources"
57
+ resource_added: "Resource %{resource} has been added to %{label}"
58
+ resource_removed: "Resource %{resource} has been removed from %{label}"
59
+ resources: "Label %{name} has: %{resources}"
60
+ does_not_have_resource: "Label %{label} does not have Resource %{resource}"
61
+ no_resources: "%{name} has no resources, so it cannot be locked"
@@ -0,0 +1,352 @@
1
+ require 'spec_helper'
2
+
3
+ describe Lita::Handlers::Locker, lita_handler: true do
4
+ # it { routes('(lock) foobar').to(:lock) }
5
+ # it { routes('(unlock) foobar').to(:unlock) }
6
+
7
+ it { routes_command('lock foobar').to(:lock) }
8
+ # it { routes_command('lock foobar 30m').to(:lock) }
9
+
10
+ it { routes_command('unlock foobar').to(:unlock) }
11
+ it { routes_command('unlock foobar force').to(:unlock_force) }
12
+
13
+ it { routes_command('locker resource list').to(:resource_list) }
14
+ it { routes_command('locker resource create foobar').to(:resource_create) }
15
+ it { routes_command('locker resource delete foobar').to(:resource_delete) }
16
+ it { routes_command('locker resource show foobar').to(:resource_show) }
17
+
18
+ it { routes_command('locker label list').to(:label_list) }
19
+ it { routes_command('locker label create foobar').to(:label_create) }
20
+ it { routes_command('locker label delete foobar').to(:label_delete) }
21
+ it { routes_command('locker label show foobar').to(:label_show) }
22
+ it { routes_command('locker label add foo to bar').to(:label_add) }
23
+ it { routes_command('locker label remove foo from bar').to(:label_remove) }
24
+
25
+ it { routes_http(:get, '/locker/label/foobar').to(:http_label_show) }
26
+ it { routes_http(:get, '/locker/resource/foobar').to(:http_resource_show) }
27
+
28
+ describe '#lock' do
29
+ it 'locks a resource when it is available' do
30
+ send_command('locker resource create foobar')
31
+ send_command('lock foobar')
32
+ expect(replies.last).to eq('foobar locked')
33
+ end
34
+
35
+ it 'locks a label when it is available and has resources' do
36
+ send_command('locker resource create foobar')
37
+ send_command('locker label create bazbat')
38
+ send_command('locker label add foobar to bazbat')
39
+ send_command('lock bazbat')
40
+ expect(replies.last).to eq('bazbat locked')
41
+ send_command('locker resource show foobar')
42
+ expect(replies.last).to eq('Resource: foobar, state: locked')
43
+ end
44
+
45
+ it 'shows a warning when a label has no resources' do
46
+ send_command('locker label create foobar')
47
+ send_command('lock foobar')
48
+ expect(replies.last).to eq('foobar has no resources, ' \
49
+ 'so it cannot be locked')
50
+ end
51
+
52
+ it 'shows a warning when a resource is unavailable' do
53
+ send_command('locker resource create foobar')
54
+ send_command('lock foobar')
55
+ send_command('lock foobar')
56
+ expect(replies.last).to eq('foobar is locked')
57
+ end
58
+
59
+ it 'shows a warning when a label is unavailable' do
60
+ send_command('locker resource create foobar')
61
+ send_command('locker label create bazbat')
62
+ send_command('locker label add foobar to bazbat')
63
+ send_command('lock foobar')
64
+ send_command('lock bazbat')
65
+ expect(replies.last).to eq('bazbat unable to be locked')
66
+ end
67
+
68
+ it 'shows an error when a <subject> does not exist' do
69
+ send_command('lock foobar')
70
+ expect(replies.last).to eq('foobar does not exist')
71
+ end
72
+
73
+ # it 'locks a resource when it is available for a period of time' do
74
+ # send_command('locker resource create foobar')
75
+ # send_command('lock foobar 17m')
76
+ # expect(replies.last).to eq('foobar locked for 17 minutes')
77
+ # send_command('locker resource show foobar')
78
+ # expect(replies.last).to eq('Resource: foobar, state: locked')
79
+ # send_command('unlock foobar')
80
+ # send_command('lock foobar 12s')
81
+ # expect(replies.last).to eq('foobar locked for 17 seconds')
82
+ # send_command('unlock foobar')
83
+ # send_command('lock foobar 14h')
84
+ # expect(replies.last).to eq('foobar locked for 14 hours')
85
+ # end
86
+ end
87
+
88
+ describe '#unlock' do
89
+ it 'unlocks a resource when it is available' do
90
+ send_command('locker resource create foobar')
91
+ send_command('lock foobar')
92
+ send_command('unlock foobar')
93
+ expect(replies.last).to eq('foobar unlocked')
94
+ end
95
+
96
+ it 'does not unlock a resource when someone else locked it' do
97
+ alice = Lita::User.create(1, name: 'Alice')
98
+ bob = Lita::User.create(2, name: 'Bob')
99
+ send_command('locker resource create foobar')
100
+ send_command('lock foobar', as: alice)
101
+ send_command('unlock foobar', as: bob)
102
+ expect(replies.last).to eq('foobar is locked by Alice')
103
+ end
104
+
105
+ it 'unlocks a label when it is available' do
106
+ send_command('locker resource create foobar')
107
+ send_command('locker label create bazbat')
108
+ send_command('locker label add foobar to bazbat')
109
+ send_command('lock bazbat')
110
+ send_command('unlock bazbat')
111
+ expect(replies.last).to eq('bazbat unlocked')
112
+ end
113
+
114
+ it 'does not unlock a label when someone else locked it' do
115
+ alice = Lita::User.create(1, name: 'Alice')
116
+ bob = Lita::User.create(2, name: 'Bob')
117
+ send_command('locker resource create foobar')
118
+ send_command('locker label create bazbat')
119
+ send_command('locker label add foobar to bazbat')
120
+ send_command('lock bazbat', as: alice)
121
+ send_command('unlock bazbat', as: bob)
122
+ expect(replies.last).to eq('bazbat is locked by Alice')
123
+ end
124
+
125
+ it 'shows a warning when a resource is already unlocked' do
126
+ send_command('locker resource create foobar')
127
+ send_command('unlock foobar')
128
+ expect(replies.last).to eq('foobar is unlocked')
129
+ end
130
+
131
+ it 'shows a warning when a label is already unlocked' do
132
+ send_command('locker resource create foobar')
133
+ send_command('locker label create bazbat')
134
+ send_command('locker label add foobar to bazbat')
135
+ send_command('unlock bazbat')
136
+ send_command('unlock bazbat')
137
+ expect(replies.last).to eq('bazbat is unlocked')
138
+ end
139
+
140
+ it 'shows an error when a <subject> does not exist' do
141
+ send_command('unlock foobar')
142
+ expect(replies.last).to eq('foobar does not exist')
143
+ end
144
+ end
145
+
146
+ describe '#unlock_force' do
147
+ it 'unlocks a resource from someone else when it is available' do
148
+ alice = Lita::User.create(1, name: 'Alice')
149
+ bob = Lita::User.create(2, name: 'Bob')
150
+ send_command('locker resource create foobar')
151
+ send_command('lock foobar', as: alice)
152
+ send_command('unlock foobar force', as: bob)
153
+ expect(replies.last).to eq('foobar unlocked')
154
+ end
155
+
156
+ it 'unlocks a label from someone else when it is available' do
157
+ alice = Lita::User.create(1, name: 'Alice')
158
+ bob = Lita::User.create(2, name: 'Bob')
159
+ send_command('locker resource create foobar')
160
+ send_command('locker label create bazbat')
161
+ send_command('locker label add foobar to bazbat')
162
+ send_command('lock bazbat', as: alice)
163
+ send_command('unlock bazbat force', as: bob)
164
+ expect(replies.last).to eq('bazbat unlocked')
165
+ end
166
+
167
+ it 'shows an error when a <subject> does not exist' do
168
+ send_command('unlock foobar force')
169
+ expect(replies.last).to eq('foobar does not exist')
170
+ end
171
+ end
172
+
173
+ describe '#label_list' do
174
+ it 'shows a list of labels if there are any' do
175
+ send_command('locker label create foobar')
176
+ send_command('locker label create bazbat')
177
+ send_command('locker label list')
178
+ expect(replies.last).to eq('Label: bazbat')
179
+ end
180
+ end
181
+
182
+ describe '#label_create' do
183
+ it 'creates a label with <name>' do
184
+ send_command('locker label create foobar')
185
+ expect(replies.last).to eq('Label foobar created')
186
+ end
187
+
188
+ it 'shows a warning when the <name> already exists as a label' do
189
+ send_command('locker label create foobar')
190
+ send_command('locker label create foobar')
191
+ expect(replies.last).to eq('foobar already exists')
192
+ end
193
+
194
+ it 'shows a warning when the <name> already exists as a resource' do
195
+ send_command('locker resource create foobar')
196
+ send_command('locker label create foobar')
197
+ expect(replies.last).to eq('foobar already exists')
198
+ end
199
+ end
200
+
201
+ describe '#label_delete' do
202
+ it 'deletes a label with <name>' do
203
+ send_command('locker label create foobar')
204
+ send_command('locker label delete foobar')
205
+ expect(replies.last).to eq('Label foobar deleted')
206
+ end
207
+
208
+ it 'shows a warning when <name> does not exist' do
209
+ send_command('locker label delete foobar')
210
+ expect(replies.last).to eq('Label foobar does not exist')
211
+ end
212
+ end
213
+
214
+ describe '#label_show' do
215
+ it 'shows a list of resources for a label if there are any' do
216
+ send_command('locker resource create whatever')
217
+ send_command('locker label create foobar')
218
+ send_command('locker label add whatever to foobar')
219
+ send_command('locker label show foobar')
220
+ expect(replies.last).to eq('Label foobar has: whatever')
221
+ end
222
+
223
+ it 'shows a warning if there are no resources for the label' do
224
+ send_command('locker label create foobar')
225
+ send_command('locker label show foobar')
226
+ expect(replies.last).to eq('Label foobar has no resources')
227
+ end
228
+
229
+ it 'shows an error if the label does not exist' do
230
+ send_command('locker label show foobar')
231
+ expect(replies.last).to eq('Label foobar does not exist')
232
+ end
233
+ end
234
+
235
+ describe '#label_add' do
236
+ it 'adds a resource to a label if both exist' do
237
+ send_command('locker resource create foo')
238
+ send_command('locker label create bar')
239
+ send_command('locker label add foo to bar')
240
+ expect(replies.last).to eq('Resource foo has been added to bar')
241
+ send_command('locker label show bar')
242
+ expect(replies.last).to eq('Label bar has: foo')
243
+ end
244
+
245
+ it 'adds multiple resources to a label if all exist' do
246
+ send_command('locker resource create foo')
247
+ send_command('locker resource create baz')
248
+ send_command('locker label create bar')
249
+ send_command('locker label add foo to bar')
250
+ send_command('locker label add baz to bar')
251
+ send_command('locker label show bar')
252
+ expect(replies.last).to eq('Label bar has: baz, foo')
253
+ end
254
+
255
+ it 'shows an error if the label does not exist' do
256
+ send_command('locker label add foo to bar')
257
+ expect(replies.last).to eq('Label bar does not exist')
258
+ end
259
+
260
+ it 'shows an error if the resource does not exist' do
261
+ send_command('locker label create bar')
262
+ send_command('locker label add foo to bar')
263
+ expect(replies.last).to eq('Resource foo does not exist')
264
+ end
265
+ end
266
+
267
+ describe '#label_remove' do
268
+ it 'removes a resource from a label if both exist and are related' do
269
+ send_command('locker resource create foo')
270
+ send_command('locker label create bar')
271
+ send_command('locker label add foo to bar')
272
+ send_command('locker label remove foo from bar')
273
+ send_command('locker label show bar')
274
+ expect(replies.last).to eq('Label bar has no resources')
275
+ end
276
+
277
+ it 'shows an error if they both exist but are not related' do
278
+ send_command('locker resource create foo')
279
+ send_command('locker label create bar')
280
+ send_command('locker label remove foo from bar')
281
+ expect(replies.last).to eq('Label bar does not have Resource foo')
282
+ end
283
+
284
+ it 'shows an error if the label does not exist' do
285
+ send_command('locker label add foo to bar')
286
+ expect(replies.last).to eq('Label bar does not exist')
287
+ end
288
+
289
+ it 'shows an error if the resource does not exist' do
290
+ send_command('locker label create bar')
291
+ send_command('locker label add foo to bar')
292
+ expect(replies.last).to eq('Resource foo does not exist')
293
+ end
294
+ end
295
+
296
+ describe '#resource_list' do
297
+ it 'shows a list of resources if there are any' do
298
+ send_command('locker resource create foobar')
299
+ send_command('locker resource create bazbat')
300
+ send_command('locker resource list')
301
+ expect(replies.last).to eq('Resource: foobar, state: unlocked')
302
+ end
303
+ end
304
+
305
+ describe '#resource_create' do
306
+ it 'creates a resource with <name>' do
307
+ send_command('locker resource create foobar')
308
+ expect(replies.last).to eq('Resource foobar created')
309
+ end
310
+
311
+ it 'shows a warning when the <name> already exists as a resource' do
312
+ send_command('locker resource create foobar')
313
+ send_command('locker resource create foobar')
314
+ expect(replies.last).to eq('foobar already exists')
315
+ end
316
+
317
+ it 'shows a warning when the <name> already exists as a label' do
318
+ send_command('locker label create foobar')
319
+ send_command('locker resource create foobar')
320
+ expect(replies.last).to eq('foobar already exists')
321
+ end
322
+ end
323
+
324
+ describe '#resource_delete' do
325
+ it 'deletes a resource with <name>' do
326
+ send_command('locker resource create foobar')
327
+ send_command('locker resource delete foobar')
328
+ expect(replies.last).to eq('Resource foobar deleted')
329
+ end
330
+
331
+ it 'shows a warning when <name> does not exist' do
332
+ send_command('locker resource delete foobar')
333
+ expect(replies.last).to eq('Resource foobar does not exist')
334
+ end
335
+ end
336
+
337
+ describe '#resource_show' do
338
+ it 'shows the state of a <name> if it exists' do
339
+ send_command('locker resource create foobar')
340
+ send_command('locker resource show foobar')
341
+ expect(replies.last).to eq('Resource: foobar, state: unlocked')
342
+ send_command('lock foobar')
343
+ send_command('locker resource show foobar')
344
+ expect(replies.last).to eq('Resource: foobar, state: locked')
345
+ end
346
+
347
+ it 'shows a warning when <name> does not exist' do
348
+ send_command('locker resource show foobar')
349
+ expect(replies.last).to eq('Resource foobar does not exist')
350
+ end
351
+ end
352
+ end
@@ -0,0 +1,10 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.start { add_filter '/spec/' }
8
+
9
+ require 'lita-locker'
10
+ require 'lita/rspec'
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lita-locker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Eric Sigler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-16 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: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.0.beta2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.0.beta2
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
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: coveralls
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: rubocop
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
+ description: "\"lock\" and \"unlock\" arbitrary subjects"
112
+ email:
113
+ - me@esigler.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rubocop.yml"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE
123
+ - README.md
124
+ - Rakefile
125
+ - lib/lita-locker.rb
126
+ - lib/lita/handlers/locker.rb
127
+ - lita-locker.gemspec
128
+ - locales/en.yml
129
+ - spec/lita/handlers/locker_spec.rb
130
+ - spec/spec_helper.rb
131
+ homepage: https://github.com/esigler/lita-locker
132
+ licenses:
133
+ - MIT
134
+ metadata:
135
+ lita_plugin_type: handler
136
+ post_install_message:
137
+ rdoc_options: []
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubyforge_project:
152
+ rubygems_version: 2.2.1
153
+ signing_key:
154
+ specification_version: 4
155
+ summary: "\"lock\" and \"unlock\" arbitrary subjects"
156
+ test_files:
157
+ - spec/lita/handlers/locker_spec.rb
158
+ - spec/spec_helper.rb