Merge branch 'merge-forward/2018.3-to-2019.2' into merge-forward/2019.2-develop

Conflicts:
* Gemfile
* salt/modules/file.py
* salt/modules/win_task.py
* tests/unit/modules/test_file.py
This commit is contained in:
Pedro Algarvio 2019-05-24 09:59:06 +01:00
commit dcd86a1894
No known key found for this signature in database
GPG Key ID: BB36BF6584A298FF
28 changed files with 584 additions and 158 deletions

View File

@ -30,4 +30,5 @@ group :macos do
gem 'rbnacl', '< 5.0', :require => false
gem 'rbnacl-libsodium', :require => false
gem 'bcrypt_pbkdf', '< 2.0', :require => false
gem 'ffi', '= 1.10.0', :require => false
end

View File

@ -30,5 +30,6 @@ This section contains a list of the Python modules that are used to extend the v
../ref/serializers/all/index
../ref/states/all/index
../ref/thorium/all/index
../ref/tokens/all/index
../ref/tops/all/index
../ref/wheel/all/index

View File

@ -0,0 +1,14 @@
.. _all-salt.tokens:
============
auth modules
============
.. currentmodule:: salt.tokens
.. autosummary::
:toctree:
:template: autosummary.rst.tmpl
localfs
rediscluster

View File

@ -0,0 +1,6 @@
===================
salt.tokens.localfs
===================
.. automodule:: salt.tokens.localfs
:members:

View File

@ -0,0 +1,6 @@
========================
salt.tokens.rediscluster
========================
.. automodule:: salt.tokens.rediscluster
:members:

View File

@ -61,6 +61,7 @@ import datetime
try:
from M2Crypto import EVP
HAS_REQUIRED_CRYPTO = True
HAS_M2 = True
except ImportError:
HAS_M2 = False

View File

@ -22,7 +22,6 @@ from salt.ext.six.moves import queue
log = logging.getLogger(__name__)
if sys.version_info < (2, 7):
# Since the NullHandler is only available on python >= 2.7, here's a copy
# with NewStyleClassMixIn so it's also a new style class

View File

@ -63,6 +63,7 @@ def _clear_context():
def _yes():
'''
Returns ['--yes'] if on v0.9.9.0 or later, otherwise returns an empty list
Confirm all prompts (--yes_ is available on v0.9.9.0 or later
'''
if 'chocolatey._yes' in __context__:
return __context__['chocolatey._yes']
@ -87,7 +88,7 @@ def _no_progress():
log.warning('--no-progress unsupported in choco < 0.10.4')
answer = []
__context__['chocolatey._no_progress'] = answer
return __context__['chocolatey._no_progress']
return answer
def _find_chocolatey():

View File

