2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
7 # <sales@bestpractical.com>
9 # (Except where explicitly superseded by other copyright notices)
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 # General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
31 # CONTRIBUTION SUBMISSION POLICY:
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
48 # END BPS TAGGED BLOCK }}}
52 use vars qw($Nobody $SystemUser $item);
54 # fix lib paths, some may be relative
57 my @libs = ("lib", "local/lib");
61 unless ( File::Spec->file_name_is_absolute($lib) ) {
63 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
64 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
69 $bin_path = $FindBin::Bin;
72 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
79 #This drags in RT's config.pm
80 # We do it in a begin block because RT::Handle needs to know the type to do its
91 $| = 1; # unbuffer all output.
98 'dba=s', 'dba-password=s', 'prompt-for-dba-password',
99 'datafile=s', 'datadir=s'
102 unless ( $args{'action'} ) {
107 # check and setup @actions
108 my @actions = grep $_, split /,/, $args{'action'};
109 if ( @actions > 1 && $args{'datafile'} ) {
110 print STDERR "You can not use --datafile option with multiple actions.\n";
113 foreach ( @actions ) {
114 unless ( /^(?:init|create|drop|schema|acl|coredata|insert|upgrade)$/ ) {
115 print STDERR "$0 called with an invalid --action parameter.\n";
118 if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) {
119 print STDERR "You can not mix init, drop or upgrade action with any action.\n";
124 # convert init to multiple actions
126 if ( $actions[0] eq 'init' ) {
127 @actions = qw(create schema acl coredata insert);
131 # set options from environment
132 foreach my $key(qw(Type Host Name User Password)) {
133 next unless exists $ENV{ 'RT_DB_'. uc $key };
134 print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n";
135 RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key });
138 my $db_type = RT->Config->Get('DatabaseType') || '';
139 my $db_host = RT->Config->Get('DatabaseHost') || '';
140 my $db_name = RT->Config->Get('DatabaseName') || '';
141 my $db_user = RT->Config->Get('DatabaseUser') || '';
142 my $db_pass = RT->Config->Get('DatabasePassword') || '';
144 # load it here to get error immidiatly if DB type is not supported
147 if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) {
148 $db_name = File::Spec->catfile($RT::VarPath, $db_name);
149 RT->Config->Set( DatabaseName => $db_name );
152 my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || '';
153 my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'};
155 if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) {
156 $dba_pass = get_dba_password();
157 chomp $dba_pass if defined($dba_pass);
160 print "Working with:\n"
161 ."Type:\t$db_type\nHost:\t$db_host\nName:\t$db_name\n"
162 ."User:\t$db_user\nDBA:\t$dba_user\n";
164 foreach my $action ( @actions ) {
166 my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args );
167 error($action, $msg) unless $status;
168 print $msg ."\n" if $msg;
174 my $dbh = get_system_dbh();
175 my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
176 return ($status, $msg) unless $status;
178 print "Now creating a $db_type database $db_name for RT.\n";
179 return RT::Handle->CreateDatabase( $dbh );
185 print "Dropping $db_type database $db_name.\n";
186 unless ( $args{'force'} ) {
189 About to drop $db_type database $db_name on $db_host.
190 WARNING: This will erase all data in $db_name.
193 exit(-2) unless _yesno();
196 my $dbh = get_system_dbh();
197 return RT::Handle->DropDatabase( $dbh );
202 my $dbh = get_admin_dbh();
203 my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
204 return ($status, $msg) unless $status;
206 print "Now populating database schema.\n";
207 return RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} );
212 my $dbh = get_admin_dbh();
213 my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
214 return ($status, $msg) unless $status;
216 print "Now inserting database ACLs\n";
217 return RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} );
220 sub action_coredata {
222 $RT::Handle = new RT::Handle;
223 $RT::Handle->dbh( undef );
224 RT::ConnectToDatabase();
226 my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' );
227 return ($status, $msg) unless $status;
229 print "Now inserting RT core system objects\n";
230 return $RT::Handle->InsertInitialData;
235 $RT::Handle = new RT::Handle;
237 my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' );
238 return ($status, $msg) unless $status;
240 print "Now inserting data\n";
241 my $file = $args{'datafile'};
242 $file = $RT::EtcPath . "/initialdata" if $init && !$file;
243 $file ||= $args{'datadir'}."/content";
244 return $RT::Handle->InsertData( $file );
249 my $base_dir = $args{'datadir'} || "./etc/upgrade";
250 return (0, "Couldn't read dir '$base_dir' with upgrade data")
251 unless -d $base_dir || -r _;
253 my $upgrading_from = undef;
255 if ( defined $upgrading_from ) {
256 print "Doesn't match #.#.#: ";
258 print "Enter RT version you're upgrading from: ";
260 $upgrading_from = scalar <STDIN>;
261 chomp $upgrading_from;
262 $upgrading_from =~ s/\s+//g;
263 } while $upgrading_from !~ /^\d+\.\d+\.\d+$/;
265 my $upgrading_to = $RT::VERSION;
266 return (0, "The current version $upgrading_to is lower than $upgrading_from")
267 if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0;
269 return (1, "The version $upgrading_to you're upgrading to is up to date")
270 if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0;
272 my @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
274 return (1, "No DB changes between $upgrading_from and $upgrading_to")
277 print "\nGoing to apply following upgrades:\n";
278 print map "* $_\n", @versions;
281 my $custom_upgrading_to = undef;
283 if ( defined $custom_upgrading_to ) {
284 print "Doesn't match #.#.#: ";
286 print "\nEnter RT version if you want to stop upgrade at some point,\n";
287 print " or leave it blank if you want apply above upgrades: ";
289 $custom_upgrading_to = scalar <STDIN>;
290 chomp $custom_upgrading_to;
291 $custom_upgrading_to =~ s/\s+//g;
292 last unless $custom_upgrading_to;
293 } while $custom_upgrading_to !~ /^\d+\.\d+\.\d+$/;
295 if ( $custom_upgrading_to ) {
297 0, "The version you entered ($custom_upgrading_to) is lower than\n"
298 ."version you're upgrading from ($upgrading_from)"
299 ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0;
301 return (1, "The version you're upgrading to is up to date")
302 if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0;
304 if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) {
305 print "Version you entered is greater than installed ($RT::VERSION).\n";
306 _yesno() or exit(-2);
308 # ok, checked everything no let's refresh list
309 $upgrading_to = $custom_upgrading_to;
310 @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
312 return (1, "No DB changes between $upgrading_from and $upgrading_to")
315 print "\nGoing to apply following upgrades:\n";
316 print map "* $_\n", @versions;
320 print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n";
321 _yesno() or exit(-2) unless $args{'force'};
323 foreach my $v ( @versions ) {
324 print "Processing $v\n";
325 my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef);
326 if ( -e "$base_dir/$v/schema.$db_type" ) {
327 action_schema( %tmp );
329 if ( -e "$base_dir/$v/acl.$db_type" ) {
332 if ( -e "$base_dir/$v/content" ) {
333 action_insert( %tmp );
339 sub get_versions_from_to {
340 my ($base_dir, $from, $to) = @_;
342 opendir( my $dh, $base_dir ) or die "couldn't open dir: $!";
343 my @versions = grep -d "$base_dir/$_" && /\d+\.\d+\.\d+/, readdir $dh;
347 grep RT::Handle::cmp_version($_, $to) <= 0,
348 grep RT::Handle::cmp_version($_, $from) > 0,
349 sort RT::Handle::cmp_version @versions;
353 my ($action, $msg) = @_;
354 print STDERR "Couldn't finish '$action' step.\n\n";
355 print STDERR "ERROR: $msg\n\n";
359 sub get_dba_password {
360 print "In order to create or update your RT database,"
361 . " this script needs to connect to your "
362 . " $db_type instance on $db_host as $dba_user\n";
363 print "Please specify that user's database password below. If the user has no database\n";
364 print "password, just press return.\n\n";
367 my $password = ReadLine(0);
373 =head2 get_system_dbh
375 Returns L<DBI> database handle connected to B<system> with DBA credentials.
377 See also L<RT::Handle/SystemDSN>.
382 return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass );
386 return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass );
389 =head2 get_rt_dbh [USER, PASSWORD]
391 Returns L<DBI> database handle connected to RT database,
392 you may specify credentials(USER and PASSWORD) to connect
393 with. By default connects with credentials from RT config.
398 return _get_dbh( RT::Handle->DSN, $db_user, $db_pass );
402 my ($dsn, $user, $pass) = @_;
403 my $dbh = DBI->connect(
405 { RaiseError => 0, PrintError => 0 },
408 my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr;
409 if ( $args{'debug'} ) {
410 require Carp; Carp::confess( $msg );
412 print STDERR $msg; exit -1;
419 print "Proceed [y/N]:";
420 my $x = scalar(<STDIN>);
428 $0: Set up RT's database
430 --action init Initialize the database. This is combination of
431 multiple actions listed below. Create DB, schema,
432 setup acl, insert core data and initial data.
434 upgrade Apply all needed schema/acl/content updates (will ask
435 for version to upgrade from)
437 create Create the database.
439 drop Drop the database.
440 This will ERASE ALL YOUR DATA
442 schema Initialize only the database schema
443 To use a local or supplementary datafile, specify it
444 using the '--datadir' option below.
446 acl Initialize only the database ACLs
447 To use a local or supplementary datafile, specify it
448 using the '--datadir' option below.
450 coredata Insert data into RT's database. This data is required
451 for normal functioning of any RT instance.
453 insert Insert data into RT's database.
454 By default, will use RT's installation data.
455 To use a local or supplementary datafile, specify it
456 using the '--datafile' option below.
458 Several actions can be combined using comma separated list.
460 --datafile /path/to/datafile
461 --datadir /path/to/ Used to specify a path to find the local
462 database schema and acls to be installed.
466 --dba-password dba's password
467 --prompt-for-dba-password Ask for the database administrator's password interactively