File Coverage

lib/Rex/Commands/Rsync.pm
Criterion Covered Total %
statement 9 10 90.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 13 14 92.8


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             =head1 NAME
6              
7             Rex::Commands::Rsync - Simple Rsync Frontend
8              
9             =head1 DESCRIPTION
10              
11             With this module you can sync 2 directories via the I command.
12              
13             Version <= 1.0: All these functions will not be reported.
14              
15             All these functions are not idempotent.
16              
17             =head1 DEPENDENCIES
18              
19             =over 4
20              
21             =item Expect
22              
23             The I Perl module is required to be installed on the machine
24             executing the rsync task.
25              
26             =item rsync
27              
28             The I command has to be installed on both machines involved in
29             the execution of the rsync task.
30              
31             =back
32              
33             =head1 SYNOPSIS
34              
35             use Rex::Commands::Rsync;
36              
37             sync "dir1", "dir2";
38              
39             =head1 EXPORTED FUNCTIONS
40              
41             =cut
42              
43             package Rex::Commands::Rsync;
44              
45 1     1   18 use v5.12.5;
  1         3  
46 1     1   5 use warnings;
  1         1  
  1         75  
47              
48             our $VERSION = '1.14.3'; # VERSION
49              
50             BEGIN {
51 1     1   11 use Rex::Require;
  1         2  
  1         22  
52 1     1   93 Expect->use;
53 0           $Expect::Log_Stdout = 0;
54             }
55              
56             require Rex::Exporter;
57              
58             use base qw(Rex::Exporter);
59             use vars qw(@EXPORT);
60              
61             use Net::OpenSSH::ShellQuoter;
62             use Rex::Commands qw(FALSE TRUE);
63             use Rex::Helper::IP;
64             use Rex::Helper::Path;
65             use Rex::Helper::Run;
66             use Rex::Interface::Shell;
67              
68             @EXPORT = qw(sync);
69              
70             =head2 sync($source, $dest, $opts)
71              
72             This function executes the rsync command I (where rex is being run) to sync $source and $dest with a remote. The C command is
73             invoked with the C<--recursive --links --verbose --stats> options set.
74              
75             If you want to use sudo, you need to disable I option for this user. You can do this with the following snippet in your sudoers configuration.
76              
77             Defaults:username !requiretty
78              
79             =over 4
80              
81             =item UPLOAD - Will upload all from the local directory I to the remote directory I.
82              
83             task "sync", "server01", sub {
84             sync "html/*", "/var/www/html", {
85             exclude => "*.sw*",
86             parameters => '--backup --delete',
87             };
88             };
89              
90             task "sync", "server01", sub {
91             sync "html/*", "/var/www/html", {
92             exclude => ["*.sw*", "*.tmp"],
93             parameters => '--backup --delete',
94             };
95             };
96              
97             =item DOWNLOAD - Will download all from the remote directory I to the local directory I.
98              
99             task "sync", "server01", sub {
100             sync "/var/www/html/*", "html/", {
101             download => 1,
102             parameters => '--backup',
103             };
104             };
105              
106             =back
107              
108             =cut
109              
110             sub sync {
111             my ( $source, $dest, $opt ) = @_;
112              
113             my $current_connection = Rex::get_current_connection();
114             my $server = $current_connection->{server};
115             my $cmd;
116              
117             my ( $port, $servername );
118              
119             if ( defined $server->to_s ) {
120             ( $servername, $port ) =
121             Rex::Helper::IP::get_server_and_port( $server->to_s, 22 );
122             }
123              
124             my $local_connection = TRUE;
125              
126             if ( defined $servername && $servername ne '' ) {
127             $local_connection = FALSE;
128             }
129              
130             my $auth = $current_connection->{conn}->get_auth;
131              
132             if ( !exists $opt->{download} && $source !~ m/^\// ) {
133              
134             # relative path, calculate from module root
135             $source = Rex::Helper::Path::get_file_path( $source, caller() );
136             }
137              
138             Rex::Logger::debug("Syncing $source -> $dest with rsync.");
139             if ($Rex::Logger::debug) {
140             $Expect::Log_Stdout = 1;
141             }
142              
143             my $params = "";
144             if ( $opt && exists $opt->{'exclude'} ) {
145             my $excludes = $opt->{'exclude'};
146             $excludes = [$excludes] unless ref($excludes) eq "ARRAY";
147             for my $exclude (@$excludes) {
148             $params .= " --exclude=" . $exclude;
149             }
150             }
151              
152             if ( $opt && exists $opt->{parameters} ) {
153             $params .= " " . $opt->{parameters};
154             }
155              
156             my @rsync_cmd = ();
157              
158             my $exec = Rex::Interface::Exec->create;
159             my $quoter = Net::OpenSSH::ShellQuoter->quoter( $exec->shell->name );
160              
161             if ( $opt && exists $opt->{'download'} && $opt->{'download'} == 1 ) {
162             $dest = resolv_path($dest);
163             Rex::Logger::debug("Downloading $source -> $dest");
164             push @rsync_cmd, "rsync -rl --verbose --stats $params ";
165              
166             if ( !$local_connection ) {
167             push @rsync_cmd, "-e '\%s'";
168             $source = $auth->{user} . "\@$servername:$source";
169             }
170             }
171             else {
172             $source = resolv_path($source);
173             Rex::Logger::debug("Uploading $source -> $dest");
174              
175             push @rsync_cmd, "rsync -rl --verbose --stats $params";
176              
177             if ( !$local_connection ) {
178             push @rsync_cmd, "-e '\%s'";
179             $dest = $auth->{user} . "\@$servername:$dest";
180             }
181             }
182              
183             $source = $quoter->quote_glob($source);
184             $dest = $quoter->quote_glob($dest);
185              
186             push @rsync_cmd, $source;
187             push @rsync_cmd, $dest;
188              
189             if (Rex::is_sudo) {
190             push @rsync_cmd, "--rsync-path='sudo rsync'";
191             }
192              
193             $cmd = join( " ", @rsync_cmd );
194              
195             if ( !$local_connection ) {
196             my $pass = $auth->{password};
197             my @expect_options = ();
198              
199             my $auth_type = $auth->{auth_type};
200             if ( $auth_type eq "try" ) {
201             if ( $server->get_private_key && -f $server->get_private_key ) {
202             $auth_type = "key";
203             }
204             else {
205             $auth_type = "pass";
206             }
207             }
208              
209             if ( $auth_type eq "pass" ) {
210             $cmd = sprintf( $cmd,
211             "ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no -p $port",
212             );
213             push(
214             @expect_options,
215             [
216             qr{Are you sure you want to continue connecting},
217             sub {
218             Rex::Logger::debug("Accepting key..");
219             my $fh = shift;
220             $fh->send("yes\n");
221             exp_continue;
222             }
223             ],
224             [
225             qr{password: ?$}i,
226             sub {
227             Rex::Logger::debug("Want Password");
228             my $fh = shift;
229             $fh->send( $pass . "\n" );
230             exp_continue;
231             }
232             ],
233             [
234             qr{password for.*:$}i,
235             sub {
236             Rex::Logger::debug("Want Password");
237             my $fh = shift;
238             $fh->send( $pass . "\n" );
239             exp_continue;
240             }
241             ],
242             [
243             qr{rsync error: error in rsync protocol},
244             sub {
245             Rex::Logger::debug("Error in rsync");
246             die;
247             }
248             ],
249             [
250             qr{rsync error: remote command not found},
251             sub {
252             Rex::Logger::info("Remote rsync command not found");
253             Rex::Logger::info(
254             "Please install rsync, or use Rex::Commands::Sync sync_up/sync_down"
255             );
256             die;
257             }
258             ],
259              
260             );
261             }
262             else {
263             if ( $auth_type eq "key" ) {
264             $cmd = sprintf( $cmd,
265             'ssh -i '
266             . $server->get_private_key
267             . " -o StrictHostKeyChecking=no -p $port" );
268             }
269             else {
270             $cmd = sprintf( $cmd, 'ssh -o StrictHostKeyChecking=no -p ' . "$port" );
271             }
272             push(
273             @expect_options,
274             [
275             qr{Are you sure you want to continue connecting},
276             sub {
277             Rex::Logger::debug("Accepting key..");
278             my $fh = shift;
279             $fh->send("yes\n");
280             exp_continue;
281             }
282             ],
283             [
284             qr{password: ?$}i,
285             sub {
286             Rex::Logger::debug("Want Password");
287             my $fh = shift;
288             $fh->send( $pass . "\n" );
289             exp_continue;
290             }
291             ],
292             [
293             qr{Enter passphrase for key.*: $},
294             sub {
295             Rex::Logger::debug("Want Passphrase");
296             my $fh = shift;
297             $fh->send( $pass . "\n" );
298             exp_continue;
299             }
300             ],
301             [
302             qr{rsync error: error in rsync protocol},
303             sub {
304             Rex::Logger::debug("Error in rsync");
305             die;
306             }
307             ],
308             [
309             qr{rsync error: remote command not found},
310             sub {
311             Rex::Logger::info("Remote rsync command not found");
312             Rex::Logger::info(
313             "Please install rsync, or use Rex::Commands::Sync sync_up/sync_down"
314             );
315             die;
316             }
317             ],
318              
319             );
320             }
321              
322             Rex::Logger::debug("cmd: $cmd");
323              
324             eval {
325             my $exp = Expect->spawn($cmd) or die($!);
326              
327             eval {
328             $exp->expect(
329             Rex::Config->get_timeout,
330             @expect_options,
331             [
332             qr{total size is [\d,]+\s+speedup is },
333             sub {
334             Rex::Logger::debug("Finished transfer very fast");
335             die;
336             }
337              
338             ]
339             );
340              
341             $exp->expect(
342             undef,
343             [
344             qr{total size is [\d,]+\s+speedup is },
345             sub {
346             Rex::Logger::debug("Finished transfer");
347             exp_continue;
348             }
349             ],
350             [
351             qr{rsync error: error in rsync protocol},
352             sub {
353             Rex::Logger::debug("Error in rsync");
354             die;
355             }
356             ],
357             );
358              
359             };
360              
361             $exp->soft_close;
362             $? = $exp->exitstatus;
363             };
364             }
365             else {
366             Rex::Logger::debug("Executing command: $cmd");
367              
368             i_run $cmd, fail_ok => 1;
369              
370             if ( $? != 0 ) {
371             die 'Error during local rsync operation';
372             }
373             }
374              
375             if ($@) {
376             Rex::Logger::info($@);
377             }
378              
379             }
380              
381             1;