import of rt 3.0.4
[freeside.git] / rt / sbin / rt-setup-database
diff --git a/rt/sbin/rt-setup-database b/rt/sbin/rt-setup-database
new file mode 100644 (file)
index 0000000..f84f290
--- /dev/null
@@ -0,0 +1,585 @@
+#!/usr/bin/perl -w
+# BEGIN LICENSE BLOCK
+# 
+# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com>
+# 
+# (Except where explictly superceded by other copyright notices)
+# 
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+# 
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+# 
+# Unless otherwise specified, all modifications, corrections or
+# extensions to this work which alter its source code become the
+# property of Best Practical Solutions, LLC when submitted for
+# inclusion in the work.
+# 
+# 
+# END LICENSE BLOCK
+
+use strict;
+use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
+use vars
+  qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
+
+use lib "/opt/rt3/lib";
+
+#This drags in  RT's config.pm
+# We do it in a begin block because RT::Handle needs to know the type to do its
+# inheritance
+use RT;
+use Carp;
+use RT::User;
+use RT::CurrentUser;
+use RT::Template;
+use RT::ScripAction;
+use RT::ACE;
+use RT::Group;
+use RT::User;
+use RT::Queue;
+use RT::ScripCondition;
+use RT::CustomField;
+use RT::Scrip;
+
+RT::LoadConfig();
+use Term::ReadKey;
+use Getopt::Long;
+
+my %args;
+
+GetOptions(
+    \%args,
+    'prompt-for-dba-password', 'force', 'debug',
+    'action=s',                'dba=s', 'dba-password=s', 'datafile=s',
+    'datadir=s'
+);
+
+$| = 1;    #unbuffer that output.
+
+require RT::Handle;
+my $Handle = RT::Handle->new($RT::DatabaseType);
+$Handle->BuildDSN;
+my $dbh;
+
+if ( $args{'prompt-for-dba-password'} ) {
+    $args{'dba-password'} = get_dba_password();
+    chomp( $args{'dba-password'} );
+}
+
+unless ( $args{'action'} ) {
+    help();
+    die;
+}
+if ( $args{'action'} eq 'init' ) {
+    $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
+      || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+    print "Now creating a database for RT.\n";
+    create_db();
+
+    $dbh->disconnect;
+    $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+      || die $DBI::errstr;
+
+    print "Now populating database schema.\n";
+    insert_schema();
+    print "Now inserting database ACLs\n";
+    insert_acl();
+    print "Now inserting RT core system objects\n";
+    insert_initial_data();
+    print "Now inserting RT data\n";
+    insert_data( $RT::EtcPath . "/initialdata" );
+}
+elsif ( $args{'action'} eq 'drop' ) {
+    unless ( $dbh =
+         DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
+    {
+        warn $DBI::errstr;
+        warn "Database doesn't appear to exist. Aborting database drop.";
+        exit(0);
+    }
+    drop_db();
+}
+elsif ( $args{'action'} eq 'insert' ) {
+    insert_data( $args{'datafile'} );
+}
+elsif ($args{'action'} eq 'acl') {
+    $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+      || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+     insert_acl($args{'datadir'});
+}
+elsif ($args{'action'} eq 'schema') {
+    $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
+      || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
+        insert_schema($args{'datadir'});
+}
+
+else {
+    print STDERR '$0 called with an invalid --action parameter';
+    exit(-1);
+}
+
+# {{{ sub insert_schema
+sub insert_schema {
+        my $base_path = (shift || $RT::EtcPath);
+    my (@schema);
+    print "Creating database schema.\n";
+
+    if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
+       no warnings 'unopened';
+
+        open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
+        open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
+
+        my $statement = "";
+        foreach my $line (<SCHEMA>, <SCHEMA_LOCAL>) {
+            $line =~ s/\#.*//g;
+            $statement .= $line;
+            if ( $line =~ /;(\s*)$/ ) {
+                $statement =~ s/;(\s*)$//g;
+                push @schema, $statement;
+                $statement = "";
+            }
+        }
+
+        foreach my $statement (@schema) {
+            print STDERR $statement if $args{'debug'};
+            my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+            unless ( $sth->execute ) {
+                die "Problem with statement:\n $statement\n" . $sth->errstr;
+            }
+        }
+
+    }
+    else {
+        die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
+    }
+    print "schema sucessfully inserted\n";
+
+}
+
+# }}}
+
+# {{{ sub drop_db
+sub drop_db {
+    return if ( $RT::DatabaseType eq 'SQLite' );
+    unless ( $args{'force'} ) {
+        print <<END;
+
+About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
+WARNING: This will erase all data in $RT::DatabaseName.
+
+END
+        exit unless _yesno();
+
+    }
+
+    print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
+
+    $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
+}
+
+# }}}
+
+# {{{ sub create_db
+sub create_db {
+    print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
+    if ( $RT::DatabaseType eq 'SQLite' ) {
+        return;
+    }
+    elsif ( $RT::DatabaseType eq 'Pg' ) {
+        $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
+        if ($DBI::errstr) {
+            $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
+        }
+    }
+    else {
+        $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
+    }
+}
+
+# }}}
+
+sub get_dba_password {
+    print
+"In order to create a new database and grant RT access to that database,\n";
+    print "this script needs to connect to your "
+      . $RT::DatabaseType
+      . " instance on "
+      . $RT::DatabaseHost . " as "
+      . $args{'dba'} . ".\n";
+    print
+"Please specify that user's database password below. If the user has no database\n";
+    print "password, just press return.\n\n";
+    print "Password: ";
+    ReadMode('noecho');
+    my $password = ReadLine(0);
+    ReadMode('normal');
+    return ($password);
+}
+
+# {{{ sub _yesno
+sub _yesno {
+    print "Proceed [y/N]:";
+    my $x = scalar(<STDIN>);
+    $x =~ /^y/i;
+}
+
+# }}}
+
+# {{{ insert_acls
+sub insert_acl {
+
+        my $base_path = (shift || $RT::EtcPath);
+
+    if ( $RT::DatabaseType =~ /^oracle$/i ) {
+        do $base_path . "/acl.Oracle"
+          || die "Couldn't find ACLS for Oracle\n" . $@;
+    }
+    elsif ( $RT::DatabaseType =~ /^pg$/i ) {
+        do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
+    }
+    elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
+        do $base_path . "/acl.mysql"
+          || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
+    }
+    elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
+        return;
+    }
+    else {
+        die "Unknown RT database type";
+    }
+
+    my @acl = acl($dbh);
+    foreach my $statement (@acl) {
+        print STDERR $statement if $args{'debug'};
+        my $sth = $dbh->prepare($statement) or die $dbh->errstr;
+        unless ( $sth->execute ) {
+            die "Problem with statement:\n $statement\n" . $sth->errstr;
+        }
+    }
+}
+
+# }}}
+
+=head2 get_system_dsn
+
+Returns a dsn suitable for database creates and drops
+and user creates and drops
+
+=cut
+
+sub get_system_dsn {
+
+    my $dsn = $Handle->DSN;
+
+    #with mysql, you want to connect sans database to funge things
+    if ( $RT::DatabaseType eq 'mysql' ) {
+        $dsn =~ s/dbname=$RT::DatabaseName//;
+
+        # with postgres, you want to connect to database1
+    }
+    elsif ( $RT::DatabaseType eq 'Pg' ) {
+        $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
+    }
+    return $dsn;
+}
+
+sub insert_initial_data {
+
+    RT::InitLogging();
+
+    #connect to the db, for actual RT work
+    require RT::Handle;
+    $RT::Handle = RT::Handle->new();
+    $RT::Handle->Connect();
+
+    #Put together a current user object so we can create a User object
+    my $CurrentUser = new RT::CurrentUser();
+
+    print "Checking for existing system user...";
+    my $test_user = RT::User->new($CurrentUser);
+    $test_user->Load('RT_System');
+    if ( $test_user->id ) {
+        print "found!\n\nYou appear to have a functional RT database.\n"
+          . "Exiting, so as not to clobber your existing data.\n";
+        exit(-1);
+
+    }
+    else {
+        print "not found.  This appears to be a new installation.\n";
+    }
+
+    print "Creating system user...";
+    my $RT_System = new RT::User($CurrentUser);
+
+    my ( $val, $msg ) = $RT_System->_BootstrapCreate(
+        Name     => 'RT_System',
+        RealName => 'The RT System itself',
+        Comments =>
+'Do not delete or modify this user. It is integral to RT\'s internal database structures',
+        Creator => '1' );
+
+    unless ($val) {
+        print "$msg\n";
+        exit(1);
+    }
+    print "done.\n";
+    $RT::Handle->dbh->disconnect();
+
+}
+
+# load some sort of data into the database
+
+sub insert_data {
+    my $datafile = shift;
+
+    #Connect to the database and get RT::SystemUser and RT::Nobody loaded
+    RT::Init;
+
+    my $CurrentUser = RT::CurrentUser->new();
+    $CurrentUser->LoadByName('RT_System');
+
+    if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
+
+        print "Creating Superuser  ACL...";
+
+        my $superuser_ace = RT::ACE->new($CurrentUser);
+        $superuser_ace->_BootstrapCreate(
+                             PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
+                             PrincipalType => 'Group',
+                             RightName     => 'SuperUser',
+                             ObjectType    => 'RT::System',
+                             ObjectId      => '1' );
+
+    }
+
+    # Slurp in stuff to insert from the datafile. Possible things to go in here:-
+    # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
+
+    require $datafile
+      || die "Couldn't find initial data for import\n" . $@;
+
+    if (@Groups) {
+        print "Creating groups...";
+        foreach $item (@Groups) {
+            my $new_entry = RT::Group->new($CurrentUser);
+            my ( $return, $msg ) = $new_entry->_Create(%$item);
+            print "(Error: $msg)" unless ($return);
+            print $return. ".";
+        }
+        print "done.\n";
+    }
+    if (@Users) {
+        print "Creating users...";
+        foreach $item (@Users) {
+            my $new_entry = new RT::User($CurrentUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            print "(Error: $msg)" unless ($return);
+            print $return. ".";
+        }
+        print "done.\n";
+    }
+    if (@Queues) {
+        print "Creating queues...";
+        for $item (@Queues) {
+            my $new_entry = new RT::Queue($CurrentUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            print "(Error: $msg)" unless ($return);
+            print $return. ".";
+        }
+        print "done.\n";
+    }
+    if (@ACL) {
+        print "Creating ACL...";
+        for my $item (@ACL) {
+
+           my ($princ, $object);
+
+           # Global rights or Queue rights?
+           if ($item->{'Queue'}) {
+                $object = RT::Queue->new($CurrentUser);
+                $object->Load( $item->{'Queue'} );
+           } else {
+               $object = $RT::System;
+           }
+
+           # Group rights or user rights?
+           if ($item->{'GroupDomain'}) {
+                $princ = RT::Group->new($CurrentUser);
+               if ($item->{'GroupDomain'} eq 'UserDefined') {
+                  $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
+               } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
+                  $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
+               } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
+                        $item->{'Queue'}) {
+                  $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
+                                             Queue => $object->id);
+               } else {
+                  $princ->Load( $item->{'GroupId'} );
+               }
+           } else {
+               $princ = RT::User->new($CurrentUser);
+               $princ->Load( $item->{'UserId'} );
+           }
+
+           # Grant it
+           my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
+                                                     Right => $item->{'Right'},
+                                                     Object => $object );
+
+            if ($return) {
+                print $return. ".";
+            }
+            else {
+                print $msg . ".";
+
+            }
+
+        }
+        print "done.\n";
+    }
+    if (@CustomFields) {
+        print "Creating custom fields...";
+        for $item (@CustomFields) {
+            my $new_entry = new RT::CustomField($CurrentUser);
+            my $values    = $item->{'Values'};
+            delete $item->{'Values'};
+            my $q     = $item->{'Queue'};
+            my $q_obj = RT::Queue->new($CurrentUser);
+            $q_obj->Load($q);
+            if ( $q_obj->Id ) {
+                $item->{'Queue'} = $q_obj->Id;
+            }
+            elsif ( $q == 0 ) {
+                $item->{'Queue'} = 0;
+            }
+            else {
+                print "(Error: Could not find queue " . $q . ")\n"
+                  unless ( $q_obj->Id );
+                next;
+            }
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+
+            foreach my $value ( @{$values} ) {
+                my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
+                print "(Error: $emsg)\n" unless ($eval);
+            }
+
+            print "(Error: $msg)\n" unless ($return);
+            print $return. ".";
+        }
+
+        print "done.\n";
+    }
+
+    if (@ScripActions) {
+        print "Creating ScripActions...";
+
+        for $item (@ScripActions) {
+            my $new_entry = RT::ScripAction->new($CurrentUser);
+            my $return    = $new_entry->Create(%$item);
+            print $return. ".";
+        }
+
+        print "done.\n";
+    }
+
+    if (@ScripConditions) {
+        print "Creating ScripConditions...";
+
+        for $item (@ScripConditions) {
+            my $new_entry = RT::ScripCondition->new($CurrentUser);
+            my $return    = $new_entry->Create(%$item);
+            print $return. ".";
+        }
+
+        print "done.\n";
+    }
+
+    if (@Templates) {
+        print "Creating templates...";
+
+        for $item (@Templates) {
+            my $new_entry = new RT::Template($CurrentUser);
+            my $return    = $new_entry->Create(%$item);
+            print $return. ".";
+        }
+        print "done.\n";
+    }
+    if (@Scrips) {
+        print "Creating scrips...";
+
+        for $item (@Scrips) {
+            my $new_entry = new RT::Scrip($CurrentUser);
+            my ( $return, $msg ) = $new_entry->Create(%$item);
+            if ($return) {
+                print $return. ".";
+            }
+            else {
+                print "(Error: $msg)\n";
+            }
+        }
+        print "done.\n";
+    }
+    $RT::Handle->Disconnect();
+
+}
+
+=head2 ACLEquivGroupId
+
+Given a userid, return that user's acl equivalence group
+
+=cut
+
+sub ACLEquivGroupId {
+    my $username = shift;
+    my $user     = RT::User->new($RT::SystemUser);
+    $user->Load($username);
+    my $equiv_group = RT::Group->new($RT::SystemUser);
+    $equiv_group->LoadACLEquivalenceGroup($user);
+    return ( $equiv_group->Id );
+}
+
+sub help {
+
+    print <<EOF;
+
+$0: Set up RT's database
+
+--action        init    Initialize the database
+                drop    Drop the database. 
+                        This will ERASE ALL YOUR DATA
+                insert  Insert data into RT's database. 
+                        By default, will use RT's installation data.
+                        To use a local or supplementary datafile, specify it
+                        using the '--datafile' option below.
+                        
+                acl     Initialize only the database ACLs
+                        To use a local or supplementary datafile, specify it
+                        using the '--datadir' option below.
+                        
+                schema  Initialize only the database schema
+                        To use a local or supplementary datafile, specify it
+                        using the '--datadir' option below.
+
+--datafile /path/to/datafile
+--datadir /path/to/              Used to specify a path to find the local
+                                database schema and acls to be installed.
+
+
+--dba                           dba's username
+--dba-password                  dba's password
+--prompt-for-dba-password       Ask for the database administrator's password interactively
+
+
+EOF
+
+}
+
+1;