@ -30,7 +30,7 @@ import time
import glob
import hashlib
import mmap
from collections import Iterable, Mapping
from collections import Iterable, Mapping, namedtuple
from functools import reduce # pylint: disable=redefined-builtin
# pylint: disable=import-error,no-name-in-module,redefined-builtin
@ -73,6 +73,9 @@ __func_alias__ = {
}
AttrChanges = namedtuple('AttrChanges', 'added,removed')
def __virtual__():
'''
Only work on POSIX-like systems
@ -161,33 +164,49 @@ def _splitlines_preserving_trailing_newline(str):
return lines
def _get_chattr_man():
'''
Get the contents of the chattr man page
'''
return subprocess.check_output(['man', 'chattr'])
def _parse_chattr_man(man):
'''
Parse the contents of a chattr man page to find the E2fsprogs version
'''
match = re.search(
r'E2fsprogs version [0-9\.]+',
salt.utils.stringutils.to_str(man),
)
if match:
version = match.group().strip('E2fsprogs version ')
else:
version = None
return version
def _chattr_version():
'''
Return the version of chattr installed
'''
return _parse_chattr_man(_get_chattr_man())
# There's no really *good* way to get the version of chattr installed.
# It's part of the e2fsprogs package - we could try to parse the version
# from the package manager, but there's no guarantee that it was
# installed that way.
#
# The most reliable approach is to just check tune2fs, since that should
# be installed with chattr, at least if it was installed in a conventional
# manner.
#
# See https://unix.stackexchange.com/a/520399/5788 for discussion.
tune2fs = salt.utils.path.which('tune2fs')
if not tune2fs or salt.utils.platform.is_aix():
return None
cmd = [tune2fs]
result = __salt__['cmd.run'](cmd, ignore_retcode=True, python_shell=False)
match = re.search(
r'tune2fs (?P<version>[0-9\.]+)',
salt.utils.stringutils.to_str(result),
)
if match is None:
version = None
else:
version = match.group('version')
return version
def _chattr_has_extended_attrs():
'''
Return ``True`` if chattr supports extended attributes, that is,
the version is >1.41.22. Otherwise, ``False``
'''
ver = _chattr_version()
if ver is None:
return False
needed_version = salt.utils.versions.LooseVersion('1.41.12')
chattr_version = salt.utils.versions.LooseVersion(ver)
return chattr_version > needed_version
def gid_to_group(gid):
@ -551,8 +570,6 @@ def _cmp_attrs(path, attrs):
attrs
string of attributes to compare against a given file
'''
diff = [None, None]
# lsattr for AIX is not the same thing as lsattr for linux.
if salt.utils.platform.is_aix():
return None
@ -563,15 +580,13 @@ def _cmp_attrs(path, attrs):
# lsattr not installed
return None
old = [chr for chr in lattrs if chr not in attrs]
if old:
diff[1] = ''.join(old)
new = set(attrs)
old = set(lattrs)
new = [chr for chr in attrs if chr not in lattrs]
if new:
diff[0] = ''.join(new)
return diff
return AttrChanges(
added=''.join(new-old) or None,
removed=''.join(old-new) or None,
)
def lsattr(path):
@ -607,15 +622,12 @@ def lsattr(path):
results = {}
for line in result.splitlines():
if not line.startswith('lsattr: '):
vals = line.split(None, 1)
needed_version = salt.utils.versions.LooseVersion('1.41.12')
chattr_version = salt.utils.versions.LooseVersion(_chattr_version())
# The version of chattr on Centos 6 does not support extended
# attributes.
if chattr_version > needed_version:
results[vals[1]] = re.findall(r"[aAcCdDeijPsStTu]", vals[0])
attrs, file = line.split(None, 1)
if _chattr_has_extended_attrs():
pattern = r"[aAcCdDeijPsStTu]"
else:
results[vals[1]] = re.findall(r"[acdijstuADST]", vals[0])
pattern = r"[acdijstuADST]"
results[file] = re.findall(pattern, attrs)
return results
@ -680,8 +692,7 @@ def chattr(*files, **kwargs):
result = __salt__['cmd.run'](cmd, python_shell=False)
if bool(result):
raise CommandExecutionError(
"chattr failed to run, possibly due to bad parameters.")
return False
return True
@ -4641,19 +4652,6 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False,
is_dir = os.path.isdir(name)
is_link = os.path.islink(name)
if attrs is not None \
and not salt.utils.platform.is_windows() \
and not is_dir and not is_link:
try:
lattrs = lsattr(name)
except SaltInvocationError:
lattrs = None
if lattrs is not None:
# List attributes on file
perms['lattrs'] = ''.join(lattrs.get(name, ''))
# Remove attributes on file so changes can be enforced.
if perms['lattrs']:
chattr(name, operator='remove', attributes=perms['lattrs'])
# user/group changes if needed, then check if it worked
if user:
@ -4735,11 +4733,6 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False,
elif 'cgroup' in perms and user != '':
ret['changes']['group'] = group
if not salt.utils.platform.is_windows() and not is_dir:
# Replace attributes on file if it had been removed
if perms.get('lattrs', ''):
chattr(name, operator='add', attributes=perms['lattrs'])
# Mode changes if needed
if mode is not None:
# File is a symlink, ignore the mode setting
@ -4769,23 +4762,37 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False,
pass
else:
diff_attrs = _cmp_attrs(name, attrs)
if diff_attrs is not None:
if diff_attrs[0] is not None or diff_attrs[1] is not None:
if diff_attrs and any(attr for attr in diff_attrs):
changes = {
'old': ''.join(lsattr(name)[name]),
'new': None,
}
if __opts__['test'] is True:
ret['changes']['attrs'] = attrs
changes['new'] = attrs
else:
if diff_attrs[0] is not None:
chattr(name, operator="add", attributes=diff_attrs[0])
if diff_attrs[1] is not None:
chattr(name, operator="remove", attributes=diff_attrs[1])
if diff_attrs.added:
chattr(
name,
operator="add",
attributes=diff_attrs.added,
)
if diff_attrs.removed:
chattr(
name,
operator="remove",
attributes=diff_attrs.removed,
)
cmp_attrs = _cmp_attrs(name, attrs)
if cmp_attrs[0] is not None or cmp_attrs[1] is not None:
if any(attr for attr in cmp_attrs):
ret['result'] = False
ret['comment'].append(
'Failed to change attributes to {0}'.format(attrs)
)
changes['new'] = ''.join(lsattr(name)[name])
else:
ret['changes']['attrs'] = attrs
changes['new'] = attrs
if changes['old'] != changes['new']:
ret['changes']['attrs'] = changes
# Set selinux attributes if needed
if salt.utils.platform.is_linux() and (seuser or serole or setype or serange):

