# # (c) Jan Gehring # # vim: set ts=2 sw=2 tw=0: # vim: set expandtab: =head1 NAME Rex::Commands::Rsync - Simple Rsync Frontend =head1 DESCRIPTION With this module you can sync 2 directories via the I command. Version <= 1.0: All these functions will not be reported. All these functions are not idempotent. =head1 DEPENDENCIES =over 4 =item Expect The I Perl module is required to be installed on the machine executing the rsync task. =item rsync The I command has to be installed on both machines involved in the execution of the rsync task. =back =head1 SYNOPSIS use Rex::Commands::Rsync; sync "dir1", "dir2"; =head1 EXPORTED FUNCTIONS =over 4 =cut package Rex::Commands::Rsync; { $Rex::Commands::Rsync::VERSION = '0.55.3'; } use strict; use warnings; BEGIN { use Rex::Require; Expect->use; $Expect::Log_Stdout = 0; } require Rex::Exporter; use base qw(Rex::Exporter); use vars qw(@EXPORT); @EXPORT = qw(sync); =item sync($source, $dest, $opts) This function executes rsync to sync $source and $dest. =over 4 =item UPLOAD - Will upload all from the local directory I to the remote directory I. task "sync", "server01", sub { sync "html/*", "/var/www/html", { exclude => "*.sw*", parameters => '--backup --delete', }; }; task "sync", "server01", sub { sync "html/*", "/var/www/html", { exclude => ["*.sw*", "*.tmp"], parameters => '--backup --delete', }; }; =item DOWNLOAD - Will download all from the remote directory I to the local directory I. task "sync", "server01", sub { sync "/var/www/html/*", "html/", { download => 1, parameters => '--backup', }; }; =back =cut sub sync { my ( $source, $dest, $opt ) = @_; my $current_connection = Rex::get_current_connection(); my $server = $current_connection->{server}; my $cmd; my $auth = $current_connection->{conn}->get_auth; if ( !exists $opt->{download} && $source !~ m/^\// ) { # relative path, calculate from module root $source = Rex::Helper::Path::get_file_path( $source, caller() ); } Rex::Logger::debug("Syncing $source -> $dest with rsync."); if ($Rex::Logger::debug) { $Expect::Log_Stdout = 1; } my $params = ""; if ( $opt && exists $opt->{'exclude'} ) { my $excludes = $opt->{'exclude'}; $excludes = [$excludes] unless ref($excludes) eq "ARRAY"; for my $exclude (@$excludes) { $params .= " --exclude=" . $exclude; } } if ( $opt && exists $opt->{parameters} ) { $params .= " " . $opt->{parameters}; } if ( $opt && exists $opt->{'download'} && $opt->{'download'} == 1 ) { Rex::Logger::debug("Downloading $source -> $dest"); $cmd = "rsync -a -e '\%s' --verbose --stats $params " . $auth->{user} . "\@" . $server . ":" . $source . " " . $dest; } else { Rex::Logger::debug("Uploading $source -> $dest"); $cmd = "rsync -a -e '\%s' --verbose --stats $params $source " . $auth->{user} . "\@" . $server . ":" . $dest; } my $pass = $auth->{password}; my @expect_options = (); my $auth_type = $auth->{auth_type}; if ( $auth_type eq "try" ) { if ( $server->get_private_key && -f $server->get_private_key ) { $auth_type = "key"; } else { $auth_type = "pass"; } } if ( $auth_type eq "pass" ) { $cmd = sprintf( $cmd, 'ssh -o StrictHostKeyChecking=no -o PubkeyAuthentication=no ' ); push( @expect_options, [ qr{Are you sure you want to continue connecting}, sub { Rex::Logger::debug("Accepting key.."); my $fh = shift; $fh->send("yes\n"); exp_continue; } ], [ qr{password: ?$}i, sub { Rex::Logger::debug("Want Password"); my $fh = shift; $fh->send( $pass . "\n" ); exp_continue; } ], [ qr{password for.*:$}i, sub { Rex::Logger::debug("Want Password"); my $fh = shift; $fh->send( $pass . "\n" ); exp_continue; } ], [ qr{rsync error: error in rsync protocol}, sub { Rex::Logger::debug("Error in rsync"); die; } ], [ qr{rsync error: remote command not found}, sub { Rex::Logger::info("Remote rsync command not found"); Rex::Logger::info( "Please install rsync, or use Rex::Commands::Sync sync_up/sync_down" ); die; } ], ); } else { if ( $auth_type eq "key" ) { $cmd = sprintf( $cmd, 'ssh -i ' . $server->get_private_key . " -o StrictHostKeyChecking=no " ); } else { $cmd = sprintf( $cmd, 'ssh -o StrictHostKeyChecking=no ' ); } push( @expect_options, [ qr{Are you sure you want to continue connecting}, sub { Rex::Logger::debug("Accepting key.."); my $fh = shift; $fh->send("yes\n"); exp_continue; } ], [ qr{password: ?$}i, sub { Rex::Logger::debug("Want Password"); my $fh = shift; $fh->send( $pass . "\n" ); exp_continue; } ], [ qr{Enter passphrase for key.*: $}, sub { Rex::Logger::debug("Want Passphrase"); my $fh = shift; $fh->send( $pass . "\n" ); exp_continue; } ], [ qr{rsync error: error in rsync protocol}, sub { Rex::Logger::debug("Error in rsync"); die; } ], [ qr{rsync error: remote command not found}, sub { Rex::Logger::info("Remote rsync command not found"); Rex::Logger::info( "Please install rsync, or use Rex::Commands::Sync sync_up/sync_down" ); die; } ], ); } Rex::Logger::debug("cmd: $cmd"); eval { my $exp = Expect->spawn($cmd) or die($!); eval { $exp->expect( Rex::Config->get_timeout, @expect_options, [ qr{total size is [\d,]+\s+speedup is }, sub { Rex::Logger::debug("Finished transfer very fast"); die; } ] ); $exp->expect( undef, [ qr{total size is [\d,]+\s+speedup is }, sub { Rex::Logger::debug("Finished transfer"); exp_continue; } ], [ qr{rsync error: error in rsync protocol}, sub { Rex::Logger::debug("Error in rsync"); die; } ], ); }; $exp->soft_close; $? = $exp->exitstatus; }; if ($@) { Rex::Logger::info($@); } } =back =cut 1;