diff --git a/Changes b/Changes index 0065e50..62c39dd 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,10 @@ +0.0291 +- atnodes: added '-E' and '-tmout' parameter to run expect mode. +0.029 +- atnodes: added a '-q' parameter to run SSH in quiet mode, + which prevents banners and motd messages from being + displayed in the output. thanks Mithun Ayachit for the patch. + 0.028 - atnodes: fixed tmp file leaks. - atnodes: automatically check if openssh version >= 4.1. diff --git a/META.yml b/META.yml index 850952d..a74d652 100644 --- a/META.yml +++ b/META.yml @@ -3,12 +3,13 @@ abstract: 'Cluster operations based on parallel SSH, set and interval arithmetic author: - "Zhang \"agentzh\" Yichun " build_requires: - ExtUtils::MakeMaker: 6.42 + ExtUtils::MakeMaker: 6.55 IPC::Run3: 0 configure_requires: - ExtUtils::MakeMaker: 6.42 + ExtUtils::MakeMaker: 6.55 distribution_type: module -generated_by: 'Module::Install version 1.01' +dynamic_config: 1 +generated_by: 'Module::Install version 1.04' license: bsd meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -26,8 +27,9 @@ requires: Net::OpenSSH: 0.34 Set::Scalar: 1.23 Term::ReadKey: 2.30 + Time::HiRes: 0 perl: 5.6.1 resources: license: http://opensource.org/licenses/bsd-license.php repository: http://github.com/agentzh/sshbatch -version: 0.028 +version: 0.0291 diff --git a/Makefile.PL b/Makefile.PL index 451db14..5316aee 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -16,6 +16,7 @@ requires ('Net::OpenSSH' => '0.34'); requires ('File::Temp'); requires ('Term::ReadKey' => '2.30'); requires ('IO::Pty'); +requires ('Time::HiRes'); build_requires ('IPC::Run3'); #build_requires ('Test::Base' => '0.54'); diff --git a/README b/README index 471cca6..9ac1a92 100644 --- a/README +++ b/README @@ -3,7 +3,7 @@ NAME arithmetic VERSION - This document describes SSH::Batch 0.028 released on 26 August 2011. + This document describes SSH::Batch 0.029 released on 29 April 2012. SYNOPSIS The following scripts are provided: diff --git a/bin/atnodes b/bin/atnodes index 34e8937..6ab37c1 100755 --- a/bin/atnodes +++ b/bin/atnodes @@ -10,6 +10,7 @@ use Term::ReadKey; use SSH::Batch::ForNodes; use File::Temp qw/ :POSIX /; use Time::HiRes qw/sleep/; +use Expect; sub help ($); sub check_openssh_version ($); @@ -30,6 +31,8 @@ my $found_sep; my $last_option; my $use_tty; my $use_quiet_mode; +my $use_expect_mode; +my $expect_timeout = 1; my $line_mode = $ENV{SSH_BATCH_LINE_MODE}; for (@ARGV) { if (defined $fetch_value) { @@ -71,6 +74,10 @@ for (@ARGV) { $use_tty = 1; } elsif ($group eq 'q') { $use_quiet_mode = 1; + } elsif ($group eq 'E') { + $use_expect_mode = 1; + } elsif ($group eq 'tmout') { + $fetch_value = sub { $expect_timeout = shift }; } else { die "Unknown option: $_\n"; } @@ -200,14 +207,30 @@ while (1) { for my $host (@active_hosts) { my ($out, $outfile) = tmpnam(); my $ssh = $conns{$host}; - my $pid = $ssh->system({ - (defined $password? - (stdin_data => "$password\n") : ()), - stdout_fh => $out, - stderr_to_stdout => 1, - async => 1, - defined $use_tty ? (tty => 1) : (), - }, @cmd); + my $pid; + my $pty; + if ($use_expect_mode && $use_expect_mode == 1) { + ($pty, $pid) = $ssh->open2pty({'stderr_to_stdout' => 1}, @cmd); + my $expect = Expect->init($pty); + $expect->log_file($outfile, "w"); + $expect->raw_pty(1); + $expect->expect($expect_timeout, ":"); + if (defined $password) { + $expect->send("$password\n"); + } + $expect->expect($expect_timeout, "\n"); + $expect->raw_pty(0); + close $expect; + } else { + $pid = $ssh->system({ + (defined $password? + (stdin_data => "$password\n") : ()), + stdout_fh => $out, + stderr_to_stdout => 1, + async => 1, + defined $use_tty ? (tty => 1) : (), + }, @cmd); + } #warn "PID: $pid\n"; if (!defined $pid or $pid == -1) { $active_count--; @@ -311,7 +334,9 @@ OPTIONS: -P Prompt for passphrase (used for login, could be privided by SSH_BATCH_PASSPHRASE). -tty Pseudo-tty. - -q Run SSH in quiet mode + -E Use expect for input, such as password. + -tmout Set timeout of expect. + -q Run SSH in quiet mode. _EOC_ if ($exit_code == 0) { print $msg; diff --git a/inc/Module/AutoInstall.pm b/inc/Module/AutoInstall.pm index 60b90ea..3aabb10 100644 --- a/inc/Module/AutoInstall.pm +++ b/inc/Module/AutoInstall.pm @@ -7,7 +7,7 @@ use ExtUtils::MakeMaker (); use vars qw{$VERSION}; BEGIN { - $VERSION = '1.03'; + $VERSION = '1.04'; } # special map on pre-defined feature sets @@ -17,11 +17,14 @@ my %FeatureMap = ( ); # various lexical flags -my ( @Missing, @Existing, %DisabledTests, $UnderCPAN, $HasCPANPLUS ); +my ( @Missing, @Existing, %DisabledTests, $UnderCPAN, $InstallDepsTarget, $HasCPANPLUS ); my ( - $Config, $CheckOnly, $SkipInstall, $AcceptDefault, $TestOnly, $AllDeps + $Config, $CheckOnly, $SkipInstall, $AcceptDefault, $TestOnly, $AllDeps, + $UpgradeDeps ); -my ( $PostambleActions, $PostambleUsed ); +my ( $PostambleActions, $PostambleActionsNoTest, $PostambleActionsUpgradeDeps, + $PostambleActionsUpgradeDepsNoTest, $PostambleActionsListDeps, + $PostambleActionsListAllDeps, $PostambleUsed, $NoTest); # See if it's a testing or non-interactive session _accept_default( $ENV{AUTOMATED_TESTING} or ! -t STDIN ); @@ -31,6 +34,10 @@ sub _accept_default { $AcceptDefault = shift; } +sub _installdeps_target { + $InstallDepsTarget = shift; +} + sub missing_modules { return @Missing; } @@ -63,6 +70,11 @@ sub _init { __PACKAGE__->install( $Config, @Missing = split( /,/, $1 ) ); exit 0; } + elsif ( $arg =~ /^--upgradedeps=(.*)$/ ) { + $UpgradeDeps = 1; + __PACKAGE__->install( $Config, @Missing = split( /,/, $1 ) ); + exit 0; + } elsif ( $arg =~ /^--default(?:deps)?$/ ) { $AcceptDefault = 1; } @@ -125,7 +137,7 @@ sub import { # check entirely since we don't want to have to load (and configure) # an old CPAN just for a cosmetic message - $UnderCPAN = _check_lock(1) unless $SkipInstall; + $UnderCPAN = _check_lock(1) unless $SkipInstall || $InstallDepsTarget; while ( my ( $feature, $modules ) = splice( @args, 0, 2 ) ) { my ( @required, @tests, @skiptests ); @@ -207,6 +219,7 @@ sub import { $CheckOnly or ($mandatory and $UnderCPAN) or $AllDeps + or $InstallDepsTarget or _prompt( qq{==> Auto-install the } . ( @required / 2 ) @@ -237,10 +250,17 @@ sub import { } } - if ( @Missing and not( $CheckOnly or $UnderCPAN ) ) { + if ( @Missing and not( $CheckOnly or $UnderCPAN) ) { require Config; - print -"*** Dependencies will be installed the next time you type '$Config::Config{make}'.\n"; + my $make = $Config::Config{make}; + if ($InstallDepsTarget) { + print +"*** To install dependencies type '$make installdeps' or '$make installdeps_notest'.\n"; + } + else { + print +"*** Dependencies will be installed the next time you type '$make'.\n"; + } # make an educated guess of whether we'll need root permission. print " (You may need to do that as the 'root' user.)\n" @@ -271,6 +291,10 @@ END_MESSAGE sub _check_lock { return unless @Missing or @_; + if ($ENV{PERL5_CPANM_IS_RUNNING}) { + return _running_under('cpanminus'); + } + my $cpan_env = $ENV{PERL5_CPAN_IS_RUNNING}; if ($ENV{PERL5_CPANPLUS_IS_RUNNING}) { @@ -332,6 +356,11 @@ sub install { } } + if ($UpgradeDeps) { + push @modules, @installed; + @installed = (); + } + return @installed unless @modules; # nothing to do return @installed if _check_lock(); # defer to the CPAN shell @@ -463,6 +492,11 @@ sub _cpanplus_config { } else { die "*** Cannot convert option $key = '$value' to CPANPLUS version.\n"; } + push @config, 'prereqs', $value; + } elsif ( $key eq 'force' ) { + push @config, $key, $value; + } elsif ( $key eq 'notest' ) { + push @config, 'skiptest', $value; } else { die "*** Cannot convert option $key to CPANPLUS version.\n"; } @@ -497,10 +531,14 @@ sub _install_cpan { # set additional options while ( my ( $opt, $arg ) = splice( @config, 0, 2 ) ) { ( $args{$opt} = $arg, next ) - if $opt =~ /^force$/; # pseudo-option + if $opt =~ /^(?:force|notest)$/; # pseudo-option $CPAN::Config->{$opt} = $arg; } + if ($args{notest} && (not CPAN::Shell->can('notest'))) { + die "Your version of CPAN is too old to support the 'notest' pragma"; + } + local $CPAN::Config->{prerequisites_policy} = 'follow'; while ( my ( $pkg, $ver ) = splice( @modules, 0, 2 ) ) { @@ -519,8 +557,16 @@ sub _install_cpan { delete $INC{$inc}; } - my $rv = $args{force} ? CPAN::Shell->force( install => $pkg ) - : CPAN::Shell->install($pkg); + my $rv = do { + if ($args{force}) { + CPAN::Shell->force( install => $pkg ) + } elsif ($args{notest}) { + CPAN::Shell->notest( install => $pkg ) + } else { + CPAN::Shell->install($pkg) + } + }; + $rv ||= eval { $CPAN::META->instance( 'CPAN::Distribution', $obj->cpan_file, ) ->{install} @@ -763,6 +809,35 @@ sub _make_args { : "\$(NOECHO) \$(NOOP)" ); + my $deps_list = join( ',', @Missing, @Existing ); + + $PostambleActionsUpgradeDeps = + "\$(PERL) $0 --config=$config --upgradedeps=$deps_list"; + + my $config_notest = + join( ',', (UNIVERSAL::isa( $Config, 'HASH' ) ? %{$Config} : @{$Config}), + 'notest', 1 ) + if $Config; + + $PostambleActionsNoTest = ( + ($missing and not $UnderCPAN) + ? "\$(PERL) $0 --config=$config_notest --installdeps=$missing" + : "\$(NOECHO) \$(NOOP)" + ); + + $PostambleActionsUpgradeDepsNoTest = + "\$(PERL) $0 --config=$config_notest --upgradedeps=$deps_list"; + + $PostambleActionsListDeps = + '@$(PERL) -le "print for @ARGV" ' + . join(' ', map $Missing[$_], grep $_ % 2 == 0, 0..$#Missing); + + my @all = (@Missing, @Existing); + + $PostambleActionsListAllDeps = + '@$(PERL) -le "print for @ARGV" ' + . join(' ', map $all[$_], grep $_ % 2 == 0, 0..$#all); + return %args; } @@ -797,11 +872,15 @@ sub Write { sub postamble { $PostambleUsed = 1; + my $fragment; - return <<"END_MAKE"; + $fragment .= <<"AUTO_INSTALL" if !$InstallDepsTarget; config :: installdeps \t\$(NOECHO) \$(NOOP) +AUTO_INSTALL + + $fragment .= <<"END_MAKE"; checkdeps :: \t\$(PERL) $0 --checkdeps @@ -809,12 +888,28 @@ checkdeps :: installdeps :: \t$PostambleActions +installdeps_notest :: +\t$PostambleActionsNoTest + +upgradedeps :: +\t$PostambleActionsUpgradeDeps + +upgradedeps_notest :: +\t$PostambleActionsUpgradeDepsNoTest + +listdeps :: +\t$PostambleActionsListDeps + +listalldeps :: +\t$PostambleActionsListAllDeps + END_MAKE + return $fragment; } 1; __END__ -#line 1071 +#line 1178 diff --git a/inc/Module/Install.pm b/inc/Module/Install.pm index 74caf9c..c685ca4 100644 --- a/inc/Module/Install.pm +++ b/inc/Module/Install.pm @@ -31,7 +31,7 @@ BEGIN { # This is not enforced yet, but will be some time in the next few # releases once we can make sure it won't clash with custom # Module::Install extensions. - $VERSION = '1.01'; + $VERSION = '1.04'; # Storage for the pseudo-singleton $MAIN = undef; @@ -451,7 +451,7 @@ sub _version ($) { } sub _cmp ($$) { - _version($_[0]) <=> _version($_[1]); + _version($_[1]) <=> _version($_[2]); } # Cloned from Params::Util::_CLASS diff --git a/inc/Module/Install/AutoInstall.pm b/inc/Module/Install/AutoInstall.pm index bc3d172..f7f4283 100644 --- a/inc/Module/Install/AutoInstall.pm +++ b/inc/Module/Install/AutoInstall.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } @@ -73,6 +73,17 @@ sub auto_install { ); } +sub installdeps_target { + my ($self, @args) = @_; + + $self->include('Module::AutoInstall'); + require Module::AutoInstall; + + Module::AutoInstall::_installdeps_target(1); + + $self->auto_install(@args); +} + sub auto_install_now { my $self = shift; $self->auto_install(@_); diff --git a/inc/Module/Install/Base.pm b/inc/Module/Install/Base.pm index d3662c9..b520616 100644 --- a/inc/Module/Install/Base.pm +++ b/inc/Module/Install/Base.pm @@ -4,7 +4,7 @@ package Module::Install::Base; use strict 'vars'; use vars qw{$VERSION}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; } # Suspend handler for "redefined" warnings diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm index 276409a..a162ad4 100644 --- a/inc/Module/Install/Can.pm +++ b/inc/Module/Install/Can.pm @@ -9,7 +9,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm index 093cb7a..a412576 100644 --- a/inc/Module/Install/Fetch.pm +++ b/inc/Module/Install/Fetch.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } diff --git a/inc/Module/Install/Include.pm b/inc/Module/Install/Include.pm index 90cc979..dd001eb 100644 --- a/inc/Module/Install/Include.pm +++ b/inc/Module/Install/Include.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } diff --git a/inc/Module/Install/Makefile.pm b/inc/Module/Install/Makefile.pm index 4c71003..035cef2 100644 --- a/inc/Module/Install/Makefile.pm +++ b/inc/Module/Install/Makefile.pm @@ -8,7 +8,7 @@ use Fcntl qw/:flock :seek/; use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } @@ -219,14 +219,14 @@ sub write { # an underscore, even though its own version may contain one! # Hence the funny regexp to get rid of it. See RT #35800 # for details. - my $v = $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/; + my ($v) = $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/; $self->build_requires( 'ExtUtils::MakeMaker' => $v ); $self->configure_requires( 'ExtUtils::MakeMaker' => $v ); } else { # Allow legacy-compatibility with 5.005 by depending on the # most recent EU:MM that supported 5.005. - $self->build_requires( 'ExtUtils::MakeMaker' => 6.42 ); - $self->configure_requires( 'ExtUtils::MakeMaker' => 6.42 ); + $self->build_requires( 'ExtUtils::MakeMaker' => 6.36 ); + $self->configure_requires( 'ExtUtils::MakeMaker' => 6.36 ); } # Generate the MakeMaker params @@ -241,7 +241,6 @@ in a module, and provide its file path via 'version_from' (or 'all_from' if you prefer) in Makefile.PL. EOT - $DB::single = 1; if ( $self->tests ) { my @tests = split ' ', $self->tests; my %seen; @@ -412,4 +411,4 @@ sub postamble { __END__ -#line 541 +#line 540 diff --git a/inc/Module/Install/Metadata.pm b/inc/Module/Install/Metadata.pm index 3b01e09..31c953e 100644 --- a/inc/Module/Install/Metadata.pm +++ b/inc/Module/Install/Metadata.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } @@ -151,15 +151,21 @@ sub install_as_site { $_[0]->installdirs('site') } sub install_as_vendor { $_[0]->installdirs('vendor') } sub dynamic_config { - my $self = shift; - unless ( @_ ) { - warn "You MUST provide an explicit true/false value to dynamic_config\n"; - return $self; + my $self = shift; + my $value = @_ ? shift : 1; + if ( $self->{values}->{dynamic_config} ) { + # Once dynamic we never change to static, for safety + return 0; } - $self->{values}->{dynamic_config} = $_[0] ? 1 : 0; + $self->{values}->{dynamic_config} = $value ? 1 : 0; return 1; } +# Convenience command +sub static_config { + shift->dynamic_config(0); +} + sub perl_version { my $self = shift; return $self->{values}->{perl_version} unless @_; @@ -170,7 +176,7 @@ sub perl_version { # Normalize the version $version = $self->_perl_version($version); - # We don't support the reall old versions + # We don't support the really old versions unless ( $version >= 5.005 ) { die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n"; } @@ -582,7 +588,7 @@ sub bugtracker_from { sub requires_from { my $self = shift; my $content = Module::Install::_readperl($_[0]); - my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg; + my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+(v?[\d\.]+)/mg; while ( @requires ) { my $module = shift @requires; my $version = shift @requires; diff --git a/inc/Module/Install/Scripts.pm b/inc/Module/Install/Scripts.pm index e9510f8..12f5a1b 100644 --- a/inc/Module/Install/Scripts.pm +++ b/inc/Module/Install/Scripts.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm index 3139a63..99d9631 100644 --- a/inc/Module/Install/Win32.pm +++ b/inc/Module/Install/Win32.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm index 1f724a7..86bb25e 100644 --- a/inc/Module/Install/WriteAll.pm +++ b/inc/Module/Install/WriteAll.pm @@ -6,7 +6,7 @@ use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { - $VERSION = '1.01'; + $VERSION = '1.04'; @ISA = qw{Module::Install::Base}; $ISCORE = 1; } diff --git a/lib/SSH/Batch.pm b/lib/SSH/Batch.pm index 5c3ae29..710aa41 100644 --- a/lib/SSH/Batch.pm +++ b/lib/SSH/Batch.pm @@ -5,7 +5,7 @@ package SSH::Batch; use strict; use warnings; -our $VERSION = '0.028'; +our $VERSION = '0.0291'; 1; __END__ @@ -18,7 +18,7 @@ SSH::Batch - Cluster operations based on parallel SSH, set and interval arithmet =head1 VERSION -This document describes SSH::Batch 0.028 released on 26 August 2011. +This document describes SSH::Batch 0.029 released on 29 April 2012. =head1 SYNOPSIS diff --git a/lib/SSH/Batch/ForNodes.pm b/lib/SSH/Batch/ForNodes.pm index 26543a5..482e091 100644 --- a/lib/SSH/Batch/ForNodes.pm +++ b/lib/SSH/Batch/ForNodes.pm @@ -5,7 +5,7 @@ package SSH::Batch::ForNodes; use strict; use warnings; -our $VERSION = '0.028'; +our $VERSION = '0.029'; use Set::Scalar; use File::HomeDir; diff --git a/t/atnodes.t b/t/atnodes.t index 3269aba..3850d0f 100644 --- a/t/atnodes.t +++ b/t/atnodes.t @@ -59,7 +59,9 @@ OPTIONS: -P Prompt for passphrase (used for login, could be privided by SSH_BATCH_PASSPHRASE). -tty Pseudo-tty. - -q Run SSH in quiet mode + -E Use expect for input, such as password. + -tmout Set timeout of expect. + -q Run SSH in quiet mode. --- status: 1