icinga-rest 0.1.0

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.
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source :rubygems
2
+ gem "json"
3
+ group :development do
4
+ gem "ruby-debug"
5
+ gem "rdoc"
6
+ gem "rspec"
7
+ gem "gem-this"
8
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ columnize (0.3.2)
5
+ diff-lcs (1.1.3)
6
+ gem-this (0.3.7)
7
+ json (1.6.1)
8
+ linecache (0.43)
9
+ rdoc (3.9.4)
10
+ rspec (2.6.0)
11
+ rspec-core (~> 2.6.0)
12
+ rspec-expectations (~> 2.6.0)
13
+ rspec-mocks (~> 2.6.0)
14
+ rspec-core (2.6.4)
15
+ rspec-expectations (2.6.0)
16
+ diff-lcs (~> 1.1.2)
17
+ rspec-mocks (2.6.0)
18
+ ruby-debug (0.10.4)
19
+ columnize (>= 0.1)
20
+ ruby-debug-base (~> 0.10.4.0)
21
+ ruby-debug-base (0.10.4)
22
+ linecache (>= 0.3)
23
+
24
+ PLATFORMS
25
+ ruby
26
+
27
+ DEPENDENCIES
28
+ gem-this
29
+ json
30
+ rdoc
31
+ rspec
32
+ ruby-debug
data/Rakefile ADDED
@@ -0,0 +1,91 @@
1
+ require 'rake'
2
+ require "rubygems"
3
+ require "rubygems/package_task"
4
+ require "rdoc/task"
5
+
6
+ require "rspec"
7
+ require "rspec/core/rake_task"
8
+
9
+ $stdout.sync = true
10
+
11
+ desc "Run all specs whenever anything changes"
12
+ task :stakeout do
13
+ system "rake spec"
14
+ system "stakeout rake spec/*.rb spec/*/*_spec.rb lib/*.rb lib/*/*.rb"
15
+ end
16
+
17
+ RSpec::Core::RakeTask.new do |t|
18
+ t.rspec_opts = %w(--format documentation --colour)
19
+ end
20
+
21
+ task :default => ["spec"]
22
+
23
+ # This builds the actual gem. For details of what all these options
24
+ # mean, and other ones you can add, check the documentation here:
25
+ #
26
+ # http://rubygems.org/read/chapter/20
27
+ #
28
+ spec = Gem::Specification.new do |s|
29
+
30
+ # Change these as appropriate
31
+ s.name = "icinga-rest"
32
+ s.version = "0.1.0"
33
+ s.summary = "Simple, basic access to the Icinga REST API"
34
+ s.author = "David Salgado"
35
+ s.email = "david@digitalronin.com"
36
+ s.homepage = "http://roninonrails.wordpress.com/2011/10/08/icinga-rest-ruby-gem"
37
+
38
+ s.has_rdoc = true
39
+ # You should probably have a README of some kind. Change the filename
40
+ # as appropriate
41
+ s.extra_rdoc_files = %w(Readme.markdown)
42
+ # s.rdoc_options = %w(--main README)
43
+ s.description = "Use the Icinga REST API to count hosts with services in a given state"
44
+
45
+ # Add any extra files to include in the gem (like your README)
46
+ s.files = %w(Gemfile Gemfile.lock Rakefile) + Dir.glob("{spec,lib}/**/*")
47
+ s.require_paths = ["lib"]
48
+
49
+ # If you want to depend on other gems, add them here, along with any
50
+ # relevant versions
51
+ s.add_dependency("json")
52
+
53
+ # If your tests use any gems, include them here
54
+ s.add_development_dependency("rspec")
55
+ s.add_development_dependency("ruby-debug")
56
+ end
57
+
58
+ # This task actually builds the gem. We also regenerate a static
59
+ # .gemspec file, which is useful if something (i.e. GitHub) will
60
+ # be automatically building a gem for this project. If you're not
61
+ # using GitHub, edit as appropriate.
62
+ #
63
+ # To publish your gem online, install the 'gemcutter' gem; Read more
64
+ # about that here: http://gemcutter.org/pages/gem_docs
65
+ Gem::PackageTask.new(spec) do |pkg|
66
+ pkg.gem_spec = spec
67
+ end
68
+
69
+ desc "Build the gemspec file #{spec.name}.gemspec"
70
+ task :gemspec do
71
+ file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
72
+ File.open(file, "w") {|f| f << spec.to_ruby }
73
+ end
74
+
75
+ # If you don't want to generate the .gemspec file, just remove this line. Reasons
76
+ # why you might want to generate a gemspec:
77
+ # - using bundler with a git source
78
+ # - building the gem without rake (i.e. gem build blah.gemspec)
79
+ # - maybe others?
80
+ task :package => :gemspec
81
+
82
+ # Generate documentation
83
+ RDoc::Task.new do |rd|
84
+ rd.rdoc_files.include("lib/**/*.rb")
85
+ rd.rdoc_dir = "rdoc"
86
+ end
87
+
88
+ desc 'Clear out RDoc and generated packages'
89
+ task :clean => [:clobber_rdoc, :clobber_package] do
90
+ rm "#{spec.name}.gemspec"
91
+ end
data/Readme.markdown ADDED
@@ -0,0 +1,56 @@
1
+ Icinga REST Gem
2
+ ===============
3
+
4
+ A gem to simplify the use of the Icinga REST API.
5
+
6
+ Currently, the only function that this gem will perform is a count of hosts where a specific service is in a given state.
7
+
8
+ e.g. To get a count of all hosts whose names begin with 'web' and whose 'Load' service is critical, you would do this;
9
+
10
+ #!/usr/bin/env ruby
11
+
12
+ require 'rubygems'
13
+ require 'icinga_rest'
14
+
15
+ check = IcingaRest::ServiceCheck.new(
16
+ :host => 'my.icinga.host',
17
+ :authkey => 'mysecretapikey',
18
+ :filter => [
19
+ {:host_name => 'web*'},
20
+ {:service_name => 'Load', :state => :critical}
21
+ ]
22
+ )
23
+
24
+ puts check.count
25
+
26
+ Requirements
27
+ ------------
28
+
29
+ You must have enabled the REST API on your Icinga server (and configured the hosts and services to be monitored, of course)
30
+
31
+ Any box that runs this check will need the wget program in /usr/bin/wget
32
+
33
+ wget is used instead of a nice ruby http library because the URLs to access the Icinga REST API are not valid http URLs, so all the libraries I tried barf on them. wget is more forgiving.
34
+
35
+ MIT License
36
+ ===========
37
+
38
+ (c) David Salgado 2011, or whenever
39
+
40
+ Permission is hereby granted, free of charge, to any person obtaining a copy
41
+ of this software and associated documentation files (the "Software"), to deal
42
+ in the Software without restriction, including without limitation the rights
43
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
44
+ copies of the Software, and to permit persons to whom the Software is
45
+ furnished to do so, subject to the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be included in
48
+ all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
51
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
52
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
53
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
54
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
55
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
56
+ THE SOFTWARE.
@@ -0,0 +1,12 @@
1
+ # Gem to simplify the use of the Icinga REST API
2
+ class IcingaRest
3
+ end
4
+
5
+ require 'rubygems'
6
+ require 'json'
7
+
8
+ libdir = File.join(File.dirname(__FILE__), 'icinga_rest')
9
+
10
+ require "#{libdir}/request"
11
+ require "#{libdir}/service_check"
12
+
@@ -0,0 +1,50 @@
1
+ # Wrapper to simplify constructing the http GET request
2
+ # to access the Icinga REST API
3
+ class IcingaRest::Request
4
+ attr_accessor :host, # The Icinga server
5
+ :authkey, # API key
6
+ :target, # host|service
7
+ :filter, # filter string to use. e.g. 'AND(SERVICE_NAME|=|Foobar;AND(SERVICE_CURRENT_STATE|!=|0))'
8
+ :count_column, # count this column to produce the total
9
+ :output # json|xml
10
+
11
+ WGET = '/usr/bin/wget'
12
+
13
+ def initialize(params)
14
+ @host = params[:host]
15
+ @target = params[:target]
16
+ @filter = params[:filter]
17
+ @count_column = params[:count_column]
18
+ @authkey = params[:authkey]
19
+ @output = params[:output]
20
+ end
21
+
22
+ # It would be nicer to use Net::HTTP, or something, but the
23
+ # URLs required by the Icinga API are not well-formed, and
24
+ # the URI library, used by most of the ruby http libs, barfs.
25
+ # So, we shell out to wget, which is more tolerant.
26
+ # Fugly, but functional.
27
+ def get
28
+ `#{WGET} -q -O - '#{to_url}'`
29
+ end
30
+
31
+ def to_url
32
+ "http://%s/icinga-web/web/api/%s/%s/authkey=%s/%s" % [host, target, url_options, authkey, output]
33
+ end
34
+
35
+ private
36
+
37
+ # The optional components of the API request path
38
+ def url_options
39
+ [filter_url, count_column_url].compact.join('/')
40
+ end
41
+
42
+ def filter_url
43
+ self.filter ? "filter[%s]" % filter : nil
44
+ end
45
+
46
+ def count_column_url
47
+ self.count_column ? "countColumn=%s"% count_column : nil
48
+ end
49
+
50
+ end
@@ -0,0 +1,107 @@
1
+ # Class to count services in a given state, optionally filtered by host name, either as
2
+ # a pattern ('foo*', or '*foo*'), or as an exact match ('foobar')
3
+ class IcingaRest::ServiceCheck
4
+ attr_accessor :host, # The Icinga server
5
+ :authkey, # API key
6
+ :filter # List of tuples
7
+
8
+ SERVICE_STATES = {
9
+ :ok => 0,
10
+ :warn => 1,
11
+ :critical => 2
12
+ }
13
+
14
+ # Define a service check to be carried out.
15
+ #
16
+ # Currently, only counting services in a given state is possible,
17
+ # where the service name and state are provided as literals, with
18
+ # optional filtering on host name, either as an exact match or
19
+ # with wildcards in the host name.
20
+ #
21
+ # Arguments:
22
+ # * :host - The Icinga host. The REST API must be available at the default location
23
+ # http://[host]/icinga-web/web/api/
24
+ # * :authkey - Your API key to access the REST API
25
+ # * :filter - a list of tuples to filter the count
26
+ # e.g.
27
+ # [ {:host_name => 'web*'}, {:service_name => 'Load', :state => :critical} ]
28
+ #
29
+ # The :host_name and :service_name should match hosts and services you have configured
30
+ # in Icinga (otherwise your count will always be zero).
31
+ #
32
+ # :state should be one of :ok, :warn, :critical
33
+ #
34
+ # Example:
35
+ #
36
+ # check = IcingaRest::ServiceCheck.new(
37
+ # :host => 'my.icinga.host',
38
+ # :authkey => 'mysecretapikey',
39
+ # :filter => [
40
+ # {:host_name => 'web*'},
41
+ # {:service_name => 'Load', :state => :critical}
42
+ # ]
43
+ # )
44
+ #
45
+ # puts check.count
46
+ #
47
+ def initialize(params)
48
+ @host = params[:host]
49
+ @authkey = params[:authkey]
50
+ @filter = params[:filter]
51
+ end
52
+
53
+ def count
54
+ json = request.get
55
+ result = JSON.load json
56
+ if result['success'] == 'true'
57
+ result['total']
58
+ else
59
+ raise "API call failed"
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def request
66
+ IcingaRest::Request.new(
67
+ :host => host,
68
+ :target => 'service',
69
+ :filter => filters,
70
+ :count_column => 'SERVICE_ID',
71
+ :authkey => authkey,
72
+ :output => 'json'
73
+ )
74
+ end
75
+
76
+ def filters
77
+ clauses = filter.inject([]) {|list, tuple| list << filter_clause(tuple)}
78
+ "AND(%s)" % clauses.map {|c| "(#{c})"}.join(';AND')
79
+ end
80
+
81
+ def filter_clause(tuple)
82
+ if tuple[:host_name]
83
+ host_clause tuple
84
+ elsif tuple[:service_name]
85
+ service_clause tuple
86
+ else
87
+ raise "Bad filter clause #{tuple.inspect}"
88
+ end
89
+ end
90
+
91
+
92
+ # {:service_name => 'Foobar', :state => :critical} => '(SERVICE_NAME|=|Foobar);AND(SERVICE_CURRENT_STATE|=|2)'
93
+ def service_clause(tuple)
94
+ name = tuple[:service_name]
95
+ state = SERVICE_STATES[tuple[:state]]
96
+ "(SERVICE_NAME|=|%s);AND(SERVICE_CURRENT_STATE|=|%d)" % [name, state]
97
+ end
98
+
99
+ # {:host_name => '*foobar*'} => 'HOST_NAME|like|*foobar*'
100
+ # {:host_name => 'foobar'} => 'HOST_NAME|=|foobar'
101
+ def host_clause(tuple)
102
+ name = tuple[:host_name]
103
+ operator = name.index('*').nil? ? '=' : 'like'
104
+ ['HOST_NAME', operator, name].join('|')
105
+ end
106
+
107
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe IcingaRest::Request do
4
+ before(:each) do
5
+ @request = IcingaRest::Request.new(
6
+ :host => 'my.icinga.host',
7
+ :target => 'service',
8
+ :filter => 'AND(SERVICE_NAME|=|Foobar;AND(SERVICE_CURRENT_STATE|!=|0))',
9
+ :count_column => 'SERVICE_ID',
10
+ :authkey => 'itsasekrit',
11
+ :output => 'json'
12
+ )
13
+ end
14
+
15
+ context "requesting" do
16
+ it "composes a URI" do
17
+ url = 'http://my.icinga.host/icinga-web/web/api/service/filter[AND(SERVICE_NAME|=|Foobar;AND(SERVICE_CURRENT_STATE|!=|0))]/countColumn=SERVICE_ID/authkey=itsasekrit/json'
18
+ @request.to_url.should == url
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe IcingaRest::ServiceCheck do
4
+ before(:each) do
5
+ @check = IcingaRest::ServiceCheck.new(
6
+ :host => 'my.icinga.host',
7
+ :authkey => 'itsasekrit',
8
+ :filter => [
9
+ {:service_name => 'Foobar', :state => :critical}
10
+ ]
11
+ )
12
+ end
13
+
14
+ context "filtering" do
15
+
16
+ it "filters on hostname like whatever*" do
17
+ @check.filter << {:host_name => 'foobar*'}
18
+ request = mock IcingaRest::Request, :get => '{"success":"true"}'
19
+ request_params = {
20
+ :host => 'my.icinga.host',
21
+ :target => 'service',
22
+ :filter => 'AND(((SERVICE_NAME|=|Foobar);AND(SERVICE_CURRENT_STATE|=|2));AND(HOST_NAME|like|foobar*))',
23
+ :count_column => 'SERVICE_ID',
24
+ :authkey => 'itsasekrit',
25
+ :output => 'json'
26
+ }
27
+ IcingaRest::Request.should_receive(:new).with(request_params).and_return(request)
28
+ @check.count
29
+ end
30
+
31
+ it "filters on hostname is whatever" do
32
+ @check.filter << {:host_name => 'foobar'}
33
+ request = mock IcingaRest::Request, :get => '{"success":"true"}'
34
+ request_params = {
35
+ :host => 'my.icinga.host',
36
+ :target => 'service',
37
+ :filter => 'AND(((SERVICE_NAME|=|Foobar);AND(SERVICE_CURRENT_STATE|=|2));AND(HOST_NAME|=|foobar))',
38
+ :count_column => 'SERVICE_ID',
39
+ :authkey => 'itsasekrit',
40
+ :output => 'json'
41
+ }
42
+ IcingaRest::Request.should_receive(:new).with(request_params).and_return(request)
43
+ @check.count
44
+ end
45
+
46
+ it "creates a request" do
47
+ request = mock IcingaRest::Request, :get => '{"success":"true"}'
48
+ request_params = {
49
+ :host => 'my.icinga.host',
50
+ :target => 'service',
51
+ :filter => 'AND(((SERVICE_NAME|=|Foobar);AND(SERVICE_CURRENT_STATE|=|2)))',
52
+ :count_column => 'SERVICE_ID',
53
+ :authkey => 'itsasekrit',
54
+ :output => 'json'
55
+ }
56
+ IcingaRest::Request.should_receive(:new).with(request_params).and_return(request)
57
+ @check.count
58
+ end
59
+ end
60
+
61
+ context "counting" do
62
+ it "throws an exception if request unsuccessful" do
63
+ json = '{"success":"false"}'
64
+ request = mock IcingaRest::Request, :get => json
65
+ IcingaRest::Request.stub!(:new).and_return(request)
66
+ expect {
67
+ @check.count
68
+ }.to raise_error("API call failed")
69
+ end
70
+
71
+ it "parses json response" do
72
+ json = '{"result":[{"SERVICE_ID":"16649","SERVICE_OBJECT_ID":"4546","SERVICE_IS_ACTIVE":"1","SERVICE_INSTANCE_ID":"1","SERVICE_NAME":"Foobar","SERVICE_DISPLAY_NAME":"Foobar","SERVICE_OUTPUT":"CRITICAL: Foobar is broken","SERVICE_PERFDATA":"in_service=0"},{"SERVICE_ID":"14083","SERVICE_OBJECT_ID":"1972","SERVICE_IS_ACTIVE":"1","SERVICE_INSTANCE_ID":"1","SERVICE_NAME":"Foobar","SERVICE_DISPLAY_NAME":"Foobar","SERVICE_OUTPUT":"CRITICAL: Foobar is broken","SERVICE_PERFDATA":"in_service=0"},{"SERVICE_ID":"12688","SERVICE_OBJECT_ID":"548","SERVICE_IS_ACTIVE":"1","SERVICE_INSTANCE_ID":"1","SERVICE_NAME":"Foobar","SERVICE_DISPLAY_NAME":"Foobar","SERVICE_OUTPUT":"CHECK_NRPE: Socket timeout after 10 seconds.","SERVICE_PERFDATA":""},{"SERVICE_ID":"13138","SERVICE_OBJECT_ID":"1013","SERVICE_IS_ACTIVE":"1","SERVICE_INSTANCE_ID":"1","SERVICE_NAME":"Foobar","SERVICE_DISPLAY_NAME":"Foobar","SERVICE_OUTPUT":"CHECK_NRPE: Socket timeout after 10 seconds.","SERVICE_PERFDATA":""},{"SERVICE_ID":"12763","SERVICE_OBJECT_ID":"638","SERVICE_IS_ACTIVE":"1","SERVICE_INSTANCE_ID":"1","SERVICE_NAME":"Foobar","SERVICE_DISPLAY_NAME":"Foobar","SERVICE_OUTPUT":"CRITICAL: Foobar is broken","SERVICE_PERFDATA":"in_service=0"}],"success":"true","total":5}'
73
+ request = mock IcingaRest::Request, :get => json
74
+ IcingaRest::Request.stub!(:new).and_return(request)
75
+
76
+ @check.count.should == 5
77
+ end
78
+
79
+ it "sends request" do
80
+ request = mock IcingaRest::Request
81
+ IcingaRest::Request.stub!(:new).and_return(request)
82
+ request.should_receive(:get).and_return('{"success":"true","total":5}')
83
+ @check.count
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ require 'ruby-debug'
2
+ Debugger.start
3
+
4
+ require 'lib/icinga_rest'
5
+
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: icinga-rest
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - David Salgado
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-10-08 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: json
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: ruby-debug
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ type: :development
61
+ version_requirements: *id003
62
+ description: Use the Icinga REST API to count hosts with services in a given state
63
+ email: david@digitalronin.com
64
+ executables: []
65
+
66
+ extensions: []
67
+
68
+ extra_rdoc_files:
69
+ - Readme.markdown
70
+ files:
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - Rakefile
74
+ - spec/icinga_rest/request_spec.rb
75
+ - spec/icinga_rest/service_check_spec.rb
76
+ - spec/spec_helper.rb
77
+ - lib/icinga_rest/request.rb
78
+ - lib/icinga_rest/service_check.rb
79
+ - lib/icinga_rest.rb
80
+ - Readme.markdown
81
+ homepage: http://roninonrails.wordpress.com/2011/10/08/icinga-rest-ruby-gem
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options: []
86
+
87
+ require_paths:
88
+ - lib
89
+ required_ruby_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ hash: 3
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project:
110
+ rubygems_version: 1.8.10
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Simple, basic access to the Icinga REST API
114
+ test_files: []
115
+