Source of scanner/lib/Uliska.pm


        =head1 NAME

Uliska - Main library for Uliska scanner.

=head1 SYNOPSIS

    use Uliska;
    read_config;
    $ENV{PATH} = $config{path};
    executeList(read_commands('generic', 1)); # 1 - means requried
    executeList(read_commands($result{kernel_name}));
    my $os = Uliska->init($result{kernel_name});

=head1 DESCRIPTION

Package Uliska.pm provides functions that are used in main C
executable script. Additionally function C is a factory
inintializer, it loads specific OS/archtecture etc. modules when
required.

=cut

package Uliska;

use strict;
use warnings;
use Exporter;
use YAML::Old;

our @ISA = qw(Exporter);

=head2 EXPORTED FUNCTIONS

    &read_commands
    &executeList
    &read_config
    &init
    &output_result

=head2 GLOBAL VARIABLES

    %config
    %result

=cut


our @EXPORT = qw/&read_commands &executeList &read_config &init
                 &output_result %config %result/;

BEGIN {
  use vars qw/ %result %config/;

  %result = (
             __WARNINGS__ => [],
             __ERRORS__ => [],
             __LOG__ => []
            );
  $ENV{$_} = 'C' foreach qw/LANG LC_TYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES LC_ALL/;

  (my $libdir = $INC{'Uliska.pm'}) =~ s/Uliska.pm$//;
  (my $cfgdir = $libdir) =~ s/lib\/?$/cfg/;
  #
  # Default configuration
  # ----------------------------------------
  %config = (
             lib_dir => $libdir,
             cfg_dir => $cfgdir,
             path    => "/bin:/usr/bin:/sbin:/usr/sbin",
             dest_dirname => "/tmp",
             dest_basename => "uliska_inventory_scan.yml"
            );
  $ENV{PATH} = $config{path};
}

=head1 FUNCTIONS

=head2 init

Init a factory module on demand. Module is searched in the
./lib/Uliska directory. If module exists, it's required and instance
returned. If module file does not exist, undef is returned.

Loaded module must provide run() function.

=head3 Parameter

=over

=item  $module_file 

Module PATH starting from $config{lib_dir}/Uliska/. Can include nested
subdirectories.

=back

=head3 Example

   my $os = Uliska->init($result{kernel_name});
   $os->run() if defined $os;
   my $os = Uliska->init("Linux");
   my $os = Uliska->init("Linux/RedHat");
   my $os = Uliska->init("Linux/RedHat/5");

=cut

sub init {
  my ($class,$module_file) = (shift, shift);

  my $module = $module_file;
  $module =~ s/\//::/g;

  my $require_file = "$config{lib_dir}/Uliska/$module_file.pm";
  $class = "Uliska::$module";
  if (-f $require_file ) {
    require $require_file;
    &to_log("Loaded module file: $require_file");
    return $class->new(@_);
  } else {
    &missing("Module does not exist: $require_file");
    return undef;
  }
}

=head2 output_result

Print collected data to file or STDOUT. Function outputs YAML formatted
global hash %result;

TODO

=cut

sub output_result {
    print to_yaml(\%result);
}

=head2 read_config

Read script configuration file and override defaults, that are set in
the Uliska package.

TODO

=cut

sub read_config () {};

=head2 executeList

Execute list of commands. Parameter is array with list of
commands. Function can be called multiple times during script run:
shift array while executing, so that commands not executed twice on
successive calls.

=cut

sub executeList ($) {
  my $commands = shift;
  while (my $cmd = shift @$commands) { run($cmd) }
}

=head2 read_commands

Read command list from file and push into @commands array. If 2nd
parameter $must_exist is not true, simply ignore if commands list
file does not exist: make it a responsibility of user to populate
lists.

Return array @commands

=cut
sub read_commands {
    my ($cmd_file, $must_exist) = @_;
    my $file = "$config{cfg_dir}/${cmd_file}.cfg";
    if (-f $file) {
      my @commands;
      open (my $fh, "<", $file) or die "can not open commands file $file: $!";
      while (<$fh>) { push @commands, $_ }
      &to_log("Loaded command file: $file");
      return \@commands;
    } else {
      if (defined $must_exist and $must_exist) {
        die "Command file does not exist: $file"
      } else {
        &missing("command file does not exist: $file");
        return # exit is file doe not exist and is not required to exist
      }
    }
}

=head2 run

Execute command and populate global hash variable %result.

=cut
sub run ($) {
  my $arg = trim(shift);
  my ($command,$name) = split /\s*\#\s*/, $arg, 2;

  return if (! defined $command or $command =~ /^\s*$/); # If it's comment line or empty

  push @{$result{'__COMMANDS__'}}, $arg; # Store list of all commands

  my @out =
    `($command 2>&1 | cat -v) 2>&1`;  # Pipe output through cat -v to
                                      # filter out, non printables.

  $name = (defined $name and $name !~ /^\s*$/) ? $name : $command;
  for (@out) { chomp };
  if (defined $out[0]) {
    if (
        scalar grep {/(operation\s+not\s+permitted|
                        command\s+not\s+found|
                        no\s+such\s+file\s+or\s+directory|
                        permission\s+denied$|
                        cat:\s+cannot\s+open
                      )/ix} @out or
        scalar grep {/^usage: /} @out
       ) {
      push @{$result{'__ERRORS__'}}, ({ trim($arg) => \@out})
    } else {
      $result{trim($name)} = (scalar @out == 1) ? $out[0] : \@out;
    }
  }
}

=head2 trim

Strip \n and spaces from string

=cut
sub trim ($) {
  chomp(my $ret = shift);
  $ret =~ s/^\s+|\s+$//g;
  $ret;
};


=head2 to_yaml

Take Hash input and convert it to YAML string. Hash key is command or
alias, values are arrays of lines of output. Special keys are
__COMMANDS__ and __ERRORS__: list of all commands executed by script,
and execution errors.

Return YAML formatted string.

=cut
sub to_yaml ($) { return Dump(shift) };

=head2 to_log

Push log messages to $result{__LOG__}

to_log("Message")

=cut

sub to_log ($) { push @{$result{__LOG__}}, shift };

=head2 missing

Report missing modules or configuration files.

=cut

sub missing ($) { push @{$result{__WARNINGS__}}, shift };

1;

=head1 AUTHOR

Dmytro Kovalov

dmytro.kovalov@gmail.com

Started January 2012

=cut