This commit was generated by cvs2svn to compensate for changes in r3241,
[freeside.git] / rt / sbin / rt-setup-database
1 #!/usr/bin/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 "/opt/rt3/lib";
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::Queue-Role' &&
454                          $item->{'Queue'}) {
455                   $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
456                                               Queue => $object->id);
457                 } else {
458                   $princ->Load( $item->{'GroupId'} );
459                 }
460             } else {
461                 $princ = RT::User->new($CurrentUser);
462                 $princ->Load( $item->{'UserId'} );
463             }
464
465             # Grant it
466             my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
467                                                      Right => $item->{'Right'},
468                                                      Object => $object );
469
470             if ($return) {
471                 print $return. ".";
472             }
473             else {
474                 print $msg . ".";
475
476             }
477
478         }
479         print "done.\n";
480     }
481     if (@CustomFields) {
482         print "Creating custom fields...";
483         for $item (@CustomFields) {
484             my $new_entry = new RT::CustomField($CurrentUser);
485             my $values    = $item->{'Values'};
486             delete $item->{'Values'};
487             my $q     = $item->{'Queue'};
488             my $q_obj = RT::Queue->new($CurrentUser);
489             $q_obj->Load($q);
490             if ( $q_obj->Id ) {
491                 $item->{'Queue'} = $q_obj->Id;
492             }
493             elsif ( $q == 0 ) {
494                 $item->{'Queue'} = 0;
495             }
496             else {
497                 print "(Error: Could not find queue " . $q . ")\n"
498                   unless ( $q_obj->Id );
499                 next;
500             }
501             my ( $return, $msg ) = $new_entry->Create(%$item);
502
503             foreach my $value ( @{$values} ) {
504                 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
505                 print "(Error: $emsg)\n" unless ($eval);
506             }
507
508             print "(Error: $msg)\n" unless ($return);
509             print $return. ".";
510         }
511
512         print "done.\n";
513     }
514
515     if (@ScripActions) {
516         print "Creating ScripActions...";
517
518         for $item (@ScripActions) {
519             my $new_entry = RT::ScripAction->new($CurrentUser);
520             my $return    = $new_entry->Create(%$item);
521             print $return. ".";
522         }
523
524         print "done.\n";
525     }
526
527     if (@ScripConditions) {
528         print "Creating ScripConditions...";
529
530         for $item (@ScripConditions) {
531             my $new_entry = RT::ScripCondition->new($CurrentUser);
532             my $return    = $new_entry->Create(%$item);
533             print $return. ".";
534         }
535
536         print "done.\n";
537     }
538
539     if (@Templates) {
540         print "Creating templates...";
541
542         for $item (@Templates) {
543             my $new_entry = new RT::Template($CurrentUser);
544             my $return    = $new_entry->Create(%$item);
545             print $return. ".";
546         }
547         print "done.\n";
548     }
549     if (@Scrips) {
550         print "Creating scrips...";
551
552         for $item (@Scrips) {
553             my $new_entry = new RT::Scrip($CurrentUser);
554             my ( $return, $msg ) = $new_entry->Create(%$item);
555             if ($return) {
556                 print $return. ".";
557             }
558             else {
559                 print "(Error: $msg)\n";
560             }
561         }
562         print "done.\n";
563     }
564     $RT::Handle->Disconnect();
565
566 }
567
568 =head2 ACLEquivGroupId
569
570 Given a userid, return that user's acl equivalence group
571
572 =cut
573
574 sub ACLEquivGroupId {
575     my $username = shift;
576     my $user     = RT::User->new($RT::SystemUser);
577     $user->Load($username);
578     my $equiv_group = RT::Group->new($RT::SystemUser);
579     $equiv_group->LoadACLEquivalenceGroup($user);
580     return ( $equiv_group->Id );
581 }
582
583 sub help {
584
585     print <<EOF;
586
587 $0: Set up RT's database
588
589 --action        init    Initialize the database
590                 drop    Drop the database. 
591                         This will ERASE ALL YOUR DATA
592                 insert  Insert data into RT's database. 
593                         By default, will use RT's installation data.
594                         To use a local or supplementary datafile, specify it
595                         using the '--datafile' option below.
596                         
597                 acl     Initialize only the database ACLs
598                         To use a local or supplementary datafile, specify it
599                         using the '--datadir' option below.
600                         
601                 schema  Initialize only the database schema
602                         To use a local or supplementary datafile, specify it
603                         using the '--datadir' option below.
604
605 --datafile /path/to/datafile
606 --datadir /path/to/              Used to specify a path to find the local
607                                 database schema and acls to be installed.
608
609
610 --dba                           dba's username
611 --dba-password                  dba's password
612 --prompt-for-dba-password       Ask for the database administrator's password interactively
613
614
615 EOF
616
617 }
618
619 1;