File Coverage

blib/lib/Net/SSH/Mechanize/Multi.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Net::SSH::Mechanize::Multi;
2 1     1   1546 use Moose;
  1         3  
  1         7  
3 1     1   6230 use Net::SSH::Mechanize;
  1         5  
  1         27  
4 1     1   5 use Carp qw(croak);
  1         2  
  1         48  
5 1     1   340 use Coro;
  0            
  0            
6              
7             our $VERSION = '0.1.2'; # VERSION
8              
9             ######################################################################
10             # attributes
11              
12             has 'ssh_instances' => (
13             isa => 'ArrayRef[Net::SSH::Mechanize]',
14             is => 'ro',
15             default => sub { [] },
16             );
17              
18              
19             has 'names' => (
20             isa => 'HashRef[Net::SSH::Mechanize]',
21             is => 'ro',
22             default => sub { +{} },
23             );
24              
25             has 'constructor_defaults' => (
26             isa => 'HashRef',
27             is => 'rw',
28             default => sub { +{} },
29             );
30              
31             ######################################################################
32              
33             sub _to_ssh {
34             my $self = shift;
35             my @instances;
36             my $defaults = $self->constructor_defaults;
37              
38             while(@_) {
39             my ($name, $connection) = splice @_, 0, 2;
40             $connection = Net::SSH::Mechanize->new(%$defaults, %$connection)
41             if ref $connection eq 'HASH';
42              
43             $connection = Net::SSH::Mechanize->new(%$defaults, connection_params => $connection)
44             if blessed $connection
45             && $connection->isa('Net::SSH::Mechanize::ConnectParams');
46            
47             croak "Connection '$name' is not a hashref, Net::SSH::Mechanize::ConnectParams instance, nor a",
48             "Net::SSH::Mechanize instance (it is $connection)"
49             unless blessed $connection
50             && $connection->isa('Net::SSH::Mechanize');
51              
52             push @instances, $connection;
53             }
54              
55             return @instances;
56             }
57              
58              
59             sub add {
60             my $self = shift;
61             croak "uneven number of name => connection parameters"
62             if @_ % 2;
63              
64             my %new_instances = @_;
65              
66             my @new_names = keys %new_instances;
67             my $names = $self->names;
68             my @defined = grep { $names->{$_} } @new_names;
69              
70             croak "These names are already defined: @defined"
71             if @defined;
72              
73             my @new_instances = $self->_to_ssh(%new_instances);
74            
75             my $instances = $self->ssh_instances;
76              
77             @$names{@new_names} = @new_instances;
78             push @$instances, @new_instances;
79              
80             return @new_instances;
81             }
82              
83              
84             sub in_parallel {
85             my $self = shift;
86             my $cb = pop;
87             croak "you must supply a callback"
88             unless ref $cb eq 'CODE';
89            
90             my @names = @_;
91             my $known_names = $self->names;
92             my @instances = map { $known_names->{$_} } @names;
93             if (@names != grep { defined } @instances) {
94             my @unknown = grep { !$known_names->{$_} } @names;
95             croak "These names are unknown: @unknown";
96             }
97              
98             my @threads;
99             my $ix = 0;
100              
101             foreach my $ix (0..$#instances) {
102             push @threads, async {
103             my $name = $names[$ix];
104             my $ssh = $instances[$ix];
105            
106             eval {
107             $cb->($name, $ssh);
108             1;
109             } or do {
110             print "error ($name): $@";
111             };
112             }
113             }
114              
115             return \@threads;
116             }
117              
118              
119              
120             __PACKAGE__->meta->make_immutable;
121             1;
122             __END__
123              
124             =head1 NAME
125              
126             Net::SSH::Mechanize::Multi - parallel ssh invocation
127              
128             =head1 VERSION
129              
130             version 0.1.2
131              
132             =head1 SYNOPSIS
133              
134             my $manager = Net::SSH::Mechanize::Multi->new(
135              
136             # Set the default parameters for the Net::SSH::Mechanize
137             # instances we will create
138             constructor_defaults => { login_timeout => 180 },
139              
140             );
141              
142             # Add connection definitions as a list of connection-name to
143             # Net::SSH::Mechanize instance pairs (or shorthand equivalents, as
144             # illustrated).
145             $manager->add(
146              
147             # This defines the connection using a
148             # Net::SSH::Mechanize::ConnectParams instance (or subclass
149             # thereof)
150             connection1 => Net::SSH::Mechanize::ConnectParams->new(
151             hostname => 'host1.com',
152             ),
153              
154             # This defines it using a hashref of constructor parameters
155             # for Net::SSH::Mechanize
156             connection2 => {
157             user => 'joe',
158             hostname => 'host1.com',
159             login_timeout => 60,
160             },
161            
162             # This passes a Net::SSH::Mechanize instance directly
163             connection3 => Net::SSH::Mechanize->new(
164             user => 'joe',
165             hostname => 'host2.com',
166             login_timeout => 60,
167             ),
168              
169             # ...
170             );
171              
172             # At this point no hosts have been contacted.
173              
174             # Connect to a named subset of them all in parallel like this.
175             # The callback should expect the name and the appropriate
176             # Net::SSH::Mechanize instance as arguments.
177             # Synchronous commands in the callback work asyncronously
178             # thanks to Coro::AnyEvent.
179             my @names = qw(host1 host2);
180             my $threads = $manager->in_parallel(@names => sub {
181             my ($name, $ssh) = @_;
182              
183             printf "About to connect to %s using;\n'$s'\n\n",
184             $name, $ssh->connection_params->ssh_cmd
185              
186             # note, the login is done implicitly when you access the
187             # ->session parameter, or a method which delegates to it.
188              
189             # do stuff...
190             print "checking git status of config definition:\n";
191             print $ssh->sudo_capture("cd /root/config; git status");
192            
193             # ...
194             });
195              
196             # Wait for them all to complete.
197             $_->join for @$threads;
198              
199             print "done\n";
200              
201              
202             There is a full implementation of this kind of usage is in the
203             C<gofer> script included in this distribution.
204              
205             =head1 CLASS METHODS
206              
207             =head2 C<< $obj = $class->new(%params) >>
208              
209             Creates a new instance. Parameters is a hash or a list of key-value
210             parameters. Valid parameter keys are:
211              
212             =over 4
213              
214             =item C<constructor_defaults>
215              
216             This is an optional parameter which can be used to define a hashref of
217             default parameters to pass to C<Net::SSH::Mechanize> instances created
218             by this class. These defaults can be overridden in individual cases.
219              
220             =back
221              
222             Currently it is also possible to pass parameters to initialise the
223             C<names> and C<ssh_instances> attributes, but since the content of
224             those are intended to be correlated this is definitely not
225             recommended. Use C<< ->add >> instead.
226              
227             =head1 INSTANCE ATTRIBUTES
228              
229             =head2 C<< $hashref = $obj->constructor_defaults >>
230             =head2 C<< $obj->constructor_defaults(\%hashref) >>
231              
232             This is an read-write accessor which allows you to specify the default
233             parameters to pass to the C<Net::SSH::Mechanize> constructor. These
234             defaults can be overridden in individual cases.
235              
236             =head2 C<< $hashref = $obj->names >>
237              
238             This is a hashref of connection names mapped to C<Net::SSH::Mechanize>
239             instances. It is not recommend you change this directly, use the
240             C<< ->add >> method instead.
241              
242             =head2 C<< $hashref = $obj->ssh_instances >>
243              
244             This is an arrayref listing C<Net::SSH::Mechanize> instances we have
245             created, in the order they have been defined via C<< ->add >> (which
246             is also the order they will be iterated over within C<< ->in_parallel >>).
247              
248             =head1 INSTANCE METHODS
249              
250             =head2 C<< @mech_instances = $obj->add(%connections) >>
251              
252             Adds one or more named connection definitions.
253              
254             C<%connections> is a hash or list of name-value pairs defining what to
255             connect to. The order in which they are defined here is preserved and
256             used when iterating over them in C<< ->in_parallel >>.
257              
258             The names are simple string aliases, and can be anything unique (to
259             this instance). If a duplicate is encountered, an exception will be
260             thrown.
261              
262             The values can be:
263              
264             =over 4
265              
266             =item B<<A C<Net::SSH::Mechanize> instance>>
267              
268             The instance will be used as given to connect to a host.
269              
270             =item B<<A C<Net::SSH::Mechanize::ConnectParams>> instance>>
271              
272             The instance will be used to create a C<Net::SSH::Mechanize> instance to use.
273              
274             =item B<A hashref of constructor paramters for C<Net::SSH::Mechanize> >>
275              
276             The hashref will be passed to C<< Net::SSH::Mechanize->new >> to get an instance to use.
277              
278             =back
279              
280             A list of the C<Net::SSH::Mechanize> instances are returned, in the
281             order they were defined.
282              
283             This method can be called any number of times, so long as the
284             connection names are never duplicated.
285              
286              
287             =head2 C<< @threads = $obj->in_parallel(@names, \&actions) >>
288              
289             This method accepts a list of host-names, and a callback. The callback
290             should expect two parameters: a connection name, and a
291             C<Net::SSH::Mechanize> instance.
292              
293             It first checks that all the names have been defined in an earlier
294             C<< ->add >> invocation. If any are unknown, an exception is thrown.
295              
296             Otherwise, it iterates over the names in the order given, and invokes
297             the callback with the name and the appropriate C<Net::SSH::Mechanize>
298             instance.
299              
300             Note: the callback is invoked each time as a I<co-routine>. See
301             L<Coro> and L<Coro::AnyEvent> for more information about this, but it
302             essentially means that each one is asynchronously run in parallel.
303             This method returns a list of C<Coro> threads, immediately, before the
304             callbacks have completed.
305              
306             Your program is then free to do other things, and/or call C<< ->join >>
307             on each of the threads to wait for their termination.
308              
309             =head1 AUTHOR
310              
311             Nick Stokoe C<< <wulee@cpan.org> >>
312              
313              
314             =head1 LICENCE AND COPYRIGHT
315              
316             Copyright (c) 2011, Nick Stokoe C<< <wulee@cpan.org> >>. All rights reserved.
317              
318             This module is free software; you can redistribute it and/or
319             modify it under the same terms as Perl itself. See L<perlartistic>.