icinga-rest 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+