nagi 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/NEWS ADDED
@@ -0,0 +1,10 @@
1
+ 0.2.0: 2012-05-31
2
+ - Added support for collecting multiple statuses during check
3
+
4
+ 0.1.1: 2012-05-30
5
+ - Made Utility::execute() work with Ruby 1.8
6
+ - Properly escape quotes in Utility::execute()
7
+ - Fixed syntax errors in README example
8
+
9
+ 0.1.0: 2012-05-18
10
+ - Initial release
data/README.md CHANGED
@@ -9,11 +9,50 @@ GNU GPL v3.
9
9
 
10
10
  ## Example
11
11
 
12
- A typical plugin looks like this:
12
+ A very simple plugin looks like this:
13
13
 
14
14
  ```ruby
15
15
  #!/usr/bin/env ruby
16
+ require 'nagi'
17
+
18
+ Nagi do
19
+ name 'check_true'
20
+ version '0.1'
21
+ prefix 'TRUE'
22
+
23
+ check do |args|
24
+ if true
25
+ ok 'True is still true'
26
+ else
27
+ critical 'Uh oh'
28
+ end
29
+ end
30
+ end
31
+ ```
32
+
33
+ The first few methods just set some basic info for the plugin - the name,
34
+ version, and a prefix for the check output. These are all optional.
35
+
36
+ Next, the check block is defined, which runs the actual Nagios check. `args`
37
+ will contains any options (see command-line parsing below), and the methods ok,
38
+ warning, critical, and unknown are used to return the check status. A missing
39
+ status or uncaught exception will result in an 'unknown' status.
16
40
 
41
+ The plugin is run as any regular script - save it as a file, make it executable
42
+ and run it:
43
+
44
+ ```
45
+ $ ./test.rb
46
+ TRUE OK: True is still true
47
+ ```
48
+
49
+ ### Command-line options
50
+
51
+ Nagi can automatically parse command-line arguments when requested, and provide
52
+ them as input to the check block. Here is a simple example:
53
+
54
+ ```ruby
55
+ #!/usr/bin/env ruby
17
56
  require 'nagi'
18
57
  require 'resolv'
19
58
 
@@ -40,23 +79,99 @@ Nagi do
40
79
  end
41
80
  ```
42
81
 
43
- Here we first set up some basic info like name and version, and specify a few
44
- command-line options. Then we write a block of code containing the actual
45
- check, which is given the parsed command-line options as a hash, and returns a
46
- status via methods like `ok` and `critical`.
82
+ The `argument` method specifies a required positional argument, and only takes
83
+ the name of the argument. `switch` specifies an optional switch, which may or
84
+ may not take an argument of its own. The first `switch` parameter is its name,
85
+ while the rest are passed to the standard Ruby OptionParser.on method - see its
86
+ documentation for details.
87
+
88
+ The parsed arguments are passed to the `check` block via the `args` parameter,
89
+ which is a hash containing argument names and values. In the case of a switch
90
+ with no argument of its own, the value will be `true`.
91
+
92
+ A few usage examples of this plugin:
93
+
94
+ ```
95
+ $ ./check_dns.rb
96
+ Error: Argument 'hostname' not given
97
+
98
+ Usage: ./check_dns.rb [options] <hostname>
99
+ -i, --ip IP Expected IP address
100
+ -h, --help Display this help message
101
+ -V, --version Display version information
102
+
103
+ $ ./check_dns.rb www.google.com
104
+ DNS OK: www.google.com resolves to 173.194.35.148
105
+
106
+ $ ./check_dns.rb -i 1.2.3.4 www.google.com
107
+ DNS CRITICAL: www.google.com resolves to 173.194.35.148, expected 1.2.3.4
108
+ ```
109
+
110
+ Nagi will automatically set up the -h/--help and -V/--version switches and
111
+ handle them appropriately.
112
+
113
+ ### Handling multiple statuses
114
+
115
+ In some cases it is useful to collect several statuses and return the most
116
+ severe, rather than returning the first status encountered. This is typically
117
+ used when monitoring multiple resources with a single check.
118
+
119
+ The `collect` setting tells Nagi to continue running the check when a status
120
+ is given. It can be set to either `:severe`, in which case the most severe
121
+ status will be returned at the end, or `:all` which will use the most severe
122
+ status code but also combine all status messages into one.
123
+
124
+ This example checks used space on all disk drives, and returns critical if
125
+ any of the drives are above a threshold:
126
+
127
+ ```ruby
128
+ #!/usr/bin/env ruby
129
+ require 'nagi'
130
+
131
+ Nagi do
132
+ name 'check_df'
133
+ version '0.1'
134
+ prefix 'DF'
135
+ argument :percentage
136
+ collect :all
137
+
138
+ check do |args|
139
+ execute('df').lines.each do |line|
140
+ if line =~ /^.*?(\d+)%\s*(.*)$/
141
+ if $1.to_i > args[:percentage].to_i
142
+ critical "#{$2} #{$1}% used"
143
+ else
144
+ ok "#{$2} #{$1}% used"
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ ```
151
+
152
+ When run, it will output something like this:
153
+
154
+ ```
155
+ $ ./check_df.rb 90
156
+ DF OK: / 80% used, /Volumes/Media 57% used
47
157
 
48
- To run the plugin, type `./check_dns.rb --ip 127.0.0.1 localhost`, or try
49
- `./check_dns.rb --help` for more info.
158
+ $ ./check_df.rb 70
159
+ DF CRITICAL: / 80% used, /Volumes/Media 57% used
160
+ ```
50
161
 
