3 # Copyright (c) 2004, 2005, 2006, 2007, 2008 Peter Palfrader <peter@palfrader.org>
9 GENERATED_PREFIX="./generated/"
12 %w(hosts hostgroups services dependencies hostextinfo serviceextinfo).each{
13 |x| nagios_filename[x] = GENERATED_PREFIX+"auto-#{x}.cfg"
15 nagios_filename['nrpe'] = GENERATED_PREFIX+"nrpe_#{ ORG }.cfg"
18 MAX_CHECK_ATTEMPTS_DEFAULT=6
20 NRPE_CHECKNAME="#{ ORG }_check_nrpe" # check that takes one argument: service name to be checked
21 NRPE_CHECKNAME_HOST="#{ ORG }_check_nrpe_host" # check that takes two arguments: relay host on which to run check, service name to be checked
23 HOST_TEMPLATE_NAME='generic-host' # host templates that all our host definitions use
24 SERVICE_TEMPLATE_NAME='generic-service' # host templates that all our host definitions use
25 HOST_ALIVE_CHECK='check-host-alive' # host alive check if server is pingable
26 NRPE_PROCESS_SERVICE='process - nrpe' # nrpe checks will depend on this one
32 def set_if_unset(hash, key, value)
33 hash[key] = value unless hash.has_key?(key)
35 def set_complain_if_set(hash, key, value, type, name)
36 throw "#{type} definition '#{name}' has '#{key}' already defined" if hash.has_key?(key)
40 # Make an array out of something. If there is nothing, create an empty array
41 # if it is just a string, make a list with just that element, if it already is
43 def ensure_array(something)
46 elsif something.kind_of?(String)
47 result = [ something ]
48 elsif something.kind_of?(Array)
51 throw "Do now know how to make an array out of #{something}: " + something.to_yaml
57 # This class keeps track of the checks done via NRPE and makes sure
58 # each gets a unique name.
60 # Unforutunately NRPE limits check names to some 30 characters, so
61 # we need to mangle service names near the end.
67 def make_name( name, check )
68 name = name.tr_s("^a-zA-Z", "_").gsub("process", "ps")
70 result = "#{ SHORTORG }_" + name[0,19]
74 while (@checks.has_key?(result + hash))
75 # hash it, so that we don't lose uniqeness by cutting it off
76 hash = (check+skew).crypt("$1$")
77 hash = hash[-5,5] # 5 chars are enough
79 skew += ' ' # change it a bit so the hash changes
82 return result # max of 32 or so chars
85 def add( name, check )
86 if @checks.has_value? check
87 @checks.each_pair{ |key, value|
88 return key if value == check
91 key = make_name(name, check)
92 @checks[ key ] = check
103 # Prints the keys and values of hash to a file
104 # This is the function that prints the bodies of most our
105 # host/service/etc definitions
107 # It skips over such keys as are listed in exclude_keys
108 # and also skips private keys (those starting with an underscre)
109 def print_block(fd, kind, hash, exclude_keys)
110 fd.puts "define #{kind} {"
111 hash.each_pair{ |key, value|
112 next if key[0,1] == '_'
113 next if exclude_keys.include? key
114 fd.puts " #{key} #{value}"
121 # Add the service definition service to hosts
122 # f is the file for service definitions, deps the file for dependencies
123 def addService(hosts, service, files)
125 set_if_unset service, 'use' , SERVICE_TEMPLATE_NAME
126 set_complain_if_set service, 'host_name' , hosts.join(',') , 'Service', service['service_description']
127 set_if_unset service, 'max_check_attempts', MAX_CHECK_ATTEMPTS_DEFAULT
129 service['max_check_attempts'] = MAX_CHECK_ATTEMPTS_DEFAULT + service['max_check_attempts'] if service['max_check_attempts'] < 0
132 throw "We already have a check_command (#{service['check_command']}) but we are in the NRPE block (nrpe: #{service['nrpe']})."+
133 " This should have been caught much earlier" if service.has_key?('check_command');
135 check = $nrpe.add(service['service_description'], service['nrpe'])
136 service['check_command'] = "#{ NRPE_CHECKNAME }!#{ check }"
138 service['depends'] = ensure_array( service['depends'] )
139 service['depends'] << NRPE_PROCESS_SERVICE unless service['service_description'] == NRPE_PROCESS_SERVICE # Depend on NRPE unless we are it
142 print_block files['services'], 'service', service, %w(nrpe runfrom remotecheck
144 hosts hostgroups excludehosts excludehostgroups)
146 if service['depends']
147 service['depends'].each{ |prerequisite|
149 prerequisite_host = host
151 # split off a hostname if there's one
152 bananasplit = prerequisite.split(':')
153 if bananasplit.size == 2
154 prerequisite_host = bananasplit[0]
156 elsif bananasplit.size > 2
157 throw "Cannot prase prerequisite #{prerequisite} for service #{service['service_description']} into host:service"
160 'host_name' => prerequisite_host,
161 'service_description' => pre,
162 'dependent_host_name' => host,
163 'dependent_service_description' => service['service_description'],
164 'execution_failure_criteria' => 'n',
165 'notification_failure_criteria' => 'w,u,c'
167 print_block files['dependencies'], 'servicedependency', dependency, %w()
173 set_complain_if_set service['_extinfo'], 'service_description' , service['service_description'], 'serviceextinfo', service['service_description']
174 set_complain_if_set service['_extinfo'], 'host_name' , hosts.join(',') , 'serviceextinfo', service['service_description']
176 print_block files['serviceextinfo'], 'serviceextinfo', service['_extinfo'], %w()
179 # hostlists in services can be given as both, single hosts and hostgroups
180 # This functinn merges hostgroups and a simple list of hosts
182 # it also takes a prefix so that it can be used for excludelists as well
183 def merge_hosts_and_hostgroups(service, servers, hostgroups, prefix)
185 hosts = service[prefix+'hosts'].split(/,/).map{ |x| x.strip } if service[prefix+'hosts']
187 throw "host #{host} does not exist - used in service #{service['service_description']}" unless servers[host]
189 if service[prefix+'hostgroups']
190 service[prefix+'hostgroups'].split(/,/).map{ |x| x.strip }.each{ |hg|
191 throw "hostgroup #{hg} does not exist - used in service #{service['service_description']}" unless hostgroups[hg]
192 hosts = hosts.concat hostgroups[hg]['_memberlist']
199 # Figure out the hosts a given service applies to
201 # For a given service find the list of hosts minus excluded hosts that this service runs on
202 def find_hosts(service, servers, hostgroups)
203 hosts = merge_hosts_and_hostgroups service, servers, hostgroups, ''
204 excludehosts = merge_hosts_and_hostgroups service, servers, hostgroups, 'exclude'
206 excludehosts.each{ |host|
207 if hosts.delete(host) == nil
208 throw "Cannot remove host #{host} from service #{service['service_description']}: it's not included anyway or excluded twice."
215 # Move all elements that have a key that starts with "extinfo-"
216 # into the _extinfo subhash
217 def split_away_extinfo(hash)
218 hash['_extinfo'] = {}
219 hash.keys.each{ |key|
220 if key[0, 8] == 'extinfo-'
221 hash['_extinfo'][ key[8, key.length-8] ] = hash[key]
228 #############################################################################################
229 #############################################################################################
230 #############################################################################################
233 config = YAML::load( File.open( 'nagios-master.cfg' ) )
236 # Remove old created files
237 nagios_filename.each_pair{ |name, filename|
238 files[name] = File.new(filename, "w")
241 #################################
242 # create a few hostgroups
243 #################################
244 # create the "all" and "pingable" hostgroups
245 config['hostgroups']['all'] = {}
246 config['hostgroups']['all']['alias'] = "all servers"
247 config['hostgroups']['pingable'] = {}
248 config['hostgroups']['pingable']['alias'] = "pingable servers"
250 config['hostgroups'].each_pair{ |name, hg|
251 throw "Empty hostgroup or hostgroup #{name} not a hash" unless hg.kind_of?(Hash)
252 split_away_extinfo hg
254 hg['_memberlist'] = []
257 config['servers'].each_pair{ |name, server|
258 throw "Empty server or server #{name} not a hash" unless server.kind_of?(Hash)
260 split_away_extinfo server
262 throw "No hostgroups defined for #{name}" unless server['hostgroups']
263 server['_hostgroups'] = server['hostgroups'].split(/,/).map{ |x| x.strip };
264 server['_hostgroups'] << 'all'
265 server['_hostgroups'] << 'pingable' unless server['pingable'] == false
267 server['_hostgroups'].each{ |hg|
268 throw "Hostgroup #{hg} is not defined" unless config['hostgroups'].has_key?(hg)
269 config['hostgroups'][hg]['_memberlist'] << name
276 config['servers'].each_pair{ |name, server|
277 # Formerly we used 'ip' instead of 'address' in our source file
278 # Handle this change but warn XXX
279 if server.has_key?('ip')
280 STDERR.puts("Host definition for #{name} has an 'ip' field. Please use 'address' instead");
281 server['address'] = server['ip'];
285 set_complain_if_set server, 'host_name' , name, 'Host', name
286 set_if_unset server, 'alias' , name
287 set_if_unset server, 'use' , HOST_TEMPLATE_NAME
288 set_if_unset server, 'check_command', HOST_ALIVE_CHECK unless server['pingable'] == false
290 print_block files['hosts'] , 'host' , server , %w(hostgroups pingable)
295 config['hostgroups'][ server['_hostgroups'].first ]['_extinfo'].each_pair{ |k, v|
296 # substitute hostname into the notes_url
297 v = sprintf(v,name) if k == 'notes_url'
299 set_if_unset server['_extinfo'], k ,v
302 set_complain_if_set server['_extinfo'], 'host_name' , name, 'hostextinfo', name
303 set_if_unset server['_extinfo'], 'vrml_image' , server['_extinfo']['icon_image'] if server['_extinfo'].has_key?('icon_image')
304 set_if_unset server['_extinfo'], 'statusmap_image' , server['_extinfo']['icon_image'] if server['_extinfo'].has_key?('icon_image')
306 print_block files['hostextinfo'], 'hostextinfo', server['_extinfo'], %w()
314 config['hostgroups'].each_pair{ |name, hg|
315 next if hg['private']
317 set_complain_if_set hg, 'hostgroup_name', name , 'Hostgroup', name
318 set_complain_if_set hg, 'members' , hg['_memberlist'].join(","), 'Hostgroup', name
320 print_block files['hostgroups'], 'hostgroup', hg, %w()
325 # SERVICES and DEPENDENCIES
327 config['services'].each{ |service|
328 throw "Empty service or service not a hash" unless service.kind_of?(Hash)
330 split_away_extinfo service
333 # Both 'name' and 'service_description' are valid for a service's name
334 # Internally we only use service_description as that's nagios' official term
335 if service.has_key?('name')
336 throw "Service definition has both a name (#{service['name']})" +
337 "and a service_description (#{service['service_description']})" if service.has_key?('service_description')
338 #STDERR.puts("Service definition #{service['name']} has a 'name' field. Please use 'service_description' instead");
339 service['service_description'] = service['name'];
340 service.delete('name');
342 # Both 'check' and 'check_command' are valid for a service's check command
343 # Internally we only use check_command as that's nagios' official term
344 if service.has_key?('check')
345 throw "Service definition has both a check (#{service['check']})" +
346 "and a check_command (#{service['check_command']})" if service.has_key?('check_command')
347 #STDERR.puts("Service definition #{service['service_description']} has a 'check' field. Please use 'check_command' instead");
348 service['check_command'] = service['check'];
349 service.delete('check');
353 hosts = find_hosts service, config['servers'], config['hostgroups']
354 throw "no hosts for service #{service['service_description']}" if hosts.empty?
356 throw "nrpe, check, and remotecheck are mutually exclusive in service #{service['service_description']}" if
357 (service['nrpe'] ? 1 : 0) +
358 (service['check_command'] ? 1 : 0) +
359 (service['remotecheck'] ? 1 : 0) >= 2
361 if service['runfrom'] && service['remotecheck']
362 # If the service check is to be run from a remote monitor server ("relay")
363 # add that as an NRPE check to be run on the relay and make this
364 # service also depend on NRPE on the relay
365 relay = service['runfrom']
368 # how to recursively copy this thing?
369 hostservice = YAML::load( service.to_yaml )
370 host_ip = config['servers'][host]['address']
371 throw "For some reason I do not have an address for #{host}. This shouldn't be." unless host_ip
373 check = $nrpe.add("#{host}_#{hostservice['service_description']}", hostservice['remotecheck'].gsub(/\$HOSTADDRESS\$/, host_ip))
374 hostservice['check_command'] = "#{NRPE_CHECKNAME_HOST}!#{ config['servers'][ relay ]['address'] }!#{ check }"
376 # Make sure dependencies are an array. If there are none, create an empty array
377 # if depends is just a string, make a list with just that element
378 hostservice['depends'] = ensure_array( hostservice['depends'] )
379 # And append this new dependency
380 hostservice['depends'] << "#{ relay }:#{ NRPE_PROCESS_SERVICE }";
382 addService( [ host ], hostservice, files)
384 elsif service['runfrom'] || service['remotecheck']
385 throw "runfrom and remotecheck must either appear both or not at all in service #{service['service_description']}"
386 throw "must not remotecheck without runfrom" if service['remotecheck']
388 addService(hosts, service, files)
396 $nrpe.checks.each_pair{ |name, check|
397 files['nrpe'].puts "command[#{ name }]=#{ check }"