| Module-CGI-Install documentation | Contained in the Module-CGI-Install distribution. |
Module::CGI::Install - Installer for CGI applications
Module::CGI::Install is a package for installing CGI applications.
It is based on the principle that a particular application may need to be installed multiple times on a single host.
So an application can be installed normally onto the system, and from there the functionality provided by Module::CGI::Install creates a way to
quickly, easily and safely move a copy of that application (or at least the parts that matter) from the default system install location to the specific CGI directory.
The API described below is primarily for the benefit of CGI application authors.
End-users looking to actually install the applications should be using the cgiinstall command line tool.
All bugs should be filed via the bug tracker at
http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Module-CGI-Install
For other issues, or commercial enhancement or support, contact the author.
Adam Kennedy <adamk@cpan.org>
Copyright 2007 - 2008 Adam Kennedy.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the LICENSE file included with this module.
| Module-CGI-Install documentation | Contained in the Module-CGI-Install distribution. |
package Module::CGI::Install;
use 5.005; use strict; use Config; use Carp (); use File::Spec (); use File::Copy (); use File::Path (); use File::chmod (); use File::Remove (); use File::Basename (); use Scalar::Util (); use Params::Util qw{ _STRING _CLASS _INSTANCE }; use Term::Prompt (); use URI::ToDisk (); use LWP::Simple (); use CGI::Capture (); use ExtUtils::Packlist (); use vars qw{$VERSION $CGICAPTURE}; BEGIN { $VERSION = '0.07'; } $CGICAPTURE ||= __PACKAGE__->_find_script('CGI::Capture', 'cgicapture'); unless ( $CGICAPTURE and -f $CGICAPTURE ) { Carp::croak("Failed to locate the 'cgicapture' application"); } use Object::Tiny qw{ force interactive install_cgi install_static install_config cgi_dir cgi_uri cgi_capture static_dir static_uri config_dir errstr }; ##################################################################### # Constructor and Accessors sub new { my $self = shift->SUPER::new(@_); # Create the arrays for scripts and libraries $self->{script} = []; $self->{class} = []; $self->{config} = {}; # By default, install CGI but not static or config unless ( defined $self->install_cgi ) { $self->{install_cgi} = 1; } unless ( defined $self->install_static ) { $self->{install_static} = 0; } unless ( defined $self->install_config ) { $self->{install_config} = 0; } # Auto-detect interactive mode if needed unless ( defined $self->interactive ) { $self->{interactive} = $self->_is_interactive; } # Normalize the boolean flags $self->{force} = !! $self->{force}; $self->{interactive} = !! $self->{interactive}; $self->{install_cgi} = !! $self->{install_cgi}; $self->{install_static} = !! $self->{install_static}; $self->{install_config} = !! $self->{install_config}; # Delete params that should not have been provided unless ( $self->install_cgi ) { delete $self->{cgi_uri}; delete $self->{cgi_dir}; } unless ( $self->install_static ) { delete $self->{static_uri}; delete $self->{static_dir}; } unless ( $self->install_config ) { delete $self->{config_dir}; delete $self->{config_keep}; } return $self; } sub prepare { my $self = shift; # Check the cgi params if installing CGI if ( $self->install_cgi ) { # Get and check the base cgi path if ( $self->interactive and ! defined $self->cgi_dir ) { $self->{cgi_dir} = Term::Prompt::prompt( 'x', 'CGI Directory:', '', File::Spec->rel2abs( File::Spec->curdir ), ); } my $cgi_dir = $self->cgi_dir; unless ( defined $cgi_dir ) { return $self->prepare_error("No cgi_dir provided"); } unless ( -d $cgi_dir ) { return $self->prepare_error("The cgi_dir '$cgi_dir' does not exist"); } unless ( -w $cgi_dir ) { return $self->prepare_error("The cgi_dir '$cgi_dir' is not writable"); } # Get and check the cgi_uri if ( $self->interactive and ! defined $self->cgi_uri ) { $self->{cgi_uri} = Term::Prompt::prompt( 'x', 'CGI URI:', '', '', ); } unless ( defined _STRING($self->cgi_uri) ) { return $self->prepare_error("No cgi_dir provided"); } # Validate the CGI settings unless ( $self->force or $self->validate_cgi_dir($self->cgi_map) ) { return $self->prepare_error("CGI mapping failed testing"); } } # Check the config params if installing config if ( $self->install_config ) { # Get and check the base config directory if ( $self->interactive and ! defined $self->config_dir ) { my $default = $self->install_cgi ? $self->cgi_dir : File::Spec->rel2abs( File::Spec->curdir ); $self->{config_dir} = Term::Prompt::prompt( 'x', 'Config Directory:', '', $default ); } my $config_dir = $self->config_dir; unless ( defined $config_dir ) { return $self->prepare_error("No config_dir provided"); } unless ( -d $config_dir ) { return $self->prepare_error("The config_dir '$config_dir' does not exist"); } unless ( -w $config_dir ) { return $self->prepare_error("The config_dir '$config_dir' is not writable"); } } # Check the static params if installing static if ( $self->install_static ) { # Get and check the base cgi directory if ( $self->interactive and ! defined $self->static_dir ) { $self->{static_dir} = Term::Prompt::prompt( 'x', 'Static Directory:', '', File::Spec->rel2abs( File::Spec->curdir ), ); } my $static_dir = $self->static_dir; unless ( defined $static_dir ) { return $self->prepare_error("No static_dir provided"); } unless ( -d $static_dir ) { return $self->prepare_error("The static_dir '$static_dir' does not exist"); } unless ( -w $static_dir ) { return $self->prepare_error("The static_dir '$static_dir' is not writable"); } # Get and check the cgi_uri if ( $self->interactive and ! defined $self->static_uri ) { $self->{static_uri} = Term::Prompt::prompt( 'x', 'Static URI:', '', '', ); } unless ( defined _STRING($self->static_uri) ) { return $self->prepare_error("No static_dir provided"); } # Validate the CGI settings unless ( $self->force or $self->validate_static_dir($self->static_map) ) { return $self->prepare_error("Static mapping failed testing"); } } return 1; } sub run { my $self = shift; # Install any binary files foreach my $script ( @{$self->{script}} ) { my $from = $script->[2]; unless ( $from and -f $from ) { die "Unexpectedly failed to find '$script->[1]'"; } my $to = $self->cgi_map->catfile($script->[1])->path; File::Copy::copy( $from => $to ); unless ( -f $to ) { die "Unexpectedly failed to create '$to'"; } unless ( File::chmod::chmod('a+rx', $to) ) { die "Failed to set executable permissions"; } } # Install any class files foreach my $class ( @{$self->{class}} ) { my $from = $self->_module_path($class); my $to = File::Spec->catfile( $self->cgi_map->catdir('lib')->path, File::Spec->catfile(split /::/, $class) . '.pm', ); my $dirname = File::Basename::dirname($to); File::Path::mkpath( $dirname, 0, 0755 ); unless ( -d $dirname ) { die "Failed to create directory '$dirname'"; } File::Copy::copy( $from => $to ); unless ( -f $to ) { die "Unexpectedly failed to create '$to'"; } } # Install any config files foreach my $name ( %{$self->{config}} ) { my $from = $self->{config}->{$name}; my $to = File::Spec->catfile( $self->config_dir, $name, ); if ( _INSTANCE($from, 'YAML::Tiny') or _INSTANCE($from, 'Config::Tiny') ) { unless ( $from->write($to) ) { die "Failed to write to config file '$name'"; } } } return 1; } ##################################################################### # Accessor-Derived Methods sub cgi_map { $_[0]->install_cgi or return undef; URI::ToDisk->new( $_[0]->cgi_dir => $_[0]->cgi_uri ); } sub static_map { $_[0]->install_static or return undef; URI::ToDisk->new( $_[0]->static_dir => $_[0]->static_uri ); } ##################################################################### # Manipulation sub add_script { my $self = shift; my $class = _CLASS(shift) or die "Invalid class name"; my $script = _STRING(shift) or die "Invalid script name"; my $path = $self->_find_script($class, $script); unless ( $path and -f $path ) { Carp::croak( "Failed to find '$script'"); } push @{$self->{script}}, [ $class, $script, $path ]; return 1; } sub add_class { my $self = shift; my $class = _CLASS(shift) or die "Invalid class name"; $self->_module_exists($class) or die "Failed to find '$class'"; push @{$self->{class}}, $class; return 1; } sub add_config { my $self = shift; my $config = shift; my $name = _STRING(shift) or die "Did not provide a config file name"; if ( _CLASSISA($config, 'Config::Tiny') ) { $config = $config->new; } if ( _CLASSISA($config, 'YAML::Tiny') ) { $config = $config->new( {} ); } unless ( _INSTANCE($config, 'Config::Tiny') or _INSTANCE($config, 'Config::YAML') ) { die "Missing, invalid, or unsupported config object"; } $self->{config}->{$name} = $config; return 1; } ##################################################################### # Functional Methods sub validate_cgi_dir { my $self = shift; my $dir = _INSTANCE(shift, 'URI::ToDisk') or Carp::croak("Did not pass a URI::ToDisk object to valid_cgi"); my $file = $dir->catfile('cgicapture'); # Copy the cgicapture application to the CGI path unless ( File::Copy::copy( $CGICAPTURE, $file->path ) ) { return undef; # Carp::croak("Failed to copy cgicapture into place"); } unless ( File::chmod::chmod('a+rx', $file->path) ) { return undef; # Carp::croak("Failed to set executable permissions"); } # Call the URI my $www = LWP::Simple::get( $file->URI ); # Clean up the file now, before we check for errors File::Remove::remove( $file->path ); # Continue and check for errors unless ( defined $www ) { return undef; # Carp::croak("Nothing returned from the cgicapture web request"); } if ( $www =~ /^\#\!\/usr\/bin\/perl/ ) { return undef; # Carp::croak("URI is not a CGI path"); } unless ( $www =~ /^---\nARGV\:/ ) { return undef; # Carp::croak("Unknown value returned from URI"); } # Superficially ok, convert to capture object $self->{cgi_capture} = CGI::Capture->from_yaml_string($www); unless ( _INSTANCE($self->cgi_capture, 'CGI::Capture') ) { return undef; # Carp::croak("Failed to create capture object"); } return 1; } sub validate_static_dir { my $self = shift; my $dir = _INSTANCE(shift, 'URI::ToDisk') or Carp::croak("Did not pass a URI::ToDisk object to valid_static"); my $file = $dir->catfile('cgiinstall.txt'); # Write a test file to the directory my $test_string = int(rand(100000000+1000)); open( FILE, '>' . $file->path ) or die "open: $!"; print FILE $test_string or die "print: $!"; close FILE or die "close: $!"; # Call the URI my $www = LWP::Simple::get( $file->URI ); # Clean up the file now, before we check for errors File::Remove::remove( $file->path ); # Continue and check for errors unless ( defined $www ) { return undef; # Carp::croak("Nothing returned from the cgicapture web request"); } # Check the result unless ( $www eq $test_string ) { return undef; # Carp::croak("Unknown value returned from URI"); } return 1; } ##################################################################### # Utility Methods sub new_error { my $self = shift; $self->{errstr} = _STRING(shift) || 'Unknown error'; return; } sub prepare_error { my $self = shift; return _STRING(shift) || 'Unknown error'; } # Copied from IO::Interactive sub _is_interactive { my $self = shift; # Default to default output handle my ($out_handle) = (@_, select); # Not interactive if output is not to terminal... return 0 if not -t $out_handle; # If *ARGV is opened, we're interactive if... if ( Scalar::Util::openhandle *ARGV ) { # ...it's currently opened to the magic '-' file return -t *STDIN if defined $ARGV && $ARGV eq '-'; # ...it's at end-of-file and the next file is the magic '-' file return @ARGV > 0 && $ARGV[0] eq '-' && -t *STDIN if eof *ARGV; # ...it's directly attached to the terminal return -t *ARGV; } # If *ARGV isn't opened, it will be interactive if *STDIN is attached # to a terminal and either there are no files specified on the command line # or if there are files and the first is the magic '-' file return -t *STDIN && (@ARGV==0 || $ARGV[0] eq '-'); } sub _module_exists { my $self = shift; my $path = $self->_module_path(shift); return !! $path; } sub _module_path { my $self = shift; my @parts = split /::/, $_[0]; my @found = grep { -f $_ } map { File::Spec->catdir($_, @parts) . '.pm' } grep { -d $_ } @INC; return $found[0]; } sub _find_script { my $either = shift; my $module = shift; my $script = shift; my @dirs = grep { -e } ( $Config{archlibexp}, $Config{sitearchexp} ); my $file = File::Spec->catfile( 'auto', split( /::/, $module), '.packlist', ); foreach my $dir ( @dirs ) { my $path = File::Spec->catfile( $dir, $file ); next unless -f $path; # Load the file my $packlist = ExtUtils::Packlist->new($path); unless ( $packlist ) { die "Failed to load .packlist file for $module"; } my $regex = quotemeta $script; my @script = sort grep { /\b$regex$/ } keys %$packlist; die "Unexpectedly found more than one $script file" if @script > 1; die "Failed to find $script script" unless @script; return $script[0]; } die "Failed to locate .packfile for $module"; } 1;