#!@PERL@ -w # BEGIN BPS TAGGED BLOCK {{{ # # COPYRIGHT: # # This software is Copyright (c) 1996-2005 Best Practical Solutions, LLC # # # (Except where explicitly superseded by other copyright notices) # # # LICENSE: # # This work is made available to you under the terms of Version 2 of # the GNU General Public License. A copy of that license should have # been provided with this software, but in any event can be snarfed # from www.gnu.org. # # This work is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # # CONTRIBUTION SUBMISSION POLICY: # # (The following paragraph is not intended to limit the rights granted # to you to modify and distribute this software under the terms of # the GNU General Public License and is only of importance to you if # you choose to contribute your changes and enhancements to the # community by submitting them to Best Practical Solutions, LLC.) # # By intentionally submitting any modifications, corrections or # derivatives to this work, or any other work intended for use with # Request Tracker, to Best Practical Solutions, LLC, you confirm that # you are the copyright holder for those contributions and you grant # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable, # royalty-free, perpetual, license to use, copy, create derivative # works based on those contributions, and sublicense and distribute # those contributions and any derivatives thereof. # # END BPS TAGGED BLOCK }}} use strict; use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item); use vars qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips); use lib "@RT_LIB_PATH@"; #This drags in RT's config.pm # We do it in a begin block because RT::Handle needs to know the type to do its # inheritance use RT; use Carp; use RT::User; use RT::CurrentUser; use RT::Template; use RT::ScripAction; use RT::ACE; use RT::Group; use RT::User; use RT::Queue; use RT::ScripCondition; use RT::CustomField; use RT::Scrip; RT::LoadConfig(); use Term::ReadKey; use Getopt::Long; my %args; GetOptions( \%args, 'prompt-for-dba-password', 'force', 'debug', 'action=s', 'dba=s', 'dba-password=s', 'datafile=s', 'datadir=s' ); $| = 1; #unbuffer that output. require RT::Handle; my $Handle = RT::Handle->new($RT::DatabaseType); $Handle->BuildDSN; my $dbh; if ( $args{'prompt-for-dba-password'} ) { $args{'dba-password'} = get_dba_password(); chomp( $args{'dba-password'} ); } unless ( $args{'action'} ) { help(); die; } if ( $args{'action'} eq 'init' ) { $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; print "Now creating a database for RT.\n"; if ($RT::DatabaseType ne 'Oracle' || $args{'dba'} ne $RT::DatabaseUser) { create_db(); } else { print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n"; } if ($RT::DatabaseType eq "mysql") { # Check which version we're running my ($version) = $dbh->selectrow_hashref("show variables like 'version'")->{Value} =~ /^(\d\.\d+)/; print "*** Warning: RT is unsupported on MySQL versions before 4.0.x\n" if $version < 4; # MySQL must have InnoDB support my $innodb = $dbh->selectrow_hashref("show variables like 'have_innodb'")->{Value}; if ($innodb eq "NO") { die "RT requires that MySQL be compiled with InnoDB table support.\n". "See http://dev.mysql.com/doc/mysql/en/InnoDB.html\n"; } elsif ($innodb eq "DISABLED") { die "RT requires that MySQL InnoDB table support be enabled.\n". ($version < 4 ? "Add 'innodb_data_file_path=ibdata1:10M:autoextend' to the [mysqld] section of my.cnf\n" : "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n"); } } # SQLite can't deal with the disconnect/reconnect unless ($RT::DatabaseType eq 'SQLite') { $dbh->disconnect; $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr; } print "Now populating database schema.\n"; insert_schema(); print "Now inserting database ACLs\n"; insert_acl() unless ($RT::DatabaseType eq 'Oracle'); print "Now inserting RT core system objects\n"; insert_initial_data(); print "Now inserting RT data\n"; insert_data( $RT::EtcPath . "/initialdata" ); } elsif ( $args{'action'} eq 'drop' ) { unless ( $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) ) { warn $DBI::errstr; warn "Database doesn't appear to exist. Aborting database drop."; exit(0); } drop_db(); } elsif ( $args{'action'} eq 'insert' ) { insert_data( $args{'datafile'} || ($args{'datadir'}."/content")); } elsif ($args{'action'} eq 'acl') { $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; insert_acl($args{'datadir'}); } elsif ($args{'action'} eq 'schema') { $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr"; insert_schema($args{'datadir'}); } else { print STDERR '$0 called with an invalid --action parameter'; exit(-1); } # {{{ sub insert_schema sub insert_schema { my $base_path = (shift || $RT::EtcPath); my (@schema); print "Creating database schema.\n"; if ( -f $base_path . "/schema." . $RT::DatabaseType ) { no warnings 'unopened'; open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType ); open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType ); my $statement = ""; foreach my $line (, ($_ = ';;'), ) { $line =~ s/\#.*//g; $line =~ s/--.*//g; $statement .= $line; if ( $line =~ /;(\s*)$/ ) { $statement =~ s/;(\s*)$//g; push @schema, $statement; $statement = ""; } } local $SIG{__WARN__} = sub {}; my $is_local = 0; # local/etc/schema needs to be nonfatal. $dbh->begin_work or die $dbh->errstr; foreach my $statement (@schema) { if ($statement =~ /^\s*;$/) { $is_local = 1; next; } print STDERR "SQL: $statement\n" if defined $args{'debug'}; my $sth = $dbh->prepare($statement) or die $dbh->errstr; unless ( $sth->execute or $is_local ) { die "Problem with statement:\n $statement\n" . $sth->errstr; } } $dbh->commit or die $dbh->errstr; } else { die "Couldn't find schema file for " . $RT::DatabaseType . "\n"; } print "Done setting up database schema.\n"; } # }}} # {{{ sub drop_db sub drop_db { if ( $RT::DatabaseType eq 'Oracle' ) { print <do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr; } # }}} # {{{ sub create_db sub create_db { print "Creating $RT::DatabaseType database $RT::DatabaseName.\n"; if ( $RT::DatabaseType eq 'SQLite' ) { return; } elsif ( $RT::DatabaseType eq 'Pg' ) { $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'"); if ($DBI::errstr) { $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr; } } elsif ($RT::DatabaseType eq 'Oracle') { insert_acl(); } elsif ( $RT::DatabaseType eq 'Informix' ) { $ENV{DB_LOCALE} = 'en_us.utf8'; $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG"); } else { $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr; } } # }}} sub get_dba_password { print "In order to create or update your RT database,"; print "this script needs to connect to your " . $RT::DatabaseType . " instance on " . $RT::DatabaseHost . " as " . $args{'dba'} . ".\n"; print "Please specify that user's database password below. If the user has no database\n"; print "password, just press return.\n\n"; print "Password: "; ReadMode('noecho'); my $password = ReadLine(0); ReadMode('normal'); print "\n"; return ($password); } # {{{ sub _yesno sub _yesno { print "Proceed [y/N]:"; my $x = scalar(); $x =~ /^y/i; } # }}} # {{{ insert_acls sub insert_acl { my $base_path = (shift || $RT::EtcPath); if ( $RT::DatabaseType =~ /^oracle$/i ) { do $base_path . "/acl.Oracle" || die "Couldn't find ACLS for Oracle\n" . $@; } elsif ( $RT::DatabaseType =~ /^pg$/i ) { do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@; } elsif ( $RT::DatabaseType =~ /^mysql$/i ) { do $base_path . "/acl.mysql" || die "Couldn't find ACLS for mysql in $base_path\n" . $@; } elsif ( $RT::DatabaseType =~ /^Sybase$/i ) { do $base_path . "/acl.Sybase" || die "Couldn't find ACLS for Sybase in $base_path\n" . $@; } elsif ( $RT::DatabaseType =~ /^informix$/i ) { do $base_path . "/acl.Informix" || die "Couldn't find ACLS for Informix in $base_path\n" . $@; } elsif ( $RT::DatabaseType =~ /^SQLite$/i ) { return; } else { die "Unknown RT database type"; } my @acl = acl($dbh); foreach my $statement (@acl) { print STDERR $statement if $args{'debug'}; my $sth = $dbh->prepare($statement) or die $dbh->errstr; unless ( $sth->execute ) { die "Problem with statement:\n $statement\n" . $sth->errstr; } } print "Done setting up database ACLs.\n"; } # }}} =head2 get_system_dsn Returns a dsn suitable for database creates and drops and user creates and drops =cut sub get_system_dsn { my $dsn = $Handle->DSN; #with mysql, you want to connect sans database to funge things if ( $RT::DatabaseType eq 'mysql' ) { $dsn =~ s/dbname=$RT::DatabaseName//; # with postgres, you want to connect to database1 } elsif ( $RT::DatabaseType eq 'Pg' ) { $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/; } elsif ( $RT::DatabaseType eq 'Informix' ) { # with Informix, you want to connect sans database: $dsn =~ s/Informix:$RT::DatabaseName/Informix:/; } return $dsn; } sub insert_initial_data { RT::InitLogging(); #connect to the db, for actual RT work require RT::Handle; $RT::Handle = RT::Handle->new(); $RT::Handle->Connect(); #Put together a current user object so we can create a User object my $CurrentUser = new RT::CurrentUser(); print "Checking for existing system user..."; my $test_user = RT::User->new($CurrentUser); $test_user->Load('RT_System'); if ( $test_user->id ) { print "found!\n\nYou appear to have a functional RT database.\n" . "Exiting, so as not to clobber your existing data.\n"; exit(-1); } else { print "not found. This appears to be a new installation.\n"; } print "Creating system user..."; my $RT_System = new RT::User($CurrentUser); my ( $val, $msg ) = $RT_System->_BootstrapCreate( Name => 'RT_System', RealName => 'The RT System itself', Comments => 'Do not delete or modify this user. It is integral to RT\'s internal database structures', Creator => '1', LastUpdatedBy => '1' ); unless ($val) { print "$msg\n"; exit(1); } print "done.\n"; $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite'); } # load some sort of data into the database sub insert_data { my $datafile = shift; #Connect to the database and get RT::SystemUser and RT::Nobody loaded RT::Init; my $CurrentUser = RT::CurrentUser->new(); $CurrentUser->LoadByName('RT_System'); if ( $datafile eq $RT::EtcPath . "/initialdata" ) { print "Creating Superuser ACL..."; my $superuser_ace = RT::ACE->new($CurrentUser); $superuser_ace->_BootstrapCreate( PrincipalId => ACLEquivGroupId( $CurrentUser->Id ), PrincipalType => 'Group', RightName => 'SuperUser', ObjectType => 'RT::System', ObjectId => '1' ); print "done.\n"; } # Slurp in stuff to insert from the datafile. Possible things to go in here:- # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates require $datafile || die "Couldn't find initial data for import\n" . $@; if (@Groups) { print "Creating groups..."; foreach $item (@Groups) { my $new_entry = RT::Group->new($CurrentUser); my ( $return, $msg ) = $new_entry->_Create(%$item); print "(Error: $msg)" unless ($return); print $return. "."; } print "done.\n"; } if (@Users) { print "Creating users..."; foreach $item (@Users) { my $new_entry = new RT::User($CurrentUser); my ( $return, $msg ) = $new_entry->Create(%$item); print "(Error: $msg)" unless ($return); print $return. "."; } print "done.\n"; } if (@Queues) { print "Creating queues..."; for $item (@Queues) { my $new_entry = new RT::Queue($CurrentUser); my ( $return, $msg ) = $new_entry->Create(%$item); print "(Error: $msg)" unless ($return); print $return. "."; } print "done.\n"; } if (@ACL) { print "Creating ACL..."; for my $item (@ACL) { my ($princ, $object); # Global rights or Queue rights? if ($item->{'Queue'}) { $object = RT::Queue->new($CurrentUser); $object->Load( $item->{'Queue'} ); } else { $object = $RT::System; } # Group rights or user rights? if ($item->{'GroupDomain'}) { $princ = RT::Group->new($CurrentUser); if ($item->{'GroupDomain'} eq 'UserDefined') { $princ->LoadUserDefinedGroup( $item->{'GroupId'} ); } elsif ($item->{'GroupDomain'} eq 'SystemInternal') { $princ->LoadSystemInternalGroup( $item->{'GroupType'} ); } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') { $princ->LoadSystemRoleGroup( $item->{'GroupType'} ); } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' && $item->{'Queue'}) { $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'}, Queue => $object->id); } else { $princ->Load( $item->{'GroupId'} ); } } else { $princ = RT::User->new($CurrentUser); $princ->Load( $item->{'UserId'} ); } # Grant it my ( $return, $msg ) = $princ->PrincipalObj->GrantRight( Right => $item->{'Right'}, Object => $object ); if ($return) { print $return. "."; } else { print $msg . "."; } } print "done.\n"; } if (@CustomFields) { print "Creating custom fields..."; for $item (@CustomFields) { my $new_entry = new RT::CustomField($CurrentUser); my $values = $item->{'Values'}; delete $item->{'Values'}; my $q = $item->{'Queue'}; my $q_obj = RT::Queue->new($CurrentUser); $q_obj->Load($q); if ( $q_obj->Id ) { $item->{'Queue'} = $q_obj->Id; } elsif ( $q == 0 ) { $item->{'Queue'} = 0; } else { print "(Error: Could not find queue " . $q . ")\n" unless ( $q_obj->Id ); next; } my ( $return, $msg ) = $new_entry->Create(%$item); foreach my $value ( @{$values} ) { my ( $eval, $emsg ) = $new_entry->AddValue(%$value); print "(Error: $emsg)\n" unless ($eval); } print "(Error: $msg)\n" unless ($return); print $return. "."; } print "done.\n"; } if (@ScripActions) { print "Creating ScripActions..."; for $item (@ScripActions) { my $new_entry = RT::ScripAction->new($CurrentUser); my $return = $new_entry->Create(%$item); print $return. "."; } print "done.\n"; } if (@ScripConditions) { print "Creating ScripConditions..."; for $item (@ScripConditions) { my $new_entry = RT::ScripCondition->new($CurrentUser); my $return = $new_entry->Create(%$item); print $return. "."; } print "done.\n"; } if (@Templates) { print "Creating templates..."; for $item (@Templates) { my $new_entry = new RT::Template($CurrentUser); my $return = $new_entry->Create(%$item); print $return. "."; } print "done.\n"; } if (@Scrips) { print "Creating scrips..."; for $item (@Scrips) { my $new_entry = new RT::Scrip($CurrentUser); my ( $return, $msg ) = $new_entry->Create(%$item); if ($return) { print $return. "."; } else { print "(Error: $msg)\n"; } } print "done.\n"; } $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite'); print "Done setting up database content.\n"; } =head2 ACLEquivGroupId Given a userid, return that user's acl equivalence group =cut sub ACLEquivGroupId { my $username = shift; my $user = RT::User->new($RT::SystemUser); $user->Load($username); my $equiv_group = RT::Group->new($RT::SystemUser); $equiv_group->LoadACLEquivalenceGroup($user); return ( $equiv_group->Id ); } sub help { print <