zabbirc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,31 @@
1
+ module Zabbirc
2
+ module Services
3
+ class Events < Base
4
+ def iterate
5
+ synchronize do
6
+ recent_events = Zabbix::Event.recent
7
+ recent_events = filter_out_repeated_events(recent_events)
8
+
9
+ recent_events.each do |event|
10
+ @service.ops.interested_in(event).notify event
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def filter_out_repeated_events events
18
+ triggers = events.group_by{|e| e.related_object.id }
19
+ triggers.collect do |_id, events|
20
+ events.sort_by{|e| e.created_at }.last
21
+ end
22
+ end
23
+
24
+ def send_notifications op, events
25
+ events.each do |event|
26
+ op.notify event
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ module Zabbirc
2
+ module Services
3
+ class Ops < Base
4
+ def iterate
5
+ synchronize do
6
+ @cinch_bot.channels.each do |channel|
7
+ sync_ops channel
8
+ end
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def channel_nicks channel
15
+ channel.users.keys.collect(&:nick)
16
+ end
17
+
18
+ def channel_find_user channel, nick
19
+ channel.users.keys.find { |irc_user| irc_user.nick == nick }
20
+ end
21
+
22
+ def sync_ops channel
23
+ nicks = channel_nicks channel
24
+ zabbix_users = Zabbix::User.get filter: { alias: nicks }
25
+ zabbix_users.each do |zabbix_user|
26
+ irc_user = channel_find_user channel, zabbix_user.alias
27
+ op = @service.ops.add(Op.new(zabbix_user: zabbix_user, irc_user: irc_user))
28
+ op.add_channel channel
29
+ end
30
+
31
+ @service.ops.each do |op|
32
+ op.remove_channel channel unless nicks.include? op.nick
33
+ end
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,31 @@
1
+ module Zabbirc
2
+ class Setting
3
+ DEFAULTS = {
4
+ notify: true,
5
+ primary_channel: nil,
6
+ events_priority: :information
7
+ }
8
+
9
+ def initialize
10
+ @options = ActiveSupport::HashWithIndifferentAccess.new DEFAULTS.deep_dup
11
+ end
12
+
13
+ def set name, value
14
+ @options[name] = value
15
+ end
16
+
17
+ def get name
18
+ @options[name]
19
+ end
20
+
21
+ def fetch name, value
22
+ @options[name] ||= value
23
+ end
24
+
25
+ def to_s
26
+ @options.collect do |k, v|
27
+ "#{k}: #{v}"
28
+ end.join(", ")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ # This class is used for stopping service threads
2
+ class StopError < StandardError
3
+ end
@@ -0,0 +1,26 @@
1
+ require 'zabbix/client'
2
+
3
+ module Zabbirc
4
+ module Zabbix
5
+ class Connection
6
+ attr_reader :client
7
+
8
+ def self.get_connection
9
+ Thread.current[:zabbix_connection] ||= self.new
10
+ end
11
+
12
+ def self.test_connection
13
+ self.new
14
+ true
15
+ rescue => e
16
+ Zabbirc.logger.fatal "Could not connect to zabbix: #{e}"
17
+ false
18
+ end
19
+
20
+ def initialize
21
+ @client = ::Zabbix::Client.new(Zabbirc.config.zabbix_api_url, debug: false)
22
+ @client.user.login(user: Zabbirc.config.zabbix_login, password: Zabbirc.config.zabbix_password)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,94 @@
1
+ module Zabbirc
2
+ module Zabbix
3
+ class Event < Resource::Base
4
+ has_many :hosts
5
+
6
+ def self.recent options={}
7
+ params = {
8
+ acknowledged: false,
9
+ time_from: Zabbirc.config.notify_about_events_from_last.ago.utc.to_i,
10
+ priority_from: 0,
11
+ selectRelatedObject: :extend,
12
+ selectHosts: :extend
13
+ }.merge(options)
14
+
15
+ priority_from = Priority.new(params.delete(:priority_from))
16
+ events = get params
17
+ events = events.find_all{|e| e.priority >= priority_from }
18
+ events.sort{|x,y| x.priority <=> y.priority }
19
+ end
20
+
21
+ attr_reader :attrs
22
+
23
+ delegate :priority, :priority_code, to: :related_object
24
+
25
+ def related_object
26
+ raise AttributeError, "`source` attribute required" if @attrs[:source].blank?
27
+ raise AttributeError, "`object` attribute required" if @attrs[:object].blank?
28
+ @related_object ||= determine_related_object
29
+ end
30
+
31
+ def acknowledge message
32
+ res = api.event.acknowledge(eventids: id, message: message)
33
+ res["eventids"].collect(&:to_i).include? id.to_i
34
+ end
35
+
36
+ def acknowledged?
37
+ acknowledged.to_i == 1
38
+ end
39
+
40
+ def created_at
41
+ Time.at(clock.to_i)
42
+ end
43
+
44
+ def value
45
+ case @attrs[:source].to_i
46
+ when 0
47
+ case @attrs[:value].to_i
48
+ when 0
49
+ :ok
50
+ when 1
51
+ :problem
52
+ end
53
+ else
54
+ @attrs[:value]
55
+ end
56
+ end
57
+
58
+ alias_method :state, :value
59
+
60
+ def message
61
+ desc = related_object.description
62
+ if desc.include?("{HOST.NAME}")
63
+ desc.sub("{HOST.NAME}", hosts.collect(&:host).join(', '))
64
+ else
65
+ "#{desc} on #{hosts.collect(&:host).join(', ')}"
66
+ end
67
+ end
68
+
69
+ def label
70
+ format_label "|%id| %time [%priority-code] %msg - %state"
71
+ end
72
+
73
+ def format_label fmt
74
+ fmt.gsub("%priority-code", "#{priority.code}").
75
+ gsub("%priority-num", "#{priority.number}").
76
+ gsub("%time", "#{created_at.to_formatted_s(:short)}").
77
+ gsub("%msg", "#{message}").
78
+ gsub("%id", "#{id}").
79
+ gsub("%state", "#{state}")
80
+ end
81
+
82
+ private
83
+
84
+ def determine_related_object
85
+ case @attrs[:object].to_i
86
+ when 0
87
+ @attrs[:relatedObject] ? Trigger.new(@attrs[:relatedObject]) : Trigger.find(@attrs[:objectid])
88
+ else
89
+ raise StandardError, "related object #{@attrs[:object].to_i} not implemented yet"
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,6 @@
1
+ module Zabbirc
2
+ module Zabbix
3
+ class Host < Resource::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,30 @@
1
+ module Zabbirc
2
+ module Zabbix
3
+ module Resource
4
+ module Associations
5
+ def has_many name
6
+ define_method name do
7
+ @associations ||= ActiveSupport::HashWithIndifferentAccess.new
8
+ @associations[name] ||= begin
9
+ assoc_class = Zabbix.const_get(name.to_s.singularize.camelize)
10
+ hash_data = @attrs[name]
11
+ if hash_data.blank?
12
+ this = self.class.find id, :"select#{name.to_s.camelize}" => :extend
13
+ raise StandardError, "zabbix response does not contain #{name}" if this[name].blank?
14
+ hash_data = this[name]
15
+ end
16
+
17
+ hash_data.collect do |obj|
18
+ assoc_class.new obj
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def has_one name
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'finders'
2
+ require_relative 'associations'
3
+
4
+ module Zabbirc
5
+ module Zabbix
6
+
7
+ IDNotUniqueError = Class.new(StandardError)
8
+
9
+ module Resource
10
+ class Base
11
+ extend Finders, Associations
12
+
13
+ def self.set_model_name model_name
14
+ @model_name = model_name
15
+ end
16
+
17
+ def self.model_name
18
+ @model_name ||= name.split(/::/).last.underscore
19
+ end
20
+
21
+ def self.api
22
+ Connection.get_connection.client
23
+ end
24
+
25
+ def api
26
+ Connection.get_connection.client
27
+ end
28
+
29
+ def initialize attrs
30
+ @attrs = ActiveSupport::HashWithIndifferentAccess.new attrs
31
+ raise ArgumentError, "attribute `#{self.class.model_name}id` not found, probably not an Event" unless @attrs.key? :"#{self.class.model_name}id"
32
+ end
33
+
34
+ def id
35
+ @attrs["#{self.class.model_name}id"]
36
+ end
37
+
38
+ def [] attr
39
+ @attrs[attr]
40
+ end
41
+
42
+ def method_missing method, *args, &block
43
+ if args.length == 0 and not block_given? and @attrs.key? method
44
+ @attrs[method]
45
+ else
46
+ super
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,37 @@
1
+ module Zabbirc
2
+ module Zabbix
3
+ module Resource
4
+ module Finders
5
+ def find id, *options
6
+ options = options.extract_options!
7
+ options = options.reverse_merge({
8
+ :"#{model_name}ids" => id
9
+ })
10
+ res = api.send(model_name).get options
11
+ if res.size == 0
12
+ nil
13
+ elsif res.size > 1
14
+ raise IDNotUniqueError, "#{model_name.camelize} ID `#{id}` is not unique"
15
+ else
16
+ self.new res.first
17
+ end
18
+ rescue Errno::ETIMEDOUT => e
19
+ Zabbirc.logger.error "Zabbix::Resource#find: #{e}"
20
+ nil
21
+ end
22
+
23
+ def get *options
24
+ options = options.extract_options!
25
+ res = api.send(model_name).get options
26
+ res.collect do |obj|
27
+ self.new obj
28
+ end
29
+ rescue Errno::ETIMEDOUT => e
30
+ Zabbirc.logger.error "Zabbix::Resource#get: #{e}"
31
+ []
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ module Zabbirc
2
+ module Zabbix
3
+ class Trigger < Resource::Base
4
+ has_many :hosts
5
+
6
+ def priority
7
+ Priority.new(super.to_i)
8
+ end
9
+
10
+ def value
11
+ case @attrs[:value].to_i
12
+ when 0
13
+ :ok
14
+ when 1
15
+ :problem
16
+ else
17
+ @attrs[:value]
18
+ end
19
+ end
20
+
21
+ def message
22
+ if description.include?("{HOST.NAME}")
23
+ description.sub("{HOST.NAME}", hosts.collect(&:host).join(', '))
24
+ else
25
+ "#{description} on #{hosts.collect(&:host).join(', ')}"
26
+ end
27
+ end
28
+
29
+ def label
30
+ format_label "%time [%priority-code] %msg - %value"
31
+ end
32
+
33
+ def changed_at
34
+ Time.at(lastchange.to_i)
35
+ end
36
+
37
+ def format_label fmt
38
+ fmt.gsub("%priority-code", "#{priority.code}").
39
+ gsub("%priority-num", "#{priority.number}").
40
+ gsub("%time", "#{changed_at.to_formatted_s(:short)}").
41
+ gsub("%msg", "#{message}").
42
+ gsub("%id", "#{id}").
43
+ gsub("%value", "#{value}")
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,10 @@
1
+ module Zabbirc
2
+ module Zabbix
3
+ class User < Resource::Base
4
+ def self.find_by_alias _alias, options={}
5
+ options = options.merge filter: { alias: _alias }
6
+ get(options).first
7
+ end
8
+ end
9
+ end
10
+ end
data/lib/zabbirc.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'active_support/all'
2
+ require 'singleton'
3
+ require 'dotenv'
4
+ Dotenv.load
5
+
6
+ def require_dir dir
7
+ base_dir = Pathname.new(File.expand_path(File.dirname(__FILE__)))
8
+ Dir.glob(base_dir.join(dir)).each do |f|
9
+ require f
10
+ end
11
+ end
12
+
13
+ module Zabbirc
14
+ def self.synchronize &block
15
+ @mutex ||= Mutex.new
16
+ @mutex.synchronize &block
17
+ end
18
+ end
19
+
20
+ require_dir "zabbirc/*.rb"
21
+ require_dir "zabbirc/irc/*.rb"
22
+ require 'zabbirc/zabbix/resource/base'
23
+ require_dir "zabbirc/zabbix/*.rb"
24
+ require_dir "zabbirc/services/*.rb"
data/spec/bot_spec.rb ADDED
@@ -0,0 +1,186 @@
1
+ describe Zabbirc::Irc::PluginMethods do
2
+ # let(:service) { Zabbirc::ServiceMock.new }
3
+ let(:mock_message) { double("Cinch::Message", user: mock_user) }
4
+ let(:mock_user) { double("Cinch::User", nick: mock_nick) }
5
+ let(:bot) { Zabbirc::MockBot.new }
6
+ let(:mock_nick) { "op1" }
7
+ let(:mock_user_settings) { nil }
8
+
9
+ before do
10
+ bot.setup_op mock_nick, mock_user_settings
11
+ end
12
+
13
+ describe "#acknowledge_event" do
14
+ let(:event) { double "Event", id: 1, label: "Event 1 label" }
15
+ let(:message) { "ack message" }
16
+ before do
17
+ allow(event).to receive(:acknowledge).and_return(true)
18
+ allow(Zabbirc::Zabbix::Event).to receive(:find).and_return(event)
19
+ end
20
+
21
+ it "should acknowledge event" do
22
+ expect(mock_message).to receive(:reply).with("#{mock_nick}: Event `#{event.label}` acknowledged with message: #{message}")
23
+ bot.acknowledge_event mock_message, event.id, message
24
+ end
25
+ end
26
+
27
+ describe "#host_status" do
28
+ before do
29
+ allow(Zabbirc::Zabbix::Host).to receive(:get).and_return(hosts)
30
+ end
31
+
32
+ context "reporting" do
33
+ before do
34
+ allow(Zabbirc::Zabbix::Trigger).to receive(:get).and_return(problem_triggers)
35
+ end
36
+ let(:host) { double "Host", id: 1, name: "Host-1" }
37
+ let(:hosts) { [host] }
38
+ let(:problem_trigger) { double "Trigger", priority: Zabbirc::Priority.new(1), label: "problem_trigger", value: 1 }
39
+ context "problem trigger" do
40
+ let(:problem_triggers) { [problem_trigger] }
41
+ let(:expected_msg) do
42
+ msg = ["#{mock_nick}: Host: #{host.name} - status: #{problem_triggers.size} problems"]
43
+ problem_triggers.each do |trigger|
44
+ msg << "#{mock_nick}: status: #{trigger.label}"
45
+ end
46
+ msg.join("\n")
47
+ end
48
+ it "should report problem" do
49
+ expect(mock_message).to receive(:reply).with(expected_msg)
50
+ bot.host_status mock_message, host.name
51
+ end
52
+ end
53
+
54
+ context "ok trigger" do
55
+ let(:problem_triggers) { [] }
56
+ let(:expected_msg) { "#{mock_nick}: Host: #{host.name} - status: OK" }
57
+ it "should report problem" do
58
+ expect(mock_message).to receive(:reply).with(expected_msg)
59
+ bot.host_status mock_message, host.name
60
+ end
61
+ end
62
+ end # context reporting
63
+
64
+ context "host identification" do
65
+ context "no hosts" do
66
+ let(:hosts) { [] }
67
+ let(:host_name) { "undefined_host_name" }
68
+ it "should not found host" do
69
+ expect(mock_message).to receive(:reply).with("#{mock_nick}: Host not found `#{host_name}`")
70
+ bot.host_status mock_message, host_name
71
+ end
72
+ end
73
+
74
+ context "2 - 10 hosts" do
75
+ let(:host1) { double "Host1", id: 1, name: "host-1" }
76
+ let(:host2) { double "Host2", id: 2, name: "host-2" }
77
+ let(:hosts) { [host1, host2] }
78
+ let(:expected_msg) { "#{mock_nick}: Found #{hosts.size} hosts: #{hosts.collect(&:name).join(', ')}. Be more specific" }
79
+ it "should print host names" do
80
+ expect(mock_message).to receive(:reply).with(expected_msg)
81
+ bot.host_status mock_message, "host"
82
+ end
83
+ end
84
+
85
+ context "more than 10 hosts" do
86
+ let(:hosts) { double "HostsArray", size: 11 }
87
+ let(:expected_msg) { "#{mock_nick}: Found #{hosts.size} Be more specific" }
88
+ it "should print host names" do
89
+ expect(mock_message).to receive(:reply).with(expected_msg)
90
+ bot.host_status mock_message, "host"
91
+ end
92
+ end
93
+ end # context host identification
94
+ end # context #host_status
95
+
96
+ context "#host_latest" do
97
+ let(:host) { double "Host", id: 1, name: "Host1" }
98
+ let(:hosts) { [host] }
99
+ let(:event1) { double "Event", label: "Event 1 label" }
100
+ let(:events) { [event1] }
101
+ let(:expected_msg) do
102
+ msg = ["#{mock_nick}: Host: #{host.name} - showing last #{events.size} events"]
103
+ events.each do |event|
104
+ msg << "#{mock_nick}: !latest: #{event.label}"
105
+ end
106
+ msg.join("\n")
107
+ end
108
+ before do
109
+ allow(Zabbirc::Zabbix::Host).to receive(:get).and_return(hosts)
110
+ allow(Zabbirc::Zabbix::Event).to receive(:get).and_return(events)
111
+ end
112
+
113
+ it "should print latest events" do
114
+ expect(mock_message).to receive(:reply).with(expected_msg)
115
+ bot.host_latest mock_message, "Host1", nil, nil
116
+ end
117
+ end
118
+
119
+ describe "#list_events" do
120
+ before do
121
+ allow(Zabbirc::Zabbix::Event).to receive(:recent).and_return(recent_events)
122
+ end
123
+ context "no last events" do
124
+ let(:recent_events) { [] }
125
+
126
+ it "should report no last events" do
127
+ expect(mock_message).to receive(:reply).with("#{mock_nick}: No last events")
128
+ bot.list_events mock_message
129
+ end
130
+ end
131
+
132
+ context "no last events" do
133
+ let(:event1) { double "Event1", label: "Event 1 label" }
134
+ let(:event2) { double "Event2", label: "Event 2 label" }
135
+ let(:recent_events) { [event1, event2] }
136
+ let(:expected_msg) { recent_events.collect{|e| "#{mock_nick}: #{e.label}"}.join("\n") }
137
+
138
+ it "should report no last events" do
139
+ expect(mock_message).to receive(:reply).with(expected_msg)
140
+ bot.list_events mock_message
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "#show_settings" do
146
+ let(:mock_user_settings) { {primary_channel: "#channel-1", events_priority: "high", notify: false } }
147
+ let(:expected_msg) { "#{mock_nick}: notify: false, primary_channel: #channel-1, events_priority: high" }
148
+ it "should show settings" do
149
+ expect(mock_message).to receive(:reply).with(expected_msg)
150
+ bot.show_settings mock_message
151
+ end
152
+ end
153
+
154
+ describe "#set_setting" do
155
+ shared_examples "set_setting" do |key, value, expected_setting_value|
156
+ let(:expected_msg) { "#{mock_nick}: setting `#{key}` was set to `#{expected_setting_value}`" }
157
+ let(:op) { bot.get_op mock_nick }
158
+ it "should set #{key} setting to #{value}" do
159
+ expect(mock_message).to receive(:reply).with(expected_msg)
160
+ bot.set_setting mock_message, key, nil, value
161
+ expect(op.setting.get(key)).to eq expected_setting_value
162
+ end
163
+ end
164
+
165
+
166
+ context "notify" do
167
+ it_should_behave_like "set_setting", "notify", "false", false
168
+ it_should_behave_like "set_setting", "notify", "true", true
169
+ end
170
+
171
+ context "events_priority" do
172
+ it_should_behave_like "set_setting", "events_priority", "high", :high
173
+ it_should_behave_like "set_setting", "events_priority", "5", :disaster
174
+ end
175
+
176
+ context "primary_channel" do
177
+ before do
178
+ op.add_channel double("#channel1", name: "#channel1")
179
+ op.add_channel double("#channel2double", name: "#channel2")
180
+ end
181
+ it_should_behave_like "set_setting", "primary_channel", "#channel1", "#channel1"
182
+ it_should_behave_like "set_setting", "primary_channel", "#channel2", "#channel2"
183
+ end
184
+
185
+ end
186
+ end