#!/usr/bin/env ruby require 'yajl/json_gem' module Netsaint class Service def self.all @services = [] Netsaint::Config.services.each do |id, attributes| attributes.each do |attrs| @services << self.new(attrs.merge("id" => id)) end end #@services[0..500] @services end def initialize(opts={}) @attributes = opts @attributes.keys.each do |key| if key == "id" || !self.respond_to?(key.to_sym) instance_eval <<-METHOD def #{key} @attributes["#{key}"] end METHOD end end end def flapjack_id "#{id}-#{description.downcase.gsub(' ', '_')}" end def check_command parts = @attributes["check_command"].split('!') command_id = parts.first arguments = parts[1..-1] attrs = { "arguments" => arguments, "hostname" => self.id } #command = ::Netsaint::Command.get(command_id) #command.attributes = attrs #command.expand_command_line "exit 0" end def to_flapjack {"id" => flapjack_id, "command" => check_command, "interval" => check_interval.to_i * 60 } end end class Command def self.all @commands = [] Netsaint::Config.commands.each do |id, attributes| attributes.each do |attrs| @commands << self.new(attrs.merge("id" => id)) end end @commands end def self.get(id) attrs = Netsaint::Config.commands[id].first command = attrs.merge({"id" => id}) self.new(command) end def initialize(opts={}) @attributes = opts @attributes.keys.each do |key| if key == "id" || !self.respond_to?(key.to_sym) instance_eval <<-METHOD def #{key} @attributes["#{key}"] end METHOD end end end def attributes=(attrs) @attributes.merge!(attrs) end def expand_command_line @attributes["command_line"].gsub(/\$\w+\$/) do |string| lookup_macro(string) end end def lookup_macro(string) case string when /^\$USER\d+\$$/ ::Netsaint::Config.fetch(string)[string] when /^\$ARG(\d+)\$$/ index = $1.to_i - 1 @attributes["arguments"][index] when "$HOSTNAME$" @attributes["hostname"] when "$HOSTADDRESS$" hostname = @attributes["hostname"] ::Netsaint::Host.get(hostname).address else raise "Don't know how to handle the #{string} macro!" end end end class Host def self.get(id) attrs = Netsaint::Config.hosts[id].first host = attrs.merge({"id" => id}) self.new(host) end def initialize(opts={}) @attributes = opts @attributes.keys.each do |key| if key == "id" || !self.respond_to?(key.to_sym) instance_eval <<-METHOD def #{key} @attributes["#{key}"] end METHOD end end end end class ConfigFile attr_accessor :filename, :config def initialize(filename, opts={}) @filename = filename @config = {} end def read @raw_cfg = File.readlines(@filename).grep(/^[^#]/).map {|l| l.strip.empty? ? nil : l.strip }.compact end def extract(keyname, opts={}, &blk) opts[:delete] ||= true attrs = @config.find_all {|k, v| k =~ /^#{keyname}\[/ } extracted = {} attrs.each do |key, value| @config.delete(key) if opts[:delete] name = key[/#{keyname}\[(.+)\]/, 1] extracted[name] ||= [] if value.class == Array value.each { |v| extracted[name] << v } else extracted[name] << value end end if block_given? yielded = extracted.map do |key, value| key, value = yield key, value [ key, value ] end extracted = Hash[yielded] end extracted end def build_timeperiods timeperiods = extract "timeperiod" do |key, value| value.map! do |v| parts = v.split(';') { "timeperiod_alias" => parts[1], "sun_timeranges" => parts[2], "mon_timeranges" => parts[3], "tue_timeranges" => parts[4], "wed_timeranges" => parts[5], "thu_timeranges" => parts[6], "fri_timeranges" => parts[7], "sat_timeranges" => parts[8] } end [ key, value ] end @config["timeperiods"] = timeperiods end def build_services services = extract "service" do |key, value| value.map! do |v| parts = v.split(';') { "description" => parts[0], "volatile" => parts[1], "check_period" => parts[2], "max_attempts" => parts[3], "check_interval" => parts[4], "retry_interval" => parts[5], "notification_group" => parts[6], "notification_interval" => parts[7], "notification_period" => parts[8], "notify_recovery" => parts[9], "notify_critical" => parts[10], "notify_warning" => parts[11], "event_hander" => parts[12], "check_command" => parts[13] } end [ key, value ] end @config["services"] = services end def build_contactgroups contactgroups = extract "contactgroup" do |key, value| value.map! do |v| parts = v.split(';') { "group_alias" => parts[0], "contacts" => parts[1] } end [ key, value ] end @config["contactgroups"] = contactgroups end def build_contacts contacts = extract "contact" do |key, value| value.map! do |v| parts = v.split(';') { "contact_alias" => parts[0], "svc_notification_period" => parts[1], "host_notification_period" => parts[2], "notify_service_recovery" => parts[3], "notify_service_critical" => parts[4], "notify_service_warning" => parts[5], "notify_host_recovery" => parts[6], "notify_host_down" => parts[7], "notify_host_unreachable" => parts[8], "service_notify_commands" => parts[9], "host_notify_commands" => parts[10], "email_address" => parts[11], "pager" => parts[12] } end [ key, value ] end @config["contacts"] = contacts end def build_hosts hosts = extract "host" do |key, value| value.map! do |v| parts = v.split(';') { "host_alias" => parts[0], "address" => parts[1], "parent_hosts" => parts[2], "host_check_command" => parts[3], "max_attempts" => parts[4], "notification_interval" => parts[5], "notification_period" => parts[6], "notify_recovery" => parts[7], "notify_down" => parts[8], "notify_unreachable" => parts[9], "event_handler" => parts[10] } end [ key, value] end @config["hosts"] = hosts end def build_commands commands = extract "command" do |key, value| value.map! do |v| parts = v.split(';') { "command_line" => parts[0] } end [ key, value] end @config["commands"] = commands end def to_hash self.read unless @raw_cfg @raw_cfg.each do |o| parts = o.scan(/^([^=]+)=(.+)$/).flatten key = parts.first value = parts.last case when @config[key] && @config[key].class == Array @config[key] << value when @config[key] @config[key] = [ @config[key] ] else @config[key] = value end end build_timeperiods build_services build_contactgroups build_contacts build_hosts build_commands @config end end class Config include Enumerable class << self attr_accessor :root, :configs, :files @@configs = [] @@files = [] def root=(arg) @@root = arg @@root = Pathname.new(@@root) unless @@root.class == Pathname end def build filename = @@root.join('netsaint.cfg') build_file(filename) end def build_file(filename) file = ConfigFile.new(filename) hash = file.to_hash @@files << file @@configs << hash %w(cfg_file resource_file).each do |linked_file| if hash[linked_file] hash[linked_file].each do |name| path = relativise_path(name) build_file(path) end end end end def relativise_path(filename) @@match = nil parent = @@root.dirname.dirname path = parent + filename.to_s[1..-1] path end # Makes Enumerable mixin work def each @@configs.each {|i| yield i } end # Search across all configs for a particular key. # # Can be passed a string: # # Netsaint::Config.fetch("command_check_interval") # => {"command_check_interval" => "1"} # # Or a regex: # # Netsaint::Config.fetch(/^\$USER\d+\$$/) # => { "$USER2$"=>"/tmp", "$USER1$"=>"/boot" } # def fetch(key) configs = @@configs.find_all {|c| c.keys.find {|k| k[k]} } results = configs.map {|c| Hash[c.find_all { |k,v| k[key] }] } results.inject {|final, hash| final.merge(hash) } end %w(timeperiods services contactgroups contacts hosts commands).each do |method| class_eval <<-METHOD def #{method} #{method} = {} @@configs.each do |c| c["#{method}"].each do |key, value| #{method}[key] ||= [] #{method}[key] << value #{method}[key].flatten! end end #{method} end METHOD end end end end command = ARGV[0] case command when "print" options = ARGV[2..-1] options = Hash[options.map {|o| o.scan(/--(.+)=(.+)/).flatten }] netsaint_root = Pathname.new(options["source"]).expand_path Netsaint::Config.root = netsaint_root Netsaint::Config.build command = ARGV[0] type = ARGV[1] json = { type => Netsaint::Config.method(type).call }.to_json if filename = options["to"] File.open(filename, 'w') do |f| f << json end else puts json end when "dump" options = ARGV[1..-1] options = Hash[options.map {|o| o.scan(/--(.+)=(.+)/).flatten }] netsaint_root = Pathname.new(options["source"]).expand_path Netsaint::Config.root = netsaint_root Netsaint::Config.build @checks = Netsaint::Service.all.map { |service| service.to_flapjack } json = { "batch" => {"id" => 1, "created_at" => Time.now}, "checks" => @checks }.to_json if filename = options["to"] File.open(filename, 'w') do |f| f << json end puts "Dumped config to #{filename}" else puts json end end