View File

@ -69,9 +69,10 @@ HAS_WINDOWS_MODULES = False
try:
if salt.utils.platform.is_windows():
import win32api
import win32file
import win32con
from pywintypes import error as pywinerror
import win32file
import win32security
import salt.platform.win
HAS_WINDOWS_MODULES = True
except ImportError:
HAS_WINDOWS_MODULES = False
@ -1148,10 +1149,19 @@ def symlink(src, link):
is_dir = os.path.isdir(src)
# Elevate the token from the current process
desired_access = (
win32security.TOKEN_QUERY |
win32security.TOKEN_ADJUST_PRIVILEGES
)
th = win32security.OpenProcessToken(win32api.GetCurrentProcess(),
desired_access)
salt.platform.win.elevate_token(th)
try:
win32file.CreateSymbolicLink(link, src, int(is_dir))
return True
except pywinerror as exc:
except win32file.error as exc:
raise CommandExecutionError(
'Could not create \'{0}\' - [{1}] {2}'.format(
link,

View File

@ -145,6 +145,24 @@ instances = {'Parallel': TASK_INSTANCES_PARALLEL,
'No New Instance': TASK_INSTANCES_IGNORE_NEW,
'Stop Existing': TASK_INSTANCES_STOP_EXISTING}
results = {0x0: 'The operation completed successfully',
0x1: 'Incorrect or unknown function called',
0x2: 'File not found',
0xA: 'The environment is incorrect',
0x41300: 'Task is ready to run at its next scheduled time',
0x41301: 'Task is currently running',
0x41302: 'Task is disabled',
0x41303: 'Task has not yet run',
0x41304: 'There are no more runs scheduled for this task',
0x41306: 'Task was terminated by the user',
0x8004130F: 'Credentials became corrupted',
0x8004131F: 'An instance of this task is already running',
0x800710E0: 'The operator or administrator has refused the request',
0x800704DD: 'The service is not available (Run only when logged '
'in?)',
0xC000013A: 'The application terminated as a result of CTRL+C',
0xC06D007E: 'Unknown software exception'}
def show_win32api_code(code):
'''
@ -2036,7 +2054,7 @@ def add_trigger(name=None,
*MonthlyDay*
The task will run monthly an the specified day.
The task will run monthly on the specified day.
months_of_year (list):
Sets the months of the year during which the task runs. Should

View File

@ -1146,7 +1146,7 @@ def dup_token(th):
def elevate_token(th):
'''
Set all token priviledges to enabled
Set all token privileges to enabled
'''
# Get list of privileges this token contains
privileges = win32security.GetTokenInformation(

View File

@ -67,7 +67,7 @@ if HAS_PIP is True:
from pip._internal.exceptions import InstallationError # pylint: disable=E0611,E0401
elif salt.utils.versions.compare(ver1=pip.__version__,
oper='>=',
ver2='10.0'):
ver2='1.0'):
from pip.exceptions import InstallationError # pylint: disable=E0611,E0401
else:
InstallationError = ValueError

View File

@ -261,11 +261,15 @@ class IPCClient(object):
self.socket_path = socket_path
self._closing = False
self.stream = None
if six.PY2:
encoding = None
# msgpack deprecated `encoding` starting with version 0.5.2
if msgpack.version >= (0, 5, 2):
msgpack_kwargs = {'raw': False}
else:
encoding = 'utf-8'
self.unpacker = msgpack.Unpacker(encoding=encoding)
if six.PY2:
msgpack_kwargs = {'encoding': None}
else:
msgpack_kwargs = {'encoding': 'utf-8'}
self.unpacker = msgpack.Unpacker(**msgpack_kwargs)
def connected(self):
return self.stream is not None and not self.stream.closed()

View File

@ -25,6 +25,7 @@ except ImportError:
try:
# Windows does not have the crypt module
# consider using passlib.hash instead
import crypt
HAS_CRYPT = True
except ImportError:
@ -54,7 +55,7 @@ def secure_password(length=20, use_random=True):
except UnicodeDecodeError:
continue
pw += re.sub(
salt.utils.stringutils.to_str(r'\W'),
salt.utils.stringutils.to_str(r'[\W_]'),
str(), # future lint: disable=blacklisted-function
char
)

View File

@ -3,7 +3,9 @@
- system_site_packages: False
- distribute: True
{#- Provide the real path for the python executable in case tests are running inside a virtualenv #}
{%- if salt.runtests_helpers.get_python_executable() %}
- python: {{ salt.runtests_helpers.get_python_executable() }}
{%- endif %}
pep8-pip:
pip.installed:

View File

@ -6,7 +6,9 @@
- system_site_packages: False
- distribute: True
{#- Provide the real path for the python executable in case tests are running inside a virtualenv #}
{%- if salt.runtests_helpers.get_python_executable() %}
- python: {{ salt.runtests_helpers.get_python_executable() }}
{%- endif %}
install_older_venv_1:
pip.installed:

View File

@ -55,10 +55,6 @@ def setup_handlers():
pass
sock.close()
# One million log messages is more than enough to queue.
# Above that value, if `process_queue` can't process fast enough,
# start dropping. This will contain a memory leak in case `process_queue`
# can't process fast enough of in case it can't deliver the log records at all.
if is_darwin():
queue_size = 32767
else:

View File

@ -2252,6 +2252,7 @@ class StateModuleTest(ModuleCase, SaltReturnAssertsMixin):
for id in _expected:
self.assertEqual(sls[id]['comment'], _expected[id]['comment'])
@skipIf(six.PY3 and salt.utils.platform.is_darwin(), 'Test is broken on macosx and PY3')
def test_state_sls_unicode_characters(self):
'''
test state.sls when state file contains non-ascii characters
@ -2262,6 +2263,7 @@ class StateModuleTest(ModuleCase, SaltReturnAssertsMixin):
_expected = "cmd_|-echo1_|-echo 'This is Æ test!'_|-run"
self.assertIn(_expected, ret)
@skipIf(six.PY3 and salt.utils.platform.is_darwin(), 'Test is broken on macosx and PY3')
def test_state_sls_unicode_characters_cmd_output(self):
'''
test the output from running and echo command with non-ascii

View File

@ -48,7 +48,7 @@ class ManageTest(ShellCase):
)
# Make sure we can see the new key
expected = 'Passed invalid arguments:'
self.assertIn(expected, ret['return'])
self.assertRaisesRegex(TypeError, expected)
def test_grains(self):
'''

View File

@ -36,7 +36,7 @@ class ManageTest(ShellCase):
'''
ret = self.run_run_plus('jobs.lookup_jid')
expected = 'Passed invalid arguments:'
self.assertIn(expected, ret['return'])
self.assertRaises(TypeError, expected)
@skipIf(True, 'to be re-enabled when #23623 is merged')
def test_list_jobs(self):

View File

@ -32,4 +32,4 @@ class SaltRunnerTest(ShellCase):
'''
ret = self.run_run_plus('salt.cmd')
expected = 'Passed invalid arguments:'
self.assertIn(expected, ret['return'])
self.assertRaisesRegex(TypeError, expected)

View File

@ -49,6 +49,7 @@ class MinionTest(ShellCase, testprogram.TestProgramCase, ShellCaseCommonTestsMix
'subminion',
)
@skipIf(salt.utils.platform.is_darwin(), 'Test is flaky on macosx')
def test_issue_7754(self):
old_cwd = os.getcwd()
config_dir = os.path.join(RUNTIME_VARS.TMP, 'issue-7754')

View File

@ -29,6 +29,9 @@ class CronTest(ModuleCase):
'''
self.run_state('user.present', name='test_cron_user')
self.user_primary_group = self.run_function('user.primary_group',
name='test_cron_user')
def tearDown(self):
'''
Teardown
@ -45,7 +48,7 @@ class CronTest(ModuleCase):
_expected = {
'changes': {
'diff': '--- \n+++ \n@@ -1 +1,2 @@\n-\n+# Lines below here are managed by Salt, do not edit\n+@hourly touch /tmp/test-file\n',
'group': user_id,
'group': self.user_primary_group,
'user': user_id,
},
}

View File

@ -2137,6 +2137,367 @@ class FileBasicsTestCase(TestCase, LoaderModuleMockMixin):
self.assertEqual(list(ret), ['file://' + self.myfile, 'filehash'])
class LsattrTests(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
return {
filemod: {
'__salt__': {
'cmd.run': cmdmod.run,
},
},
}
def run(self, result=None):
patch_aix = patch(
'salt.utils.platform.is_aix',
Mock(return_value=False),
)
patch_exists = patch(
'os.path.exists',
Mock(return_value=True),
)
patch_which = patch(
'salt.utils.path.which',
Mock(return_value='fnord'),
)
with patch_aix, patch_exists, patch_which:
super(LsattrTests, self).run(result)
def test_if_lsattr_is_missing_it_should_return_None(self):
patch_which = patch(
'salt.utils.path.which',
Mock(return_value=None),
)
with patch_which:
actual = filemod.lsattr('foo')
assert actual is None, actual
def test_on_aix_lsattr_should_be_None(self):
patch_aix = patch(
'salt.utils.platform.is_aix',
Mock(return_value=True),
)
with patch_aix:
# SaltInvocationError will be raised if filemod.lsattr
# doesn't early exit
actual = filemod.lsattr('foo')
self.assertIsNone(actual)
def test_SaltInvocationError_should_be_raised_when_file_is_missing(self):
patch_exists = patch(
'os.path.exists',
Mock(return_value=False),
)
with patch_exists, self.assertRaises(SaltInvocationError):
filemod.lsattr('foo')
def test_if_chattr_version_is_less_than_required_flags_should_ignore_extended(self):
fname = '/path/to/fnord'
with_extended = textwrap.dedent(
'''
aAcCdDeijPsStTu---- {}
'''
).strip().format(fname)
expected = set('acdijstuADST')
patch_has_ext = patch(
'salt.modules.file._chattr_has_extended_attrs',
Mock(return_value=False),
)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': Mock(return_value=with_extended)},
)
with patch_has_ext, patch_run:
actual = set(filemod.lsattr(fname)[fname])
msg = 'Actual: {!r} Expected: {!r}'.format(actual, expected) # pylint: disable=E1322
assert actual == expected, msg
def test_if_chattr_version_is_high_enough_then_extended_flags_should_be_returned(self):
fname = '/path/to/fnord'
with_extended = textwrap.dedent(
'''
aAcCdDeijPsStTu---- {}
'''
).strip().format(fname)
expected = set('aAcCdDeijPsStTu')
patch_has_ext = patch(
'salt.modules.file._chattr_has_extended_attrs',
Mock(return_value=True),
)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': Mock(return_value=with_extended)},
)
with patch_has_ext, patch_run:
actual = set(filemod.lsattr(fname)[fname])
msg = 'Actual: {!r} Expected: {!r}'.format(actual, expected) # pylint: disable=E1322
assert actual == expected, msg
def test_if_supports_extended_but_there_are_no_flags_then_none_should_be_returned(self):
fname = '/path/to/fnord'
with_extended = textwrap.dedent(
'''
------------------- {}
'''
).strip().format(fname)
expected = set('')
patch_has_ext = patch(
'salt.modules.file._chattr_has_extended_attrs',
Mock(return_value=True),
)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': Mock(return_value=with_extended)},
)
with patch_has_ext, patch_run:
actual = set(filemod.lsattr(fname)[fname])
msg = 'Actual: {!r} Expected: {!r}'.format(actual, expected) # pylint: disable=E1322
assert actual == expected, msg
class ChattrTests(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
return {
filemod: {
'__salt__': {
'cmd.run': cmdmod.run,
},
'__opts__': {
'test': False,
},
},
}
def run(self, result=None):
patch_aix = patch(
'salt.utils.platform.is_aix',
Mock(return_value=False),
)
patch_exists = patch(
'os.path.exists',
Mock(return_value=True),
)
patch_which = patch(
'salt.utils.path.which',
Mock(return_value='some/tune2fs'),
)
with patch_aix, patch_exists, patch_which:
super(ChattrTests, self).run(result)
def test_chattr_version_returns_None_if_no_tune2fs_exists(self):
patch_which = patch(
'salt.utils.path.which',
Mock(return_value=''),
)
with patch_which:
actual = filemod._chattr_version()
self.assertIsNone(actual)
def test_on_aix_chattr_version_should_be_None_even_if_tune2fs_exists(self):
patch_which = patch(
'salt.utils.path.which',
Mock(return_value='fnord'),
)
patch_aix = patch(
'salt.utils.platform.is_aix',
Mock(return_value=True),
)
mock_run = MagicMock(return_value='fnord')
patch_run = patch.dict(filemod.__salt__, {'cmd.run': mock_run})
with patch_which, patch_aix, patch_run:
actual = filemod._chattr_version()
self.assertIsNone(actual)
mock_run.assert_not_called()
def test_chattr_version_should_return_version_from_tune2fs(self):
expected = '1.43.4'
sample_output = textwrap.dedent(
'''
tune2fs 1.43.4 (31-Jan-2017)
Usage: tune2fs [-c max_mounts_count] [-e errors_behavior] [-f] [-g group]
[-i interval[d|m|w]] [-j] [-J journal_options] [-l]
[-m reserved_blocks_percent] [-o [^]mount_options[,...]]
[-p mmp_update_interval] [-r reserved_blocks_count] [-u user]
[-C mount_count] [-L volume_label] [-M last_mounted_dir]
[-O [^]feature[,...]] [-Q quota_options]
[-E extended-option[,...]] [-T last_check_time] [-U UUID]
[-I new_inode_size] [-z undo_file] device
'''
)
patch_which = patch(
'salt.utils.path.which',
Mock(return_value='fnord'),
)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': MagicMock(return_value=sample_output)},
)
with patch_which, patch_run:
actual = filemod._chattr_version()
self.assertEqual(actual, expected)
def test_if_tune2fs_has_no_version_version_should_be_None(self):
patch_which = patch(
'salt.utils.path.which',
Mock(return_value='fnord'),
)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': MagicMock(return_value='fnord')},
)
with patch_which, patch_run:
actual = filemod._chattr_version()
self.assertIsNone(actual)
def test_chattr_has_extended_attrs_should_return_False_if_chattr_version_is_None(self):
patch_chattr = patch(
'salt.modules.file._chattr_version',
Mock(return_value=None),
)
with patch_chattr:
actual = filemod._chattr_has_extended_attrs()
assert not actual, actual
def test_chattr_has_extended_attrs_should_return_False_if_version_is_too_low(self):
below_expected = '0.1.1'
patch_chattr = patch(
'salt.modules.file._chattr_version',
Mock(return_value=below_expected),
)
with patch_chattr:
actual = filemod._chattr_has_extended_attrs()
assert not actual, actual
def test_chattr_has_extended_attrs_should_return_False_if_version_is_equal_threshold(self):
threshold = '1.41.12'
patch_chattr = patch(
'salt.modules.file._chattr_version',
Mock(return_value=threshold),
)
with patch_chattr:
actual = filemod._chattr_has_extended_attrs()
assert not actual, actual
def test_chattr_has_extended_attrs_should_return_True_if_version_is_above_threshold(self):
higher_than = '1.41.13'
patch_chattr = patch(
'salt.modules.file._chattr_version',
Mock(return_value=higher_than),
)
with patch_chattr:
actual = filemod._chattr_has_extended_attrs()
assert actual, actual
def test_check_perms_should_report_no_attr_changes_if_there_are_none(self):
filename = '/path/to/fnord'
attrs = 'aAcCdDeijPsStTu'
higher_than = '1.41.13'
patch_chattr = patch(
'salt.modules.file._chattr_version',
Mock(return_value=higher_than),
)
patch_exists = patch(
'os.path.exists',
Mock(return_value=True),
)
patch_stats = patch(
'salt.modules.file.stats',
Mock(return_value={
'user': 'foo',
'group': 'bar',
'mode': '123',
}),
)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': MagicMock(return_value='--------- '+filename)},
)
with patch_chattr, patch_exists, patch_stats, patch_run:
actual_ret, actual_perms = filemod.check_perms(
name=filename,
ret=None,
user='foo',
group='bar',
mode='123',
attrs=attrs,
follow_symlinks=False,
)
assert actual_ret.get('changes', {}).get('attrs')is None, actual_ret
def test_check_perms_should_report_attrs_new_and_old_if_they_changed(self):
filename = '/path/to/fnord'
attrs = 'aAcCdDeijPsStTu'
existing_attrs = 'aeiu'
expected = {
'attrs': {
'old': existing_attrs,
'new': attrs,
},
}
higher_than = '1.41.13'
patch_chattr = patch(
'salt.modules.file._chattr_version',
Mock(return_value=higher_than),
)
patch_stats = patch(
'salt.modules.file.stats',
Mock(return_value={
'user': 'foo',
'group': 'bar',
'mode': '123',
}),
)
patch_cmp = patch(
'salt.modules.file._cmp_attrs',
MagicMock(side_effect=[
filemod.AttrChanges(
added='aAcCdDeijPsStTu',
removed='',
),
filemod.AttrChanges(
None,
None,
),
]),
)
patch_chattr = patch(
'salt.modules.file.chattr',
MagicMock(),
)
def fake_cmd(cmd, *args, **kwargs):
if cmd == ['lsattr', '/path/to/fnord']:
return textwrap.dedent(
'''
{}---- {}
'''.format(existing_attrs, filename)
).strip()
else:
assert False, "not sure how to handle {}".format(cmd)
patch_run = patch.dict(
filemod.__salt__,
{'cmd.run': MagicMock(side_effect=fake_cmd)},
)
patch_ver = patch(
'salt.modules.file._chattr_has_extended_attrs',
MagicMock(return_value=True),
)
with patch_chattr, patch_stats, patch_cmp, patch_run, patch_ver:
actual_ret, actual_perms = filemod.check_perms(
name=filename,
ret=None,
user='foo',
group='bar',
mode='123',
attrs=attrs,
follow_symlinks=False,
)
self.assertDictEqual(actual_ret['changes'], expected)
@skipIf(salt.modules.selinux.getenforce() != 'Enforcing', 'Skip if selinux not enabled')
class FileSelinuxTestCase(TestCase, LoaderModuleMockMixin):
def setup_loader_modules(self):
@ -2207,34 +2568,3 @@ class FileSelinuxTestCase(TestCase, LoaderModuleMockMixin):
result = filemod.check_perms(self.tfile3.name, {}, 'root', 'root', 644, seuser=None,
serole=None, setype='lost_found_t', serange=None)
self.assertEqual(result, expected_result)
class ChattrVersionTests(TestCase):
CHATTR_MAN = salt.utils.stringutils.to_bytes((
'AVAILABILITY\n'
'chattr is part of the e2fsprogs package and is available '
'from http://e2fsprogs.sourceforge.net.\n'
'SEE ALSO\n'
' lsattr(1), btrfs(5), ext4(5), xfs(5).\n\n'
'E2fsprogs version 1.43.4 '
' '
'January 2017 '
' '
' CHATTR(1)'
))
def test__parse_chattr_version(self):
'''
Validate we can parse the E2fsprogs version from the chattr man page
'''
man_out = dedent(self.CHATTR_MAN)
parsed_version = filemod._parse_chattr_man(man_out)
assert parsed_version == '1.43.4', parsed_version
def test__chattr_version(self):
'''
The _chattr_version method works
'''
with patch('subprocess.check_output', return_value=self.CHATTR_MAN):
parsed_version = filemod._chattr_version()
assert parsed_version == '1.43.4', parsed_version

