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 "@RT_LIB_PATH@";
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";
86 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
89 print "Now populating database schema.\n";
91 print "Now inserting database ACLs\n";
93 print "Now inserting RT core system objects\n";
94 insert_initial_data();
95 print "Now inserting RT data\n";
96 insert_data( $RT::EtcPath . "/initialdata" );
98 elsif ( $args{'action'} eq 'drop' ) {
100 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
103 warn "Database doesn't appear to exist. Aborting database drop.";
108 elsif ( $args{'action'} eq 'insert' ) {
109 insert_data( $args{'datafile'} );
111 elsif ($args{'action'} eq 'acl') {
112 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
113 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
114 insert_acl($args{'datadir'});
116 elsif ($args{'action'} eq 'schema') {
117 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
118 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
119 insert_schema($args{'datadir'});
123 print STDERR '$0 called with an invalid --action parameter';
127 # {{{ sub insert_schema
129 my $base_path = (shift || $RT::EtcPath);
131 print "Creating database schema.\n";
133 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
134 no warnings 'unopened';
136 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
137 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
140 foreach my $line (<SCHEMA>, <SCHEMA_LOCAL>) {
143 if ( $line =~ /;(\s*)$/ ) {
144 $statement =~ s/;(\s*)$//g;
145 push @schema, $statement;
150 foreach my $statement (@schema) {
151 print STDERR $statement if $args{'debug'};
152 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
153 unless ( $sth->execute ) {
154 die "Problem with statement:\n $statement\n" . $sth->errstr;
160 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
162 print "schema sucessfully inserted\n";
170 return if ( $RT::DatabaseType eq 'SQLite' );
171 unless ( $args{'force'} ) {
174 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
175 WARNING: This will erase all data in $RT::DatabaseName.
178 exit unless _yesno();
182 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
184 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
191 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
192 if ( $RT::DatabaseType eq 'SQLite' ) {
195 elsif ( $RT::DatabaseType eq 'Pg' ) {
196 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
198 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
202 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
208 sub get_dba_password {
210 "In order to create a new database and grant RT access to that database,\n";
211 print "this script needs to connect to your "
214 . $RT::DatabaseHost . " as "
215 . $args{'dba'} . ".\n";
217 "Please specify that user's database password below. If the user has no database\n";
218 print "password, just press return.\n\n";
221 my $password = ReadLine(0);
228 print "Proceed [y/N]:";
229 my $x = scalar(<STDIN>);
238 my $base_path = (shift || $RT::EtcPath);
240 if ( $RT::DatabaseType =~ /^oracle$/i ) {
241 do $base_path . "/acl.Oracle"
242 || die "Couldn't find ACLS for Oracle\n" . $@;
244 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
245 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
247 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
248 do $base_path . "/acl.mysql"
249 || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
251 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
255 die "Unknown RT database type";
259 foreach my $statement (@acl) {
260 print STDERR $statement if $args{'debug'};
261 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
262 unless ( $sth->execute ) {
263 die "Problem with statement:\n $statement\n" . $sth->errstr;
270 =head2 get_system_dsn
272 Returns a dsn suitable for database creates and drops
273 and user creates and drops
279 my $dsn = $Handle->DSN;
281 #with mysql, you want to connect sans database to funge things
282 if ( $RT::DatabaseType eq 'mysql' ) {
283 $dsn =~ s/dbname=$RT::DatabaseName//;
285 # with postgres, you want to connect to database1
287 elsif ( $RT::DatabaseType eq 'Pg' ) {
288 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
293 sub insert_initial_data {
297 #connect to the db, for actual RT work
299 $RT::Handle = RT::Handle->new();
300 $RT::Handle->Connect();
302 #Put together a current user object so we can create a User object
303 my $CurrentUser = new RT::CurrentUser();
305 print "Checking for existing system user...";
306 my $test_user = RT::User->new($CurrentUser);
307 $test_user->Load('RT_System');
308 if ( $test_user->id ) {
309 print "found!\n\nYou appear to have a functional RT database.\n"
310 . "Exiting, so as not to clobber your existing data.\n";
315 print "not found. This appears to be a new installation.\n";
318 print "Creating system user...";
319 my $RT_System = new RT::User($CurrentUser);
321 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
323 RealName => 'The RT System itself',
325 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
333 $RT::Handle->dbh->disconnect();
337 # load some sort of data into the database
340 my $datafile = shift;
342 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
345 my $CurrentUser = RT::CurrentUser->new();
346 $CurrentUser->LoadByName('RT_System');
348 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
350 print "Creating Superuser ACL...";
352 my $superuser_ace = RT::ACE->new($CurrentUser);
353 $superuser_ace->_BootstrapCreate(
354 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
355 PrincipalType => 'Group',
356 RightName => 'SuperUser',
357 ObjectType => 'RT::System',
362 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
363 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
366 || die "Couldn't find initial data for import\n" . $@;
369 print "Creating groups...";
370 foreach $item (@Groups) {
371 my $new_entry = RT::Group->new($CurrentUser);
372 my ( $return, $msg ) = $new_entry->_Create(%$item);
373 print "(Error: $msg)" unless ($return);
379 print "Creating users...";
380 foreach $item (@Users) {
381 my $new_entry = new RT::User($CurrentUser);
382 my ( $return, $msg ) = $new_entry->Create(%$item);
383 print "(Error: $msg)" unless ($return);
389 print "Creating queues...";
390 for $item (@Queues) {
391 my $new_entry = new RT::Queue($CurrentUser);
392 my ( $return, $msg ) = $new_entry->Create(%$item);
393 print "(Error: $msg)" unless ($return);
399 print "Creating ACL...";
400 for my $item (@ACL) {
402 my ($princ, $object);
404 # Global rights or Queue rights?
405 if ($item->{'Queue'}) {
406 $object = RT::Queue->new($CurrentUser);
407 $object->Load( $item->{'Queue'} );
409 $object = $RT::System;
412 # Group rights or user rights?
413 if ($item->{'GroupDomain'}) {
414 $princ = RT::Group->new($CurrentUser);
415 if ($item->{'GroupDomain'} eq 'UserDefined') {
416 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
417 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
418 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
419 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
421 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
422 Queue => $object->id);
424 $princ->Load( $item->{'GroupId'} );
427 $princ = RT::User->new($CurrentUser);
428 $princ->Load( $item->{'UserId'} );
432 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
433 Right => $item->{'Right'},
448 print "Creating custom fields...";
449 for $item (@CustomFields) {
450 my $new_entry = new RT::CustomField($CurrentUser);
451 my $values = $item->{'Values'};
452 delete $item->{'Values'};
453 my $q = $item->{'Queue'};
454 my $q_obj = RT::Queue->new($CurrentUser);
457 $item->{'Queue'} = $q_obj->Id;
460 $item->{'Queue'} = 0;
463 print "(Error: Could not find queue " . $q . ")\n"
464 unless ( $q_obj->Id );
467 my ( $return, $msg ) = $new_entry->Create(%$item);
469 foreach my $value ( @{$values} ) {
470 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
471 print "(Error: $emsg)\n" unless ($eval);
474 print "(Error: $msg)\n" unless ($return);
482 print "Creating ScripActions...";
484 for $item (@ScripActions) {
485 my $new_entry = RT::ScripAction->new($CurrentUser);
486 my $return = $new_entry->Create(%$item);
493 if (@ScripConditions) {
494 print "Creating ScripConditions...";
496 for $item (@ScripConditions) {
497 my $new_entry = RT::ScripCondition->new($CurrentUser);
498 my $return = $new_entry->Create(%$item);
506 print "Creating templates...";
508 for $item (@Templates) {
509 my $new_entry = new RT::Template($CurrentUser);
510 my $return = $new_entry->Create(%$item);
516 print "Creating scrips...";
518 for $item (@Scrips) {
519 my $new_entry = new RT::Scrip($CurrentUser);
520 my ( $return, $msg ) = $new_entry->Create(%$item);
525 print "(Error: $msg)\n";
530 $RT::Handle->Disconnect();
534 =head2 ACLEquivGroupId
536 Given a userid, return that user's acl equivalence group
540 sub ACLEquivGroupId {
541 my $username = shift;
542 my $user = RT::User->new($RT::SystemUser);
543 $user->Load($username);
544 my $equiv_group = RT::Group->new($RT::SystemUser);
545 $equiv_group->LoadACLEquivalenceGroup($user);
546 return ( $equiv_group->Id );
553 $0: Set up RT's database
555 --action init Initialize the database
556 drop Drop the database.
557 This will ERASE ALL YOUR DATA
558 insert Insert data into RT's database.
559 By default, will use RT's installation data.
560 To use a local or supplementary datafile, specify it
561 using the '--datafile' option below.
563 acl Initialize only the database ACLs
564 To use a local or supplementary datafile, specify it
565 using the '--datadir' option below.
567 schema Initialize only the database schema
568 To use a local or supplementary datafile, specify it
569 using the '--datadir' option below.
571 --datafile /path/to/datafile
572 --datadir /path/to/ Used to specify a path to find the local
573 database schema and acls to be installed.
577 --dba-password dba's password
578 --prompt-for-dba-password Ask for the database administrator's password interactively