lita-locker 0.5.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +9 -14
- data/.travis.yml +1 -1
- data/CONTRIBUTING.md +9 -0
- data/Gemfile +1 -1
- data/lib/lita-locker.rb +10 -0
- data/lib/lita/handlers/locker.rb +62 -413
- data/lib/lita/handlers/locker_events.rb +41 -0
- data/lib/lita/handlers/locker_http.rb +30 -0
- data/lib/lita/handlers/locker_labels.rb +114 -0
- data/lib/lita/handlers/locker_misc.rb +62 -0
- data/lib/lita/handlers/locker_resources.rb +82 -0
- data/lib/locker/label.rb +67 -0
- data/lib/locker/misc.rb +15 -0
- data/lib/locker/regex.rb +12 -0
- data/lib/locker/resource.rb +48 -0
- data/lita-locker.gemspec +4 -4
- data/locales/en.yml +8 -1
- data/spec/lita/handlers/locker_events_spec.rb +6 -0
- data/spec/lita/handlers/locker_http_spec.rb +11 -0
- data/spec/lita/handlers/locker_labels_spec.rb +147 -0
- data/spec/lita/handlers/locker_misc_spec.rb +90 -0
- data/spec/lita/handlers/locker_resources_spec.rb +74 -0
- data/spec/lita/handlers/locker_spec.rb +38 -261
- data/spec/spec_helper.rb +12 -0
- metadata +36 -17
- data/TODO.md +0 -5
@@ -0,0 +1,41 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
# Event-related handlers
|
4
|
+
class LockerEvents < Handler
|
5
|
+
namespace 'Locker'
|
6
|
+
|
7
|
+
include ::Locker::Label
|
8
|
+
include ::Locker::Misc
|
9
|
+
include ::Locker::Regex
|
10
|
+
include ::Locker::Resource
|
11
|
+
|
12
|
+
on :lock_attempt, :lock_attempt
|
13
|
+
on :unlock_attempt, :unlock_attempt
|
14
|
+
|
15
|
+
def lock_attempt(payload)
|
16
|
+
label = payload[:label]
|
17
|
+
user = Lita::User.find_by_id(payload[:user_id])
|
18
|
+
request_id = payload[:request_id]
|
19
|
+
|
20
|
+
if label_exists?(label) && lock_label!(label, user, nil)
|
21
|
+
robot.trigger(:lock_success, request_id: request_id)
|
22
|
+
else
|
23
|
+
robot.trigger(:lock_failure, request_id: request_id)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def unlock_attempt(payload)
|
28
|
+
label = payload[:label]
|
29
|
+
request_id = payload[:request_id]
|
30
|
+
|
31
|
+
if label_exists?(label) && unlock_label!(label)
|
32
|
+
robot.trigger(:unlock_success, request_id: request_id)
|
33
|
+
else
|
34
|
+
robot.trigger(:unlock_failure, request_id: request_id)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Lita.register_handler(LockerEvents)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
# HTTP-related handlers
|
4
|
+
class LockerHttp < Handler
|
5
|
+
namespace 'Locker'
|
6
|
+
|
7
|
+
include ::Locker::Label
|
8
|
+
include ::Locker::Misc
|
9
|
+
include ::Locker::Regex
|
10
|
+
include ::Locker::Resource
|
11
|
+
|
12
|
+
http.get '/locker/label/:name', :http_label_show
|
13
|
+
http.get '/locker/resource/:name', :http_resource_show
|
14
|
+
|
15
|
+
def http_label_show(request, response)
|
16
|
+
name = request.env['router.params'][:name]
|
17
|
+
response.headers['Content-Type'] = 'application/json'
|
18
|
+
response.write(label(name).to_json)
|
19
|
+
end
|
20
|
+
|
21
|
+
def http_resource_show(request, response)
|
22
|
+
name = request.env['router.params'][:name]
|
23
|
+
response.headers['Content-Type'] = 'application/json'
|
24
|
+
response.write(resource(name).to_json)
|
25
|
+
end
|
26
|
+
|
27
|
+
Lita.register_handler(LockerHttp)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
# Label-related handlers
|
4
|
+
class LockerLabels < Handler
|
5
|
+
namespace 'Locker'
|
6
|
+
|
7
|
+
include ::Locker::Label
|
8
|
+
include ::Locker::Misc
|
9
|
+
include ::Locker::Regex
|
10
|
+
include ::Locker::Resource
|
11
|
+
|
12
|
+
route(
|
13
|
+
/^locker\slabel\slist$/,
|
14
|
+
:list,
|
15
|
+
command: true,
|
16
|
+
help: { t('help.label.list.syntax') => t('help.label.list.desc') }
|
17
|
+
)
|
18
|
+
|
19
|
+
route(
|
20
|
+
/^locker\slabel\screate\s#{LABEL_REGEX}$/,
|
21
|
+
:create,
|
22
|
+
command: true,
|
23
|
+
help: { t('help.label.create.syntax') => t('help.label.create.desc') }
|
24
|
+
)
|
25
|
+
|
26
|
+
route(
|
27
|
+
/^locker\slabel\sdelete\s#{LABEL_REGEX}$/,
|
28
|
+
:delete,
|
29
|
+
command: true,
|
30
|
+
help: { t('help.label.delete.syntax') => t('help.label.delete.desc') }
|
31
|
+
)
|
32
|
+
|
33
|
+
route(
|
34
|
+
/^locker\slabel\sshow\s#{LABEL_REGEX}$/,
|
35
|
+
:show,
|
36
|
+
command: true,
|
37
|
+
help: { t('help.label.show.syntax') => t('help.label.show.desc') }
|
38
|
+
)
|
39
|
+
|
40
|
+
route(
|
41
|
+
/^locker\slabel\sadd\s#{RESOURCE_REGEX}\sto\s#{LABEL_REGEX}$/,
|
42
|
+
:add,
|
43
|
+
command: true,
|
44
|
+
help: { t('help.label.add.syntax') => t('help.label.add.desc') }
|
45
|
+
)
|
46
|
+
|
47
|
+
route(
|
48
|
+
/^locker\slabel\sremove\s#{RESOURCE_REGEX}\sfrom\s#{LABEL_REGEX}$/,
|
49
|
+
:remove,
|
50
|
+
command: true,
|
51
|
+
help: { t('help.label.remove.syntax') => t('help.label.remove.desc') }
|
52
|
+
)
|
53
|
+
|
54
|
+
def list(response)
|
55
|
+
labels.sort.each do |n|
|
56
|
+
name = n.sub('label_', '')
|
57
|
+
l = label(name)
|
58
|
+
response.reply(t('label.desc', name: name, state: l['state']))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def create(response)
|
63
|
+
name = response.matches[0][0]
|
64
|
+
if create_label(name)
|
65
|
+
response.reply(t('label.created', name: name))
|
66
|
+
else
|
67
|
+
response.reply(t('label.exists', name: name))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def delete(response)
|
72
|
+
name = response.matches[0][0]
|
73
|
+
if delete_label(name)
|
74
|
+
response.reply(t('label.deleted', name: name))
|
75
|
+
else
|
76
|
+
response.reply(t('label.does_not_exist', name: name))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def show(response)
|
81
|
+
name = response.matches[0][0]
|
82
|
+
return response.reply(t('label.does_not_exist', name: name)) unless label_exists?(name)
|
83
|
+
members = label_membership(name)
|
84
|
+
return response.reply(t('label.has_no_resources', name: name)) unless members.count > 0
|
85
|
+
response.reply(t('label.resources', name: name, resources: members.join(', ')))
|
86
|
+
end
|
87
|
+
|
88
|
+
def add(response)
|
89
|
+
resource_name = response.matches[0][0]
|
90
|
+
label_name = response.matches[0][1]
|
91
|
+
return response.reply(t('label.does_not_exist', name: label_name)) unless label_exists?(label_name)
|
92
|
+
return response.reply(t('resource.does_not_exist', name: resource_name)) unless resource_exists?(resource_name)
|
93
|
+
add_resource_to_label(label_name, resource_name)
|
94
|
+
response.reply(t('label.resource_added', label: label_name, resource: resource_name))
|
95
|
+
end
|
96
|
+
|
97
|
+
def remove(response)
|
98
|
+
resource_name = response.matches[0][0]
|
99
|
+
label_name = response.matches[0][1]
|
100
|
+
return response.reply(t('label.does_not_exist', name: label_name)) unless label_exists?(label_name)
|
101
|
+
return response.reply(t('resource.does_not_exist', name: resource_name)) unless resource_exists?(resource_name)
|
102
|
+
members = label_membership(label_name)
|
103
|
+
if members.include?(resource_name)
|
104
|
+
remove_resource_from_label(label_name, resource_name)
|
105
|
+
response.reply(t('label.resource_removed', label: label_name, resource: resource_name))
|
106
|
+
else
|
107
|
+
response.reply(t('label.does_not_have_resource', label: label_name, resource: resource_name))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Lita.register_handler(LockerLabels)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
# Misc Locker handlers
|
4
|
+
class LockerMisc < Handler
|
5
|
+
namespace 'Locker'
|
6
|
+
|
7
|
+
include ::Locker::Label
|
8
|
+
include ::Locker::Misc
|
9
|
+
include ::Locker::Regex
|
10
|
+
include ::Locker::Resource
|
11
|
+
|
12
|
+
route(
|
13
|
+
/^locker\sstatus\s#{LABEL_REGEX}$/,
|
14
|
+
:status,
|
15
|
+
command: true,
|
16
|
+
help: { t('help.status.syntax') => t('help.status.desc') }
|
17
|
+
)
|
18
|
+
|
19
|
+
route(
|
20
|
+
/^locker\slist\s#{USER_REGEX}$/,
|
21
|
+
:list,
|
22
|
+
command: true,
|
23
|
+
help: { t('help.list.syntax') => t('help.list.desc') }
|
24
|
+
)
|
25
|
+
|
26
|
+
def status(response)
|
27
|
+
name = response.matches[0][0]
|
28
|
+
if label_exists?(name)
|
29
|
+
l = label(name)
|
30
|
+
if l['owner_id'] && l['owner_id'] != ''
|
31
|
+
o = Lita::User.find_by_id(l['owner_id'])
|
32
|
+
response.reply(t('label.desc_owner', name: name,
|
33
|
+
state: l['state'],
|
34
|
+
owner_name: o.name))
|
35
|
+
else
|
36
|
+
response.reply(t('label.desc', name: name, state: l['state']))
|
37
|
+
end
|
38
|
+
elsif resource_exists?(name)
|
39
|
+
r = resource(name)
|
40
|
+
response.reply(t('resource.desc', name: name, state: r['state']))
|
41
|
+
else
|
42
|
+
response.reply(t('subject.does_not_exist', name: name))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def list(response)
|
47
|
+
username = response.match_data['username']
|
48
|
+
user = Lita::User.fuzzy_find(username)
|
49
|
+
return response.reply('Unknown user') unless user
|
50
|
+
l = user_locks(user)
|
51
|
+
return response.reply('That user has no active locks') unless l.size > 0
|
52
|
+
composed = ''
|
53
|
+
l.each do |label_name|
|
54
|
+
composed += "Label: #{label_name}\n"
|
55
|
+
end
|
56
|
+
response.reply(composed)
|
57
|
+
end
|
58
|
+
|
59
|
+
Lita.register_handler(LockerMisc)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Lita
|
2
|
+
module Handlers
|
3
|
+
# Resource-related handlers
|
4
|
+
class LockerResources < Handler
|
5
|
+
namespace 'Locker'
|
6
|
+
|
7
|
+
include ::Locker::Label
|
8
|
+
include ::Locker::Misc
|
9
|
+
include ::Locker::Regex
|
10
|
+
include ::Locker::Resource
|
11
|
+
|
12
|
+
route(
|
13
|
+
/^locker\sresource\slist$/,
|
14
|
+
:list,
|
15
|
+
command: true,
|
16
|
+
help: { t('help.resource.list.syntax') => t('help.resource.list.desc') }
|
17
|
+
)
|
18
|
+
|
19
|
+
route(
|
20
|
+
/^locker\sresource\screate\s#{RESOURCE_REGEX}$/,
|
21
|
+
:create,
|
22
|
+
command: true,
|
23
|
+
restrict_to: [:locker_admins],
|
24
|
+
help: {
|
25
|
+
t('help.resource.create.syntax') => t('help.resource.create.desc')
|
26
|
+
}
|
27
|
+
)
|
28
|
+
|
29
|
+
route(
|
30
|
+
/^locker\sresource\sdelete\s#{RESOURCE_REGEX}$/,
|
31
|
+
:delete,
|
32
|
+
command: true,
|
33
|
+
restrict_to: [:locker_admins],
|
34
|
+
help: {
|
35
|
+
t('help.resource.delete.syntax') => t('help.resource.delete.desc')
|
36
|
+
}
|
37
|
+
)
|
38
|
+
|
39
|
+
route(
|
40
|
+
/^locker\sresource\sshow\s#{RESOURCE_REGEX}$/,
|
41
|
+
:show,
|
42
|
+
command: true,
|
43
|
+
help: { t('help.resource.show.syntax') => t('help.resource.show.desc') }
|
44
|
+
)
|
45
|
+
|
46
|
+
def list(response)
|
47
|
+
output = ''
|
48
|
+
resources.each do |r|
|
49
|
+
r_name = r.sub('resource_', '')
|
50
|
+
res = resource(r_name)
|
51
|
+
output += t('resource.desc', name: r_name, state: res['state'])
|
52
|
+
end
|
53
|
+
response.reply(output)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create(response)
|
57
|
+
name = response.matches[0][0]
|
58
|
+
if create_resource(name)
|
59
|
+
response.reply(t('resource.created', name: name))
|
60
|
+
else
|
61
|
+
response.reply(t('resource.exists', name: name))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def delete(response)
|
66
|
+
name = response.matches[0][0]
|
67
|
+
return response.reply(t('resource.does_not_exist', name: name)) unless resource_exists?(name)
|
68
|
+
delete_resource(name)
|
69
|
+
response.reply(t('resource.deleted', name: name))
|
70
|
+
end
|
71
|
+
|
72
|
+
def show(response)
|
73
|
+
name = response.matches[0][0]
|
74
|
+
return response.reply(t('resource.does_not_exist', name: name)) unless resource_exists?(name)
|
75
|
+
r = resource(name)
|
76
|
+
response.reply(t('resource.desc', name: name, state: r['state']))
|
77
|
+
end
|
78
|
+
|
79
|
+
Lita.register_handler(LockerResources)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/locker/label.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Locker subsystem
|
2
|
+
module Locker
|
3
|
+
# Label helpers
|
4
|
+
module Label
|
5
|
+
def label(name)
|
6
|
+
redis.hgetall("label_#{name}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def labels
|
10
|
+
redis.keys('label_*')
|
11
|
+
end
|
12
|
+
|
13
|
+
def label_exists?(name)
|
14
|
+
redis.exists("label_#{name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock_label!(name, owner, time_until)
|
18
|
+
return false unless label_exists?(name)
|
19
|
+
key = "label_#{name}"
|
20
|
+
members = label_membership(name)
|
21
|
+
members.each do |m|
|
22
|
+
return false unless lock_resource!(m, owner, time_until)
|
23
|
+
end
|
24
|
+
redis.hset(key, 'state', 'locked')
|
25
|
+
redis.hset(key, 'owner_id', owner.id)
|
26
|
+
redis.hset(key, 'until', time_until)
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
def unlock_label!(name)
|
31
|
+
return false unless label_exists?(name)
|
32
|
+
key = "label_#{name}"
|
33
|
+
members = label_membership(name)
|
34
|
+
members.each do |m|
|
35
|
+
unlock_resource!(m)
|
36
|
+
end
|
37
|
+
redis.hset(key, 'state', 'unlocked')
|
38
|
+
redis.hset(key, 'owner_id', '')
|
39
|
+
true
|
40
|
+
end
|
41
|
+
|
42
|
+
def create_label(name)
|
43
|
+
label_key = "label_#{name}"
|
44
|
+
redis.hset(label_key, 'state', 'unlocked') unless
|
45
|
+
resource_exists?(name) || label_exists?(name)
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_label(name)
|
49
|
+
label_key = "label_#{name}"
|
50
|
+
redis.del(label_key) if label_exists?(name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def label_membership(name)
|
54
|
+
redis.smembers("membership_#{name}")
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_resource_to_label(label, resource)
|
58
|
+
return unless label_exists?(label) && resource_exists?(resource)
|
59
|
+
redis.sadd("membership_#{label}", resource)
|
60
|
+
end
|
61
|
+
|
62
|
+
def remove_resource_from_label(label, resource)
|
63
|
+
return unless label_exists?(label) && resource_exists?(resource)
|
64
|
+
redis.srem("membership_#{label}", resource)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/locker/misc.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Locker subsystem
|
2
|
+
module Locker
|
3
|
+
# Misc helpers
|
4
|
+
module Misc
|
5
|
+
def user_locks(user)
|
6
|
+
owned = []
|
7
|
+
labels.each do |name|
|
8
|
+
name.slice! 'label_'
|
9
|
+
label = label(name)
|
10
|
+
owned.push(name) if label['owner_id'] == user.id
|
11
|
+
end
|
12
|
+
owned
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/locker/regex.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
# Locker subsystem
|
2
|
+
module Locker
|
3
|
+
# Regex definitions
|
4
|
+
module Regex
|
5
|
+
LABEL_REGEX = /([\.\w\s-]+)/
|
6
|
+
RESOURCE_REGEX = /([\.\w-]+)/
|
7
|
+
COMMENT_REGEX = /(\s\#.+)?/
|
8
|
+
LOCK_REGEX = /\(lock\)\s/i
|
9
|
+
USER_REGEX = /(?:@)?(?<username>[\w\s]+)/
|
10
|
+
UNLOCK_REGEX = /(?:\(unlock\)|\(release\))\s/i
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# Locker subsystem
|
2
|
+
module Locker
|
3
|
+
# Resource helpers
|
4
|
+
module Resource
|
5
|
+
def resource(name)
|
6
|
+
redis.hgetall("resource_#{name}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def resources
|
10
|
+
redis.keys('resource_*')
|
11
|
+
end
|
12
|
+
|
13
|
+
def resource_exists?(name)
|
14
|
+
redis.exists("resource_#{name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def lock_resource!(name, owner, time_until)
|
18
|
+
return false unless resource_exists?(name)
|
19
|
+
resource_key = "resource_#{name}"
|
20
|
+
value = redis.hget(resource_key, 'state')
|
21
|
+
return false unless value == 'unlocked'
|
22
|
+
# FIXME: Race condition!
|
23
|
+
redis.hset(resource_key, 'state', 'locked')
|
24
|
+
redis.hset(resource_key, 'owner_id', owner.id)
|
25
|
+
redis.hset(resource_key, 'until', time_until)
|
26
|
+
true
|
27
|
+
end
|
28
|
+
|
29
|
+
def unlock_resource!(name)
|
30
|
+
return false unless resource_exists?(name)
|
31
|
+
key = "resource_#{name}"
|
32
|
+
redis.hset(key, 'state', 'unlocked')
|
33
|
+
redis.hset(key, 'owner_id', '')
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def create_resource(name)
|
38
|
+
resource_key = "resource_#{name}"
|
39
|
+
redis.hset(resource_key, 'state', 'unlocked') unless
|
40
|
+
resource_exists?(name) || label_exists?(name)
|
41
|
+
end
|
42
|
+
|
43
|
+
def delete_resource(name)
|
44
|
+
resource_key = "resource_#{name}"
|
45
|
+
redis.del(resource_key) if resource_exists?(name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|