View File

@ -257,29 +257,6 @@ class PipStateTest(TestCase, SaltReturnAssertsMixin, LoaderModuleMockMixin):
{'test': ret}
)
# Test VCS installations with version info like >= 0.1
with patch.object(pip, '__version__', MagicMock(side_effect=AttributeError(
'Faked missing __version__ attribute'))):
mock = MagicMock(return_value={'retcode': 0, 'stdout': ''})
pip_list = MagicMock(return_value={'SaltTesting': '0.5.0'})
pip_install = MagicMock(return_value={
'retcode': 0,
'stderr': '',
'stdout': 'Cloned!'
})
with patch.dict(pip_state.__salt__, {'cmd.run_all': mock,
'pip.list': pip_list,
'pip.install': pip_install}):
with patch.dict(pip_state.__opts__, {'test': False}):
ret = pip_state.installed(
'git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting>=0.5.0'
)
self.assertSaltTrueReturn({'test': ret})
self.assertInSaltComment(
'packages are already installed',
{'test': ret}
)
def test_install_in_editable_mode(self):
'''
Check that `name` parameter containing bad characters is not parsed by

View File

@ -1001,25 +1001,25 @@ class LoaderGlobalsTest(ModuleCase):
Verify that the globals listed in the doc string (from the test) are in these modules
'''
# find the globals
global_vars = []
global_vars = {}
for val in six.itervalues(mod_dict):
# only find salty globals
if val.__module__.startswith('salt.loaded'):
if hasattr(val, '__globals__'):
if hasattr(val, '__wrapped__') or '__wrapped__' in val.__globals__:
global_vars.append(sys.modules[val.__module__].__dict__)
global_vars[val.__module__] = sys.modules[val.__module__].__dict__
else:
global_vars.append(val.__globals__)
global_vars[val.__module__] = val.__globals__
# if we couldn't find any, then we have no modules -- so something is broken
self.assertNotEqual(global_vars, [], msg='No modules were loaded.')
self.assertNotEqual(global_vars, {}, msg='No modules were loaded.')
# get the names of the globals you should have
func_name = inspect.stack()[1][3]
names = next(six.itervalues(salt.utils.yaml.safe_load(getattr(self, func_name).__doc__)))
# Now, test each module!
for item in global_vars:
for item in six.itervalues(global_vars):
for name in names:
self.assertIn(name, list(item.keys()))

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import re
# Import Salt Libs
import salt.utils.pycrypto
# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
log = logging.getLogger(__name__)
class PycryptoTestCase(TestCase):
'''
TestCase for salt.utils.pycrypto module
'''
@skipIf(not salt.utils.pycrypto.HAS_CRYPT, 'No crypto library available')
def test_gen_hash(self):
'''
Test gen_hash
'''
passwd = 'test_password'
ret = salt.utils.pycrypto.gen_hash(password=passwd)
self.assertTrue(ret.startswith('$6$'))
ret = salt.utils.pycrypto.gen_hash(password=passwd, algorithm='md5')
self.assertTrue(ret.startswith('$1$'))
ret = salt.utils.pycrypto.gen_hash(password=passwd, algorithm='sha256')
self.assertTrue(ret.startswith('$5$'))
def test_secure_password(self):
'''
test secure_password
'''
ret = salt.utils.pycrypto.secure_password()
check = re.compile(r'[!@#$%^&*()_=+]')
assert check.search(ret) is None
assert ret