webgui/sbin/upgrade.pl
2004-09-03 17:01:59 +00:00

376 lines
11 KiB
Perl

#!/usr/bin/perl
#-------------------------------------------------------------------
# WebGUI is Copyright 2001-2004 Plain Black Corporation.
#-------------------------------------------------------------------
# Please read the legal notices (docs/legal.txt) and the license
# (docs/license.txt) that came with this distribution before using
# this software.
#-------------------------------------------------------------------
# http://www.plainblack.com info@plainblack.com
#-------------------------------------------------------------------
our ($webguiRoot);
BEGIN {
$webguiRoot = "..";
unshift (@INC, $webguiRoot."/lib");
}
use DBI;
use File::Path;
use Getopt::Long;
use Parse::PlainConfig;
use strict;
use WebGUI::SQL;
my $help;
my $history;
my $override;
my $quiet;
my $mysql = "/usr/bin/mysql";
my $mysqldump = "/usr/bin/mysqldump";
my $backupDir = "/data/backups";
my $skipBackup;
my $doit;
GetOptions(
'help'=>\$help,
'history'=>\$history,
'override'=>\$override,
'quiet'=>\$quiet,
'mysql=s'=>\$mysql,
'doit'=>\$doit,
'mysqldump=s'=>\$mysqldump,
'backupDir=s'=>\$backupDir,
'skipbackup'=>\$skipBackup
);
if ($help){
print <<STOP;
Usage: perl $0
Options:
--backupDir The folder where backups should be
created. Defaults to '/data/backups'.
--help Display this help message and exit.
--history Displays the upgrade history for each of
your sites. Note that running with this
flag will NOT run the upgrade.
--mysql The path to your mysql client executable.
Defaults to '/usr/bin/mysql'.
--mysqldump The path to your mysqldump executable.
Defaults to '/usr/bin/mysqldump'.
--override This utility is designed to be run as
a privileged user on Linux style systems.
If you wish to run this utility without
being the super user, then use this flag,
but note that it may not work as
intended.
--quiet Disable output unless there's an error.
--skipBackup Backups will not be performed during the
upgrade.
STOP
exit;
}
unless ($doit) {
print <<STOP;
+--------------------------------------------------------------------+
| |
| For more information about this utility type: |
| |
| perl upgrade.pl --help |
| |
+--------------------------------------------------------------------+
| |
| W A R N I N G |
| |
| There are no guarantees of any kind provided with this software. |
| This utility has been tested rigorously, and has performed without |
| error or consequence in our labs, and on our production servers |
| for more than a year. However, there is no substitute for a good |
| backup of your software and data before performing any kind of |
| upgrade. |
| |
| NOTE: This utility will work on MySQL databases only. Any |
| configs using non-MySQL databases will be skipped. |
| |
+--------------------------------------------------------------------+
| |
| You must include the command line argument "--doit" in your |
| command in order to bypass this message. The upgrade will not run |
| without the "--doit" flag. |
| |
+--------------------------------------------------------------------+
STOP
exit;
}
if (!($^O =~ /^Win/i) && $> != 0 && !$override) {
print "You must be the super user to use this utility.\n";
exit;
}
## Globals
$| = 1;
our $perl = $^X;
our $slash;
if ($^O =~ /^Win/i) {
$slash = "\\";
} else {
$slash = "/";
}
our $upgradesPath = $webguiRoot.$slash."docs".$slash."upgrades".$slash;
our $configsPath = $webguiRoot.$slash."etc".$slash;
our (%upgrade, %config);
## Find site configs.
print "\nGetting site configs...\n" unless ($quiet);
opendir (DIR,$configsPath) or die "Can't open $configsPath\n";
my @files=readdir(DIR);
closedir(DIR);
foreach my $file (@files) {
if ($file =~ /(.*?)\.conf$/ && $file ne "some_other_site.conf") {
print "\tFound $file.\n" unless ($quiet);
$config{$file}{configFile} = $file;
my $config = Parse::PlainConfig->new('DELIM' => '=',
'FILE' => $configsPath.$config{$file}{configFile},
'PURGE' => 1);
$config{$file}{dsn} = $config->get('dsn');
my $temp = _parseDSN($config{$file}{dsn}, ['database', 'host', 'port']);
if ($temp->{'driver'} eq "mysql") {
$config{$file}{db} = $temp->{'database'};
$config{$file}{host} = $temp->{'host'};
$config{$file}{port} = $temp->{'port'};
$config{$file}{dbuser} = $config->get('dbuser');
$config{$file}{dbpass} = $config->get('dbpass');
$config{$file}{mysqlCLI} = $config->get('mysqlCLI');
$config{$file}{mysqlDump} = $config->get('mysqlDump');
$config{$file}{backupPath} = $config->get('backupPath');
my $dbh = DBI->connect($config{$file}{dsn},$config{$file}{dbuser},$config{$file}{dbpass});
($config{$file}{version}) = WebGUI::SQL->quickArray("select webguiVersion from webguiVersion
order by dateApplied desc, webguiVersion desc limit 1",$dbh);
$dbh->disconnect;
rmtree($config->get("uploadsPath".$slash."temp"));
} else {
delete $config{$file};
print "\tSkipping non-MySQL database.\n" unless ($quiet);
}
}
}
if ($history) {
print "\nDisplaying upgrade history for each site.\n";
require WebGUI::DateTime;
foreach my $file (keys %config) {
print "\n".$file."\n";
my $dbh = DBI->connect($config{$file}{dsn},$config{$file}{dbuser},$config{$file}{dbpass});
my $sth = WebGUI::SQL->read("select * from webguiVersion order by dateApplied asc, webguiVersion asc",$dbh);
while (my $data = $sth->hashRef) {
print "\t".sprintf("%-8s %-15s %-15s",
$data->{webguiVersion},
WebGUI::DateTime::epochToHuman($data->{dateApplied},"%y-%m-%d"),
$data->{versionType})."\n";
}
$sth->finish;
$dbh->disconnect;
}
exit;
}
## Find upgrade files.
print "\nLooking for upgrade files...\n" unless ($quiet);
opendir(DIR,$upgradesPath) or die "Couldn't open $upgradesPath\n";
my @files = readdir(DIR);
closedir(DIR);
foreach my $file (@files) {
if ($file =~ /upgrade_(\d+\.\d+\.\d+)-(\d+\.\d+\.\d+)\.(\w+)/) {
if (checkVersion($1)) {
if ($3 eq "sql") {
print "\tFound upgrade script from $1 to $2.\n" unless ($quiet);
$upgrade{$1}{sql} = $file;
} elsif ($3 eq "pl") {
print "\tFound upgrade executable from $1 to $2.\n" unless ($quiet);
$upgrade{$1}{pl} = $file;
}
$upgrade{$1}{from} = $1;
$upgrade{$1}{to} = $2;
}
}
}
print "\nREADY TO BEGIN UPGRADES\n" unless ($quiet);
my $notRun = 1;
chdir($upgradesPath);
foreach my $config (keys %config) {
my $clicmd = $config{$config}{mysqlCLI} || $mysql;
my $dumpcmd = $config{$config}{mysqlDump} || $mysqldump;
my $backupTo = $config{$config}{backupPath} || $backupDir;
mkdir($backupTo);
while ($upgrade{$config{$config}{version}}{sql} ne "") {
my $upgrade = $upgrade{$config{$config}{version}}{from};
unless ($skipBackup) {
print "\n".$config{$config}{db}." ".$upgrade{$upgrade}{from}."-".$upgrade{$upgrade}{to}."\n" unless ($quiet);
print "\tBacking up $config{$config}{db} ($upgrade{$upgrade}{from})..." unless ($quiet);
my $cmd = $dumpcmd." -u".$config{$config}{dbuser}." -p".$config{$config}{dbpass};
$cmd .= " --host=".$config{$config}{host} if ($config{$config}{host});
$cmd .= " --port=".$config{$config}{port} if ($config{$config}{port});
$cmd .= " --add-drop-table --databases ".$config{$config}{db}." > "
.$backupTo.$slash.$config{$config}{db}."_".$upgrade{$upgrade}{from}.".sql";
unless (system($cmd)) {
print "OK\n" unless ($quiet);
} else {
print "Failed!\n" unless ($quiet);
fatalError();
}
}
print "\tUpgrading to ".$upgrade{$upgrade}{to}."..." unless ($quiet);
my $cmd = $clicmd." -u".$config{$config}{dbuser}." -p".$config{$config}{dbpass};
$cmd .= " --host=".$config{$config}{host} if ($config{$config}{host});
$cmd .= " --port=".$config{$config}{port} if ($config{$config}{port});
$cmd .= " --database=".$config{$config}{db}." < ".$upgrade{$upgrade}{sql};
unless (system($cmd)) {
print "OK\n" unless ($quiet);
} else {
print "Failed!\n" unless ($quiet);
fatalError();
}
if ($upgrade{$upgrade}{pl} ne "") {
my $cmd = $perl." ".$upgrade{$upgrade}{pl}." --configFile=".$config;
$cmd .= " --quiet" if ($quiet);
if (system($cmd)) {
print "\tProcessing upgrade executable failed!\n";
fatalError();
}
}
$config{$config}{version} = $upgrade{$upgrade}{to};
$notRun = 0;
}
}
if ($notRun) {
print "\nNO UPGRADES NECESSARY\n\n" unless ($quiet);
} else {
unless ($quiet) {
print <<STOP;
UPGRADES COMPLETE
Please restart your web server and test your sites.
NOTE: If you have not already done so, please consult
docs/gotcha.txt for possible upgrade complications.
STOP
}
}
#-----------------------------------------
# checkVersion($versionNumber)
#-----------------------------------------
# Version number must be 3.5.1 or greater
# in order to be upgraded by this utility.
#-----------------------------------------
sub checkVersion {
$_[0] =~ /(\d+)\.(\d+)\.(\d+)/;
if ($1 > 3) {
return 1;
} elsif ($1 == 3) {
if ($2 > 5) {
return 1;
} elsif ($2 == 5) {
if ($3 > 0) {
return 1;
} else {
return 0;
}
} else {
return 0;
}
} else {
return 0;
}
}
#-----------------------------------------
sub fatalError {
print <<STOP;
The upgrade process failed and has stopped so you can either restore
from backup, or attempt to fix the problem and continue.
STOP
exit;
}
#-----------------------------------------
sub _parseDSN {
my($dsn, $args) = @_;
my($var, $val, $hash);
$hash = {};
if (!defined($dsn)) {
return;
}
$dsn =~ s/^dbi:(\w*?)(?:\((.*?)\))?://i
or '' =~ /()/; # ensure $1 etc are empty if match fails
$hash->{driver} = $1;
while (length($dsn)) {
if ($dsn =~ /([^:;]*)[:;](.*)/) {
$val = $1;
$dsn = $2;
} else {
$val = $dsn;
$dsn = '';
}
if ($val =~ /([^=]*)=(.*)/) {
$var = $1;
$val = $2;
if ($var eq 'hostname' || $var eq 'host') {
$hash->{'host'} = $val;
} elsif ($var eq 'db' || $var eq 'dbname') {
$hash->{'database'} = $val;
} else {
$hash->{$var} = $val;
}
} else {
foreach $var (@$args) {
if (!defined($hash->{$var})) {
$hash->{$var} = $val;
last;
}
}
}
}
return $hash;
}