Fixes and cleanup to file functions and module

- unified set attribute functions ... not sure why 2 identical functions
exist with diff names, now there are 3 while i repoint all modules to 1
- fixed issue with symlinks being created w/o existing src when force=no
- refactored conditionals, simplified where possible
- added tests for symlink to nonexistant source, with both force options
- made symlink on existing attomic (force)
reviewable/pr18780/r1
Brian Coca 11 years ago
parent 4f1ccfd66f
commit 7ca6343ca3

@ -139,9 +139,6 @@ EXAMPLES = '''
def main(): def main():
# FIXME: pass this around, should not use global
global module
module = AnsibleModule( module = AnsibleModule(
argument_spec = dict( argument_spec = dict(
state = dict(choices=['file','directory','link','hard','touch','absent'], default=None), state = dict(choices=['file','directory','link','hard','touch','absent'], default=None),
@ -151,6 +148,7 @@ def main():
force = dict(required=False,default=False,type='bool'), force = dict(required=False,default=False,type='bool'),
diff_peek = dict(default=None), diff_peek = dict(default=None),
validate = dict(required=False, default=None), validate = dict(required=False, default=None),
src = dict(required=False, default=None),
), ),
add_file_common_args=True, add_file_common_args=True,
supports_check_mode=True supports_check_mode=True
@ -159,10 +157,14 @@ def main():
params = module.params params = module.params
state = params['state'] state = params['state']
force = params['force'] force = params['force']
diff_peek = params['diff_peek']
src = params['src']
# modify source as we later reload and pass, specially relevant when used by other modules.
params['path'] = path = os.path.expanduser(params['path']) params['path'] = path = os.path.expanduser(params['path'])
# short-circuit for diff_peek # short-circuit for diff_peek
if params.get('diff_peek', None) is not None: if diff_peek is not None:
appears_binary = False appears_binary = False
try: try:
f = open(path) f = open(path)
@ -174,8 +176,8 @@ def main():
pass pass
module.exit_json(path=path, changed=False, appears_binary=appears_binary) module.exit_json(path=path, changed=False, appears_binary=appears_binary)
# Find out current state
prev_state = 'absent' prev_state = 'absent'
if os.path.lexists(path): if os.path.lexists(path):
if os.path.islink(path): if os.path.islink(path):
prev_state = 'link' prev_state = 'link'
@ -187,76 +189,60 @@ def main():
# could be many other things, but defaulting to file # could be many other things, but defaulting to file
prev_state = 'file' prev_state = 'file'
if prev_state is not None and state is None: # state should default to file, but since that creates many conflicts,
# set state to current type of file # default to 'current' when it exists.
if state is None:
if prev_state != 'absent':
state = prev_state state = prev_state
elif state is None: else:
# set default state to file
state = 'file' state = 'file'
# source is both the source of a symlink or an informational passing of the src for a template module # 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 # or copy module, even if this module never uses it, it is needed to key off some things
if src is not None:
src = params.get('src', None)
if src:
src = os.path.expanduser(src) src = os.path.expanduser(src)
if src is not None and os.path.isdir(path) and state not in ["link", "absent"]: # original_basename is used by other modules that depend on file.
if os.path.isdir(path) and state not in ["link", "absent", "directory"]:
if params['original_basename']: if params['original_basename']:
basename = params['original_basename'] basename = params['original_basename']
else: else:
basename = os.path.basename(src) basename = os.path.basename(src)
params['path'] = path = os.path.join(path, basename) params['path'] = path = os.path.join(path, basename)
else:
file_args = module.load_file_common_arguments(params) if state in ['link','hard']:
if state in ['link','hard'] and (src is None or path is None):
module.fail_json(msg='src and dest are required for creating links') module.fail_json(msg='src and dest are required for creating links')
elif path is None:
module.fail_json(msg='path is required')
file_args = module.load_file_common_arguments(params)
changed = False changed = False
recurse = params['recurse'] recurse = params['recurse']
if recurse and state != 'directory':
module.fail_json(path=path, msg="recurse option requires state to be 'directory'")
if recurse and state == 'file' and prev_state == 'directory': if state == 'absent':
state = 'directory' if state != prev_state:
if not module.check_mode:
if prev_state != 'absent' and state == 'absent':
try:
if prev_state == 'directory': if prev_state == 'directory':
if os.path.islink(path):
if module.check_mode:
module.exit_json(changed=True)
os.unlink(path)
else:
try: try:
if module.check_mode:
module.exit_json(changed=True)
shutil.rmtree(path, ignore_errors=False) shutil.rmtree(path, ignore_errors=False)
except Exception, e: except Exception, e:
module.fail_json(msg="rmtree failed: %s" % str(e)) module.fail_json(msg="rmtree failed: %s" % str(e))
else: else:
if module.check_mode: try:
module.exit_json(changed=True)
os.unlink(path) os.unlink(path)
except Exception, e: except Exception, e:
module.fail_json(path=path, msg=str(e)) module.fail_json(path=path, msg="unlinking failed: %s " % str(e))
module.exit_json(path=path, changed=True) module.exit_json(path=path, changed=True)
else:
if prev_state != 'absent' and prev_state != state:
if not (force and (prev_state == 'file' or prev_state == 'hard' or prev_state == 'directory') and state == 'link') and state != 'touch':
module.fail_json(path=path, msg='refusing to convert between %s and %s for %s' % (prev_state, state, src))
if prev_state == 'absent' and state == 'absent':
module.exit_json(path=path, changed=False) module.exit_json(path=path, changed=False)
if state == 'file': elif state == 'file':
if state != prev_state:
# 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))
if prev_state != 'file': changed = module.set_fs_attributes_if_different(file_args, changed)
module.fail_json(path=path, msg='file (%s) does not exist, use copy or template module to create' % path)
changed = module.set_file_attributes_if_different(file_args, changed)
module.exit_json(path=path, changed=changed) module.exit_json(path=path, changed=changed)
elif state == 'directory': elif state == 'directory':
@ -266,31 +252,29 @@ def main():
os.makedirs(path) os.makedirs(path)
changed = True changed = True
changed = module.set_directory_attributes_if_different(file_args, changed) changed = module.set_fs_attributes_if_different(file_args, changed)
if recurse: if recurse:
for root,dirs,files in os.walk( file_args['path'] ): for root,dirs,files in os.walk( file_args['path'] ):
for dir in dirs: for fsobj in dirs + files:
dirname=os.path.join(root,dir) fsname=os.path.join(root, fsobj)
tmp_file_args = file_args.copy()
tmp_file_args['path']=dirname
changed = module.set_directory_attributes_if_different(tmp_file_args, changed)
for file in files:
filename=os.path.join(root,file)
tmp_file_args = file_args.copy() tmp_file_args = file_args.copy()
tmp_file_args['path']=filename tmp_file_args['path']=fsname
changed = module.set_file_attributes_if_different(tmp_file_args, changed) changed = module.set_fs_attributes_if_different(tmp_file_args, changed)
module.exit_json(path=path, changed=changed) module.exit_json(path=path, changed=changed)
elif state in ['link','hard']: elif state in ['link','hard']:
if not os.path.exists(src) and not force:
module.fail_json(path=path, src=src, msg='src file does not exist')
if state == 'hard': if state == 'hard':
if os.path.isabs(src): if not os.path.isabs(src):
abs_src = src
else:
module.fail_json(msg="absolute paths are required") module.fail_json(msg="absolute paths are required")
if not os.path.exists(abs_src) and not force: elif prev_state in ['file', 'hard', 'directory'] and not force:
module.fail_json(path=path, src=src, msg='src file does not exist') module.fail_json(path=path, msg='refusing to convert between %s and %s for %s' % (prev_state, state, src))
if prev_state == 'absent': if prev_state == 'absent':
changed = True changed = True
@ -300,26 +284,29 @@ def main():
changed = True changed = True
elif prev_state == 'hard': 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(path).st_ino == os.stat(src).st_ino):
if not force:
module.fail_json(dest=path, src=src, msg='Cannot link, different hard link exists at destination')
changed = True changed = True
elif prev_state == 'file':
if not force: if not force:
module.fail_json(dest=path, src=src, msg='Cannot link, file exists at destination') module.fail_json(dest=path, src=src, msg='Cannot link, different hard link exists at destination')
elif prev_state in ['file', 'directory']:
changed = True changed = True
elif prev_state == 'directory':
if not force: if not force:
module.fail_json(dest=path, src=src, msg='Cannot link, directory exists at destination') module.fail_json(dest=path, src=src, msg='Cannot link, %s exists at destination' % prev_state)
changed = True
else: else:
module.fail_json(dest=path, src=src, msg='unexpected position reached') module.fail_json(dest=path, src=src, msg='unexpected position reached')
if changed and not module.check_mode: if changed and not module.check_mode:
if prev_state != 'absent': if prev_state != 'absent':
# try to replace atomically
tmppath = ".%s.%s.%s.tmp" % (path,os.getpid(),time.time())
try: try:
os.unlink(path) if state == 'hard':
os.link(src,tmppath)
else:
os.symlink(src, tmppath)
os.rename(tmppath, path)
except OSError, e: except OSError, e:
module.fail_json(path=path, msg='Error while removing existing target: %s' % str(e)) os.unlink(tmppath)
module.fail_json(path=path, msg='Error while replacing: %s' % str(e))
try: try:
if state == 'hard': if state == 'hard':
os.link(src,path) os.link(src,path)
@ -328,29 +315,29 @@ def main():
except OSError, e: except OSError, e:
module.fail_json(path=path, msg='Error while linking: %s' % str(e)) module.fail_json(path=path, msg='Error while linking: %s' % str(e))
changed = module.set_file_attributes_if_different(file_args, changed) changed = module.set_fs_attributes_if_different(file_args, changed)
module.exit_json(dest=path, src=src, changed=changed) module.exit_json(dest=path, src=src, changed=changed)
elif state == 'touch': elif state == 'touch':
if module.check_mode: if not module.check_mode:
module.exit_json(path=path, skipped=True)
if prev_state not in ['file', 'directory', 'absent']: if prev_state == 'absent':
module.fail_json(msg='Cannot touch other than files and directories') try:
if prev_state != 'absent': open(path, 'w').close()
except OSError, e:
module.fail_json(path=path, msg='Error, could not touch target: %s' % str(e))
elif prev_state in ['file', 'directory']:
try: try:
os.utime(path, None) os.utime(path, None)
except OSError, e: except OSError, e:
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' % str(e))
else: else:
try: module.fail_json(msg='Cannot touch other than files and directories')
open(path, 'w').close()
except OSError, e: module.set_fs_attributes_if_different(file_args, True)
module.fail_json(path=path, msg='Error, could not touch target: %s' % str(e))
module.set_file_attributes_if_different(file_args, True)
module.exit_json(dest=path, changed=True) module.exit_json(dest=path, changed=True)
else:
module.fail_json(path=path, msg='unexpected position reached') module.fail_json(path=path, msg='unexpected position reached')
# import module snippets # import module snippets

Loading…
Cancel
Save