X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Fsbin%2Frt-setup-database.in;h=edf3d4636ba66dc762da1854bfdcb83d3ed803e6;hp=a51076fee0d852a38189fa4512dc91f1495e857e;hb=e9e0cf0989259b94d9758eceff448666a2e5a5cc;hpb=5c96d46d56f2066bb40d9a34c4db56f53f43c6f2 diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in index a51076fee..edf3d4636 100644 --- a/rt/sbin/rt-setup-database.in +++ b/rt/sbin/rt-setup-database.in @@ -1,41 +1,41 @@ #!@PERL@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC -# -# +# +# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC +# +# # (Except where explicitly superseded by other copyright notices) -# -# +# +# # LICENSE: -# +# # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. -# +# # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 or visit their web page on the internet at # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html. -# -# +# +# # CONTRIBUTION SUBMISSION POLICY: -# +# # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) -# +# # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that @@ -44,7 +44,7 @@ # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. -# +# # END BPS TAGGED BLOCK }}} use strict; use warnings; @@ -76,34 +76,59 @@ BEGIN { } -#This drags in RT's config.pm -# We do it in a begin block because RT::Handle needs to know the type to do its -# inheritance -BEGIN { - use RT; - RT::LoadConfig(); - RT::InitClasses(); -} - use Term::ReadKey; use Getopt::Long; $| = 1; # unbuffer all output. -my %args; +my %args = ( + dba => '@DB_DBA@', + package => 'RT', +); GetOptions( \%args, 'action=s', 'force', 'debug', - 'dba=s', 'dba-password=s', 'prompt-for-dba-password', - 'datafile=s', 'datadir=s' + 'dba=s', 'dba-password=s', 'prompt-for-dba-password', 'package=s', + 'datafile=s', 'datadir=s', 'skip-create', 'root-password-file=s', + 'upgrade-from=s', 'upgrade-to=s', + 'help|h', ); -unless ( $args{'action'} ) { - help(); - exit(-1); +no warnings 'once'; +if ( $args{help} || ! $args{'action'} ) { + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; +} + +require RT; +RT->LoadConfig(); +RT->InitClasses(); + +# Force warnings to be output to STDERR if we're not already logging +# them at a higher level +RT->Config->Set( LogToScreen => 'warning') + unless ( RT->Config->Get( 'LogToScreen' ) + && RT->Config->Get( 'LogToScreen' ) =~ /^(debug|info|notice)$/ ); + +# get customized root password +my $root_password; +if ( $args{'root-password-file'} ) { + open( my $fh, '<', $args{'root-password-file'} ) + or die "Couldn't open 'args{'root-password-file'}' for reading: $!"; + $root_password = <$fh>; + chomp $root_password; + my $min_length = RT->Config->Get('MinimumPasswordLength'); + if ($min_length) { + die +"password needs to be at least $min_length long, please check file '$args{'root-password-file'}'" + if length $root_password < $min_length; + } + close $fh; } + # check and setup @actions my @actions = grep $_, split /,/, $args{'action'}; if ( @actions > 1 && $args{'datafile'} ) { @@ -124,7 +149,11 @@ foreach ( @actions ) { # convert init to multiple actions my $init = 0; if ( $actions[0] eq 'init' ) { - @actions = qw(create schema acl coredata insert); + if ($args{'skip-create'}) { + @actions = qw(schema coredata insert); + } else { + @actions = qw(create schema acl coredata insert); + } $init = 1; } @@ -137,6 +166,7 @@ foreach my $key(qw(Type Host Name User Password)) { my $db_type = RT->Config->Get('DatabaseType') || ''; my $db_host = RT->Config->Get('DatabaseHost') || ''; +my $db_port = RT->Config->Get('DatabasePort') || ''; my $db_name = RT->Config->Get('DatabaseName') || ''; my $db_user = RT->Config->Get('DatabaseUser') || ''; my $db_pass = RT->Config->Get('DatabasePassword') || ''; @@ -150,29 +180,39 @@ if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) { } my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || ''; -my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'}; - -if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) { - $dba_pass = get_dba_password(); - chomp $dba_pass if defined($dba_pass); +my $dba_pass = exists($args{'dba-password'}) + ? $args{'dba-password'} + : $ENV{'RT_DBA_PASSWORD'}; + +if ($args{'skip-create'}) { + $dba_user = $db_user; + $dba_pass = $db_pass; +} else { + if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) { + $dba_pass = get_dba_password(); + chomp $dba_pass if defined($dba_pass); + } } +my $version_word_regex = join '|', RT::Handle->version_words; +my $version_dir = qr/^\d+\.\d+\.\d+(?:$version_word_regex)?\d*$/; + print "Working with:\n" - ."Type:\t$db_type\nHost:\t$db_host\nName:\t$db_name\n" - ."User:\t$db_user\nDBA:\t$dba_user\n"; + ."Type:\t$db_type\nHost:\t$db_host\nPort:\t$db_port\nName:\t$db_name\n" + ."User:\t$db_user\nDBA:\t$dba_user" . ($args{'skip-create'} ? ' (No DBA)' : '') . "\n"; foreach my $action ( @actions ) { no strict 'refs'; my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args ); error($action, $msg) unless $status; - print $msg ."\n" if $msg; + print $msg .".\n" if $msg; print "Done.\n"; } sub action_create { my %args = @_; my $dbh = get_system_dbh(); - my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' ); + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'create' ); return ($status, $msg) unless $status; print "Now creating a $db_type database $db_name for RT.\n"; @@ -186,7 +226,7 @@ sub action_drop { unless ( $args{'force'} ) { print <CheckCompatibility( $dbh, 'pre' ); + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'schema' ); return ($status, $msg) unless $status; print "Now populating database schema.\n"; @@ -210,38 +250,62 @@ sub action_schema { sub action_acl { my %args = @_; my $dbh = get_admin_dbh(); - my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' ); + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'acl' ); return ($status, $msg) unless $status; - print "Now inserting database ACLs\n"; + print "Now inserting database ACLs.\n"; return RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} ); } sub action_coredata { my %args = @_; - $RT::Handle = new RT::Handle; + $RT::Handle = RT::Handle->new; $RT::Handle->dbh( undef ); RT::ConnectToDatabase(); RT::InitLogging(); - my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' ); + my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'coredata' ); return ($status, $msg) unless $status; - print "Now inserting RT core system objects\n"; + print "Now inserting RT core system objects.\n"; return $RT::Handle->InsertInitialData; } sub action_insert { my %args = @_; - $RT::Handle = new RT::Handle; + $RT::Handle = RT::Handle->new; RT::Init(); - my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' ); + my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'insert' ); return ($status, $msg) unless $status; - print "Now inserting data\n"; + print "Now inserting data.\n"; my $file = $args{'datafile'}; $file = $RT::EtcPath . "/initialdata" if $init && !$file; $file ||= $args{'datadir'}."/content"; - return $RT::Handle->InsertData( $file ); + + # Slurp in backcompat + my %removed; + my @back = @{$args{backcompat} || []}; + if (@back) { + my @lines = do {local @ARGV = @back; <>}; + for (@lines) { + s/\#.*//; + next unless /\S/; + my ($class, @fields) = split; + $class->_BuildTableAttributes; + $RT::Logger->debug("Temporarily removing @fields from $class"); + $removed{$class}{$_} = delete $RT::Record::_TABLE_ATTR->{$class}{$_} + for @fields; + } + } + + my @ret = $RT::Handle->InsertData( $file, $root_password ); + + # Put back the fields we chopped off + for my $class (keys %removed) { + $RT::Record::_TABLE_ATTR->{$class}{$_} = $removed{$class}{$_} + for keys %{$removed{$class}}; + } + return @ret; } sub action_upgrade { @@ -255,12 +319,12 @@ sub action_upgrade { if ( defined $upgrading_from ) { print "Doesn't match #.#.#: "; } else { - print "Enter RT version you're upgrading from: "; + print "Enter $args{package} version you're upgrading from: "; } - $upgrading_from = scalar ; + $upgrading_from = $args{'upgrade-from'} || scalar ; chomp $upgrading_from; $upgrading_from =~ s/\s+//g; - } while $upgrading_from !~ /^\d+\.\d+\.\d+$/; + } while $upgrading_from !~ /$version_dir/; my $upgrading_to = $RT::VERSION; return (0, "The current version $upgrading_to is lower than $upgrading_from") @@ -269,11 +333,16 @@ sub action_upgrade { return (1, "The version $upgrading_to you're upgrading to is up to date") if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0; - my @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to); - - return (1, "No DB changes between $upgrading_from and $upgrading_to") + my @versions = get_versions_from_to($base_dir, $upgrading_from, undef); + return (1, "No DB changes since $upgrading_from") unless @versions; + if (RT::Handle::cmp_version($versions[-1], $upgrading_to) > 0) { + print "\n***** There are upgrades for $versions[-1], which is later than $upgrading_to,\n"; + print "***** which you are nominally upgrading to. Upgrading to $versions[-1] instead.\n"; + $upgrading_to = $versions[-1]; + } + print "\nGoing to apply following upgrades:\n"; print map "* $_\n", @versions; @@ -283,14 +352,14 @@ sub action_upgrade { if ( defined $custom_upgrading_to ) { print "Doesn't match #.#.#: "; } else { - print "\nEnter RT version if you want to stop upgrade at some point,\n"; + print "\nEnter $args{package} version if you want to stop upgrade at some point,\n"; print " or leave it blank if you want apply above upgrades: "; } - $custom_upgrading_to = scalar ; + $custom_upgrading_to = $args{'upgrade-to'} || scalar ; chomp $custom_upgrading_to; $custom_upgrading_to =~ s/\s+//g; last unless $custom_upgrading_to; - } while $custom_upgrading_to !~ /^\d+\.\d+\.\d+$/; + } while $custom_upgrading_to !~ /$version_dir/; if ( $custom_upgrading_to ) { return ( @@ -320,17 +389,23 @@ sub action_upgrade { print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n"; _yesno() or exit(-2) unless $args{'force'}; - foreach my $v ( @versions ) { + my ( $ret, $msg ); + foreach my $n ( 0..$#versions ) { + my $v = $versions[$n]; + my @back = grep {-e $_} map {"$base_dir/$versions[$_]/backcompat"} $n+1..$#versions; print "Processing $v\n"; - my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef); + my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef, backcompat => \@back); if ( -e "$base_dir/$v/schema.$db_type" ) { - action_schema( %tmp ); + ( $ret, $msg ) = action_schema( %tmp ); + return ( $ret, $msg ) unless $ret; } if ( -e "$base_dir/$v/acl.$db_type" ) { - action_acl( %tmp ); + ( $ret, $msg ) = action_acl( %tmp ); + return ( $ret, $msg ) unless $ret; } if ( -e "$base_dir/$v/content" ) { - action_insert( %tmp ); + ( $ret, $msg ) = action_insert( %tmp ); + return ( $ret, $msg ) unless $ret; } } return 1; @@ -339,12 +414,15 @@ sub action_upgrade { sub get_versions_from_to { my ($base_dir, $from, $to) = @_; - opendir my $dh, $base_dir or die "couldn't open dir: $!"; - my @versions = grep -d "$base_dir/$_" && /\d+\.\d+\.\d+/, readdir $dh; + opendir( my $dh, $base_dir ) or die "couldn't open dir: $!"; + my @versions = grep -d "$base_dir/$_" && /$version_dir/, readdir $dh; closedir $dh; + die "\nERROR: No upgrade data found in '$base_dir'! Perhaps you specified the wrong --datadir?\n" + unless @versions; + return - grep RT::Handle::cmp_version($_, $to) <= 0, + grep defined $to ? RT::Handle::cmp_version($_, $to) <= 0 : 1, grep RT::Handle::cmp_version($_, $from) > 0, sort RT::Handle::cmp_version @versions; } @@ -359,7 +437,7 @@ sub error { sub get_dba_password { print "In order to create or update your RT database," . " this script needs to connect to your " - . " $db_type instance on $db_host as $dba_user\n"; + . " $db_type instance on $db_host (port '$db_port') as $dba_user\n"; print "Please specify that user's database password below. If the user has no database\n"; print "password, just press return.\n\n"; print "Password: "; @@ -370,13 +448,10 @@ sub get_dba_password { return ($password); } -=head2 get_system_dbh +# get_system_dbh +# Returns L database handle connected to B with DBA credentials. +# See also L. -Returns L database handle connected to B with DBA credentials. - -See also L. - -=cut sub get_system_dbh { return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass ); @@ -386,13 +461,11 @@ sub get_admin_dbh { return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass ); } -=head2 get_rt_dbh [USER, PASSWORD] +# get_rt_dbh [USER, PASSWORD] -Returns L database handle connected to RT database, -you may specify credentials(USER and PASSWORD) to connect -with. By default connects with credentials from RT config. - -=cut +# Returns L database handle connected to RT database, +# you may specify credentials(USER and PASSWORD) to connect +# with. By default connects with credentials from RT config. sub get_rt_dbh { return _get_dbh( RT::Handle->DSN, $db_user, $db_pass ); @@ -421,54 +494,116 @@ sub _yesno { $x =~ /^y/i; } -sub help { +1; - print <. + +=item schema + +Initialize only the database schema + +To use a local or supplementary datafile, specify it using the '--datadir' +option below. + +=item acl + +Initialize only the database ACLs + +To use a local or supplementary datafile, specify it using the '--datadir' +option below. + +=item coredata + +Insert data into RT's database. This data is required for normal functioning of +any RT instance. + +=item insert + +Insert data into RT's database. By default, will use RT's installation data. +To use a local or supplementary datafile, specify it using the '--datafile' +option below. + +=back + +=item datafile + +file path of the data you want to action on + +e.g. C<--datafile /path/to/datafile> + +=item datadir + +Used to specify a path to find the local database schema and acls to be +installed. + +e.g. C<--datadir /path/to/> + +=item dba + +dba's username + +=item dba-password + +dba's password + +=item prompt-for-dba-password + +Ask for the database administrator's password interactively + +=item skip-create + +for 'init': skip creating the database and the user account, so we don't need +administrator privileges + +=item root-password-file + +for 'init' and 'insert': rather than using the default administrative password +for RT's "root" user, use the password in this file. + +=item upgrade-from + +for 'upgrade': specifies the version to upgrade from, and do not prompt +for it if it appears to be a valid version. + +=item upgrade-to + +for 'upgrade': specifies the version to upgrade to, and do not prompt +for it if it appears to be a valid version. + +=back