From 5b71ace9833b70b5379effaf7ced2e8bce8a1ab9 Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Wed, 24 Oct 2018 15:49:58 +0100 Subject: [PATCH 1/3] add individual syncers and release note --- doc/topics/releases/neon.rst | 3 + salt/states/saltutil.py | 62 ++++++++++++++ tests/unit/modules/test_saltutil.py | 121 ++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 salt/states/saltutil.py create mode 100644 tests/unit/modules/test_saltutil.py diff --git a/doc/topics/releases/neon.rst b/doc/topics/releases/neon.rst index bc9d39b647fd..6b2999ae2cf4 100644 --- a/doc/topics/releases/neon.rst +++ b/doc/topics/releases/neon.rst @@ -217,6 +217,9 @@ State Changes - Added new :py:func:`ssh_auth.manage ` state to ensure only the specified ssh keys are present for the specified user. +- Added new :py:func:`saltutil ` state to use instead of + ``module.run`` to more easily handle change. + Module Changes ============== diff --git a/salt/states/saltutil.py b/salt/states/saltutil.py new file mode 100644 index 000000000000..7611110c6490 --- /dev/null +++ b/salt/states/saltutil.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +''' +Saltutil State +============== + +This state wraps the saltutil execution modules to make them easier to run +from a states. Rather than needing to to use ``module.run`` this state allows for +improved change detection. + + .. versionadded: Neon +''' +from __future__ import absolute_import, unicode_literals, print_function + +import logging + +# Define the module's virtual name +__virtualname__ = 'saltutil' + +log = logging.getLogger(__name__) + + +def __virtual__(): + ''' + Named saltutil + ''' + return __virtualname__ + + +def sync_all(name, **kwargs): + ''' + Performs the same task as saltutil.sync_all module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_all: + - refresh: True + ''' + ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''} + + if __opts__['test']: + ret['result'] = None + ret['comment'] = "saltutil.sync_all would have been run" + return ret + + try: + sync_status = __salt__['saltutil.sync_all'](**kwargs) + for key, value in sync_status.items(): + if value: + ret['changes'][key] = value + ret['comment'] += "Updated {0}. ".format(key) + except Exception as e: + log.error("Failed to run saltutil.sync_all: %s", e) + ret['result'] = False + ret['comment'] = "Failed to run sync_all: {0}".format(e) + return ret + + if not ret['changes']: + ret['comment'] = "No updates to sync" + ret['comment'] = ret['comment'].strip() + return ret diff --git a/tests/unit/modules/test_saltutil.py b/tests/unit/modules/test_saltutil.py new file mode 100644 index 000000000000..e8c296fc46f0 --- /dev/null +++ b/tests/unit/modules/test_saltutil.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +''' + Tests for the saltutil state +''' +# Import Python libs +from __future__ import absolute_import, print_function, unicode_literals + +# Import Salt Testing Libs +from tests.support.mixins import LoaderModuleMockMixin +from tests.support.unit import skipIf, TestCase +from tests.support.mock import ( + NO_MOCK, + NO_MOCK_REASON, + MagicMock, + patch +) + +# Import Salt Libs +import salt.states.saltutil as saltutil + + +@skipIf(NO_MOCK, NO_MOCK_REASON) +class Saltutil(TestCase, LoaderModuleMockMixin): + ''' + Test cases for salt.states.saltutil + ''' + def setup_loader_modules(self): + return {saltutil: {'__opts__': {'test': False}}} + + def test_saltutil_sync_all_nochange(self): + sync_output = { + "clouds": [], + "engines": [], + "grains": [], + "beacons": [], + "utils": [], + "returners": [], + "modules": [], + "renderers": [], + "log_handlers": [], + "thorium": [], + "states": [], + "sdb": [], + "proxymodules": [], + "output": [], + "pillar": [] + } + state_id = 'somename' + state_result = {'changes': {}, + 'comment': 'No updates to sync', + 'name': 'somename', + 'result': True + } + + mock_moduleout = MagicMock(return_value=sync_output) + with patch.dict(saltutil.__salt__, {'saltutil.sync_all': mock_moduleout}): + result = saltutil.sync_all(state_id, refresh=True) + self.assertEqual(result, state_result) + + def test_saltutil_sync_all_test(self): + sync_output = { + "clouds": [], + "engines": [], + "grains": [], + "beacons": [], + "utils": [], + "returners": [], + "modules": [], + "renderers": [], + "log_handlers": [], + "thorium": [], + "states": [], + "sdb": [], + "proxymodules": [], + "output": [], + "pillar": [] + } + state_id = 'somename' + state_result = {'changes': {}, + 'comment': 'saltutil.sync_all would have been run', + 'name': 'somename', + 'result': None + } + + mock_moduleout = MagicMock(return_value=sync_output) + with patch.dict(saltutil.__salt__, {'saltutil.sync_all': mock_moduleout}): + with patch.dict(saltutil.__opts__, {"test": True}): + result = saltutil.sync_all(state_id, refresh=True) + self.assertEqual(result, state_result) + + + def test_saltutil_sync_all_change(self): + sync_output = { + "clouds": [], + "engines": [], + "grains": [], + "beacons": [], + "utils": [], + "returners": [], + "modules": ["modules.file"], + "renderers": [], + "log_handlers": [], + "thorium": [], + "states": ["states.saltutil", "states.ssh_auth"], + "sdb": [], + "proxymodules": [], + "output": [], + "pillar": [] + } + state_id = 'somename' + state_result = {'changes': {'modules': ['modules.file'], + 'states': ['states.saltutil', 'states.ssh_auth']}, + 'comment': 'Updated modules. Updated states.', + 'name': 'somename', + 'result': True + } + + mock_moduleout = MagicMock(return_value=sync_output) + with patch.dict(saltutil.__salt__, {'saltutil.sync_all': mock_moduleout}): + result = saltutil.sync_all(state_id, refresh=True) + self.assertEqual(result, state_result) From 40d9a00d1bb0ad0d9370b9f9a6347f1086301d46 Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Wed, 24 Oct 2018 15:52:33 +0100 Subject: [PATCH 2/3] add individual syncers --- salt/states/saltutil.py | 249 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 249 insertions(+) diff --git a/salt/states/saltutil.py b/salt/states/saltutil.py index 7611110c6490..0a7f36a10aca 100644 --- a/salt/states/saltutil.py +++ b/salt/states/saltutil.py @@ -26,6 +26,31 @@ def __virtual__(): return __virtualname__ +def _sync_single(name, module, **kwargs): + ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''} + + if __opts__['test']: + ret['result'] = None + ret['comment'] = "saltutil.sync_{0} would have been run".format(module) + return ret + + try: + sync_status = __salt__['saltutil.sync_{0}'.format(module)](**kwargs) + if sync_status: + ret['changes'][module] = sync_status + ret['comment'] = "Updated {0}.".format(module) + except Exception as e: + log.error("Failed to run saltutil.sync_%s: %s", module, e) + ret['result'] = False + ret['comment'] = "Failed to run sync_{0}: {1}".format(module, e) + return ret + + if not ret['changes']: + ret['comment'] = "No updates to sync" + + return ret + + def sync_all(name, **kwargs): ''' Performs the same task as saltutil.sync_all module @@ -60,3 +85,227 @@ def sync_all(name, **kwargs): ret['comment'] = "No updates to sync" ret['comment'] = ret['comment'].strip() return ret + + +def sync_beacons(name, **kwargs): + ''' + Performs the same task as saltutil.sync_beacons module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_beacons: + - refresh: True + ''' + return _sync_single(name, "beacons", **kwargs) + + +def sync_clouds(name, **kwargs): + ''' + Performs the same task as saltutil.sync_clouds module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_clouds: + - refresh: True + ''' + return _sync_single(name, "clouds", **kwargs) + + +def sync_engines(name, **kwargs): + ''' + Performs the same task as saltutil.sync_engines module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_engines: + - refresh: True + ''' + return _sync_single(name, "engines", **kwargs) + + +def sync_grains(name, **kwargs): + ''' + Performs the same task as saltutil.sync_grains module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_grains: + - refresh: True + ''' + return _sync_single(name, "grains", **kwargs) + + +def sync_log_handlers(name, **kwargs): + ''' + Performs the same task as saltutil.sync_log_handlers module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_log_handlers: + - refresh: True + ''' + return _sync_single(name, "log_handlers", **kwargs) + + +def sync_modules(name, **kwargs): + ''' + Performs the same task as saltutil.sync_modules module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_modules: + - refresh: True + ''' + return _sync_single(name, "modules", **kwargs) + + +def sync_output(name, **kwargs): + ''' + Performs the same task as saltutil.sync_output module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_output: + - refresh: True + ''' + return _sync_single(name, "output", **kwargs) + + +def sync_outputters(name, **kwargs): + ''' + Performs the same task as saltutil.sync_outputters module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_outputters: + - refresh: True + ''' + return _sync_single(name, "outputters", **kwargs) + + +def sync_pillar(name, **kwargs): + ''' + Performs the same task as saltutil.sync_pillar module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_pillar: + - refresh: True + ''' + return _sync_single(name, "pillar", **kwargs) + + +def sync_proxymodules(name, **kwargs): + ''' + Performs the same task as saltutil.sync_proxymodules module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_proxymodules: + - refresh: True + ''' + return _sync_single(name, "proxymodules", **kwargs) + + +def sync_renderers(name, **kwargs): + ''' + Performs the same task as saltutil.sync_renderers module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_renderers: + - refresh: True + ''' + return _sync_single(name, "renderers", **kwargs) + + +def sync_returners(name, **kwargs): + ''' + Performs the same task as saltutil.sync_returners module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_returners: + - refresh: True + ''' + return _sync_single(name, "returners", **kwargs) + + +def sync_sdb(name, **kwargs): + ''' + Performs the same task as saltutil.sync_sdb module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_sdb: + - refresh: True + ''' + return _sync_single(name, "sdb", **kwargs) + + +def sync_states(name, **kwargs): + ''' + Performs the same task as saltutil.sync_states module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_states: + - refresh: True + ''' + return _sync_single(name, "states", **kwargs) + + +def sync_thorium(name, **kwargs): + ''' + Performs the same task as saltutil.sync_thorium module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_thorium: + - refresh: True + ''' + return _sync_single(name, "thorium", **kwargs) + + +def sync_utils(name, **kwargs): + ''' + Performs the same task as saltutil.sync_utils module + See :mod:`saltutil module for full list of options ` + + .. code-block:: yaml + + sync_everything: + saltutil.sync_utils: + - refresh: True + ''' + return _sync_single(name, "utils", **kwargs) From a59e64c1bcf43a755cd34311df3867b9cd6bc63b Mon Sep 17 00:00:00 2001 From: Christian McHugh Date: Mon, 29 Oct 2018 09:20:24 +0000 Subject: [PATCH 3/3] adjust state comment output and cleanup tests --- salt/states/saltutil.py | 4 ++-- tests/unit/{modules => states}/test_saltutil.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename tests/unit/{modules => states}/test_saltutil.py (98%) diff --git a/salt/states/saltutil.py b/salt/states/saltutil.py index 0a7f36a10aca..99952a1b7d1b 100644 --- a/salt/states/saltutil.py +++ b/salt/states/saltutil.py @@ -74,7 +74,7 @@ def sync_all(name, **kwargs): for key, value in sync_status.items(): if value: ret['changes'][key] = value - ret['comment'] += "Updated {0}. ".format(key) + ret['comment'] = "Sync performed" except Exception as e: log.error("Failed to run saltutil.sync_all: %s", e) ret['result'] = False @@ -83,7 +83,7 @@ def sync_all(name, **kwargs): if not ret['changes']: ret['comment'] = "No updates to sync" - ret['comment'] = ret['comment'].strip() + return ret diff --git a/tests/unit/modules/test_saltutil.py b/tests/unit/states/test_saltutil.py similarity index 98% rename from tests/unit/modules/test_saltutil.py rename to tests/unit/states/test_saltutil.py index e8c296fc46f0..707201e9e2d3 100644 --- a/tests/unit/modules/test_saltutil.py +++ b/tests/unit/states/test_saltutil.py @@ -110,7 +110,7 @@ def test_saltutil_sync_all_change(self): state_id = 'somename' state_result = {'changes': {'modules': ['modules.file'], 'states': ['states.saltutil', 'states.ssh_auth']}, - 'comment': 'Updated modules. Updated states.', + 'comment': 'Sync performed', 'name': 'somename', 'result': True }