lita-locker 0.5.3 → 0.7.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 +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
|