2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2007 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., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/copyleft/gpl.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 }}}
50 use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
52 qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips @Attributes);
54 use lib "@LOCAL_LIB_PATH@";
55 use lib "@RT_LIB_PATH@";
57 #This drags in RT's config.pm
58 # We do it in a begin block because RT::Handle needs to know the type to do its
70 use RT::ScripCondition;
82 'prompt-for-dba-password', 'force', 'debug',
83 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
87 unless ( $args{'action'} ) {
92 $| = 1; #unbuffer that output.
95 my $Handle = RT::Handle->new($RT::DatabaseType);
99 if ( $args{'prompt-for-dba-password'} ) {
100 $args{'dba-password'} = get_dba_password();
101 chomp( $args{'dba-password'} );
104 if ( $args{'action'} eq 'init' ) {
105 $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
106 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
107 print "Now creating a database for RT.\n";
108 if ( $RT::DatabaseType ne 'Oracle' || $args{'dba'} ne $RT::DatabaseUser ) {
111 print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
114 if ( $RT::DatabaseType eq "mysql" ) {
115 # Check which version we're running
116 my ($version) = $dbh->selectrow_hashref("show variables like 'version'")->{Value} =~ /^(\d\.\d+)/;
117 print "*** Warning: RT is unsupported on MySQL versions before 4.0.x\n" if $version < 4;
119 # MySQL must have InnoDB support
120 my $innodb = $dbh->selectrow_hashref("show variables like 'have_innodb'")->{Value};
121 if ( $innodb eq "NO" ) {
122 die "RT requires that MySQL be compiled with InnoDB table support.\n".
123 "See http://dev.mysql.com/doc/mysql/en/InnoDB.html\n";
124 } elsif ( $innodb eq "DISABLED" ) {
125 die "RT requires that MySQL InnoDB table support be enabled.\n".
127 ? "Add 'innodb_data_file_path=ibdata1:10M:autoextend' to the [mysqld] section of my.cnf\n"
128 : "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
132 # SQLite can't deal with the disconnect/reconnect
133 unless ( $RT::DatabaseType eq 'SQLite' ) {
137 if ( $RT::DatabaseType eq "Oracle" ) {
138 $RT::DatabasePassword = $RT::DatabasePassword; #Warning avidance
139 $dbh = DBI->connect( $Handle->DSN, ${RT::DatabaseUser}, ${RT::DatabasePassword} ) || die $DBI::errstr;
141 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr;
144 print "Now populating database schema.\n";
146 print "Now inserting database ACLs\n";
147 insert_acl() unless $RT::DatabaseType eq 'Oracle';
148 print "Now inserting RT core system objects\n";
149 insert_initial_data();
150 print "Now inserting RT data\n";
151 insert_data( $RT::EtcPath . "/initialdata" );
153 elsif ( $args{'action'} eq 'drop' ) {
155 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
158 warn "Database doesn't appear to exist. Aborting database drop.";
163 elsif ( $args{'action'} eq 'insert' ) {
164 insert_data( $args{'datafile'} || ($args{'datadir'}."/content") );
166 elsif ( $args{'action'} eq 'acl' ) {
167 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
168 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
169 insert_acl($args{'datadir'});
171 elsif ( $args{'action'} eq 'schema' ) {
172 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
173 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
174 insert_schema($args{'datadir'});
177 print STDERR "$0 called with an invalid --action parameter\n";
181 # {{{ sub insert_schema
183 my $base_path = (shift || $RT::EtcPath);
185 print "Creating database schema.\n";
187 my $schema_file = $base_path . "/schema." . $RT::DatabaseType;
188 if ( -f $schema_file ) {
189 open( SCHEMA, "<$schema_file" ) or die "Can't open $schema_file: $!";
190 my @lines = <SCHEMA>;
192 my $local_schema_file = $RT::LocalEtcPath . "/schema." . $RT::DatabaseType;
193 if (-f $local_schema_file) {
194 open( SCHEMA_LOCAL, "<$local_schema_file" )
195 or die "Can't open $local_schema_file: $!";
196 push @lines, ';;', <SCHEMA_LOCAL>;
200 foreach my $line (@lines) {
204 if ( $line =~ /;(\s*)$/ ) {
205 $statement =~ s/;(\s*)$//g;
206 push @schema, $statement;
211 local $SIG{__WARN__} = sub {};
212 my $is_local = 0; # local/etc/schema needs to be nonfatal.
213 $dbh->begin_work or die $dbh->errstr;
214 foreach my $statement (@schema) {
215 if ( $statement =~ /^\s*;$/ ) { $is_local = 1; next; }
217 print STDERR "SQL: $statement\n" if defined $args{'debug'};
218 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
219 unless ( $sth->execute or $is_local ) {
220 die "Problem with statement:\n $statement\n" . $sth->errstr;
223 $dbh->commit or die $dbh->errstr;
226 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
228 print "Done setting up database schema.\n";
235 if ( $RT::DatabaseType eq 'Oracle' ) {
238 To delete the tables and sequences of the RT Oracle database by running
245 unless ( $args{'force'} ) {
248 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
249 WARNING: This will erase all data in $RT::DatabaseName.
252 exit unless _yesno();
256 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
258 if ( $RT::DatabaseType eq 'SQLite' ) {
259 unlink $RT::DatabaseName or warn $!;
262 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
269 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
270 if ( $RT::DatabaseType eq 'SQLite' ) {
273 elsif ( $RT::DatabaseType eq 'Pg' ) {
274 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
275 if ( $DBI::errstr ) {
276 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
279 elsif ( $RT::DatabaseType eq 'Oracle' ) {
282 elsif ( $RT::DatabaseType eq 'Informix' ) {
283 $ENV{DB_LOCALE} = 'en_us.utf8';
284 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
287 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
293 sub get_dba_password {
294 print "In order to create or update your RT database,";
295 print "this script needs to connect to your "
298 . $RT::DatabaseHost . " as "
299 . $args{'dba'} . ".\n";
300 print "Please specify that user's database password below. If the user has no database\n";
301 print "password, just press return.\n\n";
304 my $password = ReadLine(0);
312 print "Proceed [y/N]:";
313 my $x = scalar(<STDIN>);
321 my $base_path = (shift || $RT::EtcPath);
323 if ( $RT::DatabaseType =~ /^oracle$/i ) {
324 do $base_path . "/acl.Oracle"
325 || die "Couldn't find ACLS for Oracle\n" . $@;
327 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
328 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
330 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
331 do $base_path . "/acl.mysql"
332 || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
334 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
335 do $base_path . "/acl.Sybase"
336 || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
338 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
339 do $base_path . "/acl.Informix"
340 || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
342 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
346 die "Unknown RT database type";
350 foreach my $statement (@acl) {
351 print STDERR $statement if $args{'debug'};
352 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
353 unless ( $sth->execute ) {
354 die "Problem with statement:\n $statement\n" . $sth->errstr;
357 print "Done setting up database ACLs.\n";
362 =head2 get_system_dsn
364 Returns a dsn suitable for database creates and drops
365 and user creates and drops
371 my $dsn = $Handle->DSN;
373 #with mysql, you want to connect sans database to funge things
374 if ( $RT::DatabaseType eq 'mysql' ) {
375 $dsn =~ s/dbname=$RT::DatabaseName//;
377 # with postgres, you want to connect to database1
379 elsif ( $RT::DatabaseType eq 'Pg' ) {
380 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
382 elsif ( $RT::DatabaseType eq 'Informix' ) {
383 # with Informix, you want to connect sans database:
384 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
389 sub insert_initial_data {
393 #connect to the db, for actual RT work
395 $RT::Handle = RT::Handle->new();
396 $RT::Handle->Connect();
398 #Put together a current user object so we can create a User object
399 my $CurrentUser = new RT::CurrentUser();
401 print "Checking for existing system user...";
402 my $test_user = RT::User->new($CurrentUser);
403 $test_user->Load('RT_System');
404 if ( $test_user->id ) {
405 print "found!\n\nYou appear to have a functional RT database.\n"
406 . "Exiting, so as not to clobber your existing data.\n";
411 print "not found. This appears to be a new installation.\n";
414 print "Creating system user...";
415 my $RT_System = new RT::User($CurrentUser);
417 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
419 RealName => 'The RT System itself',
421 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
423 LastUpdatedBy => '1',
431 $RT::Handle->Disconnect() unless $RT::DatabaseType eq 'SQLite';
435 # load some sort of data into the database
438 my $datafile = shift;
440 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
443 my $CurrentUser = RT::CurrentUser->new();
444 $CurrentUser->LoadByName('RT_System');
446 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
448 print "Creating Superuser ACL...";
450 my $superuser_ace = RT::ACE->new($CurrentUser);
451 $superuser_ace->_BootstrapCreate(
452 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
453 PrincipalType => 'Group',
454 RightName => 'SuperUser',
455 ObjectType => 'RT::System',
461 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
462 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
465 || die "Couldn't find initial data for import\n" . $@;
468 print "Creating groups...";
469 foreach $item (@Groups) {
470 my $new_entry = RT::Group->new($CurrentUser);
471 my ( $return, $msg ) = $new_entry->_Create(%$item);
472 print "(Error: $msg)" unless $return;
478 print "Creating users...";
479 foreach $item (@Users) {
480 my $new_entry = new RT::User($CurrentUser);
481 my ( $return, $msg ) = $new_entry->Create(%$item);
482 print "(Error: $msg)" unless $return;
488 print "Creating queues...";
489 for $item (@Queues) {
490 my $new_entry = new RT::Queue($CurrentUser);
491 my ( $return, $msg ) = $new_entry->Create(%$item);
492 print "(Error: $msg)" unless $return;
498 print "Creating ACL...";
499 for my $item (@ACL) {
501 my ($princ, $object);
503 # Global rights or Queue rights?
504 if ( $item->{'Queue'} ) {
505 $object = RT::Queue->new($CurrentUser);
506 $object->Load( $item->{'Queue'} );
508 $object = $RT::System;
511 # Group rights or user rights?
512 if ( $item->{'GroupDomain'} ) {
513 $princ = RT::Group->new($CurrentUser);
514 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
515 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
516 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
517 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
518 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
519 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
520 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
523 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
524 Queue => $object->id);
526 $princ->Load( $item->{'GroupId'} );
529 $princ = RT::User->new($CurrentUser);
530 $princ->Load( $item->{'UserId'} );
534 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
535 Right => $item->{'Right'},
549 if ( @CustomFields ) {
550 print "Creating custom fields...";
551 for $item (@CustomFields) {
552 my $new_entry = new RT::CustomField($CurrentUser);
553 my $values = $item->{'Values'};
554 delete $item->{'Values'};
555 my ( $return, $msg ) = $new_entry->Create(%$item);
557 print "(Error: $msg)\n";
561 foreach my $value ( @{$values} ) {
562 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
563 print "(Error: $emsg)\n" unless $eval;
566 if ( $item->{LookupType} && !exists $item->{'Queue'} ) { # enable by default
567 my $ocf = RT::ObjectCustomField->new($CurrentUser);
568 $ocf->Create( CustomField => $new_entry->Id );
571 print "(Error: $msg)\n" unless $return;
578 if ( @ScripActions ) {
579 print "Creating ScripActions...";
581 for $item (@ScripActions) {
582 my $new_entry = RT::ScripAction->new($CurrentUser);
583 my ($return,$msg) = $new_entry->Create(%$item);
585 print "(Error: $msg)\n";
594 if ( @ScripConditions ) {
595 print "Creating ScripConditions...";
597 for $item (@ScripConditions) {
598 my $new_entry = RT::ScripCondition->new($CurrentUser);
599 my ($return,$msg) = $new_entry->Create(%$item);
601 print "(Error: $msg)\n";
611 print "Creating templates...";
613 for $item (@Templates) {
614 my $new_entry = new RT::Template($CurrentUser);
615 my $return = $new_entry->Create(%$item);
621 print "Creating scrips...";
623 for $item (@Scrips) {
624 my $new_entry = new RT::Scrip($CurrentUser);
625 my ( $return, $msg ) = $new_entry->Create(%$item);
630 print "(Error: $msg)\n";
636 print "Creating predefined searches...";
637 my $sys = RT::System->new($CurrentUser);
639 for $item (@Attributes) {
640 my $obj = delete $item->{Object}; # XXX: make this something loadable
642 my ( $return, $msg ) = $obj->AddAttribute (%$item);
647 print "(Error: $msg)\n";
652 $RT::Handle->Disconnect() unless $RT::DatabaseType eq 'SQLite';
653 print "Done setting up database content.\n";
656 =head2 ACLEquivGroupId
658 Given a userid, return that user's acl equivalence group
662 sub ACLEquivGroupId {
663 my $username = shift;
664 my $user = RT::User->new($RT::SystemUser);
665 $user->Load($username);
666 my $equiv_group = RT::Group->new($RT::SystemUser);
667 $equiv_group->LoadACLEquivalenceGroup($user);
668 return ( $equiv_group->Id );
675 $0: Set up RT's database
677 --action init Initialize the database
678 drop Drop the database.
679 This will ERASE ALL YOUR DATA
680 insert Insert data into RT's database.
681 By default, will use RT's installation data.
682 To use a local or supplementary datafile, specify it
683 using the '--datafile' option below.
685 acl Initialize only the database ACLs
686 To use a local or supplementary datafile, specify it
687 using the '--datadir' option below.
689 schema Initialize only the database schema
690 To use a local or supplementary datafile, specify it
691 using the '--datadir' option below.
693 --datafile /path/to/datafile
694 --datadir /path/to/ Used to specify a path to find the local
695 database schema and acls to be installed.
699 --dba-password dba's password
700 --prompt-for-dba-password Ask for the database administrator's password interactively