I recently read a great article on production-level scripting in the August 2005 issue of Sys Admin Magazine. In it, the author advocated the use of a template to make production-script rollout much faster. In an example template, the author included multiple levels of verbosity, options to email execution reports and error reports, and a test mode that would perform a test run of the code without actually committing to any changes. The example the author provided in the article was a bash shell script, but said the same techniques could be applied to perl or other administrative languages.
I have attempted to do the same thing in perl, but I have encountered 2 problems:
Any suggestions you have would be greatly appreciated! My template script in it's entirety is below:
#!/usr/bin/perl
##############################################################
# Script : template
# Author : roswell1329
# Date : 08/31/2005
# Last Edited: 09/07/2005, roswell1329
# Description: working template for fast production rollout
##############################################################
# Purpose:
# - create a working template with logging, and emailed
# reports to make production development of scripts
# faster
# Requirements:
# - error code checking
# - different debugging/logging levels
# - option for email execution report delivery
# - option for email error delivery
# - option for TEST (go through motions and logging with no
# actual changes made
# Method:
# - make a script 'shell' with some of the standard options
# already implemented
# Syntax: template [-h]
# template [-m email] [-e email] [-t] [-d]
# template [-m email] [-e email] [-t] [-v]
# Notes:
# - not happy with execution logging or failure checking
##############################################################
######################################
#### Opening Initializations ####
######################################
use strict;
use Getopt::Std;
my $ex_status = 0;
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
$ENV{PATH} = '/bin:/usr/bin';
$SIG{INT} = sub { $ex_status = 1; die };
$SIG{PIPE} = sub { $ex_status = 2; die };
my $cmdname = (split /\//,$0)[-1];
my $basedir = (split /\/$cmdname/,$0)[0];
my $cfgfile = "$basedir/$cmdname.cfg";
my $logfile = "$basedir/$cmdname.log";
my $maillog = "";
my $thread = $$;
my $user = (getpwuid $<)[0];
my %opts = ();
my %configs = ();
getopts('de:hm:tv', \%opts);
&usage if ($opts{h});
&usage if (exists($opts{m}) && (!$opts{m}));
&usage if (exists($opts{e}) && (!$opts{e}));
&usage if (exists($opts{d}) && exists($opts{v}));
if(-e $cfgfile)
{
open(CONFIG,"$cfgfile")
or die "Cannot open config file $cfgfile\n";
foreach my $attrib (<CONFIG>)
{
next if ($attrib =~ /^$/);
next if ($attrib =~ /^#/);
my ($attrib_name,$attrib_val) = split /\s*=\s*/,$attrib;
chomp($attrib_name,$attrib_val);
$configs{$attrib_name} = $attrib_val;
}
&debugger("Config file found. Values loaded");
while(my($k,$v) = each %configs)
{
&debugger("|- $k attribute loaded with '$v'");
}
}
else
{
&debugger("No config file found. Proceeding with internal values");
}
######################################
#### Main Loop ####
######################################
&debugger("Initializations complete");
&debugger("Starting main routine");
######################################
#### Support Functions ####
######################################
sub debugger($)
{
if(!exists($opts{v}) && !exists($opts{d}))
{
return;
}
my $dbstring = shift @_;
my ($dsec,$dmin,$dhour,$dday,$dmon,$dyear,$dwday)=(localtime)[0..6];
my $monname = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug',
'Sep','Oct','Nov','Dec')[$dmon];
my $dayname = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat')[$dwday];
open(LOG,">>$logfile") or die "Cannot open logfile $logfile\n";
if(exists($opts{d}))
{
warn "[DEBUG]: $dbstring\n";
$maillog .= $dbstring . "\n" if ($opts{m});
printf LOG "%s %s %02d %02d:%02d:%02d thread[%5d]: %s\n",
+
($dayname,$monname,$dday,$dhour,$dmin,$dsec,$thread,$dbstring);
}
elsif(exists($opts{v}))
{
next if($dbstring =~ /\|-/);
$maillog .= $dbstring . "\n" if ($opts{m});
printf LOG "%s %s %02d %02d:%02d:%02d thread[%5d]: %s\n",
($dayname,$monname,$dday,$dhour,$dmin,$dsec,$thread,$dbstring);
}
close(LOG);
}
sub mail_report($$)
{
my ($mesg,$mailsubj) = @_;
my $mailfrom = $user;
open(MAIL, "| sendmail -oi -t") or die "Cannot start sendmail\n";
print MAIL "To:$opts{m}\nFrom:$mailfrom\nSubject:$mailsubj\n\n";
print MAIL $mesg;
close(MAIL);
}
sub usage
{
print <<EOF;
Usage: $cmdname [-h]
$cmdname [-m email] [-e email] [-t] [-v]
$cmdname [-m email] [-e email] [-t] [-d]
Options: -d debugging mode. This mode will verbosely describe
the current execution of $cmdname.
-e [EMAIL] error reporting. This option will send any errors
detected by $cmdname to email address EMAIL.
-h help. This option will display this help message.
-m [EMAIL] execution reporting. This option will send a
$cmdname execution report to the supplied email
address.
-t test mode. This allows you to run the script
without making any critical changes.
-v verbose mode. This option will enable logging for
this execution of $cmdname
EOF
}
END
{
$ex_status > 0 ? &debugger("Program killed!") :
&debugger("Exiting normally");
&debugger("Mailing reports");
&mail_report($maillog,"$cmdname Execution Report") if ($opts{m});
if($opts{e})
{
if(($!) || ($?))
{
my $mesg = "Internal Failures:\n------------------\n$!\n\n" .
"External Failures:\n-----------------\n$?\n";
&mail_report($mesg,"$cmdname Error Report");
}
}
exit;
}