X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Fsbin%2Frt-setup-database.in;h=c4bb384317c8bf4376fbaef20645f0b17ace29f4;hp=9e990e5b89cfec73c9968df3830f25d749f6176e;hb=HEAD;hpb=289340780927b5bac2c7604d7317c3063c6dd8cc diff --git a/rt/sbin/rt-setup-database.in b/rt/sbin/rt-setup-database.in index 9e990e5b8..11e23581b 100644 --- a/rt/sbin/rt-setup-database.in +++ b/rt/sbin/rt-setup-database.in @@ -1,619 +1,802 @@ -#!@PERL@ -w -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent -# -# (Except where explictly superceded by other copyright notices) -# +#!@PERL@ +# BEGIN BPS TAGGED BLOCK {{{ +# +# COPYRIGHT: +# +# This software is Copyright (c) 1996-2019 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. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK - +# +# 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 +# you are the copyright holder for those contributions and you grant +# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, +# 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 vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item); -use vars - qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips); - -use lib "@RT_LIB_PATH@"; - -#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 -use RT; -use Carp; -use RT::User; -use RT::CurrentUser; -use RT::Template; -use RT::ScripAction; -use RT::ACE; -use RT::Group; -use RT::User; -use RT::Queue; -use RT::ScripCondition; -use RT::CustomField; -use RT::Scrip; - -RT::LoadConfig(); +use warnings; +use 5.010; + +use vars qw($Nobody $SystemUser $item); + +# fix lib paths, some may be relative +BEGIN { # BEGIN RT CMD BOILERPLATE + require File::Spec; + require Cwd; + my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@"); + my $bin_path; + + for my $lib (@libs) { + unless ( File::Spec->file_name_is_absolute($lib) ) { + $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1]; + $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib ); + } + unshift @INC, $lib; + } + +} + use Term::ReadKey; use Getopt::Long; +use Data::GUID; -my %args; +$| = 1; # unbuffer all output. +my %args = ( + package => 'RT', +); GetOptions( \%args, - 'prompt-for-dba-password', 'force', 'debug', - 'action=s', 'dba=s', 'dba-password=s', 'datafile=s', - 'datadir=s' + 'action=s', + 'force', 'debug', + 'dba=s', 'dba-password=s', 'prompt-for-dba-password', 'package=s', + 'datafile=s', 'datadir=s', 'skip-create', 'root-password-file=s', + 'package=s', 'ext-version=s', + 'upgrade-from=s', 'upgrade-to=s', + 'help|h', ); -$| = 1; #unbuffer that output. - -require RT::Handle; -my $Handle = RT::Handle->new($RT::DatabaseType); -$Handle->BuildDSN; -my $dbh; - -if ( $args{'prompt-for-dba-password'} ) { - $args{'dba-password'} = get_dba_password(); - chomp( $args{'dba-password'} ); +no warnings 'once'; +if ( $args{help} || ! $args{'action'} ) { + require Pod::Usage; + Pod::Usage::pod2usage({ verbose => 2 }); + exit; } -unless ( $args{'action'} ) { - help(); - die; +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( LogToSTDERR => 'warning') + unless ( RT->Config->Get( 'LogToSTDERR' ) + && RT->Config->Get( 'LogToSTDERR' ) =~ /^(debug|info|notice)$/ ); +RT::InitLogging(); + +# 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; } -if ( $args{'action'} eq 'init' ) { - $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) - || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; - print "Now creating a database for RT.\n"; - if ($RT::DatabaseType ne 'Oracle' || - $args{'dba'} ne $RT::DatabaseUser) { - create_db(); - } else { - print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n"; - } - $dbh->disconnect; - $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) - || die $DBI::errstr; - print "Now populating database schema.\n"; - insert_schema(); - print "Now inserting database ACLs\n"; - insert_acl() unless ($RT::DatabaseType eq 'Oracle'); - print "Now inserting RT core system objects\n"; - insert_initial_data(); - print "Now inserting RT data\n"; - insert_data( $RT::EtcPath . "/initialdata" ); +# check and setup @actions +my @actions = grep $_, split /,/, $args{'action'}; +if ( @actions > 1 && $args{'datafile'} ) { + print STDERR "You can not use --datafile option with multiple actions.\n"; + exit(-1); } -elsif ( $args{'action'} eq 'drop' ) { - unless ( $dbh = - DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) ) - { - warn $DBI::errstr; - warn "Database doesn't appear to exist. Aborting database drop."; - exit(0); +foreach ( @actions ) { + unless ( /^(?:init|create|drop|schema|acl|indexes|coredata|insert|upgrade)$/ ) { + print STDERR "$0 called with an invalid --action parameter.\n"; + exit(-1); + } + if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) { + print STDERR "You can not mix init, drop or upgrade action with any action.\n"; + exit(-1); } - drop_db(); } -elsif ( $args{'action'} eq 'insert' ) { - insert_data( $args{'datafile'} ); + +# convert init to multiple actions +my $init = 0; +if ( $actions[0] eq 'init' ) { + if ($args{'skip-create'}) { + @actions = qw(schema coredata insert); + } else { + @actions = qw(create schema acl coredata insert); + } + $init = 1; } -elsif ($args{'action'} eq 'acl') { - $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) - || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; - insert_acl($args{'datadir'}); + +# set options from environment +foreach my $key(qw(Type Host Name User Password)) { + next unless exists $ENV{ 'RT_DB_'. uc $key }; + print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n"; + RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key }); } -elsif ($args{'action'} eq 'schema') { - $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) - || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; - insert_schema($args{'datadir'}); + +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') || ''; + +# load it here to get error immidiatly if DB type is not supported +require RT::Handle; + +if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) { + $db_name = File::Spec->catfile($RT::VarPath, $db_name); + RT->Config->Set( DatabaseName => $db_name ); } -else { - print STDERR '$0 called with an invalid --action parameter'; - exit(-1); +my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || ''; +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); + } } -# {{{ sub insert_schema -sub insert_schema { - my $base_path = (shift || $RT::EtcPath); - my (@schema); - print "Creating database schema.\n"; - - if ( -f $base_path . "/schema." . $RT::DatabaseType ) { - no warnings 'unopened'; - - open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType ); - open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType ); - - my $statement = ""; - foreach my $line (, ($_ = ';;'), ) { - $line =~ s/\#.*//g; - $line =~ s/--.*//g; - $statement .= $line; - if ( $line =~ /;(\s*)$/ ) { - $statement =~ s/;(\s*)$//g; - push @schema, $statement; - $statement = ""; - } - } +my $version_word_regex = join '|', RT::Handle->version_words; +my $version_dir = qr/^\d+\.\d+\.\d+(?:$version_word_regex)?\d*$/; - local $SIG{__WARN__} = sub {}; - my $is_local = 0; # local/etc/schema needs to be nonfatal. - foreach my $statement (@schema) { - if ($statement =~ /^\s*;$/) { $is_local = 1; next; } - print STDERR "SQL: $statement\n" if defined $args{'debug'}; - my $sth = $dbh->prepare($statement) or die $dbh->errstr; - unless ( $sth->execute or $is_local ) { - die "Problem with statement:\n $statement\n" . $sth->errstr; - } - } +print "Working with:\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"; - } - else { - die "Couldn't find schema file for " . $RT::DatabaseType . "\n"; - } - print "schema sucessfully inserted\n"; +my $package = $args{'package'} || 'RT'; +my $ext_version = $args{'ext-version'}; +my $full_id = Data::GUID->new->as_string; +my $log_actions = 0; +if ($args{'package'} ne 'RT') { + RT->ConnectToDatabase(); + RT->InitSystemObjects(); + $log_actions = 1; } -# }}} +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 "Done.\n"; +} -# {{{ sub drop_db -sub drop_db { - return if ( $RT::DatabaseType eq 'SQLite' ); - if ( $RT::DatabaseType eq 'Oracle' ) { - print <CheckCompatibility( $dbh, 'create' ); + return ($status, $msg) unless $status; -To delete the tables and sequences of the RT Oracle database by running - \@etc/drop.Oracle -through SQLPlus. + print "Now creating a $db_type database $db_name for RT.\n"; + return RT::Handle->CreateDatabase( $dbh ); +} -END - return; - } +sub action_drop { + my %args = @_; + + print "Dropping $db_type database $db_name.\n"; unless ( $args{'force'} ) { print <DropDatabase( $dbh ); +} + +sub action_schema { + my %args = @_; + my $dbh = get_admin_dbh(); + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'schema' ); + return ($status, $msg) unless $status; + + my $individual_id = Data::GUID->new->as_string(); + my %upgrade_data = ( + action => 'schema', + filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''), + stage => 'before', + full_id => $full_id, + individual_id => $individual_id, + ); + $upgrade_data{'ext_version'} = $ext_version if $ext_version; + RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; + + print "Now populating database schema.\n"; + my @ret = RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} ); + + %upgrade_data = ( + stage => 'after', + individual_id => $individual_id, + return_value => [ @ret ], + ); + RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; + + return @ret; +} - $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr; +sub action_acl { + my %args = @_; + my $dbh = get_admin_dbh(); + my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'acl' ); + return ($status, $msg) unless $status; + + my $individual_id = Data::GUID->new->as_string(); + my %upgrade_data = ( + action => 'acl', + filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''), + stage => 'before', + full_id => $full_id, + individual_id => $individual_id, + ); + $upgrade_data{'ext_version'} = $ext_version if $ext_version; + RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; + + print "Now inserting database ACLs.\n"; + my @ret = RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} ); + + %upgrade_data = ( + stage => 'after', + individual_id => $individual_id, + return_value => [ @ret ], + ); + RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; + + return @ret; } -# }}} +sub action_indexes { + my %args = @_; + RT->ConnectToDatabase; + my $individual_id = Data::GUID->new->as_string(); + my %upgrade_data = ( + action => 'indexes', + filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''), + stage => 'before', + full_id => $full_id, + individual_id => $individual_id, + ); + $upgrade_data{'ext_version'} = $ext_version if $ext_version; + RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; + + my $dbh = get_admin_dbh(); + $RT::Handle = RT::Handle->new; + $RT::Handle->dbh( $dbh ); + RT::InitLogging(); + + print "Now inserting database indexes.\n"; + my @ret = RT::Handle->InsertIndexes( $dbh, $args{'datafile'} || $args{'datadir'} ); + + $RT::Handle = RT::Handle->new; + $RT::Handle->dbh( undef ); + RT->ConnectToDatabase; + %upgrade_data = ( + stage => 'after', + individual_id => $individual_id, + return_value => [ @ret ], + ); + RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions; + + return @ret; +} -# {{{ sub create_db -sub create_db { - print "Creating $RT::DatabaseType database $RT::DatabaseName.\n"; - if ( $RT::DatabaseType eq 'SQLite' ) { - return; +sub action_coredata { + my %args = @_; + $RT::Handle = RT::Handle->new; + $RT::Handle->dbh( undef ); + RT::ConnectToDatabase(); + my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'coredata' ); + return ($status, $msg) unless $status; + + print "Now inserting RT core system objects.\n"; + return $RT::Handle->InsertInitialData; +} + +sub action_insert { + state $RAN_INIT; + my %args = @_; + unless ($RAN_INIT) { + $RT::Handle = RT::Handle->new; + RT::Init(); + $RAN_INIT++; + } + $log_actions = 1; + + my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'insert' ); + return ($status, $msg) unless $status; + + print "Now inserting data.\n"; + my $file = $args{'datafile'}; + $file = $RT::EtcPath . "/initialdata" if $init && !$file; + $file ||= $args{'datadir'}."/content"; + + my $individual_id = Data::GUID->new->as_string(); + my %upgrade_data = ( + action => 'insert', + filename => Cwd::abs_path($file), + stage => 'before', + full_id => $full_id, + individual_id => $individual_id + ); + $upgrade_data{'ext_version'} = $ext_version if $ext_version; + + open my $handle, '<', $file or warn "Unable to open $file: $!"; + $upgrade_data{content} = do {local $/; <$handle>} if $handle; + + RT->System->AddUpgradeHistory($package => \%upgrade_data); + + my @ret; + + my $upgrade = sub { @ret = $RT::Handle->InsertData( $file, $root_password ) }; + + for my $file (@{$args{backcompat} || []}) { + my $lines = do {local $/; local @ARGV = ($file); <>}; + my $sub = eval "sub {\n# line 1 $file\n$lines\n}"; + unless ($sub) { + warn "Failed to load backcompat $file: $@"; + next; + } + my $current = $upgrade; + $upgrade = sub { $sub->($current) }; } - elsif ( $RT::DatabaseType eq 'Pg' ) { - $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'"); - if ($DBI::errstr) { - $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr; + + $upgrade->(); + + # XXX Reconnecting to insert the history entry + # until we can sort out removing + # the disconnect at the end of InsertData. + RT->ConnectToDatabase(); + + %upgrade_data = ( + stage => 'after', + individual_id => $individual_id, + return_value => [ @ret ], + ); + + RT->System->AddUpgradeHistory($package => \%upgrade_data); + + my $db_type = RT->Config->Get('DatabaseType'); + $RT::Handle->Disconnect() unless $db_type eq 'SQLite'; + + return @ret; +} + +sub action_upgrade { + my %args = @_; + my $base_dir = $args{'datadir'} || "./etc/upgrade"; + return (0, "Couldn't read dir '$base_dir' with upgrade data") + unless -d $base_dir || -r _; + + my $upgrading_from = undef; + do { + if ( defined $upgrading_from ) { + print "Doesn't match #.#.#: "; + } else { + print "Enter $args{package} version you're upgrading from: "; } + $upgrading_from = $args{'upgrade-from'} || scalar ; + chomp $upgrading_from; + $upgrading_from =~ s/\s+//g; + } while $upgrading_from !~ /$version_dir/; + + my $upgrading_to = $RT::VERSION; + return (0, "The current version $upgrading_to is lower than $upgrading_from") + if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0; + + 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, 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]; } - elsif ($RT::DatabaseType eq 'Oracle') { - insert_acl(); + + print "\nGoing to apply following upgrades:\n"; + print map "* $_\n", @versions; + + { + my $custom_upgrading_to = undef; + do { + if ( defined $custom_upgrading_to ) { + print "Doesn't match #.#.#: "; + } else { + 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 = $args{'upgrade-to'} || scalar ; + chomp $custom_upgrading_to; + $custom_upgrading_to =~ s/\s+//g; + last unless $custom_upgrading_to; + } while $custom_upgrading_to !~ /$version_dir/; + + if ( $custom_upgrading_to ) { + return ( + 0, "The version you entered ($custom_upgrading_to) is lower than\n" + ."version you're upgrading from ($upgrading_from)" + ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0; + + return (1, "The version you're upgrading to is up to date") + if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0; + + if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) { + print "Version you entered is greater than installed ($RT::VERSION).\n"; + _yesno() or exit(-2); + } + # ok, checked everything no let's refresh list + $upgrading_to = $custom_upgrading_to; + @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to); + + return (1, "No DB changes between $upgrading_from and $upgrading_to") + unless @versions; + + print "\nGoing to apply following upgrades:\n"; + print map "* $_\n", @versions; + } } - elsif ( $RT::DatabaseType eq 'Informix' ) { - $ENV{DB_LOCALE} = 'en_us.utf8'; - $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG"); + + unless ( $args{'force'} ) { + print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n"; + _yesno() or exit(-2); + } + + RT->ConnectToDatabase(); + RT->InitSystemObjects(); + $log_actions = 1; + + RT->System->AddUpgradeHistory($package => { + type => 'full upgrade', + action => 'upgrade', + stage => 'before', + from => $upgrading_from, + to => $upgrading_to, + versions => [@versions], + full_id => $full_id, + individual_id => $full_id + }); + + # Ensure that the Attributes column is big enough to hold the + # upgrade steps we're going to add; this step exists in 4.0.6 for + # mysql, but that may be too late. Run it as soon as possible. + if (RT->Config->Get('DatabaseType') eq 'mysql' + and RT::Handle::cmp_version( $upgrading_from, '4.0.6') < 0) { + my $dbh = get_admin_dbh(); + # Before the binary switch in 3.7.87, we want to alter text -> + # longtext, not blob -> longblob + if (RT::Handle::cmp_version( $upgrading_from, '3.7.87') < 0) { + $dbh->do("ALTER TABLE Attributes MODIFY Content LONGTEXT") + } else { + $dbh->do("ALTER TABLE Attributes MODIFY Content LONGBLOB") + } } - else { - $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr; + + my $previous = $upgrading_from; + my ( $ret, $msg ); + foreach my $n ( 0..$#versions ) { + my $v = $versions[$n]; + my $individual_id = Data::GUID->new->as_string(); + + my @back = grep {-e $_} map {"$base_dir/$versions[$_]/backcompat"} $n+1..$#versions; + print "Processing $v\n"; + + RT->System->AddUpgradeHistory($package => { + action => 'upgrade', + type => 'individual upgrade', + stage => 'before', + from => $previous, + to => $v, + full_id => $full_id, + individual_id => $individual_id, + }); + + my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef, backcompat => \@back); + + if ( -e "$base_dir/$v/schema.$db_type" ) { + ( $ret, $msg ) = action_schema( %tmp ); + return ( $ret, $msg ) unless $ret; + } + if ( -e "$base_dir/$v/acl.$db_type" ) { + ( $ret, $msg ) = action_acl( %tmp ); + return ( $ret, $msg ) unless $ret; + } + if ( -e "$base_dir/$v/indexes" ) { + ( $ret, $msg ) = action_indexes( %tmp ); + return ( $ret, $msg ) unless $ret; + } + if ( -e "$base_dir/$v/content" ) { + ( $ret, $msg ) = action_insert( %tmp ); + return ( $ret, $msg ) unless $ret; + } + + # XXX: Another connect since the insert called + # previous to this step will disconnect. + + RT->ConnectToDatabase(); + + RT->System->AddUpgradeHistory($package => { + stage => 'after', + individual_id => $individual_id, + }); + + $previous = $v; } + + RT->System->AddUpgradeHistory($package => { + stage => 'after', + individual_id => $full_id, + }); + + return 1; } -# }}} +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/$_" && /$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 defined $to ? RT::Handle::cmp_version($_, $to) <= 0 : 1, + grep RT::Handle::cmp_version($_, $from) > 0, + sort RT::Handle::cmp_version @versions; +} + +sub error { + my ($action, $msg) = @_; + print STDERR "Couldn't finish '$action' step.\n\n"; + print STDERR "ERROR: $msg\n\n"; + exit(-1); +} sub get_dba_password { - print -"In order to create a new database and grant RT access to that database,\n"; - print "this script needs to connect to your " - . $RT::DatabaseType - . " instance on " - . $RT::DatabaseHost . " as " - . $args{'dba'} . ".\n"; - print -"Please specify that user's database password below. If the user has no database\n"; + print "In order to create or update your RT database," + . " this script needs to connect to your " + . " $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: "; ReadMode('noecho'); my $password = ReadLine(0); ReadMode('normal'); + print "\n"; return ($password); } -# {{{ sub _yesno -sub _yesno { - print "Proceed [y/N]:"; - my $x = scalar(); - $x =~ /^y/i; +# get_system_dbh +# Returns L database handle connected to B with DBA credentials. +# See also L. + + +sub get_system_dbh { + return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass ); } -# }}} +sub get_admin_dbh { + return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass ); +} -# {{{ insert_acls -sub insert_acl { +# get_rt_dbh [USER, PASSWORD] - my $base_path = (shift || $RT::EtcPath); +# 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. - if ( $RT::DatabaseType =~ /^oracle$/i ) { - do $base_path . "/acl.Oracle" - || die "Couldn't find ACLS for Oracle\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^pg$/i ) { - do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^mysql$/i ) { - do $base_path . "/acl.mysql" - || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^informix$/i ) { - do $base_path . "/acl.Informix" - || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@; - } - elsif ( $RT::DatabaseType =~ /^SQLite$/i ) { - return; - } - else { - die "Unknown RT database type"; - } +sub get_rt_dbh { + return _get_dbh( RT::Handle->DSN, $db_user, $db_pass ); +} - my @acl = acl($dbh); - foreach my $statement (@acl) { - print STDERR $statement if $args{'debug'}; - my $sth = $dbh->prepare($statement) or die $dbh->errstr; - unless ( $sth->execute ) { - die "Problem with statement:\n $statement\n" . $sth->errstr; +sub _get_dbh { + my ($dsn, $user, $pass) = @_; + my $dbh = DBI->connect( + $dsn, $user, $pass, + { RaiseError => 0, PrintError => 0 }, + ); + unless ( $dbh ) { + my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr; + if ( $args{'debug'} ) { + require Carp; Carp::confess( $msg ); + } else { + print STDERR $msg; exit -1; } } + return $dbh; } -# }}} +sub _yesno { + print "Proceed [y/N]:"; + my $x = scalar(); + $x =~ /^y/i; +} -=head2 get_system_dsn +1; -Returns a dsn suitable for database creates and drops -and user creates and drops +__END__ -=cut +=head1 NAME -sub get_system_dsn { +rt-setup-database - Set up RT's database - my $dsn = $Handle->DSN; +=head1 SYNOPSIS - #with mysql, you want to connect sans database to funge things - if ( $RT::DatabaseType eq 'mysql' ) { - $dsn =~ s/dbname=$RT::DatabaseName//; + rt-setup-database --action ... - # with postgres, you want to connect to database1 - } - elsif ( $RT::DatabaseType eq 'Pg' ) { - $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/; - } - elsif ( $RT::DatabaseType eq 'Informix' ) { - # with Informix, you want to connect sans database: - $dsn =~ s/Informix:$RT::DatabaseName/Informix:/; - } - return $dsn; -} +=head1 OPTIONS -sub insert_initial_data { +=over - RT::InitLogging(); +=item action - #connect to the db, for actual RT work - require RT::Handle; - $RT::Handle = RT::Handle->new(); - $RT::Handle->Connect(); +Several actions can be combined using comma separated list. - #Put together a current user object so we can create a User object - my $CurrentUser = new RT::CurrentUser(); +=over - print "Checking for existing system user ($CurrentUser)..."; - my $test_user = RT::User->new($CurrentUser); - $test_user->Load('RT_System'); - if ( $test_user->id ) { - print "found!\n\nYou appear to have a functional RT database.\n" - . "Exiting, so as not to clobber your existing data.\n"; - exit(-1); +=item init - } - else { - print "not found. This appears to be a new installation.\n"; - } +Initialize the database. This is combination of multiple actions listed below. +Create DB, schema, setup acl, insert core data and initial data. - print "Creating system user..."; - my $RT_System = new RT::User($CurrentUser); +=item upgrade - my ( $val, $msg ) = $RT_System->_BootstrapCreate( - Name => 'RT_System', - RealName => 'The RT System itself', - Comments => -'Do not delete or modify this user. It is integral to RT\'s internal database structures', - Creator => '1' ); +Apply all needed schema/acl/content updates (will ask for version to upgrade +from) - unless ($val) { - print "$msg\n"; - exit(1); - } - print "done.\n"; - $RT::Handle->Disconnect(); +=item create -} +Create the database. -# load some sort of data into the database +=item drop -sub insert_data { - my $datafile = shift; +Drop the database. This will B. - #Connect to the database and get RT::SystemUser and RT::Nobody loaded - RT::Init; +=item schema - my $CurrentUser = RT::CurrentUser->new(); - $CurrentUser->LoadByName('RT_System'); +Initialize only the database schema - if ( $datafile eq $RT::EtcPath . "/initialdata" ) { +To use a local or supplementary datafile, specify it using the '--datadir' +option below. - print "Creating Superuser ACL..."; +=item acl - my $superuser_ace = RT::ACE->new($CurrentUser); - $superuser_ace->_BootstrapCreate( - PrincipalId => ACLEquivGroupId( $CurrentUser->Id ), - PrincipalType => 'Group', - RightName => 'SuperUser', - ObjectType => 'RT::System', - ObjectId => '1' ); +Initialize only the database ACLs - } +To use a local or supplementary datafile, specify it using the '--datadir' +option below. - # Slurp in stuff to insert from the datafile. Possible things to go in here:- - # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates +=item coredata - require $datafile - || die "Couldn't find initial data for import\n" . $@; +Insert data into RT's database. This data is required for normal functioning of +any RT instance. - if (@Groups) { - print "Creating groups..."; - foreach $item (@Groups) { - my $new_entry = RT::Group->new($CurrentUser); - my ( $return, $msg ) = $new_entry->_Create(%$item); - print "(Error: $msg)" unless ($return); - print $return. "."; - } - print "done.\n"; - } - if (@Users) { - print "Creating users..."; - foreach $item (@Users) { - my $new_entry = new RT::User($CurrentUser); - my ( $return, $msg ) = $new_entry->Create(%$item); - print "(Error: $msg)" unless ($return); - print $return. "."; - } - print "done.\n"; - } - if (@Queues) { - print "Creating queues..."; - for $item (@Queues) { - my $new_entry = new RT::Queue($CurrentUser); - my ( $return, $msg ) = $new_entry->Create(%$item); - print "(Error: $msg)" unless ($return); - print $return. "."; - } - print "done.\n"; - } - if (@ACL) { - print "Creating ACL..."; - for my $item (@ACL) { - - my ($princ, $object); - - # Global rights or Queue rights? - if ($item->{'Queue'}) { - $object = RT::Queue->new($CurrentUser); - $object->Load( $item->{'Queue'} ); - } else { - $object = $RT::System; - } - - # Group rights or user rights? - if ($item->{'GroupDomain'}) { - $princ = RT::Group->new($CurrentUser); - if ($item->{'GroupDomain'} eq 'UserDefined') { - $princ->LoadUserDefinedGroup( $item->{'GroupId'} ); - } elsif ($item->{'GroupDomain'} eq 'SystemInternal') { - $princ->LoadSystemInternalGroup( $item->{'GroupType'} ); - } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' && - $item->{'Queue'}) { - $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'}, - Queue => $object->id); - } else { - $princ->Load( $item->{'GroupId'} ); - } - } else { - $princ = RT::User->new($CurrentUser); - $princ->Load( $item->{'UserId'} ); - } - - # Grant it - my ( $return, $msg ) = $princ->PrincipalObj->GrantRight( - Right => $item->{'Right'}, - Object => $object ); - - if ($return) { - print $return. "."; - } - else { - print $msg . "."; +=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. - } - print "done.\n"; - } - if (@CustomFields) { - print "Creating custom fields..."; - for $item (@CustomFields) { - my $new_entry = new RT::CustomField($CurrentUser); - my $values = $item->{'Values'}; - delete $item->{'Values'}; - my $q = $item->{'Queue'}; - my $q_obj = RT::Queue->new($CurrentUser); - $q_obj->Load($q); - if ( $q_obj->Id ) { - $item->{'Queue'} = $q_obj->Id; - } - elsif ( $q == 0 ) { - $item->{'Queue'} = 0; - } - else { - print "(Error: Could not find queue " . $q . ")\n" - unless ( $q_obj->Id ); - next; - } - my ( $return, $msg ) = $new_entry->Create(%$item); +=back - foreach my $value ( @{$values} ) { - my ( $eval, $emsg ) = $new_entry->AddValue(%$value); - print "(Error: $emsg)\n" unless ($eval); - } +=item datafile - print "(Error: $msg)\n" unless ($return); - print $return. "."; - } +file path of the data you want to action on - print "done.\n"; - } +e.g. C<--datafile /path/to/datafile> - if (@ScripActions) { - print "Creating ScripActions..."; - - for $item (@ScripActions) { - my $new_entry = RT::ScripAction->new($CurrentUser); - my $return = $new_entry->Create(%$item); - print $return. "."; - } +=item datadir - print "done.\n"; - } +Used to specify a path to find the local database schema and acls to be +installed. - if (@ScripConditions) { - print "Creating ScripConditions..."; +e.g. C<--datadir /path/to/> - for $item (@ScripConditions) { - my $new_entry = RT::ScripCondition->new($CurrentUser); - my $return = $new_entry->Create(%$item); - print $return. "."; - } +=item dba - print "done.\n"; - } +dba's username - if (@Templates) { - print "Creating templates..."; +=item dba-password - for $item (@Templates) { - my $new_entry = new RT::Template($CurrentUser); - my $return = $new_entry->Create(%$item); - print $return. "."; - } - print "done.\n"; - } - if (@Scrips) { - print "Creating scrips..."; - - for $item (@Scrips) { - my $new_entry = new RT::Scrip($CurrentUser); - my ( $return, $msg ) = $new_entry->Create(%$item); - if ($return) { - print $return. "."; - } - else { - print "(Error: $msg)\n"; - } - } - print "done.\n"; - } - $RT::Handle->Disconnect(); +dba's password -} +=item prompt-for-dba-password -=head2 ACLEquivGroupId +Ask for the database administrator's password interactively -Given a userid, return that user's acl equivalence group +=item skip-create -=cut +for 'init': skip creating the database and the user account, so we don't need +administrator privileges -sub ACLEquivGroupId { - my $username = shift; - my $user = RT::User->new($RT::SystemUser); - $user->Load($username); - my $equiv_group = RT::Group->new($RT::SystemUser); - $equiv_group->LoadACLEquivalenceGroup($user); - return ( $equiv_group->Id ); -} +=item root-password-file -sub help { +for 'init' and 'insert': rather than using the default administrative password +for RT's "root" user, use the password in this file. - print <