2015-01-02 13:51:15 +00:00
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012-2013, Timothy Appnel <tim@appnel.com>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
2015-04-13 20:28:01 +00:00
from __future__ import ( absolute_import , division , print_function )
__metaclass__ = type
2015-01-02 13:51:15 +00:00
import os . path
from ansible . plugins . action import ActionBase
2015-07-29 16:10:24 +00:00
from ansible . plugins import connection_loader
2015-01-02 13:51:15 +00:00
from ansible . utils . boolean import boolean
2015-05-06 22:06:43 +00:00
from ansible import constants
2015-01-02 13:51:15 +00:00
class ActionModule ( ActionBase ) :
2015-04-02 16:54:45 +00:00
def _get_absolute_path ( self , path ) :
if self . _task . _role is not None :
original_path = path
2015-08-16 06:36:49 +00:00
if self . _task . _role is not None :
path = self . _loader . path_dwim_relative ( self . _task . _role . _role_path , ' files ' , path )
else :
path = self . _loader . path_dwim_relative ( self . _loader . get_basedir ( ) , ' files ' , path )
2015-04-02 16:54:45 +00:00
if original_path and original_path [ - 1 ] == ' / ' and path [ - 1 ] != ' / ' :
# make sure the dwim'd path ends in a trailing "/"
# if the original path did
path + = ' / '
2015-01-02 13:51:15 +00:00
return path
2015-04-02 16:54:45 +00:00
def _process_origin ( self , host , path , user ) :
2015-01-02 13:51:15 +00:00
2015-08-03 22:04:41 +00:00
if host not in ( ' 127.0.0.1 ' , ' localhost ' , ' ::1 ' ) :
2015-01-02 13:51:15 +00:00
if user :
return ' %s @ %s : %s ' % ( user , host , path )
else :
return ' %s : %s ' % ( host , path )
2015-08-03 22:04:41 +00:00
if ' : ' not in path and not path . startswith ( ' / ' ) :
path = self . _get_absolute_path ( path = path )
return path
2015-01-02 13:51:15 +00:00
2015-05-07 17:20:11 +00:00
def _process_remote ( self , host , path , user ) :
2015-07-21 16:12:22 +00:00
transport = self . _play_context . connection
2015-08-03 22:04:41 +00:00
if host not in ( ' 127.0.0.1 ' , ' localhost ' , ' ::1 ' ) or transport != " local " :
2015-01-02 13:51:15 +00:00
if user :
2015-08-03 22:04:41 +00:00
return ' %s @ %s : %s ' % ( user , host , path )
2015-01-02 13:51:15 +00:00
else :
2015-08-03 22:04:41 +00:00
return ' %s : %s ' % ( host , path )
2015-01-02 13:51:15 +00:00
2015-08-03 22:04:41 +00:00
if ' : ' not in path and not path . startswith ( ' / ' ) :
path = self . _get_absolute_path ( path = path )
return path
2015-01-02 13:51:15 +00:00
2015-08-03 20:29:54 +00:00
def _override_module_replaced_vars ( self , task_vars ) :
""" Some vars are substituted into the modules. Have to make sure
that those are correct for localhost when synchronize creates its own
connection to localhost . """
# Clear the current definition of these variables as they came from the
# connection to the remote host
if ' ansible_syslog_facility ' in task_vars :
del task_vars [ ' ansible_syslog_facility ' ]
for key in task_vars :
if key . startswith ( " ansible_ " ) and key . endswith ( " _interpreter " ) :
del task_vars [ key ]
2015-08-05 20:06:39 +00:00
# Add the definitions from localhost
2015-08-05 20:32:12 +00:00
localhost = task_vars [ ' hostvars ' ] [ ' 127.0.0.1 ' ]
2015-08-03 20:29:54 +00:00
if ' ansible_syslog_facility ' in localhost :
task_vars [ ' ansible_syslog_facility ' ] = localhost [ ' ansible_syslog_facility ' ]
for key in localhost :
if key . startswith ( " ansible_ " ) and key . endswith ( " _interpreter " ) :
task_vars [ key ] = localhost [ key ]
2015-01-02 13:51:15 +00:00
def run ( self , tmp = None , task_vars = dict ( ) ) :
''' generates params and passes them on to the rsync module '''
2015-07-21 16:12:22 +00:00
original_transport = task_vars . get ( ' ansible_connection ' ) or self . _play_context . connection
2015-08-05 20:06:39 +00:00
remote_transport = False
if original_transport != ' local ' :
remote_transport = True
2015-07-31 21:51:26 +00:00
try :
delegate_to = self . _play_context . delegate_to
except ( AttributeError , KeyError ) :
delegate_to = None
2015-01-02 13:51:15 +00:00
2015-05-06 22:06:43 +00:00
use_ssh_args = self . _task . args . pop ( ' use_ssh_args ' , None )
2015-01-02 13:51:15 +00:00
2015-07-29 16:10:24 +00:00
# Parameter name needed by the ansible module
self . _task . args [ ' _local_rsync_path ' ] = task_vars . get ( ' ansible_rsync_path ' ) or ' rsync '
2015-01-02 13:51:15 +00:00
2015-08-05 20:06:39 +00:00
# rsync thinks that one end of the connection is localhost and the
# other is the host we're running the task for (Note: We use
# ansible's delegate_to mechanism to determine which host rsync is
# running on so localhost could be a non-controller machine if
# delegate_to is used)
2015-07-29 16:10:24 +00:00
src_host = ' 127.0.0.1 '
2015-05-07 17:20:11 +00:00
dest_host = task_vars . get ( ' ansible_ssh_host ' ) or task_vars . get ( ' inventory_hostname ' )
2015-01-02 13:51:15 +00:00
2015-08-05 20:06:39 +00:00
dest_is_local = dest_host in ( ' 127.0.0.1 ' , ' localhost ' , ' ::1 ' )
2015-07-29 16:10:24 +00:00
2015-01-02 13:51:15 +00:00
# CHECK FOR NON-DEFAULT SSH PORT
2015-08-05 20:06:39 +00:00
if self . _task . args . get ( ' dest_port ' , None ) is None :
inv_port = task_vars . get ( ' ansible_ssh_port ' , None ) or constants . DEFAULT_REMOTE_PORT
if inv_port is not None :
self . _task . args [ ' dest_port ' ] = inv_port
2015-01-02 13:51:15 +00:00
2015-08-05 20:06:39 +00:00
# Set use_delegate if we are going to run rsync on a delegated host
# instead of localhost
2015-07-29 16:10:24 +00:00
use_delegate = False
2015-07-31 21:51:26 +00:00
if dest_host == delegate_to :
2015-07-29 16:10:24 +00:00
# edge case: explicit delegate and dest_host are the same
2015-07-31 21:51:26 +00:00
# so we run rsync on the remote machine targetting its localhost
# (itself)
2015-01-02 13:51:15 +00:00
dest_host = ' 127.0.0.1 '
2015-07-29 16:10:24 +00:00
use_delegate = True
2015-08-05 20:06:39 +00:00
elif delegate_to is not None and remote_transport :
# If we're delegating to a remote host then we need to use the
# delegate_to settings
use_delegate = True
2015-07-29 16:10:24 +00:00
# Delegate to localhost as the source of the rsync unless we've been
# told (via delegate_to) that a different host is the source of the
# rsync
2015-07-31 21:51:26 +00:00
transport_overridden = False
2015-08-05 20:06:39 +00:00
if not use_delegate and remote_transport :
2015-07-29 16:10:24 +00:00
# Create a connection to localhost to run rsync on
new_stdin = self . _connection . _new_stdin
new_connection = connection_loader . get ( ' local ' , self . _play_context , new_stdin )
self . _connection = new_connection
2015-07-31 21:51:26 +00:00
transport_overridden = True
2015-08-03 20:29:54 +00:00
self . _override_module_replaced_vars ( task_vars )
2015-08-01 01:26:30 +00:00
2015-08-05 20:06:39 +00:00
# COMPARE DELEGATE, HOST AND TRANSPORT
2015-08-05 20:32:12 +00:00
between_multiple_hosts = False
2015-08-05 20:06:39 +00:00
if dest_host != src_host and remote_transport :
# We're not copying two filesystem trees on the same host so we
# need to correctly format the paths for rsync (like
# user@host:path/to/tree
2015-08-05 20:32:12 +00:00
between_multiple_hosts = True
2015-08-05 20:06:39 +00:00
# SWITCH SRC AND DEST HOST PER MODE
if self . _task . args . get ( ' mode ' , ' push ' ) == ' pull ' :
( dest_host , src_host ) = ( src_host , dest_host )
2015-07-29 16:10:24 +00:00
2015-01-02 13:51:15 +00:00
# MUNGE SRC AND DEST PER REMOTE_HOST INFO
2015-08-05 20:06:39 +00:00
src = self . _task . args . get ( ' src ' , None )
2015-07-29 16:10:24 +00:00
dest = self . _task . args . get ( ' dest ' , None )
2015-08-05 20:32:12 +00:00
if between_multiple_hosts or use_delegate :
2015-08-05 20:06:39 +00:00
# Private key handling
2015-01-02 13:51:15 +00:00
if use_delegate :
2015-07-21 16:12:22 +00:00
private_key = task_vars . get ( ' ansible_ssh_private_key_file ' ) or self . _play_context . private_key_file
2015-01-02 13:51:15 +00:00
else :
2015-07-21 16:12:22 +00:00
private_key = task_vars . get ( ' ansible_ssh_private_key_file ' ) or self . _play_context . private_key_file
2015-01-02 13:51:15 +00:00
2015-01-08 16:51:54 +00:00
if private_key is not None :
2015-01-02 13:51:15 +00:00
private_key = os . path . expanduser ( private_key )
2015-05-07 17:20:11 +00:00
self . _task . args [ ' private_key ' ] = private_key
2015-08-05 20:06:39 +00:00
# Src and dest rsync "path" handling
# Determine if we need a user@
user = None
if boolean ( self . _task . args . get ( ' set_remote_user ' , ' yes ' ) ) :
if use_delegate :
if ' hostvars ' in task_vars and delegate_to in task_vars [ ' hostvars ' ] :
user = task_vars [ ' hostvars ' ] [ delegate_to ] . get ( ' ansible_ssh_user ' , None )
if not use_delegate or not user :
user = task_vars . get ( ' ansible_ssh_user ' ) or self . _play_context . remote_user
2015-01-02 13:51:15 +00:00
# use the mode to define src and dest's url
if self . _task . args . get ( ' mode ' , ' push ' ) == ' pull ' :
# src is a remote path: <user>@<host>, dest is a local path
2015-08-05 20:06:39 +00:00
src = self . _process_remote ( src_host , src , user )
2015-04-02 16:54:45 +00:00
dest = self . _process_origin ( dest_host , dest , user )
2015-01-02 13:51:15 +00:00
else :
# src is a local path, dest is a remote path: <user>@<host>
2015-08-05 20:06:39 +00:00
src = self . _process_origin ( src_host , src , user )
2015-04-02 16:54:45 +00:00
dest = self . _process_remote ( dest_host , dest , user )
2015-08-05 20:32:12 +00:00
else :
# Still need to munge paths (to account for roles) even if we aren't
# copying files between hosts
if not src . startswith ( ' / ' ) :
src = self . _get_absolute_path ( path = src )
if not dest . startswith ( ' / ' ) :
dest = self . _get_absolute_path ( path = dest )
2015-01-02 13:51:15 +00:00
2015-07-29 16:10:24 +00:00
self . _task . args [ ' src ' ] = src
self . _task . args [ ' dest ' ] = dest
2015-08-05 20:06:39 +00:00
# Allow custom rsync path argument
2015-01-02 13:51:15 +00:00
rsync_path = self . _task . args . get ( ' rsync_path ' , None )
2015-08-05 20:06:39 +00:00
# If no rsync_path is set, sudo was originally set, and dest is remote then add 'sudo rsync' argument
2015-07-21 16:12:22 +00:00
if not rsync_path and transport_overridden and self . _play_context . become and self . _play_context . become_method == ' sudo ' and not dest_is_local :
2015-04-02 16:54:45 +00:00
rsync_path = ' sudo rsync '
2015-01-02 13:51:15 +00:00
# make sure rsync path is quoted.
if rsync_path :
2015-04-02 16:54:45 +00:00
self . _task . args [ ' rsync_path ' ] = ' " %s " ' % rsync_path
2015-01-02 13:51:15 +00:00
2015-05-06 22:06:43 +00:00
if use_ssh_args :
self . _task . args [ ' ssh_args ' ] = constants . ANSIBLE_SSH_ARGS
2015-01-02 13:51:15 +00:00
# run the module and store the result
2015-06-04 19:43:07 +00:00
result = self . _execute_module ( ' synchronize ' , task_vars = task_vars )
2015-01-02 13:51:15 +00:00
2015-08-03 21:43:27 +00:00
if ' SyntaxError ' in result [ ' msg ' ] :
# Emit a warning about using python3 because synchronize is
# somewhat unique in running on localhost
result [ ' traceback ' ] = result [ ' msg ' ]
result [ ' msg ' ] = ' SyntaxError parsing module. Perhaps invoking " python " on your local (or delegate_to) machine invokes python3. You can set ansible_python_interpreter for localhost (or the delegate_to machine) to the location of python2 to fix this '
2015-01-02 13:51:15 +00:00
return result