#!/usr/bin/perl # $Id: oi_manage,v 1.68 2003/11/17 02:17:33 lachoy Exp $ use strict; use Cwd qw( cwd ); use Data::Dumper qw( Dumper ); use ExtUtils::Manifest; use File::Copy qw( cp ); use File::Path; use Getopt::Long qw( GetOptions ); use OpenInteract::Package qw( READONLY_FILE ); use OpenInteract::PackageRepository; use OpenInteract::Startup; use Pod::Usage qw( pod2usage ); use SPOPS::Secure qw( :level :scope ); use SPOPS::HashFile (); use Text::Wrap qw( wrap ); my $VERSION = sprintf("%d.%02d", q$Revision: 1.68 $ =~ /(\d+)\.(\d+)/); $Text::Wrap::columns = 60; use constant DEBUG => 0; use constant REPOSITORY_BACKUP => 'oimanagebackup'; # Legitimate commands; anything not listed here will be kicked out and # the 'usage' stuff displayed my %COMMANDS = map { $_ => 1 } qw( initial_packages list_packages list_actions list_objects install upgrade create_skeleton export_package check_package change_spops_driver create_website install_sql test_db test_ldap install_package apply_package upgrade_package remove_package install_template dump_template remove_template refresh_doc refresh_widget update_object upgrade_website_repository upgrade_installation_repository ); # These are aliases people might type by accident instead of the # proper command; add as necessary (alias on the left, correct command # on right) my %ALIASES = ( input_template => 'install_template', install_website => 'create_website', test_database => 'test_db', install_oi => 'install', action_list => 'list_actions', object_list => 'list_objects', upgrade_oi => 'upgrade', refresh_docs => 'refresh_doc', refresh_widgets => 'refresh_widget', ); my %TMPL_DEPRECATED = map { $_ => 1 } qw( install_template dump_template remove_template ); # Subdirectories used when creating a base installation repository my @BASE_SUBDIR = qw( conf doc pkg template ); # Subdirectories used when creating a website my @WEBSITE_SUBDIR = qw( cache cache/tt cache/content conf data doc error html html/images logs mail overflow pkg template test uploads ); # Packages shipped with OI -- the 'base' packages necessary to run the system my @WEBSITE_BASE_PKG = qw( base base_box base_error base_group base_page base_security base_template base_theme base_user lookup object_activity results_manage system_doc ); # Extra packages shipped with OI -- small applications that show how things work my @WEBSITE_EXTRA_PKG = qw( full_text classified news ); # Files that are copied from the directory where you untar/gz the main # OpenInteract distribution into the base installation, when you run # the 'install' command my @INITIAL_FILES = qw( README INSTALL INSTALL.website COPYING UPGRADE ); # Separator used in output my $SEP = '========================='; # Data used for getting templates into and out of the database my $TMPL_EXT = 'tmpl'; my $META_EXT = 'meta'; my $TMPL_DIR = 'template'; # Class names we use throughout my $REPOS_CLASS = 'OpenInteract::PackageRepository'; my $PKG_CLASS = 'OpenInteract::Package'; # Address of the openinteract-dev mailing list my $DEV_LIST = 'openinteract-dev@lists.sourceforge.net'; { my ( $OPT_help, $OPT_man, $OPT_base_dir, $OPT_website_dir, $OPT_website_name, $OPT_dump_dir, $OPT_object, $OPT_package_file, $OPT_package_dir, $OPT_package_list_file, $OPT_driver, $OPT_sql_action ); my ( @OPT_package ); GetOptions( 'help|?' => \$OPT_help, 'man' => \$OPT_man, 'base_dir=s' => \$OPT_base_dir, 'website_dir=s' => \$OPT_website_dir, 'website_name=s' => \$OPT_website_name, 'package_file=s' => \$OPT_package_file, 'package_list_file=s' => \$OPT_package_list_file, 'package_dir=s' => \$OPT_package_dir, 'package=s' => \@OPT_package, 'dump_dir=s' => \$OPT_dump_dir, 'driver=s' => \$OPT_driver, 'object=s' => \$OPT_object, 'sql_action=s' => \$OPT_sql_action, ); # Give them the man page if they want (note that 'pod2usage' exits # the script after displaying the page) pod2usage( -exitstatus => 0, -verbose => 2 ) if ( $OPT_man ); # Grab the command -- if it's not there or doesn't exist in the # list of valid commands, then print the basic help page my $command = lc shift @ARGV; # Allow (for example) 'install-package' to be used for # 'install_package' $command =~ s/\-/_/g; # If the command is one of the aliases, alias it and let them know # so the user can change his/her behavior. if ( $ALIASES{ $command } ) { print "[oi_manage]: Command ($command) is an alias for $ALIASES{ $command }.\n", "Using alias and continuing...\n"; $command = $ALIASES{ $command }; } if ( $TMPL_DEPRECATED{ $command } ) { print "[oi_manage]: Command ($command) has been deprecated since\n", "templates are no longer supported in the database. Please read\n", "doc/templates.html from the source directory or \n", "WEBSITE_DIR/html/oidocs/templates.html from your website for more\n", "information on how templates are used now.\n"; exit(0); } # Now, see if the user wants help. If no command was given, then # they obviously need some help :) $OPT_help++ unless ( $COMMANDS{ $command } ); if ( $OPT_help ) { my $msg = ''; if ( $command ) { my $valid_commands = _build_valid_commands( ' ' ); $msg = "[oi_manage]: $command is not a valid command.\n\nValid commands:" . "\n\n $valid_commands\n"; } pod2usage( -exitstatus => 0, -verbose => 1, -msg => $msg ) ; } # Easiest to process, so do first if ( $command eq 'initial_packages' ) { print "\n[oi_manage]: initial_packages\n\n", "Packages installed when a new website is created:\n$SEP\n", join( "\n", @WEBSITE_BASE_PKG ), "\n"; exit(0); } # Set any defaults and do initializations $REPOS_CLASS->class_initialize; if ( ! $OPT_base_dir and $ENV{OPENINTERACT} ) { print "[oi_manage]: Using ($ENV{OPENINTERACT}) for 'base_dir'.\n"; $OPT_base_dir = $ENV{OPENINTERACT}; } if ( ! $OPT_website_dir and $ENV{OIWEBSITE} ) { print "[oi_manage]: Using ($ENV{OIWEBSITE}) for 'website_dir'.\n"; $OPT_website_dir = $ENV{OIWEBSITE}; } if ( $OPT_package_list_file ) { unless ( -f $OPT_package_list_file ) { die "[oi_manage]: The parameter 'package_list_file' must point to a valid file.\n"; } open( PKGLIST, $OPT_package_list_file ) || die "[oi_manage]: Cannot open the file ($OPT_package_list_file). Error: $!\n"; while( ) { chomp; next if ( /^\#/ ); next if ( /^\s*$/ ); push @OPT_package, $_; } close( PKGLIST ); } if ( scalar @OPT_package ) { # allows --package=x,y --package=z to be combined @OPT_package = split( /,/, join( ',', @OPT_package ) ); # Allow a special keyword for users to specify all the initial # (base) packages. This allows something like: # # oi_manage --package=INITIAL ... # oi_manage --package=INITIAL,mypkg,theirpkg ... # # and the keyword 'INITIAL' will be replaced by all the initial # packages, which can be found by doing 'oi_manage # initial_packages' my %pkg_names = map { $_ => 1 } @OPT_package; if ( exists $pkg_names{INITIAL} ) { map { $pkg_names{ $_ } = 1 } @WEBSITE_BASE_PKG; delete $pkg_names{INITIAL}; @OPT_package = sort keys %pkg_names; } } # Lop off any trailing '/' characters in directories passed in s/[\\|\/]$// for ( $OPT_base_dir, $OPT_website_dir, $OPT_package_dir, $OPT_dump_dir ); # If we've made it down here, we can be certain that $command # actually contains a valid command. Each command section can still # call 'pod2usage' as necessary if there are arguments necessary for # them to run that were not specified. # # CREATE SKELETON # my ( @error_msg ); if ( $command eq 'create_skeleton' ) { unless ( scalar @OPT_package ) { push @error_msg, "Command 'create_skeleton' requires you to specify at least one package name\n"; } unless ( -d $OPT_base_dir ) { push @error_msg, "Command 'create_skeleton' requires the parameter " . "'base_dir' to be defined and exist as a directory\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Creating package skeleton in current directory.\n$SEP\n\n"; foreach my $package_name ( @OPT_package ) { my $repository = $REPOS_CLASS->fetch( undef, { directory => $OPT_base_dir } ); my $package_name_new = eval { $PKG_CLASS->create_skeleton( $repository, $package_name ) }; my $status = $@ || 'ok'; if ( $status eq 'ok' and $package_name ne $package_name_new ) { $status .= " -- package <$package_name> renamed to <$package_name_new>"; } printf( "Package %-15s: %s\n", $package_name_new, $status ); } print "\n$SEP\nFinished with create_skeleton!\n"; exit(0); } # # INSTALL # if ( $command eq 'install' ) { unless ( $OPT_base_dir ) { push @error_msg, "Command 'install' requires the parameter 'base_dir' " . "to be set. Once set, oi_manage will create the directory for you." } if ( -d $OPT_base_dir ) { push @error_msg, "The directory <$OPT_base_dir> already exists! Command 'install' " . "wants to create this directory itself. Please remove" . "or rename the specified directory and rerun the command, " . "or choose a new one and run the command with the new directory.\n"; } unless ( -d 'pkg' ) { push @error_msg, "Command 'install' must be run from the OpenInteract source " . "directory. Please go there and run this command again.\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Running install...\n$SEP\n\n"; my $status = install_oi({ base_dir => $OPT_base_dir }); print "Packages installed:\n", join( "\n", map { print_status_line( $_ ) } @{ $status } ), "\n", "\n$SEP\nInstallation of OpenInteract complete!\n"; exit(0); } # # UPGRADE # if ( $command eq 'upgrade' ) { unless ( -d $OPT_base_dir ) { push @error_msg, "Command 'upgrade' requires the parameter 'base_dir' to be set to an " . "OpenInteract base installation directory."; } unless ( -d 'pkg' ) { push @error_msg, "Command 'upgrade' must be run from the OpenInteract source " . "directory. Please change to that directory run this command again.\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Running upgrade...\n$SEP\n\n"; my $status = upgrade_oi({ base_dir => $OPT_base_dir }); my $status_text = ''; if ( scalar @{ $status } ) { $status_text = join( "\n", map { print_status_line( $_ ) } @{ $status } ), "\n"; } print "Packages upgraded:\n", $status_text, "\n$SEP\nUpgrade of OpenInteract packages complete! You can now ", "use the packages in your websites.\n"; exit(0); } # # CREATE WEBSITE # if ( $command eq 'create_website' ) { unless ( $OPT_website_name and $OPT_website_dir and -d $OPT_base_dir ) { push @error_msg, "Command 'create_website' requires the parameters " . "'website_name' and 'website_dir' to be defined and 'base_dir' to " . "be defined and exist as a directory\n"; } # Ensure that the website directory doesn't already exist if ( -d $OPT_website_dir ) { push @error_msg, "The directory specified in " . "'website_directory' ($OPT_website_dir) already exists. " . "Please rename or remove it and then re-run this program.\n\n" ; } # Ensure that the website name fits Perl naming conventions: # no spaces, cannot begin with number, must begin with alpha # character. Note that Perl lets you begin a package with an # underscore, but we disallow that here. Also, note that '\W' # will NOT match an underscore. if ( $OPT_website_name ) { if ( $OPT_website_name =~ /\W/ ) { push @error_msg, "The parameter 'website_name' must not have any non-word " . "characters except for '_'! (Ok: A-Z, 1-9, '_')"; } if ( $OPT_website_name =~ /^\d/ ) { push @error_msg, "The parameter 'website_name' must be a valid Perl " . "identifier so it cannot begin with a number."; } if ( $OPT_website_name =~ /^_/ ) { push @error_msg, "OpenInteract requires that 'website_name' begin with " . "a letter, underscores not allowed."; } } notify_error( @error_msg ) if ( @error_msg ); print "Running create_website...\n$SEP\n\n"; my $status = create_website({ website_name => $OPT_website_name, website_dir => $OPT_website_dir, base_dir => $OPT_base_dir }); my $pkg_status = join( "\n", map { print_status_line( $_ ) } @{ $status } ), "\n"; print < $OPT_base_dir, website_dir => $OPT_website_dir }); print "\n$SEP\nFinished listing packages!\n"; exit(0); } # # LIST ACTIONS # if ( $command eq 'list_actions' ) { unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'list_actions' requires that the parameter 'website_dir' " . "refer to a valid directory."; } notify_error( @error_msg ) if ( @error_msg ); print "Running list_actions...\n$SEP\n\n"; my $action_info = list_actions({ website_dir => $OPT_website_dir }); foreach my $info ( @{ $action_info } ) { print "ACTION: $info->{action}\n", "Package: $info->{package_name} ($info->{package_version})\n"; if ( $info->{template} ) { print "Template: $info->{template}\n\n"; } else { print "Class: $info->{class}\n", "Method: $info->{method}\n\n"; } } print "\n$SEP\nFinished listing actions!\n"; exit(0); } # # LIST OBJECTS # if ( $command eq 'list_objects' ) { unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'list_objects' requires that the parameter 'website_dir' " . "refer to a valid directory."; } notify_error( @error_msg ) if ( @error_msg ); print "Running list_objects...\n$SEP\n\n"; my $object_info = list_objects({ website_dir => $OPT_website_dir }); foreach my $info ( @{ $object_info } ) { print "Aliases: $info->{alias}\n", "Class: $info->{class}\n\n"; } print "\n$SEP\nFinished listing objects!\n"; exit(0); } # # REFRESH DOC # if ( $command eq 'refresh_doc' ) { unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'refresh_doc' requires that the parameter 'website_dir' " . "refer to a valid directory."; } notify_error( @error_msg ) if ( @error_msg ); print "Running refresh_doc...\n$SEP\n\n"; my $doc_status_list = refresh_doc({ website_dir => $OPT_website_dir }); my $doc_status = ''; foreach my $status ( @{ $doc_status_list } ) { $doc_status .= "$status->{filename}: "; if ( $status->{error} ) { $doc_status .= "Error: $status->{error} "; } else { $doc_status .= "OK "; $doc_status .= "(new file copied) " if ( $status->{copied} ); $doc_status .= "(not copied) " if ( ! $status->{copied} ); } $doc_status .= "\n"; } print "The following documents were considered:\n\n", $doc_status, "\n$SEP\nFinished refreshing docs!\n"; exit(0); } # # REFRESH WIDGET # if ( $command eq 'refresh_widget' ) { unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'refresh_widget' requires that the parameter 'website_dir' " . "refer to a valid directory."; } notify_error( @error_msg ) if ( @error_msg ); print "Running refresh_widget...\n$SEP\n\n"; my $widget_status_list = refresh_widget({ website_dir => $OPT_website_dir }); my $widget_status = ''; foreach my $status ( @{ $widget_status_list } ) { $widget_status .= "$status->{filename}: "; if ( $status->{error} ) { $widget_status .= "Error: $status->{error} "; } else { $widget_status .= "OK "; $widget_status .= "(new file copied) " if ( $status->{copied} ); $widget_status .= "(not copied) " if ( ! $status->{copied} ); } $widget_status .= "\n"; } print "The following widgets were considered:\n\n", $widget_status, "\n$SEP\nFinished refreshing widgets!\n"; exit(0); } # # EXPORT PACKAGE # if ( $command eq 'export_package' ) { print "Running export_package...\n$SEP\n\n"; if ( $OPT_package_dir ) { unless ( -d $OPT_package_dir ) { push @error_msg, "Command 'export_package' requires the 'package_dir' parameter " . "to point to an existing directory\n"; } unless ( scalar @OPT_package ) { push @error_msg, "Command 'export_package' requires one or more packages to " . "be specified if you use the 'package_dir' parameter. You may also simply 'cd' " . "to the directory with the package and run this command without any parameters.\n"; } } notify_error( @error_msg ) if ( @error_msg ); my $status = export_package({ package_dir => $OPT_package_dir, package => \@OPT_package } ); print "Status of the packages you requested to be exported:\n", join( "\n", map { print_status_line( $_ ) } @{ $status } ), "\n", "\n$SEP\nFinished export_package!\n"; exit(0); } # # CHECK PACKAGE # if ( $command eq 'check_package' ) { my $use_dir = 0; if ( $OPT_package_dir and ! -d $OPT_package_dir ) { push @error_msg, "Command 'check_package' requires the parameter " . "'package_dir' exist as a directory if you choose to specify it.\n"; $use_dir++; } if ( $OPT_website_dir and ! -d $OPT_website_dir ) { push @error_msg, "Command 'check_package' requires the parameter " . "'website_dir' exist as a directory if you choose to specify it.\n"; $use_dir++; } if ( $OPT_base_dir and ! -d $OPT_base_dir ) { push @error_msg, "Command 'check_package' requires the parameter " . "'base_dir' exist as a directory if you choose to specify it.\n"; $use_dir++; } if ( $use_dir ) { unless ( scalar @OPT_package ) { push @error_msg, "Command 'check_package' requires that you specify one or more packages " . "if you specify one of the parameters 'package_dir', 'base_dir', 'website_dir'\n"; } } notify_error( @error_msg ) if ( @error_msg ); print "Running check_package...\n$SEP\n\n"; my $status = []; if ( $OPT_package_dir ) { if ( scalar @OPT_package ) { foreach my $package_name ( @OPT_package ) { push @{ $status }, $PKG_CLASS->check({ package_dir => "$OPT_package_dir/$package_name" }); } } else { push @{ $status }, $PKG_CLASS->check( { package_dir => $OPT_package_dir } ); } } elsif ( $use_dir ) { $status = check_installed_package({ package => \@OPT_package, website_dir => $OPT_website_dir, base_dir => $OPT_base_dir }); } else { push @{ $status }, $PKG_CLASS->check({ package_dir => cwd }); } print "Status of the packages you requested to be checked:\n", join( "\n", map { print_status_line( $_, { same_line => 1 } ) } @{ $status } ), "\n", "\n$SEP\nFinished with check_package!\n"; exit(0); } # # INSTALL PACKAGE # if ( $command eq 'install_package' ) { unless ( -d $OPT_base_dir ) { push @error_msg, "Command 'install_package' requires the parameter 'base_dir' " . "to be set to an existing directory\n"; } unless ( -f $OPT_package_file ) { push @error_msg, "Command 'install_package' requires the parameter 'package_file' " . "to be set to an existing package distribution \n"; } if ( my $msg = base_dir_permission( $OPT_base_dir ) ) { push @error_msg, "Cannot run 'install_package' because you have " . "insufficient access to the base installation " . "directory. (Message: $msg.) Please try this " . "action again as a user that has sufficient " . "permission.\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Running install_package...\n$SEP\n\n"; my $repository = $REPOS_CLASS->fetch( undef, { directory => $OPT_base_dir } ); $repository->backup({ filename => REPOSITORY_BACKUP }); my $num_pkg = scalar @{ $repository->fetch_all_packages }; my $pkg = $PKG_CLASS->install_distribution({ repository => $repository, package_file => $OPT_package_file }); if ( $pkg ) { print "Installed package: $pkg->{name}-$pkg->{version}\n"; } else { print "Error installating package.\n"; my $current_num_pkg = scalar @{ $repository->fetch_all_packages }; if ( $num_pkg != $current_num_pkg ) { $repository->restore_backup( REPOSITORY_BACKUP ); } } print "\n$SEP\nFinished installing package!\n"; exit(0); } # # APPLY PACKAGE # if ( $command eq 'apply_package' ) { unless ( scalar @OPT_package ) { push @error_msg, "Command 'apply_package' requires one or more package names to install."; } unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'apply_package' requires the parameter " . "'website_dir' be set to an existing directory\n"; } if ( $OPT_base_dir and ! -d $OPT_base_dir ) { push @error_msg, "Command 'apply_package' requires the parameter " . "'base_dir' be either not set or set to an existing directory\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Running apply_package...\n$SEP\n\n"; my $status = apply_package({ package => \@OPT_package, base_dir => $OPT_base_dir, website_dir => $OPT_website_dir }); print "Status of the packages you requested to be applied:\n", join( "\n", map { print_status_line( $_ ) } @{ $status } ), "\n", "\n$SEP\nFinished applying package!\n"; exit(0); } # # REMOVE PACKAGE # if ( $command eq 'remove_package' ) { unless ( scalar @OPT_package ) { push @error_msg, "Command 'remove_package' requires one or more package names to remove."; } unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'remove_package' requires the parameter " . "to be set to existing directories\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Running remove_package...\n$SEP\n\n"; my $status = remove_package({ package => \@OPT_package, website_dir => $OPT_website_dir }); my $removal_status = join( "\n", map { print_status_line( $_ ) } @{ $status } ); print < \@OPT_package, website_dir => $OPT_website_dir }); my $pkg_status = join( "\n", map { print_status_line( $_ ) } @{ $status } ); print < \@OPT_package, website_dir => $OPT_website_dir }); my $pkg_status = join( "\n", map { print_status_line( $_ ) } @{ $status } ); print < \@OPT_package, website_dir => $OPT_website_dir }); my $pkg_status = join( "\n", map { print_status_line( $_ ) } @{ $status } ); print < \@OPT_package, dump_dir => $OPT_dump_dir, website_dir => $OPT_website_dir }); my $pkg_status = join( "\n", map { print_status_line( $_ ) } @{ $status } ); print < \@OPT_package, website_dir => $OPT_website_dir, action => $OPT_sql_action }); my $pkg_status = join( "\n$SEP\n", map { print_status_line( $_ ) } @{ $status } ); my @notes = grep { $_->{note} } @{ $status }; my $note_status = ''; foreach my $status ( @notes ) { $note_status .= "Package $status->{name}-$status->{version}\n" . "$status->{note}\n"; } $note_status ||= 'none'; print < $OPT_website_dir, driver => $OPT_driver }); my $changed = ( scalar @{ $status->{changed} } ) ? '--' . join( "\n--", @{ $status->{changed} } ) : ''; my $not = ( scalar @{ $status->{not_changed} } ) ? '--' . join( "\n--", @{ $status->{not_changed} } ) : ''; print < $OPT_website_dir, object_tag => $OPT_object }); my $error_msg = ( ref $status->{error} eq 'ARRAY' && scalar @{ $status->{error} } > 0 ) ? join( "\n -- ", scalar @{ $status->{error} }, @{ $status->{error} } ) : 'None'; print <{count} Objects updated: $status->{save} Errors: $error_msg $SEP Finished update_object! OBMSG exit(0); } # # TEST DB # if ( $command eq 'test_db' ) { unless ( -d $OPT_website_dir ) { push @error_msg, "Command 'test_db' requires the parameter " . "'website_dir' to be set to an existing directory\n"; } notify_error( @error_msg ) if ( @error_msg ); print "Running test_db...\n$SEP\n\n"; my $status = test_db_connection({ website_dir => $OPT_website_dir }); my $status_info = print_status_line( $status ); print < $OPT_website_dir }); my $status_info = print_status_line( $status ); print <new({ filename => $hash_file, perm => 'new' }); delete $hash->{perm}; delete $hash->{filename}; while ( my ( $k, $v ) = each %gdbm ) { next if ( $k eq 'perm' or $k eq 'filename' ); my ( $class, $pkg_key ) = split /\-\-/, $k; my ( $data ); eval $v; $hash->{ $pkg_key } = $data; DEBUG && _w( 1, "Wrote entry for package ($pkg_key)" ); } untie %gdbm; $hash->{META_INF}{base_dir} = $dir; eval { $hash->save({ dumper_level => 1 }) }; if ( $@ ) { return undef; } rename( $gdbm_file, "$gdbm_file.old" ); return 1; } sub _build_valid_commands { my ( $spacer ) = @_; $spacer ||= ''; my $valid_commands = undef; my $count = 1; foreach my $command ( sort keys %COMMANDS ) { $valid_commands .= "$command | "; $valid_commands .= "\n$spacer" if ( $count % 4 == 0 ); $count++; } $valid_commands =~ s/ \| $//; return $valid_commands; } sub notify_error { my @msgs = @_; my $error_msg = "[oi_manage]\n"; foreach ( @msgs ) { $error_msg .= " -- " . wrap( undef, undef, $_) . "\n\n"; } pod2usage( -exitstatus => 0, -verbose => 1, -msg => $error_msg ); } # Test to see whether this user has permission to write to the base # installation directory and the base installation package # repository. Returns error message on error, undef on success. (Hey, # just like 'system'!) # We should also test this in the package repository module... sub base_dir_permission { my ( $base_dir ) = @_; return "Directory not writable!" unless ( -w $base_dir ); return "Repository not writable!" unless ( -w "$base_dir/conf/package_repository.perl" ); return "Package directory not writeable!" unless ( -w "$base_dir/pkg" ); return undef; } # # list_packages # sub list_packages { my ( $p ) = @_; my $use_dir = $p->{website_dir} || $p->{base_dir}; my $repository = $REPOS_CLASS->fetch( undef, { directory => $use_dir }); my $pkg_list = $repository->fetch_all_packages(); my $package_location = ( $p->{website_dir} ) ? 'website directory' : 'installation directory'; print "Available packages in the $package_location:\n"; foreach my $pkg ( @{ $pkg_list } ) { print "Package: $pkg->{name}-$pkg->{version}\n", "Installed: $pkg->{installed_on}\n", "Directory: $use_dir/$pkg->{package_dir}\n\n"; } } # # refresh_doc # sub refresh_doc { my ( $p ) = @_; my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'refresh_doc' }) } my $base_doc = "$bc->{base_dir}/doc"; my $html_doc = "$p->{website_dir}/html/oi_docs"; opendir( BASE, $base_doc ) || die "Cannot open base documentation directory! ($!)"; opendir( SITE, $html_doc ) || die "Cannot open website documentation directory! ($!)"; my %base_info = map { $_ => (stat( "$base_doc/$_" ))[7] } grep { -f "$base_doc/$_" } grep ! /old$/, readdir( BASE ); my %site_info = map { $_ => (stat( "$html_doc/$_" ))[7] } grep { -f "$html_doc/$_" } grep ! /old$/, readdir( SITE ); close( SITE ); close( BASE ); my @status = (); foreach my $base_filename ( keys %base_info ) { if ( $base_info{ $base_filename } != $site_info{ $base_filename } ) { eval { cp( "$base_doc/$base_filename", "$html_doc/$base_filename" ) || die $! }; if ( $@ ) { push @status, { filename => $base_filename, ok => 0, error => $@ }; } else { push @status, { filename => $base_filename, ok => 1, copied => 1 }; } } else { push @status, { filename => $base_filename, ok => 1, copied => 0 }; } } return \@status; } # # refresh_widget # sub refresh_widget { my ( $p ) = @_; my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'refresh_widget' }); } my $base_widget_dir = "$bc->{base_dir}/template"; my $site_widget_dir = "$p->{website_dir}/template"; unless ( -d $site_widget_dir ) { File::Path::mkpath( $site_widget_dir, 0775, undef ); } opendir( BASE, $base_widget_dir ) || die "Cannot open base widget directory! ($!)"; opendir( SITE, $site_widget_dir ) || die "Cannot open website widget directory! ($!)"; my %base_info = map { $_ => (stat( "$base_widget_dir/$_" ))[7] } grep { -f "$base_widget_dir/$_" } grep ! /old$/, readdir( BASE ); my %site_info = map { $_ => (stat( "$site_widget_dir/$_" ))[7] } grep { -f "$site_widget_dir/$_" } grep ! /old$/, readdir( SITE ); close( SITE ); close( BASE ); my %no_write = map { $_ => 1 } $PKG_CLASS->read_readonly_file( $site_widget_dir ); my @status = (); foreach my $base_filename ( keys %base_info ) { next if ( $no_write{ $base_filename } ); if ( $base_info{ $base_filename } != $site_info{ $base_filename } ) { eval { cp( "$base_widget_dir/$base_filename", "$site_widget_dir/$base_filename" ) || die $! }; if ( $@ ) { push @status, { filename => $base_filename, ok => 0, error => $@ }; } else { push @status, { filename => $base_filename, ok => 1, copied => 1 }; } } else { push @status, { filename => $base_filename, ok => 1, copied => 0 }; } } return \@status; } # # list_actions # sub list_actions { my ( $p ) = @_; my $R = OpenInteract::Startup->setup_static_environment( $p->{website_dir}, '', { temp_lib => 'lazy' } ); my $CONFIG = $R->CONFIG; my @actions = (); ACTION: foreach my $action ( sort keys %{ $CONFIG->{action} } ) { if ( ! $action or $action =~ /^_/ ) { my $redir_action = $CONFIG->{action}{ $action }{redir}; push @actions, { action => "SYSTEM ($action) redirected to ($redir_action)" }; next ACTION; } push @actions, { action => $action, package_name => $CONFIG->{action}{ $action }{package_name}, package_version => $CONFIG->{action}{ $action }{package_version}, class => $CONFIG->{action}{ $action }{class}, method => $CONFIG->{action}{ $action }{method}, template => $CONFIG->{action}{ $action }{tmpl_name} }; } return \@actions; } # # list_objects # sub list_objects { my ( $p ) = @_; my $R = OpenInteract::Startup->setup_static_environment( $p->{website_dir}, '', { temp_lib => 'lazy' } ); my $CONFIG = $R->CONFIG; my @objects = (); OBJECT: foreach my $alias ( sort keys %{ $CONFIG->{SPOPS} } ) { next OBJECT if ( ! $alias or $alias =~ /^_/ ); my @alias_list = ( $alias ); if ( ref $CONFIG->{SPOPS}{alias} eq 'ARRAY' ) { push @alias_list, @{ $CONFIG->{SPOPS}{alias} }; } push @objects, { alias => join ( ', ', @alias_list ), class => $CONFIG->{SPOPS}{ $alias }{class} }; } return \@objects; } # # install_oi # # requires parameter base_dir be defined but not exist yet. sub install_oi { my ( $p ) = @_; my $pwd = cwd; # Create the new install dir if ( -d $p->{base_dir} ) { die "[oi_manage]: Installation directory ($p->{base_dir}) already ", "exists! Please remove it before continuing.\n"; } mkdir( $p->{base_dir}, 0775 ) || die "Cannot create installation directory! Error: $!"; # Make our subdirectories foreach my $sub_dir ( @BASE_SUBDIR ) { mkdir( "$p->{base_dir}/$sub_dir", 0775 ) || die "Cannot create dir ($p->{base_dir}/$sub_dir). Error: $!"; } # Copy some files that are specified above foreach my $file_frag ( @INITIAL_FILES ) { cp( $file_frag, "$p->{base_dir}/$file_frag" ) || _w( 0, "Could not copy ($file_frag) to new dir! Error: $!" ); } # Now copy over everything in the 'conf/', 'doc/' and 'template/' # directories COPYDIR: foreach my $dir_name ( qw( conf doc template ) ) { eval { opendir( CPDIR, $dir_name ) || die $! }; if ( $@ ) { _w( 0, "Cannot open ($dir_name) for reading! ($@) Continuing..." ); next COPYDIR; } my @file_list = grep ! /old$/, grep { -f "$dir_name/$_" } readdir( CPDIR ); closedir( CPDIR ); foreach my $file_name ( @file_list ) { cp( "$dir_name/$file_name", "$p->{base_dir}/$dir_name/$file_name" ) || _w( 0, "Could not copy file ($dir_name/$file_name)", "to new directory! Error: $!" ); } } # First match up the package names in our initial and extra list # with the filename of the package distributed. Then install them # to the repository and return the status of the install. my $repository = $REPOS_CLASS->new({ directory => $p->{base_dir}, perm => 'new' }); my $package_match = _match_base_packages( cwd ); my @package_status = (); PKG: foreach my $package_name ( @WEBSITE_BASE_PKG, @WEBSITE_EXTRA_PKG ) { DEBUG && _w( 1, " -- Trying to install package $package_name" ); my $status = { ok => 0, name => $package_name }; unless ( $package_match->{ $package_name } ) { $status->{msg} = "Cannot match package ($package_name) to file " . "in distribution! Not installed!"; push @package_status, $status; } my $pkg = eval { $PKG_CLASS->install_distribution({ repository => $repository, package_file => $package_match->{ $package_name } }) }; if ( $@ ) { _w( 0, "Error installing: $@" ); $status->{msg} = $@; } else { $status->{ok} = 1; $status->{version} = $pkg->{version}; } push @package_status, $status; } return \@package_status; } # # upgrade_oi # # this could be cleaner but it's quick # sub upgrade_oi { my ( $p ) = @_; my $pwd = cwd; # First, see if the package repository is currently in GDBM format # and if so, upgrade it. my $gdbm_file = "$p->{base_dir}/conf/package_install.gdbm"; DEBUG && _w( 1, "Test to see if GDBM repository ($gdbm_file) exists." ); if ( -f $gdbm_file ) { DEBUG && _w( 1, "GDBM-based repository exists, need to transfer", "contents to text-based." ); my $rv = upgrade_repository( $p->{base_dir} ); unless ( $rv ) { die < -- -- and it will be a seamless transition. If you do not do this the -- websites will fail to run and everybody will tease you! REPOSMSG } # Open up the package repository my $repository = $p->{repository} || $REPOS_CLASS->fetch( undef, { directory => $p->{base_dir} } ); my $base_dir = $repository->{META_INF}{base_dir}; # Copy some files that are specified above foreach my $file_frag ( @INITIAL_FILES ) { rename( "$base_dir/$file_frag", "$base_dir/$file_frag.old" ) || _w( 0, "Could not rename $base_dir/$file_frag -> ", "$base_dir/$file_frag.old" ); cp( $file_frag, "$base_dir/$file_frag" ) || _w( 0, "Could not copy ($file_frag) to new dir! Error: $!" ); } # Now copy over everything in the 'conf/', 'doc/' and 'template/' # directories COPYDIR: foreach my $dir_name ( qw( conf doc template ) ) { eval { opendir( CPDIR, $dir_name ) || die $! }; if ( $@ ) { _w( 0, "Cannot open ($dir_name) for reading! ($@) Continuing..." ); next COPYDIR; } eval { File::Path::mkpath( "$base_dir/$dir_name", undef, 0775 ) }; if ( $@ ) { _w( 0, "Cannot create path ($base_dir/$dir_name): $@" ); next; } my @file_list = grep ! /old$/, grep { -f "$dir_name/$_" } readdir( CPDIR ); closedir( CPDIR ); foreach my $file_name ( @file_list ) { rename( "$base_dir/$dir_name/$file_name", "$base_dir/$dir_name/$file_name.old" ); cp( "$dir_name/$file_name", "$base_dir/$dir_name/$file_name" ) || _w( 0, "Could not copy file ($dir_name/$file_name) ", "to new directory! Error: $!" ); } } # Install all the packages and return the status of installing them my $package_match = _match_base_packages( cwd ); my @package_status = (); PKG: foreach my $package_name ( @WEBSITE_BASE_PKG, @WEBSITE_EXTRA_PKG ) { DEBUG && _w( 1, " -- Trying to install package $package_name" ); my $status = { ok => 0, name => $package_name }; unless ( -f $package_match->{ $package_name } ) { $status->{msg} = "Cannot match $package_name to file in " . "distribution! Not installed!"; push @package_status, $status; next; } my $info = eval { $PKG_CLASS->install_distribution({ repository => $repository, package_file => $package_match->{ $package_name } }) }; DEBUG && _w( 2, "Results of install_distribution() for package ", "($package_name)\n", Dumper( $info ) ); # If we get an error, assume it's because this package already # exists; otherwise, just mark the status if ( $@ ) { $status->{msg} = $@; } else { $status->{ok} = 1; $status->{version} = $info->{version}; } push @package_status, $status; } return \@package_status; } sub _match_base_packages { my ( $dir ) = @_; unless ( -d $dir ) { die "No valid dir specified to find base packages! (Given: $dir)\n"; } # Match up the package names in our initial and extra list with the # filename of the package distributed opendir( PKG, "$dir/pkg/" ) || die "Cannot open package dir ($dir/pkg/) for reading! Error: $!"; my @package_files = grep { -f "pkg/$_" } grep /\.tar\.gz$/, readdir( PKG ); closedir( PKG ); my ( %package_match ); foreach my $package_file ( @package_files ) { my ( $package_name ) = $package_file =~ /^(.*)\-\d+\.\d+\.tar\.gz$/; $package_match{ $package_name } = "$dir/pkg/$package_file"; } DEBUG && _w( 2, "Packages matched: ", Dumper( \%package_match ) ); return \%package_match; } # # check_installed_package # # wrapper around check_package for installed packages # # Parameters: # package (\@) # website_dir | base_dir sub check_installed_package { my ( $p ) = @_; my $app_name = undef; my $use_dir = $p->{base_dir}; if ( $p->{website_dir} ) { $use_dir = $p->{website_dir}; my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'check_installed_package' }); } $app_name = $bc->{website_name}; } my $repository = $REPOS_CLASS->fetch( undef, { directory => $use_dir } ); my @status = (); PKG: foreach my $package_name ( @{ $p->{package} } ) { my $info = $repository->fetch_package_by_name({ name => $package_name }); unless ( $info ) { push @status, { name => $package_name, ok => 0, msg => 'Package $package_name not found.' }; next PKG; } my $package_dir = join( '/', $use_dir, $info->{package_dir} ); push @status, $PKG_CLASS->check({ package_dir => $package_dir, website_name => $app_name, package_name => $package_name }); } return \@status; } # # export_package # sub export_package { my ( $p ) = @_; my @status = (); my @dir_list = (); my $pwd = cwd; # Each package specified: try to just open up the # $pkg_dir/$pkg_name directory -- this normally works, because # when you're doing batches of packages at once you're probably # (hopefully) doing it from a devel directory where the packages # have no version numbers; if that directory isn't found, we try # to find a directory name that matches the package name plus a # dash and the beginning of a version number. For instance: # # $package_name = 'base'; # @dirs = qw( 'base_config', 'base_doodad', 'base-1.02' ); # -- even through the first and second *begin* with 'base' it is # not followed by a '-' and then a number, so it will only match # the third. if ( ref $p->{package} eq 'ARRAY' and scalar @{ $p->{package} } ) { opendir( PKGDIR, $p->{package_dir} ) || die "Cannot read $p->{package_dir}: $!"; my %package_dirs = map { $_ => 1 } grep ! /^\./, grep { -d "$p->{package_dir}/$_" } readdir( PKGDIR ); closedir( PKGDIR ); DEBUG && _w( 1, "Found package directories: ", Dumper( \%package_dirs ) ); PKG: foreach my $package_name ( @{ $p->{package} } ) { DEBUG && _w( 1, "Trying to export ($package_name)" ); my $target_dir = "$p->{package_dir}/$package_name"; DEBUG && _w( 1, "Testing target $target_dir" ); unless ( -d $target_dir ) { $target_dir = undef; while ( my ( $check_dir, $v ) = each %package_dirs ) { if ( $check_dir =~ /^$package_name\-\d/ ) { $target_dir = "$p->{package_dir}/$check_dir"; last; } } unless ( $target_dir ) { push @status, { ok => 0, name => $package_name, msg => "Could not find matching directory. No distribution created." }; next PKG; } } DEBUG && _w( 1, "Found target directory $package_name: $target_dir" ); push @dir_list, $target_dir; } } # If no package_dir specified, just use the current directory, # which is what most people will probably use. else { push @dir_list, $pwd; } foreach my $package_dir ( @dir_list ) { DEBUG && _w( 1, "Exporting directory: $package_dir" ); my $status_info = $PKG_CLASS->export({ directory => $package_dir }); if ( scalar @dir_list > 1 and $status_info->{file} ) { my ( $base_file ) = $status_info->{file} =~ m|^.*/(.*)$|; rename( $status_info->{file}, "$pwd/$base_file" ); $status_info->{file} = "$pwd/$base_file"; } push @status, { ok => 1, name => $status_info->{name}, msg => "Version $status_info->{version} distribution to $status_info->{file}" }; } return \@status; } # # create_website # sub create_website { my ( $p ) = @_; # Create the main directory and subdirectories mkdir( $p->{website_dir}, 0775 ) || die "[oi_manage]: Cannot complete comamnd: cannot create ", "website directory ($p->{website_dir}): $!"; foreach my $sub_dir ( @WEBSITE_SUBDIR, $p->{website_name} ) { mkdir ( "$p->{website_dir}/$sub_dir", 0775 ) || die "[oi_manage]: Cannot complete command: failed ", "creating subdir ($p->{website_dir}/$sub_dir): $!"; } # Copy over all OI system documentation. (This can be refreshed to # the latest version from the base installation with the # 'refresh_doc' oi_manage command.) my $html_doc_root = "$p->{website_dir}/html/oi_docs"; eval { mkdir( $html_doc_root, 0775 ) || die $! }; if ( $@ ) { _w( 0, "Cannot create dir 'html/oi_docs' in website directory, \n", "so I cannot copy OpenInteract documentation to viewable ", "location. Continuing..." ); } else { my $oi_doc_root = "$p->{base_dir}/doc"; eval { opendir( DOC, $oi_doc_root ) || die $! }; if ( $@ ) { _w( 0, "Cannot read system docs from ($oi_doc_root). Please ask\n", "your system administrator to install it there..\n", "Continuing without system documentation available." ); } else { my @file_list = grep ! /old$/, grep { -f "$oi_doc_root/$_" } readdir( DOC ); foreach my $file_name ( @file_list ) { cp( "$oi_doc_root/$file_name", "$html_doc_root/$file_name" ) || _w( 0, "Could not copy ($file_name) from system ", "documentation directory ($!)." ); } } } # Copy over all template widgets. (This can be refreshed to the # latest version from the base installatin with the # 'refresh_widgets' oi_manage command.) my $site_template_root = "$p->{website_dir}/template"; my $base_template_root = "$p->{base_dir}/template"; eval { opendir( DOC, $base_template_root ) || die $! }; if ( $@ ) { _w( 0, "Cannot read template widgets from ($base_template_root).", "Please ask your administrator to install it there.\n", "Continuing without template widgets available." ); } else { my @file_list = grep ! /old$/, grep { -f "$base_template_root/$_" } readdir( DOC ); foreach my $file_name ( @file_list ) { cp( "$base_template_root/$file_name", "$site_template_root/$file_name" ) || _w( 0, "Could not copy ($file_name) from base template widget ($!)." ); } my $template_msg = < "conf/apache.dat", "conf/sample-Stash.pm" => "$p->{website_name}/Stash.pm", "conf/sample-httpd_modperl.conf" => "conf/httpd_modperl.conf", "conf/sample-httpd_modperl_solo.conf" => "conf/httpd_modperl_solo.conf", "conf/sample-httpd_static.conf" => "conf/httpd_static.conf", "conf/sample-base.conf" => "conf/base.conf", "conf/sample-server.perl" => "conf/server.perl", "conf/sample-server.ini" => "conf/server.ini", "conf/sample-startup.pl" => "conf/startup.pl", "conf/sample-override_spops.ini" => "conf/sample-override_spops.ini", "INSTALL.website" => "INSTALL.website" ); my $replace_keys = [ '%%INTERACT_DIR%%', '%%WEBSITE_DIR%%', '%%WEBSITE_NAME%%', '%%STASH_CLASS%%', "OpenInteract\:\:Sample" ]; my $replace_vals = [ $p->{base_dir}, $p->{website_dir}, $p->{website_name}, "$p->{website_name}\:\:Stash", "$p->{website_name}\:\:" ]; foreach my $from_name ( keys %file_match ) { my $from_file = join( '/', $p->{base_dir}, $from_name ); my $to_file = join( '/', $p->{website_dir}, $file_match{ $from_name } ); $PKG_CLASS->replace_and_copy({ from_file => $from_file, to_file => $to_file, from_text => $replace_keys, to_text => $replace_vals }); } # First apply all the packages... my $status_list = apply_package({ package => \@WEBSITE_BASE_PKG, base_dir => $p->{base_dir}, website_name => $p->{website_name}, website_dir => $p->{website_dir} }); # Then create a nowrite flag for the index.html page my $html_msg = <{website_dir}/html", $html_msg ); return $status_list; } # # apply_package # # requires: base_dir, website_dir sub apply_package { my ( $p ) = @_; # Grab the base configuration information to find the website_name unless ( $p->{base_dir} and $p->{website_name} ) { my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'apply_package' }); } $p->{website_name} ||= $bc->{website_name}; $p->{base_dir} ||= $bc->{base_dir}; } DEBUG && _w( 1, "Incoming info (after base): ", Dumper( $p ) ); my $CONFIG = OpenInteract::Startup->create_config({ website_dir => $p->{website_dir} }); my $base_repos = $REPOS_CLASS->fetch( undef, { directory => $p->{base_dir} } ); my $website_repos = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir}, perm => 'write-new' } ); # Ensure that the packages passed in are actual packages by # fetching the ones named my $pkg_exist = $base_repos->verify_package( @{ $p->{package} } ); my $pkg_exist_list = ( ref $pkg_exist eq 'ARRAY' ) ? $pkg_exist : [ $pkg_exist ]; # We create a hash of all the packages passed in and for each # existing one remove it -- anything left after processing all # packages didn't get processed and is therefore not a verified # (good) package to begin with my %pkg_track = map { lc $_ => 1 } @{ $p->{package} }; my @status = (); # Cycle through the installed packages -- first see if the package # already exists in the website; if not, change a couple of # parameters and run the 'install_to_website' method of the # package, then save the package to our website db PACKAGE: foreach my $info ( @{ $pkg_exist_list } ) { delete $pkg_track{ lc $info->{name} }; DEBUG && _w( 1, "Try to apply package $info->{name}-$info->{version}" ); my $this_status = { ok => 0, name => $info->{name} }; my $app_pkg = $website_repos->fetch_package_by_name({ name => $info->{name} }); if ( $app_pkg ) { $this_status->{msg} = "This package (Version: $app_pkg->{version}) already exists in " . "the website. Please run 'upgrade_package' to upgrade it."; push @status, $this_status; next PACKAGE; } $website_repos->backup({ filename => REPOSITORY_BACKUP }); my $num_pkg = scalar @{ $website_repos->fetch_all_packages() }; my $website_info = \%{ $info }; $this_status->{version} = $website_info->{version}; $website_info->{website_dir} = $p->{website_dir}; $website_info->{website_name} = $p->{website_name}; $website_info->{installed_on} = $base_repos->now; eval { $PKG_CLASS->install_to_website( $base_repos, $website_repos, $website_info, $CONFIG ) }; if ( $@ ) { $this_status->{msg} = "Cannot install package to website. Error: $@\n"; my $current_num_pkg = scalar @{ $website_repos->fetch_all_packages() }; if ( $num_pkg != $current_num_pkg ) { $website_repos->restore_backup; } } else { # If the status is ok, open up the 'INSTALL' file from the # base installation directory (if it exists) and put it # into the {notes} field of the status. This is used for # upgrade/other notes and displayed in 'print_status_line' # below. $this_status->{ok}++; my $notes_file = join( '/', $website_info->{base_dir}, $website_info->{package_dir}, 'INSTALL' ); if ( -f $notes_file ) { eval { open( NOTES, $notes_file ) || die $! }; if ( $@ ) { $this_status->{notes} = "Failed to open package installation " . "notes file. Error: $@"; } else { local $/ = undef; $this_status->{notes} = ; close( NOTES ); } } } push @status, $this_status; } # Now report status of the packages not verified foreach my $pkg_name ( sort keys %pkg_track ) { push @status, { ok => 0, msg => "Package could not be verified -- either it " . "doesn't exist in base install or there's " . "a problem with the base install package." }; } return \@status; } # # remove_package # # Required: website_dir, package list sub remove_package { my ( $p ) = @_; my @status = (); my $repository = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir}, perm => 'write' } ); PKG: foreach my $package_name ( @{ $p->{package} } ) { my $this_status = { ok => 0, name => $package_name }; my $info = $repository->fetch_package_by_name({ name => $package_name }); unless ( $info ) { $this_status->{msg} = 'Package does not exist in this website!'; push @status, $this_status; next PKG; } $this_status->{version} = $info->{version}; eval { $PKG_CLASS->remove( $repository, $info ) }; if ( $@ ) { $this_status->{msg} = "Failed to remove. Error: $@"; } else { $this_status->{ok}++; } push @status, $this_status; } return \@status; } # # upgrade_package # # Accomplish this by removing the old package definition and then # applying the new package sub upgrade_package { my ( $p ) = @_; unless ( $p->{base_dir} ) { my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'upgrade_package' }) } $p->{base_dir} ||= $bc->{base_dir}; $p->{website_name} ||= $bc->{website_name}; } my $CONFIG = OpenInteract::Startup->create_config({ website_dir => $p->{website_dir} }); my $base_repository = $REPOS_CLASS->fetch( undef, { directory => $p->{base_dir} } ); my $website_repository = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir}, perm => 'write' } ); my @status = (); PKG: foreach my $package_name ( @{ $p->{package} } ) { my $this_status = { ok => 0, name => $package_name }; # First, ensure that the package exists in the local app my $app_info = $website_repository->fetch_package_by_name({ name => $package_name }); unless ( $app_info ) { $this_status->{msg} = "Package does not exist in website -- run " . "'apply_package' to install from OpenInteract " . "installation to this website. No action taken."; push @status, $this_status; next PKG; } # Next, ensure that the package exists in the main app my $base_info = $base_repository->fetch_package_by_name({ name => $package_name }); unless ( $base_info ) { $this_status->{msg} = "Package does not exist in base install -- cannot upgrade."; push @status, $this_status; next PKG; } # Ensure that the versions are different unless ( $base_info->{version} > $app_info->{version} ) { $this_status->{msg} = "Installed package ($base_info->{version}) does not have version higher " . "than package in website ($app_info->{version}). No action taken."; push @status, $this_status; next PKG; } # Looks good; let's do it. First remove the website package my $rmv_status = { ok => 0 }; my $upg_status = { ok => 0, msg => 'Did not run since remove failed' }; my $app_status = { ok => 0 }; $PKG_CLASS->remove( $website_repository, $app_info ); $rmv_status->{ok}++; $rmv_status->{msg} = "removed package version $app_info->{version} ok"; # If removal went ok, apply the package from the install directory my $website_info = \%{ $base_info }; $website_info->{website_name} = $p->{website_name}; $PKG_CLASS->install_to_website( $base_repository, $website_repository, $website_info, $CONFIG ); $upg_status->{ok}++; $upg_status->{msg} = "upgraded to version $base_info->{version}"; $this_status->{ok}++; $this_status->{msg} = " Removal: " . print_status_line( $rmv_status, { same_line => 1 } ) . "\n" . " Apply: " . print_status_line( $upg_status, { same_line => 1 } ); my $notes_file = join( '/', $website_info->{base_dir}, $website_info->{package_dir}, 'UPGRADE' ); if ( -f $notes_file ) { eval { open( NOTES, $notes_file ) || die $! }; if ( $@ ) { $this_status->{notes} = "Failed to open package installation notes file. Error: $@"; } else { local $/ = undef; $this_status->{notes} = ; close( NOTES ); } } push @status, $this_status; } return \@status; } # # install_sql # # TODO: Make this routine less repetitive, now that we've split it # into three distinct sections sub install_sql { my ( $p ) = @_; require OpenInteract::SQLInstall; $p->{action} ||= 'all'; my ( $handles, $CONFIG ) = initialize_db_actions({ website_dir => $p->{website_dir}, action => 'install SQL' }); my $base_config = OpenInteract::Startup->read_base_config({ website_dir => $p->{website_dir} }); OpenInteract::Startup->create_temp_lib( $base_config ); # Grab the repository and existing packages my $repository = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir} } ); my $pkg_exist = $repository->verify_package( @{ $p->{package} } ); my $pkg_exist_list = ( ref $pkg_exist eq 'ARRAY' ) ? $pkg_exist : [ $pkg_exist ]; my @status = (); my %pkg_status = (); # These are used by all the steps my %args = ( config => $CONFIG, status => 'raw' ); # Error track -- if we're doing multiple steps we insert the # package name here and successive steps skip them my %error_track = (); # Go through the packages and do some initial setup foreach my $info ( @{ $pkg_exist_list } ) { my $installer_class = OpenInteract::SQLInstall->require_package_installer( $info ); $pkg_status{ $info->{name} }{installer} = $installer_class; $pkg_status{ $info->{name} }{version} = $info->{version}; $pkg_status{ $info->{name} }{ok} = 1; } # Go through and do the structures first... if ( $p->{action} =~ /^(all|structure)$/ ) { foreach my $info ( @{ $pkg_exist_list } ) { DEBUG && _w( 1, "Starting structure install for", "$info->{name}-$info->{version}" ); my $installer_class = $pkg_status{ $info->{name} }{installer}; next unless ( $installer_class ); $args{package} = $info; # TODO: Pass ALL db handles to SQLInstall and let it sort # them out. my $struct_status = eval { $installer_class->apply({ %args, db => $handles->{main}, action => 'create_structure' }) }; if ( $@ ) { $pkg_status{ $info->{name} }{ok} = 0; $error_track{ $info->{name} }++; warn "$@\n"; } else { $pkg_status{ $info->{name} }{structure} = join( "\n * ", "Structure:", map { $_->{msg} } @{ $struct_status } ); my @notes = grep { defined $_ } map { $_->{note} } @{ $struct_status }; if ( @notes ) { $pkg_status{ $info->{name} }{note} = join( "\n", @notes ); } } } } # Disconnect from all handles once... (we reconstruct below) foreach my $db ( values %{ $handles } ) { $db->disconnect; } # Now create the entire request. Processes (like DBI field # discovery) that depend on the structures should now be happy. my $R = initialize_request({ website_dir => $p->{website_dir}, action => 'install SQL' }); # Now install the data and security as needed if ( $p->{action} =~ /^(all|all_data|data)$/ ) { foreach my $info ( @{ $pkg_exist_list } ) { DEBUG && _w( 1, "Starting data install for ", "$info->{name}-$info->{version}" ); next if ( $error_track{ $info->{name} } ); my $installer_class = $pkg_status{ $info->{name} }{installer}; next unless ( $installer_class ); $args{package} = $info; my $data_status = eval { $installer_class->apply({ %args, db => $R->db( 'main' ), action => 'install_data' }) }; if ( $@ ) { $pkg_status{ $info->{name} }{ok} = 0; $error_track{ $info->{name} }++; } else { $pkg_status{ $info->{name} }{data} = join( "\n * ", 'Data: ', map { $_->{msg} } @{ $data_status } ); my @notes = grep { defined $_ } map { $_->{note} } @{ $data_status }; if ( @notes ) { $pkg_status{ $info->{name} }{note} .= join( "\n", @notes ); } } } } if ( $p->{action} =~ /^(all|all_data|security)$/ ) { foreach my $info ( @{ $pkg_exist_list } ) { DEBUG && _w( 1, "Starting server install for ", "$info->{name}-$info->{version}" ); next if ( $error_track{ $info->{name} } ); my $installer_class = $pkg_status{ $info->{name} }{installer}; next unless ( $installer_class ); $args{package} = $info; my $security_status = eval { $installer_class->apply({ %args, action => 'install_security' }) }; if ( $@ ) { $pkg_status{ $info->{name} }{ok} = 0; $error_track{ $info->{name} }++; } else { $pkg_status{ $info->{name} }{security} = join( "\n * ", 'Security: ', map { $_->{msg} } @{ $security_status } ); my @notes = grep { defined $_ } map { $_->{note} } @{ $security_status }; if ( @notes ) { $pkg_status{ $info->{name} }{note} .= join( "\n", @notes ) } } } } foreach my $db ( values %{ $handles } ) { $db->disconnect; } foreach my $name ( sort keys %pkg_status ) { my $this_status = { ok => $pkg_status{ $name }{ok}, name => $name, version => $pkg_status{ $name }{version} }; if ( $pkg_status{ $name }{installer} ) { my $msg_struct = $pkg_status{ $name }{structure} || 'No structure installation done.'; my $msg_data = $pkg_status{ $name }{data} || 'No data installation done.'; my $msg_secure = $pkg_status{ $name }{security} || 'No security installation done.'; $this_status->{msg} = "\n\n" . join( "\n", $msg_struct, $msg_data, $msg_secure ) ."\n"; $this_status->{note} = $pkg_status{ $name }{note}; } else { $this_status->{msg} = "No installer defined, no changes " . "made for package.\n"; } push @status, $this_status; } return \@status; } # # install_template # sub install_template { my ( $p ) = @_; my $R = initialize_request({ website_dir => $p->{website_dir}, action => 'install templates' }); my $TMPL_CLASS = $R->site_template; # Get the ID of our admin group for security setting later on my $site_admin_id = $R->CONFIG->{default_objects}{site_admin_group}; # Cycle through the packages in the website package db and bring # in all their templates my $repository = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir} } ); my $pkg_exist = $repository->verify_package( @{ $p->{package} } ); my $pkg_exist_list = ( ref $pkg_exist eq 'ARRAY' ) ? $pkg_exist : [ $pkg_exist ]; my @status = (); PACKAGE: foreach my $info ( @{ $pkg_exist_list } ) { my $this_status = { ok => 1, name => $info->{name} }; my $this_status_msg = "\n"; my $pkg_dir = join( '/', $info->{website_dir}, $info->{package_dir} ); my $template_dir = join( '/', $pkg_dir, $TMPL_DIR ); # First, find all the .meta files in the package -- every # template must have a .meta file opendir( META, $template_dir ) || die "Cannot open directory ($template_dir): $!"; my @meta_files = grep { -f "$template_dir/$_" } grep /\.$META_EXT$/, readdir( META ); closedir( META ); # Now cycle through them, read them in and parse them, # constructing a template from either one already saved or # creating a new object, then read the .tmpl file into the # object foreach my $meta_file ( @meta_files ) { my $part_meta = $meta_file; $part_meta =~ s/\.$META_EXT$//; my $full_meta = join( '/', $template_dir, $meta_file ); my $ti = {}; open( MF, $full_meta ) || die "Cannot open ($full_meta): $!"; while ( ) { chomp; # skip comments in meta file only next if ( /^\s*\#/ ); my ( $action, $info ) = /^(\w+):\s*(.*)$/; if ( $action ) { $ti->{ $action } = $info; } else { $ti->{description} .= $_; } } # Now, see if we can retrieve an existing template using # the package/name combo my $tmpl_list = $TMPL_CLASS->fetch_group({ where => 'package = ? AND name = ?', value => [ $ti->{package}, $ti->{name} ], skip_security => 1, skip_cache => 1 }); # ... if so, just set the title and description right now my $tmpl = $tmpl_list->[0]; if ( $tmpl ) { $tmpl->{title} = $ti->{title}; $tmpl->{description} = $ti->{description}; $this_status_msg .= "$ti->{name} found in database " . "($tmpl->{template_id}), updating... "; } # Otherwise, create a new template object and set all the # parameters available else { $tmpl = $TMPL_CLASS->new( $ti ); $this_status_msg .= "$ti->{name} being created as new... "; } # Read in the full template, which also includes the # script part of the object my $full_template_file = join( '/', $template_dir, "$part_meta.$TMPL_EXT" ); open( TMPL, $full_template_file ) || die "Cannot open template file: $full_template_file: $!"; { local $/ = undef; $tmpl->{template} = ; my @script_segments = $tmpl->{template} =~ m|(.*?)|gims; $tmpl->{template} =~ s|.*?||gims; $tmpl->{script} = join( "\n\n", @script_segments ); } close( TMPL ); # Save the object eval { $tmpl->save({ skip_security => 1, skip_log => 1, skip_cache => 1 }) }; if ( $@ ) { my $ei = SPOPS::Error->get; $this_status_msg .= "failed! Could not save template " . "object. Error: $@ -- $ei->{system_msg}"; } # If save ok, we need to set security else { $tmpl->set_security({ scope => SEC_SCOPE_WORLD, security_level => SEC_LEVEL_READ }); $tmpl->set_security({ scope => SEC_SCOPE_GROUP, scope_id => $site_admin_id, security_level => SEC_LEVEL_WRITE }); $this_status_msg .= "ok! Template saved and security set ok."; } $this_status_msg .= "\n"; } $this_status->{msg} = $this_status_msg; push @status, $this_status; } $R->db->disconnect; return \@status; } # # remove_template # sub remove_template { my ( $p ) = @_; my $R = initialize_request({ website_dir => $p->{website_dir}, action => 'remove template' }); my $TMPL_CLASS = $R->site_template; my @status = (); foreach my $package_name ( @{ $p->{package} } ) { my $this_status = { name => $package_name, ok => 1 }; my $template_list = $TMPL_CLASS->fetch_group({ where => 'package = ?', value => [ $package_name ] }); foreach my $tmpl ( @{ $template_list } ) { eval { $tmpl->remove({ skip_security => 1, skip_log => 1 }) }; if ( $@ ) { $this_status->{ok} = 0; push @{ $this_status->{msg_list} }, "Template $tmpl->{name}: Failed to remove - $@"; } else { push @{ $this_status->{msg_list} }, "Template $tmpl->{name}: removed ok"; } } $this_status->{msg} = join( "\n", @{ $this_status->{msg_list} } ); $this_status->{msg_list} = undef; push @status, $this_status; } $R->db->disconnect; return \@status; } # # dump_template # sub dump_template { my ( $p ) = @_; my $R = initialize_request({ website_dir => $p->{website_dir}, action => 'dump templates' }); my $TMPL_CLASS = $R->site_template; my @status = (); my $repository = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir} } ); my $pkg_exist = $repository->verify_package( @{ $p->{package} } ); my $pkg_exist_list = ( ref $pkg_exist eq 'ARRAY' ) ? $pkg_exist : [ $pkg_exist ]; PKG: foreach my $info ( @{ $pkg_exist_list } ) { my $this_status = { name => $info->{name}, ok => 1 }; # If we specified a dump directory then just use that; otherwise, # create a dump dir and ensure it's clean before we fill it. my $template_dir = $p->{dump_dir}; unless ( $template_dir ) { $template_dir = join( '/', $info->{website_dir}, $info->{package_dir}, $TMPL_DIR, 'dump' ); if ( -d $template_dir ) { File::Path::rmtree( $template_dir, undef, undef ); } mkdir( $template_dir, 0775 ) || die "[oi_manage]: Cannot create ($template_dir): $!"; } my $template_list = $TMPL_CLASS->fetch_group({ where => 'package = ?', value => [ $info->{name} ], skip_security => 1 }); $this_status->{msg_list} = []; foreach my $tmpl ( @{ $template_list } ) { my $filename = $tmpl->{name}; $filename =~ s/[\s=\+!@\#\$%&*()\[\]\\\/]/_/g; my $new_base = "$template_dir/$filename"; # First dump the template itself, then the script information my $template_file = "$new_base.$TMPL_EXT"; open( TMPL, "> $template_file" ) || die "Cannot open ($template_file) for writing: $!"; print TMPL $tmpl->{template}; if ( $tmpl->{script} ) { print TMPL qq(\n\n); } close( TMPL ); # Then do the meta information my $meta_file = "$new_base.$META_EXT"; open( META, "> $meta_file" ) || die "Cannot open $meta_file for writing: $!"; print META "name: $tmpl->{name}\n", "title: $tmpl->{title}\n", "package: $info->{name}\n", "$tmpl->{description}"; close( META ); push @{ $this_status->{msg_list} }, "Template $tmpl->{name}: dumped ok"; } $this_status->{msg} = "directory: $template_dir\n --" . join( "\n --", @{ $this_status->{msg_list} } ); $this_status->{msg_list} = undef; push @status, $this_status; } $R->db->disconnect; return \@status; } # # change_spops_driver # sub change_spops_driver { my ( $p ) = @_; # Go through the packages installed to the website and find all the # 'conf/spops.conf' files my $repository = $REPOS_CLASS->fetch( undef, { directory => $p->{website_dir} }); my $package_list = $repository->fetch_all_packages(); my @conf_files = (); foreach my $pkg ( @{ $package_list } ) { my $spops_file = $PKG_CLASS->find_file( $pkg, 'conf/spops.perl' ); push @conf_files, $spops_file if ( $spops_file ); } my %status = ( changed => [], not_changed => [] ); # Now open up each spops.conf file and check the 'isa' value foreach my $spops_file ( @conf_files ) { my ( $is_changed ); my $spops_info = SPOPS::HashFile->new({ filename => $spops_file, perm => 'write' }); foreach my $spops_alias ( keys %{ $spops_info } ) { next unless ( ref $spops_info->{ $spops_alias }{isa} eq 'ARRAY' ); for my $i ( 0 .. ( scalar @{ $spops_info->{ $spops_alias }{isa} } - 1 ) ) { my $class_name = $spops_info->{ $spops_alias }{isa}->[ $i ]; if ( $class_name =~ /^SPOPS::DBI::/ ) { $spops_info->{ $spops_alias }{isa}->[ $i ] = $p->{driver}; $is_changed++; } } } if ( $is_changed ) { $spops_info->save; push @{ $status{changed} }, $spops_file; } else { push @{ $status{not_changed} }, $spops_file; } } return \%status; } # # update_objects # sub update_objects { my ( $p ) = @_; my $R = initialize_request({ website_dir => $p->{website_dir}, action => 'update objects' }); my $object_tag = $p->{object_tag}; my $class = $R->$object_tag(); unless ( $class ) { die "Cannot find a valid class for the object you specified " . "($p->{object_tag})\n"; } my $iter = $class->fetch_iterator({ skip_security => 1 }); my $status = { count => 0, save => 0, error => [] }; my $id_field = $class->id_field; while ( my $object = $iter->get_next ) { $object->has_change; eval { $object->save({ skip_security => 1, skip_log => 1, skip_cache => 1 }) }; if ( $@ ) { my $ei = SPOPS::Error->get; push @{ $status->{error} }, "Error with ID ($object->{ $id_field }): " . $ei->{system_msg}; } else { $status->{save}++; } $status->{count}++; } return $status; } # # test_db_connection # sub test_db_connection { my ( $p ) = @_; require OpenInteract::DBI; my $status = { ok => 1 }; # Grab the base config, then the config object my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'test_db_connection' }) } my $C = OpenInteract::Startup->create_config({ base_config => $bc }); # Perform initial sanity checks if ( ref $C->{db_info} ne 'HASH' ) { $status->{msg} = "The key 'db_info' in your configuration must " . "be a hashref. No connection attempted."; } elsif ( ! scalar keys %{ $C->{db_info} } ) { $status->{msg} = "You have no DBI datasources defined. No " . "onnection attempted."; } return $status if ( $status->{msg} ); my @msg = (); # Initial checks are done, now scroll through each of the # connection hashrefs and try to make a connection DBKEY: foreach my $db_key ( keys %{ $C->{db_info} } ) { my $default_status = ( $db_key eq $C->{datasource}{default_connection_db} || $db_key eq $C->{default_connection_db} ) ? " (default connection) " : ''; push @msg, "\nStatus of connection <<$db_key>>$default_status\n$SEP"; if ( ! $C->{db_info}{ $db_key }{dsn} ) { $status->{ok} = 0; push @msg, "Failed. You must at least define 'dsn' in the " . "configuration information."; next DBKEY; } my $db = eval { OpenInteract::DBI->connect( $C->{db_info}{ $db_key } ) }; if ( $@ ) { my ( $error ) = $@ =~ /failed:\s*(.*)$/; $error ||= $@; if ( $@ =~ /^Connect/ ) { push @msg, "-- Basic connect: failed -- $error", "-- Use database: not attempted", "-- Create table: not attempted"; } elsif ( $@ =~ /^Use/ ) { push @msg, "-- Basic connect: ok", "-- Use database: failed -- $error", "-- Create table: not attempted"; } else { push @msg, "-- Other failure: $error"; } $status->{ok} = 0; next DBKEY; } unless ( $db and UNIVERSAL::isa( $db, 'DBI::db' ) ) { push @msg, "-- Basic connect: failed (no error, but no database handle returned)", "-- Use database: not attempted", "-- Create table: not attempted"; $status->{ok} = 0; next DBKEY; } my $test_table = 'oi_test_create'; push @msg, "-- Basic connect: ok", "-- Use database: ok"; eval { $db->do( "CREATE TABLE $test_table " . "( oi_id int not null, primary key( oi_id ) )" ) }; if ( $@ ) { push @msg, "-- Create table: failed -- $@"; } else { push @msg, "-- Create table: ok"; eval { $db->do( "DROP TABLE $test_table" ) }; if ( $@ ) { push @msg, "(NOTE: 'DROP TABLE' failed -- $@; " . "please remove table ($test_table) manually.)"; } } eval { $db->disconnect }; if ( $@ ) { push @msg, "(NOTE: Error trying to disconnect: $@)"; } } $status->{msg} = join "\n", @msg; return $status; } # # test_ldap_connection # sub test_ldap_connection { my ( $p ) = @_; require OpenInteract::LDAP; my $status = { ok => 1 }; # Grab the base config, then the config object my $bc = eval { OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }) }; if ( $@ ) { base_config_file_error({ website_dir => $p->{website_dir}, error => $@, sub => 'test_ldap_connection' }) } my $C = OpenInteract::Startup->create_config({ base_config => $bc }); # Perform initial sanity checks if ( ref $C->{ldap_info} ne 'HASH' ) { $status->{msg} = "The key 'ldap_info' in your configuration must " . "be a hashref. No connection attempted."; } elsif ( ! scalar keys %{ $C->{ldap_info} } ) { $status->{msg} = "You have no LDAP datasources defined. No " . "connection attempted."; } return $status if ( $status->{msg} ); my @msg = (); # Initial checks are done, now scroll through each of the # connection hashrefs and try to make a connection LDAPKEY: foreach my $ldap_key ( keys %{ $C->{ldap_info} } ) { my $default_status = ( $ldap_key eq $C->{datasource}{default_connection_ldap} || $ldap_key eq $C->{default_connection_ldap} ) ? " (default connection) " : ''; push @msg, "\nStatus of connection <<$ldap_key>>$default_status\n$SEP"; if ( ! $C->{ldap_info}{ $ldap_key }{host} ) { $status->{ok} = 0; push @msg, "Failed. You must at least define 'host' in the " . "configuration information."; next LDAPKEY; } elsif ( ! $C->{ldap_info}{ $ldap_key }{base_dn} ) { $status->{ok} = 0; push @msg, "Failed. You must at least define 'base_dn' in " . "the configuration information."; next LDAPKEY; } my $ldap = eval { OpenInteract::LDAP->connect_and_bind( $C->{ldap_info}{ $ldap_key } ) }; if ( $@ ) { my ( $error ) = $@ =~ /failed:\s*(.*)$/; $error ||= $@; if ( $@ =~ /^Connect/ ) { push @msg, "-- Basic connect: failed -- $error", "-- Bind to directory: not attempted"; } elsif ( $@ =~ /^Bind/ ) { push @msg, "-- Basic connect: ok", "-- Bind to directory: failed -- $error"; } else { push @msg, "-- Other failure: $error"; } $status->{ok} = 0; next LDAPKEY; } push @msg, "-- Basic connect: ok", "-- Bind to directory: ok"; } $status->{msg} = join "\n", @msg; return $status; } # # create_readonly_file # sub create_readonly_file { my ( $dir, $msg ) = @_; my $no_overwrite_file = join( '/', $dir, READONLY_FILE ); open( NOWRITE, "> $no_overwrite_file" ) || _w( 0, "Could not create ($no_overwrite_file): $!" ); print NOWRITE $msg; close( NOWRITE ); } # # initialize_db_actions # sub initialize_db_actions { my ( $p ) = @_; my $C = OpenInteract::Startup->create_config({ website_dir => $p->{website_dir} }); my %handles = (); require OpenInteract::DBI; foreach my $db_key ( keys %{ $C->{db_info} } ) { $handles{ $db_key } = eval { OpenInteract::DBI->connect( $C->{db_info}{ $db_key } ) }; die "Cannot open datasource ($db_key): $@" if ( $@ ); } return ( \%handles, $C ); } # # initialize_request # sub initialize_request { my ( $p ) = @_; my $R = eval { OpenInteract::Startup->setup_static_environment( $p->{website_dir} ) }; if ( $@ ) { my $bc = OpenInteract::Startup->read_base_config({ dir => $p->{website_dir} }); my $config_msg = ( ref $bc eq 'HASH' ) ? join( '/', "$p->{website_dir}/conf", $bc->{config_file} ) : 'Could not be determined'; my $error_msg = <{action}. Error: $@ Configuration file used: $config_msg MSG die $error_msg; } return $R; } sub base_config_file_error { my ( $p ) = @_; my $base_conf_file = OpenInteract::Startup->create_base_config_filename( $p->{website_dir} ); my $msg = "[oi_manage]: Failure! Cannot find base configuration or " . "configuration is invalid. Does the file: ($base_conf_file) " . "exist in your website? " . "(Specific error: <$p->{error}> from routine <$p->{sub}>)"; die wrap( undef, undef, "$msg\n" ); } sub print_status_line { my ( $status, $opt ) = @_; my $line = ''; if ( $status->{name} ) { $line = $status->{name}; $line .= " ($status->{version})" if ( $status->{version} ); $line .= "\n" unless ( $opt->{same_line} ); $line .= ' '; } $line .= ( $status->{ok} ) ? 'OK' : 'FAILED!'; my $indent = ( $status->{ok} ) ? ' ' : ' '; $line .= ': ' . wrap( undef, $indent, $status->{msg} ) if ( $status->{msg} ); if ( $status->{notes} ) { $line .= "\n\nNotes from Package Author (READ THESE)\n" . "$status->{notes}\n$SEP\n"; } return $line; } sub _w { my ( $level, @msg ) = @_; return undef if ( DEBUG < $level ); my ( $pkg, $file, $line ) = caller; my @ci = caller(1); warn "$ci[3] ($line) >> ", join( ' ', @msg ), "\n"; } __END__ =pod =head1 NAME oi_manage - Manage OpenInteract websites and packages =head1 SYNOPSIS oi_manage [options] [command] Administration commands: install, upgrade, install_package Package development commands: create_skeleton, export_package, check_package Website creator commands: create_website, apply_package, upgrade_package, remove_package, install_sql, install_template, dump_template, remove_template, refresh_doc, test_db, test_ldap, change_spops_driver Other commands: initial_packages, list_packages, list_actions, list_objects For more information, run 'oi_manage --man' =head1 COMMON COMMANDS Commands by the Administrator: install - Install OpenInteract to base directory upgrade - Upgrade core OpenInteract packages install_package - Install package to the base Commands by the Package Developer: create_skeleton - Create a skeleton package for development export_package - Export package(s) to distribution file(s) check_package - Ensure that package passes initial inspection Commands by the Website Creator: create_website - Create a new website apply_package - Install a package from base to website upgrade_package - Upgrade a website package remove_package - Remove a package from a website install_sql - Install the SQL for packages install_template - Install package templates to the database dump_template - Dump package templates to the filesystem remove_template - Remove package templates from the database refresh_doc - Sync website docs to base installation docs update_object - Re-save all objects in a given class test_db - Test database settings in 'server.perl' test_ldap - Test LDAP settings in 'server.perl' change_spops_driver - Change the SPOPS driver for your objects Other commands: initial_packages - List packages marked as 'initial' list_packages - List packages installed to app or base list_actions - List actions currently implemented in website list_objects - List object classes currently implemented in website =head1 COMMON OPTIONS Summary of common options: --base_dir OpenInteract install directory --website_dir Website install directory --website_name Website name --package_dir Directory with package subdirectories (usually devel) --package_file Distribution file containing an OpenInteract package --package List packages to operate on --package_list_file File specifying packages to operate on --dump_dir Directory to dump stuff into --driver A generic driver (DBI, SPOPS, etc.) --object An SPOPS object tag --help Display brief help --man Display full help Details: --base_dir=/path/to/OpenInteract Name the directory where OpenInteract is installed. You can set the environment variable 'OPENINTERACT' instead of passing the value on the command-line, We recommend you set this environment variable for all OpenInteract users and developers. --website_dir=/path/to/OpenInteract/website Name the directory where an OpenInteract website is installed. This directory will have the website package database in the 'conf/' directory. You can set the environment variable 'OIWEBSITE' instead of passing the value on the command line. However, setting this permanently will cause you problems, so it is best to set on a temporary basis. --website_name=MyAppName Name of your website. Must be all one word (no underscores or anything), and StudlyCaps are A-OK (in fact, recommended). Note that this name becomes your perl namespace, so all your packages become 'MyAppName::News' and 'MyAppName::WebLink::Handler', etc. --package_dir=/path/to/my/devel/packages Name the directory where you do your OpenInteract development. This is used by the 'export_package' and the 'check_package' commands. This directory can also be where a single package is -- we also look at the 'package' parameter to discern which way to use 'package_dir'. --package_file=an-oi-package-x.xx.tar.gz OpenInteract packages are distributed in common tarballs, which can be created by the 'export_package' command and installed by the 'install_package' command. --package=a,b,c,d OR --package=a --package=b --package=d --package_list_file=/path/to/package_list File containing package names, one per line, without version numbers or anything else. Blank lines and lines beginning with a '#' are skipped. You can substitute this wherever you see '--package' specified as a parameter in the discussions below. --dump_dir=/path/to/dump/stuff Specify a directory where you would like to dump something -- such as the SQL for a package or the templates belonging to a package. Dumping routines typically have a designated place for this (usually the 'dump/' directory in the area pertaining to what is being dumped), but sometimes you might want to put the data elsewhere. --driver=SPOPS::DBI::Pg Specify some sort of driver -- this can be used to name an SPOPS driver (such as 'SPOPS::DBI::Pg') or a DBD driver ('DBD::mysql') when necessary. --object=SPOPS-object-tag Specify the name of an SPOPS object tag. This is not an object class but rather the 'alias' by which OpenInteract can refer to an object. For instance, the alias for the template objects is 'site_template' and the alias for the page objects stored in the database is 'basic_page'. --sql_action=(all|structure|data|security|all_data) Specify an action to take with the 'install_sql' command. The action 'all' is the default if not specified. You should not need this often, if at all. Other options depend on the I you choose and are listed under that command below. =head1 DESCRIPTION B is the command-line interface for managing packages within OpenInteract and installing new OpenInteract websites. It is also useful for developers so they can export their work into a I file for distribution, or install it into the OpenInteract package database. =head1 SHORTCUTS A few shortcuts that can make your life much simpler. B =over 4 =item 1. Set the 'OPENINTERACT' environment variable and never type '--base_dir=/blah' again. =item 2. Set the 'OIWEBSITE' environment variable and never type '--website_dir=/blah' again. =back How to do this? If you are on a Linux machine using the I shell, just do: export OPENINTERACT=/path/to/base/installation and export OIWEBSITE=/path/to/my/website If you do not wish to type these in everytime you login, just put them in your '.bashrc' or equivalent. (If you do not know what '.bashrc' is, ask your friendly sysadmin.) B If you are working with the core set of packages necessary to make OpenInteract function, you should know about the 'INITIAL' keyword. The 'INITIAL' keyword can be used in place of a package name when you use the '--package' parameter specification. For instance, running: oi_manage export_package --package_dir=/my/devel/oi/pkg --package=INITIAL First replaces 'INITIAL' with the list of initial packages, then runs the operation. You can see the list of initial packages by running: $ oi_manage initial_packages B If you commonly perform an operation on a number of different packages at once, you can store the package names in an external file and refer to it when you run 'oi_manage'. The file format is simple: one package name per line, blank lines and lines beginning with a comment ('#') are skipped. The following would be a valid file: BEGIN--------------- # Packages I contribute to news # Packages I own and work on photo_album recipe_box bicycle_trip ---------------END Specify the file to 'oi_manage' using the '--package_list_file' parameter specification. =head1 COMMANDS The following tools and actions are available from B: =head2 INSTALL Command: install Required options: --base_dir=/path/to/OpenInteract Install OpenInteract. Note that you must be in the OpenInteract source directory to run this command. For instance, a typical installation might look like the following sequence: >> tar -zxvf OpenInteract-1.01.tar.gz >> cd OpenInteract-1.01 >> perl Makefile.PL >> make >> make test >> make install (file 'oi_manage' is now in /usr/local/bin) >> /usr/local/bin/oi_manage --base_dir=/opt/OpenInteract install You should only ever need to do this once. But just in case, it might be a good idea to keep the initial source directory around. =head2 UPGRADE Command: upgrade --base_dir=/path/to/OpenInteract Upgrade OpenInteract packages. Note that you must be in the OpenInteract source directory to run this command. For instance, a typical upgrademight look like the following sequence: >> tar -zxvf OpenInteract-1.52.tar.gz >> cd OpenInteract-1.52 >> perl Makefile.PL >> make >> make test >> make install (file 'oi_manage' is now in /usr/bin) >> /usr/local/bin/oi_manage --base_dir=/opt/OpenInteract upgrade While you run the 'install' command only once, you can run the 'upgrade' command for every new release of OpenInteract that comes out. All old documentation files and configuration samples are saved with an C<.old> suffix, but they will be overwritten (with the new old file) if you run the 'upgrade' command again. =head2 INITIAL PACKAGES Command: initial_packages Just lists the initial package B is currently configured to install when given a 'create_website' command. =head2 LIST PACKAGES Command: list_packages Required options -- one of the following: --base_dir=/path/to/OpenInteract --website_dir=/path/to/my_website List the packages currently installed in a website or in the base OI installation. =head2 LIST ACTIONS Command: list_actions Required options: --website_dir=/path/to/my_website Bootstrap an OpenInteract environment from the command line and inspect it to see what actions are created in the action table. Output includes the action name, the package the action is found in, and either the class and method used to call it or the template which implements it. This can be extremely useful if you are unsure what actions your website currently implements, and for ensuring that you do not chose an action for your new package that is already in use elsewhere. =head2 LIST OBJECTS Command: list_objects Required options: --website_dir=/path/to/my_website Bootstrap an OpenInteract environment from the command line and inspect it to see what SPOPS objects can be created in the environment. Output is simply the aliases by which the object class are known (frequently just one) and the class implementing the object. =head2 CREATE SKELETON Command: create_skeleton Required options: --package=mypackagename --base_dir=/path/to/OpenInteract Creates skeleton package(s) in your current directory for development. This includes: =over 4 =item * The necessary directories =item * An initial C file =item * A documentation template and index =item * Commented sample C and C, configuration files =item * Commented sample C file =item * Commented sample template files in C