zabbix_sender_api 1.0.7 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +12 -0
  3. data/CHANGELOG.md +9 -0
  4. data/Gemfile +1 -0
  5. data/README.md +29 -28
  6. data/Rakefile +8 -1
  7. data/bin/bundle +114 -0
  8. data/bin/byebug +29 -0
  9. data/bin/coderay +29 -0
  10. data/bin/example.rb +29 -0
  11. data/bin/pry +29 -0
  12. data/bin/rake +29 -0
  13. data/lib/zabbix_sender_api/api.rb +163 -29
  14. data/lib/zabbix_sender_api/version.rb +1 -1
  15. data/lib/zabbix_sender_api.rb +2 -0
  16. data/public/Zabbix/AgentConfiguration.html +413 -0
  17. data/public/Zabbix/Sender/Batch.html +660 -0
  18. data/public/Zabbix/Sender/Connection.html +650 -0
  19. data/public/Zabbix/Sender/Discovery.html +597 -0
  20. data/public/Zabbix/Sender/ItemData.html +678 -0
  21. data/public/Zabbix/Sender/Pipe.html +461 -0
  22. data/public/Zabbix/Sender/Socket.html +528 -0
  23. data/public/Zabbix/Sender.html +115 -0
  24. data/public/Zabbix.html +117 -0
  25. data/public/ZabbixSenderApi/Error.html +124 -0
  26. data/public/ZabbixSenderApi.html +133 -0
  27. data/public/_index.html +248 -0
  28. data/public/class_list.html +51 -0
  29. data/public/css/common.css +1 -0
  30. data/public/css/full_list.css +58 -0
  31. data/public/css/style.css +497 -0
  32. data/public/file.CHANGELOG.html +89 -0
  33. data/public/file.LICENSE.html +70 -0
  34. data/public/file.README.html +183 -0
  35. data/public/file_list.html +66 -0
  36. data/public/frames.html +17 -0
  37. data/public/images/Spectacle.Z29721.png +0 -0
  38. data/public/images/Spectacle.l29721.png +0 -0
  39. data/public/index.html +183 -0
  40. data/public/js/app.js +314 -0
  41. data/public/js/full_list.js +216 -0
  42. data/public/js/jquery.js +4 -0
  43. data/public/method_list.html +363 -0
  44. data/public/top-level-namespace.html +110 -0
  45. data/zabbix_sender_api.gemspec +5 -3
  46. metadata +47 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 89e13bebbddda9a40ec5990b685c876cccc4dc535d4f0aa71ee5e9e4c9ff0643
4
- data.tar.gz: 5570e008ccdeae7d5f9866fa4edc6faf71bbc7968f1ee5a5a5f2a6d1c815131a
3
+ metadata.gz: 941c8d7d5179168c654a89712df253c202b9704fac194933bc48b8233d30052d
4
+ data.tar.gz: eac54ddbb5a9976b43579e5609ae81d119a68b11a283eff5aaa4e5e3b25af334
5
5
  SHA512:
6
- metadata.gz: 614555a8b2dd25f742140504eae44f84d28a977888ba7d3583ad8db818ecc646a3302a33893c5a46b92215e744f1e0ce7bed0126b4cfc1ac15d03c8657dd7880
7
- data.tar.gz: 8ca65d78ae038b7ac78a093c9b85df976d3f969d3e115e50c0301f72d4a4664834adc3a2e0cbc79ab38180c4b0e4ea9412ff998aaea51a16ad3c687163808d40
6
+ metadata.gz: 4a63dec0276fabb920a0bb94398ea4727db7b8f6db65cf961e68c86f1dd588245f9525873000e62104781c77c111c4e9de379dc25ccc34d782165ff4eb39e61b
7
+ data.tar.gz: a5c8dcb806341aa096ca806f45d37597a40385131602a6ea7cd8c6e4ed7b2ea6f227bd95cf52220dba30db2c964f85cdd3314754c7e2a42ebac24efde973eba8
data/.gitlab-ci.yml ADDED
@@ -0,0 +1,12 @@
1
+ image: alpine:latest
2
+
3
+ pages:
4
+ stage: deploy
5
+ script:
6
+ - echo 'Nothing to do...'
7
+ artifacts:
8
+ paths:
9
+ - public
10
+ expire_in: never
11
+ only:
12
+ - master
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
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
8
+ # 1.1.2
9
+ - This is just a documentation fix. rubydoc.info wasn't generating docs from the gem properly so docs are now in a gitlab "pages" site.
data/Gemfile CHANGED
@@ -6,3 +6,4 @@ gemspec
6
6
  gem "rake"
7
7
  gem "pry"
8
8
  gem "pry-byebug"
