nagios-herald 0.0.2

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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +9 -0
  4. data/CHANGELOG.md +11 -0
  5. data/CONTRIBUTING.md +28 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE +21 -0
  8. data/README.md +94 -0
  9. data/Rakefile +9 -0
  10. data/bin/draw_stack_bars +76 -0
  11. data/bin/dump_nagios_env.sh +25 -0
  12. data/bin/get_ganglia_graph +82 -0
  13. data/bin/get_graph +50 -0
  14. data/bin/get_graphite_graph +58 -0
  15. data/bin/nagios-herald +6 -0
  16. data/bin/splunk_alert_frequency +54 -0
  17. data/contrib/nrpe-plugins/check_cpu_stats.sh +186 -0
  18. data/contrib/nrpe-plugins/check_disk.sh +34 -0
  19. data/contrib/nrpe-plugins/check_mem.pl +181 -0
  20. data/contrib/nrpe-plugins/nrpe-plugin-examples.md +11 -0
  21. data/docs/config.md +62 -0
  22. data/docs/example_alerts.md +48 -0
  23. data/docs/formatters.md +180 -0
  24. data/docs/helpers.md +12 -0
  25. data/docs/images/cpu_no_context.png +0 -0
  26. data/docs/images/cpu_with_context.png +0 -0
  27. data/docs/images/disk_space_no_context.png +0 -0
  28. data/docs/images/disk_space_with_context.png +0 -0
  29. data/docs/images/memory_high_no_context.png +0 -0
  30. data/docs/images/memory_high_with_context.png +0 -0
  31. data/docs/images/nagios-herald-formatter-content-example.png +0 -0
  32. data/docs/images/nagios-herald.png +0 -0
  33. data/docs/images/stack-bars.png +0 -0
  34. data/docs/images/vanilla-nagios.png +0 -0
  35. data/docs/messages.md +16 -0
  36. data/docs/nagios-config.md +74 -0
  37. data/docs/tools.md +79 -0
  38. data/etc/config.yml.example +14 -0
  39. data/etc/readme.md +2 -0
  40. data/lib/nagios-herald/config.rb +25 -0
  41. data/lib/nagios-herald/executor.rb +265 -0
  42. data/lib/nagios-herald/formatter_loader.rb +82 -0
  43. data/lib/nagios-herald/formatters/base.rb +524 -0
  44. data/lib/nagios-herald/formatters/check_cpu.rb +71 -0
  45. data/lib/nagios-herald/formatters/check_disk.rb +143 -0
  46. data/lib/nagios-herald/formatters/check_logstash.rb +155 -0
  47. data/lib/nagios-herald/formatters/check_memory.rb +42 -0
  48. data/lib/nagios-herald/formatters/example.rb +19 -0
  49. data/lib/nagios-herald/formatters.rb +1 -0
  50. data/lib/nagios-herald/helpers/ganglia_graph.rb +99 -0
  51. data/lib/nagios-herald/helpers/graphite_graph.rb +85 -0
  52. data/lib/nagios-herald/helpers/logstash_query.rb +125 -0
  53. data/lib/nagios-herald/helpers/splunk_alert_frequency.rb +170 -0
  54. data/lib/nagios-herald/helpers/splunk_query.rb +119 -0
  55. data/lib/nagios-herald/helpers/url_image.rb +76 -0
  56. data/lib/nagios-herald/helpers.rb +5 -0
  57. data/lib/nagios-herald/logging.rb +48 -0
  58. data/lib/nagios-herald/message_loader.rb +40 -0
  59. data/lib/nagios-herald/messages/base.rb +56 -0
  60. data/lib/nagios-herald/messages/email.rb +150 -0
  61. data/lib/nagios-herald/messages/irc.rb +58 -0
  62. data/lib/nagios-herald/messages/pager.rb +75 -0
  63. data/lib/nagios-herald/messages.rb +3 -0
  64. data/lib/nagios-herald/test_helpers/base_test_case.rb +82 -0
  65. data/lib/nagios-herald/util.rb +45 -0
  66. data/lib/nagios-herald/version.rb +3 -0
  67. data/lib/nagios-herald.rb +7 -0
  68. data/lib/stackbars/__init__.py +0 -0
  69. data/lib/stackbars/chart_utils.py +25 -0
  70. data/lib/stackbars/grouped_stackbars.py +97 -0
  71. data/lib/stackbars/pilfonts/Tahoma.ttf +0 -0
  72. data/lib/stackbars/pilfonts/aerial.ttf +0 -0
  73. data/lib/stackbars/pilfonts/arial_black.ttf +0 -0
  74. data/lib/stackbars/stackbar.py +100 -0
  75. data/nagios-herald.gemspec +33 -0
  76. data/test/env_files/check_cpu_idle.CRITICAL +199 -0
  77. data/test/env_files/check_cpu_iowait.WARNING +199 -0
  78. data/test/env_files/check_disk.CRITICAL +197 -0
  79. data/test/env_files/check_disk.CRITICAL_ICINGA +197 -0
  80. data/test/env_files/check_disk.RECOVERY +197 -0
  81. data/test/env_files/check_memory.CRITICAL +197 -0
  82. data/test/env_files/nagios_vars.EXAMPLE +197 -0
  83. data/test/unit/test_config.rb +31 -0
  84. data/test/unit/test_executor.rb +65 -0
  85. data/test/unit/test_formatter_base.rb +131 -0
  86. data/test/unit/test_formatter_check_cpu_idle_critical.rb +135 -0
  87. data/test/unit/test_formatter_check_memory.rb +135 -0
  88. data/test/unit/test_icinga_variables.rb +31 -0
  89. data/test/unit/test_logging.rb +35 -0
  90. data/test/unit/test_message_email.rb +69 -0
  91. data/test/unit/test_message_pager.rb +69 -0
  92. metadata +204 -0
