remove accidentally doubled lines in usage inst
[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_initial' ) {
114     insert_initial_data();
115 }
116 elsif ( $args{'action'} eq 'insert' ) {
117     insert_data( $args{'datafile'} );
118 }
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'});
123 }
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'});
128 }
129
130 else {
131     print STDERR '$0 called with an invalid --action parameter';
132     exit(-1);
133 }
134
135 # {{{ sub insert_schema
136 sub insert_schema {
137         my $base_path = (shift || $RT::EtcPath);
138     my (@schema);
139     print "Creating database schema.\n";
140
141     if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
142         no warnings 'unopened';
143
144         open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
145         open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
146
147         my $statement = "";
148         foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
149             $line =~ s/\#.*//g;
150             $line =~ s/--.*//g;
151             $statement .= $line;
152             if ( $line =~ /;(\s*)$/ ) {
153                 $statement =~ s/;(\s*)$//g;
154                 push @schema, $statement;
155                 $statement = "";
156             }
157         }
158
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;
167             }
168         }
169
170     }
171     else {
172         die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
173     }
174     print "schema sucessfully inserted\n";
175
176 }
177
178 # }}}
179
180 # {{{ sub drop_db
181 sub drop_db {
182     return if ( $RT::DatabaseType eq 'SQLite' );
183     if ( $RT::DatabaseType eq 'Oracle' ) {
184         print <<END;
185
186 To delete the tables and sequences of the RT Oracle database by running 
187     \@etc/drop.Oracle 
188 through SQLPlus.
189
190 END
191         return;
192     }   
193     unless ( $args{'force'} ) {
194         print <<END;
195
196 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
197 WARNING: This will erase all data in $RT::DatabaseName.
198
199 END
200         exit unless _yesno();
201
202     }
203
204     print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
205
206     $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
207 }
208
209 # }}}
210
211 # {{{ sub create_db
212 sub create_db {
213     print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
214     if ( $RT::DatabaseType eq 'SQLite' ) {
215         return;
216     }
217     elsif ( $RT::DatabaseType eq 'Pg' ) {
218         $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
219         if ($DBI::errstr) {
220             $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
221         }
222     }
223     elsif ($RT::DatabaseType eq 'Oracle') {
224         insert_acl();
225     }
226     elsif ( $RT::DatabaseType eq 'Informix' ) {
227         $ENV{DB_LOCALE} = 'en_us.utf8';
228         $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
229     }
230     else {
231         $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
232     }
233 }
234
235 # }}}
236
237 sub get_dba_password {
238     print
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 "
241       . $RT::DatabaseType
242       . " instance on "
243       . $RT::DatabaseHost . " as "
244       . $args{'dba'} . ".\n";
245     print
246 "Please specify that user's database password below. If the user has no database\n";
247     print "password, just press return.\n\n";
248     print "Password: ";
249     ReadMode('noecho');
250     my $password = ReadLine(0);
251     ReadMode('normal');
252     return ($password);
253 }
254
255 # {{{ sub _yesno
256 sub _yesno {
257     print "Proceed [y/N]:";
258     my $x = scalar(<STDIN>);
259     $x =~ /^y/i;
260 }
261
262 # }}}
263
264 # {{{ insert_acls
265 sub insert_acl {
266
267         my $base_path = (shift || $RT::EtcPath);
268
269     if ( $RT::DatabaseType =~ /^oracle$/i ) {
270         do $base_path . "/acl.Oracle"
271           || die "Couldn't find ACLS for Oracle\n" . $@;
272     }
273     elsif ( $RT::DatabaseType =~ /^pg$/i ) {
274         do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
275     }
276     elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
277         do $base_path . "/acl.mysql"
278           || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
279     }
280     elsif ( $RT::DatabaseType =~ /^informix$/i ) {
281         do $base_path . "/acl.Informix"
282           || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
283     }
284     elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
285         return;
286     }
287     else {
288         die "Unknown RT database type";
289     }
290
291     my @acl = acl($dbh);
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;
297         }
298     }
299 }
300
301 # }}}
302
303 =head2 get_system_dsn
304
305 Returns a dsn suitable for database creates and drops
306 and user creates and drops
307
308 =cut
309
310 sub get_system_dsn {
311
312     my $dsn = $Handle->DSN;
313
314     #with mysql, you want to connect sans database to funge things
315     if ( $RT::DatabaseType eq 'mysql' ) {
316         $dsn =~ s/dbname=$RT::DatabaseName//;
317
318         # with postgres, you want to connect to database1
319     }
320     elsif ( $RT::DatabaseType eq 'Pg' ) {
321         $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
322     }
323     elsif ( $RT::DatabaseType eq 'Informix' ) {
324         # with Informix, you want to connect sans database:
325         $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
326     }
327     return $dsn;
328 }
329
330 sub insert_initial_data {
331
332     RT::InitLogging();
333
334     #connect to the db, for actual RT work
335     require RT::Handle;
336     $RT::Handle = RT::Handle->new();
337     $RT::Handle->Connect();
338
339     #Put together a current user object so we can create a User object
340     my $CurrentUser = new RT::CurrentUser();
341
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";
348         exit(-1);
349
350     }
351     else {
352         print "not found.  This appears to be a new installation.\n";
353     }
354
355     print "Creating system user...";
356     my $RT_System = new RT::User($CurrentUser);
357
358     my ( $val, $msg ) = $RT_System->_BootstrapCreate(
359         Name     => 'RT_System',
360         RealName => 'The RT System itself',
361         Comments =>
362 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
363         Creator => '1' );
364
365     unless ($val) {
366         print "$msg\n";
367         exit(1);
368     }
369     print "done.\n";
370     $RT::Handle->Disconnect();
371
372 }
373
374 # load some sort of data into the database
375
376 sub insert_data {
377     my $datafile = shift;
378
379     #Connect to the database and get RT::SystemUser and RT::Nobody loaded
380     RT::Init;
381
382     my $CurrentUser = RT::CurrentUser->new();
383     $CurrentUser->LoadByName('RT_System');
384
385     if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
386
387         print "Creating Superuser  ACL...";
388
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',
395                              ObjectId      => '1' );
396
397     }
398
399     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
400     # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
401
402     require $datafile
403       || die "Couldn't find initial data for import\n" . $@;
404
405     if (@Groups) {
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);
411             print $return. ".";
412         }
413         print "done.\n";
414     }
415     if (@Users) {
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);
421             print $return. ".";
422         }
423         print "done.\n";
424     }
425     if (@Queues) {
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);
431             print $return. ".";
432         }
433         print "done.\n";
434     }
435     if (@ACL) {
436         print "Creating ACL...";
437         for my $item (@ACL) {
438
439             my ($princ, $object);
440
441             # Global rights or Queue rights?
442             if ($item->{'Queue'}) {
443                 $object = RT::Queue->new($CurrentUser);
444                 $object->Load( $item->{'Queue'} );
445             } else {
446                 $object = $RT::System;
447             }
448
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' &&
457                          $item->{'Queue'}) {
458                   $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
459                                               Queue => $object->id);
460                 } else {
461                   $princ->Load( $item->{'GroupId'} );
462                 }
463             } else {
464                 $princ = RT::User->new($CurrentUser);
465                 $princ->Load( $item->{'UserId'} );
466             }
467
468             # Grant it
469             my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
470                                                      Right => $item->{'Right'},
471                                                      Object => $object );
472
473             if ($return) {
474                 print $return. ".";
475             }
476             else {
477                 print $msg . ".";
478
479             }
480
481         }
482         print "done.\n";
483     }
484     if (@CustomFields) {
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);
492             $q_obj->Load($q);
493             if ( $q_obj->Id ) {
494                 $item->{'Queue'} = $q_obj->Id;
495             }
496             elsif ( $q == 0 ) {
497                 $item->{'Queue'} = 0;
498             }
499             else {
500                 print "(Error: Could not find queue " . $q . ")\n"
501                   unless ( $q_obj->Id );
502                 next;
503             }
504             my ( $return, $msg ) = $new_entry->Create(%$item);
505
506             foreach my $value ( @{$values} ) {
507                 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
508                 print "(Error: $emsg)\n" unless ($eval);
509             }
510
511             print "(Error: $msg)\n" unless ($return);
512             print $return. ".";
513         }
514
515         print "done.\n";
516     }
517
518     if (@ScripActions) {
519         print "Creating ScripActions...";
520
521         for $item (@ScripActions) {
522             my $new_entry = RT::ScripAction->new($CurrentUser);
523             my $return    = $new_entry->Create(%$item);
524             print $return. ".";
525         }
526
527         print "done.\n";
528     }
529
530     if (@ScripConditions) {
531         print "Creating ScripConditions...";
532
533         for $item (@ScripConditions) {
534             my $new_entry = RT::ScripCondition->new($CurrentUser);
535             my $return    = $new_entry->Create(%$item);
536             print $return. ".";
537         }
538
539         print "done.\n";
540     }
541
542     if (@Templates) {
543         print "Creating templates...";
544
545         for $item (@Templates) {
546             my $new_entry = new RT::Template($CurrentUser);
547             my $return    = $new_entry->Create(%$item);
548             print $return. ".";
549         }
550         print "done.\n";
551     }
552     if (@Scrips) {
553         print "Creating scrips...";
554
555         for $item (@Scrips) {
556             my $new_entry = new RT::Scrip($CurrentUser);
557             my ( $return, $msg ) = $new_entry->Create(%$item);
558             if ($return) {
559                 print $return. ".";
560             }
561             else {
562                 print "(Error: $msg)\n";
563             }
564         }
565         print "done.\n";
566     }
567     $RT::Handle->Disconnect();
568
569 }
570
571 =head2 ACLEquivGroupId
572
573 Given a userid, return that user's acl equivalence group
574
575 =cut
576
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 );
584 }
585
586 sub help {
587
588     print <<EOF;
589
590 $0: Set up RT's database
591
592 --action        init    Initialize the database
593                 drop    Drop the database. 
594                         This will ERASE ALL YOUR DATA
595                 insert_initial 
596                         Insert RT's core system objects
597                 insert  Insert data into RT's database. 
598                         By default, will use RT's installation data.
599                         To use a local or supplementary datafile, specify it
600                         using the '--datafile' option below.
601                         
602                 acl     Initialize only the database ACLs
603                         To use a local or supplementary datafile, specify it
604                         using the '--datadir' option below.
605                         
606                 schema  Initialize only the database schema
607                         To use a local or supplementary datafile, specify it
608                         using the '--datadir' option below.
609
610 --datafile /path/to/datafile
611 --datadir /path/to/              Used to specify a path to find the local
612                                 database schema and acls to be installed.
613
614
615 --dba                           dba's username
616 --dba-password                  dba's password
617 --prompt-for-dba-password       Ask for the database administrator's password interactively
618
619
620 EOF
621
622 }
623
624 1;