@ -18,12 +18,6 @@
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
import errno
import shutil
import stat
import grp
import pwd
DOCUMENTATION = '''
- - -
module : file
@ -37,7 +31,7 @@ description:
notes :
- See also M ( copy ) , M ( template ) , M ( assemble )
requirements : [ ]
author :
author :
- " Ansible Core Team "
- " Michael DeHaan "
options :
@ -112,16 +106,27 @@ EXAMPLES = '''
'''
import errno
import os
import shutil
import time
# import module snippets
from ansible . module_utils . basic import AnsibleModule
from ansible . module_utils . pycompat24 import get_exception
from ansible . module_utils . six import b
from ansible . module_utils . _text import to_bytes , to_native
def get_state ( path ) :
def get_state ( b_path ) :
''' Find out current state '''
if os . path . lexists ( path ) :
if os . path . islink ( path ) :
if os . path . lexists ( b_ path) :
if os . path . islink ( b_ path) :
return ' link '
elif os . path . isdir ( path ) :
elif os . path . isdir ( b_ path) :
return ' directory '
elif os . stat ( path ) . st_nlink > 1 :
elif os . stat ( b_ path) . st_nlink > 1 :
return ' hard '
else :
# could be many other things, but defaulting to file
@ -129,70 +134,73 @@ def get_state(path):
return ' absent '
def recursive_set_attributes ( module , path , follow , file_args ) :
def recursive_set_attributes ( module , b_path , follow , file_args ) :
changed = False
for root, dirs, files in os . walk ( path) :
for fsobj in dirs + files:
fsname = os . path . join ( root, fsobj)
if not os . path . islink ( fsname) :
for b_ root, b_ dirs, b_ files in os . walk ( b_ path) :
for b_ fsobj in b_ dirs + b_ files:
b_ fsname = os . path . join ( b_ root, b_ fsobj)
if not os . path . islink ( b_ fsname) :
tmp_file_args = file_args . copy ( )
tmp_file_args [ ' path ' ] = fsname
tmp_file_args [ ' path ' ] = to_native( b_ fsname, errors = ' surrogate_or_strict ' )
changed | = module . set_fs_attributes_if_different ( tmp_file_args , changed )
else :
tmp_file_args = file_args . copy ( )
tmp_file_args [ ' path ' ] = fsname
tmp_file_args [ ' path ' ] = to_native( b_ fsname, errors = ' surrogate_or_strict ' )
changed | = module . set_fs_attributes_if_different ( tmp_file_args , changed )
if follow :
fsname = os . path . join ( root, os . readlink ( fsname) )
if os . path . isdir ( fsname) :
changed | = recursive_set_attributes ( module , fsname, follow , file_args )
b_ fsname = os . path . join ( b_ root, os . readlink ( b_ fsname) )
if os . path . isdir ( b_ fsname) :
changed | = recursive_set_attributes ( module , b_ fsname, follow , file_args )
tmp_file_args = file_args . copy ( )
tmp_file_args [ ' path ' ] = fsname
tmp_file_args [ ' path ' ] = to_native( b_ fsname, errors = ' surrogate_or_strict ' )
changed | = module . set_fs_attributes_if_different ( tmp_file_args , changed )
return changed
def main ( ) :
module = AnsibleModule (
argument_spec = dict (
state = dict ( choices = [ ' file ' , ' directory ' , ' link ' , ' hard ' , ' touch ' , ' absent ' ] , default = None ) ,
path = dict ( aliases = [ ' dest ' , ' name ' ] , required = True ) ,
original_basename = dict ( required = False ) , # Internal use only, for recursive ops
recurse = dict ( default = False , type = ' bool ' ) ,
force = dict ( required = False , default = False , type = ' bool ' ) ,
diff_peek = dict ( default = None ) , # Internal use only, for internal checks in the action plugins
validate = dict ( required = False , default = None ) , # Internal use only, for template and copy
src = dict ( required = False , default = None ) ,
argument_spec = dict (
state = dict ( choices = [ ' file ' , ' directory ' , ' link ' , ' hard ' , ' touch ' , ' absent ' ] , default = None ) ,
path = dict ( aliases = [ ' dest ' , ' name ' ] , required = True , type = ' path ' ) ,
original_basename = dict ( required = False ) , # Internal use only, for recursive ops
recurse = dict ( default = False , type = ' bool ' ) ,
force = dict ( required = False , default = False , type = ' bool ' ) ,
diff_peek = dict ( default = None ) , # Internal use only, for internal checks in the action plugins
validate = dict ( required = False , default = None ) , # Internal use only, for template and copy
src = dict ( required = False , default = None , type = ' path ' ) ,
) ,
add_file_common_args = True ,
supports_check_mode = True
)
params = module . params
state = params [ ' state ' ]
state = params [ ' state ' ]
force = params [ ' force ' ]
diff_peek = params [ ' diff_peek ' ]
src = params [ ' src ' ]
b_src = to_bytes ( src , errors = ' surrogate_or_strict ' )
follow = params [ ' follow ' ]
# modify source as we later reload and pass, specially relevant when used by other modules.
params [ ' path ' ] = path = os . path . expanduser ( params [ ' path ' ] )
path = params [ ' path ' ]
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
# short-circuit for diff_peek
if diff_peek is not None :
appears_binary = False
try :
f = open ( path)
b = f . read ( 8192 )
f = open ( b_ path, ' rb ' )
head = f . read ( 8192 )
f . close ( )
if " \x00 " in b :
if b ( " \x00 " ) in head :
appears_binary = True
except :
pass
module . exit_json ( path = path , changed = False , appears_binary = appears_binary )
prev_state = get_state ( path )
prev_state = get_state ( b_path )
# state should default to file, but since that creates many conflicts,
# default to 'current' when it exists.
@ -204,18 +212,16 @@ def main():
# source is both the source of a symlink or an informational passing of the src for a template module
# or copy module, even if this module never uses it, it is needed to key off some things
if src is not None :
src = os . path . expanduser ( src )
else :
if state in [ ' link ' , ' hard ' ] :
if src is None :
if state in ( ' link ' , ' hard ' ) :
if follow and state == ' link ' :
# use the current target of the link as the source
src = os. path . realpath ( path)
src = to_native( os. path . realpath ( b_ path) , errors = ' strict ' )
else :
module . fail_json ( msg = ' src and dest are required for creating links ' )
# original_basename is used by other modules that depend on file.
if os . path . isdir ( path) and state not in [ " link " , " absent " ] :
if os . path . isdir ( b_ path) and state not in ( " link " , " absent " ) :
basename = None
if params [ ' original_basename ' ] :
basename = params [ ' original_basename ' ]
@ -223,6 +229,7 @@ def main():
basename = os . path . basename ( src )
if basename :
params [ ' path ' ] = path = os . path . join ( path , basename )
b_path = to_bytes ( path , errors = ' surrogate_or_strict ' )
# make sure the target path is a directory when we're doing a recursive operation
recurse = params [ ' recurse ' ]
@ -232,11 +239,8 @@ def main():
file_args = module . load_file_common_arguments ( params )
changed = False
diff = { ' before ' :
{ ' path ' : path }
,
' after ' :
{ ' path ' : path }
diff = { ' before ' : { ' path ' : path } ,
' after ' : { ' path ' : path } ,
}
state_change = False
@ -250,13 +254,13 @@ def main():
if not module . check_mode :
if prev_state == ' directory ' :
try :
shutil . rmtree ( path, ignore_errors = False )
shutil . rmtree ( b_ path, ignore_errors = False )
except Exception :
e = get_exception ( )
module . fail_json ( msg = " rmtree failed: %s " % str ( e ) )
else :
try :
os . unlink ( path)
os . unlink ( b_ path)
except Exception :
e = get_exception ( )
module . fail_json ( path = path , msg = " unlinking failed: %s " % str ( e ) )
@ -269,11 +273,12 @@ def main():
if state_change :
if follow and prev_state == ' link ' :
# follow symlink and operate on original
path = os . path . realpath ( path )
prev_state = get_state ( path )
b_path = os . path . realpath ( b_path )
path = to_native ( b_path , errors = ' strict ' )
prev_state = get_state ( b_path )
file_args [ ' path ' ] = path
if prev_state not in [' file ' , ' hard ' ] :
if prev_state not in (' file ' , ' hard ' ) :
# file is not absent and any other state is a conflict
module . fail_json ( path = path , msg = ' file ( %s ) is %s , cannot continue ' % ( path , prev_state ) )
@ -282,8 +287,9 @@ def main():
elif state == ' directory ' :
if follow and prev_state == ' link ' :
path = os . path . realpath ( path )
prev_state = get_state ( path )
b_path = os . path . realpath ( b_path )
path = to_native ( b_path , errors = ' strict ' )
prev_state = get_state ( b_path )
if prev_state == ' absent ' :
if module . check_mode :
@ -301,17 +307,18 @@ def main():
# Remove leading slash if we're creating a relative path
if not os . path . isabs ( path ) :
curpath = curpath . lstrip ( ' / ' )
if not os . path . exists ( curpath ) :
b_curpath = to_bytes ( curpath , errors = ' surrogate_or_strict ' )
if not os . path . exists ( b_curpath ) :
try :
os . mkdir ( curpath)
os . mkdir ( b_ curpath)
except OSError :
ex = get_exception ( )
# Possibly something else created the dir since the os.path.exists
# check above. As long as it's a dir, we don't need to error out.
if not ( ex . errno == errno . EEXIST and os . path . isdir ( curpath) ) :
if not ( ex . errno == errno . EEXIST and os . path . isdir ( b_ curpath) ) :
raise
tmp_file_args = file_args . copy ( )
tmp_file_args [ ' path ' ] = curpath
tmp_file_args [ ' path ' ] = curpath
changed = module . set_fs_attributes_if_different ( tmp_file_args , changed , diff )
except Exception :
e = get_exception ( )
@ -324,45 +331,47 @@ def main():
changed = module . set_fs_attributes_if_different ( file_args , changed , diff )
if recurse :
changed | = recursive_set_attributes ( module , file_args[ ' path ' ] , follow , file_args )
changed | = recursive_set_attributes ( module , to_bytes( file_args[ ' path ' ] , errors = ' surrogate_or_strict ' ) , follow , file_args )
module . exit_json ( path = path , changed = changed , diff = diff )
elif state in [' link ' , ' hard ' ] :
elif state in (' link ' , ' hard ' ) :
if os . path . isdir ( path) and not os . path . islink ( path) :
if os . path . isdir ( b_ path) and not os . path . islink ( b_ path) :
relpath = path
else :
relpath = os . path . dirname ( path )
b_relpath = os . path . dirname ( b_path )
relpath = to_native ( b_relpath , errors = ' strict ' )
absrc = os . path . join ( relpath , src )
if not os . path . exists ( absrc ) and not force :
b_absrc = to_bytes ( absrc , errors = ' surrogate_or_strict ' )
if not os . path . exists ( b_absrc ) and not force :
module . fail_json ( path = path , src = src , msg = ' src file does not exist, use " force=yes " if you really want to create the link: %s ' % absrc )
if state == ' hard ' :
if not os . path . isabs ( src) :
if not os . path . isabs ( b_ src) :
module . fail_json ( msg = " absolute paths are required " )
elif prev_state == ' directory ' :
if not force :
module . fail_json ( path = path , msg = ' refusing to convert between %s and %s for %s ' % ( prev_state , state , path ) )
elif len ( os . listdir ( path) ) > 0 :
elif len ( os . listdir ( b_ path) ) > 0 :
# refuse to replace a directory that has files in it
module . fail_json ( path = path , msg = ' the directory %s is not empty, refusing to convert it ' % path )
elif prev_state in [ ' file ' , ' hard ' ] and not force :
elif prev_state in ( ' file ' , ' hard ' ) and not force :
module . fail_json ( path = path , msg = ' refusing to convert between %s and %s for %s ' % ( prev_state , state , path ) )
if prev_state == ' absent ' :
changed = True
elif prev_state == ' link ' :
old_src = os . readlink ( path)
if old_src != src:
b_ old_src = os . readlink ( b_ path)
if b_ old_src != b_ src:
changed = True
elif prev_state == ' hard ' :
if not ( state == ' hard ' and os . stat ( path) . st_ino == os . stat ( src) . st_ino ) :
if not ( state == ' hard ' and os . stat ( b_ path) . st_ino == os . stat ( b_ src) . st_ino ) :
changed = True
if not force :
module . fail_json ( dest = path , src = src , msg = ' Cannot link, different hard link exists at destination ' )
elif prev_state in [ ' file ' , ' directory ' ] :
elif prev_state in ( ' file ' , ' directory ' ) :
changed = True
if not force :
module . fail_json ( dest = path , src = src , msg = ' Cannot link, %s exists at destination ' % prev_state )
@ -372,31 +381,33 @@ def main():
if changed and not module . check_mode :
if prev_state != ' absent ' :
# try to replace atomically
tmppath = ' / ' . join ( [ os . path . dirname ( path ) , " . %s . %s .tmp " % ( os . getpid ( ) , time . time ( ) ) ] )
b_tmppath = to_bytes ( os . path . sep ) . join (
[ os . path . dirname ( b_path ) , to_bytes ( " . %s . %s .tmp " % ( os . getpid ( ) , time . time ( ) ) ) ]
)
try :
if prev_state == ' directory ' and ( state == ' hard ' or state == ' link ' ) :
os . rmdir ( path)
os . rmdir ( b_ path)
if state == ' hard ' :
os . link ( src, tmppath)
os . link ( b_ src, b_ tmppath)
else :
os . symlink ( src, tmppath)
os . rename ( tmppath, path)
os . symlink ( b_ src, b_ tmppath)
os . rename ( b_ tmppath, b_ path)
except OSError :
e = get_exception ( )
if os . path . exists ( tmppath) :
os . unlink ( tmppath)
module . fail_json ( path = path , msg = ' Error while replacing: %s ' % str ( e ) )
if os . path . exists ( b_ tmppath) :
os . unlink ( b_ tmppath)
module . fail_json ( path = path , msg = ' Error while replacing: %s ' % to_native ( e , nonstring = ' simplerepr ' ) )
else :
try :
if state == ' hard ' :
os . link ( src, path)
os . link ( b_ src, b_ path)
else :
os . symlink ( src, path)
os . symlink ( b_ src, b_ path)
except OSError :
e = get_exception ( )
module . fail_json ( path = path , msg = ' Error while linking: %s ' % str ( e ) )
module . fail_json ( path = path , msg = ' Error while linking: %s ' % to_native ( e , nonstring = ' simplerepr ' ) )
if module . check_mode and not os . path . exists ( path) :
if module . check_mode and not os . path . exists ( b_ path) :
module . exit_json ( dest = path , src = src , changed = changed , diff = diff )
changed = module . set_fs_attributes_if_different ( file_args , changed , diff )
@ -407,16 +418,16 @@ def main():
if prev_state == ' absent ' :
try :
open ( path, ' w ' ) . close ( )
open ( b_ path, ' w b ' ) . close ( )
except OSError :
e = get_exception ( )
module . fail_json ( path = path , msg = ' Error, could not touch target: %s ' % str ( e ) )
elif prev_state in [ ' file ' , ' directory ' , ' hard ' ] :
module . fail_json ( path = path , msg = ' Error, could not touch target: %s ' % to_native ( e , nonstring = ' simplerepr ' ) )
elif prev_state in ( ' file ' , ' directory ' , ' hard ' ) :
try :
os . utime ( path, None )
os . utime ( b_ path, None )
except OSError :
e = get_exception ( )
module . fail_json ( path = path , msg = ' Error while touching existing target: %s ' % str ( e ) )
module . fail_json ( path = path , msg = ' Error while touching existing target: %s ' % to_native ( e , nonstring = ' simplerepr ' ) )
else :
module . fail_json ( msg = ' Cannot touch other than files, directories, and hardlinks ( %s is %s ) ' % ( path , prev_state ) )
try :
@ -428,15 +439,12 @@ def main():
# somewhere in basic.py
if prev_state == ' absent ' :
# If we just created the file we can safely remove it
os . remove ( path)
os . remove ( b_ path)
raise e
module . exit_json ( dest = path , changed = True , diff = diff )
module . fail_json ( path = path , msg = ' unexpected position reached ' )
# import module snippets
from ansible . module_utils . basic import *
if __name__ == ' __main__ ' :
main ( )