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_initial' ) {
155 insert_initial_data();
157 elsif ( $args{'action'} eq 'insert' ) {
158 insert_data( $args{'datafile'} || ($args{'datadir'}."/content"));
160 elsif ($args{'action'} eq 'acl') {
161 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
162 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
163 insert_acl($args{'datadir'});
165 elsif ($args{'action'} eq 'schema') {
166 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
167 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
168 insert_schema($args{'datadir'});
172 print STDERR '$0 called with an invalid --action parameter';
176 # {{{ sub insert_schema
178 my $base_path = (shift || $RT::EtcPath);
180 print "Creating database schema.\n";
182 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
183 no warnings 'unopened';
185 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
186 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
189 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
193 if ( $line =~ /;(\s*)$/ ) {
194 $statement =~ s/;(\s*)$//g;
195 push @schema, $statement;
200 local $SIG{__WARN__} = sub {};
201 my $is_local = 0; # local/etc/schema needs to be nonfatal.
202 $dbh->begin_work or die $dbh->errstr;
203 foreach my $statement (@schema) {
204 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
205 print STDERR "SQL: $statement\n" if defined $args{'debug'};
206 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
207 unless ( $sth->execute or $is_local ) {
208 die "Problem with statement:\n $statement\n" . $sth->errstr;
211 $dbh->commit or die $dbh->errstr;
215 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
217 print "Done setting up database schema.\n";
225 if ( $RT::DatabaseType eq 'Oracle' ) {
228 To delete the tables and sequences of the RT Oracle database by running
235 unless ( $args{'force'} ) {
238 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
239 WARNING: This will erase all data in $RT::DatabaseName.
242 exit unless _yesno();
246 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
248 if ( $RT::DatabaseType eq 'SQLite' ) {
249 unlink $RT::DatabaseName or warn $!;
252 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
259 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
260 if ( $RT::DatabaseType eq 'SQLite' ) {
263 elsif ( $RT::DatabaseType eq 'Pg' ) {
264 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
266 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
269 elsif ($RT::DatabaseType eq 'Oracle') {
272 elsif ( $RT::DatabaseType eq 'Informix' ) {
273 $ENV{DB_LOCALE} = 'en_us.utf8';
274 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
277 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
283 sub get_dba_password {
284 print "In order to create or update your RT database,";
285 print "this script needs to connect to your "
288 . $RT::DatabaseHost . " as "
289 . $args{'dba'} . ".\n";
290 print "Please specify that user's database password below. If the user has no database\n";
291 print "password, just press return.\n\n";
294 my $password = ReadLine(0);
302 print "Proceed [y/N]:";
303 my $x = scalar(<STDIN>);
312 my $base_path = (shift || $RT::EtcPath);
314 if ( $RT::DatabaseType =~ /^oracle$/i ) {
315 do $base_path . "/acl.Oracle"
316 || die "Couldn't find ACLS for Oracle\n" . $@;
318 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
319 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
321 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
322 do $base_path . "/acl.mysql"
323 || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
325 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
326 do $base_path . "/acl.Sybase"
327 || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
329 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
330 do $base_path . "/acl.Informix"
331 || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
333 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
337 die "Unknown RT database type";
341 foreach my $statement (@acl) {
342 print STDERR $statement if $args{'debug'};
343 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
344 unless ( $sth->execute ) {
345 die "Problem with statement:\n $statement\n" . $sth->errstr;
348 print "Done setting up database ACLs.\n";
353 =head2 get_system_dsn
355 Returns a dsn suitable for database creates and drops
356 and user creates and drops
362 my $dsn = $Handle->DSN;
364 #with mysql, you want to connect sans database to funge things
365 if ( $RT::DatabaseType eq 'mysql' ) {
366 $dsn =~ s/dbname=$RT::DatabaseName//;
368 # with postgres, you want to connect to database1
370 elsif ( $RT::DatabaseType eq 'Pg' ) {
371 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
373 elsif ( $RT::DatabaseType eq 'Informix' ) {
374 # with Informix, you want to connect sans database:
375 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
380 sub insert_initial_data {
384 #connect to the db, for actual RT work
386 $RT::Handle = RT::Handle->new();
387 $RT::Handle->Connect();
389 #Put together a current user object so we can create a User object
390 my $CurrentUser = new RT::CurrentUser();
392 print "Checking for existing system user...";
393 my $test_user = RT::User->new($CurrentUser);
394 $test_user->Load('RT_System');
395 if ( $test_user->id ) {
396 print "found!\n\nYou appear to have a functional RT database.\n"
397 . "Exiting, so as not to clobber your existing data.\n";
402 print "not found. This appears to be a new installation.\n";
405 print "Creating system user...";
406 my $RT_System = new RT::User($CurrentUser);
408 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
410 RealName => 'The RT System itself',
412 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
414 LastUpdatedBy => '1' );
421 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
425 # load some sort of data into the database
428 my $datafile = shift;
430 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
433 my $CurrentUser = RT::CurrentUser->new();
434 $CurrentUser->LoadByName('RT_System');
436 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
438 print "Creating Superuser ACL...";
440 my $superuser_ace = RT::ACE->new($CurrentUser);
441 $superuser_ace->_BootstrapCreate(
442 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
443 PrincipalType => 'Group',
444 RightName => 'SuperUser',
445 ObjectType => 'RT::System',
451 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
452 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
455 || die "Couldn't find initial data for import\n" . $@;
458 print "Creating groups...";
459 foreach $item (@Groups) {
460 my $new_entry = RT::Group->new($CurrentUser);
461 my ( $return, $msg ) = $new_entry->_Create(%$item);
462 print "(Error: $msg)" unless ($return);
468 print "Creating users...";
469 foreach $item (@Users) {
470 my $new_entry = new RT::User($CurrentUser);
471 my ( $return, $msg ) = $new_entry->Create(%$item);
472 print "(Error: $msg)" unless ($return);
478 print "Creating queues...";
479 for $item (@Queues) {
480 my $new_entry = new RT::Queue($CurrentUser);
481 my ( $return, $msg ) = $new_entry->Create(%$item);
482 print "(Error: $msg)" unless ($return);
488 print "Creating ACL...";
489 for my $item (@ACL) {
491 my ($princ, $object);
493 # Global rights or Queue rights?
494 if ($item->{'Queue'}) {
495 $object = RT::Queue->new($CurrentUser);
496 $object->Load( $item->{'Queue'} );
498 $object = $RT::System;
501 # Group rights or user rights?
502 if ($item->{'GroupDomain'}) {
503 $princ = RT::Group->new($CurrentUser);
504 if ($item->{'GroupDomain'} eq 'UserDefined') {
505 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
506 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
507 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
508 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
509 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
510 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
512 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
513 Queue => $object->id);
515 $princ->Load( $item->{'GroupId'} );
518 $princ = RT::User->new($CurrentUser);
519 $princ->Load( $item->{'UserId'} );
523 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
524 Right => $item->{'Right'},
539 print "Creating custom fields...";
540 for $item (@CustomFields) {
541 my $new_entry = new RT::CustomField($CurrentUser);
542 my $values = $item->{'Values'};
543 delete $item->{'Values'};
544 my $q = $item->{'Queue'};
545 my $q_obj = RT::Queue->new($CurrentUser);
548 $item->{'Queue'} = $q_obj->Id;
551 $item->{'Queue'} = 0;
554 print "(Error: Could not find queue " . $q . ")\n"
555 unless ( $q_obj->Id );
558 my ( $return, $msg ) = $new_entry->Create(%$item);
560 foreach my $value ( @{$values} ) {
561 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
562 print "(Error: $emsg)\n" unless ($eval);
565 print "(Error: $msg)\n" unless ($return);
573 print "Creating ScripActions...";
575 for $item (@ScripActions) {
576 my $new_entry = RT::ScripAction->new($CurrentUser);
577 my $return = $new_entry->Create(%$item);
584 if (@ScripConditions) {
585 print "Creating ScripConditions...";
587 for $item (@ScripConditions) {
588 my $new_entry = RT::ScripCondition->new($CurrentUser);
589 my $return = $new_entry->Create(%$item);
597 print "Creating templates...";
599 for $item (@Templates) {
600 my $new_entry = new RT::Template($CurrentUser);
601 my $return = $new_entry->Create(%$item);
607 print "Creating scrips...";
609 for $item (@Scrips) {
610 my $new_entry = new RT::Scrip($CurrentUser);
611 my ( $return, $msg ) = $new_entry->Create(%$item);
616 print "(Error: $msg)\n";
621 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
622 print "Done setting up database content.\n";
625 =head2 ACLEquivGroupId
627 Given a userid, return that user's acl equivalence group
631 sub ACLEquivGroupId {
632 my $username = shift;
633 my $user = RT::User->new($RT::SystemUser);
634 $user->Load($username);
635 my $equiv_group = RT::Group->new($RT::SystemUser);
636 $equiv_group->LoadACLEquivalenceGroup($user);
637 return ( $equiv_group->Id );
644 $0: Set up RT's database
646 --action init Initialize the database
647 drop Drop the database.
648 This will ERASE ALL YOUR DATA
650 Insert RT's core system objects
651 insert Insert data into RT's database.
652 By default, will use RT's installation data.
653 To use a local or supplementary datafile, specify it
654 using the '--datafile' option below.
656 acl Initialize only the database ACLs
657 To use a local or supplementary datafile, specify it
658 using the '--datadir' option below.
660 schema Initialize only the database schema
661 To use a local or supplementary datafile, specify it
662 using the '--datadir' option below.
664 --datafile /path/to/datafile
665 --datadir /path/to/ Used to specify a path to find the local
666 database schema and acls to be installed.
670 --dba-password dba's password
671 --prompt-for-dba-password Ask for the database administrator's password interactively