2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC
7 # <jesse@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., 675 Mass Ave, Cambridge, MA 02139, USA.
29 # CONTRIBUTION SUBMISSION POLICY:
31 # (The following paragraph is not intended to limit the rights granted
32 # to you to modify and distribute this software under the terms of
33 # the GNU General Public License and is only of importance to you if
34 # you choose to contribute your changes and enhancements to the
35 # community by submitting them to Best Practical Solutions, LLC.)
37 # By intentionally submitting any modifications, corrections or
38 # derivatives to this work, or any other work intended for use with
39 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
40 # you are the copyright holder for those contributions and you grant
41 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
42 # royalty-free, perpetual, license to use, copy, create derivative
43 # works based on those contributions, and sublicense and distribute
44 # those contributions and any derivatives thereof.
46 # END BPS TAGGED BLOCK }}}
48 use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
50 qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
52 use lib "@RT_LIB_PATH@";
54 #This drags in RT's config.pm
55 # We do it in a begin block because RT::Handle needs to know the type to do its
67 use RT::ScripCondition;
79 'prompt-for-dba-password', 'force', 'debug',
80 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
84 $| = 1; #unbuffer that output.
87 my $Handle = RT::Handle->new($RT::DatabaseType);
91 if ( $args{'prompt-for-dba-password'} ) {
92 $args{'dba-password'} = get_dba_password();
93 chomp( $args{'dba-password'} );
96 unless ( $args{'action'} ) {
100 if ( $args{'action'} eq 'init' ) {
101 $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
102 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
103 print "Now creating a database for RT.\n";
104 if ($RT::DatabaseType ne 'Oracle' ||
105 $args{'dba'} ne $RT::DatabaseUser) {
108 print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
111 if ($RT::DatabaseType eq "mysql") {
112 # Check which version we're running
113 my ($version) = $dbh->selectrow_hashref("show variables like 'version'")->{Value} =~ /^(\d\.\d+)/;
114 print "*** Warning: RT is unsupported on MySQL versions before 4.0.x\n" if $version < 4;
116 # MySQL must have InnoDB support
117 my $innodb = $dbh->selectrow_hashref("show variables like 'have_innodb'")->{Value};
118 if ($innodb eq "NO") {
119 die "RT requires that MySQL be compiled with InnoDB table support.\n".
120 "See http://dev.mysql.com/doc/mysql/en/InnoDB.html\n";
121 } elsif ($innodb eq "DISABLED") {
122 die "RT requires that MySQL InnoDB table support be enabled.\n".
124 ? "Add 'innodb_data_file_path=ibdata1:10M:autoextend' to the [mysqld] section of my.cnf\n"
125 : "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
129 # SQLite can't deal with the disconnect/reconnect
130 unless ($RT::DatabaseType eq 'SQLite') {
134 if ($RT::DatabaseType eq "Oracle") {
135 $RT::DatabasePassword = $RT::DatabasePassword; #Warning avidance
136 $dbh = DBI->connect( $Handle->DSN, ${RT::DatabaseUser}, ${RT::DatabasePassword} ) || die $DBI::errstr;
139 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr;
142 print "Now populating database schema.\n";
144 print "Now inserting database ACLs\n";
145 insert_acl() unless ($RT::DatabaseType eq 'Oracle');
146 print "Now inserting RT core system objects\n";
147 insert_initial_data();
148 print "Now inserting RT data\n";
149 insert_data( $RT::EtcPath . "/initialdata" );
151 elsif ( $args{'action'} eq 'drop' ) {
153 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
156 warn "Database doesn't appear to exist. Aborting database drop.";
161 elsif ( $args{'action'} eq 'insert' ) {
162 insert_data( $args{'datafile'} || ($args{'datadir'}."/content"));
164 elsif ($args{'action'} eq 'acl') {
165 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
166 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
167 insert_acl($args{'datadir'});
169 elsif ($args{'action'} eq 'schema') {
170 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
171 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
172 insert_schema($args{'datadir'});
176 print STDERR '$0 called with an invalid --action parameter';
180 # {{{ sub insert_schema
182 my $base_path = (shift || $RT::EtcPath);
184 print "Creating database schema.\n";
186 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
187 no warnings 'unopened';
189 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
190 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
193 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
197 if ( $line =~ /;(\s*)$/ ) {
198 $statement =~ s/;(\s*)$//g;
199 push @schema, $statement;
204 local $SIG{__WARN__} = sub {};
205 my $is_local = 0; # local/etc/schema needs to be nonfatal.
206 $dbh->begin_work or die $dbh->errstr;
207 foreach my $statement (@schema) {
208 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
209 print STDERR "SQL: $statement\n" if defined $args{'debug'};
210 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
211 unless ( $sth->execute or $is_local ) {
212 die "Problem with statement:\n $statement\n" . $sth->errstr;
215 $dbh->commit or die $dbh->errstr;
219 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
221 print "Done setting up database schema.\n";
229 if ( $RT::DatabaseType eq 'Oracle' ) {
232 To delete the tables and sequences of the RT Oracle database by running
239 unless ( $args{'force'} ) {
242 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
243 WARNING: This will erase all data in $RT::DatabaseName.
246 exit unless _yesno();
250 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
252 if ( $RT::DatabaseType eq 'SQLite' ) {
253 unlink $RT::DatabaseName or warn $!;
256 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
263 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
264 if ( $RT::DatabaseType eq 'SQLite' ) {
267 elsif ( $RT::DatabaseType eq 'Pg' ) {
268 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
270 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
273 elsif ($RT::DatabaseType eq 'Oracle') {
276 elsif ( $RT::DatabaseType eq 'Informix' ) {
277 $ENV{DB_LOCALE} = 'en_us.utf8';
278 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
281 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
287 sub get_dba_password {
288 print "In order to create or update your RT database,";
289 print "this script needs to connect to your "
292 . $RT::DatabaseHost . " as "
293 . $args{'dba'} . ".\n";
294 print "Please specify that user's database password below. If the user has no database\n";
295 print "password, just press return.\n\n";
298 my $password = ReadLine(0);
306 print "Proceed [y/N]:";
307 my $x = scalar(<STDIN>);
316 my $base_path = (shift || $RT::EtcPath);
318 if ( $RT::DatabaseType =~ /^oracle$/i ) {
319 do $base_path . "/acl.Oracle"
320 || die "Couldn't find ACLS for Oracle\n" . $@;
322 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
323 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
325 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
326 do $base_path . "/acl.mysql"
327 || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
329 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
330 do $base_path . "/acl.Sybase"
331 || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
333 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
334 do $base_path . "/acl.Informix"
335 || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
337 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
341 die "Unknown RT database type";
345 foreach my $statement (@acl) {
346 print STDERR $statement if $args{'debug'};
347 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
348 unless ( $sth->execute ) {
349 die "Problem with statement:\n $statement\n" . $sth->errstr;
352 print "Done setting up database ACLs.\n";
357 =head2 get_system_dsn
359 Returns a dsn suitable for database creates and drops
360 and user creates and drops
366 my $dsn = $Handle->DSN;
368 #with mysql, you want to connect sans database to funge things
369 if ( $RT::DatabaseType eq 'mysql' ) {
370 $dsn =~ s/dbname=$RT::DatabaseName//;
372 # with postgres, you want to connect to database1
374 elsif ( $RT::DatabaseType eq 'Pg' ) {
375 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
377 elsif ( $RT::DatabaseType eq 'Informix' ) {
378 # with Informix, you want to connect sans database:
379 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
384 sub insert_initial_data {
388 #connect to the db, for actual RT work
390 $RT::Handle = RT::Handle->new();
391 $RT::Handle->Connect();
393 #Put together a current user object so we can create a User object
394 my $CurrentUser = new RT::CurrentUser();
396 print "Checking for existing system user...";
397 my $test_user = RT::User->new($CurrentUser);
398 $test_user->Load('RT_System');
399 if ( $test_user->id ) {
400 print "found!\n\nYou appear to have a functional RT database.\n"
401 . "Exiting, so as not to clobber your existing data.\n";
406 print "not found. This appears to be a new installation.\n";
409 print "Creating system user...";
410 my $RT_System = new RT::User($CurrentUser);
412 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
414 RealName => 'The RT System itself',
416 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
418 LastUpdatedBy => '1' );
425 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
429 # load some sort of data into the database
432 my $datafile = shift;
434 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
437 my $CurrentUser = RT::CurrentUser->new();
438 $CurrentUser->LoadByName('RT_System');
440 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
442 print "Creating Superuser ACL...";
444 my $superuser_ace = RT::ACE->new($CurrentUser);
445 $superuser_ace->_BootstrapCreate(
446 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
447 PrincipalType => 'Group',
448 RightName => 'SuperUser',
449 ObjectType => 'RT::System',
455 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
456 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
459 || die "Couldn't find initial data for import\n" . $@;
462 print "Creating groups...";
463 foreach $item (@Groups) {
464 my $new_entry = RT::Group->new($CurrentUser);
465 my ( $return, $msg ) = $new_entry->_Create(%$item);
466 print "(Error: $msg)" unless ($return);
472 print "Creating users...";
473 foreach $item (@Users) {
474 my $new_entry = new RT::User($CurrentUser);
475 my ( $return, $msg ) = $new_entry->Create(%$item);
476 print "(Error: $msg)" unless ($return);
482 print "Creating queues...";
483 for $item (@Queues) {
484 my $new_entry = new RT::Queue($CurrentUser);
485 my ( $return, $msg ) = $new_entry->Create(%$item);
486 print "(Error: $msg)" unless ($return);
492 print "Creating ACL...";
493 for my $item (@ACL) {
495 my ($princ, $object);
497 # Global rights or Queue rights?
498 if ($item->{'Queue'}) {
499 $object = RT::Queue->new($CurrentUser);
500 $object->Load( $item->{'Queue'} );
502 $object = $RT::System;
505 # Group rights or user rights?
506 if ($item->{'GroupDomain'}) {
507 $princ = RT::Group->new($CurrentUser);
508 if ($item->{'GroupDomain'} eq 'UserDefined') {
509 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
510 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
511 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
512 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
513 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
514 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
516 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
517 Queue => $object->id);
519 $princ->Load( $item->{'GroupId'} );
522 $princ = RT::User->new($CurrentUser);
523 $princ->Load( $item->{'UserId'} );
527 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
528 Right => $item->{'Right'},
543 print "Creating custom fields...";
544 for $item (@CustomFields) {
545 my $new_entry = new RT::CustomField($CurrentUser);
546 my $values = $item->{'Values'};
547 delete $item->{'Values'};
548 my $q = $item->{'Queue'};
549 my $q_obj = RT::Queue->new($CurrentUser);
552 $item->{'Queue'} = $q_obj->Id;
555 $item->{'Queue'} = 0;
558 print "(Error: Could not find queue " . $q . ")\n"
559 unless ( $q_obj->Id );
562 my ( $return, $msg ) = $new_entry->Create(%$item);
564 print "(Error: $msg)\n";
568 foreach my $value ( @{$values} ) {
569 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
570 print "(Error: $emsg)\n" unless ($eval);
573 print "(Error: $msg)\n" unless ($return);
581 print "Creating ScripActions...";
583 for $item (@ScripActions) {
584 my $new_entry = RT::ScripAction->new($CurrentUser);
585 my $return = $new_entry->Create(%$item);
592 if (@ScripConditions) {
593 print "Creating ScripConditions...";
595 for $item (@ScripConditions) {
596 my $new_entry = RT::ScripCondition->new($CurrentUser);
597 my $return = $new_entry->Create(%$item);
605 print "Creating templates...";
607 for $item (@Templates) {
608 my $new_entry = new RT::Template($CurrentUser);
609 my $return = $new_entry->Create(%$item);
615 print "Creating scrips...";
617 for $item (@Scrips) {
618 my $new_entry = new RT::Scrip($CurrentUser);
619 my ( $return, $msg ) = $new_entry->Create(%$item);
624 print "(Error: $msg)\n";
629 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
630 print "Done setting up database content.\n";
633 =head2 ACLEquivGroupId
635 Given a userid, return that user's acl equivalence group
639 sub ACLEquivGroupId {
640 my $username = shift;
641 my $user = RT::User->new($RT::SystemUser);
642 $user->Load($username);
643 my $equiv_group = RT::Group->new($RT::SystemUser);
644 $equiv_group->LoadACLEquivalenceGroup($user);
645 return ( $equiv_group->Id );
652 $0: Set up RT's database
654 --action init Initialize the database
655 drop Drop the database.
656 This will ERASE ALL YOUR DATA
657 insert Insert data into RT's database.
658 By default, will use RT's installation data.
659 To use a local or supplementary datafile, specify it
660 using the '--datafile' option below.
662 acl Initialize only the database ACLs
663 To use a local or supplementary datafile, specify it
664 using the '--datadir' option below.
666 schema Initialize only the database schema
667 To use a local or supplementary datafile, specify it
668 using the '--datadir' option below.
670 --datafile /path/to/datafile
671 --datadir /path/to/ Used to specify a path to find the local
672 database schema and acls to be installed.
676 --dba-password dba's password
677 --prompt-for-dba-password Ask for the database administrator's password interactively