zabbix_sender_api 1.0.7 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +23 -26
- data/bin/bundle +114 -0
- data/bin/byebug +29 -0
- data/bin/coderay +29 -0
- data/bin/example.rb +29 -0
- data/bin/pry +29 -0
- data/bin/rake +29 -0
- data/lib/zabbix_sender_api/api.rb +166 -30
- data/lib/zabbix_sender_api/version.rb +1 -1
- data/lib/zabbix_sender_api.rb +3 -0
- data/zabbix_sender_api.gemspec +4 -2
- metadata +14 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c23c9561bca313a86ed0bbd449c45214ace6db1053214867759dfe131fdf1721
|
4
|
+
data.tar.gz: 47a7b93f97b811e81b705c58d93dbb8090cf6266f6866e3daa700de0b3891096
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 71bfc54efbc387979ebf53e033863e3c3f9a72e8e6580def7dedf3e46f65107f13472fbf03cbf9e74271358edd2d3c1fff4aca474d8dd7519e0db531dd090bfc
|
7
|
+
data.tar.gz: 6ffb54e8ae9fb8069865b2b748e605948cf4f3f8e53451b4499f959714a735ee3b57c9fd00e7accdcc65df2f894818cf5564610d9ff0e97ecfdc6cf21102252f
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# 1.1.0
|
2
|
+
- added native (socket based) sender logic (Zabbix::Sender::Socket).
|
3
|
+
- changed Zabbix::Sender::Pipe's open method from popen to Open3::popen3 to allow capture of stdout stderf and exitA
|
4
|
+
- the Socket and Pipe .flush method returns status, sendBatchAtomic relays it from its internal call to flush
|
5
|
+
# 1.1.1
|
6
|
+
- added ability to override Zabbix::AgentConfiguration search path
|
7
|
+
- cleaned up a few issues + the docs
|
data/README.md
CHANGED
@@ -1,39 +1,17 @@
|
|
1
1
|
# zabbix_sender_api
|
2
2
|
|
3
|
-
This gem
|
3
|
+
This gem provides a model for assembling and sending data to Zabbix via its sender/trapper mechanism. I works both with the zabbix_sender command line utility by itself using a socket connection to a trapper port direct from Ruby.
|
4
4
|
|
5
5
|
**Detailed documentation for this library is at [rubydoc.info](https://rubydoc.info/gems/zabbix_sender_api)**
|
6
6
|
|
7
7
|
Look at [the source for the zfstozab gem](https://gitlab.com/svdasein/zfstozab/-/blob/master/exe/zabbix-zfs.rb) for a practical example how to use this api.
|
8
8
|
|
9
|
-
|
9
|
+
zabbix_sender_api can send data to zabbix via one of two included connection types:
|
10
|
+
* zabbix_sender (this was the original and for quite some time only mode supported)
|
11
|
+
* TCP socket connection directly to a Zabbix "trapper" port (as of vers 1.1.0)
|
10
12
|
|
11
|
-
* [Zabbix sender manpage](https://www.zabbix.com/documentation/4.0/manpages/zabbix_sender)
|
12
|
-
* [Zabbix sender CLI help](zabbix-sender-help.md)
|
13
13
|
|
14
14
|
|
15
|
-
zabbix_sender_api's function is to generate data to send to a zabbix_sender instance via stdin where zabbix_sender was invoked with these options:
|
16
|
-
|
17
|
-
```
|
18
|
-
-i --input-file input-file Load values from input file. Specify - for
|
19
|
-
standard input. Each line of file contains
|
20
|
-
whitespace delimited: <host> <key> <value>.
|
21
|
-
Specify - in <host> to use hostname from
|
22
|
-
configuration file or --host argument
|
23
|
-
|
24
|
-
-T --with-timestamps Each line of file contains whitespace delimited:
|
25
|
-
<host> <key> <timestamp> <value>. This can be used
|
26
|
-
with --input-file option. Timestamp should be
|
27
|
-
specified in Unix timestamp format
|
28
|
-
```
|
29
|
-
|
30
|
-
It is analogous to:
|
31
|
-
|
32
|
-
\<program that generates sender input\> | zabbix-sender -z \<zabbix-server-or-proxy-ip\> -T -i -
|
33
|
-
|
34
|
-
(You can use this api to just generate output that you then pipe to zabbix_sender yourself if you prefer)
|
35
|
-
|
36
|
-
|
37
15
|
## Installation
|
38
16
|
|
39
17
|
$ gem install zabbix_sender_api
|
@@ -81,6 +59,7 @@ data.addDiscovery(disco)
|
|
81
59
|
|
82
60
|
### Under the hood
|
83
61
|
|
62
|
+
#### Zabbix::Sender::Pipe method:
|
84
63
|
The zabbix-sender cli utility provides a number of methods by which to insert data into zabbix.
|
85
64
|
|
86
65
|
* zabbix-sender ... -s zabbixHostName -k keyName -o value (one k-v pair at a time)
|
@@ -120,3 +99,21 @@ If you wished to use the above lld to actually do some discovery, you'd set thin
|
|
120
99
|
|
121
100
|
![Discovery rule configuration](images/Spectacle.Z29721.png)
|
122
101
|
![Item prototype configuration](images/Spectacle.l29721.png )
|
102
|
+
|
103
|
+
#### Zabbix::Sender::Socket method:
|
104
|
+
|
105
|
+
You can switch between using the Pipe(zabbix_sender) method and the Socket(direct to zabbix via tcp socket) method very simply. Just change:
|
106
|
+
|
107
|
+
```
|
108
|
+
sender = Zabbix::Sender::Pipe.new
|
109
|
+
```
|
110
|
+
to
|
111
|
+
```
|
112
|
+
sender = Zabbix::Sender::Socket.new
|
113
|
+
```
|
114
|
+
|
115
|
+
If you were specifiying a path to zabbix_sender using the Pipe method, remove that. If you're using a socket other than 10051 on your server/proxy, you'll want to add the port: option
|
116
|
+
|
117
|
+
The socket method doesn't support sending multiple batches between flushes, so you should just use sendBatchAtomic(aBatch) with the Socket method.
|
118
|
+
|
119
|
+
You don't have to change anything else - the rest of zabbix_sender_api works exactly the same with the Socket method, save for that it's arguably more efficient with system resources. The only drawback might be if zabbix changes the sender protocol. At that point until this library is update to support the change you can revert to the Pipe/zabbix_sender method if needed (if they change the command line switches radically there, changes will be required in this library as well).
|
data/bin/bundle
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'bundle' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "rubygems"
|
12
|
+
|
13
|
+
m = Module.new do
|
14
|
+
module_function
|
15
|
+
|
16
|
+
def invoked_as_script?
|
17
|
+
File.expand_path($0) == File.expand_path(__FILE__)
|
18
|
+
end
|
19
|
+
|
20
|
+
def env_var_version
|
21
|
+
ENV["BUNDLER_VERSION"]
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_arg_version
|
25
|
+
return unless invoked_as_script? # don't want to hijack other binstubs
|
26
|
+
return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
|
27
|
+
bundler_version = nil
|
28
|
+
update_index = nil
|
29
|
+
ARGV.each_with_index do |a, i|
|
30
|
+
if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
|
31
|
+
bundler_version = a
|
32
|
+
end
|
33
|
+
next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
|
34
|
+
bundler_version = $1
|
35
|
+
update_index = i
|
36
|
+
end
|
37
|
+
bundler_version
|
38
|
+
end
|
39
|
+
|
40
|
+
def gemfile
|
41
|
+
gemfile = ENV["BUNDLE_GEMFILE"]
|
42
|
+
return gemfile if gemfile && !gemfile.empty?
|
43
|
+
|
44
|
+
File.expand_path("../../Gemfile", __FILE__)
|
45
|
+
end
|
46
|
+
|
47
|
+
def lockfile
|
48
|
+
lockfile =
|
49
|
+
case File.basename(gemfile)
|
50
|
+
when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
|
51
|
+
else "#{gemfile}.lock"
|
52
|
+
end
|
53
|
+
File.expand_path(lockfile)
|
54
|
+
end
|
55
|
+
|
56
|
+
def lockfile_version
|
57
|
+
return unless File.file?(lockfile)
|
58
|
+
lockfile_contents = File.read(lockfile)
|
59
|
+
return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
|
60
|
+
Regexp.last_match(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
def bundler_version
|
64
|
+
@bundler_version ||=
|
65
|
+
env_var_version || cli_arg_version ||
|
66
|
+
lockfile_version
|
67
|
+
end
|
68
|
+
|
69
|
+
def bundler_requirement
|
70
|
+
return "#{Gem::Requirement.default}.a" unless bundler_version
|
71
|
+
|
72
|
+
bundler_gem_version = Gem::Version.new(bundler_version)
|
73
|
+
|
74
|
+
requirement = bundler_gem_version.approximate_recommendation
|
75
|
+
|
76
|
+
return requirement unless Gem::Version.new(Gem::VERSION) < Gem::Version.new("2.7.0")
|
77
|
+
|
78
|
+
requirement += ".a" if bundler_gem_version.prerelease?
|
79
|
+
|
80
|
+
requirement
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_bundler!
|
84
|
+
ENV["BUNDLE_GEMFILE"] ||= gemfile
|
85
|
+
|
86
|
+
activate_bundler
|
87
|
+
end
|
88
|
+
|
89
|
+
def activate_bundler
|
90
|
+
gem_error = activation_error_handling do
|
91
|
+
gem "bundler", bundler_requirement
|
92
|
+
end
|
93
|
+
return if gem_error.nil?
|
94
|
+
require_error = activation_error_handling do
|
95
|
+
require "bundler/version"
|
96
|
+
end
|
97
|
+
return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
|
98
|
+
warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
|
99
|
+
exit 42
|
100
|
+
end
|
101
|
+
|
102
|
+
def activation_error_handling
|
103
|
+
yield
|
104
|
+
nil
|
105
|
+
rescue StandardError, LoadError => e
|
106
|
+
e
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
m.load_bundler!
|
111
|
+
|
112
|
+
if m.invoked_as_script?
|
113
|
+
load Gem.bin_path("bundler", "bundle")
|
114
|
+
end
|
data/bin/byebug
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'byebug' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("byebug", "byebug")
|
data/bin/coderay
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'coderay' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("coderay", "coderay")
|
data/bin/example.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'example.rb' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("zabbix_sender_api", "example.rb")
|
data/bin/pry
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'pry' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("pry", "pry")
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
@@ -1,20 +1,28 @@
|
|
1
1
|
module Zabbix
|
2
2
|
require 'socket'
|
3
3
|
require 'json'
|
4
|
+
require 'open3'
|
4
5
|
|
5
6
|
##
|
6
7
|
# AgentConfiguration holds data that's scraped from a zabbix_agentd config file. It's
|
7
|
-
# initialized when the gem is required.
|
8
|
+
# initialized when the gem is required. You may optionally re-initialize the
|
9
|
+
# class with your own list of paths to search. If it finds configuration you can
|
8
10
|
# access it with class methods. This is not meant to be instantiated.
|
9
|
-
|
11
|
+
#
|
10
12
|
class AgentConfiguration
|
11
|
-
|
12
|
-
|
13
|
+
##
|
14
|
+
# You may optionally pass an array of full paths to agent conf files to look for
|
15
|
+
# during initialization. By default some common places are checked, but
|
16
|
+
# you can specify your own. If you call this you'll re-initialize the class, which
|
17
|
+
# will scan for values in any of the listed files it happens to find.
|
18
|
+
#
|
19
|
+
def self.initialize(paths: [
|
13
20
|
'/etc/zabbix/zabbix_agentd.conf',
|
14
21
|
'/usr/local/etc/zabbix/zabbix_agentd.conf',
|
15
22
|
'/opt/zabbix/etc/zabbix_agentd.conf',
|
16
23
|
'/opt/zabbix/conf/zabbix_agentd.conf'
|
17
|
-
]
|
24
|
+
])
|
25
|
+
@agentConfPaths = paths
|
18
26
|
|
19
27
|
@proxy = nil
|
20
28
|
@hostname = nil
|
@@ -63,50 +71,155 @@ module Sender
|
|
63
71
|
require 'set'
|
64
72
|
|
65
73
|
##
|
66
|
-
#
|
67
|
-
#
|
68
|
-
#
|
69
|
-
|
70
|
-
#
|
71
|
-
class Pipe
|
72
|
-
##
|
73
|
-
# pipe file handle object
|
74
|
-
attr_reader :pipe
|
74
|
+
# Connection is an abstract class that defines the basis of specific
|
75
|
+
# connection types (Pipe, Socket). It is not meant to be instantiated
|
76
|
+
# on its own.
|
77
|
+
class Connection
|
75
78
|
##
|
76
79
|
# target host (server or proxy) name or ip
|
77
80
|
attr_reader :targetHost
|
81
|
+
attr_reader :pipe
|
82
|
+
##
|
83
|
+
# Initialize a new Connector object. Proxy is optional.
|
84
|
+
#
|
85
|
+
#
|
86
|
+
# An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
|
87
|
+
# file in one of the usual places and zabbix_sender is on your path, it'll probably
|
88
|
+
# just work
|
89
|
+
#
|
90
|
+
def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy)
|
91
|
+
@targetHost = proxy
|
92
|
+
@pipe = nil
|
93
|
+
end
|
94
|
+
##
|
95
|
+
# Aborts execution if directly called. Subclasses must override.
|
96
|
+
#
|
97
|
+
def open
|
98
|
+
abort("Call to abstract method Connection::open")
|
99
|
+
end
|
100
|
+
##
|
101
|
+
# Aborts execution if directly called. Subclasses must override.
|
102
|
+
#
|
103
|
+
def sendBatch(aBatch)
|
104
|
+
abort("Call to abstract method Connection::sendBatch(aBatch)")
|
105
|
+
end
|
106
|
+
##
|
107
|
+
# Send a Batch instance to zabbix (via zabbix_sender). This opens the pipe,
|
108
|
+
# writes the data to the pipe, and closes the pipe all in one go.
|
109
|
+
#
|
110
|
+
def sendBatchAtomic(aBatch)
|
111
|
+
self.open
|
112
|
+
self.sendBatch(aBatch)
|
113
|
+
return self.flush
|
114
|
+
end
|
115
|
+
##
|
116
|
+
# Closes the zabbix_sender pipe if it's open
|
117
|
+
#
|
118
|
+
def flush
|
119
|
+
if @pipe and not @pipe.closed?
|
120
|
+
@pipe.close
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Socket instances enable TCPSocket based communication with a zabbix trapper server instance
|
127
|
+
#
|
128
|
+
class Socket < Connection
|
129
|
+
attr_reader :port
|
130
|
+
##
|
131
|
+
# Create a new socket connection object. Both proxy and port are optional.
|
132
|
+
#
|
133
|
+
# An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
|
134
|
+
# file in one of the usual places and zabbix_sender is on your path, it'll probably
|
135
|
+
# just work
|
136
|
+
#
|
137
|
+
def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy, port: 10051)
|
138
|
+
super(proxy: proxy)
|
139
|
+
@port = port
|
140
|
+
@lastres = nil
|
141
|
+
end
|
78
142
|
##
|
79
|
-
#
|
80
|
-
|
143
|
+
# Open tcp socket to target proxy/server
|
144
|
+
#
|
145
|
+
def open
|
146
|
+
@pipe = TCPSocket.new(@targetHost, @port)
|
147
|
+
end
|
148
|
+
##
|
149
|
+
# Send aBatch to zabbix via the socket. Note that zabbix will close
|
150
|
+
# the connection after you send a complete message, so you
|
151
|
+
# *can't* do this:
|
152
|
+
#
|
153
|
+
# socket.open
|
154
|
+
# socket.sendBatch(a)
|
155
|
+
# socket.sendBatch(b) <-- this will blow up
|
156
|
+
# socket.flush
|
157
|
+
#
|
158
|
+
# ...as you can with Zabbix::Sender::Pipe. You're really best off
|
159
|
+
# just using sendBatchAtomic for sockets.
|
160
|
+
#
|
161
|
+
# This assumes that the socket is already open.
|
162
|
+
#
|
163
|
+
def sendBatch(aBatch)
|
164
|
+
header = %Q(ZBXD\x01)
|
165
|
+
data = aBatch.to_senderstruct.to_json
|
166
|
+
blob = %Q(#{header}#{[data.bytesize].pack("Q").force_encoding("UTF-8")}#{data})
|
167
|
+
@pipe.write(blob)
|
168
|
+
respHeader = @pipe.read(header.bytesize + 8)
|
169
|
+
datalen = respHeader[header.bytesize, 8].unpack("Q")[0]
|
170
|
+
@lastres = JSON.parse(@pipe.read(datalen))
|
171
|
+
end
|
172
|
+
|
173
|
+
def flush
|
174
|
+
super
|
175
|
+
return @lastres
|
176
|
+
end
|
177
|
+
end
|
81
178
|
|
179
|
+
##
|
180
|
+
# Pipe instances utilize communication to a running instance of zabbix_sender
|
181
|
+
# via a pipe to STDIN. If you want your program to send data itself (as opposed
|
182
|
+
# to say printing stdin lines that you can then pipe to zabbix_sender yourself),
|
183
|
+
# you'll want to make an instance of this
|
184
|
+
#
|
185
|
+
class Pipe < Connection
|
82
186
|
|
83
187
|
##
|
84
|
-
#
|
188
|
+
# Create a new Pipe object. Both proxy: and path: are optional.
|
85
189
|
#
|
86
190
|
# An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
|
87
191
|
# file in one of the usual places and zabbix_sender is on your path, it'll probably
|
88
192
|
# just work
|
89
193
|
#
|
90
194
|
def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy, path: 'zabbix_sender')
|
91
|
-
|
195
|
+
super(proxy: proxy)
|
196
|
+
@wait_thr = @stdout = @stderr = nil
|
92
197
|
@exePath = path
|
93
|
-
@pipe = nil
|
94
198
|
end
|
95
199
|
|
96
200
|
##
|
97
201
|
# Opens a pipe to the zabbix_sender command with appropriate options specified.
|
98
202
|
# If the pipe is already opened when this command is called, it is first closed
|
99
203
|
# via a call to flush
|
204
|
+
#
|
100
205
|
def open
|
101
206
|
self.flush
|
102
|
-
|
207
|
+
#@pipe = IO.popen(%Q(#{@exePath} -z #{@targetHost} -vv -T -i-),'w')
|
208
|
+
cmd = %Q(#{@exePath} -z #{@targetHost} -vv -T -i-)
|
209
|
+
@pipe,@stdout,@stderr,@wait_thr = Open3.popen3(cmd)
|
103
210
|
end
|
104
211
|
|
105
212
|
##
|
106
|
-
# Closes the
|
213
|
+
# Closes the open3 pipe stuff. We need this override method as
|
214
|
+
# closing an open3 pipe requires some extra work.
|
107
215
|
def flush
|
108
216
|
if @pipe and not @pipe.closed?
|
109
217
|
@pipe.close
|
218
|
+
stdout = @stdout.read
|
219
|
+
stderr = @stderr.read
|
220
|
+
@stdout.close
|
221
|
+
@stderr.close
|
222
|
+
return {stdout: stdout, stderr: stderr, success: @wait_thr.value.success?}
|
110
223
|
end
|
111
224
|
end
|
112
225
|
|
@@ -118,14 +231,6 @@ class Pipe
|
|
118
231
|
@pipe.puts(aBatch.to_senderline)
|
119
232
|
end
|
120
233
|
|
121
|
-
##
|
122
|
-
# Send a Batch instance to zabbix (via zabbix_sender). This opens the pipe,
|
123
|
-
# writes the data to the pipe, and closes the pipe all in one go.
|
124
|
-
def sendBatchAtomic(aBatch)
|
125
|
-
self.open
|
126
|
-
self.sendBatch(aBatch)
|
127
|
-
self.flush
|
128
|
-
end
|
129
234
|
|
130
235
|
end
|
131
236
|
|
@@ -165,6 +270,21 @@ class ItemData
|
|
165
270
|
end
|
166
271
|
return %Q("#{@hostname}" #{@key} #{@timestamp.to_i} #{@value}\n)
|
167
272
|
end
|
273
|
+
##
|
274
|
+
# Render the ItemData instance as an object suitable for conversion to json, for socket transmission
|
275
|
+
def to_senderstruct
|
276
|
+
if @timestamp.to_i == 0
|
277
|
+
puts %Q("#{@hostname}" #{@key} #{@timestamp.to_i} #{@value}\n)
|
278
|
+
abort("Attempt was made to render a timestamp of zero. You DO NOT want this - it can kill db performance. Fix it.")
|
279
|
+
else
|
280
|
+
return item = {
|
281
|
+
host: @hostname,
|
282
|
+
key: @key,
|
283
|
+
value: @value,
|
284
|
+
clock: @timestamp.to_i
|
285
|
+
}
|
286
|
+
end
|
287
|
+
end
|
168
288
|
end
|
169
289
|
|
170
290
|
##
|
@@ -204,12 +324,18 @@ class Discovery < ItemData
|
|
204
324
|
def to_discodata
|
205
325
|
disco = { 'data'=>Array.new }
|
206
326
|
disco['data'] = @entities.to_a
|
207
|
-
return disco
|
327
|
+
return disco
|
208
328
|
end
|
209
329
|
##
|
210
330
|
# Render this discovery instance as a zabbix_sender line.
|
211
331
|
#
|
212
332
|
def to_senderline
|
333
|
+
@value = self.to_discodata.to_json
|
334
|
+
super
|
335
|
+
end
|
336
|
+
##
|
337
|
+
# Render this discovery instance as an object suitable for conversion to json for socket transmission
|
338
|
+
def to_senderstruct
|
213
339
|
@value = self.to_discodata
|
214
340
|
super
|
215
341
|
end
|
@@ -282,6 +408,16 @@ class Batch
|
|
282
408
|
def to_senderline
|
283
409
|
@data.collect {|line| line.to_senderline}.join
|
284
410
|
end
|
411
|
+
##
|
412
|
+
# Render this batch as a json object
|
413
|
+
#
|
414
|
+
def to_senderstruct
|
415
|
+
return batch = {
|
416
|
+
request: "sender data",
|
417
|
+
data: @data.collect {|item| item.to_senderstruct},
|
418
|
+
clock: @time.to_i
|
419
|
+
}
|
420
|
+
end
|
285
421
|
end
|
286
422
|
|
287
423
|
end # module Sender
|
data/lib/zabbix_sender_api.rb
CHANGED
data/zabbix_sender_api.gemspec
CHANGED
@@ -6,8 +6,8 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ["Dave Parker"]
|
7
7
|
spec.email = ["daveparker01@gmail.com"]
|
8
8
|
|
9
|
-
spec.summary = %q{Ruby
|
10
|
-
spec.description = %q{This gem
|
9
|
+
spec.summary = %q{Ruby library that greatly simplifies sending data to Zabbix via the sender/trapper protocol}
|
10
|
+
spec.description = %q{This gem implements an api that abstracts the zabbix sender/trapper mechanism. It saves tons of time when you're cranking out custom polling logic}
|
11
11
|
spec.homepage = "https://rubydoc.info/gems/zabbix_sender_api"
|
12
12
|
spec.license = "MIT"
|
13
13
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
|
|
16
16
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
18
18
|
spec.metadata["source_code_uri"] = "https://gitlab.com/svdasein/zabbix_sender_api"
|
19
|
+
spec.metadata["changelog_uri"] = "https://gitlab.com/svdasein/zabbix_sender_api/-/blob/master/CHANGELOG.md"
|
20
|
+
|
19
21
|
|
20
22
|
# Specify which files should be added to the gem when it is released.
|
21
23
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
metadata
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: zabbix_sender_api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dave Parker
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06
|
11
|
+
date: 2021-09-06 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: This gem
|
14
|
-
of
|
13
|
+
description: This gem implements an api that abstracts the zabbix sender/trapper mechanism.
|
14
|
+
It saves tons of time when you're cranking out custom polling logic
|
15
15
|
email:
|
16
16
|
- daveparker01@gmail.com
|
17
17
|
executables:
|
@@ -20,11 +20,18 @@ extensions: []
|
|
20
20
|
extra_rdoc_files: []
|
21
21
|
files:
|
22
22
|
- ".gitignore"
|
23
|
+
- CHANGELOG.md
|
23
24
|
- Gemfile
|
24
25
|
- LICENSE.txt
|
25
26
|
- README.md
|
26
27
|
- Rakefile
|
28
|
+
- bin/bundle
|
29
|
+
- bin/byebug
|
30
|
+
- bin/coderay
|
27
31
|
- bin/console
|
32
|
+
- bin/example.rb
|
33
|
+
- bin/pry
|
34
|
+
- bin/rake
|
28
35
|
- bin/setup
|
29
36
|
- exe/example.rb
|
30
37
|
- images/.gitkeep
|
@@ -42,6 +49,7 @@ metadata:
|
|
42
49
|
allowed_push_host: https://rubygems.org
|
43
50
|
homepage_uri: https://rubydoc.info/gems/zabbix_sender_api
|
44
51
|
source_code_uri: https://gitlab.com/svdasein/zabbix_sender_api
|
52
|
+
changelog_uri: https://gitlab.com/svdasein/zabbix_sender_api/-/blob/master/CHANGELOG.md
|
45
53
|
post_install_message:
|
46
54
|
rdoc_options: []
|
47
55
|
require_paths:
|
@@ -60,5 +68,6 @@ requirements: []
|
|
60
68
|
rubygems_version: 3.1.6
|
61
69
|
signing_key:
|
62
70
|
specification_version: 4
|
63
|
-
summary: Ruby
|
71
|
+
summary: Ruby library that greatly simplifies sending data to Zabbix via the sender/trapper
|
72
|
+
protocol
|
64
73
|
test_files: []
|