9
+ gem "yard"
data/README.md CHANGED
@@ -1,38 +1,20 @@
1
- # zabbix_sender_api
1
+ # zabbix_sender_api [![Gem Version](https://badge.fury.io/rb/zabbix_sender_api.svg)](https://badge.fury.io/rb/zabbix_sender_api)
2
2
 
3
- This gem describes an api that takes some of the drudgery out of the task of putting together bulk data for the zabbix_sender utility.
3
+ This gem provides a model for assembling and sending data to Zabbix via its sender/trapper mechanism. It works both with the zabbix_sender command line utility, or by itself using a socket connection to a trapper port direct from Ruby.
4
4
 
5
- **Detailed documentation for this library is at [rubydoc.info](https://rubydoc.info/gems/zabbix_sender_api)**
5
+ **Detailed documentation for this library is [>>HERE<<](https://svdasein.gitlab.io/zabbix_sender_api)**
6
6
 
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
-
9
- zabbix_sender is a command line utility for sending monitoring data to Zabbix server or proxy. On the Zabbix server an item of type Zabbix trapper should be created with corresponding key.
7
+ **Source repository is [>>HERE<<](https://gitlab.com/svdasein/zabbix_sender_api)**
10
8
 
11
- * [Zabbix sender manpage](https://www.zabbix.com/documentation/4.0/manpages/zabbix_sender)
12
- * [Zabbix sender CLI help](zabbix-sender-help.md)
9
+ 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.
13
10
 
11
+ zabbix_sender_api can send data to zabbix via one of two included connection types:
14
12
 
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:
13
+ * zabbix_sender (this was the original and for quite some time only mode supported)
14
+ * TCP socket connection directly to a Zabbix "trapper" port (as of vers 1.1.0)
16
15
 
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
- ```
16
+ If you need to interact with the Zabbix REST API, you might also be interested in [zabbix-api-simple](https://svdasein.gitlab.io/zabbix-api/)
29
17
 
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
18
 
37
19
  ## Installation
38
20
 
@@ -63,7 +45,7 @@ The above will execute zabbix_sender for you and send data into it as described
63
45
  Alternatively you can just
64
46
 
65
47
  ```ruby
66
- puts data.to_senderinput
48
+ puts data.to_senderline
67
49
  ```
68
50
  ... and do whatever you want w/ the stdout
69
51
 
@@ -81,6 +63,7 @@ data.addDiscovery(disco)
81
63
 
82
64
  ### Under the hood
83
65
 
66
+ #### Zabbix::Sender::Pipe method:
84
67
  The zabbix-sender cli utility provides a number of methods by which to insert data into zabbix.
85
68
 
86
69
  * zabbix-sender ... -s zabbixHostName -k keyName -o value (one k-v pair at a time)
@@ -120,3 +103,21 @@ If you wished to use the above lld to actually do some discovery, you'd set thin
120
103
 
121
104
  ![Discovery rule configuration](images/Spectacle.Z29721.png)
122
105
  ![Item prototype configuration](images/Spectacle.l29721.png )
106
+
107
+ #### Zabbix::Sender::Socket method:
108
+
109
+ You can switch between using the Pipe(zabbix_sender) method and the Socket(direct to zabbix via tcp socket) method very simply. Just change:
110
+
111
+ ```
112
+ sender = Zabbix::Sender::Pipe.new
113
+ ```
114
+ to
115
+ ```
116
+ sender = Zabbix::Sender::Socket.new
117
+ ```
118
+
119
+ 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
120
+
121
+ The socket method doesn't support sending multiple batches between flushes, so you should just use sendBatchAtomic(aBatch) with the Socket method.
122
+
123
+ 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/Rakefile CHANGED
@@ -1,2 +1,9 @@
1
1
  require "bundler/gem_tasks"
2
- task :default => :spec
2
+ desc "Generate yard docs in public"
3
+ task :gendocs do
4
+ puts %x(yard -o public --files CHANGELOG.md,LICENSE.txt)
5
+ puts %x(git add public/*)
6
+ end
7
+ desc "Generate docs and run :build"
8
+ task :buildall => [ :gendocs, :build ]
9
+ task :default => :buildall
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,26 @@
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. If it finds configuration you can
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
-
10
11
  class AgentConfiguration
11
- def self.initialize
12
- @agentConfPaths = [
12
+ ##
13
+ # You may optionally pass an array of full paths to agent conf files to look for
14
+ # during initialization. By default some common places are checked, but
15
+ # you can specify your own. If you call this you'll re-initialize the class, which
16
+ # will scan for values in any of the listed files it happens to find.
17
+ def self.initialize(paths: [
13
18
  '/etc/zabbix/zabbix_agentd.conf',
14
19
  '/usr/local/etc/zabbix/zabbix_agentd.conf',
15
20
  '/opt/zabbix/etc/zabbix_agentd.conf',
16
21
  '/opt/zabbix/conf/zabbix_agentd.conf'
17
- ]
22
+ ])
23
+ @agentConfPaths = paths
18
24
 
19
25
  @proxy = nil
20
26
  @hostname = nil
@@ -63,50 +69,155 @@ module Sender
63
69
  require 'set'
64
70
 
65
71
  ##
66
- # Pipe instances encapsulate communication to a running instance of zabbix_sender
67
- # via a pipe to STDIN. If you want your program to send data itself (as opposed
68
- # to say printing stdin lines that you can then pipe to zabbix_sender yourself),
69
- # you'll want to make an instance of this
70
- #
71
- class Pipe
72
- ##
73
- # pipe file handle object
74
- attr_reader :pipe
72
+ # Connection is an abstract class that defines the basis of specific
73
+ # connection types (Pipe, Socket). It is not meant to be instantiated
74
+ # on its own.
75
+ class Connection
75
76
  ##
76
77
  # target host (server or proxy) name or ip
77
78
  attr_reader :targetHost
79
+ attr_reader :pipe
80
+ ##
81
+ # Initialize a new Connector object. Proxy is optional.
82
+ #
83
+ #
84
+ # An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
85
+ # file in one of the usual places and zabbix_sender is on your path, it'll probably
86
+ # just work
87
+ #
88
+ def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy)
89
+ @targetHost = proxy
90
+ @pipe = nil
91
+ end
92
+ ##
93
+ # Aborts execution if directly called. Subclasses must override.
94
+ #
95
+ def open
96
+ abort("Call to abstract method Connection::open")
97
+ end
98
+ ##
99
+ # Aborts execution if directly called. Subclasses must override.
100
+ #
101
+ def sendBatch(aBatch)
102
+ abort("Call to abstract method Connection::sendBatch(aBatch)")
103
+ end
104
+ ##
105
+ # Send a Batch instance to zabbix (via zabbix_sender). This opens the pipe,
106
+ # writes the data to the pipe, and closes the pipe all in one go.
107
+ #
108
+ def sendBatchAtomic(aBatch)
109
+ self.open
110
+ self.sendBatch(aBatch)
111
+ return self.flush
112
+ end
113
+ ##
114
+ # Closes the zabbix_sender pipe if it's open
115
+ #
116
+ def flush
117
+ if @pipe and not @pipe.closed?
118
+ @pipe.close
119
+ end
120
+ end
121
+ end
122
+
123
+ ##
124
+ # Socket instances enable TCPSocket based communication with a zabbix trapper server instance
125
+ #
126
+ class Socket < Connection
127
+ attr_reader :port
128
+ ##
129
+ # Create a new socket connection object. Both proxy and port are optional.
130
+ #
131
+ # An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
132
+ # file in one of the usual places and zabbix_sender is on your path, it'll probably
133
+ # just work
134
+ #
135
+ def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy, port: 10051)
136
+ super(proxy: proxy)
137
+ @port = port
138
+ @lastres = nil
139
+ end
78
140
  ##
79
- # path to the zabbix_sender executable
80
- attr_reader :exePath
141
+ # Open tcp socket to target proxy/server
142
+ #
143
+ def open
144
+ @pipe = TCPSocket.new(@targetHost, @port)
145
+ end
146
+ ##
147
+ # Send aBatch to zabbix via the socket. Note that zabbix will close
148
+ # the connection after you send a complete message, so you
149
+ # *can't* do this:
150
+ #
151
+ # socket.open
152
+ # socket.sendBatch(a)
153
+ # socket.sendBatch(b) <-- this will blow up
154
+ # socket.flush
155
+ #
156
+ # ...as you can with Zabbix::Sender::Pipe. You're really best off
157
+ # just using sendBatchAtomic for sockets.
158
+ #
159
+ # This assumes that the socket is already open.
160
+ #
161
+ def sendBatch(aBatch)
162
+ header = %Q(ZBXD\x01)
163
+ data = aBatch.to_senderstruct.to_json
164
+ blob = %Q(#{header}#{[data.bytesize].pack("Q").force_encoding("UTF-8")}#{data})
165
+ @pipe.write(blob)
166
+ respHeader = @pipe.read(header.bytesize + 8)
167
+ datalen = respHeader[header.bytesize, 8].unpack("Q")[0]
168
+ @lastres = JSON.parse(@pipe.read(datalen))
169
+ end
170
+
171
+ def flush
172
+ super
173
+ return @lastres
174
+ end
175
+ end
81
176
 
177
+ ##
178
+ # Pipe instances utilize communication to a running instance of zabbix_sender
179
+ # via a pipe to STDIN. If you want your program to send data itself (as opposed
180
+ # to say printing stdin lines that you can then pipe to zabbix_sender yourself),
181
+ # you'll want to make an instance of this
182
+ #
183
+ class Pipe < Connection
82
184
 
83
185
  ##
84
- # Initialize a new Sender object. Both proxy: and path: are optional.
186
+ # Create a new Pipe object. Both proxy: and path: are optional.
85
187
  #
86
188
  # An attempt is made to provide sane default values. If you have a zabbix_agentd.conf
87
189
  # file in one of the usual places and zabbix_sender is on your path, it'll probably
88
190
  # just work
89
191
  #
90
192
  def initialize(proxy: Zabbix::AgentConfiguration.zabbixProxy, path: 'zabbix_sender')
91
- @targetHost = proxy
193
+ super(proxy: proxy)
194
+ @wait_thr = @stdout = @stderr = nil
92
195
  @exePath = path
93
- @pipe = nil
94
196
  end
95
197
 
96
198
  ##
97
199
  # Opens a pipe to the zabbix_sender command with appropriate options specified.
98
200
  # If the pipe is already opened when this command is called, it is first closed
99
201
  # via a call to flush
202
+ #
100
203
  def open
101
204
  self.flush
102
- @pipe = IO.popen(%Q(#{@exePath} -z #{@targetHost} -vv -T -i-),'w')
205
+ #@pipe = IO.popen(%Q(#{@exePath} -z #{@targetHost} -vv -T -i-),'w')
206
+ cmd = %Q(#{@exePath} -z #{@targetHost} -vv -T -i-)
207
+ @pipe,@stdout,@stderr,@wait_thr = Open3.popen3(cmd)
103
208
  end
104
209
 
105
210
  ##
106
- # Closes the zabbix_sender pipe if it's open
211
+ # Closes the open3 pipe stuff. We need this override method as
212
+ # closing an open3 pipe requires some extra work.
107
213
  def flush
108
214
  if @pipe and not @pipe.closed?
109
215
  @pipe.close
216
+ stdout = @stdout.read
217
+ stderr = @stderr.read
218
+ @stdout.close
219
+ @stderr.close
220
+ return {stdout: stdout, stderr: stderr, success: @wait_thr.value.success?}
110
221
  end
111
222
  end
112
223
 
@@ -118,14 +229,6 @@ class Pipe
118
229
  @pipe.puts(aBatch.to_senderline)
119
230
  end
120
231
 
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
232
 
130
233
  end
131
234
 
@@ -165,6 +268,21 @@ class ItemData
165
268
  end
166
269
  return %Q("#{@hostname}" #{@key} #{@timestamp.to_i} #{@value}\n)
167
270
  end
271
+ ##
272
+ # Render the ItemData instance as an object suitable for conversion to json, for socket transmission
273
+ def to_senderstruct
274
+ if @timestamp.to_i == 0
275
+ puts %Q("#{@hostname}" #{@key} #{@timestamp.to_i} #{@value}\n)
276
+ abort("Attempt was made to render a timestamp of zero. You DO NOT want this - it can kill db performance. Fix it.")
277
+ else
278
+ return item = {
279
+ host: @hostname,
280
+ key: @key,
281
+ value: @value,
282
+ clock: @timestamp.to_i
283
+ }
284
+ end
285
+ end
168
286
  end
169
287
 
170
288
  ##
@@ -210,6 +328,12 @@ class Discovery < ItemData
210
328
  # Render this discovery instance as a zabbix_sender line.
211
329
  #
212
330
  def to_senderline
331
+ @value = self.to_discodata.to_json
332
+ super
333
+ end
334
+ ##
335
+ # Render this discovery instance as an object suitable for conversion to json for socket transmission
336
+ def to_senderstruct
213
337
  @value = self.to_discodata
214
338
  super
215
339
  end
@@ -282,6 +406,16 @@ class Batch
282
406
  def to_senderline
283
407
  @data.collect {|line| line.to_senderline}.join
284
408
  end
409
+ ##
410
+ # Render this batch as a json object
411
+ #
412
+ def to_senderstruct
413
+ return batch = {
414
+ request: "sender data",
415
+ data: @data.collect {|item| item.to_senderstruct},
416
+ clock: @time.to_i
417
+ }
418
+ end
285
419
  end
286
420
 
287
421
  end # module Sender
@@ -1,3 +1,3 @@
1
1
  module ZabbixSenderApi
2
- VERSION = "1.0.7"
2
+ VERSION = "1.1.3"
3
3
  end