diff --git a/changelog/60990.added b/changelog/60990.added new file mode 100644 index 00000000000..f70dbd54b27 --- /dev/null +++ b/changelog/60990.added @@ -0,0 +1 @@ +Added percent success/failure of state runs in highstate summary output via new state_output_pct option diff --git a/conf/master b/conf/master index 07bf2e9591c..4cca7255097 100644 --- a/conf/master +++ b/conf/master @@ -639,6 +639,10 @@ # will be shown for each state run. #state_output_profile: True +# The state_output_pct setting changes whether success and failure information +# as a percent of total actions will be shown for each state run. +#state_output_pct: False + # Automatically aggregate all states that have support for mod_aggregate by # setting to 'True'. Or pass a list of state module names to automatically # aggregate just those types. diff --git a/conf/minion b/conf/minion index f4d26226832..a82555d292f 100644 --- a/conf/minion +++ b/conf/minion @@ -709,6 +709,10 @@ # will be shown for each state run. #state_output_profile: True +# The state_output_pct setting changes whether success and failure information +# as a percent of total actions will be shown for each state run. +#state_output_pct: False + # Fingerprint of the master public key to validate the identity of your Salt master # before the initial key exchange. The master fingerprint can be found by running # "salt-key -f master.pub" on the Salt master. diff --git a/conf/proxy b/conf/proxy index c0eec84224d..c3ebf89b5d1 100644 --- a/conf/proxy +++ b/conf/proxy @@ -514,6 +514,10 @@ # will be shown for each state run. #state_output_profile: True +# The state_output_pct setting changes whether success and failure information +# as a percent of total actions will be shown for each state run. +#state_output_pct: False + # Fingerprint of the master public key to validate the identity of your Salt master # before the initial key exchange. The master fingerprint can be found by running # "salt-key -F master" on the Salt master. diff --git a/conf/suse/master b/conf/suse/master index fa39d5bc053..42129a7be8e 100644 --- a/conf/suse/master +++ b/conf/suse/master @@ -574,6 +574,10 @@ syndic_user: salt # states is cluttering the logs. Set it to True to ignore them. #state_output_diff: False +# The state_output_pct setting changes whether success and failure information +# as a percent of total actions will be shown for each state run. +#state_output_pct: False + # Automatically aggregate all states that have support for mod_aggregate by # setting to 'True'. Or pass a list of state module names to automatically # aggregate just those types. diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index e4d64f0deb6..c7f772bebd9 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -2565,6 +2565,20 @@ will be shown for each state run. state_output_profile: True +.. conf_master:: state_output_pct + +``state_output_pct`` +------------------------ + +Default: ``False`` + +The ``state_output_pct`` setting changes whether success and failure information +as a percent of total actions will be shown for each state run. + +.. code-block:: yaml + + state_output_pct: False + .. conf_master:: state_aggregate ``state_aggregate`` diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index dcf6a314b7e..5ff6858c687 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -2291,6 +2291,20 @@ will be shown for each state run. state_output_profile: True +.. conf_minion:: state_output_pct + +``state_output_pct`` +------------------------ + +Default: ``False`` + +The ``state_output_pct`` setting changes whether success and failure information +as a percent of total actions will be shown for each state run. + +.. code-block:: yaml + + state_output_pct: False + .. conf_minion:: autoload_dynamic_modules ``autoload_dynamic_modules`` diff --git a/salt/config/__init__.py b/salt/config/__init__.py index 97a7fce2f00..e5b4ca1b1fa 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -372,6 +372,8 @@ VALID_OPTS = immutabletypes.freeze( "state_output_diff": bool, # Tells the highstate outputter whether profile information will be shown for each state run "state_output_profile": bool, + # Tells the highstate outputter whether success and failure percents will be shown for each state run + "state_output_pct": bool, # When true, states run in the order defined in an SLS file, unless requisites re-order them "state_auto_order": bool, # Fire events as state chunks are processed by the state compiler diff --git a/salt/output/highstate.py b/salt/output/highstate.py index e3ba073945d..ca07998747a 100644 --- a/salt/output/highstate.py +++ b/salt/output/highstate.py @@ -10,10 +10,11 @@ Two configurations can be set to modify the highstate outputter. These values can be set in the master config to change the output of the ``salt`` command or set in the minion config to change the output of the ``salt-call`` command. -state_verbose +state_verbose: By default `state_verbose` is set to `True`, setting this to `False` will instruct the highstate outputter to omit displaying anything in green, this means that nothing with a result of True and no changes will not be printed + state_output: The highstate outputter has six output modes, ``full``, ``terse``, ``mixed``, ``changes`` and ``filter`` @@ -55,6 +56,10 @@ state_tabular: output format. If you wish to use a custom format, this can be set to a string. +state_output_pct: + Set `state_output_pct` to `True` in order to add "Success %" and "Failure %" + to the "Summary" section at the end of the highstate output. + Example usage: If ``state_output: filter`` is set in the configuration file: @@ -479,6 +484,46 @@ def _format_host(host, data, indent_level=1): ) ) + if __opts__.get("state_output_pct", False): + # Add success percentages to the summary output + try: + success_pct = round( + ( + (rcounts.get(True, 0) + rcounts.get(None, 0)) + / (sum(rcounts.values()) - rcounts.get("warnings", 0)) + ) + * 100, + 2, + ) + + hstrs.append( + colorfmt.format( + colors["GREEN"], + _counts(f"Success %", success_pct), + colors, + ) + ) + except ZeroDivisionError: + pass + + # Add failure percentages to the summary output + try: + failed_pct = round( + (num_failed / (sum(rcounts.values()) - rcounts.get("warnings", 0))) + * 100, + 2, + ) + + hstrs.append( + colorfmt.format( + colors["RED"] if num_failed else colors["CYAN"], + _counts(f"Failure %", failed_pct), + colors, + ) + ) + except ZeroDivisionError: + pass + num_warnings = rcounts.get("warnings", 0) if num_warnings: hstrs.append( diff --git a/tests/unit/output/test_highstate.py b/tests/unit/output/test_highstate.py index 6f61d08fe3c..a6cb38af26e 100644 --- a/tests/unit/output/test_highstate.py +++ b/tests/unit/output/test_highstate.py @@ -220,3 +220,102 @@ class JsonNestedTestCase(TestCase, LoaderModuleMockMixin): self.assertIn(" Succeeded: 2 (changed=1)", ret) self.assertIn(" Failed: 0", ret) self.assertIn(" Total states run: 2", ret) + + +class HighstatePercentTestCase(TestCase, LoaderModuleMockMixin): + """ + Test cases for salt.output.highstate with state_output_pct set to True + """ + + def setup_loader_modules(self): + return { + highstate: { + "__opts__": { + "extension_modules": "", + "optimization_order": [0, 1, 2], + "color": False, + "state_output_pct": True, + } + } + } + + def setUp(self): + self.data = { + "data": { + "master": { + "salt_|-call_sleep_state_|-call_sleep_state_|-state": { + "__id__": "call_sleep_state", + "__jid__": "20170418153529810135", + "__run_num__": 0, + "__sls__": "orch.simple", + "changes": { + "out": "highstate", + "ret": { + "minion": { + "module_|-simple-ping_|-test.ping_|-run": { + "__id__": "simple-ping", + "__run_num__": 0, + "__sls__": "simple-ping", + "changes": {"ret": True}, + "comment": "Module function test.ping executed", + "duration": 56.179, + "name": "test.ping", + "result": True, + "start_time": "15:35:31.282099", + } + }, + "sub_minion": { + "module_|-simple-ping_|-test.ping_|-run": { + "__id__": "simple-ping", + "__run_num__": 0, + "__sls__": "simple-ping", + "changes": {"ret": True}, + "comment": "Module function test.ping executed", + "duration": 54.103, + "name": "test.ping", + "result": True, + "start_time": "15:35:31.005606", + } + }, + }, + }, + "comment": ( + "States ran successfully. Updating sub_minion, minion." + ), + "duration": 1638.047, + "name": "call_sleep_state", + "result": True, + "start_time": "15:35:29.762657", + }, + "salt_|-cmd_run_example_|-cmd.run_|-function": { + "__id__": "cmd_run_example", + "__jid__": "20200411195112288850", + "__run_num__": 1, + "__sls__": "orch.simple", + "changes": { + "out": "highstate", + "ret": {"minion": "file1\nfile2\nfile3"}, + }, + "comment": ( + "Function ran successfully. Function cmd.run ran on minion." + ), + "duration": 412.397, + "name": "cmd.run", + "result": True, + "start_time": "21:51:12.185868", + }, + } + }, + "outputter": "highstate", + "retcode": 0, + } + self.addCleanup(delattr, self, "data") + + def test_default_output(self): + ret = highstate.output(self.data) + self.assertIn("Succeeded: 1 (changed=1)", ret) + self.assertIn("Failed: 0", ret) + self.assertIn("Success %: 100.0", ret) + self.assertIn("Failure %: 0.0", ret) + self.assertIn("Total states run: 1", ret) + self.assertIn(" file2", ret)