51
162
  ## Reference
52
163
 
53
- ### Metadata
164
+ ### Plugin info
54
165
 
55
166
  These describe the program, and are usually given first, if necessary.
56
167
 
57
168
  * `name` *name*: the program name.
58
169
  * `version` *version*: the program version.
59
170
  * `prefix` *prefix*: a prefix for the check output.
171
+ * `collect` *type*: collect statuses and continue the check, rather than
172
+ returning. *type* can be either `:severe` or `:all` - `:severe` will
173
+ return the last, most severe status, and `:all` will use the most severe
174
+ status but join all the status messages together into one.
60
175
 
61
176
  ### Command-line arguments
62
177
 
@@ -77,6 +192,9 @@ parsed command-line arguments as a hash. It should use one of the methods `ok`,
77
192
  `warning`, `critical`, or `unknown` to return a status. If no status is given,
78
193
  or the block raises an unhandled exception, an Unknown status will be returned.
79
194
 
195
+ The `collect` setting (see above) can be used to collect statuses and continue
196
+ the check, rather than returning the first status encountered.
197
+
80
198
  * `check` *block*: the code block the the check. Parsed command-line arguments
81
199
  are passed as a hash.
82
200
  * `ok` *message*: returns an OK status.
data/lib/nagi/dsl.rb CHANGED
@@ -4,6 +4,8 @@ module Nagi
4
4
 
5
5
  def initialize(&block)
6
6
  @plugin = Nagi::Plugin.new
7
+ @collect = nil
8
+ @collected = []
7
9
  instance_eval &block
8
10
  end
9
11
 
@@ -12,14 +14,36 @@ module Nagi
12
14
  end
13
15
 
14
16
  def check(&block)
17
+ # make data available to block
18
+ collect = @collect
19
+ collected = @collected
20
+
15
21
  p = class << @plugin; self; end
16
22
  p.send(:define_method, :check) do |options|
17
- return catch(:status) { block.call(options) }
23
+ status = catch(:status) {
24
+ block.call(options)
25
+ nil # to avoid returning status if not thrown
26
+ }
27
+ return status if status or not collect
28
+ return nil if collected.empty?
29
+ return collected.reverse.max if collect == :severe
30
+ return collected.reverse.max.class.new(
31
+ collected.map { |s| s.message }.join(', ')
32
+ ) if collect == :all
33
+ return nil
18
34
  end
19
35
  end
20
36
 
37
+ def collect(type)
38
+ raise "Invalid collect type #{type.to_s}" unless [:all, :severe].include?(type)
39
+ @collect = type
40
+ end
41
+
21
42
  def critical(message)
