Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use stat module instead of checksum code #14490

Merged
merged 2 commits into from
Feb 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 42 additions & 16 deletions lib/ansible/plugins/action/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,28 +291,54 @@ def _remote_chmod(self, mode, path, sudoable=False):
res = self._low_level_execute_command(cmd, sudoable=sudoable)
return res

def _remote_checksum(self, path, all_vars):
def _execute_remote_stat(self, path, all_vars, follow):
'''
Takes a remote checksum and returns 1 if no file
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'

python_interp = all_vars.get('ansible_python_interpreter', 'python')
return mystat['stat']

cmd = self._connection._shell.checksum(path, python_interp)
data = self._low_level_execute_command(cmd, sudoable=True)
def _remote_checksum(self, path, all_vars):
'''
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
try:
data2 = data['stdout'].strip().splitlines()[-1]
if data2 == u'':
# this may happen if the connection to the remote server
# failed, so just return "INVALIDCHECKSUM" to avoid errors
return "INVALIDCHECKSUM"
remote_stat = self._execute_remote_stat(path, all_vars, follow=False)
if remote_stat['exists'] and remote_stat['isdir']:
x = "3" # its a directory not a file
else:
return data2.split()[0]
except IndexError:
display.warning(u"Calculating checksum failed unusually, please report this to "
u"the list so it can be fixed\ncommand: %s\n----\noutput: %s\n----\n" % (to_unicode(cmd), data))
# this will signal that it changed and allow things to keep going
return "INVALIDCHECKSUM"
x = remote_stat['checksum'] # if 1, file is missing
except AnsibleError as e:
errormsg = to_unicode(e)
if errormsg.endswith('Permission denied'):
x = "2" # cannot read file
elif errormsg.endswith('MODULE FAILURE'):
x = "4" # python not found or module uncaught exception
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we aren't sending the errormsg to an exception, we want to use to_unicode rather than to_bytes. to_bytes won't cause any problems with the code here in python2 but it will fail in python3 (because the string literals will be of the text type and so it won't match with errormsg as bytes then.)

finally:
return x


def _remote_expand_user(self, path):
''' takes a remote path and performs tilde expansion on the remote host '''
Expand Down
5 changes: 3 additions & 2 deletions lib/ansible/plugins/action/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def run(self, tmp=None, task_vars=None):
delimiter = self._task.args.get('delimiter', None)
remote_src = self._task.args.get('remote_src', 'yes')
regexp = self._task.args.get('regexp', None)
follow = self._task.args.get('follow', False)
ignore_hidden = self._task.args.get('ignore_hidden', False)

if src is None or dest is None:
Expand Down Expand Up @@ -119,10 +120,10 @@ def run(self, tmp=None, task_vars=None):

path_checksum = checksum_s(path)
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 = {}
if path_checksum != remote_checksum:
if path_checksum != dest_stat['checksum']:
resultant = file(path).read()

if self._play_context.diff:
Expand Down
17 changes: 9 additions & 8 deletions lib/ansible/plugins/action/copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def run(self, tmp=None, task_vars=None):
force = boolean(self._task.args.get('force', 'yes'))
faf = self._task.first_available_file
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:
result['failed'] = True
Expand Down Expand Up @@ -167,27 +168,27 @@ def run(self, tmp=None, task_vars=None):
else:
dest_file = self._connection._shell.join_path(dest)

# Attempt to get the remote checksum
remote_checksum = self._remote_checksum(dest_file, all_vars=task_vars)
# Attempt to get remote file info
dest_status = self._execute_remote_stat(dest_file, all_vars=task_vars, follow=follow)

if remote_checksum == '3':
# The remote_checksum was executed on a directory.
if dest_status['exists'] and dest_status['isdir']:
# The dest is a directory.
if content is not None:
# If source was defined as content remove the temporary file and fail out.
self._remove_tempfile_if_content_defined(content, content_tempfile)
result['failed'] = True
result['msg'] = "can not use content with a dir as dest"
return result
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)
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.
continue

if local_checksum != remote_checksum:
if local_checksum != dest_status['checksum']:
# The checksums don't match and we will change or error out.
changed = True

Expand Down
19 changes: 7 additions & 12 deletions lib/ansible/plugins/action/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,18 @@ class ActionModule(ActionBase):
TRANSFERS_FILES = True

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'):
# 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:
if dest_stat['exists'] and dest_stat['isdir'] and try_directory and source:
base = os.path.basename(source)
dest = os.path.join(dest, base)
remote_checksum = self.get_checksum(dest, all_vars=all_vars, try_directory=False)
if remote_checksum not in ('0', '2', '3', '4'):
return remote_checksum
dest_stat = self._execute_remote_stat(dest, all_vars=all_vars, follow=False)

result = dict(failed=True, msg="failed to checksum remote file."
" Checksum error code: %s" % remote_checksum)
return result
except Exception as e:
return dict(failed=True, msg=to_bytes(e))

return remote_checksum
return dest_stat['checksum']

def run(self, tmp=None, task_vars=None):
''' handler for template operations '''
Expand Down