@ -20,13 +20,13 @@ __metaclass__ = type
import os
import os
import base64
import base64
from ansible . errors import Ansible Error
from ansible . errors import Ansible ActionFail, AnsibleActionSkip
from ansible . module_utils . _text import to_bytes
from ansible . module_utils . _text import to_bytes
from ansible . module_utils . six import string_types
from ansible . module_utils . six import string_types
from ansible . module_utils . parsing . convert_bool import boolean
from ansible . module_utils . parsing . convert_bool import boolean
from ansible . plugins . action import ActionBase
from ansible . plugins . action import ActionBase
from ansible . utils . hashing import checksum , checksum_s , md5 , secure_hash
from ansible . utils . hashing import checksum , checksum_s , md5 , secure_hash
from ansible . utils . path import makedirs_safe
from ansible . utils . path import makedirs_safe , is_subpath
try :
try :
from __main__ import display
from __main__ import display
@ -47,24 +47,23 @@ class ActionModule(ActionBase):
try :
try :
if self . _play_context . check_mode :
if self . _play_context . check_mode :
result [ ' skipped ' ] = True
raise AnsibleActionSkip ( ' check mode not (yet) supported for this module ' )
result [ ' msg ' ] = ' check mode not (yet) supported for this module '
return result
source = self . _task . args . get ( ' src ' , None )
source = self . _task . args . get ( ' src ' , None )
dest = self . _task . args . get ( ' dest ' , None )
original_dest = dest = self . _task . args . get ( ' dest ' , None )
flat = boolean ( self . _task . args . get ( ' flat ' ) , strict = False )
flat = boolean ( self . _task . args . get ( ' flat ' ) , strict = False )
fail_on_missing = boolean ( self . _task . args . get ( ' fail_on_missing ' , True ) , strict = False )
fail_on_missing = boolean ( self . _task . args . get ( ' fail_on_missing ' , True ) , strict = False )
validate_checksum = boolean ( self . _task . args . get ( ' validate_checksum ' ,
validate_checksum = boolean ( self . _task . args . get ( ' validate_checksum ' ,
self . _task . args . get ( ' validate_md5 ' , True ) ) ,
self . _task . args . get ( ' validate_md5 ' , True ) ) ,
strict = False )
strict = False )
msg = ' '
# validate source and dest are strings FIXME: use basic.py and module specs
# validate source and dest are strings FIXME: use basic.py and module specs
if not isinstance ( source , string_types ) :
if not isinstance ( source , string_types ) :
result[ ' msg' ] = " Invalid type supplied for source option, it must be a string "
msg = " Invalid type supplied for source option, it must be a string "
if not isinstance ( dest , string_types ) :
if not isinstance ( dest , string_types ) :
result[ ' msg' ] = " Invalid type supplied for dest option, it must be a string "
msg = " Invalid type supplied for dest option, it must be a string "
# validate_md5 is the deprecated way to specify validate_checksum
# validate_md5 is the deprecated way to specify validate_checksum
if ' validate_md5 ' in self . _task . args and ' validate_checksum ' in self . _task . args :
if ' validate_md5 ' in self . _task . args and ' validate_checksum ' in self . _task . args :
@ -74,11 +73,10 @@ class ActionModule(ActionBase):
display . deprecated ( ' Use validate_checksum instead of validate_md5 ' , version = ' 2.8 ' )
display . deprecated ( ' Use validate_checksum instead of validate_md5 ' , version = ' 2.8 ' )
if source is None or dest is None :
if source is None or dest is None :
result[ ' msg' ] = " src and dest are required "
msg = " src and dest are required "
if result . get ( ' msg ' ) :
if msg :
result [ ' failed ' ] = True
raise AnsibleActionFail ( msg )
return result
source = self . _connection . _shell . join_path ( source )
source = self . _connection . _shell . join_path ( source )
source = self . _remote_expand_user ( source )
source = self . _remote_expand_user ( source )
@ -106,12 +104,6 @@ class ActionModule(ActionBase):
remote_data = base64 . b64decode ( slurpres [ ' content ' ] )
remote_data = base64 . b64decode ( slurpres [ ' content ' ] )
if remote_data is not None :
if remote_data is not None :
remote_checksum = checksum_s ( remote_data )
remote_checksum = checksum_s ( remote_data )
# the source path may have been expanded on the
# target system, so we compare it here and use the
# expanded version if it's different
remote_source = slurpres . get ( ' source ' )
if remote_source and remote_source != source :
source = remote_source
# calculate the destination name
# calculate the destination name
if os . path . sep not in self . _connection . _shell . join_path ( ' a ' , ' ' ) :
if os . path . sep not in self . _connection . _shell . join_path ( ' a ' , ' ' ) :
@ -120,13 +112,14 @@ class ActionModule(ActionBase):
else :
else :
source_local = source
source_local = source
dest = os . path . expanduser ( dest )
# ensure we only use file name, avoid relative paths
if not is_subpath ( dest , original_dest ) :
# TODO: ? dest = os.path.expanduser(dest.replace(('../','')))
raise AnsibleActionFail ( " Detected directory traversal, expected to be contained in ' %s ' but got ' %s ' " % ( original_dest , dest ) )
if flat :
if flat :
if os . path . isdir ( to_bytes ( dest , errors = ' surrogate_or_strict ' ) ) and not dest . endswith ( os . sep ) :
if os . path . isdir ( to_bytes ( dest , errors = ' surrogate_or_strict ' ) ) and not dest . endswith ( os . sep ) :
result [ ' msg ' ] = " dest is an existing directory, use a trailing slash if you want to fetch src into that directory "
raise AnsibleActionFail ( " dest is an existing directory, use a trailing slash if you want to fetch src into that directory " )
result [ ' file ' ] = dest
result [ ' failed ' ] = True
return result
if dest . endswith ( os . sep ) :
if dest . endswith ( os . sep ) :
# if the path ends with "/", we'll use the source filename as the
# if the path ends with "/", we'll use the source filename as the
# destination filename
# destination filename
@ -143,8 +136,6 @@ class ActionModule(ActionBase):
target_name = self . _play_context . remote_addr
target_name = self . _play_context . remote_addr
dest = " %s / %s / %s " % ( self . _loader . path_dwim ( dest ) , target_name , source_local )
dest = " %s / %s / %s " % ( self . _loader . path_dwim ( dest ) , target_name , source_local )
dest = dest . replace ( " // " , " / " )
if remote_checksum in ( ' 0 ' , ' 1 ' , ' 2 ' , ' 3 ' , ' 4 ' , ' 5 ' ) :
if remote_checksum in ( ' 0 ' , ' 1 ' , ' 2 ' , ' 3 ' , ' 4 ' , ' 5 ' ) :
result [ ' changed ' ] = False
result [ ' changed ' ] = False
result [ ' file ' ] = source
result [ ' file ' ] = source
@ -172,6 +163,8 @@ class ActionModule(ActionBase):
result [ ' msg ' ] + = " , not transferring, ignored "
result [ ' msg ' ] + = " , not transferring, ignored "
return result
return result
dest = os . path . normpath ( dest )
# calculate checksum for the local file
# calculate checksum for the local file
local_checksum = checksum ( dest )
local_checksum = checksum ( dest )
@ -188,7 +181,7 @@ class ActionModule(ActionBase):
f . write ( remote_data )
f . write ( remote_data )
f . close ( )
f . close ( )
except ( IOError , OSError ) as e :
except ( IOError , OSError ) as e :
raise Ansible Error ( " Failed to fetch the file: %s " % e )
raise Ansible ActionFail ( " Failed to fetch the file: %s " % e )
new_checksum = secure_hash ( dest )
new_checksum = secure_hash ( dest )
# For backwards compatibility. We'll return None on FIPS enabled systems
# For backwards compatibility. We'll return None on FIPS enabled systems
try :
try :