22
- throw :status, Nagi::Status::Critical.new(message)
43
+ status = Nagi::Status::Critical.new(message)
44
+ throw :status, status unless @collect
45
+ @collected.push(status)
46
+ return status
23
47
  end
24
48
 
25
49
  def execute(command)
@@ -31,7 +55,10 @@ module Nagi
31
55
  end
32
56
 
33
57
  def ok(message)
34
- throw :status, Nagi::Status::OK.new(message)
58
+ status = Nagi::Status::OK.new(message)
59
+ throw :status, status unless @collect
60
+ @collected.push(status)
61
+ return status
35
62
  end
36
63
 
37
64
  def prefix(prefix)
@@ -43,7 +70,10 @@ module Nagi
43
70
  end
44
71
 
45
72
  def unknown(message)
46
- throw :status, Nagi::Status::Unknown.new(message)
73
+ status = Nagi::Status::Unknown.new(message)
74
+ throw :status, status unless @collect
75
+ @collected.push(status)
76
+ return status
47
77
  end
48
78
 
49
79
  def version(version)
@@ -51,7 +81,10 @@ module Nagi
51
81
  end
52
82
 
53
83
  def warning(message)
54
- throw :status, Nagi::Status::Warning.new(message)
84
+ status = Nagi::Status::Warning.new(message)
85
+ throw :status, status unless @collect
86
+ @collected.push(status)
87
+ return status
55
88
  end
56
89
  end
57
90
  end
data/lib/nagi/status.rb CHANGED
@@ -1,9 +1,21 @@
1
1
  module Nagi
2
2
  module Status
3
3
  class Status
4
+ include Comparable
4
5
  attr_accessor :message
5
6
  attr_reader :code, :name
6
7
 
8
+ def <=>(other)
9
+ if not other.is_a? Nagi::Status::Status
10
+ raise ArgumentError.new("comparison of Nagi::Status::Status with #{other.class} failed.")
11
+ end
12
+
13
+ # Make Unknown the least severe status
14
+ c = @code >= 3 ? -1 : @code
15
+ o = other.code >= 3 ? -1 : other.code
16
+ return c <=> o
17
+ end
18
+
7
19
  def initialize(code, name, message)
8
20
  @code = code
9
21
  @name = name
data/lib/nagi/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Nagi
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -38,6 +38,58 @@ describe Nagi::DSL do
38
38
  end
39
39
  @dsl.plugin.check({}).should eq 'status'
40
40
  end
41
+
42
+ it 'catches throwns :status and returns payload, even when collect is set' do
43
+ @dsl.collect(:all)
44
+ @dsl.check do |options|
45
+ throw :status, 'status'
46
+ end
47
+ @dsl.plugin.check({}).should eq 'status'
48
+ end
49
+
50
+ it 'returns first, most severe status for collect :severe' do
51
+ @dsl.collect(:severe)
52
+ @dsl.check do |o|
53
+ @dsl.warning 'w1'
54
+ @dsl.critical 'c1'
55
+ @dsl.ok 'o1'
56
+ @dsl.critical 'c2'
57
+ @dsl.ok 'o2'
58
+ end
59
+
60
+ status = @dsl.plugin.check({})
61
+ status.class.should eq Nagi::Status::Critical
62
+ status.message.should eq 'c2'
63
+ end
64
+
65
+ it 'returns most severe status with all messages joined for collect :all' do
66
+ @dsl.collect(:all)
67
+ @dsl.check do |o|
68
+ @dsl.warning 'w1'
69
+ @dsl.critical 'c1'
70
+ @dsl.ok 'o1'
71
+ @dsl.critical 'c2'
72
+ @dsl.ok 'o2'
73
+ end
74
+
75
+ status = @dsl.plugin.check({})
76
+ status.class.should eq Nagi::Status::Critical
77
+ status.message.should eq 'w1, c1, o1, c2, o2'
78
+ end
79
+ end
80
+
81
+ describe '.collect' do
82
+ it 'accepts :all' do
83
+ @dsl.collect(:all)
84
+ end
85
+
86
+ it 'accepts :severe' do
87
+ @dsl.collect(:severe)
88
+ end
89
+
90
+ it 'raises exception on other value' do
91
+ lambda { @dsl.collect(:dummy) }.should raise_error
92
+ end
41
93
  end
