From 0c4e38ced4a20518a84314ba806b4bc386c7f9b2 Mon Sep 17 00:00:00 2001 From: Nicole Thomas Date: Mon, 23 May 2016 15:11:16 -0600 Subject: [PATCH 01/32] Fix the saltutil.wheel function and add integration tests (#33414) * Fix the saltutil.wheel function and add integration tests Fixes #32446 * Provide a more useful message when command is run on non-local minion --- salt/modules/saltutil.py | 44 ++++++++++++++++---- tests/integration/modules/saltutil.py | 59 +++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 7 deletions(-) create mode 100644 tests/integration/modules/saltutil.py diff --git a/salt/modules/saltutil.py b/salt/modules/saltutil.py index d6958175b263..be16eaeb7425 100644 --- a/salt/modules/saltutil.py +++ b/salt/modules/saltutil.py @@ -26,7 +26,7 @@ except ImportError: HAS_ESKY = False # pylint: disable=no-name-in-module -from salt.ext.six import string_types +import salt.ext.six as six from salt.ext.six.moves.urllib.error import URLError # pylint: enable=import-error,no-name-in-module @@ -52,7 +52,7 @@ import salt.transport import salt.wheel from salt.exceptions import ( - SaltReqTimeoutError, SaltRenderError, CommandExecutionError + SaltReqTimeoutError, SaltRenderError, CommandExecutionError, SaltInvocationError ) __proxyenabled__ = ['*'] @@ -88,7 +88,7 @@ def _sync(form, saltenv=None): ''' if saltenv is None: saltenv = _get_top_file_envs() - if isinstance(saltenv, string_types): + if isinstance(saltenv, six.string_types): saltenv = saltenv.split(',') ret = [] remote = set() @@ -989,7 +989,7 @@ def runner(_fun, **kwargs): return rclient.cmd(_fun, kwarg=kwargs) -def wheel(_fun, **kwargs): +def wheel(_fun, *args, **kwargs): ''' Execute a wheel module (this function must be run on the master) @@ -997,6 +997,13 @@ def wheel(_fun, **kwargs): name The name of the function to run + + args + Any positional arguments to pass to the wheel function. A common example + of this would be the ``match`` arg needed for key functions. + + .. versionadded:: v2015.8.11 + kwargs Any keyword arguments to pass to the wheel function @@ -1004,10 +1011,33 @@ def wheel(_fun, **kwargs): .. code-block:: bash - salt '*' saltutil.wheel key.accept match=jerry + salt '*' saltutil.wheel key.accept jerry ''' - wclient = salt.wheel.WheelClient(__opts__) - return wclient.cmd(_fun, kwarg=kwargs) + if __opts__['__role'] == 'minion': + master_config = os.path.join(os.path.dirname(__opts__['conf_file']), + 'master') + master_opts = salt.config.client_config(master_config) + wheel_client = salt.wheel.WheelClient(master_opts) + else: + wheel_client = salt.wheel.WheelClient(__opts__) + + # The WheelClient cmd needs args, kwargs, and pub_data separated out from + # the "normal" kwargs structure, which at this point contains __pub_x keys. + pub_data = {} + valid_kwargs = {} + for key, val in six.iteritems(kwargs): + if key.startswith('__'): + pub_data[key] = val + else: + valid_kwargs[key] = val + + try: + ret = wheel_client.cmd(_fun, arg=args, pub_data=pub_data, kwarg=valid_kwargs) + except SaltInvocationError: + raise CommandExecutionError('This command can only be executed on a minion ' + 'that is located on the master.') + + return ret # this is the only way I could figure out how to get the REAL file_roots diff --git a/tests/integration/modules/saltutil.py b/tests/integration/modules/saltutil.py new file mode 100644 index 000000000000..ebce06f96124 --- /dev/null +++ b/tests/integration/modules/saltutil.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +''' +Integration tests for the saltutil module. +''' + +# Import Python libs +from __future__ import absolute_import + +# Import Salt Testing libs +from salttesting.helpers import ensure_in_syspath + +ensure_in_syspath('../../') + +# Import Salt libs +import integration + + +class SaltUtilModuleTest(integration.ModuleCase): + ''' + Testcase for the saltutil execution module + ''' + + # Tests for the wheel function + + def test_wheel_just_function(self): + ''' + Tests using the saltutil.wheel function when passing only a function. + ''' + ret = self.run_function('saltutil.wheel', ['minions.connected']) + self.assertEqual(ret, ['minion', 'sub_minion']) + + def test_wheel_with_arg(self): + ''' + Tests using the saltutil.wheel function when passing a function and an arg. + ''' + ret = self.run_function('saltutil.wheel', ['key.list', 'minion']) + self.assertEqual(ret, {}) + + def test_wheel_no_arg_raise_error(self): + ''' + Tests using the saltutil.wheel function when passing a function that requires + an arg, but one isn't supplied. + ''' + self.assertRaises(TypeError, 'saltutil.wheel', ['key.list']) + + def test_wheel_with_kwarg(self): + ''' + Tests using the saltutil.wheel function when passing a function and a kwarg. + This function just generates a key pair, but doesn't do anything with it. We + just need this for testing purposes. + ''' + ret = self.run_function('saltutil.wheel', ['key.gen'], keysize=1024) + self.assertIn('pub', ret) + self.assertIn('priv', ret) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(SaltUtilModuleTest) From c8b4f338d8b9ce9f0c8beab3a3269f98eb178aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mihai=20Dinc=C4=83?= Date: Mon, 23 May 2016 23:15:29 +0200 Subject: [PATCH 02/32] Make --gpg-auto-import-keys a global param when calling zypper (#33432) --- salt/modules/zypper.py | 11 +++++----- tests/unit/modules/zypper_test.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index f96e0733a247..b314a7f4c3ac 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -766,6 +766,7 @@ def mod_repo(repo, **kwargs): # Modify added or existing repo according to the options cmd_opt = [] + global_cmd_opt = [] if 'enabled' in kwargs: cmd_opt.append(kwargs['enabled'] and '--enable' or '--disable') @@ -779,18 +780,18 @@ def mod_repo(repo, **kwargs): if 'gpgcheck' in kwargs: cmd_opt.append(kwargs['gpgcheck'] and '--gpgcheck' or '--no-gpgcheck') - if kwargs.get('gpgautoimport') is True: - cmd_opt.append('--gpg-auto-import-keys') - if 'priority' in kwargs: cmd_opt.append("--priority={0}".format(kwargs.get('priority', DEFAULT_PRIORITY))) if 'humanname' in kwargs: cmd_opt.append("--name='{0}'".format(kwargs.get('humanname'))) + if kwargs.get('gpgautoimport') is True: + global_cmd_opt.append('--gpg-auto-import-keys') + if cmd_opt: - cmd_opt.append(repo) - __zypper__.refreshable.xml.call('mr', *cmd_opt) + cmd_opt = global_cmd_opt + ['mr'] + cmd_opt + [repo] + __zypper__.refreshable.xml.call(*cmd_opt) # If repo nor added neither modified, error should be thrown if not added and not cmd_opt: diff --git a/tests/unit/modules/zypper_test.py b/tests/unit/modules/zypper_test.py index 4e735cde261a..c0ba3e95e642 100644 --- a/tests/unit/modules/zypper_test.py +++ b/tests/unit/modules/zypper_test.py @@ -9,6 +9,7 @@ # Import Salt Testing Libs from salttesting import TestCase, skipIf from salttesting.mock import ( + Mock, MagicMock, patch, NO_MOCK, @@ -413,6 +414,39 @@ def test_repo_value_info(self): self.assertEqual(r_info['enabled'], alias == 'SLE12-SP1-x86_64-Update') self.assertEqual(r_info['autorefresh'], alias == 'SLE12-SP1-x86_64-Update') + def test_modify_repo_gpg_auto_import_keys_parameter_position(self): + ''' + Tests if when modifying a repo, --gpg-auto-import-keys is a global option + + :return: + ''' + zypper_patcher = patch.multiple( + 'salt.modules.zypper', + **{ + '_get_configured_repos': Mock( + **{ + 'return_value.sections.return_value': ['mock-repo-name'] + } + ), + '__zypper__': Mock(), + 'get_repo': Mock() + } + ) + with zypper_patcher: + zypper.mod_repo( + 'mock-repo-name', + **{ + 'disabled': False, + 'url': 'http://repo.url/some/path', + 'gpgkey': 'http://repo.key', + 'refresh': True, + 'gpgautoimport': True + } + ) + zypper.__zypper__.refreshable.xml.call.assert_called_once_with( + '--gpg-auto-import-keys', 'mr', '--refresh', 'mock-repo-name' + ) + if __name__ == '__main__': from integration import run_tests run_tests(ZypperTestCase, needs_daemon=False) From df3c0b8e78ba46c2de03f5706304943170352de1 Mon Sep 17 00:00:00 2001 From: Oscar Steele <912@intrafile.com> Date: Mon, 23 May 2016 14:45:26 -0700 Subject: [PATCH 03/32] Correct (and make consistent) determination of the test flag. Fixes: #33363 --- salt/modules/state.py | 71 ++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 45 deletions(-) diff --git a/salt/modules/state.py b/salt/modules/state.py index 8d1d68bf6160..128544a7f9ca 100644 --- a/salt/modules/state.py +++ b/salt/modules/state.py @@ -196,7 +196,22 @@ def low(data, queue=False, **kwargs): return ret -def high(data, test=False, queue=False, **kwargs): +def _get_test_value(test=None, **kwargs): + ''' + Determine the correct value for the test flag. + ''' + ret = True + if test is None: + if salt.utils.test_mode(test=test, **kwargs): + ret = True + else: + ret = __opts__.get('test', None) + else: + ret = test + return ret + + +def high(data, test=None, queue=False, **kwargs): ''' Execute the compound calls stored in a single set of high data @@ -214,12 +229,7 @@ def high(data, test=False, queue=False, **kwargs): return conflict opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - elif test is not None: - opts['test'] = test - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') if pillar is not None and not isinstance(pillar, dict): @@ -622,13 +632,7 @@ def highstate(test=None, opts = _get_opts(kwargs.get('localconfig')) - if test is None: - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) - else: - opts['test'] = test + opts['test'] = _get_test_value(test, **kwargs) if 'env' in kwargs: salt.utils.warn_until( @@ -801,12 +805,7 @@ def sls(mods, orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - elif test is not None: - opts['test'] = test - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') if pillar is not None and not isinstance(pillar, dict): @@ -909,10 +908,7 @@ def top(topfn, return err orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') if pillar is not None and not isinstance(pillar, dict): @@ -1018,10 +1014,7 @@ def sls_id( return conflict orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) if 'pillarenv' in kwargs: opts['pillarenv'] = kwargs['pillarenv'] st_ = salt.state.HighState(opts) @@ -1082,10 +1075,7 @@ def show_low_sls(mods, return conflict orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) if 'pillarenv' in kwargs: opts['pillarenv'] = kwargs['pillarenv'] st_ = salt.state.HighState(opts) @@ -1138,10 +1128,7 @@ def show_sls(mods, saltenv='base', test=None, queue=False, env=None, **kwargs): orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') if pillar is not None and not isinstance(pillar, dict): @@ -1224,10 +1211,7 @@ def single(fun, name, test=None, queue=False, **kwargs): 'name': name}) orig_test = __opts__.get('test', None) opts = _get_opts(kwargs.get('localconfig')) - if salt.utils.test_mode(test=test, **kwargs): - opts['test'] = True - else: - opts['test'] = __opts__.get('test', None) + opts['test'] = _get_test_value(test, **kwargs) pillar = kwargs.get('pillar') if pillar is not None and not isinstance(pillar, dict): @@ -1276,7 +1260,7 @@ def clear_cache(): return ret -def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs): +def pkg(pkg_path, pkg_sum, hash_type, test=None, **kwargs): ''' Execute a packaged state run, the packaged state run will exist in a tarball available locally. This packaged state @@ -1320,10 +1304,7 @@ def pkg(pkg_path, pkg_sum, hash_type, test=False, **kwargs): popts = _get_opts(kwargs.get('localconfig')) popts['fileclient'] = 'local' popts['file_roots'] = {} - if salt.utils.test_mode(test=test, **kwargs): - popts['test'] = True - else: - popts['test'] = __opts__.get('test', None) + popts['test'] = _get_test_value(test, **kwargs) envs = os.listdir(root) for fn_ in envs: full = os.path.join(root, fn_) From 82f8f3efff7cbd5fec1ee9875f7e96e6c2c7f9f2 Mon Sep 17 00:00:00 2001 From: rallytime Date: Mon, 23 May 2016 16:33:51 -0600 Subject: [PATCH 04/32] Make doc formatting consistent and use correct versionadded --- salt/states/grains.py | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/salt/states/grains.py b/salt/states/grains.py index 17312dca4ec5..c44e0ae43bf9 100644 --- a/salt/states/grains.py +++ b/salt/states/grains.py @@ -10,16 +10,19 @@ Note: This does NOT override any grains set in the minion file. ''' +# Import Python libs from __future__ import absolute_import -from salt.defaults import DEFAULT_TARGET_DELIM import re +# Import Salt libs +from salt.defaults import DEFAULT_TARGET_DELIM + def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False): ''' Ensure that a grain is set - .. versionchanged:: Boron + .. versionchanged:: v2015.8.2 name The grain name @@ -27,14 +30,16 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False): value The value to set on the grain - :param force: If force is True, the existing grain will be overwritten + force + If force is True, the existing grain will be overwritten regardless of its existing or provided value type. Defaults to False - .. versionadded:: Boron + .. versionadded:: v2015.8.2 - :param delimiter: A delimiter different from the default can be provided. + delimiter + A delimiter different from the default can be provided. - .. versionadded:: Boron + .. versionadded:: v2015.8.2 It is now capable to set a grain to a complex value (ie. lists and dicts) and supports nested grains as well. @@ -59,11 +64,11 @@ def present(name, value, delimiter=DEFAULT_TARGET_DELIM, force=False): - name: icinga:Apache SSL - value: - command: check_https - - params: -H localhost -p 443 -S + - params: -H localhost -p 443 -S with,a,custom,delimiter: grains.present: - - value: yay + - value: yay - delimiter: , ''' name = re.sub(delimiter, DEFAULT_TARGET_DELIM, name) @@ -106,9 +111,10 @@ def list_present(name, value, delimiter=DEFAULT_TARGET_DELIM): value The value is present in the list type grain. - :param delimiter: A delimiter different from the default ``:`` can be provided. + delimiter + A delimiter different from the default ``:`` can be provided. - .. versionadded:: Boron + .. versionadded:: v2015.8.2 The grain should be `list type `_ @@ -198,9 +204,10 @@ def list_absent(name, value, delimiter=DEFAULT_TARGET_DELIM): value The value to delete from the grain list. - :param delimiter: A delimiter different from the default ``:`` can be provided. + delimiter + A delimiter different from the default ``:`` can be provided. - .. versionadded:: Boron + .. versionadded:: v2015.8.2 The grain should be `list type `_ @@ -273,19 +280,23 @@ def absent(name, name The grain name - :param destructive: If destructive is True, delete the entire grain. If + destructive + If destructive is True, delete the entire grain. If destructive is False, set the grain's value to None. Defaults to False. - :param force: If force is True, the existing grain will be overwritten + force + If force is True, the existing grain will be overwritten regardless of its existing or provided value type. Defaults to False - .. versionadded:: Boron + .. versionadded:: v2015.8.2 + + delimiter + A delimiter different from the default can be provided. - :param delimiter: A delimiter different from the default can be provided. + .. versionadded:: v2015.8.2 - .. versionadded:: Boron + .. versionchanged:: v2015.8.2 - .. versionchanged:: Boron This state now support nested grains and complex values. It is also more conservative: if a grain has a value that is a list or a dict, it will not be removed unless the `force` parameter is True. @@ -367,13 +378,15 @@ def append(name, value, convert=False, value The value to append - :param convert: If convert is True, convert non-list contents into a list. + convert + If convert is True, convert non-list contents into a list. If convert is False and the grain contains non-list contents, an error is given. Defaults to False. - :param delimiter: A delimiter different from the default can be provided. + delimiter + A delimiter different from the default can be provided. - .. versionadded:: Boron + .. versionadded:: v2015.8.2 .. code-block:: yaml From 7ba40c4f31a132cc431a5bf3bf0845361062056f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Tue, 24 May 2016 09:21:43 +0100 Subject: [PATCH 05/32] jobs.exit_success allow to check if a job has executed and exit successfully --- salt/runners/jobs.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index 57d032466353..b95cc943a0a2 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -488,6 +488,48 @@ def print_job(jid, ext_source=None, outputter=None): return ret +def exit_success(jid, ext_source=None): + ''' + Check if a job has been executed and exit successfully + + jid + The jid to look up. + ext_source + The external job cache to use. Default: `None`. + + CLI Example: + .. code-block:: bash + salt-run jobs.exit_success 20160520145827701627 + ''' + ret = {} + mminion = salt.minion.MasterMinion(__opts__) + returner = _get_returner(( + __opts__['ext_job_cache'], + ext_source, + __opts__['master_job_cache'] + )) + + try: + data = list_job( + jid, + ext_source=ext_source + ) + except TypeError: + return ('Requested returner could not be loaded. ' + 'No JIDs could be retrieved.') + + returns = data.get('Result', {}) + + for minion in returns: + minion_ret = returns[minion].get('return', {}) + if minion_ret['retcode'] == 0: + ret[minion] = True + else: + ret[minion] = False + + return ret + + def last_run(ext_source=None, outputter=None, metadata=None, From 8a21b9149ea1da943225f3e1f2b43a94ac8f0c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Tue, 24 May 2016 11:01:55 +0100 Subject: [PATCH 06/32] check the signature of downloaded RPM files --- salt/modules/rpm.py | 34 ++++++++++++++++++++++++++++++++++ salt/modules/zypper.py | 2 ++ 2 files changed, 36 insertions(+) diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py index 1469368e99a0..839658ffbe97 100644 --- a/salt/modules/rpm.py +++ b/salt/modules/rpm.py @@ -602,3 +602,37 @@ def version_cmp(ver1, ver2): log.warning("Failed to compare version '{0}' to '{1}' using RPM: {2}".format(ver1, ver2, exc)) return salt.utils.version_cmp(ver1, ver2) + + +def check_sig(*paths): + ''' + Return if the signature of a RPM file is valid. + + CLI Example: + + .. code-block:: bash + + salt '*' lowpkg.check_sig /path/to/package1.rpm + salt '*' lowpkg.check_sig /path/to/package1.rpm /path/to/package2.rpm + ''' + ret = {} + + if not paths: + raise CommandExecutionError("No RPM files has been specified.") + + for package_file in paths: + ret[package_file] = False + + if not __salt__['file.file_exists'](package_file): + continue + + check_cmd = ["rpm", "-K", "--quiet", package_file] + check_args = { + 'ignore_retcode': True, + 'output_loglevel': 'trace', + 'python_shell': False, + } + if __salt__['cmd.retcode'](check_cmd, **check_args) == 0: + ret[package_file] = True + + return ret diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index b314a7f4c3ac..04c03dbb1387 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -1534,6 +1534,8 @@ def download(*packages, **kwargs): 'repository-alias': repo.getAttribute("alias"), 'path': dld_result.getElementsByTagName("localfile")[0].getAttribute("path"), } + if not __salt__['lowpkg.check_sig'](pkg_info['path']): + continue pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info if pkg_ret: From d56e3f425860fdbade1dec8a287b239c429da4e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Tue, 24 May 2016 11:11:53 +0100 Subject: [PATCH 07/32] bugfix: showing errors when a package download fails using zypper pkg.download --- salt/modules/zypper.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index 04c03dbb1387..e210c5762081 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -1539,6 +1539,10 @@ def download(*packages, **kwargs): pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info if pkg_ret: + failed = [pkg for pkg in packages if pkg not in pkg_ret] + if failed: + pkg_ret['_error'] = ('The following package(s) failed to download: {0}' + .format(', '.join(failed))) return pkg_ret raise CommandExecutionError("Unable to download packages: {0}.".format(', '.join(packages))) From 80fe303e38bdca056b53fdfa6d35ee7ad52bac4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Tue, 24 May 2016 13:05:48 +0100 Subject: [PATCH 08/32] Renamed check_sig to checksum and some refactoring --- salt/modules/rpm.py | 14 ++++---------- salt/modules/zypper.py | 8 +++----- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py index 839658ffbe97..aed755cdd878 100644 --- a/salt/modules/rpm.py +++ b/salt/modules/rpm.py @@ -604,7 +604,7 @@ def version_cmp(ver1, ver2): return salt.utils.version_cmp(ver1, ver2) -def check_sig(*paths): +def checksum(*paths): ''' Return if the signature of a RPM file is valid. @@ -612,8 +612,8 @@ def check_sig(*paths): .. code-block:: bash - salt '*' lowpkg.check_sig /path/to/package1.rpm - salt '*' lowpkg.check_sig /path/to/package1.rpm /path/to/package2.rpm + salt '*' lowpkg.checksum /path/to/package1.rpm + salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm ''' ret = {} @@ -626,13 +626,7 @@ def check_sig(*paths): if not __salt__['file.file_exists'](package_file): continue - check_cmd = ["rpm", "-K", "--quiet", package_file] - check_args = { - 'ignore_retcode': True, - 'output_loglevel': 'trace', - 'python_shell': False, - } - if __salt__['cmd.retcode'](check_cmd, **check_args) == 0: + if not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file], ignore_retcode=True, output_loglevel='trace', python_shell=False): ret[package_file] = True return ret diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index e210c5762081..e97f42cf9e92 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -1534,15 +1534,13 @@ def download(*packages, **kwargs): 'repository-alias': repo.getAttribute("alias"), 'path': dld_result.getElementsByTagName("localfile")[0].getAttribute("path"), } - if not __salt__['lowpkg.check_sig'](pkg_info['path']): - continue - pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info + if __salt__['lowpkg.checksum'](pkg_info['path']): + pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info if pkg_ret: failed = [pkg for pkg in packages if pkg not in pkg_ret] if failed: - pkg_ret['_error'] = ('The following package(s) failed to download: {0}' - .format(', '.join(failed))) + pkg_ret['_error'] = ('The following package(s) failed to download: {0}'.format(', '.join(failed))) return pkg_ret raise CommandExecutionError("Unable to download packages: {0}.".format(', '.join(packages))) From 68d8050cb8d8563cd3a33813826fdc18825180b7 Mon Sep 17 00:00:00 2001 From: Mike Place Date: Tue, 24 May 2016 08:27:49 -0600 Subject: [PATCH 09/32] Fix diskusage beacon The examples given in the config did not match the code. Adjusted code to line up with examples. --- salt/beacons/diskusage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/salt/beacons/diskusage.py b/salt/beacons/diskusage.py index 9c874985c2fa..b704deec7d24 100644 --- a/salt/beacons/diskusage.py +++ b/salt/beacons/diskusage.py @@ -61,9 +61,9 @@ def beacon(config): - /: 63% - /mnt/nfs: 50% ''' + print(config) ret = [] - for diskusage in config: - mount = diskusage.keys()[0] + for mount in config: try: _current_usage = psutil.disk_usage(mount) @@ -73,7 +73,7 @@ def beacon(config): continue current_usage = _current_usage.percent - monitor_usage = diskusage[mount] + monitor_usage = config[mount] if '%' in monitor_usage: monitor_usage = re.sub('%', '', monitor_usage) monitor_usage = float(monitor_usage) From 3b12f396b42684741e0c5acc0e233f0a5ba0d2b4 Mon Sep 17 00:00:00 2001 From: Bo Maryniuk Date: Tue, 24 May 2016 16:37:03 +0200 Subject: [PATCH 10/32] Prevent several minion processes on the same machine (#33464) --- pkg/suse/salt-minion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/suse/salt-minion b/pkg/suse/salt-minion index c476fabe4a8e..28cf32c062dc 100644 --- a/pkg/suse/salt-minion +++ b/pkg/suse/salt-minion @@ -55,7 +55,7 @@ RETVAL=0 start() { echo -n $"Starting salt-minion daemon: " if [ -f $SUSE_RELEASE ]; then - startproc -f -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS + startproc -p /var/run/$SERVICE.pid $SALTMINION -d $MINION_ARGS rc_status -v elif [ -e $DEBIAN_VERSION ]; then if [ -f $LOCKFILE ]; then From a65071a6d1241776570077545a78ea477742ccff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Tue, 24 May 2016 15:37:57 +0100 Subject: [PATCH 11/32] simpler rpm.checksum function --- salt/modules/rpm.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py index aed755cdd878..4991f24ae646 100644 --- a/salt/modules/rpm.py +++ b/salt/modules/rpm.py @@ -615,18 +615,16 @@ def checksum(*paths): salt '*' lowpkg.checksum /path/to/package1.rpm salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm ''' - ret = {} + ret = dict() if not paths: - raise CommandExecutionError("No RPM files has been specified.") + raise CommandExecutionError("No package files has been specified.") for package_file in paths: - ret[package_file] = False - - if not __salt__['file.file_exists'](package_file): - continue - - if not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file], ignore_retcode=True, output_loglevel='trace', python_shell=False): - ret[package_file] = True + ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and + not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file], + ignore_retcode=True, + output_loglevel='trace', + python_shell=False)) return ret From 00f90909282d2e93fdad346692f3c1831642f182 Mon Sep 17 00:00:00 2001 From: Nicole Thomas Date: Tue, 24 May 2016 09:12:21 -0600 Subject: [PATCH 12/32] Add docs about PyYAML's 1024 character limitations for simple keys (#33459) Fixes #33389 --- doc/topics/troubleshooting/yaml_idiosyncrasies.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst index fc9d9e2c5506..e7be0618f7df 100644 --- a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst +++ b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst @@ -340,3 +340,13 @@ string with quotes: ValueError: month must be in 1..12 >>> yaml.safe_load('"4017-16-20"') '4017-16-20' + + +Keys Limited to 1024 Characters +=============================== + +Simple keys are limited to a single line and cannot be longer that 1024 characters. +This is a limitation from PyYaml, as seen in a comment in `PyYAML's code`_, and +applies to anything parsed by YAML in Salt. + +.. _PyYAML's code: http://pyyaml.org/browser/pyyaml/trunk/lib/yaml/scanner.py#L91 From aa2bac3a0db803e69ac44186356b69b77ee269f6 Mon Sep 17 00:00:00 2001 From: Mike Place Date: Tue, 24 May 2016 09:14:04 -0600 Subject: [PATCH 13/32] Remove debugging --- salt/beacons/diskusage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/salt/beacons/diskusage.py b/salt/beacons/diskusage.py index b704deec7d24..30eeccebfe1a 100644 --- a/salt/beacons/diskusage.py +++ b/salt/beacons/diskusage.py @@ -61,7 +61,6 @@ def beacon(config): - /: 63% - /mnt/nfs: 50% ''' - print(config) ret = [] for mount in config: From 042f17efa42ec51c6ce15e63dfb6fa648c4cccbf Mon Sep 17 00:00:00 2001 From: Mike Place Date: Mon, 11 Apr 2016 12:11:13 -0600 Subject: [PATCH 14/32] Only unsub if we have a jid Closes #32479 --- salt/client/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/salt/client/__init__.py b/salt/client/__init__.py index 91efc60b4d85..c88842a828c4 100644 --- a/salt/client/__init__.py +++ b/salt/client/__init__.py @@ -659,8 +659,7 @@ def cmd_iter( if not fn_ret: continue yield fn_ret - - self._clean_up_subscriptions(pub_data['jid']) + self._clean_up_subscriptions(pub_data['jid']) def cmd_iter_no_block( self, @@ -721,7 +720,7 @@ def cmd_iter_no_block( fn_ret[minion]['jid'] = pub_data['jid'] yield fn_ret - self._clean_up_subscriptions(pub_data['jid']) + self._clean_up_subscriptions(pub_data['jid']) def cmd_full_return( self, From 3a52ace6730070a34bb07bd9f8bc2e7eddddc2da Mon Sep 17 00:00:00 2001 From: Daniel Wallace Date: Tue, 24 May 2016 11:05:39 -0500 Subject: [PATCH 15/32] manage account information for pam (#33473) Make sure to run the account sections of pam.d directives. Closes #32455 --- salt/auth/pam.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/salt/auth/pam.py b/salt/auth/pam.py index 13ffe40f1dc5..5d2d6e324875 100644 --- a/salt/auth/pam.py +++ b/salt/auth/pam.py @@ -117,6 +117,10 @@ class PamConv(Structure): PAM_AUTHENTICATE.restype = c_int PAM_AUTHENTICATE.argtypes = [PamHandle, c_int] + PAM_ACCT_MGMT = LIBPAM.pam_acct_mgmt + PAM_ACCT_MGMT.restype = c_int + PAM_ACCT_MGMT.argtypes = [PamHandle, c_int] + PAM_END = LIBPAM.pam_end PAM_END.restype = c_int PAM_END.argtypes = [PamHandle, c_int] @@ -171,6 +175,8 @@ def my_conv(n_messages, messages, p_response, app_data): return False retval = PAM_AUTHENTICATE(handle, 0) + if retval == 0: + PAM_ACCT_MGMT(handle, 0) PAM_END(handle, 0) return retval == 0 From b4071b07f1b9fa16ea697b36c0f633bc3c62d25c Mon Sep 17 00:00:00 2001 From: Mike Place Date: Tue, 24 May 2016 09:38:42 -0700 Subject: [PATCH 16/32] Allow for config entry to be a list in a dict for beacons (#33476) Fixes #29451 --- salt/beacons/__init__.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/salt/beacons/__init__.py b/salt/beacons/__init__.py index 492469d9b0d0..07cfb0e17cec 100644 --- a/salt/beacons/__init__.py +++ b/salt/beacons/__init__.py @@ -102,7 +102,13 @@ def _determine_beacon_config(self, mod, val, config_mod): if val_config: config = val_config[0][val] elif isinstance(config_mod, dict): - config = config_mod[mod].get(val, False) + try: + config = config_mod[mod].get(val, False) + except AttributeError: # The config is a list + config = None + val_config = [arg for arg in config_mod if val in arg] + if val_config: + config = val_config[0][val] return config def _process_interval(self, mod, interval): From 1dfa95651c36132e12b0f32393bf6537ccc51c77 Mon Sep 17 00:00:00 2001 From: Erik Johnson Date: Tue, 24 May 2016 12:15:41 -0500 Subject: [PATCH 17/32] Don't allow a "repo" kwarg for pkgrepo.managed (#33477) This is not a valid argument for any of the supported pkgrepo platforms, and having it pass through to pkg.expand_repo_def results in a traceback. --- salt/states/pkgrepo.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py index 1cf8507713e4..b6b43d84de78 100644 --- a/salt/states/pkgrepo.py +++ b/salt/states/pkgrepo.py @@ -270,6 +270,13 @@ def managed(name, ppa=None, **kwargs): ret['comment'] = 'Repo management not implemented on this platform' return ret + if 'repo' in kwargs: + ret['result'] = False + ret['comment'] = ('\'repo\' is not a supported argument for this ' + 'state. The \'name\' argument is probably what was ' + 'intended.') + return ret + repo = name if __grains__['os'] == 'Ubuntu': if ppa is not None: From 6a9ae09e7942ed43d05f865db02b5d416a503526 Mon Sep 17 00:00:00 2001 From: Justin Findlay Date: Tue, 24 May 2016 12:25:26 -0600 Subject: [PATCH 18/32] states.service.__virtual__: add load fail reason --- salt/states/service.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/salt/states/service.py b/salt/states/service.py index e2f07dd26a59..6390e6bc0900 100644 --- a/salt/states/service.py +++ b/salt/states/service.py @@ -62,13 +62,21 @@ from __future__ import absolute_import import time +__virtualname__ = 'service' + def __virtual__(): ''' Only make these states available if a service provider has been detected or assigned for this minion ''' - return 'service.start' in __salt__ + if 'service.start' in __salt__: + return __virtualname__ + else: + return (False, 'No service execution module loaded: ' + 'check support for service management on {0} ' + ''.format(__grains__.get('osfinger', __grains__['os'])) + ) def _enabled_used_error(ret): From 29c00a1b1b5428e33aaeaf419738de3d3f0d39c4 Mon Sep 17 00:00:00 2001 From: Justin Findlay Date: Tue, 24 May 2016 12:30:02 -0600 Subject: [PATCH 19/32] states.service: clarify function description language --- salt/states/service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/salt/states/service.py b/salt/states/service.py index 6390e6bc0900..711b55851195 100644 --- a/salt/states/service.py +++ b/salt/states/service.py @@ -278,7 +278,7 @@ def _available(name, ret): def running(name, enable=None, sig=None, init_delay=None, **kwargs): ''' - Verify that the service is running + Ensure that the service is running name The name of the init or rc script used to manage the service @@ -446,7 +446,7 @@ def dead(name, enable=None, sig=None, **kwargs): def enabled(name, **kwargs): ''' - Verify that the service is enabled on boot, only use this state if you + Ensure that the service is enabled on boot, only use this state if you don't want to manage the running process, remember that if you want to enable a running service to use the enable: True option for the running or dead function. @@ -465,7 +465,7 @@ def enabled(name, **kwargs): def disabled(name, **kwargs): ''' - Verify that the service is disabled on boot, only use this state if you + Ensure that the service is disabled on boot, only use this state if you don't want to manage the running process, remember that if you want to disable a service to use the enable: False option for the running or dead function. From 7fd3e8f3611e0728e041be12a2bbc25a4802c812 Mon Sep 17 00:00:00 2001 From: rallytime Date: Tue, 24 May 2016 13:19:20 -0600 Subject: [PATCH 20/32] Fix docs about etcd config options and add pillar_opts doc Fixes #33423 --- doc/ref/configuration/master.rst | 20 ++++++++++++++++++++ salt/modules/etcd_mod.py | 18 ++++++++++++++++-- salt/states/etcd_mod.py | 23 +++++++++++++++++------ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index 1797a7adf7da..f243f61876c4 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -2208,6 +2208,26 @@ configuration is the same as :conf_master:`file_roots`: prod: - /srv/pillar/prod +.. conf_master:: pillar_opts + +``pillar_opts`` +--------------- + +Default: ``False`` + +The ``pillar_opts`` option adds the master configuration file data to a dict in +the pillar called ``master``. This can be used to set simple configurations in +the master config file that can then be used on minions. + +Note that setting this option to ``True`` means the master config file will be +included in all minion's pillars. While this makes global configuration of services +and systems easy, it may not be desired if sensitive data is stored in the master +configuration. + +.. code-block:: yaml + + pillar_opts: False + .. _master-configuration-ext-pillar: .. conf_master:: ext_pillar diff --git a/salt/modules/etcd_mod.py b/salt/modules/etcd_mod.py index 622f2d4cf1a4..bbbfa6c897d9 100644 --- a/salt/modules/etcd_mod.py +++ b/salt/modules/etcd_mod.py @@ -4,8 +4,11 @@ :depends: - python-etcd -In order to use an etcd server, a profile should be created in the master -configuration file: +Configuration +------------- + +To work with an etcd server you must configure an etcd profile. The etcd config +can be set in either the Salt Minion configuration file or in pillar: .. code-block:: yaml @@ -21,6 +24,17 @@ etcd.host: 127.0.0.1 etcd.port: 4001 + +.. note:: + + The etcd configuration can also be set in the Salt Master config file, + but in order to use any etcd configurations defined in the Salt Master + config, the :conf_master:`pillar_opts` must be set to ``True``. + + Be aware that setting ``pillar_opts`` to ``True`` has security implications + as this makes all master configuration settings available in all minion's + pillars. + ''' from __future__ import absolute_import diff --git a/salt/states/etcd_mod.py b/salt/states/etcd_mod.py index f9cbb4423714..e6b637594a36 100644 --- a/salt/states/etcd_mod.py +++ b/salt/states/etcd_mod.py @@ -10,11 +10,11 @@ This state module supports setting and removing keys from etcd. -Salt Master Configuration -------------------------- +Configuration +------------- -To work with an etcd server you must configure an etcd profile in the Salt -Master configuration, for example: +To work with an etcd server you must configure an etcd profile. The etcd config +can be set in either the Salt Minion configuration file or in pillar: .. code-block:: yaml @@ -22,14 +22,25 @@ etcd.host: 127.0.0.1 etcd.port: 4001 -You can also configure etcd without a profile however it is recommended that -you use profiles: +It is technically possible to configure etcd without using a profile, but this +is not considered to be a best practice, especially when multiple etcd servers +or clusters are available. .. code-block:: yaml etcd.host: 127.0.0.1 etcd.port: 4001 +.. note:: + + The etcd configuration can also be set in the Salt Master config file, + but in order to use any etcd configurations defined in the Salt Master + config, the :conf_master:`pillar_opts` must be set to ``True``. + + Be aware that setting ``pillar_opts`` to ``True`` has security implications + as this makes all master configuration settings available in all minion's + pillars. + Available Functions ------------------- From 087564528d7cae2f2bf4a222e1f7ada68e7a3350 Mon Sep 17 00:00:00 2001 From: rallytime Date: Tue, 24 May 2016 13:26:24 -0600 Subject: [PATCH 21/32] Add pillar_opts docs to master.rst --- doc/ref/configuration/master.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index 5a1340efa954..0aac426794ee 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -2121,6 +2121,26 @@ configuration is the same as :conf_master:`file_roots`: prod: - /srv/pillar/prod +.. conf_master:: pillar_opts + +``pillar_opts`` +--------------- + +Default: ``False`` + +The ``pillar_opts`` option adds the master configuration file data to a dict in +the pillar called ``master``. This can be used to set simple configurations in +the master config file that can then be used on minions. + +Note that setting this option to ``True`` means the master config file will be +included in all minion's pillars. While this makes global configuration of services +and systems easy, it may not be desired if sensitive data is stored in the master +configuration. + +.. code-block:: yaml + + pillar_opts: False + .. conf_master:: ext_pillar ``ext_pillar`` From 59e90064e6835d4feb4888ae75d8883615bdfba9 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Tue, 24 May 2016 14:58:32 -0600 Subject: [PATCH 22/32] modules.swift.head does not have a body. Should not be checked for a docstring right now. --- tests/integration/modules/sysmod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/modules/sysmod.py b/tests/integration/modules/sysmod.py index dc90e8e58201..a8a2edb30eb1 100644 --- a/tests/integration/modules/sysmod.py +++ b/tests/integration/modules/sysmod.py @@ -62,6 +62,7 @@ def test_valid_docs(self): 'runtests_decorators.depends_will_fallback', 'runtests_decorators.missing_depends', 'runtests_decorators.missing_depends_will_fallback', + 'swift.head', 'yumpkg.expand_repo_def', 'yumpkg5.expand_repo_def', 'container_resource.run', From ee2ae0ea8af5facdea48f4dfc33e50fedf689245 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Tue, 24 May 2016 15:16:07 -0600 Subject: [PATCH 23/32] Added docstring examples to glance.image_schema and schema_get --- salt/modules/glance.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/salt/modules/glance.py b/salt/modules/glance.py index 185b0bfe8f3f..105ab1764590 100644 --- a/salt/modules/glance.py +++ b/salt/modules/glance.py @@ -296,6 +296,12 @@ def image_schema(profile=None): ''' Returns names and descriptions of the schema "image"'s properties for this profile's instance of glance + + CLI Example: + + .. code-block:: bash + + salt '*' glance.image_schema ''' return schema_get('image', profile) @@ -307,6 +313,12 @@ def schema_get(name, profile=None): - images - member - members + + CLI Example: + + .. code-block:: bash + + salt '*' glance.schema_get name=f16-jeos ''' g_client = _auth(profile) pformat = pprint.PrettyPrinter(indent=4).pformat From ebf12565457fe17de9d67736fd7496b7e8b0d8aa Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Tue, 24 May 2016 15:26:28 -0600 Subject: [PATCH 24/32] Don't need to check swift.head due to it having no body --- tests/integration/modules/sysmod.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/modules/sysmod.py b/tests/integration/modules/sysmod.py index 7f41818747c4..72430cf853e6 100644 --- a/tests/integration/modules/sysmod.py +++ b/tests/integration/modules/sysmod.py @@ -66,6 +66,7 @@ def test_valid_docs(self): 'runtests_decorators.depends_will_fallback', 'runtests_decorators.missing_depends', 'runtests_decorators.missing_depends_will_fallback', + 'swift.head', 'yumpkg.expand_repo_def', 'yumpkg5.expand_repo_def', 'container_resource.run', From 0b1cae05d96d12b0a4c80e279a82adf871efb378 Mon Sep 17 00:00:00 2001 From: Justin Anderson Date: Tue, 24 May 2016 15:35:44 -0600 Subject: [PATCH 25/32] Added docstring examples to glance methods and nova.list --- salt/modules/glance.py | 19 +++++++++++++++++++ salt/modules/nova.py | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/salt/modules/glance.py b/salt/modules/glance.py index ff50a7846b58..94b2320f137f 100644 --- a/salt/modules/glance.py +++ b/salt/modules/glance.py @@ -448,6 +448,12 @@ def image_schema(profile=None): ''' Returns names and descriptions of the schema "image"'s properties for this profile's instance of glance + + CLI Example: + + .. code-block:: bash + + salt '*' glance.image_schema ''' return schema_get('image', profile) @@ -459,6 +465,13 @@ def image_update(id=None, name=None, profile=None, **kwargs): # pylint: disable - min_ram (in MB) - protected (bool) - visibility ('public' or 'private') + + CLI Example: + + .. code-block:: bash + + salt '*' glance.image_update id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df + salt '*' glance.image_update name=f16-jeos ''' if id: image = image_show(id=id, profile=profile) @@ -512,6 +525,12 @@ def schema_get(name, profile=None): - images - member - members + + CLI Example: + + .. code-block:: bash + + salt '*' glance.schema_get name=f16-jeos ''' g_client = _auth(profile) pformat = pprint.PrettyPrinter(indent=4).pformat diff --git a/salt/modules/nova.py b/salt/modules/nova.py index cc8a27e1e035..ab7d8999df40 100644 --- a/salt/modules/nova.py +++ b/salt/modules/nova.py @@ -559,6 +559,12 @@ def list_(profile=None): ''' To maintain the feel of the nova command line, this function simply calls the server_list function. + + CLI Example: + + .. code-block:: bash + + salt '*' nova.list ''' return server_list(profile=profile) From b0a9f4181f1e17a82f6b349472f6e1c26a96bce3 Mon Sep 17 00:00:00 2001 From: rallytime Date: Tue, 24 May 2016 16:10:43 -0600 Subject: [PATCH 26/32] Add docs for the syndic_finger config Fixes #18752 --- conf/minion | 4 ++++ doc/ref/configuration/minion.rst | 13 +++++++++++++ 2 files changed, 17 insertions(+) diff --git a/conf/minion b/conf/minion index da1d6bba155d..f4c01218d523 100644 --- a/conf/minion +++ b/conf/minion @@ -311,6 +311,10 @@ #include: # - /etc/salt/extra_config # - /etc/roles/webserver + +# The syndic minion can verify that it is talking to the correct master via the +# key fingerprint of the higher-level master with the "syndic_finger" config. +#syndic_finger: '' # # # diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 8de0899c5d82..750bffb2684b 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -694,6 +694,19 @@ Pull port used when :conf_minion:`ipc_mode` is set to ``tcp``. tcp_pull_port: 4511 +.. conf_minion:: syndic_finger + +``syndic_finger`` +----------------- + +Default: ``''`` + +The key fingerprint of the higher-level master for the syndic to verify it is +talking to the intended master. + +.. code-block:: yaml + + syndic_finger: 'ab:30:65:2a:d6:9e:20:4f:d8:b2:f3:a7:d4:65:50:10' Minion Module Management From 0c5548f9d1d4d412ba70f657e6a28b268ebdff2c Mon Sep 17 00:00:00 2001 From: rallytime Date: Tue, 24 May 2016 16:54:06 -0600 Subject: [PATCH 27/32] Document the postgres.psql_query function Fixes #16319 --- salt/modules/postgres.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/salt/modules/postgres.py b/salt/modules/postgres.py index 5bfdb08de4ec..86b21b25efb7 100644 --- a/salt/modules/postgres.py +++ b/salt/modules/postgres.py @@ -253,6 +253,27 @@ def psql_query(query, user=None, host=None, port=None, maintenance_db=None, WITH updated AS (UPDATE pg_authid SET rolconnlimit = 2000 WHERE rolname = 'rolename' RETURNING rolconnlimit) SELECT * FROM updated; + query + The query string. + + user + Database username, if different from config or default. + + host + Database host, if different from config or default. + + port + Database port, if different from the config or default. + + maintenance_db + The database to run the query against. + + password + User password, if different from the config or default. + + runas + User to run the command as. + CLI Example: .. code-block:: bash From 7599b18995acc09193b8125d25e08a81094ef4bb Mon Sep 17 00:00:00 2001 From: Blaine Chatman Date: Tue, 24 May 2016 17:56:45 -0700 Subject: [PATCH 28/32] fix jobs.list_jobs failing with search_target --- salt/runners/jobs.py | 10 ++-- tests/unit/runners/jobs_test.py | 87 +++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 tests/unit/runners/jobs_test.py diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index 57d032466353..c522198f962b 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -349,9 +349,13 @@ def list_jobs(ext_source=None, if search_target and _match: _match = False if 'Target' in ret[item]: - for key in salt.utils.split_input(search_target): - if fnmatch.fnmatch(ret[item]['Target'], key): - _match = True + targets = ret[item]['Target'] + if isinstance(targets, basestring): + targets = [targets] + for target in targets: + for key in salt.utils.split_input(search_target): + if fnmatch.fnmatch(target, key): + _match = True if search_function and _match: _match = False diff --git a/tests/unit/runners/jobs_test.py b/tests/unit/runners/jobs_test.py new file mode 100644 index 000000000000..c2893f9e947b --- /dev/null +++ b/tests/unit/runners/jobs_test.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +''' +unit tests for the jobs runner +''' + +# Import Python Libs +from __future__ import absolute_import + +# Import Salt Testing Libs +from salttesting import skipIf, TestCase +from salttesting.helpers import ensure_in_syspath +from salttesting.mock import ( + NO_MOCK, + NO_MOCK_REASON, + patch +) + +ensure_in_syspath('../../') + +# Import Salt Libs +from salt.runners import jobs +import salt.minion + +jobs.__opts__ = {'ext_job_cache': None, 'master_job_cache': 'local_cache'} +jobs.__salt__ = {} + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class JobsTest(TestCase): + ''' + Validate the jobs runner + ''' + def test_list_jobs_with_search_target(self): + ''' + test jobs.list_jobs runner with search_target args + ''' + mock_jobs_cache = { + '20160524035503086853': {'Arguments': [], + 'Function': 'test.ping', + 'StartTime': '2016, May 24 03:55:03.086853', + 'Target': 'node-1-1.com', + 'Target-type': 'glob', + 'User': 'root'}, + '20160524035524895387': {'Arguments': [], + 'Function': 'test.ping', + 'StartTime': '2016, May 24 03:55:24.895387', + 'Target': ['node-1-2.com', 'node-1-1.com'], + 'Target-type': 'list', + 'User': 'sudo_ubuntu'} + } + + def return_mock_jobs(): + return mock_jobs_cache + + class MockMasterMinion(object): + + returners = {'local_cache.get_jids': return_mock_jobs} + + def __init__(self, *args, **kwargs): + pass + + returns = {'all': mock_jobs_cache, + 'node-1-1.com': mock_jobs_cache, + 'node-1-2.com': {'20160524035524895387': + mock_jobs_cache['20160524035524895387']}, + 'non-existant': {}} + + with patch.object(salt.minion, 'MasterMinion', MockMasterMinion): + self.assertEqual(jobs.list_jobs(), returns['all']) + + self.assertEqual(jobs.list_jobs(search_target=['node-1-1*', + 'node-1-2*']), + returns['all']) + + self.assertEqual(jobs.list_jobs(search_target='node-1-1.com'), + returns['node-1-1.com']) + + self.assertEqual(jobs.list_jobs(search_target='node-1-2.com'), + returns['node-1-2.com']) + + self.assertEqual(jobs.list_jobs(search_target='non-existant'), + returns['non-existant']) + + +if __name__ == '__main__': + from integration import run_tests + run_tests(JobsTest, needs_daemon=False) From 9deb70fd8e0d54a088ac97a6fa8bf92626e13432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Wed, 25 May 2016 09:10:04 +0100 Subject: [PATCH 29/32] jobs.exit_success() now works parsing the results of jobs.lookup_id() --- salt/runners/jobs.py | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index b95cc943a0a2..e2b8737fcec7 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -501,31 +501,16 @@ def exit_success(jid, ext_source=None): .. code-block:: bash salt-run jobs.exit_success 20160520145827701627 ''' - ret = {} - mminion = salt.minion.MasterMinion(__opts__) - returner = _get_returner(( - __opts__['ext_job_cache'], - ext_source, - __opts__['master_job_cache'] - )) - - try: - data = list_job( - jid, - ext_source=ext_source - ) - except TypeError: - return ('Requested returner could not be loaded. ' - 'No JIDs could be retrieved.') + ret = dict() - returns = data.get('Result', {}) + data = lookup_jid( + jid, + ext_source=ext_source + ) - for minion in returns: - minion_ret = returns[minion].get('return', {}) - if minion_ret['retcode'] == 0: - ret[minion] = True - else: - ret[minion] = False + for minion in data: + if "retcode" in data[minion]: + ret[minion] = True if not data[minion]['retcode'] else False return ret From a89be5e9d4cf0862f469e858a4c5a5835b75613c Mon Sep 17 00:00:00 2001 From: Mike Place Date: Wed, 25 May 2016 09:02:38 -0700 Subject: [PATCH 30/32] Use six.string_types in jobs runner (#33499) Refs #33491 --- salt/runners/jobs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/runners/jobs.py b/salt/runners/jobs.py index c522198f962b..da31a6af3032 100644 --- a/salt/runners/jobs.py +++ b/salt/runners/jobs.py @@ -350,7 +350,7 @@ def list_jobs(ext_source=None, _match = False if 'Target' in ret[item]: targets = ret[item]['Target'] - if isinstance(targets, basestring): + if isinstance(targets, six.string_types): targets = [targets] for target in targets: for key in salt.utils.split_input(search_target): From 4f96cc1f54bc0d5ab07dd5a544f78183224d298d Mon Sep 17 00:00:00 2001 From: Shane Lee Date: Wed, 25 May 2016 10:26:21 -0600 Subject: [PATCH 31/32] Return full pending computer name (#33483) --- salt/modules/win_system.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/salt/modules/win_system.py b/salt/modules/win_system.py index c89536e47476..cf5edd398139 100644 --- a/salt/modules/win_system.py +++ b/salt/modules/win_system.py @@ -287,7 +287,7 @@ def set_computer_name(name): if windll.kernel32.SetComputerNameExW(win32con.ComputerNamePhysicalDnsHostname, name): - ret = {'Computer Name': {'Current': get_system_info()['name']}} + ret = {'Computer Name': {'Current': get_computer_name()}} pending = get_pending_computer_name() if pending not in (None, False): ret['Computer Name']['Pending'] = pending @@ -315,8 +315,8 @@ def get_pending_computer_name(): ''' current = get_computer_name() pending = read_value('HKLM', - r'SYSTEM\CurrentControlSet\Control\ComputerName\ComputerName', - 'ComputerName')['vdata'] + r'SYSTEM\CurrentControlSet\Services\Tcpip\Parameters', + 'NV Hostname')['vdata'] if pending: return pending if pending != current else None return False @@ -335,7 +335,7 @@ def get_computer_name(): salt 'minion-id' system.get_computer_name ''' - name = get_system_info()['name'] + name = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname) return name if name else False From cfc07f7641340a0ff6ca4c2d8747fcbe1c525fea Mon Sep 17 00:00:00 2001 From: rallytime Date: Wed, 25 May 2016 11:00:12 -0600 Subject: [PATCH 32/32] Add docs about minion config file in standalone minion docs Fixes #15252 --- doc/topics/tutorials/standalone_minion.rst | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/doc/topics/tutorials/standalone_minion.rst b/doc/topics/tutorials/standalone_minion.rst index 33772139c4c7..367ba72e3674 100644 --- a/doc/topics/tutorials/standalone_minion.rst +++ b/doc/topics/tutorials/standalone_minion.rst @@ -17,6 +17,20 @@ things: Otherwise, it will attempt to connect to a master and fail. The salt-call command stands on its own and does not need the salt-minion daemon. + +Minion Configuration +-------------------- + +Throughout this document there are several references to setting different +options to configure a masterless Minion. Salt Minions are easy to configure +via a configuration file that is located, by default, in ``/etc/salt/minion``. +Note, however, that on FreeBSD systems, the minion configuration file is located +in ``/usr/local/etc/salt/minion``. + +You can learn more about minion configuration options in the +:ref:`Configuring the Salt Minion ` docs. + + Telling Salt Call to Run Masterless =================================== @@ -39,7 +53,6 @@ Now the salt-call command will not look for a master and will assume that the local system has all of the file and pillar resources. - Running States Masterless ========================= @@ -81,6 +94,7 @@ it unnecessary to change the configuration file: salt-call state.apply --local + External Pillars ================