Created 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 Display;
78 use Logger;
79 use Rexec;
80
81 my ($currentHost, $skip, $log);
82
83 my %opts = (
84   usage    => sub { pod2usage },
85   help     => sub { pod2usage (-verbose => 2)},
86   verbose  => sub { set_verbose },
87   debug    => sub { set_debug },
88   username => $ENV{REXEC_USER} ? $ENV{REXEC_USER} : $ENV{USER},
89   password => $ENV{REXEC_PASSWD},
90 );
91
92 sub Interrupted {
93   use Term::ReadKey;
94
95   display BLUE . "\nInterrupted execution on $currentHost->{host}" . RESET;
96
97   display_nolf "Executing on " . YELLOW . $currentHost->{host}  . RESET . " - "
98     . CYAN      . BOLD . "C" . RESET . CYAN     . "ontinue"     . RESET . " or "
99     . MAGENTA   . BOLD . "A" . RESET . MAGENTA  . "bort run"    . RESET . " ("
100     . CYAN      . BOLD . "C" . RESET . "/"
101     . MAGENTA   . BOLD . "a" . RESET . ")?";
102
103   ReadMode ("cbreak");
104   my $answer = ReadKey (0);
105   ReadMode ("normal");
106
107   if ($answer eq "\n") {
108     display "c";
109   } else {
110     display $answer;
111   } # if
112
113   $answer = lc $answer;
114
115   if ($answer eq "s") {
116     *STDOUT->flush;
117     display "Skipping $currentHost->{host}";
118   } elsif ($answer eq "a") {
119     display RED . "Aborting run". RESET;
120     exit;
121   } else {
122     display "Continuing...";
123   } # if
124
125   return;
126 } # Interrupted
127
128 sub connectHost ($) {
129   my ($host) = @_;
130
131   # Start a log...
132   $log = Logger->new (name => $host) if $opts{log};
133
134   eval {
135     $currentHost = Rexec->new (
136       host     => $host,
137       username => $opts{username},
138       password => $opts{password},
139     );
140   };
141
142   # Problem with creating Rexec object. Log error if logging and return.
143   if ($@ || !$currentHost) {
144     if ($opts{log}) {
145       $log->err ("Unable to connect to $host") if $opts{log};
146     } else {
147       display RED . 'ERROR:' . RESET . " Unable to connect";
148     } # if
149   } # if
150
151   return;
152 } # connectHost
153
154 sub execute ($$;$) {
155   my ($host, $cmd, $prompt) = @_;
156
157   my @lines;
158
159   verbose_nolf "Connecting to machine $host...";
160
161   display_nolf BOLD . YELLOW . "$host:" . RESET if $opts{verbose};
162
163   connectHost $host unless $currentHost and $currentHost->{host} eq $host;
164
165   return (1, ()) unless $currentHost;
166
167   verbose " connected";
168
169   display WHITE . UNDERLINE . "$cmd" . RESET if $opts{verbose};
170
171   @lines = $currentHost->execute ($cmd);
172
173   verbose "Disconnected from $host";
174
175   my $status = $currentHost->status;
176
177   return ($status, @lines);
178 } # execute
179
180 $SIG{INT} = \&Interrupted;
181
182 # Get our options
183 GetOptions (
184   \%opts,
185   'usage',
186   'help',
187   'verbose',
188   'debug',
189   'username=s',
190   'password=s',
191   'log',
192   'machines=s@',
193 ) or pod2usage;
194
195 $opts{debug}   = get_debug   if ref $opts{debug}   eq 'CODE';
196 $opts{verbose} = get_verbose if ref $opts{verbose} eq 'CODE';
197
198 my $cmd = join ' ', @ARGV;
199
200 unless ($opts{machines}) {
201   $opts{machines} = [$ENV{REXEC_HOST}] if $ENV{REXEC_HOST};
202 } # unless
203
204 pod2usage 'Must specify -machines to run on' unless $opts{machines};
205
206 my @machines;
207
208 push @machines, (split /,/, join (',', $_)) for (@{$opts{machines}}); 
209
210 $opts{machines} = [@machines];
211
212 my ($status, @lines);
213
214 for my $machine (@{$opts{machines}}) {
215   if ($cmd) {
216     ($status, @lines) = execute $machine, $cmd;
217
218     display BOLD . YELLOW . "$machine:" . RESET . WHITE . $cmd;
219
220     error "Execution of $cmd on $machine yielded error $status" if $status;
221
222     display $_ for @lines;
223
224     undef $currentHost;
225   } else {
226     verbose_nolf "Connecting to machine $machine...";
227
228     connectHost $machine;
229
230     if ($currentHost) {
231       while () {
232         display_nolf BOLD . YELLOW . "$machine:" . RESET . WHITE;
233
234         $cmd = <STDIN>; 
235
236         unless ($cmd) {
237           display '';
238           last;
239         } # unless
240
241         last if $cmd =~ /^\s*(exit|quit)\s*$/i;
242         next if $cmd =~ /^\s*$/;
243
244         chomp $cmd;
245
246         ($status, @lines) = execute $machine, $cmd;
247
248         error "Execution of $cmd on $machine yielded error $status" if $status;
249
250         display $_ for @lines;
251       } # while
252     } # if
253   } # if
254 } # for