zabbirc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/zabbirc +32 -0
- data/bin/zabbirc-install +16 -0
- data/config/config.rb +5 -0
- data/lib/zabbirc/configuration.rb +43 -0
- data/lib/zabbirc/irc/plugin.rb +20 -0
- data/lib/zabbirc/irc/plugin_methods.rb +208 -0
- data/lib/zabbirc/logger.rb +45 -0
- data/lib/zabbirc/op.rb +67 -0
- data/lib/zabbirc/op_list.rb +47 -0
- data/lib/zabbirc/priority.rb +38 -0
- data/lib/zabbirc/service.rb +86 -0
- data/lib/zabbirc/services/base.rb +56 -0
- data/lib/zabbirc/services/events.rb +31 -0
- data/lib/zabbirc/services/ops.rb +38 -0
- data/lib/zabbirc/setting.rb +31 -0
- data/lib/zabbirc/stop_error.rb +3 -0
- data/lib/zabbirc/zabbix/connection.rb +26 -0
- data/lib/zabbirc/zabbix/event.rb +94 -0
- data/lib/zabbirc/zabbix/host.rb +6 -0
- data/lib/zabbirc/zabbix/resource/associations.rb +30 -0
- data/lib/zabbirc/zabbix/resource/base.rb +52 -0
- data/lib/zabbirc/zabbix/resource/finders.rb +37 -0
- data/lib/zabbirc/zabbix/trigger.rb +47 -0
- data/lib/zabbirc/zabbix/user.rb +10 -0
- data/lib/zabbirc.rb +24 -0
- data/spec/bot_spec.rb +186 -0
- data/spec/spec_helper.rb +96 -0
- data/spec/support/mock_bot.rb +29 -0
- data/templates/zabbirc_config.rb +14 -0
- data/thread_test.rb +46 -0
- data/tmp/playground.rb +37 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fa6b2a62b76aecb1541a8608444b54aa0435258a
|
4
|
+
data.tar.gz: cd3e76f12c22caed15cf276cf4518dac82160946
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec786a1141cff5e769b91cefe1ca45b9e2fa97b7f4a15ad70f5fa13c614acfb63c4674f606f365d473e2f92938589fc154703ea2cad767b661f8869196141be9
|
7
|
+
data.tar.gz: 3a0b23a5017ceb2f8aabdcc73af0a300bb2795595a2f813a77d1ff054a35d0ed469b7b44869c9a2dfd5071777eca7269b20af4d07651cb9bc907098fb5748a4a
|
data/bin/zabbirc
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'zabbirc'
|
3
|
+
require 'pry'
|
4
|
+
this_file = __FILE__
|
5
|
+
config_path = File.expand_path(ARGV[0])
|
6
|
+
|
7
|
+
unless config_path
|
8
|
+
$stderr.puts "No config path specified"
|
9
|
+
exit false
|
10
|
+
end
|
11
|
+
unless File.exists?(config_path)
|
12
|
+
$stderr.puts "Could not find config file: #{config_path}"
|
13
|
+
exit false
|
14
|
+
end
|
15
|
+
|
16
|
+
require_relative config_path
|
17
|
+
|
18
|
+
Zabbirc.logger # initializes logger
|
19
|
+
exit false unless Zabbirc::Zabbix::Connection.test_connection
|
20
|
+
|
21
|
+
s = Zabbirc::Service.new
|
22
|
+
|
23
|
+
trap "SIGINT" do
|
24
|
+
s.stop
|
25
|
+
end
|
26
|
+
|
27
|
+
trap "SIGTERM" do
|
28
|
+
s.stop
|
29
|
+
end
|
30
|
+
|
31
|
+
s.start
|
32
|
+
s.join
|
data/bin/zabbirc-install
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
case ARGV.size
|
3
|
+
when 0
|
4
|
+
install_path = Dir.pwd
|
5
|
+
when 1
|
6
|
+
install_path = ARGV[0]
|
7
|
+
when 2
|
8
|
+
puts "Too many arguments"
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
puts "Installing config file into: #{install_path}"
|
13
|
+
templates_path = Pathname.new(File.expand_path(Pathname.new(File.dirname(__FILE__)).join("../templates")))
|
14
|
+
|
15
|
+
FileUtils.cp(templates_path.join("zabbirc_config.rb"), install_path)
|
16
|
+
puts "Installed"
|
data/config/config.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
|
3
|
+
module Zabbirc
|
4
|
+
def self.configure(&block)
|
5
|
+
block.call(@config ||= Zabbirc::Configuration.new)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.config
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
class Configuration #:nodoc:
|
13
|
+
include ActiveSupport::Configurable
|
14
|
+
|
15
|
+
config_accessor :zabbix_api_url
|
16
|
+
config_accessor :zabbix_login
|
17
|
+
config_accessor :zabbix_password
|
18
|
+
|
19
|
+
config_accessor :irc_server
|
20
|
+
config_accessor :irc_channels
|
21
|
+
|
22
|
+
config_accessor :events_check_interval
|
23
|
+
config_accessor :notify_about_events_from_last
|
24
|
+
|
25
|
+
def param_name
|
26
|
+
config.param_name.respond_to?(:call) ? config.param_name.call : config.param_name
|
27
|
+
end
|
28
|
+
|
29
|
+
# define param_name writer (copied from AS::Configurable)
|
30
|
+
writer, line = 'def param_name=(value); config.param_name = value; end', __LINE__
|
31
|
+
singleton_class.class_eval writer, __FILE__, line
|
32
|
+
class_eval writer, __FILE__, line
|
33
|
+
end
|
34
|
+
|
35
|
+
# this is ugly. why can't we pass the default value to config_accessor...?
|
36
|
+
configure do |config|
|
37
|
+
config.events_check_interval = 10.seconds
|
38
|
+
config.notify_about_events_from_last = 5.minutes
|
39
|
+
|
40
|
+
config.irc_server = "irc.freenode.org"
|
41
|
+
config.irc_channels = ["#zabbirc-test", "#zabbirc-test-2"]
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative "plugin_methods"
|
2
|
+
|
3
|
+
module Zabbirc
|
4
|
+
module Irc
|
5
|
+
class Plugin
|
6
|
+
include Cinch::Plugin
|
7
|
+
include PluginMethods
|
8
|
+
|
9
|
+
listen_to :join, method: :sync_ops
|
10
|
+
listen_to :leaving, method: :sync_ops
|
11
|
+
|
12
|
+
match "settings", method: :show_settings
|
13
|
+
match /settings set ([#_a-zA-Z0-9]+)( ([#\-_a-zA-Z0-9]+))?/, method: :set_setting
|
14
|
+
match "events", method: :list_events
|
15
|
+
match /status ([a-zA-Z0-9\-.]+)/, method: :host_status
|
16
|
+
match /latest ([a-zA-Z0-9\-.]+)( (\d+))?/, method: :host_latest
|
17
|
+
match /ack (\d+) (.*)/, method: :acknowledge_event
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Zabbirc
|
2
|
+
module Irc
|
3
|
+
module PluginMethods
|
4
|
+
|
5
|
+
def acknowledge_event m, eventid, message
|
6
|
+
return unless authenticate m.user.nick
|
7
|
+
op = get_op m
|
8
|
+
event = find_event m, eventid
|
9
|
+
return unless event
|
10
|
+
|
11
|
+
if event.acknowledge "#{op.nick}: #{message}"
|
12
|
+
m.reply "#{op.nick}: Event `#{event.label}` acknowledged with message: #{message}"
|
13
|
+
else
|
14
|
+
m.reply "#{op.nick}: Could not acknowledge event `#{event.label}`"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def host_status m, host
|
19
|
+
return unless authenticate m.user.nick
|
20
|
+
op = get_op m
|
21
|
+
host = find_host m, host
|
22
|
+
return unless host
|
23
|
+
|
24
|
+
triggers = Zabbix::Trigger.get(hostids: host.id, filter: {value: 1}, selectHosts: :extend)
|
25
|
+
triggers = triggers.sort{|x,y| x.priority <=> y.priority }
|
26
|
+
msg = ["#{op.nick}: Host: #{host.name}"]
|
27
|
+
if triggers.empty?
|
28
|
+
msg[0] << " - status: OK"
|
29
|
+
else
|
30
|
+
msg[0] << " - status: #{triggers.size} problems"
|
31
|
+
triggers.each do |trigger|
|
32
|
+
msg << "#{op.nick}: status: #{trigger.label}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
m.reply msg.join("\n")
|
36
|
+
end
|
37
|
+
|
38
|
+
def host_latest m, host, _rest, limit
|
39
|
+
limit ||= 8
|
40
|
+
return unless authenticate m.user.nick
|
41
|
+
op = get_op m
|
42
|
+
host = find_host m, host
|
43
|
+
return unless host
|
44
|
+
|
45
|
+
msg = ["#{op.nick}: Host: #{host.name}"]
|
46
|
+
events = Zabbix::Event.get(hostids: host.id, limit: limit, selectHosts: :extend, selectRelatedObject: :extend, sortfield: :clock, sortorder: "DESC")
|
47
|
+
if events.empty?
|
48
|
+
msg[0] << " - no events found"
|
49
|
+
else
|
50
|
+
msg[0] << " - showing last #{events.size} events"
|
51
|
+
events.each do |event|
|
52
|
+
msg << "#{op.nick}: !latest: #{event.label}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
m.reply msg.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
def sync_ops m, u=nil
|
59
|
+
return if u and u.nick == bot.nick
|
60
|
+
bot.zabbirc_service.ops_service.iterate
|
61
|
+
end
|
62
|
+
|
63
|
+
### Settings
|
64
|
+
def show_settings m
|
65
|
+
return unless authenticate m.user.nick
|
66
|
+
op = get_op m
|
67
|
+
m.reply "#{op.nick}: #{op.setting}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_setting m, key, _rest, value
|
71
|
+
return unless authenticate m.user.nick
|
72
|
+
op = get_op m
|
73
|
+
case key
|
74
|
+
when "notify"
|
75
|
+
set_notify m, op, value
|
76
|
+
when "events_priority"
|
77
|
+
set_events_priority m, op, value
|
78
|
+
when "primary_channel"
|
79
|
+
set_primary_channel m, op, value
|
80
|
+
else
|
81
|
+
m.reply "#{op.nick}: unknown setting `#{key}`"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def set_notify m, op, value
|
86
|
+
if value.nil?
|
87
|
+
m.reply "#{op.nick}: notify allowed values: true, on, 1, false, off, 0"
|
88
|
+
return
|
89
|
+
end
|
90
|
+
case value
|
91
|
+
when "true", "on", "1"
|
92
|
+
op.setting.set :notify, true
|
93
|
+
when "false", "off", "0"
|
94
|
+
op.setting.set :notify, false
|
95
|
+
else
|
96
|
+
m.reply "#{op.nick}: uknown value `#{value}`. Allowed values: true, on, 1, false, off, 0"
|
97
|
+
return
|
98
|
+
end
|
99
|
+
m.reply "#{op.nick}: setting `notify` was set to `#{op.setting.get :notify}`"
|
100
|
+
end
|
101
|
+
|
102
|
+
def set_events_priority m, op, value
|
103
|
+
if value.nil?
|
104
|
+
m.reply "#{op.nick}: events_priority allowed values: #{Priority::PRIORITIES.values.collect{|v| "`#{v}`"}.join(', ')} or numeric #{Priority::PRIORITIES.keys.join(", ")} "
|
105
|
+
return
|
106
|
+
end
|
107
|
+
begin
|
108
|
+
value = value.to_i if value =~ /^\d+$/
|
109
|
+
priority = Priority.new value
|
110
|
+
rescue ArgumentError
|
111
|
+
m.reply "#{op.nick}: uknown value `#{value}`. Allowed values: #{Priority::PRIORITIES.values.collect{|v| "`#{v}`"}.join(', ')} or numeric #{Priority::PRIORITIES.keys.join(", ")} "
|
112
|
+
return
|
113
|
+
end
|
114
|
+
op.setting.set :events_priority, priority.code
|
115
|
+
m.reply "#{op.nick}: setting `events_priority` was set to `#{op.setting.get :events_priority}`"
|
116
|
+
end
|
117
|
+
|
118
|
+
def set_primary_channel m, op, value
|
119
|
+
channel_names = op.channels.collect(&:name)
|
120
|
+
if value.nil?
|
121
|
+
m.reply "#{op.nick}: notify allowed values: #{channel_names.join(", ")}"
|
122
|
+
return
|
123
|
+
end
|
124
|
+
case value
|
125
|
+
when *channel_names
|
126
|
+
op.setting.set :primary_channel, value
|
127
|
+
else
|
128
|
+
m.reply "#{op.nick}: uknown value `#{value}`. Allowed values: #{channel_names.join(", ")}"
|
129
|
+
return
|
130
|
+
end
|
131
|
+
m.reply "#{op.nick}: setting `primary_channel` was set to `#{op.setting.get :primary_channel}`"
|
132
|
+
end
|
133
|
+
|
134
|
+
### Events
|
135
|
+
def list_events m
|
136
|
+
return unless authenticate m.user.nick
|
137
|
+
events = Zabbix::Event.recent
|
138
|
+
msg = if events.any?
|
139
|
+
events.collect do |e|
|
140
|
+
"#{m.user.nick}: #{e.label}"
|
141
|
+
end.join("\n")
|
142
|
+
else
|
143
|
+
"#{m.user.nick}: No last events"
|
144
|
+
end
|
145
|
+
m.reply msg
|
146
|
+
end
|
147
|
+
|
148
|
+
def ops
|
149
|
+
@ops ||= bot.zabbirc_service.ops
|
150
|
+
end
|
151
|
+
|
152
|
+
### Authentication and helpers
|
153
|
+
def authenticate obj
|
154
|
+
nick = get_nick obj
|
155
|
+
ops.authenticate nick
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_op obj
|
159
|
+
nick = get_nick obj
|
160
|
+
ops.get nick
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_nick obj
|
164
|
+
case obj
|
165
|
+
when Cinch::Message
|
166
|
+
obj.user.nick
|
167
|
+
when Cinch::User
|
168
|
+
obj.nick
|
169
|
+
when String
|
170
|
+
obj
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def find_host m, host
|
177
|
+
op = get_op m
|
178
|
+
hosts = Zabbix::Host.get(search: {host: host})
|
179
|
+
case hosts.size
|
180
|
+
when 0
|
181
|
+
m.reply "#{op.nick}: Host not found `#{host}`"
|
182
|
+
when 1
|
183
|
+
return hosts.first
|
184
|
+
when 2..10
|
185
|
+
m.reply "#{op.nick}: Found #{hosts.size} hosts: #{hosts.collect(&:name).join(', ')}. Be more specific"
|
186
|
+
else
|
187
|
+
m.reply "#{op.nick}: Found #{hosts.size} Be more specific"
|
188
|
+
end
|
189
|
+
false
|
190
|
+
end
|
191
|
+
|
192
|
+
def find_event m, eventid
|
193
|
+
op = get_op m
|
194
|
+
begin
|
195
|
+
event = Zabbix::Event.find(eventid, {selectHosts: :extend, selectRelatedObject: :extend})
|
196
|
+
if event.nil?
|
197
|
+
m.reply "#{op.nick} Could not find event with id `#{eventid}`"
|
198
|
+
return false
|
199
|
+
end
|
200
|
+
event
|
201
|
+
rescue Zabbix::IDNotUniqueError => e
|
202
|
+
m.reply "#{op.nick} Could not find event: #{e}"
|
203
|
+
false
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Zabbirc
|
2
|
+
def self.logger
|
3
|
+
@logger ||= ::Logger.new(STDERR, 10, 1.megabyte).tap do |logger|
|
4
|
+
logger.formatter = Zabbirc::Logger::Formatter.new
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Logger
|
9
|
+
class Formatter
|
10
|
+
Format = "%s, [%s#%d T%d] %5s -- %s: %s\n"
|
11
|
+
|
12
|
+
attr_accessor :datetime_format
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@datetime_format = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(severity, time, progname, msg)
|
19
|
+
Format % [severity[0..0], format_datetime(time), $$, Thread.current.object_id, severity, progname, msg2str(msg)]
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def format_datetime(time)
|
25
|
+
if @datetime_format.nil?
|
26
|
+
time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec
|
27
|
+
else
|
28
|
+
time.strftime(@datetime_format)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def msg2str(msg)
|
33
|
+
case msg
|
34
|
+
when ::String
|
35
|
+
msg
|
36
|
+
when ::Exception
|
37
|
+
"#{ msg.message } (#{ msg.class })\n" <<
|
38
|
+
(msg.backtrace || []).join("\n")
|
39
|
+
else
|
40
|
+
msg.inspect
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/zabbirc/op.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Zabbirc
|
2
|
+
class Op
|
3
|
+
|
4
|
+
attr_reader :channels, :setting, :nick
|
5
|
+
def initialize zabbix_user: nil, irc_user: nil
|
6
|
+
raise ArgumentError, 'zabbix_user' if zabbix_user.nil?
|
7
|
+
raise ArgumentError, 'irc_user' if irc_user.nil?
|
8
|
+
@nick = zabbix_user.alias
|
9
|
+
@zabbix_user = zabbix_user
|
10
|
+
@irc_user = irc_user
|
11
|
+
|
12
|
+
@notified_events = {}
|
13
|
+
@channels = Set.new
|
14
|
+
@setting = Setting.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def primary_channel
|
18
|
+
@channels.find{|c| c.name == @setting.get(:primary_channel) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def interesting_priority
|
22
|
+
Priority.new @setting.get(:events_priority)
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_channel channel
|
26
|
+
@setting.fetch :primary_channel, channel
|
27
|
+
@channels << channel
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_channel channel
|
31
|
+
@channels.delete channel
|
32
|
+
|
33
|
+
if channel == @setting.get(:primary_channel)
|
34
|
+
@setting.set :primary_channel, @channels.first
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def notify event
|
39
|
+
return if event.priority < interesting_priority
|
40
|
+
@notified_events ||= {}
|
41
|
+
return if @notified_events.key? event.id
|
42
|
+
channel = primary_channel
|
43
|
+
return unless channel
|
44
|
+
channel.send "#{@nick}: #{event.label}"
|
45
|
+
@notified_events[event.id] = Time.now
|
46
|
+
clear_notified_events
|
47
|
+
end
|
48
|
+
|
49
|
+
def interested_in? event
|
50
|
+
return false unless setting.get :notify
|
51
|
+
return false if @notified_events.key? event.id
|
52
|
+
event.priority >= interesting_priority
|
53
|
+
end
|
54
|
+
|
55
|
+
def event_notified event
|
56
|
+
@notified_events[event.id] = Time.now
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def clear_notified_events
|
62
|
+
@notified_events.delete_if do |event_id, timestamp|
|
63
|
+
timestamp < (Zabbirc.config.notify_about_events_from_last * 2).seconds.ago
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Zabbirc
|
2
|
+
class OpList
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize ops=nil
|
6
|
+
@ops = {}
|
7
|
+
if ops
|
8
|
+
ops.each do |op|
|
9
|
+
add op
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def authenticate name
|
15
|
+
@ops.key? name
|
16
|
+
end
|
17
|
+
|
18
|
+
alias_method :exists?, :authenticate
|
19
|
+
|
20
|
+
def get name
|
21
|
+
@ops[name]
|
22
|
+
end
|
23
|
+
|
24
|
+
def add op
|
25
|
+
if exists? op.nick
|
26
|
+
return get(op.nick)
|
27
|
+
end
|
28
|
+
@ops[op.nick] = op
|
29
|
+
end
|
30
|
+
|
31
|
+
def each &block
|
32
|
+
@ops.values.each &block
|
33
|
+
end
|
34
|
+
|
35
|
+
def interested_in event
|
36
|
+
self.class.new(find_all{ |op| op.interested_in? event })
|
37
|
+
end
|
38
|
+
|
39
|
+
def notify event
|
40
|
+
group_by(&:primary_channel).each do |channel, ops|
|
41
|
+
op_targets = ops.collect{|op| "#{op.nick}:" }.join(" ")
|
42
|
+
channel.send "#{op_targets} #{event.label}"
|
43
|
+
ops.each{ |op| op.event_notified event }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Zabbirc
|
2
|
+
class Priority
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
PRIORITIES = {
|
6
|
+
0 => :not_classified,
|
7
|
+
1 => :information,
|
8
|
+
2 => :warning,
|
9
|
+
3 => :average,
|
10
|
+
4 => :high,
|
11
|
+
5 => :disaster
|
12
|
+
}
|
13
|
+
|
14
|
+
attr_reader :number, :code
|
15
|
+
def initialize priority
|
16
|
+
case priority
|
17
|
+
when String, Symbol
|
18
|
+
raise ArgumentError, "unknown priority `#{priority}`" unless PRIORITIES.key(priority.to_sym)
|
19
|
+
@number = PRIORITIES.key(priority.to_sym)
|
20
|
+
@code = priority.to_sym
|
21
|
+
when Integer
|
22
|
+
raise ArgumentError, "unknown priority `#{priority}`" unless PRIORITIES[priority]
|
23
|
+
@number = priority
|
24
|
+
@code = PRIORITIES[@number]
|
25
|
+
else
|
26
|
+
raise ArgumentError, "cannot create priority from `#{priority}` of class `#{priority.class}`"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def <=> other
|
31
|
+
number <=> other.number
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
code.to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'cinch'
|
2
|
+
|
3
|
+
module Zabbirc
|
4
|
+
class Service
|
5
|
+
attr_reader :cinch_bot
|
6
|
+
attr_reader :ops, :ops_service
|
7
|
+
attr_reader :running
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@semaphores_mutex = Mutex.new
|
11
|
+
@semaphores = Hash.new { |h, k| h[k] = Mutex.new }
|
12
|
+
@ops = OpList.new
|
13
|
+
@running = false
|
14
|
+
|
15
|
+
initialize_bot
|
16
|
+
|
17
|
+
@ops_service = Services::Ops.new self, @cinch_bot
|
18
|
+
@events_service = Services::Events.new self, @cinch_bot
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize_bot
|
22
|
+
@cinch_bot = Cinch::Bot.new do
|
23
|
+
configure do |c|
|
24
|
+
c.server = Zabbirc.config.irc_server
|
25
|
+
c.channels = Zabbirc.config.irc_channels
|
26
|
+
c.nick = "zabbirc"
|
27
|
+
c.plugins.plugins = [Irc::Plugin]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Stores reference to this Zabbirc::Service to be available in plugins
|
32
|
+
@cinch_bot.instance_variable_set :@zabbirc_service, self
|
33
|
+
@cinch_bot.class_eval do
|
34
|
+
attr_reader :zabbirc_service
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def synchronize(name, &block)
|
39
|
+
# Must run the default block +/ fetch in a thread safe way in order to
|
40
|
+
# ensure we always get the same mutex for a given name.
|
41
|
+
semaphore = @semaphores_mutex.synchronize { @semaphores[name] }
|
42
|
+
semaphore.synchronize(&block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def start join=true
|
46
|
+
return if @running
|
47
|
+
@running = true
|
48
|
+
@cinch_bot_thread = Thread.new do
|
49
|
+
begin
|
50
|
+
@cinch_bot.start
|
51
|
+
rescue => e
|
52
|
+
Zabbirc.logger.fatal "Cinch bot start error: #{e}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
@controll_thread = Thread.new do
|
57
|
+
begin
|
58
|
+
sleep
|
59
|
+
rescue StopError
|
60
|
+
@ops_service.stop
|
61
|
+
@events_service.stop
|
62
|
+
|
63
|
+
Zabbirc.logger.info "Stopping ops service: #{@ops_service.join}"
|
64
|
+
Zabbirc.logger.info "Stopping events service: #{@events_service.join}"
|
65
|
+
|
66
|
+
@cinch_bot.quit
|
67
|
+
ensure
|
68
|
+
@running = false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
@ops_service.start
|
73
|
+
@events_service.start
|
74
|
+
|
75
|
+
@cinch_bot_thread.join if join
|
76
|
+
end
|
77
|
+
|
78
|
+
def stop
|
79
|
+
@controll_thread.raise StopError
|
80
|
+
end
|
81
|
+
|
82
|
+
def join
|
83
|
+
@cinch_bot_thread.join
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Zabbirc
|
2
|
+
module Services
|
3
|
+
class Base
|
4
|
+
LOOP_SLEEP = 10
|
5
|
+
attr_reader :running
|
6
|
+
|
7
|
+
def initialize service, cinch_bot
|
8
|
+
@service = service
|
9
|
+
@cinch_bot = cinch_bot
|
10
|
+
@running = false
|
11
|
+
@mutex = Mutex.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def synchronize &block
|
15
|
+
@mutex.synchronize &block
|
16
|
+
end
|
17
|
+
|
18
|
+
def join
|
19
|
+
slept = 0
|
20
|
+
while slept < (LOOP_SLEEP * 2)
|
21
|
+
return true if @running == false
|
22
|
+
sleep 1
|
23
|
+
slept += 1
|
24
|
+
end
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def start
|
29
|
+
main_thread = Thread.current
|
30
|
+
@thread = Thread.new do
|
31
|
+
begin
|
32
|
+
loop do
|
33
|
+
Thread.handle_interrupt(StopError => :never) do
|
34
|
+
@running = true
|
35
|
+
iterate
|
36
|
+
end
|
37
|
+
Thread.handle_interrupt(StopError => :immediate) do
|
38
|
+
sleep LOOP_SLEEP
|
39
|
+
end
|
40
|
+
end
|
41
|
+
rescue StopError => e
|
42
|
+
# nothing, ensure block sets the variables
|
43
|
+
rescue => e
|
44
|
+
main_thread.raise e
|
45
|
+
ensure
|
46
|
+
@running = false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def stop
|
52
|
+
@thread.raise StopError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|