2013-07-25 12:50:59 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
'''
|
|
|
|
:codeauthor: :email:`Pedro Algarvio (pedro@algarvio.me)`
|
2017-08-21 17:07:51 +00:00
|
|
|
:codeauthor: :email:`Alexandru Bleotu (alexandru.bleotu@morganstanley.com)`
|
2013-09-16 16:24:00 +00:00
|
|
|
|
|
|
|
|
|
|
|
tests.unit.pillar_test
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
2013-07-25 12:50:59 +00:00
|
|
|
'''
|
|
|
|
|
2014-11-21 19:05:13 +00:00
|
|
|
# Import python libs
|
|
|
|
from __future__ import absolute_import
|
2013-08-07 20:50:24 +00:00
|
|
|
import tempfile
|
|
|
|
|
2013-07-25 12:50:59 +00:00
|
|
|
# Import Salt Testing libs
|
2017-02-27 13:58:07 +00:00
|
|
|
from tests.support.unit import skipIf, TestCase
|
|
|
|
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
|
2017-04-10 13:00:57 +00:00
|
|
|
from tests.support.paths import TMP
|
2013-07-25 12:50:59 +00:00
|
|
|
|
|
|
|
# Import salt libs
|
|
|
|
import salt.pillar
|
Use explicit unicode strings + break up salt.utils
This PR is part of what will be an ongoing effort to use explicit
unicode strings in Salt. Because Python 3 does not suport Python 2's raw
unicode string syntax (i.e. `ur'\d+'`), we must use
`salt.utils.locales.sdecode()` to ensure that the raw string is unicode.
However, because of how `salt/utils/__init__.py` has evolved into the
hulking monstrosity it is today, this means importing a large module in
places where it is not needed, which could negatively impact
performance. For this reason, this PR also breaks out some of the
functions from `salt/utils/__init__.py` into new/existing modules under
`salt/utils/`. The long term goal will be that the modules within this
directory do not depend on importing `salt.utils`.
A summary of the changes in this PR is as follows:
* Moves the following functions from `salt.utils` to new locations
(including a deprecation warning if invoked from `salt.utils`):
`to_bytes`, `to_str`, `to_unicode`, `str_to_num`, `is_quoted`,
`dequote`, `is_hex`, `is_bin_str`, `rand_string`,
`contains_whitespace`, `clean_kwargs`, `invalid_kwargs`, `which`,
`which_bin`, `path_join`, `shlex_split`, `rand_str`, `is_windows`,
`is_proxy`, `is_linux`, `is_darwin`, `is_sunos`, `is_smartos`,
`is_smartos_globalzone`, `is_smartos_zone`, `is_freebsd`, `is_netbsd`,
`is_openbsd`, `is_aix`
* Moves the functions already deprecated by @rallytime to the bottom of
`salt/utils/__init__.py` for better organization, so we can keep the
deprecated ones separate from the ones yet to be deprecated as we
continue to break up `salt.utils`
* Updates `salt/*.py` and all files under `salt/client/` to use explicit
unicode string literals.
* Gets rid of implicit imports of `salt.utils` (e.g. `from salt.utils
import foo` becomes `import salt.utils.foo as foo`).
* Renames the `test.rand_str` function to `test.random_hash` to more
accurately reflect what it does
* Modifies `salt.utils.stringutils.random()` (née `salt.utils.rand_string()`)
such that it returns a string matching the passed size. Previously
this function would get `size` bytes from `os.urandom()`,
base64-encode it, and return the result, which would in most cases not
be equal to the passed size.
2017-07-25 01:47:15 +00:00
|
|
|
import salt.utils.stringutils
|
2017-08-21 17:07:51 +00:00
|
|
|
import salt.exceptions
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2013-11-27 12:11:45 +00:00
|
|
|
|
2013-08-26 10:04:26 +00:00
|
|
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
2013-07-25 12:50:59 +00:00
|
|
|
class PillarTestCase(TestCase):
|
|
|
|
|
2017-03-15 19:23:47 +00:00
|
|
|
def tearDown(self):
|
|
|
|
for attrname in ('generic_file', 'generic_minion_file', 'ssh_file', 'ssh_minion_file', 'top_file'):
|
|
|
|
try:
|
|
|
|
delattr(self, attrname)
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
def test_pillarenv_from_saltenv(self):
|
|
|
|
with patch('salt.pillar.compile_template') as compile_template:
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
2017-05-26 05:41:21 +00:00
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
2017-04-10 13:00:57 +00:00
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
grains = {
|
|
|
|
'os': 'Ubuntu',
|
|
|
|
}
|
|
|
|
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'dev')
|
|
|
|
self.assertEqual(pillar.opts['environment'], 'dev')
|
|
|
|
self.assertEqual(pillar.opts['pillarenv'], 'dev')
|
2017-01-18 14:36:55 +00:00
|
|
|
|
2017-08-21 16:50:07 +00:00
|
|
|
def test_ext_pillar_no_extra_minion_data_val_dict(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
mock_ext_pillar_func = MagicMock()
|
|
|
|
with patch('salt.loader.pillars',
|
|
|
|
MagicMock(return_value={'fake_ext_pillar':
|
|
|
|
mock_ext_pillar_func})):
|
|
|
|
pillar = salt.pillar.Pillar(opts, {}, 'mocked-minion', 'dev')
|
|
|
|
# ext pillar function doesn't have the extra_minion_data arg
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=[]))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', {'arg': 'foo'},
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with('mocked-minion',
|
|
|
|
'fake_pillar',
|
|
|
|
arg='foo')
|
|
|
|
# ext pillar function has the extra_minion_data arg
|
|
|
|
mock_ext_pillar_func.reset_mock()
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=['extra_minion_data']))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', {'arg': 'foo'},
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with('mocked-minion',
|
|
|
|
'fake_pillar',
|
|
|
|
arg='foo')
|
|
|
|
|
|
|
|
def test_ext_pillar_no_extra_minion_data_val_list(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
mock_ext_pillar_func = MagicMock()
|
|
|
|
with patch('salt.loader.pillars',
|
|
|
|
MagicMock(return_value={'fake_ext_pillar':
|
|
|
|
mock_ext_pillar_func})):
|
|
|
|
pillar = salt.pillar.Pillar(opts, {}, 'mocked-minion', 'dev')
|
|
|
|
# ext pillar function doesn't have the extra_minion_data arg
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=[]))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', ['foo'],
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with('mocked-minion',
|
|
|
|
'fake_pillar',
|
|
|
|
'foo')
|
|
|
|
# ext pillar function has the extra_minion_data arg
|
|
|
|
mock_ext_pillar_func.reset_mock()
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=['extra_minion_data']))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', ['foo'],
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with('mocked-minion',
|
|
|
|
'fake_pillar',
|
|
|
|
'foo')
|
|
|
|
|
|
|
|
def test_ext_pillar_no_extra_minion_data_val_elem(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
mock_ext_pillar_func = MagicMock()
|
|
|
|
with patch('salt.loader.pillars',
|
|
|
|
MagicMock(return_value={'fake_ext_pillar':
|
|
|
|
mock_ext_pillar_func})):
|
|
|
|
pillar = salt.pillar.Pillar(opts, {}, 'mocked-minion', 'dev')
|
|
|
|
# ext pillar function doesn't have the extra_minion_data arg
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=[]))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', 'fake_val',
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with('mocked-minion',
|
|
|
|
'fake_pillar', 'fake_val')
|
|
|
|
# ext pillar function has the extra_minion_data arg
|
|
|
|
mock_ext_pillar_func.reset_mock()
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=['extra_minion_data']))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', 'fake_val',
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with('mocked-minion',
|
|
|
|
'fake_pillar', 'fake_val')
|
|
|
|
|
|
|
|
def test_ext_pillar_with_extra_minion_data_val_dict(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
mock_ext_pillar_func = MagicMock()
|
|
|
|
with patch('salt.loader.pillars',
|
|
|
|
MagicMock(return_value={'fake_ext_pillar':
|
|
|
|
mock_ext_pillar_func})):
|
|
|
|
pillar = salt.pillar.Pillar(opts, {}, 'mocked-minion', 'dev',
|
|
|
|
extra_minion_data={'fake_key': 'foo'})
|
|
|
|
# ext pillar function doesn't have the extra_minion_data arg
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=[]))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', {'arg': 'foo'},
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with(
|
|
|
|
'mocked-minion', 'fake_pillar', arg='foo')
|
|
|
|
# ext pillar function has the extra_minion_data arg
|
|
|
|
mock_ext_pillar_func.reset_mock()
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=['extra_minion_data']))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', {'arg': 'foo'},
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with(
|
|
|
|
'mocked-minion', 'fake_pillar', arg='foo',
|
|
|
|
extra_minion_data={'fake_key': 'foo'})
|
|
|
|
|
|
|
|
def test_ext_pillar_with_extra_minion_data_val_list(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
mock_ext_pillar_func = MagicMock()
|
|
|
|
with patch('salt.loader.pillars',
|
|
|
|
MagicMock(return_value={'fake_ext_pillar':
|
|
|
|
mock_ext_pillar_func})):
|
|
|
|
pillar = salt.pillar.Pillar(opts, {}, 'mocked-minion', 'dev',
|
|
|
|
extra_minion_data={'fake_key': 'foo'})
|
|
|
|
# ext pillar function doesn't have the extra_minion_data arg
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=[]))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', ['bar'],
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with(
|
|
|
|
'mocked-minion', 'fake_pillar', 'bar')
|
|
|
|
# ext pillar function has the extra_minion_data arg
|
|
|
|
mock_ext_pillar_func.reset_mock()
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=['extra_minion_data']))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', ['bar'],
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with(
|
|
|
|
'mocked-minion', 'fake_pillar', 'bar',
|
|
|
|
extra_minion_data={'fake_key': 'foo'})
|
|
|
|
|
|
|
|
def test_ext_pillar_with_extra_minion_data_val_elem(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'file_roots': {
|
|
|
|
'dev': [],
|
|
|
|
'base': []
|
|
|
|
},
|
|
|
|
'extension_modules': '',
|
|
|
|
'pillarenv_from_saltenv': True
|
|
|
|
}
|
|
|
|
mock_ext_pillar_func = MagicMock()
|
|
|
|
with patch('salt.loader.pillars',
|
|
|
|
MagicMock(return_value={'fake_ext_pillar':
|
|
|
|
mock_ext_pillar_func})):
|
|
|
|
pillar = salt.pillar.Pillar(opts, {}, 'mocked-minion', 'dev',
|
|
|
|
extra_minion_data={'fake_key': 'foo'})
|
|
|
|
# ext pillar function doesn't have the extra_minion_data arg
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=[]))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', 'bar',
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with(
|
|
|
|
'mocked-minion', 'fake_pillar', 'bar')
|
|
|
|
# ext pillar function has the extra_minion_data arg
|
|
|
|
mock_ext_pillar_func.reset_mock()
|
2017-09-06 16:09:54 +00:00
|
|
|
with patch('salt.utils.args.get_function_argspec',
|
2017-08-21 16:50:07 +00:00
|
|
|
MagicMock(return_value=MagicMock(args=['extra_minion_data']))):
|
|
|
|
pillar._external_pillar_data('fake_pillar', 'bar',
|
2017-08-25 10:36:25 +00:00
|
|
|
'fake_ext_pillar')
|
2017-08-21 16:50:07 +00:00
|
|
|
mock_ext_pillar_func.assert_called_once_with(
|
|
|
|
'mocked-minion', 'fake_pillar', 'bar',
|
|
|
|
extra_minion_data={'fake_key': 'foo'})
|
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
def test_malformed_pillar_sls(self):
|
|
|
|
with patch('salt.pillar.compile_template') as compile_template:
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': [],
|
|
|
|
'file_roots': [],
|
|
|
|
'extension_modules': ''
|
|
|
|
}
|
|
|
|
grains = {
|
|
|
|
'os': 'Ubuntu',
|
|
|
|
'os_family': 'Debian',
|
|
|
|
'oscodename': 'raring',
|
|
|
|
'osfullname': 'Ubuntu',
|
|
|
|
'osrelease': '13.04',
|
|
|
|
'kernel': 'Linux'
|
2013-07-25 12:50:59 +00:00
|
|
|
}
|
2017-04-10 13:00:57 +00:00
|
|
|
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
|
|
|
|
# Mock getting the proper template files
|
|
|
|
pillar.client.get_state = MagicMock(
|
|
|
|
return_value={
|
|
|
|
'dest': '/path/to/pillar/files/foo.sls',
|
|
|
|
'source': 'salt://foo.sls'
|
|
|
|
}
|
|
|
|
)
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Template compilation returned a string
|
|
|
|
compile_template.return_value = 'BAHHH'
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({}, ['SLS \'foo.sls\' does not render to a dictionary'])
|
|
|
|
)
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Template compilation returned a list
|
|
|
|
compile_template.return_value = ['BAHHH']
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({}, ['SLS \'foo.sls\' does not render to a dictionary'])
|
|
|
|
)
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Template compilation returned a dictionary, which is what's expected
|
|
|
|
compile_template.return_value = {'foo': 'bar'}
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar'}, [])
|
|
|
|
)
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Test improper includes
|
|
|
|
compile_template.side_effect = [
|
|
|
|
{'foo': 'bar', 'include': 'blah'},
|
|
|
|
{'foo2': 'bar2'}
|
|
|
|
]
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar', 'include': 'blah'},
|
|
|
|
["Include Declaration in SLS 'foo.sls' is not formed as a list"])
|
|
|
|
)
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Test includes as a list, which is what's expected
|
|
|
|
compile_template.side_effect = [
|
|
|
|
{'foo': 'bar', 'include': ['blah']},
|
|
|
|
{'foo2': 'bar2'}
|
|
|
|
]
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar', 'foo2': 'bar2'}, [])
|
|
|
|
)
|
2013-07-25 12:50:59 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Test includes as a list overriding data
|
|
|
|
compile_template.side_effect = [
|
|
|
|
{'foo': 'bar', 'include': ['blah']},
|
|
|
|
{'foo': 'bar2'}
|
|
|
|
]
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar2'}, [])
|
|
|
|
)
|
2013-08-07 20:50:24 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Test includes using empty key directive
|
|
|
|
compile_template.side_effect = [
|
|
|
|
{'foo': 'bar', 'include': [{'blah': {'key': ''}}]},
|
|
|
|
{'foo': 'bar2'}
|
|
|
|
]
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar2'}, [])
|
|
|
|
)
|
2017-03-27 23:17:33 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Test includes using simple non-nested key
|
|
|
|
compile_template.side_effect = [
|
|
|
|
{'foo': 'bar', 'include': [{'blah': {'key': 'nested'}}]},
|
|
|
|
{'foo': 'bar2'}
|
|
|
|
]
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar', 'nested': {'foo': 'bar2'}}, [])
|
|
|
|
)
|
2017-03-27 23:17:33 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
# Test includes using nested key
|
|
|
|
compile_template.side_effect = [
|
|
|
|
{'foo': 'bar', 'include': [{'blah': {'key': 'nested:level'}}]},
|
|
|
|
{'foo': 'bar2'}
|
|
|
|
]
|
|
|
|
self.assertEqual(
|
|
|
|
pillar.render_pillar({'base': ['foo.sls']}),
|
|
|
|
({'foo': 'bar', 'nested': {'level': {'foo': 'bar2'}}}, [])
|
|
|
|
)
|
2017-03-27 23:17:33 +00:00
|
|
|
|
2017-04-10 13:00:57 +00:00
|
|
|
def test_topfile_order(self):
|
|
|
|
with patch('salt.pillar.salt.fileclient.get_file_client', autospec=True) as get_file_client, \
|
|
|
|
patch('salt.pillar.salt.minion.Matcher') as Matcher: # autospec=True disabled due to py3 mock bug
|
|
|
|
opts = {
|
|
|
|
'renderer': 'yaml',
|
|
|
|
'renderer_blacklist': [],
|
|
|
|
'renderer_whitelist': [],
|
|
|
|
'state_top': '',
|
|
|
|
'pillar_roots': [],
|
|
|
|
'extension_modules': '',
|
|
|
|
'environment': 'base',
|
|
|
|
'file_roots': [],
|
|
|
|
}
|
|
|
|
grains = {
|
|
|
|
'os': 'Ubuntu',
|
|
|
|
'os_family': 'Debian',
|
|
|
|
'oscodename': 'raring',
|
|
|
|
'osfullname': 'Ubuntu',
|
|
|
|
'osrelease': '13.04',
|
|
|
|
'kernel': 'Linux'
|
|
|
|
}
|
|
|
|
# glob match takes precedence
|
|
|
|
self._setup_test_topfile_mocks(Matcher, get_file_client, 1, 2)
|
|
|
|
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
|
|
|
|
self.assertEqual(pillar.compile_pillar()['ssh'], 'bar')
|
|
|
|
# nodegroup match takes precedence
|
|
|
|
self._setup_test_topfile_mocks(Matcher, get_file_client, 2, 1)
|
|
|
|
pillar = salt.pillar.Pillar(opts, grains, 'mocked-minion', 'base')
|
|
|
|
self.assertEqual(pillar.compile_pillar()['ssh'], 'foo')
|
2013-08-07 20:50:24 +00:00
|
|
|
|
|
|
|
def _setup_test_topfile_mocks(self, Matcher, get_file_client,
|
2017-01-18 14:36:55 +00:00
|
|
|
nodegroup_order, glob_order):
|
2013-08-07 20:50:24 +00:00
|
|
|
# Write a simple topfile and two pillar state files
|
2017-04-10 13:00:57 +00:00
|
|
|
self.top_file = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-06-11 16:33:00 +00:00
|
|
|
s = '''
|
2013-08-07 20:50:24 +00:00
|
|
|
base:
|
|
|
|
group:
|
|
|
|
- match: nodegroup
|
|
|
|
- order: {nodegroup_order}
|
|
|
|
- ssh
|
2015-10-28 12:56:44 +00:00
|
|
|
- generic
|
|
|
|
'*':
|
|
|
|
- generic
|
2013-08-07 20:50:24 +00:00
|
|
|
minion:
|
|
|
|
- order: {glob_order}
|
|
|
|
- ssh.minion
|
2015-10-28 12:56:44 +00:00
|
|
|
- generic.minion
|
2015-06-11 16:33:00 +00:00
|
|
|
'''.format(nodegroup_order=nodegroup_order, glob_order=glob_order)
|
Use explicit unicode strings + break up salt.utils
This PR is part of what will be an ongoing effort to use explicit
unicode strings in Salt. Because Python 3 does not suport Python 2's raw
unicode string syntax (i.e. `ur'\d+'`), we must use
`salt.utils.locales.sdecode()` to ensure that the raw string is unicode.
However, because of how `salt/utils/__init__.py` has evolved into the
hulking monstrosity it is today, this means importing a large module in
places where it is not needed, which could negatively impact
performance. For this reason, this PR also breaks out some of the
functions from `salt/utils/__init__.py` into new/existing modules under
`salt/utils/`. The long term goal will be that the modules within this
directory do not depend on importing `salt.utils`.
A summary of the changes in this PR is as follows:
* Moves the following functions from `salt.utils` to new locations
(including a deprecation warning if invoked from `salt.utils`):
`to_bytes`, `to_str`, `to_unicode`, `str_to_num`, `is_quoted`,
`dequote`, `is_hex`, `is_bin_str`, `rand_string`,
`contains_whitespace`, `clean_kwargs`, `invalid_kwargs`, `which`,
`which_bin`, `path_join`, `shlex_split`, `rand_str`, `is_windows`,
`is_proxy`, `is_linux`, `is_darwin`, `is_sunos`, `is_smartos`,
`is_smartos_globalzone`, `is_smartos_zone`, `is_freebsd`, `is_netbsd`,
`is_openbsd`, `is_aix`
* Moves the functions already deprecated by @rallytime to the bottom of
`salt/utils/__init__.py` for better organization, so we can keep the
deprecated ones separate from the ones yet to be deprecated as we
continue to break up `salt.utils`
* Updates `salt/*.py` and all files under `salt/client/` to use explicit
unicode string literals.
* Gets rid of implicit imports of `salt.utils` (e.g. `from salt.utils
import foo` becomes `import salt.utils.foo as foo`).
* Renames the `test.rand_str` function to `test.random_hash` to more
accurately reflect what it does
* Modifies `salt.utils.stringutils.random()` (née `salt.utils.rand_string()`)
such that it returns a string matching the passed size. Previously
this function would get `size` bytes from `os.urandom()`,
base64-encode it, and return the result, which would in most cases not
be equal to the passed size.
2017-07-25 01:47:15 +00:00
|
|
|
self.top_file.write(salt.utils.stringutils.to_bytes(s))
|
2013-08-07 20:50:24 +00:00
|
|
|
self.top_file.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.ssh_file = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-06-11 16:33:00 +00:00
|
|
|
self.ssh_file.write(b'''
|
2013-08-07 20:50:24 +00:00
|
|
|
ssh:
|
|
|
|
foo
|
|
|
|
''')
|
|
|
|
self.ssh_file.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.ssh_minion_file = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-06-11 16:33:00 +00:00
|
|
|
self.ssh_minion_file.write(b'''
|
2013-08-07 20:50:24 +00:00
|
|
|
ssh:
|
|
|
|
bar
|
|
|
|
''')
|
|
|
|
self.ssh_minion_file.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.generic_file = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-10-28 12:56:44 +00:00
|
|
|
self.generic_file.write(b'''
|
|
|
|
generic:
|
|
|
|
key1:
|
|
|
|
- value1
|
|
|
|
- value2
|
|
|
|
key2:
|
|
|
|
sub_key1: []
|
|
|
|
''')
|
|
|
|
self.generic_file.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.generic_minion_file = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-10-28 12:56:44 +00:00
|
|
|
self.generic_minion_file.write(b'''
|
|
|
|
generic:
|
|
|
|
key1:
|
|
|
|
- value3
|
|
|
|
key2:
|
|
|
|
sub_key2: []
|
|
|
|
''')
|
|
|
|
self.generic_minion_file.flush()
|
2013-08-07 20:50:24 +00:00
|
|
|
|
|
|
|
# Setup Matcher mock
|
|
|
|
matcher = Matcher.return_value
|
|
|
|
matcher.confirm_top.return_value = True
|
|
|
|
|
|
|
|
# Setup fileclient mock
|
|
|
|
client = get_file_client.return_value
|
|
|
|
client.cache_file.return_value = self.top_file.name
|
|
|
|
|
|
|
|
def get_state(sls, env):
|
|
|
|
return {
|
|
|
|
'ssh': {'path': '', 'dest': self.ssh_file.name},
|
2013-08-26 10:04:26 +00:00
|
|
|
'ssh.minion': {'path': '', 'dest': self.ssh_minion_file.name},
|
2015-10-28 12:56:44 +00:00
|
|
|
'generic': {'path': '', 'dest': self.generic_file.name},
|
|
|
|
'generic.minion': {'path': '', 'dest': self.generic_minion_file.name},
|
2013-08-07 20:50:24 +00:00
|
|
|
}[sls]
|
|
|
|
|
|
|
|
client.get_state.side_effect = get_state
|
2014-03-31 01:59:04 +00:00
|
|
|
|
2015-10-28 20:45:26 +00:00
|
|
|
def _setup_test_include_mocks(self, Matcher, get_file_client):
|
2017-04-10 13:00:57 +00:00
|
|
|
self.top_file = top_file = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-10-28 20:45:26 +00:00
|
|
|
top_file.write(b'''
|
|
|
|
base:
|
|
|
|
'*':
|
|
|
|
- order: 1
|
|
|
|
- test.sub2
|
|
|
|
minion:
|
|
|
|
- order: 2
|
|
|
|
- test
|
|
|
|
''')
|
|
|
|
top_file.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.init_sls = init_sls = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-10-28 20:45:26 +00:00
|
|
|
init_sls.write(b'''
|
|
|
|
include:
|
|
|
|
- test.sub1
|
|
|
|
- test.sub2
|
|
|
|
''')
|
|
|
|
init_sls.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.sub1_sls = sub1_sls = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-10-28 20:45:26 +00:00
|
|
|
sub1_sls.write(b'''
|
|
|
|
p1:
|
|
|
|
- value1_1
|
|
|
|
- value1_2
|
|
|
|
''')
|
|
|
|
sub1_sls.flush()
|
2017-04-10 13:00:57 +00:00
|
|
|
self.sub2_sls = sub2_sls = tempfile.NamedTemporaryFile(dir=TMP)
|
2015-10-28 20:45:26 +00:00
|
|
|
sub2_sls.write(b'''
|
|
|
|
p1:
|
|
|
|
- value1_3
|
|
|
|
p2:
|
|
|
|
- value2_1
|
|
|
|
- value2_2
|
|
|
|
''')
|
|
|
|
sub2_sls.flush()
|
|
|
|
|
|
|
|
# Setup Matcher mock
|
|
|
|
matcher = Matcher.return_value
|
|
|
|
matcher.confirm_top.return_value = True
|
|
|
|
|
|
|
|
# Setup fileclient mock
|
|
|
|
client = get_file_client.return_value
|
|
|
|
client.cache_file.return_value = self.top_file.name
|
|
|
|
|
|
|
|
def get_state(sls, env):
|
|
|
|
return {
|
|
|
|
'test': {'path': '', 'dest': init_sls.name},
|
|
|
|
'test.sub1': {'path': '', 'dest': sub1_sls.name},
|
|
|
|
'test.sub2': {'path': '', 'dest': sub2_sls.name},
|
|
|
|
}[sls]
|
|
|
|
|
|
|
|
client.get_state.side_effect = get_state
|
2017-08-21 17:07:51 +00:00
|
|
|
|
|
|
|
|
|
|
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
|
|
|
@patch('salt.transport.Channel.factory', MagicMock())
|
|
|
|
class RemotePillarTestCase(TestCase):
|
|
|
|
'''
|
|
|
|
Tests for instantiating a RemotePillar in salt.pillar
|
|
|
|
'''
|
|
|
|
def setUp(self):
|
|
|
|
self.grains = {}
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
for attr in ('grains',):
|
|
|
|
try:
|
|
|
|
delattr(self, attr)
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
def test_get_opts_in_pillar_override_call(self):
|
|
|
|
mock_get_extra_minion_data = MagicMock(return_value={})
|
|
|
|
with patch(
|
|
|
|
'salt.pillar.RemotePillarMixin.get_ext_pillar_extra_minion_data',
|
|
|
|
mock_get_extra_minion_data):
|
|
|
|
|
|
|
|
salt.pillar.RemotePillar({}, self.grains, 'mocked-minion', 'dev')
|
|
|
|
mock_get_extra_minion_data.assert_called_once_with(
|
|
|
|
{'environment': 'dev'})
|
|
|
|
|
|
|
|
def test_multiple_keys_in_opts_added_to_pillar(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': ['path_to_add', 'path_to_add2']
|
|
|
|
}
|
|
|
|
pillar = salt.pillar.RemotePillar(opts, self.grains,
|
|
|
|
'mocked-minion', 'dev')
|
|
|
|
self.assertEqual(pillar.extra_minion_data,
|
|
|
|
{'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data2': ['fake_data3',
|
|
|
|
'fake_data4']}})
|
|
|
|
|
|
|
|
def test_subkey_in_opts_added_to_pillar(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data5': 'fake_data6',
|
|
|
|
'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': ['path_to_add2:fake_data5']
|
|
|
|
}
|
|
|
|
pillar = salt.pillar.RemotePillar(opts, self.grains,
|
|
|
|
'mocked-minion', 'dev')
|
|
|
|
self.assertEqual(pillar.extra_minion_data,
|
|
|
|
{'path_to_add2': {'fake_data5': 'fake_data6'}})
|
|
|
|
|
|
|
|
def test_non_existent_leaf_opt_in_add_to_pillar(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data5': 'fake_data6',
|
|
|
|
'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': ['path_to_add2:fake_data_non_exist']
|
|
|
|
}
|
|
|
|
pillar = salt.pillar.RemotePillar(opts, self.grains,
|
|
|
|
'mocked-minion', 'dev')
|
|
|
|
self.assertEqual(pillar.pillar_override, {})
|
|
|
|
|
|
|
|
def test_non_existent_intermediate_opt_in_add_to_pillar(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data5': 'fake_data6',
|
|
|
|
'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': ['path_to_add_no_exist']
|
|
|
|
}
|
|
|
|
pillar = salt.pillar.RemotePillar(opts, self.grains,
|
|
|
|
'mocked-minion', 'dev')
|
|
|
|
self.assertEqual(pillar.pillar_override, {})
|
|
|
|
|
|
|
|
def test_malformed_add_to_pillar(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data5': 'fake_data6',
|
|
|
|
'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': MagicMock()
|
|
|
|
}
|
|
|
|
with self.assertRaises(salt.exceptions.SaltClientError) as excinfo:
|
|
|
|
salt.pillar.RemotePillar(opts, self.grains, 'mocked-minion', 'dev')
|
|
|
|
self.assertEqual(excinfo.exception.strerror,
|
|
|
|
'\'pass_to_ext_pillars\' config is malformed.')
|
|
|
|
|
|
|
|
def test_pillar_send_extra_minion_data_from_config(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'pillarenv': 'fake_pillar_env',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data5': 'fake_data6',
|
|
|
|
'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': ['path_to_add']}
|
|
|
|
mock_channel = MagicMock(
|
|
|
|
crypted_transfer_decode_dictentry=MagicMock(return_value={}))
|
|
|
|
with patch('salt.transport.Channel.factory',
|
|
|
|
MagicMock(return_value=mock_channel)):
|
|
|
|
pillar = salt.pillar.RemotePillar(opts, self.grains,
|
|
|
|
'mocked_minion', 'fake_env')
|
|
|
|
|
|
|
|
ret = pillar.compile_pillar()
|
|
|
|
self.assertEqual(pillar.channel, mock_channel)
|
|
|
|
mock_channel.crypted_transfer_decode_dictentry.assert_called_once_with(
|
|
|
|
{'cmd': '_pillar', 'ver': '2',
|
|
|
|
'id': 'mocked_minion',
|
|
|
|
'grains': {},
|
|
|
|
'saltenv': 'fake_env',
|
|
|
|
'pillarenv': 'fake_pillar_env',
|
|
|
|
'pillar_override': {},
|
|
|
|
'extra_minion_data': {'path_to_add': 'fake_data'}},
|
|
|
|
dictkey='pillar')
|
2017-08-21 17:08:15 +00:00
|
|
|
|
|
|
|
|
|
|
|
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
|
|
|
@patch('salt.transport.client.AsyncReqChannel.factory', MagicMock())
|
|
|
|
class AsyncRemotePillarTestCase(TestCase):
|
|
|
|
'''
|
|
|
|
Tests for instantiating a AsyncRemotePillar in salt.pillar
|
|
|
|
'''
|
|
|
|
def setUp(self):
|
|
|
|
self.grains = {}
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
for attr in ('grains',):
|
|
|
|
try:
|
|
|
|
delattr(self, attr)
|
|
|
|
except AttributeError:
|
|
|
|
continue
|
|
|
|
|
|
|
|
def test_get_opts_in_pillar_override_call(self):
|
|
|
|
mock_get_extra_minion_data = MagicMock(return_value={})
|
|
|
|
with patch(
|
|
|
|
'salt.pillar.RemotePillarMixin.get_ext_pillar_extra_minion_data',
|
|
|
|
mock_get_extra_minion_data):
|
|
|
|
|
|
|
|
salt.pillar.RemotePillar({}, self.grains, 'mocked-minion', 'dev')
|
|
|
|
mock_get_extra_minion_data.assert_called_once_with(
|
|
|
|
{'environment': 'dev'})
|
|
|
|
|
|
|
|
def test_pillar_send_extra_minion_data_from_config(self):
|
|
|
|
opts = {
|
|
|
|
'renderer': 'json',
|
|
|
|
'pillarenv': 'fake_pillar_env',
|
|
|
|
'path_to_add': 'fake_data',
|
|
|
|
'path_to_add2': {'fake_data5': 'fake_data6',
|
|
|
|
'fake_data2': ['fake_data3', 'fake_data4']},
|
|
|
|
'pass_to_ext_pillars': ['path_to_add']}
|
|
|
|
mock_channel = MagicMock(
|
|
|
|
crypted_transfer_decode_dictentry=MagicMock(return_value={}))
|
|
|
|
with patch('salt.transport.client.AsyncReqChannel.factory',
|
|
|
|
MagicMock(return_value=mock_channel)):
|
|
|
|
pillar = salt.pillar.RemotePillar(opts, self.grains,
|
|
|
|
'mocked_minion', 'fake_env')
|
|
|
|
|
|
|
|
ret = pillar.compile_pillar()
|
|
|
|
mock_channel.crypted_transfer_decode_dictentry.assert_called_once_with(
|
|
|
|
{'cmd': '_pillar', 'ver': '2',
|
|
|
|
'id': 'mocked_minion',
|
|
|
|
'grains': {},
|
|
|
|
'saltenv': 'fake_env',
|
|
|
|
'pillarenv': 'fake_pillar_env',
|
|
|
|
'pillar_override': {},
|
|
|
|
'extra_minion_data': {'path_to_add': 'fake_data'}},
|
|
|
|
dictkey='pillar')
|