#!/usr/bin/perl # ----------------------- # Deploy Script with actions # -> require a ini file as argument # -------------------------------------- use strict; use warnings; use Config; use lib "C:/CUSTOM/LIB"; use threads; use Thread::Queue; use Data::Dumper qw(Dumper); use Log::Log4perl; use Log::Log4perl::Level; use Config::IniFiles; use POSIX qw(strftime); use Getopt::Long; use File::Basename; use Net::Ping; #------------------------- # Variables definition #------------------------- my $LOG4PERLINIT = "C:/CUSTOM/CONFIG/Log4Perl.conf"; my $LOGPATH = "C:/CUSTOM/LOGS/"; my $TMPEXT=".tmp" ; my $ROLLBACKEXT=".rollback"; my $DEFAULTEXETOKILL="SomeProcess"; my $DEFAULTCOPYSCRIPT="C:/CUSTOM/cmd/deploy_files.cmd"; my $DEFAULTROLLBACKSCRIPT="C:/CUSTOM/cmd/rollback_deploy.cmd"; my ($displayHelp, $configFile, $flagDeploy, $flagNoMove, $flagRollback, $section, $srvRef, $processToKill, $copyScript, $rollbackScript, $user, $pass) = ""; my $logger = undef ; my %config; my %threadDetails ; my @deployments ; my @targets ; my @Threads ; my @ThreadsQueue ; my @runningThreads ; #------------------------------------------------------------------------ # Subroutines definition #------------------------------------------------------------------------ # Help function sub Usage { print STDERR << "EOF"; usage: $0 [-h] [-c /path/to/ini_file.ini] [--deploy] [--novemove] [--rollback] -h|--help : this (help) message -c|--config : path to configuration ini file -d|--deploy : deploy the files to a temporary directory, kill process and move the temporary as destination old destination will remain as .rollback for rollback purpose -n|--nomove : only dispatch the file in the temporary directory without moving them as reference -r|--rollback : kill the process and move the .rollback as reference, the old content is removed This script require a configuration file path as argument and at least one action [--deploy] or [--rollback] Example: $0 --config D:\\CONFIG\\Livraison_Citri\\Livraison_Europe.ini --deploy EOF if ( @_ == 1 ) { exit($_[0]); } else { exit(0); } } # Exit on unsupported argument sub unsupported_arg { print "[".strftime('%Y/%m/%d %H:%M:%S',localtime(time()))."] | ERROR | Unexpected argument:". $_[0] ; Usage(1); } # Function used to generate a logfile name sub getLogFileName { #if (defined $logFileName) { return getReplacedPattern($logFileName, time()); } return "$LOGPATH" . fileparse($0) . '.trace.' . &strftime("%Y%m%d", localtime(time())) . '.log'; } # For each "DEPLOY-*" section: a Destination and a Source parameters are required sub checkDeploySection { my $sectionToCheck = $_[0]; if (exists($config{$sectionToCheck}{"Destination"})) { if ( $config{$sectionToCheck}{'Destination'} eq "" ) { $logger->warn("No value defined for parameter 'Destination' in section [$sectionToCheck], section will be ignored"); return 1; } } else { $logger->warn("No parameter 'Destination' [val: ".$config{$sectionToCheck}{'Destination'}." defined in section [$sectionToCheck], section will be ignored"); return 1; } if (exists($config{$sectionToCheck}{"Source"})) { if ( $config{$sectionToCheck}{'Source'} eq "" ) { $logger->warn("No value defined for parameter 'Source' in section [$sectionToCheck], section will be ignored"); return 1; } else { $logger->warn("Source directory [".$config{$sectionToCheck}{'Source'}."] in [$sectionToCheck] not found or unreachable, section will be ignored") if ( ! -d "$config{$sectionToCheck}{'Source'}" ); return 1; } } else { $logger->warn("No parameter 'Source' defined in section [$sectionToCheck], section will be ignored"); return 1; } return 0; } # Routine to check configuration provided in the ini file sub checkConfig { if (exists($config{'Config'}{'User'})) { if ( $config{'Config'}{'User'} ne "" ) { $user = $config{'Config'}{'User'} ; $logger->info("User that will execute the deploy cmd is [$user]") ; } } else { $logger->logdie("No 'User' parameter provided in [Config] for script execution, please fix"); } if (exists($config{'Config'}{'Password'})) { if ( $config{'Config'}{'Password'} ne "" ) { $pass = $config{'Config'}{'Password'} ; } } else { $logger->logdie("No 'Password' parameter provided in [Config] for script execution, please fix"); } if (exists($config{'Config'}{'Srv_Ref'})) { if ( $config{'Config'}{'Srv_Ref'} ne "" ) { $srvRef = $config{'Config'}{'Srv_Ref'} ; $logger->info("Reference server (for archiving) will be [$srvRef]") ; } } else { $logger->warn("No reference server provided [Config] parameter [Srv_Ref] provided, no archiving will be done"); } if (exists($config{'Config'}{'ExeToKill'})) { if ( $config{'Config'}{'ExeToKill'} ne "" ) { $processToKill = $config{'Config'}{'ExeToKill'} ; $logger->info("Deploy script will kill process [$processToKill]") ; } } else { $logger->warn("No process name to kill provided, will used default value [$DEFAULTEXETOKILL] "); $processToKill = $DEFAULTEXETOKILL; } if (exists($config{'Config'}{'CopyScript'})) { if ( $config{'Config'}{'CopyScript'} ne "" ) { $copyScript = $config{'Config'}{'CopyScript'} ; if ( ! -e $copyScript ) { $logger->logdie("Deploy script [$copyScript] does not exist.") ; } else { $logger->info("Deploy script will call copy script [$copyScript]") ; } } } else { $copyScript = $DEFAULTCOPYSCRIPT; if ( ! -e $copyScript ) { $logger->logdie("Default deploy [$copyScript] does not exist .") ; } else { $logger->warn("No value for copy script provided, will use default [$DEFAULTCOPYSCRIPT] "); } } if (exists($config{'Config'}{'RollbackScript'})) { if ( $config{'Config'}{'RollbackScript'} ne "" ) { $rollbackScript = $config{'Config'}{'RollbackScript'} ; if ( ! -e $rollbackScript ) { $logger->logdie("Rollback script [$rollbackScript] does not exist. ") ; } else { $logger->info("Deploy script will call rollback script (if specified) [$rollbackScript]") ; } } } else { $rollbackScript = $DEFAULTROLLBACKSCRIPT; if ( ! -e $rollbackScript ) { $logger->logdie("Rollback script [$rollbackScript] does not exist. ") ; } else { $logger->info("Deploy script will call rollback script (if specified) [$rollbackScript]") ; } } } # Function called to ensure deploy section is properly formated sub checkServeursSection { if (exists($config{"Serveurs"}) ) { if (exists($config{"Serveurs"}{"Targets"})) { # Ensure we do not have only one target since it would mess up the array... if (ref($config{"Serveurs"}{"Targets"}) eq "HASH") { foreach my $target (keys $config{"Serveurs"}{"Targets"}) { my $server = $config{"Serveurs"}{"Targets"}[$target] ; if( $server !~ /^[-|#]/i ) { push(@targets, $server); } } } else { my $server = scalar($config{"Serveurs"}{"Targets"}) ; push(@targets, $server); } } else { $logger->logdie("No parameter 'Targets' found in section [Serveurs], nowhere to deploy" ); } } else { $logger->logdie("No section [Serveurs] found in ini file, nowhere to deploy" ); } $logger->logdie("No value for 'Targets' parameter in section [Serveurs], nowhere to deploy" ) if (@targets == 0 ); } # Main subroutine, here's where we will deploy content sub deployRoutine($$$;$) { my ($remoteHost, $sourceDir, $destDir, $flagNoMove) = @_ ; $flagNoMove="" unless ($flagNoMove); my $cmd = "psexec \\\\$remoteHost /accepteula -u $user -p $pass -h -e -n 3 -f -c $copyScript $sourceDir \"$destDir\" $TMPEXT $ROLLBACKEXT $processToKill $flagNoMove" ; my $returnCode = `$cmd`; return $returnCode ; } #------------------------------------------------------------------------ # Main #------------------------------------------------------------------------ # Firt check if we can run with threads, if not... then exit $Config{useithreads} or die "[".strftime('%Y/%m/%d %H:%M:%S',localtime(time()))."] | ERROR | Perl wasn't built with threads enable, install it with threads first. "; # Usual check for the call of the script GetOptions ( 'help|h|?' => \$displayHelp, 'c|config=s' => \$configFile, 'd|deploy' => \$flagDeploy, 'n|nomove' => \$flagNoMove, 'r|rollback' => \$flagRollback, '<>' => \&unsupported_arg ); if ( ! -e $configFile ) { print "[".strftime('%Y/%m/%d %H:%M:%S',localtime(time()))."] | ERROR | Configuration file provided do not exist [".$configFile."] \n" ; exit 1; } # We source and check the ini file tie %config, 'Config::IniFiles', ( -file => "$configFile" ) or die "[".strftime('%Y/%m/%d %H:%M:%S',localtime(time()))."] | ERROR | Failed to load configuration file [".$configFile."] \n" ; # Check that there is a Config section in the ini file if (!exists($config{'Config'})) { print "[".strftime('%Y/%m/%d %H:%M:%S',localtime(time()))."] | ERROR | No [Config] section in provided ini file \n" ; exit(1); } # If ini file contains LOG_PATH instruction we use it (see default above) if (exists($config{'Config'}{'REP_LOG'})) { if ( $config{'Config'}{'REP_LOG'} ne "" ) { $LOGPATH = $config{'Config'}{'REP_LOG'} ; print "[".strftime('%Y/%m/%d %H:%M:%S',localtime(time()))."] | INFO | Using configured log path [".$LOGPATH."] \n" ; } } # Then we initiate the logger Log::Log4perl::init($LOG4PERLINIT); $logger = Log::Log4perl->get_logger('APP-BFI-IEC_Log'); $logger->level(uc('TRACE')); # Check if arguments make sens if ($flagRollback && $flagDeploy || $flagRollback && $flagNoMove) { $logger->logdie("Action [--rollback] can't be called "); } Usage(1) unless($flagDeploy || $flagRollback) ; # Check configuration in the ini file checkConfig(); # Check defintion for each deploy section checkServeursSection(); # We generate the of deployments based on DEPLOY- section(s) in the ini file # deploy section can be commented while starting with - or # and thus will be skipped foreach $section (keys %config) { if ($section =~ /deploy-/i and $section !~ /^[-|#]/i ) { push(@deployments, $section); } } # And if no DEPLOY- section exist, we quit $logger->logdie("Nothing to deliver, no section [Deploy-*] found in ini file") unless (scalar(@deployments) >0 ) ; # Check for each deploy- section if it is properly defined foreach my $deploySection (@deployments) { # Error found, we'll skip the deploy section checkDeploySection($deploySection) ; if ( $? == 1 ) { my $index=0; $index++ until $deployments[$index] eq "$deploySection"; splice(@deployments, $index, 1); } else { $logger->info("Validated section for [".$deploySection."] "); } } # Now we go for the deployment foreach my $targetServer (@targets) { # Ping host to check its available my $p = Net::Ping->new(); if ($p->ping($targetServer)) { $logger->info("Target [$targetServer] is alive, starting deployment "); foreach my $deploySection (@deployments) { if ($flagDeploy) { $logger->info("Starting deployment for [$deploySection] on target [$targetServer] "); my $deploySource = "$config{$deploySection}{'Source'}" ; my $deployDest = "$config{$deploySection}{'Destination'}" ; if ($flagNoMove) { my $thread = threads->new(sub {deployRoutine($targetServer, $deploySource, $deployDest, "true")}); push (@Threads, $thread); $threadDetails{$thread}{"Section"} = "$deploySection"; $threadDetails{$thread}{"Target"} = "$targetServer"; } else { my $thread = threads->new(sub {deployRoutine($targetServer, $deploySource, $deployDest)}); push (@Threads, $thread); $threadDetails{$thread}{"Section"} = "$deploySection"; $threadDetails{$thread}{"Target"} = "$targetServer"; } } } } else { $logger->warn("Couldn't ping target [$targetServer] host will be ignored "); } $p->close(); } # We now handle open threads, waiting for each completion @runningThreads = threads->list(threads::running); while (scalar @runningThreads != 0) { foreach my $thr (@Threads) { if ($thr->is_joinable()) { $threadDetails{$thr}{"EndStatus"} = $thr->join(); } } @runningThreads = threads->list(threads::running); } exit 0;