2006-06-13 19:09:54 +04:00
#!/usr/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Testopia System.
#
# The Initial Developer of the Original Code is Maciej Maczynski.
# Portions created by Maciej Maczynski are Copyright (C) 2001
# Maciej Maczynski. All Rights Reserved.
#
# Contributor(s): Maciej Maczynski <macmac@xdsnet.pl>
# Ed Fuentetaja <efuentetaja@acm.org>
use DBI ;
use File::Copy ;
require Bugzilla ;
use Bugzilla::Constants ;
my $ VERSION = "1.0-beta" ;
#check that we are on the Bugzilla's directory:
if ( ! - e "testopia" ) {
# I don't know where the heck I am
DieWithStyle ( "Please unpack Testopia's distribution file and run this script from within Bugzilla's directory.\n" .
"Can't continue.\n" ) ;
}
if ( ! - e "localconfig" ) {
DieWithStyle ( "Can't find file: localconfig\nBugzilla is not installed in this directory.\n" .
"Can't continue.\n" ) ;
}
print "Now installing Testopia ...\n\n" ;
do 'localconfig' ;
my $ rollbackPatchOnFail = 0 ;
my $ patchSuccessFile = "testopia/tr_patch_successful" ;
my @ files = ( ) ;
my $ patchFile = $ ARGV [ 0 ] ;
if ( $ patchFile && $ patchFile eq "-nopatch" ) {
#skip patching
print "Skip patching Bugzilla's files.\n" ;
} else {
doPatch ( $ patchFile ) ;
}
print "\nSetting file permissions ...\n" ;
SetupPermissions ( ) ;
print "Done.\n\n" ;
print "Checking Testopia database ...\n" ;
my ( $ dbh , $ my_db_base , $ my_db_host , $ my_db_port , $ my_db_name , $ my_db_user , $ my_db_sock ) = DBConnect ( ) ;
print " Make : $my_db_base\n" ;
print " Host : $my_db_host\n" ;
print " Port : $my_db_port\n" ;
print " Name : $my_db_name\n" ;
print " User : $my_db_user\n" ;
print " Socket: $my_db_sock\n" ;
print "\n" ;
UpdateDB ( $ dbh ) ;
print "Done.\n\n" ;
print "Adding Testopia groups ...\n" ;
# Identify admin group.
my $ adminid = GetAdminGroup ( $ dbh ) ;
#Create groups if needed and grant permissions to the admin group over them
my $ groupid ;
$ groupid = tr_AddGroup ( $ dbh , 'managetestplans' , 'Can create, destroy, run and edit test plans.' , $ adminid ) ;
tr_AssignAdminGrants ( $ dbh , $ groupid , $ adminid ) ;
$ groupid = tr_AddGroup ( $ dbh , 'edittestcases' , 'Can add, delete and edit test cases.' , $ adminid ) ;
tr_AssignAdminGrants ( $ dbh , $ groupid , $ adminid ) ;
$ groupid = tr_AddGroup ( $ dbh , 'runtests' , 'Can add, delete and edit test runs.' , $ adminid ) ;
tr_AssignAdminGrants ( $ dbh , $ groupid , $ adminid ) ;
print "Done.\n\n" ;
DBDisconnect ( $ dbh ) ;
#print "Copying Testopia templates ...\n";
#CopyTemplates('testopia');
#CopyTemplates('hook');
#print "Done.\n\n";
print "Cleaning up Testopia cache ...\n" ;
unlink "data/plancache" ;
print "Done.\n\n" ;
print "Running checksetup.pl ...\n" ;
`perl checksetup.pl` ;
print "Done.\n\n" ;
#now recover permissions over this script:
chmod ( 0750 , "tr_install.pl" ) ;
print "Testopia installed successfully!\n" ;
print "\n" ;
print "Please follow now the 'Users' link at the bottom of your Bugzilla's home page to assign users to groups 'Edittestcases' and 'Managetestplans'.\n" ;
print "\n" ;
1 ;
###############################################################################
sub CopyTemplates {
my ( $ d ) = @ _ ;
my $ src = "testopia/template/en/default/$d" ;
my $ dst = "template/en/default/$d" ;
_copyRec ( $ src , $ dst ) ;
}
sub _copyRec {
my ( $ src , $ dst ) = ( @ _ ) ;
opendir DIRH , $ src ;
my @ files = grep ( $ _ ne "." && $ _ ne ".." , readdir ( DIRH ) ) ;
closedir DIRH ;
mkdir $ dst ;
foreach my $ f ( @ files ) {
if ( - d "$src/$f" ) {
# A directory
_copyRec ( "$src/$f" , "$dst/$f" ) ;
} else {
copy ( "$src/$f" , "$dst/$f" ) ;
}
}
}
sub doPatch {
my ( $ fPatch ) = @ _ ;
if ( - e $ patchSuccessFile ) {
#TODO: check the version run, in case we are upgrading
#since version 0.6.2 is the first version using this system
#this won't be needed until next version (0.6.3 ?) comes along
print "Patch already run successfully. Skipping.\n" ;
return 1 ;
}
print "Patching Bugzilla's files ...\n" ;
if ( $ fPatch ) {
# check that the patch file exists:
if ( ! - e $ fPatch ) {
DieWithStyle ( "Cannot find patch file: $fPatch\n" .
"Please, double-check the patch's file name and try again." ) ;
}
print "\nUsing patch file: $fPatch\n\n" ;
} else {
# Guess the patch file to use:
print "\nNo patch file especified, trying to determine the right one to use ...\n\n" ;
print "Bugzilla version " . $ Bugzilla:: Config:: VERSION . " detected.\n" ;
# This piece of code needs to be modified when a new patch file is added to the distribution:
if ( $ Bugzilla:: Config:: VERSION =~ /^2\.18.*/ ) {
# version 2.18.* detected
$ fPatch = "patch-2.18" ;
} elsif ( $ Bugzilla:: Config:: VERSION =~ /^2\.19\.2$/ ) {
# version 2.19.2 detected
$ fPatch = "patch-2.19.2" ;
} elsif ( $ Bugzilla:: Config:: VERSION =~ /^2\.19\.3$/ ) {
# version 2.19.3 detected
$ fPatch = "patch-2.19.3" ;
} elsif ( $ Bugzilla:: Config:: VERSION =~ /^2\.20.*/ ) {
# version 2.20.* detected
$ fPatch = "patch-2.20" ;
} else {
# no suitable version available
DieWithStyle ( "No suitable patch detected for your Bugzilla. Patch cannot continue.\n" .
"Still, there is a chance that a manual patch might work on your installation. Please, check out Testopia's installation manual for details." ) ;
}
print "Patch file chosen: $fPatch\n\n" ;
if ( isWindows ( ) ) {
$ fPatch = "testopia\\" . $ fPatch ;
} else {
$ fPatch = "testopia/" . $ fPatch ;
}
}
# Read the files involved in the patch and put them in @files
readPatchFiles ( $ fPatch , \ @ files ) ;
# Now, let's go ahead and run the patch command:
print "Now patching ...\n\n" ;
my $ patchPath = "patch" ;
if ( isWindows ( ) ) {
$ patchPath = "testopia\\tools\\patch.exe" ;
}
my $ output ;
# Check out if patch is installed:
$ output = `$patchPath -v` || DieWithStyle ( "Patch failed. Is 'patch' installed and in your path?" ) ;
# Patch doesn't really run yet: --dry-run
# print "$patchPath -s --dry-run -bp 1 -i $fPatch 2>&1 \n";
# exit;
$ output = `$patchPath -s -l --dry-run -bp 2 -i $fPatch 2>&1` ;
# If the output is empty, everything was perfect (the -s argument)
chomp $ output ;
if ( $ output ) {
# Nop... the patch didn't apply correctly:
# Print out the output:
print $ output ;
DieWithStyle ( "\n*** Patching of Bugzilla's files failed. None of your files has been modified.\n" .
"\n" .
"Some possible reasons:\n" .
"\n" .
"- Your Bugzilla installation has been modified from the original.\n" .
"> Please scroll up and see if the patch command output you can see above gives you any clue. Consult Testopia's installation manual and get ready for some manual patching.\n" .
"\n" .
"- The patch file used was the wrong version.\n" .
"> If you didn't especify the patch file to use, maybe this installation script picked up the wrong one. " .
"In this case, please, check out in the testopia directory the list of available patch files (files named as patch*) and run the install script especifying the file to use, according to the Bugzilla's version you are using. " .
"For instance, if you are using Bugzilla version 2.19.2, this is the command line you should use:\n" .
" tr_install.pl testopia/patch-2.19.2\n" .
"> If you did in fact especify a patch file on the command line, please, double-check your Bugzilla version and the patch file you used and try again.\n" .
"\n" .
"- Testopia is not prepared to work with this particular version of Bugzilla.\n" .
"> Please, check the documentation. Verify that this version of Bugzilla is supported.\n" .
"\n" .
"If you still have problems, please, check out Testopia's installation manual again and, if nothing helps, report the problem to Testopia sourceforge forum at http://sourceforge.net/projects/testopia." ) ;
}
$ rollbackPatchOnFail = 1 ;
#Now let's really run the patch:
$ output = `$patchPath -s -l -z .orig -bp 2 -i $fPatch` ;
# If the output is empty, everything was perfect (the -s argument)
chomp $ output ;
if ( $ output ) {
# Nop... the patch didn't apply correctly:
# Print out the output:
print $ output ;
DieWithStyle ( "\n*** Unexpected condition. Patch failed, but I was pretty sure it should have worked.\n" .
"Try to repeat the installation.\n" ) ;
}
$ rollbackPatchOnFail = 0 ;
print "\nDone.\n" ;
restorePermissions ( ) ;
open ( MYOUTPUTFILE , ">$patchSuccessFile" ) or DieWithStyle ( "Couldn't write file: $patchSuccessFile" ) ;
print MYOUTPUTFILE $ VERSION ;
close ( MYOUTPUTFILE ) ;
print "\n" ;
print "Congratulations, patch worked flawless!\n" ;
print "\n" ;
print "A backup copy of the modified files has been saved with the .orig sufix.\n" ;
1 ;
}
sub isWindows {
return ( $^O eq "MSWin32" || $^O eq "cygwin" ) ;
}
sub SetupPermissions {
if ( isWindows ( ) ) {
SetupPermissions_windows ( ) ;
} else {
SetupPermissions_unix ( ) ;
}
}
sub SetupPermissions_windows {
print "Running on Windows or Cygwin... skipping permissions...\n" ;
}
sub SetupPermissions_unix {
`chmod 750 tr_*.cgi` ;
`chmod 640 tr_*.pl` ;
`chmod 750 tr_install.pl` ;
`chmod 750 testopia` ;
`chmod 750 testopia/tr_*` ;
`chmod -R 750 testopia/doc` ;
`chmod -R 750 testopia/img` ;
2006-07-18 21:38:13 +04:00
`chmod -R 750 testopia/css` ;
`chmod -R 750 testopia/js` ;
`chmod -R 750 testopia/dojo-ajax` ;
2006-06-13 19:09:54 +04:00
`chmod -R 750 testopia/scripts` ;
`chmod 770 testopia/temp` ;
2006-07-18 21:38:13 +04:00
if ( defined $ ::webservergroup && ( $ ::webservergroup ne '' ) ) {
print " Webserver group: $::webservergroup\n" ;
`chown :$::webservergroup tr_*` ;
`chown :$::webservergroup testopia` ;
`chown :$::webservergroup testopia/tr_*` ;
`chown -R :$::webservergroup testopia/doc` ;
`chown -R :$::webservergroup testopia/img` ;
`chown -R :$::webservergroup testopia/css` ;
`chown -R :$::webservergroup testopia/js` ;
`chown -R :$::webservergroup testopia/dojo-ajax` ;
`chown -R :$::webservergroup testopia/scripts` ;
`chown :$::webservergroup testopia/temp` ;
2006-06-13 19:09:54 +04:00
}
}
sub UpdateDB {
my ( $ dbh ) = ( @ _ ) ;
my $ testRunnerDBPresent = 0 ;
my $ qh ;
$ qh = $ dbh - > prepare ( "SHOW TABLES LIKE 'test_cases'" ) ;
$ qh - > execute ;
if ( $ qh - > rows ) {
#assume that the BTR 0.4.5 db was created successfuly
$ testRunnerDBPresent = 1 ;
}
$ qh - > finish ;
if ( $ testRunnerDBPresent ) {
print " Testopia database already present.\n" ;
} else {
print " No Testopia database was detected. Need to create it ...\n" ;
#Run the create db script:
RunSQLScript ( 'testopia/testopia.create.sql' ) ;
print " Done.\n" ;
}
}
# Copied from checksetup.pl
#
## This subroutine checks if a group exist. If not, it will be automatically
## created with the next available groupid
##
#
sub tr_AddGroup {
my ( $ dbh , $ name , $ desc ) = @ _ ;
my ( $ id ) = GroupDoesExist ( $ dbh , $ name ) ;
return $ id if $ id ;
print "Adding group $name ...\n" ;
my $ sth = $ dbh - > prepare ( ' INSERT INTO groups
( name , description , userregexp , isbuggroup )
VALUES ( ? , ? , ? , ? ) ' ) ;
$ sth - > execute ( $ name , $ desc , '' , 0 ) ;
$ sth = $ dbh - > prepare ( "SELECT last_insert_id()" ) ;
$ sth - > execute ( ) ;
( $ id ) = $ sth - > fetchrow_array ( ) ;
return $ id ;
}
sub GetAdminGroup {
my ( $ dbh ) = @ _ ;
my $ sth = $ dbh - > prepare ( "SELECT id FROM groups WHERE name = 'admin'" ) ;
$ sth - > execute ( ) ;
my ( $ adminid ) = $ sth - > fetchrow_array ( ) ;
return $ adminid ;
}
sub tr_AssignAdminGrants {
my ( $ dbh , $ id , $ adminid ) = @ _ ;
#clean up first:
$ dbh - > do ( "DELETE FROM group_group_map " .
"WHERE member_id=$adminid AND grantor_id=$id" ) ;
#Assign privileges to the admin group over this group:
my $ blessColumn ;
my $ group_membership ;
my $ group_bless ;
if ( $ Bugzilla:: Config:: VERSION =~ /^2\.18.*/ ) {
# version 2.18.* detected
$ blessColumn = 'isbless' ;
$ group_membership = 0 ;
$ group_bless = 1 ;
} else {
$ blessColumn = 'grant_type' ;
$ group_membership = GROUP_MEMBERSHIP ;
$ group_bless = GROUP_BLESS ;
}
# Admins can bless.
$ dbh - > do ( "INSERT INTO group_group_map " .
"(member_id, grantor_id, $blessColumn) " .
"VALUES ($adminid, $id," . $ group_bless . ")" ) ;
# Admins can see.
#$dbh->do("INSERT INTO group_group_map
# (member_id, grantor_id, grant_type)
# VALUES ($adminid, $last," . GROUP_VISIBLE . ")");
# Admins are initially members.
$ dbh - > do ( "INSERT INTO group_group_map " .
"(member_id, grantor_id, $blessColumn) " .
"VALUES ($adminid, $id," . $ group_membership . ")" ) ;
}
sub GroupDoesExist {
my ( $ dbh , $ name ) = @ _ ;
my $ sth = $ dbh - > prepare ( "SELECT id FROM groups WHERE name='$name'" ) ;
$ sth - > execute ;
my $ id = 0 ;
if ( $ sth - > rows ) {
( $ id ) = $ sth - > fetchrow_array ( ) ;
}
return $ id ;
}
sub GetDBConnectionInfo {
my $ my_db_base = 'mysql' ;
# code taken from checksetup.pl:
my $ my_db_host = $ { * { $ main:: { 'db_host' } } { SCALAR } } ;
my $ my_db_port = $ { * { $ main:: { 'db_port' } } { SCALAR } } ;
my $ my_db_name = $ { * { $ main:: { 'db_name' } } { SCALAR } } ;
my $ my_db_user = $ { * { $ main:: { 'db_user' } } { SCALAR } } ;
my $ my_db_pass = $ { * { $ main:: { 'db_pass' } } { SCALAR } } ;
my $ my_db_sock = '' ;
if ( defined $ main:: { 'db_sock' } ) {
$ my_db_sock = $ { * { $ main:: { 'db_sock' } } { SCALAR } } ;
}
( $ my_db_base , $ my_db_host , $ my_db_port , $ my_db_name ,
$ my_db_user , $ my_db_pass , $ my_db_sock ) ;
}
sub DBConnect {
my ( $ my_db_base , $ my_db_host , $ my_db_port , $ my_db_name ,
$ my_db_user , $ my_db_pass , $ my_db_sock ) = GetDBConnectionInfo ( ) ;
my $ dsn = "DBI:$my_db_base:$my_db_name:host=$my_db_host:port=$my_db_port" ;
if ( $ my_db_sock ne '' ) {
$ dsn . = ";mysql_socket=$my_db_sock" ;
}
my $ dbh = DBI - > connect ( $ dsn , $ my_db_user , $ my_db_pass )
|| DieWithStyle ( "Can't connect to the $my_db_base database. Is the database " .
"installed and up and running? Do you have the correct username " .
"and password selected in\nlocalconfig?" ) ;
( $ dbh , $ my_db_base , $ my_db_host , $ my_db_port , $ my_db_name ,
$ my_db_user , $ my_db_sock ) ;
}
sub DBDisconnect {
my ( $ dbh ) = ( @ _ ) ;
$ dbh - > disconnect if $ dbh ;
}
sub RunSQLScript {
my ( $ script ) = ( @ _ ) ;
my ( $ my_db_base , $ my_db_host , $ my_db_port , $ my_db_name ,
$ my_db_user , $ my_db_pass , $ my_db_sock ) = GetDBConnectionInfo ( ) ;
my $ cmdline = "mysql -h$my_db_host -P$my_db_port -u $my_db_user $my_db_name -p$my_db_pass" ;
if ( $ my_db_sock ne '' ) {
$ cmdline . = " -S$my_db_sock" ;
}
$ cmdline . = ' < ' . $ script ;
my $ output ;
#Check out that mysql is installed and can be reached:
$ output = `mysql -V` || DieWithStyle ( "MySql command failed. Is 'mysql' installed and in your path?" ) ;
$ output = `$cmdline 2>&1` ;
print $ output ;
# If the output is empty, everything was perfect
chomp $ output ;
if ( $ output ) {
DieWithStyle ( "Could not create Testopia's tables. See previous errors." ) ;
} else {
print " Testopia's tables created successfully.\n" ;
}
}
sub DieWithStyle {
my ( $ message ) = @ _ ;
print "\n$message\n" ;
if ( $ rollbackPatchOnFail ) {
rollbackPatch ( ) ;
}
die "\nInstall failed. See details above.\n" ;
}
sub readPatchFiles {
my ( $ path , $ f ) = ( @ _ ) ;
open ( MYINPUTFILE , "<$path" ) or DieWithStyle ( "Couldn't find file: $path" ) ;
while ( <MYINPUTFILE> ) {
my $ line = $ _ ;
if ( $ line =~ /^diff -[\w]* \.\/([^ ]*) .*$/ ) {
push @$ f , $ 1 ;
}
}
close ( MYINPUTFILE ) ;
}
sub rollbackPatch {
# Recoved files from the .orig copies made by patch:
print "\n" ;
print "Restoring original files ...\n" ;
foreach my $ file ( @ files ) {
if ( - e $ file . ".orig" ) {
rename ( $ file . ".orig" , $ file ) ;
} else {
print " Couldnt't restore file: $file because $file.orig doesn't exit!.\n" ;
}
}
print "Done.\n" ;
}
sub restorePermissions {
# Restore permissions from the .orig files:
print "\n" ;
print "Recovering original permissions ...\n" ;
foreach my $ file ( @ files ) {
perm_cp ( $ file . ".orig" , $ file ) ;
}
print "Done.\n" ;
}
######################################
# perm_cp lifted from Ben Okopnik's
# http://www.linuxgazette.com/issue87/misc/tips/cpmod.pl.txt
sub perm_cp {
my $ perms = perm_get ( $ _ [ 0 ] ) ;
perm_set ( $ _ [ 1 ] , $ perms ) ;
}
sub perm_get {
my ( $ filename ) = @ _ ;
my @ stats = ( stat $ filename ) [ 2 , 4 , 5 ] or
DieWithStyle ( "Cannot stat $filename ($!)" ) ;
return \ @ stats ;
}
sub perm_set {
my ( $ filename , $ perms ) = @ _ ;
chown ( $ perms - > [ 1 ] , $ perms - > [ 2 ] , $ filename ) or
DieWithStyle ( "Cannot chown $filename ($!)" ) ;
chmod ( $ perms - > [ 0 ] & 07777 , $ filename ) or
DieWithStyle ( "Cannot chmod $filename ($!)" ) ;
}
######################################