X-Git-Url: https://defaria.com/gitweb/?a=blobdiff_plain;f=bin%2Frexec.pl;h=b65933878e2db931541e57f52bd571c469d6ab64;hb=fbbb29c20458b289f2e66825684a21b5143833a4;hp=b76d4b144b19a24d30bdfa45446ac4367ac083c2;hpb=64cb05c445d480f29d266e70db6eb1d021d34456;p=clearscm.git diff --git a/bin/rexec.pl b/bin/rexec.pl index b76d4b1..b659338 100755 --- a/bin/rexec.pl +++ b/bin/rexec.pl @@ -1,4 +1,4 @@ -#!/usr/bin/perl +#!/usr/bin/env perl use strict; use warnings; @@ -18,7 +18,7 @@ Andrew DeFaria =item Revision -$Revision: 1.0 $ +$Revision: 2.0 $ =item Created: @@ -35,8 +35,9 @@ $Date: 2008/02/29 15:09:15 $ Usage: rexec.pl [-usa|ge] [-h|elp] [-v|erbose] [-d|ebug] [-use|rname ] [-p|assword ] [-log] - -m|achines ,,... - + [-m|achines ,,...] + [-f|ile ] + Where: @@ -47,6 +48,7 @@ $Date: 2008/02/29 15:09:15 $ -use|rname: User name to login as (Default: $USER - Env: REXEC_USER) -p|assword: Password to use (Default: None - Env: REXEC_PASSWD) -m|achines: Machine(s) to run the command on + -f|ile: File containing machine info -l|og: Log output (.log) : Commands to execute (Enclose multiple commands in quotes) @@ -55,7 +57,7 @@ $Date: 2008/02/29 15:09:15 $ This script will perform and arbitrary command on a set of machines. It uses the Rexec module which utilizes Perl::Expect to attempt a connection using ssh, rsh -and finally telnet. Username and password can be suppliec (or set up ssh +and finally telnet. Username and password can be supplied (or set up ssh pre-shared key) to log in. This is especially important when ssh'ing into Windows machines using Cygwin and wanting to use network resources. If you ssh into a Windows box using pre-shared key then Windows will not have your @@ -64,6 +66,36 @@ file systems. Therefore on Windows machines, do not set up preshared key if you wish to access remotely mounted file systems. Instead supply the username and password (hopefully in a secure manner). +Machines: + +The list of machines that will be operated on can be specified in the machines +option but is more often obtained using a Machines module. The default Machines +module will parse a flat file that lists machine names and the characteristics +of those machines (OS version, CPU count, owner - See Machines.pm). If a +different mechanism is used to store and retrieve machine information then the +use can write a replacement for the Machines module. This replacement must +present an object oriented approach at supplying the qualifying machines by +supporting the following methods: + +new: Create new Machines object + +find: Find machines based on a specified condition (e.g. OS = "Ubuntu 18.04") + +next: Return the next qualifying machine + +Logging and reruning: + +If -log is specified then a directory will be created based on the machine's +name in -logdir (default current directory) where all output will be written to +a log file named $machine/$machine.log. The command attempted will be written to +$machine/command and the status will be written to $machine/status. If instead +the we were not able to connect to the remote machine, often because the machine +was down, then the $machine directory will only have the command file indicating +that the command was not run on the remote machine. This allows the -restart +parameter to work. When run with -restart, rexec will exam all log directories +to see which ones only contain a command file and attempt to execute them on +$machine again. + =cut use FindBin; @@ -74,27 +106,32 @@ use POSIX ":sys_wait_h"; use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../clearadm/lib"; +use CmdLine; use Display; use Logger; use Rexec; +use Machines; -my ($currentHost, $skip, $log); +my ($currentHost, $log); my %opts = ( usage => sub { pod2usage }, help => sub { pod2usage (-verbose => 2)}, verbose => sub { set_verbose }, debug => sub { set_debug }, - username => $ENV{REXEC_USER} ? $ENV{REXEC_USER} : $ENV{USER}, + username => $ENV{REXEC_USER} || $ENV{USER}, password => $ENV{REXEC_PASSWD}, + database => 1, ); sub Interrupted { use Term::ReadKey; - display BLUE . "\nInterrupted execution on $currentHost->{host}" . RESET; + my $host = $currentHost->{host} || 'Unknown Host'; + + display BLUE . "\nInterrupted execution on $host" . RESET; - display_nolf "Executing on " . YELLOW . $currentHost->{host} . RESET . " - " + display_nolf "Executing on " . CYAN . $host . RESET . " - " . CYAN . BOLD . "C" . RESET . CYAN . "ontinue" . RESET . " or " . MAGENTA . BOLD . "A" . RESET . MAGENTA . "bort run" . RESET . " (" . CYAN . BOLD . "C" . RESET . "/" @@ -114,7 +151,7 @@ sub Interrupted { if ($answer eq "s") { *STDOUT->flush; - display "Skipping $currentHost->{host}"; + display "Skipping $host"; } elsif ($answer eq "a") { display RED . "Aborting run". RESET; exit; @@ -128,9 +165,6 @@ sub Interrupted { sub connectHost ($) { my ($host) = @_; - # Start a log... - $log = Logger->new (name => $host) if $opts{log}; - eval { $currentHost = Rexec->new ( host => $host, @@ -151,27 +185,60 @@ sub connectHost ($) { return; } # connectHost +sub initLog($) { + my ($machine) = @_; + + if ($opts{log}) { + my $logdir = $opts{logdir} ? "$opts{logdir}/$machine" : $machine; + + mkdir $logdir or error "Unable to make directory $logdir", 1; + + $log = Logger->new( + name => 'output', + path => $logdir, + ); + } # if +} # initLog + +sub Log($;$) { + my ($msg, $nocrlf) = @_; + + if ($log) { + $log->msg($msg, $nocrlf); + } else { + verbose $msg, $nocrlf; + } # +} # Log + +sub logError ($;$) { + my ($msg, $exit) = @_; + + if ($log) { + $log->err($msg, $exit); + } else { + error $msg, $exit; + } # if +} # logError + sub execute ($$;$) { my ($host, $cmd, $prompt) = @_; my @lines; - verbose_nolf "Connecting to machine $host..."; + Log "Connecting to machine $host...", 1; - display_nolf BOLD . YELLOW . "$host:" . RESET if $opts{verbose}; + display_nolf BOLD . CYAN . "$host:" . RESET if $opts{verbose}; connectHost $host unless $currentHost and $currentHost->{host} eq $host; return (1, ()) unless $currentHost; - verbose " connected"; + Log ' connected'; - display WHITE . UNDERLINE . "$cmd" . RESET if $opts{verbose}; + Log "$host:" . UNDERLINE . $cmd . RESET; @lines = $currentHost->execute ($cmd); - verbose "Disconnected from $host"; - my $status = $currentHost->status; return ($status, @lines); @@ -189,7 +256,11 @@ GetOptions ( 'username=s', 'password=s', 'log', + 'logdir', + 'filename=s', + 'database!', 'machines=s@', + 'condition=s', ) or pod2usage; $opts{debug} = get_debug if ref $opts{debug} eq 'CODE'; @@ -197,43 +268,64 @@ $opts{verbose} = get_verbose if ref $opts{verbose} eq 'CODE'; my $cmd = join ' ', @ARGV; +$opts{machines} = [$ENV{REXEC_HOST}] if $ENV{REXEC_HOST}; + unless ($opts{machines}) { - $opts{machines} = [$ENV{REXEC_HOST}] if $ENV{REXEC_HOST}; -} # unless + # Connect to Machines module + my $machines; -pod2usage 'Must specify -machines to run on' unless $opts{machines}; + unless ($opts{database}) { + require Machines; Machines->import; -my @machines; + $machines = Machines->new(filename => $opts{filename}); + } else { + require Machines::MySQL; Machines::MySQL->import; -push @machines, (split /,/, join (',', $_)) for (@{$opts{machines}}); + $machines = Machines::MySQL->new; -$opts{machines} = [@machines]; + my %machines = $machines->select($opts{condition}); + + $opts{machines} = [keys %machines]; + } # if +} # if my ($status, @lines); -for my $machine (@{$opts{machines}}) { +for my $machine (sort @{$opts{machines}}) { + initLog $machine; + if ($cmd) { ($status, @lines) = execute $machine, $cmd; - display BOLD . YELLOW . "$machine:" . RESET . WHITE . $cmd; + display BOLD . CYAN . "$machine:" . UNDERLINE . WHITE . $cmd . RESET; - error "Execution of $cmd on $machine yielded error $status" if $status; + logError "Execution of $cmd on $machine failed", $status if $status; + + if ($log) { + $log->log($_) for @lines; + } # if display $_ for @lines; undef $currentHost; + undef $log; } else { verbose_nolf "Connecting to machine $machine..."; connectHost $machine; if ($currentHost) { + my $cmdline = CmdLine->new (); + + $cmdline->set_prompt (BOLD . CYAN . "$machine:" . RESET . WHITE); + while () { - display_nolf BOLD . YELLOW . "$machine:" . RESET . WHITE; + Log "$machine:"; - $cmd = ; + $cmd = $cmdline->get(); unless ($cmd) { + $log->msg('') if $log; display ''; last; } # unless @@ -243,12 +335,17 @@ for my $machine (@{$opts{machines}}) { chomp $cmd; + Log $cmd; + ($status, @lines) = execute $machine, $cmd; - error "Execution of $cmd on $machine yielded error $status" if $status; + logError "Execution of $cmd on $machine failed", $status if $status; + Log $_ for @lines; display $_ for @lines; } # while } # if + + undef $log; } # if -} # for \ No newline at end of file +} # for