e49a32ed91333d85ff59d16274512f640040965c
[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     create_db();
84
85     $dbh->disconnect;
86     $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
87       || die $DBI::errstr;
88
89     print "Now populating database schema.\n";
90     insert_schema();
91     print "Now inserting database ACLs\n";
92     insert_acl();
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" );
97 }
98 elsif ( $args{'action'} eq 'drop' ) {
99     unless ( $dbh =
100          DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
101     {
102         warn $DBI::errstr;
103         warn "Database doesn't appear to exist. Aborting database drop.";
104         exit(0);
105     }
106     drop_db();
107 }
108 elsif ( $args{'action'} eq 'insert' ) {
109     insert_data( $args{'datafile'} );
110 }
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'});
115 }
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'});
120 }
121
122 else {
123     print STDERR '$0 called with an invalid --action parameter';
124     exit(-1);
125 }
126
127 # {{{ sub insert_schema
128 sub insert_schema {
129         my $base_path = (shift || $RT::EtcPath);
130     my (@schema);
131     print "Creating database schema.\n";
132
133     if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
134         no warnings 'unopened';
135
136         open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
137         open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
138
139         my $statement = "";
140         foreach my $line (<SCHEMA>, <SCHEMA_LOCAL>) {
141             $line =~ s/\#.*//g;
142             $statement .= $line;
143             if ( $line =~ /;(\s*)$/ ) {
144                 $statement =~ s/;(\s*)$//g;
145                 push @schema, $statement;
146                 $statement = "";
147             }
148         }
149
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;
155             }
156         }
157
158     }
159     else {
160         die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
161     }
162     print "schema sucessfully inserted\n";
163
164 }
165
166 # }}}
167
168 # {{{ sub drop_db
169 sub drop_db {
170     return if ( $RT::DatabaseType eq 'SQLite' );
171     unless ( $args{'force'} ) {
172         print <<END;
173
174 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
175 WARNING: This will erase all data in $RT::DatabaseName.
176
177 END
178         exit unless _yesno();
179
180     }
181
182     print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
183
184     $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
185 }
186
187 # }}}
188
189 # {{{ sub create_db
190 sub create_db {
191     print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
192     if ( $RT::DatabaseType eq 'SQLite' ) {
193         return;
194     }
195     elsif ( $RT::DatabaseType eq 'Pg' ) {
196         $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
197         if ($DBI::errstr) {
198             $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
199         }
200     }
201     else {
202         $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
203     }
204 }
205
206 # }}}
207
208 sub get_dba_password {
209     print
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 "
212       . $RT::DatabaseType
213       . " instance on "
214       . $RT::DatabaseHost . " as "
215       . $args{'dba'} . ".\n";
216     print
217 "Please specify that user's database password below. If the user has no database\n";
218     print "password, just press return.\n\n";
219     print "Password: ";
220     ReadMode('noecho');
221     my $password = ReadLine(0);
222     ReadMode('normal');
223     return ($password);
224 }
225
226 # {{{ sub _yesno
227 sub _yesno {
228     print "Proceed [y/N]:";
229     my $x = scalar(<STDIN>);
230     $x =~ /^y/i;
231 }
232
233 # }}}
234
235 # {{{ insert_acls
236 sub insert_acl {
237
238         my $base_path = (shift || $RT::EtcPath);
239
240     if ( $RT::DatabaseType =~ /^oracle$/i ) {
241         do $base_path . "/acl.Oracle"
242           || die "Couldn't find ACLS for Oracle\n" . $@;
243     }
244     elsif ( $RT::DatabaseType =~ /^pg$/i ) {
245         do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
246     }
247     elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
248         do $base_path . "/acl.mysql"
249           || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
250     }
251     elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
252         return;
253     }
254     else {
255         die "Unknown RT database type";
256     }
257
258     my @acl = acl($dbh);
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;
264         }
265     }
266 }
267
268 # }}}
269
270 =head2 get_system_dsn
271
272 Returns a dsn suitable for database creates and drops
273 and user creates and drops
274
275 =cut
276
277 sub get_system_dsn {
278
279     my $dsn = $Handle->DSN;
280
281     #with mysql, you want to connect sans database to funge things
282     if ( $RT::DatabaseType eq 'mysql' ) {
283         $dsn =~ s/dbname=$RT::DatabaseName//;
284
285         # with postgres, you want to connect to database1
286     }
287     elsif ( $RT::DatabaseType eq 'Pg' ) {
288         $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
289     }
290     return $dsn;
291 }
292
293 sub insert_initial_data {
294
295     RT::InitLogging();
296
297     #connect to the db, for actual RT work
298     require RT::Handle;
299     $RT::Handle = RT::Handle->new();
300     $RT::Handle->Connect();
301
302     #Put together a current user object so we can create a User object
303     my $CurrentUser = new RT::CurrentUser();
304
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";
311         exit(-1);
312
313     }
314     else {
315         print "not found.  This appears to be a new installation.\n";
316     }
317
318     print "Creating system user...";
319     my $RT_System = new RT::User($CurrentUser);
320
321     my ( $val, $msg ) = $RT_System->_BootstrapCreate(
322         Name     => 'RT_System',
323         RealName => 'The RT System itself',
324         Comments =>
325 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
326         Creator => '1' );
327
328     unless ($val) {
329         print "$msg\n";
330         exit(1);
331     }
332     print "done.\n";
333     $RT::Handle->dbh->disconnect();
334
335 }
336
337 # load some sort of data into the database
338
339 sub insert_data {
340     my $datafile = shift;
341
342     #Connect to the database and get RT::SystemUser and RT::Nobody loaded
343     RT::Init;
344
345     my $CurrentUser = RT::CurrentUser->new();
346     $CurrentUser->LoadByName('RT_System');
347
348     if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
349
350         print "Creating Superuser  ACL...";
351
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',
358                              ObjectId      => '1' );
359
360     }
361
362     # Slurp in stuff to insert from the datafile. Possible things to go in here:-
363     # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
364
365     require $datafile
366       || die "Couldn't find initial data for import\n" . $@;
367
368     if (@Groups) {
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);
374             print $return. ".";
375         }
376         print "done.\n";
377     }
378     if (@Users) {
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);
384             print $return. ".";
385         }
386         print "done.\n";
387     }
388     if (@Queues) {
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);
394             print $return. ".";
395         }
396         print "done.\n";
397     }
398     if (@ACL) {
399         print "Creating ACL...";
400         for my $item (@ACL) {
401
402             my ($princ, $object);
403
404             # Global rights or Queue rights?
405             if ($item->{'Queue'}) {
406                 $object = RT::Queue->new($CurrentUser);
407                 $object->Load( $item->{'Queue'} );
408             } else {
409                 $object = $RT::System;
410             }
411
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' &&
420                          $item->{'Queue'}) {
421                   $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
422                                               Queue => $object->id);
423                 } else {
424                   $princ->Load( $item->{'GroupId'} );
425                 }
426             } else {
427                 $princ = RT::User->new($CurrentUser);
428                 $princ->Load( $item->{'UserId'} );
429             }
430
431             # Grant it
432             my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
433                                                      Right => $item->{'Right'},
434                                                      Object => $object );
435
436             if ($return) {
437                 print $return. ".";
438             }
439             else {
440                 print $msg . ".";
441
442             }
443
444         }
445         print "done.\n";
446     }
447     if (@CustomFields) {
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);
455             $q_obj->Load($q);
456             if ( $q_obj->Id ) {
457                 $item->{'Queue'} = $q_obj->Id;
458             }
459             elsif ( $q == 0 ) {
460                 $item->{'Queue'} = 0;
461             }
462             else {
463                 print "(Error: Could not find queue " . $q . ")\n"
464                   unless ( $q_obj->Id );
465                 next;
466             }
467             my ( $return, $msg ) = $new_entry->Create(%$item);
468
469             foreach my $value ( @{$values} ) {
470                 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
471                 print "(Error: $emsg)\n" unless ($eval);
472             }
473
474             print "(Error: $msg)\n" unless ($return);
475             print $return. ".";
476         }
477
478         print "done.\n";
479     }
480
481     if (@ScripActions) {
482         print "Creating ScripActions...";
483
484         for $item (@ScripActions) {
485             my $new_entry = RT::ScripAction->new($CurrentUser);
486             my $return    = $new_entry->Create(%$item);
487             print $return. ".";
488         }
489
490         print "done.\n";
491     }
492
493     if (@ScripConditions) {
494         print "Creating ScripConditions...";
495
496         for $item (@ScripConditions) {
497             my $new_entry = RT::ScripCondition->new($CurrentUser);
498             my $return    = $new_entry->Create(%$item);
499             print $return. ".";
500         }
501
502         print "done.\n";
503     }
504
505     if (@Templates) {
506         print "Creating templates...";
507
508         for $item (@Templates) {
509             my $new_entry = new RT::Template($CurrentUser);
510             my $return    = $new_entry->Create(%$item);
511             print $return. ".";
512         }
513         print "done.\n";
514     }
515     if (@Scrips) {
516         print "Creating scrips...";
517
518         for $item (@Scrips) {
519             my $new_entry = new RT::Scrip($CurrentUser);
520             my ( $return, $msg ) = $new_entry->Create(%$item);
521             if ($return) {
522                 print $return. ".";
523             }
524             else {
525                 print "(Error: $msg)\n";
526             }
527         }
528         print "done.\n";
529     }
530     $RT::Handle->Disconnect();
531
532 }
533
534 =head2 ACLEquivGroupId
535
536 Given a userid, return that user's acl equivalence group
537
538 =cut
539
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 );
547 }
548
549 sub help {
550
551     print <<EOF;
552
553 $0: Set up RT's database
554
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.
562                         
563                 acl     Initialize only the database ACLs
564                         To use a local or supplementary datafile, specify it
565                         using the '--datadir' option below.
566                         
567                 schema  Initialize only the database schema
568                         To use a local or supplementary datafile, specify it
569                         using the '--datadir' option below.
570
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.
574
575
576 --dba                           dba's username
577 --dba-password                  dba's password
578 --prompt-for-dba-password       Ask for the database administrator's password interactively
579
580
581 EOF
582
583 }
584
585 1;