diff --git a/doc/ref/states/requisites.rst b/doc/ref/states/requisites.rst index 18dcb9a0ab42..ec68006313a4 100644 --- a/doc/ref/states/requisites.rst +++ b/doc/ref/states/requisites.rst @@ -832,6 +832,22 @@ For example: In the above case, ``some_check`` will be run prior to _each_ name -- once for ``first_deploy_cmd`` and a second time for ``second_deploy_cmd``. +.. 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. + + .. 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 + .. _onlyif-requisite: onlyif @@ -872,6 +888,28 @@ concept of ``True`` and ``False``. The above example ensures that the stop_volume and delete modules only run 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. + + .. code-block:: yaml + + install apache on redhat based distros: + pkg.latest: + - name: httpd + - onlyif: + - fun: match.grain + tgt: 'os_family: RedHat' + + install apache on debian based distros: + pkg.latest: + - name: apache2 + - onlyif: + - fun: match.grain + tgt: 'os_family: Debian' + runas ~~~~~ diff --git a/salt/state.py b/salt/state.py index 47a830f7134c..883d022d2c98 100644 --- a/salt/state.py +++ b/salt/state.py @@ -882,20 +882,39 @@ def _run_check_onlyif(self, low_data, cmd_opts): low_data_onlyif = [low_data['onlyif']] else: low_data_onlyif = low_data['onlyif'] - for entry in low_data_onlyif: - if not isinstance(entry, six.string_types): - ret.update({'comment': 'onlyif execution failed, bad type passed', 'result': False}) - return ret - cmd = self.functions['cmd.retcode']( - entry, ignore_retcode=True, python_shell=True, **cmd_opts) - log.debug('Last command return code: %s', cmd) + + def _check_cmd(cmd): if cmd != 0 and ret['result'] is False: ret.update({'comment': 'onlyif condition is false', 'skip_watch': True, 'result': True}) - return ret elif cmd == 0: ret.update({'comment': 'onlyif condition is true', 'result': False}) + + for entry in low_data_onlyif: + if isinstance(entry, six.string_types): + cmd = self.functions['cmd.retcode']( + entry, ignore_retcode=True, python_shell=True, **cmd_opts) + log.debug('Last command return code: %s', cmd) + _check_cmd(cmd) + elif isinstance(entry, dict): + if 'fun' not in entry: + ret['comment'] = 'no `fun` argument in onlyif: {0}'.format(entry) + log.warning(ret['comment']) + return ret + result = self.functions[entry.pop('fun')](**entry) + if self.state_con.get('retcode', 0): + _check_cmd(self.state_con['retcode']) + elif not result: + ret.update({'comment': 'onlyif condition is false', + 'skip_watch': True, + 'result': True}) + else: + ret.update({'comment': 'onlyif condition is true', + 'result': False}) + + else: + ret.update({'comment': 'onlyif execution failed, bad type passed', 'result': False}) return ret def _run_check_unless(self, low_data, cmd_opts): @@ -908,20 +927,37 @@ def _run_check_unless(self, low_data, cmd_opts): low_data_unless = [low_data['unless']] else: low_data_unless = low_data['unless'] - for entry in low_data_unless: - if not isinstance(entry, six.string_types): - ret.update({'comment': 'unless condition is false, bad type passed', 'result': False}) - return ret - cmd = self.functions['cmd.retcode']( - entry, ignore_retcode=True, python_shell=True, **cmd_opts) - log.debug('Last command return code: %s', cmd) + + def _check_cmd(cmd): if cmd == 0 and ret['result'] is False: ret.update({'comment': 'unless condition is true', 'skip_watch': True, 'result': True}) elif cmd != 0: ret.update({'comment': 'unless condition is false', 'result': False}) - return ret + + for entry in low_data_unless: + if isinstance(entry, six.string_types): + cmd = self.functions['cmd.retcode'](entry, ignore_retcode=True, python_shell=True, **cmd_opts) + log.debug('Last command return code: %s', cmd) + _check_cmd(cmd) + elif isinstance(entry, dict): + if 'fun' not in entry: + ret['comment'] = 'no `fun` argument in onlyif: {0}'.format(entry) + log.warning(ret['comment']) + return ret + result = self.functions[entry.pop('fun')](**entry) + if self.state_con.get('retcode', 0): + _check_cmd(self.state_con['retcode']) + elif result: + ret.update({'comment': 'unless condition is true', + 'skip_watch': True, + 'result': True}) + else: + ret.update({'comment': 'unless condition is false', + 'result': False}) + else: + ret.update({'comment': 'unless condition is false, bad type passed', 'result': False}) # No reason to stop, return ret return ret diff --git a/tests/integration/modules/test_state.py b/tests/integration/modules/test_state.py index 150e4c3b940f..247211d4d343 100644 --- a/tests/integration/modules/test_state.py +++ b/tests/integration/modules/test_state.py @@ -1424,6 +1424,144 @@ def test_requisites_use_no_state_module(self): for item, descr in six.iteritems(ret): self.assertEqual(descr['comment'], 'onlyif condition is false') + def test_onlyif_req(self): + ret = self.run_function( + 'state.single', + fun='test.succeed_with_changes', + name='onlyif test', + onlyif=[ + {} + ], + )['test_|-onlyif test_|-onlyif test_|-succeed_with_changes'] + self.assertTrue(ret['result']) + self.assertEqual(ret['comment'], 'Success!') + ret = self.run_function( + 'state.single', + fun='test.fail_with_changes', + name='onlyif test', + onlyif=[ + {'fun': 'test.false'}, + ], + )['test_|-onlyif test_|-onlyif test_|-fail_with_changes'] + self.assertTrue(ret['result']) + self.assertFalse(ret['changes']) + self.assertEqual(ret['comment'], 'onlyif condition is false') + ret = self.run_function( + 'state.single', + fun='test.fail_with_changes', + name='onlyif test', + onlyif=[ + {'fun': 'test.true'}, + ], + )['test_|-onlyif test_|-onlyif test_|-fail_with_changes'] + self.assertFalse(ret['result']) + self.assertTrue(ret['changes']) + self.assertEqual(ret['comment'], 'Failure!') + ret = self.run_function( + 'state.single', + fun='test.succeed_without_changes', + name='onlyif test', + onlyif=[ + {'fun': 'test.true'}, + ], + )['test_|-onlyif test_|-onlyif test_|-succeed_without_changes'] + self.assertTrue(ret['result']) + self.assertFalse(ret['changes']) + self.assertEqual(ret['comment'], 'Success!') + + def test_onlyif_req_retcode(self): + ret = self.run_function( + 'state.single', + fun='test.succeed_with_changes', + name='onlyif test', + onlyif=[ + {'fun': 'test.retcode'}, + ], + )['test_|-onlyif test_|-onlyif test_|-succeed_with_changes'] + self.assertTrue(ret['result']) + self.assertFalse(ret['changes']) + self.assertEqual(ret['comment'], 'onlyif condition is false') + ret = self.run_function( + 'state.single', + fun='test.succeed_with_changes', + name='onlyif test', + onlyif=[ + {'fun': 'test.retcode', 'code': 0}, + ], + )['test_|-onlyif test_|-onlyif test_|-succeed_with_changes'] + self.assertTrue(ret['result']) + self.assertTrue(ret['changes']) + self.assertEqual(ret['comment'], 'Success!') + + def test_unless_req(self): + ret = self.run_function( + 'state.single', + fun='test.succeed_with_changes', + name='unless test', + unless=[ + {} + ], + )['test_|-unless test_|-unless test_|-succeed_with_changes'] + self.assertTrue(ret['result']) + self.assertEqual(ret['comment'], 'Success!') + ret = self.run_function( + 'state.single', + fun='test.fail_with_changes', + name='unless test', + unless=[ + {'fun': 'test.true'}, + ], + )['test_|-unless test_|-unless test_|-fail_with_changes'] + self.assertTrue(ret['result']) + self.assertFalse(ret['changes']) + self.assertEqual(ret['comment'], 'unless condition is true') + ret = self.run_function( + 'state.single', + fun='test.fail_with_changes', + name='unless test', + unless=[ + {'fun': 'test.false'}, + ], + )['test_|-unless test_|-unless test_|-fail_with_changes'] + self.assertFalse(ret['result']) + self.assertTrue(ret['changes']) + self.assertEqual(ret['comment'], 'Failure!') + ret = self.run_function( + 'state.single', + fun='test.succeed_without_changes', + name='unless test', + unless=[ + {'fun': 'test.false'}, + ], + )['test_|-unless test_|-unless test_|-succeed_without_changes'] + self.assertTrue(ret['result']) + self.assertFalse(ret['changes']) + self.assertEqual(ret['comment'], 'Success!') + + def test_unless_req_retcode(self): + ret = self.run_function( + 'state.single', + fun='test.succeed_with_changes', + name='unless test', + unless=[ + {'fun': 'test.retcode'}, + ], + )['test_|-unless test_|-unless test_|-succeed_with_changes'] + self.assertTrue(ret['result']) + self.assertTrue(ret['changes']) + self.assertEqual(ret['comment'], 'Success!') + ret = self.run_function( + 'state.single', + fun='test.succeed_with_changes', + name='unless test', + unless=[ + {'fun': 'test.retcode', 'code': 0}, + ], + )['test_|-unless test_|-unless test_|-succeed_with_changes'] + self.assertTrue(ret['result']) + self.assertFalse(ret['changes']) + self.assertEqual(ret['comment'], 'unless condition is true') + def test_get_file_from_env_in_top_match(self): tgt = os.path.join(RUNTIME_VARS.TMP, 'prod-cheese-file') try: