File Coverage

lib/Command/Interactive.pm
Criterion Covered Total %
statement 126 128 98.4
branch 52 60 86.6
condition 11 12 91.6
subroutine 14 14 100.0
pod 1 1 100.0
total 204 215 94.8


line stmt bran cond sub pod time code
1             package Command::Interactive;
2             # ABSTRACT: handles interactive (and non-interactive) process invocation
3              
4 7     7   244861 use strict;
  7         14  
  7         248  
5 7     7   124 use warnings;
  7         8  
  7         267  
6              
7             our $VERSION = 1.4;
8              
9 7     7   3746 use Moose;
  7         2599699  
  7         54  
10              
11              
12             =head1 NAME
13              
14             Command::Interactive- handles interactive (and non-interactive) process invocation through a reliable and easily configured interface.
15              
16             =head1 SYNOPSIS
17              
18             This module can be used to invoke both interactive and non-interactive commands with predicatable results.
19              
20             use Command::Interactive;
21             use Carp;
22              
23             # Simple, non-interactive usage
24             my $result1 = Command::Interactive->new->run("cp foo /tmp/");
25             croak "Could not copy foo to /tmp/: $result!" if($result);
26              
27             # Interactive usage supports output parsing
28             # and automated responses to discovered strings
29             my $password_prompt = Command::Interactive::Interaction->new({
30             expected_string => 'Please enter your password:',
31             response => 'secret',
32             });
33              
34             my $command = Command::Interactive->new({
35             echo_output => 1,
36             output_stream => $my_logging_fh,
37             interactions => [ $password_prompt ],
38             });
39             my $restart_result = $command->run("ssh user@somehost 'service apachectl restart'");
40             if($restart_result)
41             {
42             warn "Couldn't restart server!";
43             }
44              
45             =cut
46              
47 7     7   44812 use Command::Interactive::Interaction;
  7         19  
  7         401  
48 7     7   3593 use IO::File;
  7         30403  
  7         859  
49 7     7   42 use Carp;
  7         10  
  7         297  
50 7     7   5554 use Expect;
  7         149812  
  7         8047  