42
94
 
43
95
  describe '.critical' do
@@ -46,6 +98,11 @@ describe Nagi::DSL do
46
98
  @dsl.critical('message')
47
99
  end.class.should eq Nagi::Status::Critical
48
100
  end
101
+
102
+ it 'returns status if collection is enabled' do
103
+ @dsl.collect(:all)
104
+ @dsl.critical('message').class.should eq Nagi::Status::Critical
105
+ end
49
106
  end
50
107
 
51
108
  describe '.execute' do
@@ -67,6 +124,11 @@ describe Nagi::DSL do
67
124
  @dsl.ok('message')
68
125
  end.class.should eq Nagi::Status::OK
69
126
  end
127
+
128
+ it 'returns status if collection is enabled' do
129
+ @dsl.collect(:all)
130
+ @dsl.ok('message').class.should eq Nagi::Status::OK
131
+ end
70
132
  end
71
133
 
72
134
  describe '.prefix' do
@@ -90,6 +152,11 @@ describe Nagi::DSL do
90
152
  @dsl.unknown('message')
91
153
  end.class.should eq Nagi::Status::Unknown
92
154
  end
155
+
156
+ it 'returns status if collection is enabled' do
157
+ @dsl.collect(:all)
158
+ @dsl.unknown('message').class.should eq Nagi::Status::Unknown
159
+ end
93
160
  end
94
161
 
95
162
  describe '.version' do
@@ -105,5 +172,10 @@ describe Nagi::DSL do
105
172
  @dsl.warning('message')
106
173
  end.class.should eq Nagi::Status::Warning
107
174
  end
175
+
176
+ it 'returns status if collection is enabled' do
177
+ @dsl.collect(:all)
178
+ @dsl.warning('message').class.should eq Nagi::Status::Warning
179
+ end
108
180
  end
109
181
  end
@@ -5,6 +5,22 @@ describe Nagi::Status::Status do
5
5
  @status = Nagi::Status::Status.new(0, 'name', 'message')
6
6
  end
7
7
 
8
+ describe '<=>' do
9
+ it 'compares status code' do
10
+ (@status <=> Nagi::Status::Status.new(1, 'n', 'm')).should eq -1
11
+ (@status <=> Nagi::Status::Status.new(0, 'n', 'm')).should eq 0
12
+ (@status <=> Nagi::Status::Status.new(-1, 'n', 'm')).should eq 1
13
+ end
14
+
15
+ it 'compares Unknown (3) as less severe than Ok (0)' do
16
+ (@status <=> Nagi::Status::Status.new(3, 'n', 'm')).should eq 1
17
+ end
18
+
19
+ it 'raises ArgumentError if compared with non-Status object' do
20
+ lambda { @status <=> 1 }.should raise_error ArgumentError
21
+ end
22
+ end
23
+
8
24
  describe '.code' do
9
25
  it 'contains status code' do
10
26
  @status.code.should eq 0
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nagi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-30 00:00:00.000000000 Z
12
+ date: 2012-05-31 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70354822658820 !ruby/object:Gem::Requirement
16
+ requirement: &70342039915540 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70354822658820
24
+ version_requirements: *70342039915540
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70354822658200 !ruby/object:Gem::Requirement
27
+ requirement: &70342039911220 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70354822658200
35
+ version_requirements: *70342039911220
36
36
  description: A DSL for writing Nagios plugins
37
37
  email:
38
38
  - erik@bengler.no
@@ -42,6 +42,7 @@ extra_rdoc_files: []
42
42
  files:
43
43
  - .gitignore
44
44
  - Gemfile
45
+ - NEWS
45
46
  - README.md
46
47
  - Rakefile
47
48
  - lib/nagi.rb
@@ -78,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
79
  version: '0'
79
80
  requirements: []
80
81
  rubyforge_project:
81
- rubygems_version: 1.8.11
82
+ rubygems_version: 1.8.10
82
83
  signing_key:
83
84
  specification_version: 3
84
85
  summary: A DSL for writing Nagios plugins