mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
use stat module instead of checksum code
- added new function for action plugins this avoids the very fragile checksum code that is shell dependant. - ported copy module to it - converted assemble to new stat function - some corrections and ported temlpate - updated old checksum function to use new stat one under the hood - documented revamped remote checksum method
This commit is contained in:
parent
6ddea3e915
commit
b9d0662faf
4 changed files with 62 additions and 39 deletions
|
@ -291,28 +291,54 @@ class ActionBase(with_metaclass(ABCMeta, object)):
|
||||||
res = self._low_level_execute_command(cmd, sudoable=sudoable)
|
res = self._low_level_execute_command(cmd, sudoable=sudoable)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
def _execute_remote_stat(self, path, all_vars, follow):
|
||||||
|
'''
|
||||||
|
Get information from remote file.
|
||||||
|
'''
|
||||||
|
module_args=dict(
|
||||||
|
path=path,
|
||||||
|
follow=follow,
|
||||||
|
get_md5=False,
|
||||||
|
get_checksum=True,
|
||||||
|
checksum_algo='sha1',
|
||||||
|
)
|
||||||
|
mystat = self._execute_module(module_name='stat', module_args=module_args, task_vars=all_vars)
|
||||||
|
|
||||||
|
if 'failed' in mystat and mystat['failed']:
|
||||||
|
raise AnsibleError('Failed to get information on remote file (%s): %s' % (path, mystat['msg']))
|
||||||
|
|
||||||
|
if not mystat['stat']['exists']:
|
||||||
|
# empty might be matched, 1 should never match, also backwards compatible
|
||||||
|
mystat['stat']['checksum'] = '1'
|
||||||
|
|
||||||
|
return mystat['stat']
|
||||||
|
|
||||||
def _remote_checksum(self, path, all_vars):
|
def _remote_checksum(self, path, all_vars):
|
||||||
'''
|
'''
|
||||||
Takes a remote checksum and returns 1 if no file
|
Produces a remote checksum given a path,
|
||||||
|
Returns a number 0-4 for specific errors instead of checksum, also ensures it is different
|
||||||
|
0 = unknown error
|
||||||
|
1 = file does not exist, this might not be an error
|
||||||
|
2 = permissions issue
|
||||||
|
3 = its a directory, not a file
|
||||||
|
4 = stat module failed, likely due to not finding python
|
||||||
'''
|
'''
|
||||||
|
x = "0" # unknown error has occured
|
||||||
python_interp = all_vars.get('ansible_python_interpreter', 'python')
|
|
||||||
|
|
||||||
cmd = self._connection._shell.checksum(path, python_interp)
|
|
||||||
data = self._low_level_execute_command(cmd, sudoable=True)
|
|
||||||
try:
|
try:
|
||||||
data2 = data['stdout'].strip().splitlines()[-1]
|
remote_stat = self._execute_remote_stat(path, all_vars, follow=False)
|
||||||
if data2 == u'':
|
if remote_stat['exists'] and remote_stat['isdir']:
|
||||||
# this may happen if the connection to the remote server
|
x = "3" # its a directory not a file
|
||||||
# failed, so just return "INVALIDCHECKSUM" to avoid errors
|
|
||||||
return "INVALIDCHECKSUM"
|
|
||||||
else:
|
else:
|
||||||
return data2.split()[0]
|
x = remote_stat['checksum'] # if 1, file is missing
|
||||||
except IndexError:
|
except AnsibleError as e:
|
||||||
display.warning(u"Calculating checksum failed unusually, please report this to "
|
errormsg = to_bytes(e)
|
||||||
u"the list so it can be fixed\ncommand: %s\n----\noutput: %s\n----\n" % (to_unicode(cmd), data))
|
if errormsg.endswith('Permission denied'):
|
||||||
# this will signal that it changed and allow things to keep going
|
x = "2" # cannot read file
|
||||||
return "INVALIDCHECKSUM"
|
elif errormsg.endswith('MODULE FAILURE'):
|
||||||
|
x = "4" # python not found or module uncaught exception
|
||||||
|
finally:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
def _remote_expand_user(self, path):
|
def _remote_expand_user(self, path):
|
||||||
''' takes a remote path and performs tilde expansion on the remote host '''
|
''' takes a remote path and performs tilde expansion on the remote host '''
|
||||||
|
|
|
@ -89,6 +89,7 @@ class ActionModule(ActionBase):
|
||||||
delimiter = self._task.args.get('delimiter', None)
|
delimiter = self._task.args.get('delimiter', None)
|
||||||
remote_src = self._task.args.get('remote_src', 'yes')
|
remote_src = self._task.args.get('remote_src', 'yes')
|
||||||
regexp = self._task.args.get('regexp', None)
|
regexp = self._task.args.get('regexp', None)
|
||||||
|
follow = self._task.args.get('follow', False)
|
||||||
ignore_hidden = self._task.args.get('ignore_hidden', False)
|
ignore_hidden = self._task.args.get('ignore_hidden', False)
|
||||||
|
|
||||||
if src is None or dest is None:
|
if src is None or dest is None:
|
||||||
|
@ -119,10 +120,10 @@ class ActionModule(ActionBase):
|
||||||
|
|
||||||
path_checksum = checksum_s(path)
|
path_checksum = checksum_s(path)
|
||||||
dest = self._remote_expand_user(dest)
|
dest = self._remote_expand_user(dest)
|
||||||
remote_checksum = self._remote_checksum(dest, all_vars=task_vars)
|
dest_stat = self._execute_remote_stat(dest, all_vars=task_vars, follow=follow)
|
||||||
|
|
||||||
diff = {}
|
diff = {}
|
||||||
if path_checksum != remote_checksum:
|
if path_checksum != dest_stat['checksum']:
|
||||||
resultant = file(path).read()
|
resultant = file(path).read()
|
||||||
|
|
||||||
if self._play_context.diff:
|
if self._play_context.diff:
|
||||||
|
|
|
@ -46,6 +46,7 @@ class ActionModule(ActionBase):
|
||||||
force = boolean(self._task.args.get('force', 'yes'))
|
force = boolean(self._task.args.get('force', 'yes'))
|
||||||
faf = self._task.first_available_file
|
faf = self._task.first_available_file
|
||||||
remote_src = boolean(self._task.args.get('remote_src', False))
|
remote_src = boolean(self._task.args.get('remote_src', False))
|
||||||
|
follow = boolean(self._task.args.get('follow', False))
|
||||||
|
|
||||||
if (source is None and content is None and faf is None) or dest is None:
|
if (source is None and content is None and faf is None) or dest is None:
|
||||||
result['failed'] = True
|
result['failed'] = True
|
||||||
|
@ -167,11 +168,11 @@ class ActionModule(ActionBase):
|
||||||
else:
|
else:
|
||||||
dest_file = self._connection._shell.join_path(dest)
|
dest_file = self._connection._shell.join_path(dest)
|
||||||
|
|
||||||
# Attempt to get the remote checksum
|
# Attempt to get remote file info
|
||||||
remote_checksum = self._remote_checksum(dest_file, all_vars=task_vars)
|
dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow)
|
||||||
|
|
||||||
if remote_checksum == '3':
|
if dest_status['exists'] and dest_status['isdir']:
|
||||||
# The remote_checksum was executed on a directory.
|
# The dest is a directory.
|
||||||
if content is not None:
|
if content is not None:
|
||||||
# If source was defined as content remove the temporary file and fail out.
|
# If source was defined as content remove the temporary file and fail out.
|
||||||
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
self._remove_tempfile_if_content_defined(content, content_tempfile)
|
||||||
|
@ -179,15 +180,15 @@ class ActionModule(ActionBase):
|
||||||
result['msg'] = "can not use content with a dir as dest"
|
result['msg'] = "can not use content with a dir as dest"
|
||||||
return result
|
return result
|
||||||
else:
|
else:
|
||||||
# Append the relative source location to the destination and retry remote_checksum
|
# Append the relative source location to the destination and get remote stats again
|
||||||
dest_file = self._connection._shell.join_path(dest, source_rel)
|
dest_file = self._connection._shell.join_path(dest, source_rel)
|
||||||
remote_checksum = self._remote_checksum(dest_file, all_vars=task_vars)
|
dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow)
|
||||||
|
|
||||||
if remote_checksum != '1' and not force:
|
if not dest_status['exists'] and not force:
|
||||||
# remote_file does not exist so continue to next iteration.
|
# remote_file does not exist so continue to next iteration.
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if local_checksum != remote_checksum:
|
if local_checksum != dest_status['checksum']:
|
||||||
# The checksums don't match and we will change or error out.
|
# The checksums don't match and we will change or error out.
|
||||||
changed = True
|
changed = True
|
||||||
|
|
||||||
|
|
|
@ -34,23 +34,18 @@ class ActionModule(ActionBase):
|
||||||
TRANSFERS_FILES = True
|
TRANSFERS_FILES = True
|
||||||
|
|
||||||
def get_checksum(self, dest, all_vars, try_directory=False, source=None):
|
def get_checksum(self, dest, all_vars, try_directory=False, source=None):
|
||||||
remote_checksum = self._remote_checksum(dest, all_vars=all_vars)
|
try:
|
||||||
|
dest_stat = self._execute_remote_stat(dest, all_vars=all_vars, follow=False)
|
||||||
|
|
||||||
if remote_checksum in ('0', '2', '3', '4'):
|
if dest_stat['exists'] and dest_stat['isdir'] and try_directory and source:
|
||||||
# Note: 1 means the file is not present which is fine; template
|
|
||||||
# will create it. 3 means directory was specified instead of file
|
|
||||||
if try_directory and remote_checksum == '3' and source:
|
|
||||||
base = os.path.basename(source)
|
base = os.path.basename(source)
|
||||||
dest = os.path.join(dest, base)
|
dest = os.path.join(dest, base)
|
||||||
remote_checksum = self.get_checksum(dest, all_vars=all_vars, try_directory=False)
|
dest_stat = self._execute_remote_stat(dest, all_vars=all_vars, follow=False)
|
||||||
if remote_checksum not in ('0', '2', '3', '4'):
|
|
||||||
return remote_checksum
|
|
||||||
|
|
||||||
result = dict(failed=True, msg="failed to checksum remote file."
|
except Exception as e:
|
||||||
" Checksum error code: %s" % remote_checksum)
|
return dict(failed=True, msg=to_bytes(e))
|
||||||
return result
|
|
||||||
|
|
||||||
return remote_checksum
|
return dest_stat['checksum']
|
||||||
|
|
||||||
def run(self, tmp=None, task_vars=None):
|
def run(self, tmp=None, task_vars=None):
|
||||||
''' handler for template operations '''
|
''' handler for template operations '''
|
||||||
|
|
Loading…
Reference in a new issue