Lusis Development

Why I love Ruby

Posted by John Vincent (jv) on Apr 03 2008 at 12:39 PM
/dev/log >>

I've not had a chance to really get into the meat of Ruby. I'm still not there but I'm off to a pretty good start.

One of my problems is that I don't have the ability to just "pick up" a language. I'm not of that mindset and I'm not a programmer. However, you can't get by in the SA/SE field without knowing enough to debug and you can't ever truly excel at your role until you have mastered at least one scripting language to make the system do what you want. You'll simply be less efficient than the guy next to you.

My skills have stagnated over the past two years that I was at Roundbox simply because I didn't have the time to work them. When I got home, the last thing I wanted to do was work on more SA/SE related stuff. With the difference in roles at MO vs RBX and the difference in work philosophy, I've had some time to actually brush up on Ruby. My problem is that, as I said, I can't just "pick up" a language in the 10 minute tutorial. I need a task that I'm trying to accomplish to really get to the meat of it.

Nagios is my task. I love Nagios. Sure there are more tools out there that are more "modern" or have bolt-on features but as I said previously, Nagios is GOOD at what it does and that is for monitoring.

So I started to work on some ruby "bindings" for Nagios. More of a toolkit really. I came up with a few basic requirements:

- Import existing configs (objects only. Not concerned about the operational parameters)
- Parse existing objects (apart from reading the configurations)
- Create objects (hosts,services,contacts, *groups, etc...)
- Write new configs
- Enforce/Support Templating

Pretty small subset. I don't want to write a new interface for Nagios. I just want a toolkit that I can use to speed up some tasks and quickly get information.

Since I had several good working configurations, I set to work on importing those configs. By importing, I mean reading the nagios.cfg, extracting all the cfg_file/cfg_dir definitions and building some Ruby-navigable representation of the defined objects. Considering that I'm a heavy user of templating, that requirement was already partially being addressed.

At the start, I was only working with hostobjects. However when I had that logic down, I wanted to add everything that it found. Take the following example:

def initialize(cfgfile)
@objects = Hash.new
@hosts = Array.new
@services = Array.new
@timeperiods = Array.new
@contacts = Array.new
@commands = Array.new
@hostgroups = Array.new
@servicegroups = Array.new
self.read_nagioscfg(cfgfile)
end
and further down in the code

def finalize
    File.open("#{@outputdir}/hosts.yaml",'w') { |f| f.puts @hosts.to_yaml}

    File.open("#{@outputdir}/services.yaml",'w') { |f| f.puts @services.to_yaml}

    File.open("#{@outputdir}/contacts.yaml",'w') { |f| f.puts @contacts.to_yaml}

    File.open("#{@outputdir}/commands.yaml",'w') { |f| f.puts @commands.to_yaml}
end

 
These type of blocks occured SEVERAL places as I was ready to parse new object types. Frustrating no? Several times, I generated exceptions because I forgot to add entries to multiple places.

Now, here's why I love Ruby. We're going to rewrite the first def more Ruby-like (and more functional in the process):

def initialize(cfgfile)
@objects = Hash.new
@object_types = %w[host service timeperiod contact contactgroup command hostgroup servicegroup servicedependency hostdependency]
@object_types.each {|x| eval("@#{x}s = Array.new") }
end


Same goes for the second block which is now replaced with this:

def finalize
@object_types.each {|x| eval("File.open\(\"#{@outputdir}/#{x}s.yaml\",'w'\) { |f| f.puts @#{x}s.to_yaml}")}
end

..... I think I finally "get" it.

Back