states.file.patch/modules.file.check_hash: use hash length to determine type

This brings the file.patch state and file.check_hash function to parity
with the source_hash handling changes made in the 2016.11 branch, by using
the hash length to determine the type.

It also fixes a shadowed builtin in the file.patch state.
This commit is contained in:
Erik Johnson 2017-02-22 10:54:35 -06:00
parent b492f7094c
commit 650dbaca4e
2 changed files with 71 additions and 37 deletions

View file

@ -687,31 +687,54 @@ def check_hash(path, file_hash):
'''
Check if a file matches the given hash string
Returns true if the hash matched, otherwise false. Raises ValueError if
the hash was not formatted correctly.
Returns ``True`` if the hash matches, otherwise ``False``.
path
A file path
Path to a file local to the minion.
hash
A string in the form <hash_type>:<hash_value>. For example:
``md5:e138491e9d5b97023cea823fe17bac22``
The hash to check against the file specified in the ``path`` argument.
For versions 2016.11.4 and newer, the hash can be specified without an
accompanying hash type (e.g. ``e138491e9d5b97023cea823fe17bac22``),
but for earlier releases it is necessary to also specify the hash type
in the format ``<hash_type>:<hash_value>`` (e.g.
``md5:e138491e9d5b97023cea823fe17bac22``).
CLI Example:
.. code-block:: bash
salt '*' file.check_hash /etc/fstab md5:<md5sum>
salt '*' file.check_hash /etc/fstab e138491e9d5b97023cea823fe17bac22
salt '*' file.check_hash /etc/fstab md5:e138491e9d5b97023cea823fe17bac22
'''
path = os.path.expanduser(path)
hash_parts = file_hash.split(':', 1)
if len(hash_parts) != 2:
# Support "=" for backward compatibility.
hash_parts = file_hash.split('=', 1)
if len(hash_parts) != 2:
raise ValueError('Bad hash format: \'{0}\''.format(file_hash))
hash_form, hash_value = hash_parts
return get_hash(path, hash_form) == hash_value
if not isinstance(file_hash, six.string_types):
raise SaltInvocationError('hash must be a string')
for sep in (':', '='):
if sep in file_hash:
hash_type, hash_value = file_hash.split(sep, 1)
break
else:
hash_value = file_hash
hash_len = len(file_hash)
hash_type = HASHES_REVMAP.get(hash_len)
if hash_type is None:
raise SaltInvocationError(
'Hash {0} (length: {1}) could not be matched to a supported '
'hash type. The supported hash types and lengths are: '
'{2}'.format(
file_hash,
hash_len,
', '.join(
['{0} ({1})'.format(HASHES_REVMAP[x], x)
for x in sorted(HASHES_REVMAP)]
),
)
)
return get_hash(path, hash_type) == hash_value
def find(path, *args, **kwargs):

View file

@ -281,7 +281,7 @@ import salt.utils.dictupdate
import salt.utils.templates
import salt.utils.url
from salt.utils.locales import sdecode
from salt.exceptions import CommandExecutionError
from salt.exceptions import CommandExecutionError, SaltInvocationError
# Import 3rd-party libs
import salt.ext.six as six
@ -4372,7 +4372,6 @@ def prepend(name,
def patch(name,
source=None,
hash=None,
options='',
dry_run_first=True,
**kwargs):
@ -4394,10 +4393,13 @@ def patch(name,
salt://spam/eggs. A source is required.
hash
Hash of the patched file. If the hash of the target file matches this
value then the patch is assumed to have been applied. The hash string
is the hash algorithm followed by the hash of the file:
md5=e138491e9d5b97023cea823fe17bac22
The hash of the patched file. If the hash of the target file matches
this value then the patch is assumed to have been applied. For versions
2016.11.4 and newer, the hash can be specified without an accompanying
hash type (e.g. ``e138491e9d5b97023cea823fe17bac22``), but for earlier
releases it is necessary to also specify the hash type in the format
``<hash_type>:<hash_value>`` (e.g.
``md5:e138491e9d5b97023cea823fe17bac22``).
options
Extra options to pass to patch.
@ -4410,7 +4412,7 @@ def patch(name,
by the ``source`` parameter. If not provided, this defaults to the
environment from which the state is being executed.
Usage:
**Usage:**
.. code-block:: yaml
@ -4418,8 +4420,15 @@ def patch(name,
/opt/file.txt:
file.patch:
- source: salt://file.patch
- hash: md5=e138491e9d5b97023cea823fe17bac22
- hash: e138491e9d5b97023cea823fe17bac22
.. note::
For minions running version 2016.11.3 or older, the hash in the example
above would need to be specified with the hash type (i.e.
``md5:e138491e9d5b97023cea823fe17bac22``).
'''
hash_ = kwargs.pop('hash', None)
if 'env' in kwargs:
salt.utils.warn_until(
'Oxygen',
@ -4439,11 +4448,16 @@ def patch(name,
return _error(ret, check_msg)
if not source:
return _error(ret, 'Source is required')
if hash is None:
if hash_ is None:
return _error(ret, 'Hash is required')
if hash and __salt__['file.check_hash'](name, hash):
ret.update(result=True, comment='Patch is already applied')
try:
if hash_ and __salt__['file.check_hash'](name, hash_):
ret['result'] = True
ret['comment'] = 'Patch is already applied'
return ret
except (SaltInvocationError, ValueError) as exc:
ret['comment'] = exc.__str__()
return ret
# get cached file or copy it to cache
@ -4454,9 +4468,8 @@ def patch(name,
return ret
log.debug(
'State patch.applied cached source {0} -> {1}'.format(
source, cached_source_path
)
'State patch.applied cached source %s -> %s',
source, cached_source_path
)
if dry_run_first or __opts__['test']:
@ -4467,20 +4480,18 @@ def patch(name,
ret['comment'] = 'File {0} will be patched'.format(name)
ret['result'] = None
return ret
if ret['changes']['retcode']:
if ret['changes']['retcode'] != 0:
return ret
ret['changes'] = __salt__['file.patch'](
name, cached_source_path, options=options
)
ret['result'] = not ret['changes']['retcode']
if ret['result'] and hash and not __salt__['file.check_hash'](name, hash):
ret.update(
result=False,
comment='File {0} hash mismatch after patch was applied'.format(
name
)
)
ret['result'] = ret['changes']['retcode'] == 0
# No need to check for SaltInvocationError or ValueError this time, since
# these exceptions would have been caught above.
if ret['result'] and hash_ and not __salt__['file.check_hash'](name, hash_):
ret['result'] = False
ret['comment'] = 'Hash mismatch after patch was applied'
return ret