Merge branch '2017.7' into 'oxygen'

Conflicts:
  - pkg/windows/req_base.txt
  - salt/engines/slack.py
  - salt/modules/cmdmod.py
  - salt/modules/osquery.py
  - salt/modules/pip.py
  - salt/modules/win_pkg.py
  - salt/states/pip_state.py
  - tests/integration/modules/test_cmdmod.py
This commit is contained in:
rallytime 2017-12-30 08:44:11 -05:00
commit da1e33abea
No known key found for this signature in database
GPG key ID: E8F1A4B90D0DEA19
16 changed files with 469 additions and 35 deletions

View file

@ -48,7 +48,6 @@ from salt.ext.six.moves import range # pylint: disable=import-error,redefined-b
# Import 3rd-party libs
from salt.ext import six
LIBPAM = CDLL(find_library('pam'))
LIBC = CDLL(find_library('c'))
CALLOC = LIBC.calloc
@ -121,6 +120,7 @@ class PamConv(Structure):
try:
LIBPAM = CDLL(find_library('pam'))
PAM_START = LIBPAM.pam_start
PAM_START.restype = c_int
PAM_START.argtypes = [c_char_p, c_char_p, POINTER(PamConv),

View file

@ -2275,6 +2275,9 @@ def script(source,
if not salt.utils.platform.is_windows():
os.chmod(path, 320)
os.chown(path, __salt__['file.user_to_uid'](runas), -1)
path = _cmd_quote(path)
ret = _run(path + ' ' + six.text_type(args) if args else path,
cwd=cwd,
stdin=stdin,

View file

@ -37,7 +37,7 @@ def _table_attrs(table):
'''
Helper function to find valid table attributes
'''
cmd = ['osqueryi'] + ['--json'] + ['pragma table_info{0}'.format(table)]
cmd = ['osqueryi'] + ['--json'] + ['pragma table_info({0})'.format(table)]
res = __salt__['cmd.run_all'](cmd)
if res['retcode'] == 0:
attrs = []
@ -63,6 +63,7 @@ def _osquery(sql, format='json'):
ret['error'] = res['stderr']
else:
ret['data'] = salt.utils.json.loads(res['stdout'])
log.debug('== {} =='.format(ret))
return ret
@ -120,9 +121,13 @@ def version():
salt '*' osquery.version
'''
_false_return = {'result': False,
'comment': 'OSQuery version unavailable.'}
res = _osquery_cmd(table='osquery_info', attrs=['version'])
if res and isinstance(res, list):
return res[0].get('version', '') or False
if 'result' in res and res['result']:
if 'data' in res and isinstance(res['data'], list):
return res['data'][0].get('version', '') or _false_return
return _false_return
def rpm_packages(attrs=None, where=None):
@ -137,7 +142,8 @@ def rpm_packages(attrs=None, where=None):
'''
if __grains__['os_family'] == 'RedHat':
return _osquery_cmd(table='rpm_packages', attrs=attrs, where=where)
return {'result': False, 'comment': 'Only available on Red Hat based systems.'}
return {'result': False,
'comment': 'Only available on Red Hat based systems.'}
def kernel_integrity(attrs=None, where=None):

View file

@ -83,6 +83,7 @@ import shutil
import logging
import sys
import tempfile
from pkg_resources import parse_version
# Import Salt libs
import salt.utils.data
@ -1288,3 +1289,79 @@ def upgrade(bin_env=None,
ret['changes'] = salt.utils.data.compare_dicts(old, new)
return ret
def list_all_versions(pkg,
bin_env=None,
include_alpha=False,
include_beta=False,
include_rc=False,
user=None,
cwd=None):
'''
.. versionadded:: 2017.7.3
List all available versions of a pip package
pkg
The package to check
bin_env
Path to pip bin or path to virtualenv. If doing a system install,
and want to use a specific pip bin (pip-2.7, pip-2.6, etc..) just
specify the pip bin you want.
include_alpha
Include alpha versions in the list
include_beta
Include beta versions in the list
include_rc
Include release candidates versions in the list
user
The user under which to run pip
cwd
Current working directory to run pip from
CLI Example:
.. code-block:: bash
salt '*' pip.list_all_versions <package name>
'''
pip_bin = _get_pip_bin(bin_env)
cmd = [pip_bin, 'install', '{0}==versions'.format(pkg)]
cmd_kwargs = dict(cwd=cwd, runas=user, output_loglevel='quiet', redirect_stderr=True)
if bin_env and os.path.isdir(bin_env):
cmd_kwargs['env'] = {'VIRTUAL_ENV': bin_env}
result = __salt__['cmd.run_all'](cmd, **cmd_kwargs)
filtered = []
if not include_alpha:
filtered.append('a')
if not include_beta:
filtered.append('b')
if not include_rc:
filtered.append('rc')
if filtered:
excludes = re.compile(r'^((?!{0}).)*$'.format('|'.join(filtered)))
else:
excludes = re.compile(r'')
versions = []
for line in result['stdout'].splitlines():
match = re.search(r'\s*Could not find a version.* \(from versions: (.*)\)', line)
if match:
versions = [v for v in match.group(1).split(', ') if v and excludes.match(v)]
versions.sort(key=parse_version)
break
if not versions:
return None
return versions

View file

@ -233,13 +233,13 @@ def list_keys(hive, key=None, use_32bit_registry=False):
for i in range(win32api.RegQueryInfoKey(handle)[0]):
subkey = win32api.RegEnumKey(handle, i)
if PY2:
subkeys.append(_to_unicode(subkey))
subkeys.append(_to_mbcs(subkey))
else:
subkeys.append(subkey)
handle.Close()
except WindowsError as exc: # pylint: disable=E0602
except pywintypes.error as exc: # pylint: disable=E0602
log.debug(exc)
log.debug('Cannot find key: {0}\\{1}'.format(hive, key))
return False, 'Cannot find key: {0}\\{1}'.format(hive, key)
@ -299,7 +299,7 @@ def list_values(hive, key=None, use_32bit_registry=False, include_default=True):
'vtype': registry.vtype_reverse[vtype],
'success': True}
values.append(value)
except WindowsError as exc: # pylint: disable=E0602
except pywintypes.error as exc: # pylint: disable=E0602
log.debug(exc)
log.debug(r'Cannot find key: {0}\{1}'.format(hive, key))
return False, r'Cannot find key: {0}\{1}'.format(hive, key)

View file

@ -98,7 +98,10 @@ def _find_utmp():
result[os.stat(utmp).st_mtime] = utmp
except Exception:
pass
return result[sorted(result).pop()]
if result > 0:
return result[sorted(result).pop()]
else:
return False
def _default_runlevel():
@ -149,12 +152,14 @@ def _runlevel():
'''
if 'upstart._runlevel' in __context__:
return __context__['upstart._runlevel']
out = __salt__['cmd.run'](['runlevel', '{0}'.format(_find_utmp())], python_shell=False)
try:
ret = out.split()[1]
except IndexError:
# The runlevel is unknown, return the default
ret = _default_runlevel()
ret = _default_runlevel()
utmp = _find_utmp()
if utmp:
out = __salt__['cmd.run'](['runlevel', '{0}'.format(utmp)], python_shell=False)
try:
ret = out.split()[1]
except IndexError:
pass
__context__['upstart._runlevel'] = ret
return ret

View file

@ -157,9 +157,9 @@ def latest_version(*names, **kwargs):
# check, whether latest available version
# is newer than latest installed version
if compare_versions(ver1=str(latest_available),
oper='>',
ver2=str(latest_installed)):
if compare_versions(ver1=six.text_type(latest_available),
oper=six.text_type('>'),
ver2=six.text_type(latest_installed)):
log.debug('Upgrade of {0} from {1} to {2} '
'is available'.format(name,
latest_installed,
@ -469,7 +469,7 @@ def _get_reg_software():
if d_name not in ignore_list:
# some MS Office updates don't register a product name which means
# their information is useless
reg_software.update({d_name: str(d_vers)})
reg_software.update({d_name: six.text_type(d_vers)})
for reg_key in __salt__['reg.list_keys'](hive, key):
update(hive, key, reg_key, False)
@ -878,7 +878,7 @@ def _get_source_sum(source_hash, file_path, saltenv):
schemes = ('salt', 'http', 'https', 'ftp', 'swift', 's3', 'file')
invalid_hash_msg = ("Source hash '{0}' format is invalid. It must be in "
"the format <hash type>=<hash>").format(source_hash)
source_hash = str(source_hash)
source_hash = six.text_type(source_hash)
source_hash_scheme = _urlparse(source_hash).scheme
if source_hash_scheme in schemes:
@ -1128,7 +1128,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
# as a float it must be converted to a string in order for
# string matching to work.
if not isinstance(version_num, six.string_types) and version_num is not None:
version_num = str(version_num)
version_num = six.text_type(version_num)
if not version_num:
# following can be version number or latest
@ -1347,6 +1347,10 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
__salt__['system.set_reboot_required_witnessed']()
ret[pkg_name] = {'install status': 'success, reboot required'}
changed.append(pkg_name)
elif result['retcode'] == 1641:
# 1641 is ERROR_SUCCESS_REBOOT_INITIATED
ret[pkg_name] = {'install status': 'success, reboot initiated'}
changed.append(pkg_name)
else:
log.error('Failed to install {0}'.format(pkg_name))
log.error('retcode {0}'.format(result['retcode']))
@ -1485,7 +1489,7 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
# as a float it must be converted to a string in order for
# string matching to work.
if not isinstance(version_num, six.string_types) and version_num is not None:
version_num = str(version_num)
version_num = six.text_type(version_num)
if version_num not in pkginfo and 'latest' in pkginfo:
version_num = 'latest'
elif 'latest' in pkginfo:
@ -1639,6 +1643,18 @@ def remove(name=None, pkgs=None, version=None, **kwargs):
if not result['retcode']:
ret[pkgname] = {'uninstall status': 'success'}
changed.append(pkgname)
elif result['retcode'] == 3010:
# 3010 is ERROR_SUCCESS_REBOOT_REQUIRED
report_reboot_exit_codes = kwargs.pop(
'report_reboot_exit_codes', True)
if report_reboot_exit_codes:
__salt__['system.set_reboot_required_witnessed']()
ret[pkgname] = {'uninstall status': 'success, reboot required'}
changed.append(pkgname)
elif result['retcode'] == 1641:
# 1641 is ERROR_SUCCESS_REBOOT_INITIATED
ret[pkgname] = {'uninstall status': 'success, reboot initiated'}
changed.append(pkgname)
else:
log.error('Failed to remove %s', pkgname)
log.error('retcode %s', result['retcode'])
@ -1826,9 +1842,9 @@ def compare_versions(ver1='', oper='==', ver2=''):
# Support version being the special meaning of 'latest'
if ver1 == 'latest':
ver1 = str(sys.maxsize)
ver1 = six.text_type(sys.maxsize)
if ver2 == 'latest':
ver2 = str(sys.maxsize)
ver2 = six.text_type(sys.maxsize)
# Support version being the special meaning of 'Not Found'
if ver1 == 'Not Found':
ver1 = '0.0.0.0.0'

View file

@ -75,6 +75,9 @@ def generate_token(minion_id, signature, impersonated_by_master=False):
'metadata': audit_data
}
if payload['policies'] == []:
return {'error': 'No policies matched minion'}
log.trace('Sending token creation request to Vault')
response = requests.post(url, headers=headers, json=payload, verify=verify)

View file

@ -23,6 +23,7 @@ requisite to a pkg.installed state for the package which provides pip
from __future__ import absolute_import
import re
import logging
from pkg_resources import parse_version
# Import salt libs
import salt.utils.versions
@ -94,7 +95,7 @@ def _fulfills_version_spec(version, version_spec):
for oper, spec in version_spec:
if oper is None:
continue
if not salt.utils.versions.compare(ver1=version, oper=oper, ver2=spec):
if not salt.utils.versions.compare(ver1=version, oper=oper, ver2=spec, cmp_func=_pep440_version_cmp):
return False
return True
@ -212,10 +213,70 @@ def _check_if_installed(prefix, state_pkg_name, version_spec,
ret['comment'] = ('Python package {0} was already '
'installed'.format(state_pkg_name))
return ret
if force_reinstall is False and upgrade:
# Check desired version (if any) against currently-installed
include_alpha = False
include_beta = False
include_rc = False
if any(version_spec):
for spec in version_spec:
if 'a' in spec[1]:
include_alpha = True
if 'b' in spec[1]:
include_beta = True
if 'rc' in spec[1]:
include_rc = True
available_versions = __salt__['pip.list_all_versions'](
prefix_realname, bin_env=bin_env, include_alpha=include_alpha,
include_beta=include_beta, include_rc=include_rc, user=user,
cwd=cwd)
desired_version = ''
if any(version_spec):
for version in reversed(available_versions):
if _fulfills_version_spec(version, version_spec):
desired_version = version
break
else:
desired_version = available_versions[-1]
if not desired_version:
ret['result'] = True
ret['comment'] = ('Python package {0} was already '
'installed and\nthe available upgrade '
'doesn\'t fulfills the version '
'requirements'.format(prefix_realname))
return ret
if _pep440_version_cmp(pip_list[prefix_realname], desired_version) == 0:
ret['result'] = True
ret['comment'] = ('Python package {0} was already '
'installed'.format(state_pkg_name))
return ret
return ret
def _pep440_version_cmp(pkg1, pkg2, ignore_epoch=False):
'''
Compares two version strings using pkg_resources.parse_version.
Return -1 if version1 < version2, 0 if version1 ==version2,
and 1 if version1 > version2. Return None if there was a problem
making the comparison.
'''
normalize = lambda x: str(x).split('!', 1)[-1] if ignore_epoch else str(x)
pkg1 = normalize(pkg1)
pkg2 = normalize(pkg2)
try:
if parse_version(pkg1) < parse_version(pkg2):
return -1
if parse_version(pkg1) == parse_version(pkg2):
return 0
if parse_version(pkg1) > parse_version(pkg2):
return 1
except Exception as exc:
logger.exception(exc)
return None
def installed(name,
pkgs=None,
pip_bin=None,

View file

@ -463,6 +463,8 @@ def query(url,
'charset' in res_params and \
not isinstance(result_text, six.text_type):
result_text = result_text.decode(res_params['charset'])
if six.PY3 and isinstance(result_text, bytes):
result_text = result.body.decode('utf-8')
ret['body'] = result_text
else:
# Tornado

View file

@ -151,15 +151,25 @@ def nodegroup_comp(nodegroup, nodegroups, skip=None, first_call=True):
# No compound operators found in nodegroup definition. Check for
# group type specifiers
group_type_re = re.compile('^[A-Z]@')
regex_chars = ['(', '[', '{', '\\', '?''}])']
if not [x for x in ret if '*' in x or group_type_re.match(x)]:
# No group type specifiers and no wildcards. Treat this as a
# list of nodenames.
joined = 'L@' + ','.join(ret)
log.debug(
'Nodegroup \'%s\' (%s) detected as list of nodenames. '
'Assuming compound matching syntax of \'%s\'',
nodegroup, ret, joined
)
# No group type specifiers and no wildcards.
# Treat this as an expression.
if [x for x in ret if x in [x for y in regex_chars if y in x]]:
joined = 'E@' + ','.join(ret)
log.debug(
'Nodegroup \'%s\' (%s) detected as an expression. '
'Assuming compound matching syntax of \'%s\'',
nodegroup, ret, joined
)
else:
# Treat this as a list of nodenames.
joined = 'L@' + ','.join(ret)
log.debug(
'Nodegroup \'%s\' (%s) detected as list of nodenames. '
'Assuming compound matching syntax of \'%s\'',
nodegroup, ret, joined
)
# Return data must be a list of compound matching components
# to be fed into compound matcher. Enclose return data in list.
return [joined]

View file

@ -26,7 +26,7 @@ else:
# Psuedo "from psutil import *"
_globals = globals()
for attr in psutil.__all__:
_temp = __import__('psutil', globals(), locals(), [attr], -1)
_temp = __import__('psutil', globals(), locals(), [attr], -1 if six.PY2 else 0)
try:
_globals[attr] = getattr(_temp, attr)
except AttributeError:

View file

@ -70,6 +70,7 @@ def get_invalid_docs():
'nspawn.stop',
'pkg.expand_repo_def',
'pip.iteritems',
'pip.parse_version',
'runtests_decorators.depends',
'runtests_decorators.depends_will_fallback',
'runtests_decorators.missing_depends',

View file

@ -4,6 +4,7 @@
from __future__ import absolute_import, print_function, unicode_literals
import os
import sys
import tempfile
import textwrap
# Import Salt Testing libs
@ -13,6 +14,7 @@ from tests.support.helpers import (
skip_if_binaries_missing,
skip_if_not_root
)
from tests.support.paths import TMP
# Import salt libs
import salt.utils.path
@ -20,7 +22,6 @@ import salt.utils.path
# Import 3rd-party libs
from salt.ext import six
AVAILABLE_PYTHON_EXECUTABLE = salt.utils.path.which_bin([
'python',
'python2',
@ -139,6 +140,28 @@ class CMDModuleTest(ModuleCase):
ret = self.run_function('cmd.script_retcode', [script])
self.assertEqual(ret, 0)
def test_script_cwd(self):
'''
cmd.script with cwd
'''
tmp_cwd = tempfile.mkdtemp(dir=TMP)
args = 'saltines crackers biscuits=yes'
script = 'salt://script.py'
ret = self.run_function('cmd.script', [script, args], cwd=tmp_cwd)
self.assertEqual(ret['stdout'], args)
def test_script_cwd_with_space(self):
'''
cmd.script with cwd
'''
tmp_cwd = "{0}{1}test 2".format(tempfile.mkdtemp(dir=TMP), os.path.sep)
os.mkdir(tmp_cwd)
args = 'saltines crackers biscuits=yes'
script = 'salt://script.py'
ret = self.run_function('cmd.script', [script, args], cwd=tmp_cwd)
self.assertEqual(ret['stdout'], args)
@destructiveTest
def test_tty(self):
'''

View file

@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Gareth J. Greenaway <gareth@saltstack.com>`
'''
# Import Python Libs
from __future__ import absolute_import
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON
)
# Import Salt Libs
import salt.modules.osquery as osquery
@skipIf(NO_MOCK, NO_MOCK_REASON)
class OSQueryTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.modules.iptables
'''
def setup_loader_modules(self):
return {osquery: {}}
def test_version(self):
'''
Test the version returned from OSQuery
'''
_table_attrs_results = [u'pid',
u'uuid',
u'instance_id',
u'version',
u'config_hash',
u'config_valid',
u'extensions',
u'build_platform',
u'build_distro',
u'start_time',
u'watcher']
_os_query_results = {'data': [{u'version': u'2.6.1'}], 'result': True}
with patch.object(osquery, '_table_attrs',
MagicMock(return_value=_table_attrs_results)):
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
self.assertEqual(osquery.version(), u'2.6.1')
def test_deb_packages(self):
'''
Test the results returned from the deb_packages function
'''
_os_query_results = {'data': [
{'arch': 'amd64', 'name': 'accountsservice', 'revision': '1',
'size': '451', 'source': '', 'version': '0.6.45-1'},
{'arch': 'amd64', 'name': 'acetoneiso', 'revision': '2+b2',
'size': '1820', 'source': 'acetoneiso (2.4-2)',
'version': '2.4-2+b2'},
{'arch': 'amd64', 'name': 'acl', 'revision': '3+b1',
'size': '200', 'source': 'acl (2.2.52-3)',
'version': '2.2.52-3+b1'},
{'arch': 'amd64', 'name': 'adb', 'revision': '2', 'size': '189',
'source': 'android-platform-system-core',
'version': '1: 7.0.0+r33-2'}],
'result': True
}
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
with patch.dict(osquery.__grains__, {'os_family': 'Debian'}):
self.assertEqual(osquery.deb_packages(), _os_query_results)
def test_deb_packages_with_attrs(self):
'''
Test the results returned from the deb_packages function
with attributes
'''
_table_attrs_results = [u'name',
u'version',
u'source',
u'size',
u'arch',
u'revision']
_os_query_results = {'data': [
{'name': 'accountsservice', 'version': '0.6.45-1'},
{'name': 'acetoneiso', 'version': '2.4-2+b2'},
{'name': 'acl', 'version': '2.2.52-3+b1'},
{'name': 'adb', 'version': '1: 7.0.0+r33-2'}],
'result': True}
with patch.object(osquery, '_table_attrs',
MagicMock(return_value=_table_attrs_results)):
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
with patch.dict(osquery.__grains__, {'os_family': 'Debian'}):
self.assertEqual(osquery.deb_packages(attrs=['name',
'version']),
_os_query_results)
def test_kernel_modules(self):
'''
Test the results returned from the kernel_modules function
'''
_os_query_results = {'data': [
{'address': '0xffffffffc14f2000', 'name': 'nls_utf8',
'size': '16384', 'status': 'Live', 'used_by': '-'},
{'address': '0xffffffffc1599000', 'name': 'udf',
'size': '90112', 'status': 'Live', 'used_by': '-'},
{'address': '0xffffffffc14b5000', 'name': 'crc_itu_t',
'size': '16384', 'status': 'Live', 'used_by': 'udf'}],
'result': True
}
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
with patch.dict(osquery.__grains__, {'os_family': 'Debian'}):
self.assertEqual(osquery.kernel_modules(),
_os_query_results)
def test_kernel_modules_with_attrs(self):
'''
Test the results returned from the kernel_modules function
with attributes
'''
_table_attrs_results = [u'address',
u'name',
u'size',
u'status',
u'used_by']
_os_query_results = {'data': [
{'name': 'nls_utf8', 'status': 'Live'},
{'name': 'udf', 'status': 'Live'},
{'name': 'crc_itu_t', 'status': 'Live'}],
'result': True
}
with patch.object(osquery, '_table_attrs',
MagicMock(return_value=_table_attrs_results)):
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
with patch.dict(osquery.__grains__, {'os_family': 'Debian'}):
self.assertEqual(osquery.kernel_modules(attrs=['name',
'status']),
_os_query_results)
def test_osquery_info(self):
'''
Test the results returned from the kernel_modules function
with attributes
'''
_table_attrs_results = [u'pid',
u'uuid',
u'instance_id',
u'version',
u'config_hash',
u'config_valid',
u'extensions',
u'build_platform',
u'build_distro',
u'start_time',
u'watcher']
_os_query_results = {'data': [
{u'build_platform': u'ubuntu', u'start_time': u'1514484833',
u'uuid': u'D31FD400-7277-11E3-ABA6-B8AEED7E173B',
u'build_distro': u'xenial',
u'pid': u'24288',
u'watcher': u'-1',
u'instance_id': u'dff196b0-5c91-4105-962b-28660d7aa282',
u'version': u'2.6.1',
u'extensions': u'inactive',
u'config_valid': u'0',
u'config_hash': u''}],
'result': True}
with patch.object(osquery, '_table_attrs',
MagicMock(return_value=_table_attrs_results)):
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
with patch.dict(osquery.__grains__, {'os_family': 'Debian'}):
self.assertEqual(osquery.osquery_info(),
_os_query_results)
def test_osquery_info_with_attrs(self):
'''
Test the results returned from the kernel_modules function
with attributes
'''
_table_attrs_results = [u'pid',
u'uuid',
u'instance_id',
u'version',
u'config_hash',
u'config_valid',
u'extensions',
u'build_platform',
u'build_distro',
u'start_time',
u'watcher']
_os_query_results = {'data': [
{u'build_platform': u'ubuntu', u'start_time': u'1514484833'}],
'result': True}
with patch.object(osquery, '_table_attrs',
MagicMock(return_value=_table_attrs_results)):
with patch.object(osquery, '_osquery',
MagicMock(return_value=_os_query_results)):
with patch.dict(osquery.__grains__, {'os_family': 'Debian'}):
self.assertEqual(osquery.osquery_info(attrs=['build_platform',
'start_time']),
_os_query_results)

View file

@ -127,6 +127,16 @@ class NodegroupCompTest(TestCase):
expected_ret = ['L@foo.domain.com,bar.domain.com,baz.domain.com', 'or', 'bl*.domain.com']
self.assertListEqual(ret, expected_ret)
def test_simple_expression_nodegroup(self):
'''
Smoke test a nodegroup with a simple expression. No recursion.
'''
simple_nodegroup = {'group1': '[foo,bar,baz].domain.com'}
ret = salt.utils.minions.nodegroup_comp('group1', simple_nodegroup)
expected_ret = ['E@[foo,bar,baz].domain.com']
self.assertListEqual(ret, expected_ret)
def test_simple_recurse(self):
'''
Test a case where one nodegroup contains a second nodegroup