51              
52             =head1 FIELDS
53              
54             =head2 always_use_expect (DEFAULT: FALSE)
55              
56             Whether to use the C<Expect> module to execute system commands. By default, Expect is only used if one or more interactions() are specified.
57              
58             =cut
59              
60             has always_use_expect => (
61             is => 'rw',
62             isa => 'Bool',
63             default => 0,
64             );
65              
66             =head2 debug_logfile (DEFAULT: undef)
67              
68             The name of a file to which lots of debugging information should be written. Typically useful only for maintainers. If you want to see what your command is doing, use echo_output() and a debugging filehandle (or just STDOUT).
69              
70             =cut
71              
72             has debug_logfile => (
73             is => 'rw',
74             isa => 'Str',
75             );
76              
77             =head2 echo_output (DEFAULT: FALSE)
78              
79             Whether to echo output to the specified output_stream(). This allows users of
80             Command::Interactive to see what is going on, but it also can clutter an interface with lots of superfluous command output. Use it wisely.
81              
82             See web_format() for a discussion about how to format command output for web interfaces.
83              
84             =cut
85              
86             has echo_output => (
87             is => 'rw',
88             isa => 'Bool',
89             default => 0,
90             );
91              
92             =head2 interactions (DEFAULT: [], the empty arrayref)
93              
94             An array reference of Command::Interactive::Interaction objects that specify the
95             interactions that may (or must) occur during the execution of the command. See
96             C<Command::Interactive::Interaction> for more information on specifying rules about command interactions.
97              
98             =cut
99              
100             has interactions => (
101             is => 'rw',
102             isa => 'ArrayRef',
103             default => sub { [] }, # References are not allowed as defaults
104             );
105              
106             =head2 output_stream (DEFAULT: STDOUT)
107              
108             The stream object to which output should be sent when echo_output() is enabled. This is any object with a print() method; it needn't have a full C<IO>-compliant interface.
109              
110             =cut
111              
112             has output_stream => (
113             is => 'rw',
114             default => *STDOUT,
115             );
116              
117             =head2 timeout (DEFAULT: undef)
118              
119             If defined, represents the timeout (in seconds) that Command::Interactive will wait for output when run() is called.
120              
121             =cut
122              
123             has timeout => (
124             is => 'rw',
125             isa => 'Int',
126             );
127              
128             =head2 web_format (DEFAULT: FALSE)
129              
130             Whether to format strings for web output when print command output as a result of echo_output(). If this is true, \r, \n, and \r\n will be replaced with "<br/>\n".
131              
132             =cut
133              
134             has web_format => (
135             is => 'rw',
136             isa => 'Bool',
137             default => 0,
138             );
139              
140             =head1 METHODS
141              
142             =head2 run($command)
143              
144             This method does the heavy lifting of Command::Interactive. If one or more interactions() are specified (or if always_use_expect() is true), then the heavy lifting is dispatched to _run_via_expect(); otherwise this method uses traditional perl C<open("$command |")> approach.
145              
146             $command is expected to be a scalar (string), properly escaped, that could be
147             executed (e.g.) via system() or ``. No matter what command you provide, the bash
148             file descriptors for stdout and stderr are tied together using '2>&1' unless you
149             have done so already. This allows Command::Interactive to capture and react to both regular output and errors using the same mechanism.
150              
151             run() returns undef if the command is successful, otherwise it returns a string describing why the command failed (or was thought to have failed).
152              
153             The command you pass in via $command is expected to exit with status code 0 on
154             success. If it returns something different, Command::Interactive will incorrectly conclude that the command failed and will return a message to that effect.
155              
156             =cut
157              
158             sub run {
159 20     20 1 1125 my $self = shift;
160 20         49 my $command = shift;
161              
162 20 100       85 confess "No command provided" unless ($command);
163              
164 19         22 my $result;
165 19 100 100     636 if ($self->always_use_expect or @{$self->interactions}) {
  13         319  
166             # We'll to use Expect to handle this,
167             # which means that we will be able to
168             # respond to input requests
169 14         44 $result = $self->_run_via_expect($command);
170             } else {
171 5         20 my $use_command = $self->_fixup_command_to_catch_stderr($command);
172 5         20 $self->_log("Executing $use_command");
173 4         39 my $cfh = IO::File->new("$use_command|");
174 4 100       9052 if ($cfh) {
175 2         858 while (my $output = <$cfh>) {
176 1         20 $self->_log("open() returned output: $output");
177 1         6 $self->_show_output($output);
178             }
179 2         42 $cfh->close;
180 2 100       85 $result = ($? >> 8) ? "Error executing $command: $!" : undef;
181             } else {
182 2         45 $result = "Could not execute $command: $!";
183             }
184             }
185              
186 18 100       750 $self->_log($result ? "Returning result: $result for command $command" : "Returning undef result, signifying success");
187 18         208 return $result;
188             }
189              
190             =head2 _show_output($chunk_of_output)
191              
192             If echo_output() is true, this command prints any output from $command to the chosen output_stream(). If web_format() is true, the output is first formatted for HTML by replacing end-of-line characters with "<br/>\n".
193              
194             =cut
195              
196             sub _show_output {
197 22     22   618 my $self = shift;
198 22         54 my $output = shift;
199              
200 22 100       730 return unless ($self->echo_output);
201              
202 2 100       72 $output =~ s/[\r\n]+/<br\/>\n/g if ($self->web_format);
203 2         13 $self->_log("Stream output: $output");
204 2         66 return $self->output_stream->print($output);
205             }
206              
207             =head2 _run_via_expect($command)
208              
209             This method handles running commands with one or more interactions() (or for which always_use_expect() is true) via the Perl module C<Expect>.
210              
211             The return semantics of _run_via_expect() are identical to those of run().
212              
213             =cut
214              
215             sub _run_via_expect {
216 14     14   21 my $self = shift;
217 14         50 my $command = shift;
218              
219 14         76 my $use_command = $self->_fixup_command_to_catch_stderr($command);
220              
221 14         62 $self->_log("Using Expect to spawn command: $use_command");
222              
223 14         117 my $exp = Expect->new;
224 14         8238 $exp->raw_pty(1);
225 14         480 $exp->log_stdout(0);
226              
227 14         334 my $e = $exp->spawn($use_command);
228 14 50       59090 return "Could not start $command: $!" unless ($e);
229              
230 14         144 my ($expect_array, $indexed_interactions) = $self->_generate_interaction_list;
231              
232 14         33 my $result;
233              
234 14         17 my ($match_position, $error, $matched_string, $before, $after);
235              
236 14         30 my $occurrences = [];
237              
238 14         33 my $already_closed = 0;
239              
240             EXPECT_READ:
241 14         43 while (!$error) {
242 20         702 ($match_position, $error, $matched_string, $before, $after) = $e->expect($self->timeout, @$expect_array);
243 20 100       2028420 if ($match_position) {
    50          
244             # Collapse this all into string just in case
245             # you have a f-ing retarded value of $/ or an
246             # overridden version of CORE::print() that puts
247             # newlines at the end of each print call
248 8         12 my $show;
249 8 100       29 $show .= $before if (length($before));
250 8         10 $show .= $matched_string;
251 8 50       25 $show .= $after if (length($after));
252 8         36 $self->_show_output($show);
253              
254             # Determine whether this was an interactive
255             # request for a response, or an error
256 8         13 $match_position -= 1;
257 8         18 my $i = $indexed_interactions->[$match_position];
258 8         45 my $type = $i->type;
259 8 100       32 $occurrences->[$match_position] = 0 unless ($occurrences->[$match_position]);
260 8         13 $occurrences->[$match_position]++;
261 8 100       292 if ($i->is_error) {
262 1         33 $result = "Got error string '$matched_string', which matched error detection $type '" . $i->expected_string . "'";
263 1         4 last EXPECT_READ;
264             }
265              
266 7 100       233 if ($occurrences->[$match_position] > $i->max_allowed_occurrences) {
267 1         47 $result =
268             "Got string '$matched_string', which matched expected $type '"
269             . $i->expected_string
270             . "'. This was occurrence #"
271             . $occurrences->[$match_position]
272             . ", which exceeds the specified limit of "
273             . $i->max_allowed_occurrences
274             . " occurrence(s) set for this $type";
275 1         6 last EXPECT_READ;
276             }
277 6 100       207 if ($i->response) {
278 1         24 $self->_log("Stream send: " . $i->response);
279 1         8 $e->send($i->actual_response_to_send);
280             }
281             } elsif ($error) {
282 12 100 100     199 if (($error eq '2:EOF') or $error =~ /3:Child PID (\d+) exited with/) {
    50          
    0          
    0          
283             # Let's see if there were any required
284             # interactions that failed to occur
285 11         62 for (my $count = 0; $count < scalar(@$indexed_interactions); $count++) {
286 7         13 my $i = $indexed_interactions->[$count];
287 7 100 66     392 if ($i->is_required and not $occurrences->[$count]) {
288 2         26 $result = "Failed to encounter required " . $i->type . " '" . $i->expected_string . "' before exit";
289             }
290             }
291             } elsif ($error eq '1:TIMEOUT') {
292 1         75 $result = "Got TIMEOUT from Expect (timeout=" . $self->timeout . " seconds)";
293 1         9 $e->hard_close;
294 1         373 $already_closed = 1;
295             } elsif ($error =~ /^3: (.+)/) # uncoverable
296             {
297 0         0 $result = "Failure on command: $1";
298             } elsif ($error =~ /^4:(.+)/) # uncoverable
299             {
300 0         0 $result = "Got error reading command filehandle: $1";
301             }
302 12         30 last EXPECT_READ;
303             }
304             }
305              
306             # Need to capture any remaining output
307 14 100       158 $self->_show_output($e->exp_before) if ($e->exp_before);
308 14 100       183 $e->expect(0) unless ($already_closed);
309             # In case the call to expect(0) caught any remaining output
310 14 100       1998 $self->_show_output($e->exp_before) if ($e->exp_before);
311 14 100       396 $e->soft_close unless ($already_closed);
312              
313 14 100 100     214 if ($e->exitstatus and not defined($result)) {
314 1         27 $result = 'Got back return value ' . $e->exitstatus . " from $command";
315             }
316              
317 14         379 return $result;
318             }
319              
320             =head2 _generate_instruction_list()
321              
322             This method returns information to be passed to C<Expect>'s expect() method, as well as a bookkeeping array using for tracking number of times a given interaction has occurred.
323              
324             =cut
325              
326             sub _generate_interaction_list {
327 14     14   54 my $self = shift;
328              
329 14         40 my $expect_array = [];
330 14         36 my $indexed_interactions = [];
331              
332 14         22 my $counter;
333 14         22 foreach my $i (@{$self->interactions}) {
  14         719  
334 10 100       376 push @$expect_array, '-re' if ($i->expected_string_is_regex);
335 10         369 push @$expect_array, $i->expected_string;
336 10         48 push @$indexed_interactions, $i;
337             }
338              
339 14         46 return ($expect_array, $indexed_interactions);
340              
341             }
342              
343             =head2 _fixup_command_to_catch_stderr($original_command)
344              
345             This method appends '2>&1' to the end of any command submitted to run(), except when that filehandle-tying string is already present in the command.
346              
347             Returns the modified version of $original_command.
348              
349             =cut
350              
351             sub _fixup_command_to_catch_stderr {
352 19     19   29 my $self = shift;
353 19         22 my $original_command = shift;
354              
355 19         46 my $use_command = $original_command;
356 19 100       76 $use_command .= " 2>&1" unless ($use_command =~ m#2>&1#);
357 19         44 return $use_command;
358             }
359              
360             =head2 _log($line_to_log)
361              
362             Used for internal logging purposes when debug_logfile() is defined. See the
363             discussion of debug_logfile() for a better way to debug YOUR command's
364             execution; this method is intended for consumption by developers of
365             Command::Interactive.
366              
367             =cut
368              
369             sub _log {
370 41     41   71 my $self = shift;
371 41         75 my $message = shift;
372              
373 41         42 my $result;
374              
375 41 100       1219 if ($self->debug_logfile) {
376 3         73 my $f = IO::File->new(">>" . $self->debug_logfile);
377 3 100       295 croak("Could not open debugging log file " . $self->debug_logfile) unless ($f);
378 2         11 my $result = $f->print(map { POSIX::strftime("[%Y-%m-%dT%H:%M:%SZ] $_\n", gmtime) } split(/[\r\n]/, $message));
  2         185  
379 2         30 $f->close;
380             }
381              
382 40         122 return $result;
383             }
384              
385 7     7   78 no Moose;
  7         14  
  7         64  
386             __PACKAGE__->meta->make_immutable;
387              
388             1;
389              
390             =head1 AUTHOR
391              
392             Binary.com, <perl@binary.com>
393              
394             =head1 LICENSE
395              
396             This module is free software; you can redistribute it and/or modify it under the
397             same terms as Perl itself.
398              
399             =cut
400