4 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
6 # (Except where explictly superceded by other copyright notices)
8 # This work is made available to you under the terms of Version 2 of
9 # the GNU General Public License. A copy of that license should have
10 # been provided with this software, but in any event can be snarfed
13 # This work is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 # General Public License for more details.
18 # Unless otherwise specified, all modifications, corrections or
19 # extensions to this work which alter its source code become the
20 # property of Best Practical Solutions, LLC when submitted for
21 # inclusion in the work.
27 use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
29 qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
31 use lib "/opt/rt3/lib";
33 #This drags in RT's config.pm
34 # We do it in a begin block because RT::Handle needs to know the type to do its
46 use RT::ScripCondition;
58 'prompt-for-dba-password', 'force', 'debug',
59 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
63 $| = 1; #unbuffer that output.
66 my $Handle = RT::Handle->new($RT::DatabaseType);
70 if ( $args{'prompt-for-dba-password'} ) {
71 $args{'dba-password'} = get_dba_password();
72 chomp( $args{'dba-password'} );
75 unless ( $args{'action'} ) {
79 if ( $args{'action'} eq 'init' ) {
80 $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
81 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
82 print "Now creating a database for RT.\n";
83 if ($RT::DatabaseType ne 'Oracle' ||
84 $args{'dba'} ne $RT::DatabaseUser) {
87 print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
91 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
94 print "Now populating database schema.\n";
96 print "Now inserting database ACLs\n";
97 insert_acl() unless ($RT::DatabaseType eq 'Oracle');
98 print "Now inserting RT core system objects\n";
99 insert_initial_data();
100 print "Now inserting RT data\n";
101 insert_data( $RT::EtcPath . "/initialdata" );
103 elsif ( $args{'action'} eq 'drop' ) {
105 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
108 warn "Database doesn't appear to exist. Aborting database drop.";
113 elsif ( $args{'action'} eq 'insert_initial' ) {
114 insert_initial_data();
116 elsif ( $args{'action'} eq 'insert' ) {
117 insert_data( $args{'datafile'} );
119 elsif ($args{'action'} eq 'acl') {
120 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
121 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
122 insert_acl($args{'datadir'});
124 elsif ($args{'action'} eq 'schema') {
125 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
126 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
127 insert_schema($args{'datadir'});
131 print STDERR '$0 called with an invalid --action parameter';
135 # {{{ sub insert_schema
137 my $base_path = (shift || $RT::EtcPath);
139 print "Creating database schema.\n";
141 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
142 no warnings 'unopened';
144 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
145 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
148 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
152 if ( $line =~ /;(\s*)$/ ) {
153 $statement =~ s/;(\s*)$//g;
154 push @schema, $statement;
159 local $SIG{__WARN__} = sub {};
160 my $is_local = 0; # local/etc/schema needs to be nonfatal.
161 foreach my $statement (@schema) {
162 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
163 print STDERR "SQL: $statement\n" if defined $args{'debug'};
164 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
165 unless ( $sth->execute or $is_local ) {
166 die "Problem with statement:\n $statement\n" . $sth->errstr;
172 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
174 print "schema sucessfully inserted\n";
182 return if ( $RT::DatabaseType eq 'SQLite' );
183 if ( $RT::DatabaseType eq 'Oracle' ) {
186 To delete the tables and sequences of the RT Oracle database by running
193 unless ( $args{'force'} ) {
196 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
197 WARNING: This will erase all data in $RT::DatabaseName.
200 exit unless _yesno();
204 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
206 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
213 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
214 if ( $RT::DatabaseType eq 'SQLite' ) {
217 elsif ( $RT::DatabaseType eq 'Pg' ) {
218 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
220 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
223 elsif ($RT::DatabaseType eq 'Oracle') {
226 elsif ( $RT::DatabaseType eq 'Informix' ) {
227 $ENV{DB_LOCALE} = 'en_us.utf8';
228 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
231 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
237 sub get_dba_password {
239 "In order to create a new database and grant RT access to that database,\n";
240 print "this script needs to connect to your "
243 . $RT::DatabaseHost . " as "
244 . $args{'dba'} . ".\n";
246 "Please specify that user's database password below. If the user has no database\n";
247 print "password, just press return.\n\n";
250 my $password = ReadLine(0);
257 print "Proceed [y/N]:";
258 my $x = scalar(<STDIN>);
267 my $base_path = (shift || $RT::EtcPath);
269 if ( $RT::DatabaseType =~ /^oracle$/i ) {
270 do $base_path . "/acl.Oracle"
271 || die "Couldn't find ACLS for Oracle\n" . $@;
273 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
274 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
276 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
277 do $base_path . "/acl.mysql"
278 || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
280 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
281 do $base_path . "/acl.Informix"
282 || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
284 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
288 die "Unknown RT database type";
292 foreach my $statement (@acl) {
293 print STDERR $statement if $args{'debug'};
294 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
295 unless ( $sth->execute ) {
296 die "Problem with statement:\n $statement\n" . $sth->errstr;
303 =head2 get_system_dsn
305 Returns a dsn suitable for database creates and drops
306 and user creates and drops
312 my $dsn = $Handle->DSN;
314 #with mysql, you want to connect sans database to funge things
315 if ( $RT::DatabaseType eq 'mysql' ) {
316 $dsn =~ s/dbname=$RT::DatabaseName//;
318 # with postgres, you want to connect to database1
320 elsif ( $RT::DatabaseType eq 'Pg' ) {
321 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
323 elsif ( $RT::DatabaseType eq 'Informix' ) {
324 # with Informix, you want to connect sans database:
325 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
330 sub insert_initial_data {
334 #connect to the db, for actual RT work
336 $RT::Handle = RT::Handle->new();
337 $RT::Handle->Connect();
339 #Put together a current user object so we can create a User object
340 my $CurrentUser = new RT::CurrentUser();
342 print "Checking for existing system user ($CurrentUser)...";
343 my $test_user = RT::User->new($CurrentUser);
344 $test_user->Load('RT_System');
345 if ( $test_user->id ) {
346 print "found!\n\nYou appear to have a functional RT database.\n"
347 . "Exiting, so as not to clobber your existing data.\n";
352 print "not found. This appears to be a new installation.\n";
355 print "Creating system user...";
356 my $RT_System = new RT::User($CurrentUser);
358 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
360 RealName => 'The RT System itself',
362 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
370 $RT::Handle->Disconnect();
374 # load some sort of data into the database
377 my $datafile = shift;
379 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
382 my $CurrentUser = RT::CurrentUser->new();
383 $CurrentUser->LoadByName('RT_System');
385 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
387 print "Creating Superuser ACL...";
389 my $superuser_ace = RT::ACE->new($CurrentUser);
390 $superuser_ace->_BootstrapCreate(
391 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
392 PrincipalType => 'Group',
393 RightName => 'SuperUser',
394 ObjectType => 'RT::System',
399 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
400 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
403 || die "Couldn't find initial data for import\n" . $@;
406 print "Creating groups...";
407 foreach $item (@Groups) {
408 my $new_entry = RT::Group->new($CurrentUser);
409 my ( $return, $msg ) = $new_entry->_Create(%$item);
410 print "(Error: $msg)" unless ($return);
416 print "Creating users...";
417 foreach $item (@Users) {
418 my $new_entry = new RT::User($CurrentUser);
419 my ( $return, $msg ) = $new_entry->Create(%$item);
420 print "(Error: $msg)" unless ($return);
426 print "Creating queues...";
427 for $item (@Queues) {
428 my $new_entry = new RT::Queue($CurrentUser);
429 my ( $return, $msg ) = $new_entry->Create(%$item);
430 print "(Error: $msg)" unless ($return);
436 print "Creating ACL...";
437 for my $item (@ACL) {
439 my ($princ, $object);
441 # Global rights or Queue rights?
442 if ($item->{'Queue'}) {
443 $object = RT::Queue->new($CurrentUser);
444 $object->Load( $item->{'Queue'} );
446 $object = $RT::System;
449 # Group rights or user rights?
450 if ($item->{'GroupDomain'}) {
451 $princ = RT::Group->new($CurrentUser);
452 if ($item->{'GroupDomain'} eq 'UserDefined') {
453 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
454 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
455 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
456 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
458 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
459 Queue => $object->id);
461 $princ->Load( $item->{'GroupId'} );
464 $princ = RT::User->new($CurrentUser);
465 $princ->Load( $item->{'UserId'} );
469 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
470 Right => $item->{'Right'},
485 print "Creating custom fields...";
486 for $item (@CustomFields) {
487 my $new_entry = new RT::CustomField($CurrentUser);
488 my $values = $item->{'Values'};
489 delete $item->{'Values'};
490 my $q = $item->{'Queue'};
491 my $q_obj = RT::Queue->new($CurrentUser);
494 $item->{'Queue'} = $q_obj->Id;
497 $item->{'Queue'} = 0;
500 print "(Error: Could not find queue " . $q . ")\n"
501 unless ( $q_obj->Id );
504 my ( $return, $msg ) = $new_entry->Create(%$item);
506 foreach my $value ( @{$values} ) {
507 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
508 print "(Error: $emsg)\n" unless ($eval);
511 print "(Error: $msg)\n" unless ($return);
519 print "Creating ScripActions...";
521 for $item (@ScripActions) {
522 my $new_entry = RT::ScripAction->new($CurrentUser);
523 my $return = $new_entry->Create(%$item);
530 if (@ScripConditions) {
531 print "Creating ScripConditions...";
533 for $item (@ScripConditions) {
534 my $new_entry = RT::ScripCondition->new($CurrentUser);
535 my $return = $new_entry->Create(%$item);
543 print "Creating templates...";
545 for $item (@Templates) {
546 my $new_entry = new RT::Template($CurrentUser);
547 my $return = $new_entry->Create(%$item);
553 print "Creating scrips...";
555 for $item (@Scrips) {
556 my $new_entry = new RT::Scrip($CurrentUser);
557 my ( $return, $msg ) = $new_entry->Create(%$item);
562 print "(Error: $msg)\n";
567 $RT::Handle->Disconnect();
571 =head2 ACLEquivGroupId
573 Given a userid, return that user's acl equivalence group
577 sub ACLEquivGroupId {
578 my $username = shift;
579 my $user = RT::User->new($RT::SystemUser);
580 $user->Load($username);
581 my $equiv_group = RT::Group->new($RT::SystemUser);
582 $equiv_group->LoadACLEquivalenceGroup($user);
583 return ( $equiv_group->Id );
590 $0: Set up RT's database
592 --action init Initialize the database
593 drop Drop the database.
594 This will ERASE ALL YOUR DATA
596 Insert RT's core system objects
598 Insert RT's core system objects
599 insert Insert data into RT's database.
600 By default, will use RT's installation data.
601 To use a local or supplementary datafile, specify it
602 using the '--datafile' option below.
604 acl Initialize only the database ACLs
605 To use a local or supplementary datafile, specify it
606 using the '--datadir' option below.
608 schema Initialize only the database schema
609 To use a local or supplementary datafile, specify it
610 using the '--datadir' option below.
612 --datafile /path/to/datafile
613 --datadir /path/to/ Used to specify a path to find the local
614 database schema and acls to be installed.
618 --dba-password dba's password
619 --prompt-for-dba-password Ask for the database administrator's password interactively