zabbirc 0.0.1

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: 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
@@ -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,5 @@
1
+ Zabbirc.configure do |config|
2
+ config.zabbix_api_url = ENV['ZABBIX_API_URL']
3
+ config.zabbix_login = ENV['ZABBIX_LOGIN']
4
+ config.zabbix_password = ENV['ZABBIX_PASSWORD']
5
+ end
@@ -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