#!/usr/bin/perl -w
#
# plot-config.pl prints a nagios configuration as graph
#
#-------------------------------------------------------------------------------
#
# Copyright (c) 2009 Henry78, mailto:henry78@gmx.at
#
# This program is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see <http://www.gnu.org/licenses/>.
#
#-------------------------------------------------------------------------------

#**********************************************************************
#
# BEFORE USING THIS SCRIPT, Nagios::Config::Object has to be edited !
#  (at least applicable for Module Version 000034)
#  due to a bug, you have to remove lines 786-788, which read:
#
#  786:     if ( ref $vf->{$key}[0] eq 'ARRAY' && $value =~ /,/ ) {
#  787:         $value = [ split /\s*,\s*/, $value ];
#  788:     }
#
#*********************************************************************

#--- CHANGELOG ---#
# 2009-04-21 by Henry78
#   - added GPL and published
#   - minor typos and fixes
#
# 2008-11-12 by Henry78
#   - changed output to SVG
#       PNG can't handle images greater than 32768
#
# 2008-09-22 by Henry78
#   - released v0.3
#       - corrected bug in Nagios::Config, processing of multiple parents
#           is now possible
#       - ### not yet, as huff isn't installable via cpan ### added colorful edges (via Huffman encoding of the hostname)
#
# 2008-09-16 by Henry78
#   - released v0.2, including:
#       - help and usage commands
#       - different graphing option (types)
#       - basic error handling

#--- AUTHORS ---#
# Henry78, mailto:henry78@gmx.at, visit http://henry78.at

#--- TODO ---#
#   - error handling has to be improved!
#   - currently only single values are processed. this is very bad. esspecially
#       the parent_host and (service) host_name options are concerned. atm they
#       are silently ignored, as I couldn't find a way to get 'em back from
#       Nagios::Config.
#       - !this got better in 0.3!, but the problem still exists for the services.
#   - type 'c' generates corrupt SVG images (?)

use strict;
use warnings;

use Nagios::Config;
use GraphViz;
use Getopt::Long;
use Data::Dumper::Names;

sub print_help();
sub print_usage();

#--- Variables ---#
my $version="0.5.0";
my $name="plot-nagios-config";
my $description="Create graphviz graphs from nagios configuration.";

my $debug=0;        # enable debug output
my $no_services=0; # include services
my $print_help=0;
my $print_usage=0;
my $g_type='d';
my $outfile='nag_conf_graph';

my $conf_file="/usr/local/nagios/etc/nagios.cfg"; # location of the main nagios configuration file
my $nagios_cfg;     # holds the nagios configurtaion (see Nagios::Config)
my $host;
my $service;
my $g;              # GraphViz graph

Getopt::Long::Configure ("bundling");
GetOptions(
    "g=s"   => \$g_type,            "graph"             =>  \$g_type,
    "h"     => \$print_help,        "help"              =>  \$print_help,
    "u"     => \$print_usage,       "usage"             =>  \$print_usage,
    "d!"    => \$debug,             "debug!"            =>  \$debug,
    "s"     => \$no_services,  "services"          =>  \$no_services,
    "f=s"   => \$conf_file,         "file=s"            =>  \$conf_file,
    "o=s"   => \$outfile,           "outfile=s"         =>  \$outfile
);
if  ( $print_help ) {  print_help() && exit 0; }
if ( $print_usage ) { print_usage() && exit 0; }
if ( $no_services ) { $outfile = "$outfile.hostsonly"; }


#--- Main ---#
#read the config file
print "Reading Nagios configuration from $conf_file\n";
#$nagios_cfg = Nagios::Config->new( Filename => $conf_file, Version => 2 );
$nagios_cfg = Nagios::Config->new( Filename => $conf_file );

#create graph and add initial node 'Nagios host'
#$g = GraphViz->new( directed => 0, overlap => 'false', ratio => 'auto')
$g = GraphViz->new( directed => 0, overlap => 'false', ratio => 'auto', 'concentrate' => 'false' )
    if $g_type eq 'd';
$g = GraphViz->new( directed => 0, overlap => 'false', ratio => 'auto', 'concentrate' => 'false', rankdir => 'true', 'ranksep' => '80' )
    if $g_type eq 'l';
$g = GraphViz->new( layout => 'neato', directed => 0, ratio=>'compress', overlap=>'scalexy' )
    if $g_type eq 'n';
$g = GraphViz->new( layout => 'twopi', directed => 0, ratio=>'compress', overlap=>'scalexy', 'epsilon' => 90 )
    if $g_type eq 't';
