Attempt to add CmdLine to rexec.pl
[clearscm.git] / bin / rexec.pl
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4
5 =pod
6
7 =head1 NAME $RCSfile: rexec.pl,v $
8
9 Run arbitrary command on another machine
10
11 =head1 VERSION
12
13 =over
14
15 =item Author
16
17 Andrew DeFaria <Andrew@ClearSCM.com>
18
19 =item Revision
20
21 $Revision: 1.0 $
22
23 =item Created:
24
25 Tue Jan  8 15:57:27 MST 2008
26
27 =item Modified:
28
29 $Date: 2008/02/29 15:09:15 $
30
31 =back
32
33 =head1 SYNOPSIS
34
35  Usage: rexec.pl [-usa|ge] [-h|elp] [-v|erbose] [-d|ebug]
36                  [-use|rname <username>] [-p|assword <password>]
37                  [-log]
38                  -m|achines <host1>,<host2>,...
39                  
40               <command>
41
42  Where:
43    -usa|ge:    Print this usage
44    -h|elp:     Print detailed usage
45    -v|erbose:  Verbose mode
46    -d|ebug:    Print debug messages
47    -use|rname: User name to login as (Default: $USER - Env: REXEC_USER)
48    -p|assword: Password to use (Default: None - Env: REXEC_PASSWD)
49    -m|achines: Machine(s) to run the command on
50    -l|og:      Log output (<machine>.log)
51
52    <command>:  Commands to execute (Enclose multiple commands in quotes)
53
54 =head1 DESCRIPTION
55
56 This script will perform and arbitrary command on a set of machines. It uses the
57 Rexec module which utilizes Perl::Expect to attempt a connection using ssh, rsh
58 and finally telnet. Username and password can be suppliec (or set up ssh 
59 pre-shared key) to log in. This is especially important when ssh'ing into
60 Windows machines using Cygwin and wanting to use network resources. If you ssh
61 into a Windows box using pre-shared key then Windows will not have your 
62 password and it needs it to authenticate your user to determine access to remote
63 file systems. Therefore on Windows machines, do not set up preshared key if you
64 wish to access remotely mounted file systems. Instead supply the username and 
65 password (hopefully in a secure manner).
66
67 =cut
68
69 use FindBin;
70 use Getopt::Long;
71 use Pod::Usage;
72 use Term::ANSIColor qw(:constants);
73 use POSIX ":sys_wait_h";
74
75 use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../clearadm/lib";
76
77 use CmdLine;
78 use Display;
79 use Logger;
80 use Rexec;
81
82 my ($currentHost, $skip, $log);
83
84 my %opts = (
85   usage    => sub { pod2usage },
86   help     => sub { pod2usage (-verbose => 2)},
87   verbose  => sub { set_verbose },
88   debug    => sub { set_debug },
89   username => $ENV{REXEC_USER} ? $ENV{REXEC_USER} : $ENV{USER},
90   password => $ENV{REXEC_PASSWD},
91 );
92
93 sub Interrupted {
94   use Term::ReadKey;
95
96   display BLUE . "\nInterrupted execution on $currentHost->{host}" . RESET;
97
98   display_nolf "Executing on " . YELLOW . $currentHost->{host}  . RESET . " - "
99     . CYAN      . BOLD . "C" . RESET . CYAN     . "ontinue"     . RESET . " or "
100     . MAGENTA   . BOLD . "A" . RESET . MAGENTA  . "bort run"    . RESET . " ("
101     . CYAN      . BOLD . "C" . RESET . "/"
102     . MAGENTA   . BOLD . "a" . RESET . ")?";
103
104   ReadMode ("cbreak");
105   my $answer = ReadKey (0);
106   ReadMode ("normal");
107
108   if ($answer eq "\n") {
109     display "c";
110   } else {
111     display $answer;
112   } # if
113
114   $answer = lc $answer;
115
116   if ($answer eq "s") {
117     *STDOUT->flush;
118     display "Skipping $currentHost->{host}";
119   } elsif ($answer eq "a") {
120     display RED . "Aborting run". RESET;
121     exit;
122   } else {
123     display "Continuing...";
124   } # if
125
126   return;
127 } # Interrupted
128
129 sub connectHost ($) {
130   my ($host) = @_;
131
132   # Start a log...
133   $log = Logger->new (name => $host) if $opts{log};
134
135   eval {
136     $currentHost = Rexec->new (
137       host     => $host,
138       username => $opts{username},
139       password => $opts{password},
140     );
141   };
142
143   # Problem with creating Rexec object. Log error if logging and return.
144   if ($@ || !$currentHost) {
145     if ($opts{log}) {
146       $log->err ("Unable to connect to $host") if $opts{log};
147     } else {
148       display RED . 'ERROR:' . RESET . " Unable to connect";
149     } # if
150   } # if
151
152   return;
153 } # connectHost
154
155 sub execute ($$;$) {
156   my ($host, $cmd, $prompt) = @_;
157
158   my @lines;
159
160   verbose_nolf "Connecting to machine $host...";
161
162   display_nolf BOLD . YELLOW . "$host:" . RESET if $opts{verbose};
163
164   connectHost $host unless $currentHost and $currentHost->{host} eq $host;
165
166   return (1, ()) unless $currentHost;
167
168   verbose " connected";
169
170   display WHITE . UNDERLINE . "$cmd" . RESET if $opts{verbose};
171
172   @lines = $currentHost->execute ($cmd);
173
174   verbose "Disconnected from $host";
175
176   my $status = $currentHost->status;
177
178   return ($status, @lines);
179 } # execute
180
181 $SIG{INT} = \&Interrupted;
182
183 # Get our options
184 GetOptions (
185   \%opts,
186   'usage',
187   'help',
188   'verbose',
189   'debug',
190   'username=s',
191   'password=s',
192   'log',
193   'machines=s@',
194 ) or pod2usage;
195
196 $opts{debug}   = get_debug   if ref $opts{debug}   eq 'CODE';
197 $opts{verbose} = get_verbose if ref $opts{verbose} eq 'CODE';
198
199 my $cmd = join ' ', @ARGV;
200
201 unless ($opts{machines}) {
202   $opts{machines} = [$ENV{REXEC_HOST}] if $ENV{REXEC_HOST};
203 } # unless
204
205 pod2usage 'Must specify -machines to run on' unless $opts{machines};
206
207 my @machines;
208
209 push @machines, (split /,/, join (',', $_)) for (@{$opts{machines}}); 
210
211 $opts{machines} = [@machines];
212
213 my ($status, @lines);
214
215 for my $machine (@{$opts{machines}}) {
216   if ($cmd) {
217     ($status, @lines) = execute $machine, $cmd;
218
219     display BOLD . YELLOW . "$machine:" . RESET . WHITE . $cmd;
220
221     error "Execution of $cmd on $machine yielded error $status" if $status;
222
223     display $_ for @lines;
224
225     undef $currentHost;
226   } else {
227     verbose_nolf "Connecting to machine $machine...";
228
229     connectHost $machine;
230
231     if ($currentHost) {
232       my $cmdline = CmdLine->new ();
233
234       $cmdline->set_prompt (BOLD . YELLOW . "$machine:" . RESET . WHITE);
235
236       while () {
237         #$cmd = <STDIN>;
238         $cmd = $cmdline->get(); 
239
240         unless ($cmd) {
241           display '';
242           last;
243         } # unless
244
245         last if $cmd =~ /^\s*(exit|quit)\s*$/i;
246         next if $cmd =~ /^\s*$/;
247
248         chomp $cmd;
249
250         ($status, @lines) = execute $machine, $cmd;
251
252         error "Execution of $cmd on $machine yielded error $status" if $status;
253
254         display $_ for @lines;
255       } # while
256     } # if
257   } # if
258 } # for