#!/usr/bin/perl -w # # NAME: Xesxupdate.pl # # AUTHOR : Jeremy Pries, Xcedex # DATE : 5/21/2007 # VERSION: 1.4 Beta # # YOU MUST UPDATE VARIABLES BELOW # # PURPOSE: # Updates VMware ESX servers using esxupdate. # Looks for a list of updates that need are available # to install. Will check if each one is installed. # If not, will install them in the correct order. # # The correct order is currently assumed to be # oldest first. If there are multiples on the same day # any order is fine. This patch order assumption cannot be # verified with official documentation at this time. # # The application looks at the timestamp # as reported by the info routine in esxupdate. This is not always # the same date as reported on the website. Accuracy is to the second. # # REQUIREMENTS: # LWP::Simple # Time::Local # Getopt::Long # # All are default modules in ESX 3.0.1 # # Outbound firewall must allow traffic. # If you are using port 80, use something like this: # esxcfg-firewall -o 80,tcp,out,HTTPout # # OTHER INFORMATION: # This application does not do logging, but output may be captured # by redirecting output, both STDOUT and STDERR. # # esxupdate logs to /var/log/vmware/esxupdate.log # # TO DO LIST: # Detect a bunk entry in updates.list # Add FTP and NFS support # Add command line option to specify variable values. # Test https with a self-signed cert. use LWP::Simple; use Time::Local; use Getopt::Long; ######################################### # # User Variables # # Modify to fit your needs # # protocol options # http # https # file (used for nfs) # ######################################### #my $protocol="file"; my $protocol="http"; my $port="80"; my $server="10.0.0.101"; my $baseDir="/kickstart/ks-oses/esx-301/patches/"; #need trailing / #my $baseDir="/vmfs/volumes/nfs-kickstart/ks-oses/esx-301/patches/"; my $patchListFile="updates.list"; ################################################################### # # Usage subroutine # ################################################################### sub usage() { print "\nThis script helps automate the esxupdate process.\n"; print "\n"; print "usage: $0 [-q] [-c]\n"; print "\n"; print " -q [optional] - quiet; supress confirmation\n"; print " -c [optional] - check; Will list patches missing\n"; print " \tand patches installed. All other optional are ignored.\n"; print "\n"; print "$0 -h will display this message\n"; exit(1); } ################################################################### # # GetPatchList subroutine # ################################################################### sub GetPatchList { my ($protocol,$server,$port,$baseDir,$patchListFile) = @_; my @patchlist; my $patchListPath; if ($protocol eq "http" || $protocol eq "https") { $patchListPath = "$protocol://$server:$port$baseDir$patchListFile"; @patchlist = split(/\n/,get $patchListPath); } elsif ($protocol eq "file") { @patchlist = split(/\n/,`cat $baseDir$patchListFile`); } chomp @patchlist; if (@patchlist == 0) { print "\nPatch list is empty. This may be a communication problem.\n"; print "You may need to allow the outbound traffic on the local\n"; print "firewall. Example:\n"; print "esxcfg-firewall -o 80,tcp,out,HTTPout\n\n"; print "\nUsing the following address:\n"; print "$patchListPath\n\n"; } return(@patchlist); } ################################################################### # # Check subroutine # ################################################################### sub check { my @patchlist = @_; my (@missing,@installed); my $querypatch; foreach $patch (@patchlist){ #Hammer in some support for bundled patches. # We can tell this if there is a / in the patch listed in updates.list if ($patch =~ /\/(.*)/){ $querypatch = $1; } else { $querypatch = $patch; } #endif # Do the actual check for the patch if (`/usr/sbin/esxupdate query | awk \'{print\$1}\' | grep $querypatch | wc -l` == 1){ #patch present @installed=($patch,@installed); } else { #patch missing @missing=($patch,@missing); } #endif } #end foreach # Print Results print "\n"; print "Patches Missing\n"; print "---------------\n"; foreach (@missing){print "$_\n"} print "\n"; print "Patches Installed\n"; print "---------------\n"; foreach (@installed){print "$_\n";} print "\n"; exit(0); } ################################################################### # # Main # ################################################################### # Other Variables my $patchesFullPath; if ($protocol eq "http" || $protocol eq "https") { $patchesFullPath = "$protocol://$server:$port$baseDir"; } elsif ($protocol eq "file") { $patchesFullPath = "file:$baseDir"; } my ($patchReleaseDateText,$patchReleaseDate); my %monthnum = qw( Jan 0 Feb 1 Mar 2 Apr 3 May 4 Jun 5 Jul 6 Aug 7 Sep 8 Oct 9 Nov 10 Dec 11 ); my %todoHash = (); my $confirm = ""; GetOptions ('check' => \$check,'quiet' => \$quiet,'help|?' => \$help); usage() if $help; # Get the list of patches my @patchlist = GetPatchList($protocol,$server,$port,$baseDir,$patchListFile); # If the command line option "check" is set, then run the check sub # and do not do the rest of this script. check(@patchlist) if $check; ################################################## # # Generate a to do list by looking at a master patch list # and checking each patch to see if it is installed # ################################################## print "Generating To Do List\n\n"; foreach $patch (@patchlist){ #Hammer in some support for bundled patches. # We can tell this if there is a / in the patch listed in updates.list # This code is also in the check subroutine. Sounds like a good use case for # yet another sub. Someday. # my $querypatch; if ($patch =~ /\/(.*)/){ $querypatch = $1; } else { $querypatch = $patch; } #endif if (`/usr/sbin/esxupdate query | awk \'{print\$1}\' | grep $querypatch | wc -l` == 1){ print "$patch present. Skipping.\n"; } else { $patchReleaseDateText = `/usr/sbin/esxupdate -r $patchesFullPath$patch info | grep "Release Date :"`; chomp $patchReleaseDateText; my @patchReleaseDate = split(" ",$patchReleaseDateText); my @patchReleaseTime = split(":",$patchReleaseDate[6]); $patchReleaseDate = timelocal($patchReleaseTime[2],$patchReleaseTime[1],$patchReleaseTime[0],$patchReleaseDate[5],$monthnum{$patchReleaseDate[4]},$patchReleaseDate[8]); $todoHash {$patch} = $patchReleaseDate; } #endif } #End foreach ################################################ # # Now we need to sort the todoHash by release date and apply oldest first # ################################################ if (scalar keys %todoHash > 0) { @todoSorted = sort{ $todoHash{$a} <=> $todoHash{$b} } keys %todoHash; # The first time through the list just display's what we are going to do. print "\nApplying the following patches in the listed order\n"; foreach $patch (@todoSorted) { print "$patch\n"; } #End foreach print "\n"; # Second time will do the actual installation. # Asking for a confirmation first. unless ( $quiet ) { print "Would you like to continue (y/n)? :"; $| = 1; $confirm = ; chomp $confirm; } #end unless if ($confirm eq "y" || $quiet) { foreach $patch (@todoSorted) { print "INSTALLING $patch...\n"; my $results = `/usr/sbin/esxupdate -r $patchesFullPath$patch --noreboot update`; #my $results = "/usr/sbin/esxupdate -r $patchesFullPath$patch --noreboot update"; print "$results\n"; print "######################################\n"; } #End foreach } else { print "Cancelled.\n"; } #endif } else { print "All patches are already installed. Nothing to do.\n"; } #endif