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') {
133 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr;
135 print "Now populating database schema.\n";
137 print "Now inserting database ACLs\n";
138 insert_acl() unless ($RT::DatabaseType eq 'Oracle');
139 print "Now inserting RT core system objects\n";
140 insert_initial_data();
141 print "Now inserting RT data\n";
142 insert_data( $RT::EtcPath . "/initialdata" );
144 elsif ( $args{'action'} eq 'drop' ) {
146 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
149 warn "Database doesn't appear to exist. Aborting database drop.";
154 elsif ( $args{'action'} eq 'insert' ) {
155 insert_data( $args{'datafile'} || ($args{'datadir'}."/content"));
157 elsif ($args{'action'} eq 'acl') {
158 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
159 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
160 insert_acl($args{'datadir'});
162 elsif ($args{'action'} eq 'schema') {
163 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
164 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
165 insert_schema($args{'datadir'});
169 print STDERR '$0 called with an invalid --action parameter';
173 # {{{ sub insert_schema
175 my $base_path = (shift || $RT::EtcPath);
177 print "Creating database schema.\n";
179 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
180 no warnings 'unopened';
182 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
183 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
186 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
190 if ( $line =~ /;(\s*)$/ ) {
191 $statement =~ s/;(\s*)$//g;
192 push @schema, $statement;
197 local $SIG{__WARN__} = sub {};
198 my $is_local = 0; # local/etc/schema needs to be nonfatal.
199 $dbh->begin_work or die $dbh->errstr;
200 foreach my $statement (@schema) {
201 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
202 print STDERR "SQL: $statement\n" if defined $args{'debug'};
203 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
204 unless ( $sth->execute or $is_local ) {
205 die "Problem with statement:\n $statement\n" . $sth->errstr;
208 $dbh->commit or die $dbh->errstr;
212 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
214 print "Done setting up database schema.\n";
222 if ( $RT::DatabaseType eq 'Oracle' ) {
225 To delete the tables and sequences of the RT Oracle database by running
232 unless ( $args{'force'} ) {
235 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
236 WARNING: This will erase all data in $RT::DatabaseName.
239 exit unless _yesno();
243 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
245 if ( $RT::DatabaseType eq 'SQLite' ) {
246 unlink $RT::DatabaseName or warn $!;
249 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
256 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
257 if ( $RT::DatabaseType eq 'SQLite' ) {
260 elsif ( $RT::DatabaseType eq 'Pg' ) {
261 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
263 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
266 elsif ($RT::DatabaseType eq 'Oracle') {
269 elsif ( $RT::DatabaseType eq 'Informix' ) {
270 $ENV{DB_LOCALE} = 'en_us.utf8';
271 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
274 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
280 sub get_dba_password {
281 print "In order to create or update your RT database,";
282 print "this script needs to connect to your "
285 . $RT::DatabaseHost . " as "
286 . $args{'dba'} . ".\n";
287 print "Please specify that user's database password below. If the user has no database\n";
288 print "password, just press return.\n\n";
291 my $password = ReadLine(0);
299 print "Proceed [y/N]:";
300 my $x = scalar(<STDIN>);
309 my $base_path = (shift || $RT::EtcPath);
311 if ( $RT::DatabaseType =~ /^oracle$/i ) {
312 do $base_path . "/acl.Oracle"
313 || die "Couldn't find ACLS for Oracle\n" . $@;
315 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
316 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
318 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
319 do $base_path . "/acl.mysql"
320 || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
322 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
323 do $base_path . "/acl.Sybase"
324 || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
326 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
327 do $base_path . "/acl.Informix"
328 || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
330 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
334 die "Unknown RT database type";
338 foreach my $statement (@acl) {
339 print STDERR $statement if $args{'debug'};
340 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
341 unless ( $sth->execute ) {
342 die "Problem with statement:\n $statement\n" . $sth->errstr;
345 print "Done setting up database ACLs.\n";
350 =head2 get_system_dsn
352 Returns a dsn suitable for database creates and drops
353 and user creates and drops
359 my $dsn = $Handle->DSN;
361 #with mysql, you want to connect sans database to funge things
362 if ( $RT::DatabaseType eq 'mysql' ) {
363 $dsn =~ s/dbname=$RT::DatabaseName//;
365 # with postgres, you want to connect to database1
367 elsif ( $RT::DatabaseType eq 'Pg' ) {
368 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
370 elsif ( $RT::DatabaseType eq 'Informix' ) {
371 # with Informix, you want to connect sans database:
372 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
377 sub insert_initial_data {
381 #connect to the db, for actual RT work
383 $RT::Handle = RT::Handle->new();
384 $RT::Handle->Connect();
386 #Put together a current user object so we can create a User object
387 my $CurrentUser = new RT::CurrentUser();
389 print "Checking for existing system user...";
390 my $test_user = RT::User->new($CurrentUser);
391 $test_user->Load('RT_System');
392 if ( $test_user->id ) {
393 print "found!\n\nYou appear to have a functional RT database.\n"
394 . "Exiting, so as not to clobber your existing data.\n";
399 print "not found. This appears to be a new installation.\n";
402 print "Creating system user...";
403 my $RT_System = new RT::User($CurrentUser);
405 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
407 RealName => 'The RT System itself',
409 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
411 LastUpdatedBy => '1' );
418 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
422 # load some sort of data into the database
425 my $datafile = shift;
427 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
430 my $CurrentUser = RT::CurrentUser->new();
431 $CurrentUser->LoadByName('RT_System');
433 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
435 print "Creating Superuser ACL...";
437 my $superuser_ace = RT::ACE->new($CurrentUser);
438 $superuser_ace->_BootstrapCreate(
439 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
440 PrincipalType => 'Group',
441 RightName => 'SuperUser',
442 ObjectType => 'RT::System',
448 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
449 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
452 || die "Couldn't find initial data for import\n" . $@;
455 print "Creating groups...";
456 foreach $item (@Groups) {
457 my $new_entry = RT::Group->new($CurrentUser);
458 my ( $return, $msg ) = $new_entry->_Create(%$item);
459 print "(Error: $msg)" unless ($return);
465 print "Creating users...";
466 foreach $item (@Users) {
467 my $new_entry = new RT::User($CurrentUser);
468 my ( $return, $msg ) = $new_entry->Create(%$item);
469 print "(Error: $msg)" unless ($return);
475 print "Creating queues...";
476 for $item (@Queues) {
477 my $new_entry = new RT::Queue($CurrentUser);
478 my ( $return, $msg ) = $new_entry->Create(%$item);
479 print "(Error: $msg)" unless ($return);
485 print "Creating ACL...";
486 for my $item (@ACL) {
488 my ($princ, $object);
490 # Global rights or Queue rights?
491 if ($item->{'Queue'}) {
492 $object = RT::Queue->new($CurrentUser);
493 $object->Load( $item->{'Queue'} );
495 $object = $RT::System;
498 # Group rights or user rights?
499 if ($item->{'GroupDomain'}) {
500 $princ = RT::Group->new($CurrentUser);
501 if ($item->{'GroupDomain'} eq 'UserDefined') {
502 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
503 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
504 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
505 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
506 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
507 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
509 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
510 Queue => $object->id);
512 $princ->Load( $item->{'GroupId'} );
515 $princ = RT::User->new($CurrentUser);
516 $princ->Load( $item->{'UserId'} );
520 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
521 Right => $item->{'Right'},
536 print "Creating custom fields...";
537 for $item (@CustomFields) {
538 my $new_entry = new RT::CustomField($CurrentUser);
539 my $values = $item->{'Values'};
540 delete $item->{'Values'};
541 my $q = $item->{'Queue'};
542 my $q_obj = RT::Queue->new($CurrentUser);
545 $item->{'Queue'} = $q_obj->Id;
548 $item->{'Queue'} = 0;
551 print "(Error: Could not find queue " . $q . ")\n"
552 unless ( $q_obj->Id );
555 my ( $return, $msg ) = $new_entry->Create(%$item);
557 foreach my $value ( @{$values} ) {
558 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
559 print "(Error: $emsg)\n" unless ($eval);
562 print "(Error: $msg)\n" unless ($return);
570 print "Creating ScripActions...";
572 for $item (@ScripActions) {
573 my $new_entry = RT::ScripAction->new($CurrentUser);
574 my $return = $new_entry->Create(%$item);
581 if (@ScripConditions) {
582 print "Creating ScripConditions...";
584 for $item (@ScripConditions) {
585 my $new_entry = RT::ScripCondition->new($CurrentUser);
586 my $return = $new_entry->Create(%$item);
594 print "Creating templates...";
596 for $item (@Templates) {
597 my $new_entry = new RT::Template($CurrentUser);
598 my $return = $new_entry->Create(%$item);
604 print "Creating scrips...";
606 for $item (@Scrips) {
607 my $new_entry = new RT::Scrip($CurrentUser);
608 my ( $return, $msg ) = $new_entry->Create(%$item);
613 print "(Error: $msg)\n";
618 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
619 print "Done setting up database content.\n";
622 =head2 ACLEquivGroupId
624 Given a userid, return that user's acl equivalence group
628 sub ACLEquivGroupId {
629 my $username = shift;
630 my $user = RT::User->new($RT::SystemUser);
631 $user->Load($username);
632 my $equiv_group = RT::Group->new($RT::SystemUser);
633 $equiv_group->LoadACLEquivalenceGroup($user);
634 return ( $equiv_group->Id );
641 $0: Set up RT's database
643 --action init Initialize the database
644 drop Drop the database.
645 This will ERASE ALL YOUR DATA
646 insert Insert data into RT's database.
647 By default, will use RT's installation data.
648 To use a local or supplementary datafile, specify it
649 using the '--datafile' option below.
651 acl Initialize only the database ACLs
652 To use a local or supplementary datafile, specify it
653 using the '--datadir' option below.
655 schema Initialize only the database schema
656 To use a local or supplementary datafile, specify it
657 using the '--datadir' option below.
659 --datafile /path/to/datafile
660 --datadir /path/to/ Used to specify a path to find the local
661 database schema and acls to be installed.
665 --dba-password dba's password
666 --prompt-for-dba-password Ask for the database administrator's password interactively