zabbirc 0.0.1

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.
@@ -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