5 use Pod
::Usage
qw( pod2usage );
6 use Getopt
::Long
qw( :config gnu_getopt );
7 use version
; my $VERSION = qv
('0.0.1');
8 use English
qw( -no_match_vars );
12 use Net
::SSH
::Perl
::Auth
;
13 use Net
::SSH
::Perl
::Constants
qw(
14 SSH2_MSG_USERAUTH_REQUEST
15 SSH2_MSG_USERAUTH_FAILURE
16 SSH2_MSG_USERAUTH_INFO_REQUEST
17 SSH2_MSG_USERAUTH_INFO_RESPONSE );
18 use Net
::SSH
::Perl
::Auth
::KeyboardInt
;
20 use Net
::SFTP
::Attributes
;
21 use File
::Basename
qw( basename );
22 use File
::Spec
::Functions
qw( catfile );
24 # Integrated logging facility
25 use Log
::Log4perl
qw( :easy );
26 Log
::Log4perl
->easy_init($INFO);
31 dir
=> '/tmp/our-deploy',
37 'version', 'username|user|u=s',
38 'password|pass|p=s', 'debug|D!',
39 'dir|directory|d=s', 'script|s=s',
42 pod2usage
(message
=> "$0 $VERSION", -verbose
=> 99, -sections
=> '')
44 pod2usage
(-verbose
=> 99, -sections
=> 'USAGE') if $config{usage
};
45 pod2usage
(-verbose
=> 99, -sections
=> 'USAGE|EXAMPLES|OPTIONS')
47 pod2usage
(-verbose
=> 2) if $config{man
};
49 # Script implementation here
50 my @hostnames = @ARGV;
53 $config{password
} = prompt
'password: ', -e
=> '*'
54 unless defined($config{password
}) && length($config{password
});
56 ($config{remote
} = $config{script
}) =~ s{[^\w.-]}{}mxsg;
57 $config{remote
} = catfile
($config{dir
}, $config{remote
});
59 for my $hostname (@hostnames) {
60 eval { operate_on_host
($hostname) };
61 carp
$EVAL_ERROR if $EVAL_ERROR;
66 my $remote = $config{remote
};
68 print "*** OPERATING ON $hostname ***\n";
69 if ($config{prompt
}) {
70 my $choice = lc(prompt
"$hostname - continue? (Yes | Skip | No) ",
71 -while => qr/\A[nsy]\z/mxs);
72 return if $choice eq 's';
73 exit 0 if $choice eq 'n';
74 } ## end if ($config{prompt})
76 # Transfer file into $remote
77 my $sftp = get_sftp
(get_ssh
($hostname));
78 make_path
($sftp, $config{dir
});
79 $sftp->put($config{script
}, $remote);
80 croak
"no $remote, sorry. Stopped" unless $sftp->do_stat($remote);
83 my $ssh = get_ssh
($hostname);
86 my ($out, $err, $exit) = $ssh->cmd($remote);
87 print "exit = $exit\n";
88 for ([STDOUT
=> $out], [STDERR
=> $err]) {
89 my ($type, $val) = @
$_;
90 next unless defined $val;
91 $val =~ s{\s+\z}{}mxs;
93 print "+ $type\n|\n$val\n|\n+ end of $type\n\n";
94 } ## end for ([STDOUT => $out], ...
96 } ## end sub operate_on_host
99 my ($sftp, $fullpath) = @_;
102 for my $chunk (split m{/}mxs, $fullpath) {
103 $path .= $chunk . '/'; # works fine with the root
104 next if $sftp->do_stat($path);
105 $sftp->do_mkdir($path, Net
::SFTP
::Attributes
->new());
107 croak
"no $path, sorry. Stopped" unless $sftp->do_stat($path);
110 } ## end sub make_path
114 my $ssh = Net
::SSH
::Perl
->new(
117 debug
=> $config{debug
}
119 $ssh->config->set(interactive
=> 1); # false!!!
120 $ssh->config->set(identity_files
=> []); # avoid 'em
121 $ssh->login($config{username
}, $config{password
}, 'suppress_shell');
127 return Net
::SFTP
::Mine
->new(
132 } ## end sub get_sftp
137 sub Net
::SSH
::Perl
::Auth
::KeyboardInt
::authenticate
{
139 my $ssh = $auth->{ssh
};
142 $packet = $ssh->packet_start(SSH2_MSG_USERAUTH_REQUEST
);
143 $packet->put_str($ssh->config->get('user'));
144 $packet->put_str("ssh-connection");
145 $packet->put_str("keyboard-interactive");
146 $packet->put_str(""); ## language
147 $packet->put_str(""); ## devices
150 $auth->mgr->register_handler(SSH2_MSG_USERAUTH_INFO_REQUEST
,
154 my $name = $packet->get_str;
155 my $instructions = $packet->get_str;
156 $packet->get_str; ## language
158 my $prompts = $packet->get_int32;
159 my $pres = $ssh->packet_start(SSH2_MSG_USERAUTH_INFO_RESPONSE
);
160 $pres->put_int32($prompts);
161 $pres->put_str($ssh->config->get('pass')) if $prompts;
167 } ## end sub Net::SSH::Perl::Auth::KeyboardInt::authenticate
170 package Net
::SFTP
::Mine
;
171 use base
qw( Net::SFTP );
172 use Net
::SSH
::Perl
::Constants
qw( :msg2 );
173 use Net
::SFTP
::Constants
174 qw( :fxp :flags :status :att SSH2_FILEXFER_VERSION );
180 $sftp->{debug
} = delete $param{debug
};
181 $sftp->{status
} = SSH2_FX_OK
;
183 $param{warn} = 1 if not defined $param{warn}; # default
184 $sftp->{warn_h
} = delete $param{warn} || sub { }; # false => ignore
185 $sftp->{warn_h
} = sub { carp
$_[1] } # true => emit warning
186 if $sftp->{warn_h
} and not ref $sftp->{warn_h
};
188 $sftp->{_msg_id
} = 0;
190 $sftp->{ssh
} = delete $param{ssh
};
192 my $channel = $sftp->_open_channel;
193 $sftp->{channel
} = $channel;
204 deploy - deploy a script on one or more remote hosts, via ssh
208 See version at beginning of script, variable $VERSION, or call
210 shell$ deploy --version
215 deploy [--usage] [--help] [--man] [--version]
217 deploy [--debug|-D] [--dir|--directory|-d <dirname>]
218 [--password|--pass|-p] [--prompt|-P]
219 [--script|-s <scriptname>] [--username|--user|-u]
225 # Upload deploy-script.pl and execute it on each server listed
227 shell$ deploy -s deploy-script.pl `cat targets`
231 This utility allows you to I<deploy> a script to one or more remote
232 hosts. Thus, you can provide a script that will be uploaded (via
233 B<sftp>) to the remote host and executed (via B<ssh>).
235 Before operations start for each host you will be prompted for
236 continuation: you can choose to go, skip or quit. You can disable
237 this by specifying C<--no-prompt>.
239 By default, directory C</tmp/our-deploy> on the target system will be
240 used. You can provide your own working directory path on the target system
241 via the C<--dir|--directory|-d> option. The directory will be created
242 if it does not exist.
244 For logging in, you can provide your own username/password pair directly
245 on the command line. Note that this utility explicitly avoids public
246 key authentication in favour of username/password authentication. Don't
247 ask me why, this may change in the future. Anyway, you're not obliged
248 to provide either on the command line: the username defaults to C<root>,
249 and you'll be prompted to provide a password if you don't put any
250 on the command line. The prompt does not show the password on the terminal.
258 turns on debug mode, which should print out more stuff during operations.
259 You should not need it as a user.
261 =item --dir | --directory | -d <dirname>
263 specify the working directory on the target system. This is the
264 directory into which the deploy script will be uploaded. It will
265 be created if it does not exist.
267 Defaults to C</tmp/our-deploy>.
271 print a somewhat more verbose help, showing usage, this description of
272 the options and some examples from the synopsis.
276 print out the full documentation for the script.
278 =item --password | --pass | -p <password>
280 you can specify the password on the command line, even if it's probably
281 best B<NOT> to do so and wait for the program to prompt you one.
283 By default, you'll be prompted a password and this will not be written
288 this option enables prompting before operations are started on each
289 host. When the prompt is enabled, you're presented with three choices:
295 B<Yes> continue deployment on the given host;
299 B<Skip> skip this host;
303 B<No> stop deployment and exit.
307 One letter suffices. By default, C<Yes> is assumed.
309 By default this option is I<always> active, so you're probably
310 interested in C<--no-prompt> to disable it.
312 =item --script | -s <scriptname>
314 set the script/program to upload and execute. This script will be uploaded
315 to the target system (see C<--directory|-d> above), but the name of the
316 script will be sanitised (only alphanumeric, C<_>, C<.> and C<-> will
317 be retained), so be careful if you have to look for the uploaded
322 print a concise usage line and exit.
324 =item --username | --user | -u <username>
326 specify the user name to use for logging into the remote host(s).
332 print the version of the script.
340 =item C<< no %s, sorry. Stopped at... >>
342 The given element is not available on the target system.
344 In case of the directory, this means that the automatic creation
345 process did not work for any reason. In case of the script, this
346 means that the file upload did not work.
351 =head1 CONFIGURATION AND ENVIRONMENT
353 deploy requires no configuration files or environment variables.
378 L<version>, but you should find it if you're using version 5.10
383 =head1 BUGS AND LIMITATIONS
385 No bugs have been reported.
387 Please report any bugs or feature requests through http://rt.cpan.org/
392 Flavio Poletti C<flavio@polettix.it>
395 =head1 LICENCE AND COPYRIGHT
397 Copyright (c) 2007-2008, Flavio Poletti C<flavio@polettix.it>.
400 This script is free software; you can redistribute it and/or
401 modify it under the same terms as Perl itself. See L<perlartistic>
404 Questo script è software libero: potete ridistribuirlo e/o
405 modificarlo negli stessi termini di Perl stesso. Vedete anche
406 L<perlartistic> e L<perlgpl>.
409 =head1 DISCLAIMER OF WARRANTY
411 BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
412 FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
413 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
414 PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
415 EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
416 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
417 ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
418 YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
419 NECESSARY SERVICING, REPAIR, OR CORRECTION.
421 IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
422 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
423 REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
424 LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
425 OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
426 THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
427 RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
428 FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
429 SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
432 =head1 NEGAZIONE DELLA GARANZIA
434 Poiché questo software viene dato con una licenza gratuita, non
435 c'è alcuna garanzia associata ad esso, ai fini e per quanto permesso
436 dalle leggi applicabili. A meno di quanto possa essere specificato
437 altrove, il proprietario e detentore del copyright fornisce questo
438 software "così com'è" senza garanzia di alcun tipo, sia essa espressa
439 o implicita, includendo fra l'altro (senza però limitarsi a questo)
440 eventuali garanzie implicite di commerciabilità e adeguatezza per
441 uno scopo particolare. L'intero rischio riguardo alla qualità ed
442 alle prestazioni di questo software rimane a voi. Se il software
443 dovesse dimostrarsi difettoso, vi assumete tutte le responsabilità
444 ed i costi per tutti i necessari servizi, riparazioni o correzioni.
446 In nessun caso, a meno che ciò non sia richiesto dalle leggi vigenti
447 o sia regolato da un accordo scritto, alcuno dei detentori del diritto
448 di copyright, o qualunque altra parte che possa modificare, o redistribuire
449 questo software così come consentito dalla licenza di cui sopra, potrà
450 essere considerato responsabile nei vostri confronti per danni, ivi
451 inclusi danni generali, speciali, incidentali o conseguenziali, derivanti
452 dall'utilizzo o dall'incapacità di utilizzo di questo software. Ciò
453 include, a puro titolo di esempio e senza limitarsi ad essi, la perdita
454 di dati, l'alterazione involontaria o indesiderata di dati, le perdite
455 sostenute da voi o da terze parti o un fallimento del software ad
456 operare con un qualsivoglia altro software. Tale negazione di garanzia
457 rimane in essere anche se i dententori del copyright, o qualsiasi altra
458 parte, è stata avvisata della possibilità di tali danneggiamenti.
460 Se decidete di utilizzare questo software, lo fate a vostro rischio
461 e pericolo. Se pensate che i termini di questa negazione di garanzia
462 non si confacciano alle vostre esigenze, o al vostro modo di
463 considerare un software, o ancora al modo in cui avete sempre trattato
464 software di terze parti, non usatelo. Se lo usate, accettate espressamente
465 questa negazione di garanzia e la piena responsabilità per qualsiasi
466 tipo di danno, di qualsiasi natura, possa derivarne.