diff --git a/doc/ref/states/requisites.rst b/doc/ref/states/requisites.rst index 0c2779e09f61..abe4e677d25b 100644 --- a/doc/ref/states/requisites.rst +++ b/doc/ref/states/requisites.rst @@ -852,8 +852,8 @@ In the above case, ``some_check`` will be run prior to _each_ name -- once for .. versionchanged:: Neon The ``unless`` requisite can take a module as a dictionary field in unless. The dictionary must contain an argument ``fun`` which is the module that is - being run, and everything else passed in will be kwargs passed to the module - function. + being run, and everything else must be passed in under the args key or will + be passed as individual kwargs to the module function. .. code-block:: yaml @@ -865,6 +865,18 @@ In the above case, ``some_check`` will be run prior to _each_ name -- once for - fun: file.file_exists path: /usr/local/bin/whatever + .. code-block:: yaml + + set mysql root password: + debconf.set: + - name: mysql-server-5.7 + - data: + 'mysql-server/root_password': {'type': 'password', 'value': {{pillar['mysql.pass']}} } + - unless: + - fun: pkg.version + args: + - mysql-server-5.7 + .. _onlyif-requisite: onlyif @@ -908,8 +920,8 @@ if the gluster commands return a 0 ret value. .. versionchanged:: Neon The ``onlyif`` requisite can take a module as a dictionary field in onlyif. The dictionary must contain an argument ``fun`` which is the module that is - being run, and everything else passed in will be kwargs passed to the module - function. + being run, and everything else must be passed in under the args key or will + be passed as individual kwargs to the module function. .. code-block:: yaml @@ -927,6 +939,17 @@ if the gluster commands return a 0 ret value. - fun: match.grain tgt: 'os_family: Debian' + .. code-block:: yaml + + arbitrary file example: + file.touch: + - name: /path/to/file + - onlyif: + - fun: file.search + args: + - /etc/crontab + - 'entry1' + runas ~~~~~ diff --git a/doc/topics/releases/neon.rst b/doc/topics/releases/neon.rst index 5fc216d04f32..51b1664835e0 100644 --- a/doc/topics/releases/neon.rst +++ b/doc/topics/releases/neon.rst @@ -175,6 +175,36 @@ New output: Skipped: 0 +Unless and onlyif Enhancements +============================== + +The ``unless`` and ``onlyif`` requisites can now be operated with salt modules. +The dictionary must contain an argument ``fun`` which is the module that is +being run, and everything else must be passed in under the args key or will be +passed as individual kwargs to the module function. + +.. code-block:: yaml + + install apache on debian based distros: + cmd.run: + - name: make install + - cwd: /path/to/dir/whatever-2.1.5/ + - unless: + - fun: file.file_exists + path: /usr/local/bin/whatever + +.. code-block:: yaml + + set mysql root password: + debconf.set: + - name: mysql-server-5.7 + - data: + 'mysql-server/root_password': {'type': 'password', 'value': {{pillar['mysql.pass']}} } + - unless: + - fun: pkg.version + args: + - mysql-server-5.7 + Keystore State and Module ========================= diff --git a/salt/state.py b/salt/state.py index 758cad492bf7..0cfc38fe38e7 100644 --- a/salt/state.py +++ b/salt/state.py @@ -905,7 +905,11 @@ def _check_cmd(cmd): ret['comment'] = 'no `fun` argument in onlyif: {0}'.format(entry) log.warning(ret['comment']) return ret - result = self.functions[entry.pop('fun')](**entry) + + if 'args' in entry: + result = self.functions[entry.pop('fun')](*entry.pop('args'), **entry) + else: + result = self.functions[entry.pop('fun')](**entry) if self.state_con.get('retcode', 0): _check_cmd(self.state_con['retcode']) elif not result: @@ -946,10 +950,14 @@ def _check_cmd(cmd): _check_cmd(cmd) elif isinstance(entry, dict): if 'fun' not in entry: - ret['comment'] = 'no `fun` argument in onlyif: {0}'.format(entry) + ret['comment'] = 'no `fun` argument in unless: {0}'.format(entry) log.warning(ret['comment']) return ret - result = self.functions[entry.pop('fun')](**entry) + + if 'args' in entry: + result = self.functions[entry.pop('fun')](*entry.pop('args'), **entry) + else: + result = self.functions[entry.pop('fun')](**entry) if self.state_con.get('retcode', 0): _check_cmd(self.state_con['retcode']) elif result: diff --git a/tests/unit/test_state.py b/tests/unit/test_state.py index 78292e17b94f..19e180dd7946 100644 --- a/tests/unit/test_state.py +++ b/tests/unit/test_state.py @@ -129,6 +129,72 @@ def test_render_requisite_require_in_disabled(self): run_num = ret['test_|-step_one_|-step_one_|-succeed_with_changes']['__run_num__'] self.assertEqual(run_num, 0) + def test_verify_onlyif_parse(self): + low_data = { + "onlyif": [ + { + "fun": "file.search", + "args": [ + "/etc/crontab", + "run-parts" + ] + } + ], + "name": "mysql-server-5.7", + "state": "debconf", + "__id__": "set root password", + "fun": "set", + "__env__": "base", + "__sls__": "debconf", + "data": { + "mysql-server/root_password": { + "type": "password", + "value": "temp123" + } + }, + "order": 10000 + } + expected_result = {'comment': 'onlyif condition is true', 'result': False} + + with patch('salt.state.State._gather_pillar') as state_patch: + minion_opts = self.get_temp_config('minion') + state_obj = salt.state.State(minion_opts) + return_result = state_obj._run_check_onlyif(low_data, '') + self.assertEqual(expected_result, return_result) + + def test_verify_unless_parse(self): + low_data = { + "unless": [ + { + "fun": "file.search", + "args": [ + "/etc/crontab", + "run-parts" + ] + } + ], + "name": "mysql-server-5.7", + "state": "debconf", + "__id__": "set root password", + "fun": "set", + "__env__": "base", + "__sls__": "debconf", + "data": { + "mysql-server/root_password": { + "type": "password", + "value": "temp123" + } + }, + "order": 10000 + } + expected_result = {'comment': 'unless condition is true', 'result': True, 'skip_watch': True} + + with patch('salt.state.State._gather_pillar') as state_patch: + minion_opts = self.get_temp_config('minion') + state_obj = salt.state.State(minion_opts) + return_result = state_obj._run_check_unless(low_data, '') + self.assertEqual(expected_result, return_result) + class HighStateTestCase(TestCase, AdaptedConfigurationTestCaseMixin): def setUp(self):