nagi 0.1.1 → 0.2.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/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