import rt 3.0.12
[freeside.git] / rt / sbin / rt-setup-database.in
1 #!@PERL@ -w
2 # BEGIN LICENSE BLOCK
3
4 # Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
5
6 # (Except where explictly superceded by other copyright notices)
7
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
11 # from www.gnu.org.
12
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.
17
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.
22
23
24 # END LICENSE BLOCK
25
26 use strict;
27 use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
28 use vars
29   qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
30
31 use lib "@RT_LIB_PATH@";
32
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
35 # inheritance
36 use RT;
37 use Carp;
38 use RT::User;
39 use RT::CurrentUser;
40 use RT::Template;
41 use RT::ScripAction;
42 use RT::ACE;
43 use RT::Group;
44 use RT::User;
45 use RT::Queue;
46 use RT::ScripCondition;
47 use RT::CustomField;
48 use RT::Scrip;
49
50 RT::LoadConfig();
51 use Term::ReadKey;
52 use Getopt::Long;
53
54 my %args;
55
56 GetOptions(
57     \%args,
58     'prompt-for-dba-password', 'force', 'debug',
59     'action=s',                'dba=s', 'dba-password=s', 'datafile=s',
60     'datadir=s'
61 );
62
63 $| = 1;    #unbuffer that output.
64
65 require RT::Handle;
66 my $Handle = RT::Handle->new($RT::DatabaseType);
67 $Handle->BuildDSN;
68 my $dbh;
69
70 if ( $args{'prompt-for-dba-password'} ) {
71     $args{'dba-password'} = get_dba_password();
72     chomp( $args{'dba-password'} );
73 }
74
75 unless ( $args{'action'} ) {
76     help();
77     die;
78 }
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) {
85     create_db();
86     } else {
87         print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
88     }
89
90     $dbh->disconnect;
91     $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
92       || die $DBI::errstr;
93
94     print "Now populating database schema.\n";
95     insert_schema();
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" );
102 }
103 elsif ( $args{'action'} eq 'drop' ) {
104     unless ( $dbh =
105          DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
106     {
107         warn $DBI::errstr;
108         warn "Database doesn't appear to exist. Aborting database drop.";
109         exit(0);
110     }
111     drop_db();
112 }
113 elsif ( $args{'action'} eq 'insert' ) {
114     insert_data( $args{'datafile'} );
115 }
116 elsif ($args{'action'} eq 'acl') {
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_acl($args{'datadir'});
120 }
121 elsif ($args{'action'} eq 'schema') {
122     $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
123       || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
124         insert_schema($args{'datadir'});
125 }
126
127 else {
128     print STDERR '$0 called with an invalid --action parameter';
129     exit(-1);
130 }
131
132 # {{{ sub insert_schema
133 sub insert_schema {
134         my $base_path = (shift || $RT::EtcPath);
135     my (@schema);
136     print "Creating database schema.\n";
137
138     if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
139         no warnings 'unopened';
140
141         open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
142         open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
143
144         my $statement = "";
145         foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
146             $line =~ s/\#.*//g;
147             $line =~ s/--.*//g;
148             $statement .= $line;
149             if ( $line =~ /;(\s*)$/ ) {
150                 $statement =~ s/;(\s*)$//g;
151                 push @schema, $statement;
152                 $statement = "";
153             }
154         }
155
156         local $SIG{__WARN__} = sub {};
157         my $is_local = 0; # local/etc/schema needs to be nonfatal. 
158         foreach my $statement (@schema) {
159             if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
160             print STDERR "SQL: $statement\n" if defined $args{'debug'};
161             my $sth = $dbh->prepare($statement) or die $dbh->errstr;
162             unless ( $sth->execute or $is_local ) {
163                 die "Problem with statement:\n $statement\n" . $sth->errstr;
164             }
165         }
166
167     }
168     else {
169         die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
170     }
171     print "schema sucessfully inserted\n";
172
173 }
174
175 # }}}
176
177 # {{{ sub drop_db
178 sub drop_db {
179     return if ( $RT::DatabaseType eq 'SQLite' );
180     if ( $RT::DatabaseType eq 'Oracle' ) {
181         print <<END;
182
183 To delete the tables and sequences of the RT Oracle database by running 
184     \@etc/drop.Oracle 
185 through SQLPlus.
186
187 END
188         return;
189     }   
190     unless ( $args{'force'} ) {
191         print <<END;
192
193 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
194 WARNING: This will erase all data in $RT::DatabaseName.
195
196 END
197         exit unless _yesno();
198
199     }
200
201     print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
202
203     $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
204 }
205
206 # }}}
207
208 # {{{ sub create_db
209 sub create_db {
210     print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
211     if ( $RT::DatabaseType eq 'SQLite' ) {
212         return;
213     }
214     elsif ( $RT::DatabaseType eq 'Pg' ) {
215         $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
216         if ($DBI::errstr) {
217             $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
218         }
219     }
220     elsif ($RT::DatabaseType eq 'Oracle') {
221         insert_acl();
222     }
223     elsif ( $RT::DatabaseType eq 'Informix' ) {
224         $ENV{DB_LOCALE} = 'en_us.utf8';
225         $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
226     }
227     else {
228         $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
229     }
230 }
231
232 # }}}
233
234 sub get_dba_password {
235     print
236 "In order to create a new database and grant RT access to that database,\n";
237     print "this script needs to connect to your "
238       . $RT::DatabaseType
239       . " instance on "
240       . $RT::DatabaseHost . " as "
241       . $args{'dba'} . ".\n";
242     print
243 "Please specify that user's database password below. If the user has no database\n";
244     print "password, just press return.\n\n";
245     print "Password: ";
246     ReadMode('noecho');
247     my $password = ReadLine(0);
248     ReadMode('normal');
249     return ($password);
250 }
251
252 # {{{ sub _yesno
253 sub _yesno {
254     print "Proceed [y/N]:";
255     my $x = scalar(<STDIN>);
256     $x =~ /^y/i;
257 }
258
259 # }}}
260
261 # {{{ insert_acls
262 sub insert_acl {
263
264         my $base_path = (shift || $RT::EtcPath);
265
266     if ( $RT::DatabaseType =~ /^oracle$/i ) {
267         do $base_path . "/acl.Oracle"
268           || die "Couldn't find ACLS for Oracle\n" . $@;
269     }
270     elsif ( $RT::DatabaseType =~ /^pg$/i ) {
271         do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
272     }
273     elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
274         do $base_path . "/acl.mysql"
275           || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
276     }
277     elsif ( $RT::DatabaseType =~ /^informix$/i ) {
278         do $base_path . "/acl.Informix"
279           || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
280     }
281     elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
282         return;
283     }
284     else {
285         die "Unknown RT database type";
286     }
287
288     my @acl = acl($dbh);
289     foreach my $statement (@acl) {
290         print STDERR $statement if $args{'debug'};
291         my $sth = $dbh->prepare($statement) or die $dbh->errstr;
292         unless ( $sth->execute ) {
293             die "Problem with statement:\n $statement\n" . $sth->errstr;
294         }
295     }
296 }
297
298 # }}}
299
300 =head2 get_system_dsn
301
302 Returns a dsn suitable for database creates and drops
303 and user creates and drops
304
305 =cut
306
307 sub get_system_dsn {
308
309     my $dsn = $Handle->DSN;
310
311     #with mysql, you want to connect sans database to funge things
312     if ( $RT::DatabaseType eq 'mysql' ) {
313         $dsn =~ s/dbname=$RT::DatabaseName//;
314
315         # with postgres, you want to connect to database1
316     }
317     elsif ( $RT::DatabaseType eq 'Pg' ) {
318         $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
319     }
320     elsif ( $RT::DatabaseType eq 'Informix' ) {
321         # with Informix, you want to connect sans database:
322         $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
323     }
324     return $dsn;
325 }
326
327 sub insert_initial_data {
328
329     RT::InitLogging();
330
331     #connect to the db, for actual RT work
332     require RT::Handle;
333     $RT::Handle = RT::Handle->new();
334     $RT::Handle->Connect();
335
336     #Put together a current user object so we can create a User object
337     my $CurrentUser = new RT::CurrentUser();
338
339     print "Checking for existing system user ($CurrentUser)...";
340     my $test_user = RT::User->new($CurrentUser);
341     $test_user->Load('RT_System');
342     if ( $test_user->id ) {
343         print "found!\n\nYou appear to have a functional RT database.\n"
344           . "Exiting, so as not to clobber your existing data.\n";
345         exit(-1);
346
347     }
348     else {
349         print "not found.  This appears to be a new installation.\n";
350     }
351
352     print "Creating system user...";
353     my $RT_System = new RT::User($CurrentUser);
354
355     my ( $val, $msg ) = $RT_System->_BootstrapCreate(
356         Name     => 'RT_System',
357         RealName => 'The RT System itself',
358         Comments =>
359 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
360         Creator => '1' );
361
362     unless ($val) {
363         print "$msg\n";
364         exit(1);
365     }
366     print "done.\n";
367     $RT::Handle->Disconnect();
368
369 }
370
371 # load some sort of data into the database
372
373 sub insert_data {
374     my $datafile = shift;
375
376     #Connect to the database and get RT::SystemUser and RT::Nobody loaded
377     RT::Init;
378
379     my $CurrentUser = RT::CurrentUser->new();
380     $CurrentUser->LoadByName('RT_System');
381
382     if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
383
384         print "Creating Superuser  ACL...";
385
386         my $superuser_ace = RT::ACE->new($CurrentUser);
387         $superuser_ace->_BootstrapCreate(
388                              PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
389                              PrincipalType => 'Group',
390                              RightName     => 'SuperUser',
391                              ObjectType    => 'RT::System',
392                              ObjectId      => '1' );
393
394     }
395
396     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
397     # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
398
399     require $datafile
400       || die "Couldn't find initial data for import\n" . $@;
401
402     if (@Groups) {
403         print "Creating groups...";
404         foreach $item (@Groups) {
405             my $new_entry = RT::Group->new($CurrentUser);
406             my ( $return, $msg ) = $new_entry->_Create(%$item);
407             print "(Error: $msg)" unless ($return);
408             print $return. ".";
409         }
410         print "done.\n";
411     }
412     if (@Users) {
413         print "Creating users...";
414         foreach $item (@Users) {
415             my $new_entry = new RT::User($CurrentUser);
416             my ( $return, $msg ) = $new_entry->Create(%$item);
417             print "(Error: $msg)" unless ($return);
418             print $return. ".";
419         }
420         print "done.\n";
421     }
422     if (@Queues) {
423         print "Creating queues...";
424         for $item (@Queues) {
425             my $new_entry = new RT::Queue($CurrentUser);
426             my ( $return, $msg ) = $new_entry->Create(%$item);
427             print "(Error: $msg)" unless ($return);
428             print $return. ".";
429         }
430         print "done.\n";
431     }
432     if (@ACL) {
433         print "Creating ACL...";
434         for my $item (@ACL) {
435
436             my ($princ, $object);
437
438             # Global rights or Queue rights?
439             if ($item->{'Queue'}) {
440                 $object = RT::Queue->new($CurrentUser);
441                 $object->Load( $item->{'Queue'} );
442             } else {
443                 $object = $RT::System;
444             }
445
446             # Group rights or user rights?
447             if ($item->{'GroupDomain'}) {
448                 $princ = RT::Group->new($CurrentUser);
449                 if ($item->{'GroupDomain'} eq 'UserDefined') {
450                   $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
451                 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
452                   $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
453                 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
454                   $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
455                 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
456                          $item->{'Queue'}) {
457                   $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
458                                               Queue => $object->id);
459                 } else {
460                   $princ->Load( $item->{'GroupId'} );
461                 }
462             } else {
463                 $princ = RT::User->new($CurrentUser);
464                 $princ->Load( $item->{'UserId'} );
465             }
466
467             # Grant it
468             my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
469                                                      Right => $item->{'Right'},
470                                                      Object => $object );
471
472             if ($return) {
473                 print $return. ".";
474             }
475             else {
476                 print $msg . ".";
477
478             }
479
480         }
481         print "done.\n";
482     }
483     if (@CustomFields) {
484         print "Creating custom fields...";
485         for $item (@CustomFields) {
486             my $new_entry = new RT::CustomField($CurrentUser);
487             my $values    = $item->{'Values'};
488             delete $item->{'Values'};
489             my $q     = $item->{'Queue'};
490             my $q_obj = RT::Queue->new($CurrentUser);
491             $q_obj->Load($q);
492             if ( $q_obj->Id ) {
493                 $item->{'Queue'} = $q_obj->Id;
494             }
495             elsif ( $q == 0 ) {
496                 $item->{'Queue'} = 0;
497             }
498             else {
499                 print "(Error: Could not find queue " . $q . ")\n"
500                   unless ( $q_obj->Id );
501                 next;
502             }
503             my ( $return, $msg ) = $new_entry->Create(%$item);
504
505             foreach my $value ( @{$values} ) {
506                 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
507                 print "(Error: $emsg)\n" unless ($eval);
508             }
509
510             print "(Error: $msg)\n" unless ($return);
511             print $return. ".";
512         }
513
514         print "done.\n";
515     }
516
517     if (@ScripActions) {
518         print "Creating ScripActions...";
519
520         for $item (@ScripActions) {
521             my $new_entry = RT::ScripAction->new($CurrentUser);
522             my $return    = $new_entry->Create(%$item);
523             print $return. ".";
524         }
525
526         print "done.\n";
527     }
528
529     if (@ScripConditions) {
530         print "Creating ScripConditions...";
531
532         for $item (@ScripConditions) {
533             my $new_entry = RT::ScripCondition->new($CurrentUser);
534             my $return    = $new_entry->Create(%$item);
535             print $return. ".";
536         }
537
538         print "done.\n";
539     }
540
541     if (@Templates) {
542         print "Creating templates...";
543
544         for $item (@Templates) {
545             my $new_entry = new RT::Template($CurrentUser);
546             my $return    = $new_entry->Create(%$item);
547             print $return. ".";
548         }
549         print "done.\n";
550     }
551     if (@Scrips) {
552         print "Creating scrips...";
553
554         for $item (@Scrips) {
555             my $new_entry = new RT::Scrip($CurrentUser);
556             my ( $return, $msg ) = $new_entry->Create(%$item);
557             if ($return) {
558                 print $return. ".";
559             }
560             else {
561                 print "(Error: $msg)\n";
562             }
563         }
564         print "done.\n";
565     }
566     $RT::Handle->Disconnect();
567
568 }
569
570 =head2 ACLEquivGroupId
571
572 Given a userid, return that user's acl equivalence group
573
574 =cut
575
576 sub ACLEquivGroupId {
577     my $username = shift;
578     my $user     = RT::User->new($RT::SystemUser);
579     $user->Load($username);
580     my $equiv_group = RT::Group->new($RT::SystemUser);
581     $equiv_group->LoadACLEquivalenceGroup($user);
582     return ( $equiv_group->Id );
583 }
584
585 sub help {
586
587     print <<EOF;
588
589 $0: Set up RT's database
590
591 --action        init    Initialize the database
592                 drop    Drop the database. 
593                         This will ERASE ALL YOUR DATA
594                 insert  Insert data into RT's database. 
595                         By default, will use RT's installation data.
596                         To use a local or supplementary datafile, specify it
597                         using the '--datafile' option below.
598                         
599                 acl     Initialize only the database ACLs
600                         To use a local or supplementary datafile, specify it
601                         using the '--datadir' option below.
602                         
603                 schema  Initialize only the database schema
604                         To use a local or supplementary datafile, specify it
605                         using the '--datadir' option below.
606
607 --datafile /path/to/datafile
608 --datadir /path/to/              Used to specify a path to find the local
609                                 database schema and acls to be installed.
610
611
612 --dba                           dba's username
613 --dba-password                  dba's password
614 --prompt-for-dba-password       Ask for the database administrator's password interactively
615
616
617 EOF
618
619 }
620
621 1;