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_initial' ) {
162 insert_initial_data();
164 elsif ( $args{'action'} eq 'insert' ) {
165 insert_data( $args{'datafile'} || ($args{'datadir'}."/content"));
167 elsif ($args{'action'} eq 'acl') {
168 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
169 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
170 insert_acl($args{'datadir'});
172 elsif ($args{'action'} eq 'schema') {
173 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
174 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
175 insert_schema($args{'datadir'});
179 print STDERR '$0 called with an invalid --action parameter';
183 # {{{ sub insert_schema
185 my $base_path = (shift || $RT::EtcPath);
187 print "Creating database schema.\n";
189 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
190 no warnings 'unopened';
192 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
193 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
196 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
200 if ( $line =~ /;(\s*)$/ ) {
201 $statement =~ s/;(\s*)$//g;
202 push @schema, $statement;
207 local $SIG{__WARN__} = sub {};
208 my $is_local = 0; # local/etc/schema needs to be nonfatal.
209 $dbh->begin_work or die $dbh->errstr;
210 foreach my $statement (@schema) {
211 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
212 print STDERR "SQL: $statement\n" if defined $args{'debug'};
213 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
214 unless ( $sth->execute or $is_local ) {
215 die "Problem with statement:\n $statement\n" . $sth->errstr;
218 $dbh->commit or die $dbh->errstr;
222 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
224 print "Done setting up database schema.\n";
232 if ( $RT::DatabaseType eq 'Oracle' ) {
235 To delete the tables and sequences of the RT Oracle database by running
242 unless ( $args{'force'} ) {
245 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
246 WARNING: This will erase all data in $RT::DatabaseName.
249 exit unless _yesno();
253 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
255 if ( $RT::DatabaseType eq 'SQLite' ) {
256 unlink $RT::DatabaseName or warn $!;
259 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
266 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
267 if ( $RT::DatabaseType eq 'SQLite' ) {
270 elsif ( $RT::DatabaseType eq 'Pg' ) {
271 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
273 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
276 elsif ($RT::DatabaseType eq 'Oracle') {
279 elsif ( $RT::DatabaseType eq 'Informix' ) {
280 $ENV{DB_LOCALE} = 'en_us.utf8';
281 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
284 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
290 sub get_dba_password {
291 print "In order to create or update your RT database,";
292 print "this script needs to connect to your "
295 . $RT::DatabaseHost . " as "
296 . $args{'dba'} . ".\n";
297 print "Please specify that user's database password below. If the user has no database\n";
298 print "password, just press return.\n\n";
301 my $password = ReadLine(0);
309 print "Proceed [y/N]:";
310 my $x = scalar(<STDIN>);
319 my $base_path = (shift || $RT::EtcPath);
321 if ( $RT::DatabaseType =~ /^oracle$/i ) {
322 do $base_path . "/acl.Oracle"
323 || die "Couldn't find ACLS for Oracle\n" . $@;
325 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
326 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
328 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
329 do $base_path . "/acl.mysql"
330 || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
332 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
333 do $base_path . "/acl.Sybase"
334 || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
336 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
337 do $base_path . "/acl.Informix"
338 || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
340 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
344 die "Unknown RT database type";
348 foreach my $statement (@acl) {
349 print STDERR $statement if $args{'debug'};
350 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
351 unless ( $sth->execute ) {
352 die "Problem with statement:\n $statement\n" . $sth->errstr;
355 print "Done setting up database ACLs.\n";
360 =head2 get_system_dsn
362 Returns a dsn suitable for database creates and drops
363 and user creates and drops
369 my $dsn = $Handle->DSN;
371 #with mysql, you want to connect sans database to funge things
372 if ( $RT::DatabaseType eq 'mysql' ) {
373 $dsn =~ s/dbname=$RT::DatabaseName//;
375 # with postgres, you want to connect to database1
377 elsif ( $RT::DatabaseType eq 'Pg' ) {
378 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
380 elsif ( $RT::DatabaseType eq 'Informix' ) {
381 # with Informix, you want to connect sans database:
382 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
387 sub insert_initial_data {
391 #connect to the db, for actual RT work
393 $RT::Handle = RT::Handle->new();
394 $RT::Handle->Connect();
396 #Put together a current user object so we can create a User object
397 my $CurrentUser = new RT::CurrentUser();
399 print "Checking for existing system user...";
400 my $test_user = RT::User->new($CurrentUser);
401 $test_user->Load('RT_System');
402 if ( $test_user->id ) {
403 print "found!\n\nYou appear to have a functional RT database.\n"
404 . "Exiting, so as not to clobber your existing data.\n";
409 print "not found. This appears to be a new installation.\n";
412 print "Creating system user...";
413 my $RT_System = new RT::User($CurrentUser);
415 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
417 RealName => 'The RT System itself',
419 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
421 LastUpdatedBy => '1' );
428 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
432 # load some sort of data into the database
435 my $datafile = shift;
437 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
440 my $CurrentUser = RT::CurrentUser->new();
441 $CurrentUser->LoadByName('RT_System');
443 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
445 print "Creating Superuser ACL...";
447 my $superuser_ace = RT::ACE->new($CurrentUser);
448 $superuser_ace->_BootstrapCreate(
449 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
450 PrincipalType => 'Group',
451 RightName => 'SuperUser',
452 ObjectType => 'RT::System',
458 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
459 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
462 || die "Couldn't find initial data for import\n" . $@;
465 print "Creating groups...";
466 foreach $item (@Groups) {
467 my $new_entry = RT::Group->new($CurrentUser);
468 my ( $return, $msg ) = $new_entry->_Create(%$item);
469 print "(Error: $msg)" unless ($return);
475 print "Creating users...";
476 foreach $item (@Users) {
477 my $new_entry = new RT::User($CurrentUser);
478 my ( $return, $msg ) = $new_entry->Create(%$item);
479 print "(Error: $msg)" unless ($return);
485 print "Creating queues...";
486 for $item (@Queues) {
487 my $new_entry = new RT::Queue($CurrentUser);
488 my ( $return, $msg ) = $new_entry->Create(%$item);
489 print "(Error: $msg)" unless ($return);
495 print "Creating ACL...";
496 for my $item (@ACL) {
498 my ($princ, $object);
500 # Global rights or Queue rights?
501 if ($item->{'Queue'}) {
502 $object = RT::Queue->new($CurrentUser);
503 $object->Load( $item->{'Queue'} );
505 $object = $RT::System;
508 # Group rights or user rights?
509 if ($item->{'GroupDomain'}) {
510 $princ = RT::Group->new($CurrentUser);
511 if ($item->{'GroupDomain'} eq 'UserDefined') {
512 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
513 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
514 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
515 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
516 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
517 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
519 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
520 Queue => $object->id);
522 $princ->Load( $item->{'GroupId'} );
525 $princ = RT::User->new($CurrentUser);
526 $princ->Load( $item->{'UserId'} );
530 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
531 Right => $item->{'Right'},
546 print "Creating custom fields...";
547 for $item (@CustomFields) {
548 my $new_entry = new RT::CustomField($CurrentUser);
549 my $values = $item->{'Values'};
550 delete $item->{'Values'};
551 my $q = $item->{'Queue'};
552 my $q_obj = RT::Queue->new($CurrentUser);
555 $item->{'Queue'} = $q_obj->Id;
558 $item->{'Queue'} = 0;
561 print "(Error: Could not find queue " . $q . ")\n"
562 unless ( $q_obj->Id );
565 my ( $return, $msg ) = $new_entry->Create(%$item);
567 foreach my $value ( @{$values} ) {
568 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
569 print "(Error: $emsg)\n" unless ($eval);
572 print "(Error: $msg)\n" unless ($return);
580 print "Creating ScripActions...";
582 for $item (@ScripActions) {
583 my $new_entry = RT::ScripAction->new($CurrentUser);
584 my $return = $new_entry->Create(%$item);
591 if (@ScripConditions) {
592 print "Creating ScripConditions...";
594 for $item (@ScripConditions) {
595 my $new_entry = RT::ScripCondition->new($CurrentUser);
596 my $return = $new_entry->Create(%$item);
604 print "Creating templates...";
606 for $item (@Templates) {
607 my $new_entry = new RT::Template($CurrentUser);
608 my $return = $new_entry->Create(%$item);
614 print "Creating scrips...";
616 for $item (@Scrips) {
617 my $new_entry = new RT::Scrip($CurrentUser);
618 my ( $return, $msg ) = $new_entry->Create(%$item);
623 print "(Error: $msg)\n";
628 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
629 print "Done setting up database content.\n";
632 =head2 ACLEquivGroupId
634 Given a userid, return that user's acl equivalence group
638 sub ACLEquivGroupId {
639 my $username = shift;
640 my $user = RT::User->new($RT::SystemUser);
641 $user->Load($username);
642 my $equiv_group = RT::Group->new($RT::SystemUser);
643 $equiv_group->LoadACLEquivalenceGroup($user);
644 return ( $equiv_group->Id );
651 $0: Set up RT's database
653 --action init Initialize the database
654 drop Drop the database.
655 This will ERASE ALL YOUR DATA
657 Insert RT's core system objects
658 insert Insert data into RT's database.
659 By default, will use RT's installation data.
660 To use a local or supplementary datafile, specify it
661 using the '--datafile' option below.
663 acl Initialize only the database ACLs
664 To use a local or supplementary datafile, specify it
665 using the '--datadir' option below.
667 schema Initialize only the database schema
668 To use a local or supplementary datafile, specify it
669 using the '--datadir' option below.
671 --datafile /path/to/datafile
672 --datadir /path/to/ Used to specify a path to find the local
673 database schema and acls to be installed.
677 --dba-password dba's password
678 --prompt-for-dba-password Ask for the database administrator's password interactively