$g = GraphViz->new( layout => 'circo', directed => 0, overlap => 'scalexy' )
    if $g_type eq 'c';
unless ( $g ) { 
    print "No graph type set, can't live without it. Try not to set the --graph flag, default to 'd'.\n";
    exit 1;
}
$g->add_node("Nagios");


# draw
foreach $host ( $nagios_cfg->list_hosts ) {
    my $hostname = $host->name;
    my $parent;
    my $service_host;
    my $service_list;
    $debug && print "Processing host " . $host->name . "\n";

    if ( $host->address ) {
        $debug && print "\taddress is " . $host->address . "\n";
        $service_list='';

        $g->add_node( $host->name, "color" => &hash_string($host->name) , 'colorscheme' => 'paired12' );  

        # add edges from child to every parent
        if ( $host->parents ) {
            foreach $parent ( @{$host->parents} ) {
                $debug && print "\thas parent " . $parent->name . "\n";
                $g->add_edge( $host->name => $parent->name , "color" => &hash_string($host->name) , 'colorscheme' => 'paired12' );
            }
        } else {
            $debug && print "\tNO PARENT, setting Nagios\n";
            $g->add_edge( $host->name => 'Nagios' );
        }

        if ( ! $no_services ) {
            $debug && print "\tProcessing services for " . $host->name . "\n";
            foreach $service ( $nagios_cfg->list_services ) {
                next if ( !$service->host_name );
                foreach $service_host ( @{$service->host_name} ) {
                    if ( $service_host->name eq $host->name ) {
                        $debug && print "\t\tnew service: " . $service->name ." for host " . $service_host->name . "\n";
                        $service_list = $service_list . $service->name . "\n";
                    }
                }
            }
            if( $service_list ) {
                $debug && print "\t\tservice list: " . $service_list . "\n";
                $service_list = "Services for " . $host->name .":\n" . $service_list;
                if ( $service_list =~ /mls/ && $g_type ne 'l' ) { # special workaround to get a nicer graph
                    $g->add_node( $service_list, shape => 'box', "color" =>  hash_string($host->name) , 'colorscheme' => 'paired12', 'height' => '5' );
                    $g->add_edge( $service_list => $host->name , "color" =>  hash_string($host->name) , 'colorscheme' => 'paired12' );
                } else {
                    $g->add_node( $service_list, shape => 'box', "color" =>  hash_string($host->name) , 'colorscheme' => 'paired12' );
                    $g->add_edge( $service_list => $host->name , "color" =>  hash_string($host->name) , 'colorscheme' => 'paired12' );
                }
            }
        }

    } else {
        $debug && print "\tNO ADDRESS, skipping\n";
    }
    $debug && print "\n";
}

$debug && print "\nProcessing output\n";

print "Writing SVG as $outfile.$g_type.svg\n";
$g->as_svg( "$outfile.$g_type.svg" );

#print $g->as_debug( "nagios_config.debug");

$debug && print "Writing TEXT as $outfile.$g_type.text\n";
$debug && $g->as_text( "$outfile.$g_type.txt");

#--- Subs ---#
sub print_help() {
    print "$name, v$version\n";
    print $description . "\n";
    print "\n";
    print_usage();
    print "  plot-config.pl [-h | --help]\n";
    print "  plot-config.pl [-u | --usage]\n";
    print "    services:\tdisable processing and graphing of services, aka only-hosts-mode\n";
    print "    debug:\tprint a lot of output, and a text version of the reulting graph (nagios_config.<type>.text)\n";
    print "    <filename>\tlocation of the main Nagios configuration file (nagios.cfg)\n";
    print "    <outfile>\t(base)name of the output file(s), defaults to 'nag_conf_graph'\n";
    print "    <type>\t'd' = dot (default)\n";
    print "\t\t'l' = dot (left to right)\n";
    print "\t\t'n' = neato\n";
    print "\t\t'c' = circo\n";
    print "\n"
}

sub print_usage() {
    print "Usage:\n";
    print "  plot-config.pl [-f | --file filename] [-d | --debug] [-s | --no-services] [-g | --graph type] [-o | --output outfile]\n";
}

sub hash_string {
    my ($string) = @_;
    my $c; my $hash;

    foreach my $c ( split(//, $string) ) {
        $hash += ord($c);
    }

    $hash %= 12;
    $hash++ if ( $hash eq 0 );

    #$hash /= 10 while ( $hash gt 1 );
    #$hash = substr $hash, 0, 4;

    return $hash;
}