@@ -0,0 +1,265 @@
1
+ require 'nagios-herald'
2
+ require 'choice'
3
+ require 'app_conf'
4
+
5
+ module NagiosHerald
6
+ class Executor
7
+ include NagiosHerald::Logging
8
+ include NagiosHerald::Util
9
+
10
+ # Public: Parse the command line options.
11
+ #
12
+ # Returns a hash of the specified options and defaults as appropriate.
13
+ def parse_options
14
+ program_name = File.basename($0)
15
+
16
+ Choice.options do
17
+ header "Nagios Herald - Spread the word"
18
+ header ""
19
+
20
+ option :config_file do
21
+ short "-c"
22
+ long "--config-file"
23
+ desc "Specify an alternate location for the config file."
24
+ default File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'etc', 'config.yml'))
25
+ end
26
+
27
+ option :debug do
28
+ short "-d"
29
+ long "--debug"
30
+ desc "BE VERBOSE! B-E V-E-R-B-O-S-E!"
31
+ end
32
+
33
+ option :env do
34
+ short "-e"
35
+ long "--env-file"
36
+ desc "Path to a file containing environment variables to use for testing/debugging (i.e. nagios_vars)."
37
+ end
38
+
39
+ option :formatter_dir do
40
+ short "-F"
41
+ long "--formatter-dir"
42
+ desc "Formatter directory"
43
+ default nil
44
+ end
45
+
46
+ option :formatter_name do
47
+ short "-f"
48
+ long "--formatter"
49
+ desc "Formatter name"
50
+ default nil
51
+ end
52
+
53
+ option :formatter_dir do
54
+ short "-F"
55
+ long "--formatter-dir"
56
+ desc "Formatter directory"
57
+ default nil
58
+ end
59
+
60
+ option :logfile do
61
+ short "-l"
62
+ long "--logfile"
63
+ desc "Logfile location"
64
+ desc "Can be a file name or STDOUT (i.e. -l /tmp/output.log or -l STDOUT)"
65
+ desc "[DEFAULT] Uses the value of 'logfile' in the config or STDOUT if not defined."
66
+ default nil
67
+ end
68
+
69
+ option :message_type, :required => true do
70
+ short "-m"
71
+ long "--message-type"
72
+ desc "[REQUIRED] Type of message to deliver (i.e. email, IRC, pager)"
73
+ end
74
+
75
+ option :no_send do
76
+ short "-N"
77
+ long "--no-send"
78
+ desc "Output content to screen but do not send it"
79
+ end
80
+
81
+ option :notification_type do
82
+ short "-n"
83
+ long "--notification-type"
84
+ desc "NAGIOS_NOTIFICATION_TYPE to report - defaults to the nagios env variable."
85
+ desc "Valid options: PROBLEM, FLAPPINGSTART, RECOVERY, FLAPPINGSTOP, ACKNOWLEDGEMENT"
86
+ end
87
+
88
+ option :pager_mode do
89
+ short "-p"
90
+ long "--pager"
91
+ desc "Enable pager mode"
92
+ end
93
+
94
+ option :recipients do
95
+ short "-r"
96
+ long "--recipient"
97
+ desc "A recipient's email address. Specify multiple recipients with multiple '-r' arguments."
98
+ desc "If not specified, recipients are looked up in the ENV['NAGIOS_CONTACTEMAIL'] environment variable."
99
+ end
100
+
101
+ option :trace do
102
+ short "-t"
103
+ long "--trace"
104
+ desc "Show a full traceback on error"
105
+ default false
106
+ end
107
+
108
+ option :nagios_url do
109
+ short "-u"
110
+ long "--nagios-cgi-url"
111
+ desc "Nagios CGI url (used for acknowledgement links)"
112
+ end
113
+
114
+ option :replyto, :required => true do
115
+ short "-y"
116
+ long "--reply-to"
117
+ desc "[REQUIRED] Reply-to email address (i.e. nagios@example.com) used for acknowledgement replies."
118
+ end
119
+
120
+ footer ""
121
+ footer "EXAMPLES"
122
+ footer "--------"
123
+ footer "#{program_name} -r ops@example.com --env-file=test/env_files/nagios_vars -y nagios@example.com --formatter=check_disk"
124
+ footer ""
125
+ end
126
+
127
+ return Choice.choices
128
+
129
+ end
130
+
131
+ # Public: Load environment variables from a file.
132
+ # This is useful for running controlled tests.
133
+ #
134
+ # Updates the ENV hash for each key/value pair.
135
+ def load_env_from_file(path)
136
+ File.readlines(path).each do |line|
137
+ values = line.split("=")
138
+ key = values[0]
139
+ value = values[1, values.length - 1 ].map {|v| v.strip() }.join('=')
140
+ ENV[key] = value
141
+ end
142
+ end
143
+
144
+ # Public: Instantiate a new FormatterLoader object.
145
+ #
146
+ # Returns a new FormatterLoader object.
147
+ def formatter_loader
148
+ @formatter_loader ||= NagiosHerald::FormatterLoader.new
149
+ end
150
+
151
+ # Public: Loads all formatter classes.
152
+ #
153
+ # Returns true.
154
+ def load_formatters
155
+ @formatters_loaded ||= formatter_loader.load_formatters
156
+ end
157
+
158
+ # Public: Instantiate a new MessageLoader object.
159
+ #
160
+ # Returns a new MessageLoader object.
161
+ def message_loader
162
+ @message_loader ||= NagiosHerald::MessageLoader.new
163
+ end
164
+
165
+ # Public: Loads all message classes.
166
+ #
167
+ # Returns true.
168
+ def load_messages
169
+ @messages_loaded ||= message_loader.load_messages
170
+ end
171
+
172
+ # Public: The main method from which notifications are generated and sent.
173
+ def announce
174
+ begin
175
+ @options = parse_options
176
+ rescue SystemExit
177
+ logger.fatal "Invalid or missing options\n"
178
+ exit 1
179
+ end
180
+
181
+ begin
182
+ # Load the environment if asked for it
183
+ load_env_from_file(@options.env) if @options.env
184
+
185
+ # Load the config for use globally
186
+ Config.load(@options)
187
+
188
+ # load the formatters and messages
189
+ load_formatters
190
+ load_messages
191
+
192
+ recipients = {}
193
+ if @options.recipients.nil?
194
+ contact_email = get_nagios_var("NAGIOS_CONTACTEMAIL")
195
+ contact_pager = get_nagios_var("NAGIOS_CONTACTPAGER")
196
+ recipients['email'] = contact_email if !contact_email.empty?
197
+ recipients['pager'] = contact_pager if !contact_pager.empty?
198
+ else
199
+ recipients['command_line'] = @options.recipients
200
+ end
201
+ nagios_notification_type = @options.notification_type.nil? ? get_nagios_var("NAGIOS_NOTIFICATIONTYPE") : @options.notification_type
202
+
203
+ # Log the host and service that's notifying (assuming we can read the environment)
204
+ state_type = get_nagios_var("NAGIOS_SERVICESTATE") != "" ? "SERVICE" : "HOST"
205
+ hostname = get_nagios_var("NAGIOS_HOSTNAME")
206
+ service_desc = get_nagios_var("NAGIOS_SERVICEDESC")
207
+ state = get_nagios_var("NAGIOS_#{state_type}STATE")
208
+ if !service_desc.nil? and !service_desc.empty?
209
+ logger.info "CHECK (SERVICE): #{hostname}/#{service_desc} is #{state}"
210
+ else
211
+ logger.info "CHECK (HOST): #{hostname} is #{state}"
212
+ end
213
+
214
+ recipients.each do |recipient_type, recipient|
215
+ contact_name = get_nagios_var("NAGIOS_CONTACTALIAS")
216
+ if recipient.nil? || recipient.eql?("")
217
+ logger.error "No recipient defined for #{contact_name} (type=#{recipient_type})!"
218
+ next
219
+ else
220
+ logger.info "Notifying #{contact_name} (#{recipient}, type=#{recipient_type})"
221
+ end
222
+
223
+ # bail if can't identify the message type because we don't know what sort of thing to send
224
+ if !Message.message_types.has_key?(@options.message_type)
225
+ logger.error "Unknown message type: '#{@options.message_type}'"
226
+ logger.error "I'm aware of the following message types:"
227
+ sorted_message_types = Message.message_types.sort_by {|message_type, message_class| message_type}
228
+ sorted_message_types.each do |message_type|
229
+ logger.error " + #{message_type.first}"
230
+ end
231
+ exit 1
232
+ end
233
+ message_class = Message.message_types[@options.message_type]
234
+ message = message_class.new(recipient, @options)
235
+
236
+ formatter_class = Formatter.formatters[@options.formatter_name]
237
+ if formatter_class.nil?
238
+ logger.info "Undefined formatter. Defaulting to the base formatter."
239
+ formatter_class = NagiosHerald::Formatter # default to the base formatter
240
+ end
241
+ formatter = formatter_class.new(@options)
242
+
243
+ formatter.generate_message_content
244
+ message.content = formatter.content
245
+ message.send
246
+ formatter.clean_sandbox # clean house
247
+ end
248
+ rescue Exception => e
249
+ logger.fatal "#{e.class}: #{e.message}"
250
+ logger.fatal "COMMAND LINE #{File.basename $0} #{ARGV.join(" ")}"
251
+ if @options[:trace]
252
+ e.backtrace.each do |line|
253
+ logger.fatal "TRACE #{line}"
254
+ end
255
+ else
256
+ logger.fatal "Use --trace for backtrace."
257
+ end
258
+ raise e if @options[:trace] || e.is_a?(SystemExit)
259
+ exit 1
260
+ end
261
+ exit 0
262
+ end
263
+
264
+ end
265
+ end
@@ -0,0 +1,82 @@
1
+ # This is why open source rocks my worl'! I lifted this pattern of loading classes
2
+ # from the good folks at Chef. The gist is that a given directory contains a set
3
+ # of class files that we want to load into the namespace for use later.
4
+ # This allows us to build up a hash of the classes where the key is the class file
5
+ # name and the value is the class itself. We can then instantiate an object of a
6
+ # given class dynamically.
7
+ # See https://github.com/opscode/chef/blob/11-stable/lib/chef/knife.rb#L79#L83 for
8
+ # an example of this in action via the #inherited method.
9
+ module NagiosHerald
10
+ class FormatterLoader
11
+ include NagiosHerald::Logging
12
+
13
+ attr_accessor :builtin_formatter_path
14
+ attr_accessor :custom_formatter_path
15
+
16
+ # Public: Initialize the formatter loader module.
17
+ # Adds the path to the built-in formatters to the module variable @formatter_paths.
18
+ # Adds the path to custom formatters if that option is passed to `nagios-herald`
19
+ # via '-F|--formatter-dir' on the command line or via the 'formatter-dir'
20
+ # option in the configuration file.
21
+ def initialize
22
+ @builtin_formatter_path = File.expand_path("formatters", File.dirname(__FILE__))
23
+ @custom_formatter_path = Config.config['formatter_dir'] if Config.config['formatter_dir']
24
+ end
25
+
26
+ # Public: Enumerate the available formatter class files.
27
+ #
28
+ # Returns a hash of the class files' absolute paths whose
29
+ # key is the formatter file's basename and the
30
+ # value is the full path to the file.
31
+ def enum_formatter_class_files
32
+ formatter_class_files = {}
33
+
34
+ # Iterate over the builtin formatters first.
35
+ builtin_formatter_class_files = Dir.glob(File.expand_path("*.rb", @builtin_formatter_path))
36
+ builtin_formatter_class_files.each do |builtin_formatter_class_file|
37
+ formatter_class_files[File.basename(builtin_formatter_class_file)] = builtin_formatter_class_file
38
+ end
39
+ # If we've been told about custom formatters, add them to the hash.
40
+ # If there are conflicts, naively merge them, with the custom formatters getting
41
+ # priority.
42
+ if @custom_formatter_path
43
+ custom_formatters = {}
44
+ custom_formatter_class_files = Dir.glob(File.expand_path("*.rb", @custom_formatter_path))
45
+ custom_formatter_class_files.each do |custom_formatter_class_file|
46
+ custom_formatters[File.basename(custom_formatter_class_file)] = custom_formatter_class_file
47
+ end
48
+ formatter_class_files.merge!(custom_formatters)
49
+ end
50
+ formatter_class_files
51
+ end
52
+
53
+ # Public: An array of class files' paths.
54
+ #
55
+ # Returns an array of class files' paths.
56
+ def formatter_class_files
57
+ @formatter_class_files ||= enum_formatter_class_files
58
+ end
59
+
60
+ # Public: Load the formatters into the namespace.
61
+ # A formatter can then easily be instantiated later.
62
+ #
63
+ # Returns nothing but loads the classes into the namespace.
64
+ def load_formatters
65
+ if formatter_class_files.empty?
66
+ if @custom_formatter_path
67
+ puts "#{$0}: No formatters were found in '#{@builtin_formatter_path}'" \
68
+ " or '#{@custom_formatter_path}' (as defined by the 'formatter_dir' option)!"
69
+ else
70
+ puts "#{$0}: No formatters were found in '#{@builtin_formatter_path}'!"
71
+ end
72
+ exit 1
73
+ else
74
+ formatter_class_files.each do |formatter_class, formatter_class_file|
75
+ Kernel.load formatter_class_file
76
+ end
77
+ end
78
+ end
79
+
80
+ end
81
+ end
82
+