diff --git a/changelog/65104.fixed.md b/changelog/65104.fixed.md new file mode 100644 index 00000000000..020b990b630 --- /dev/null +++ b/changelog/65104.fixed.md @@ -0,0 +1 @@ +The 'profile' outputter does not crash with incorrectly formatted data diff --git a/salt/output/profile.py b/salt/output/profile.py index 8f54c7017f4..87a93c17bba 100644 --- a/salt/output/profile.py +++ b/salt/output/profile.py @@ -44,12 +44,19 @@ def _find_durations(data, name_max=60): ml = len("duration (ms)") for host in data: for sid in data[host]: - dat = data[host][sid] - ts = sid.split("_|-") + try: + dat = data[host][sid] + except TypeError: + dat = {} + + if not isinstance(dat, dict): + dat = {"name": str(sid)} + + ts = str(sid).split("_|-") mod = ts[0] fun = ts[-1] name = dat.get("name", dat.get("__id__")) - dur = float(data[host][sid].get("duration", -1)) + dur = float(dat.get("duration", -1)) if name is None: name = "<>" diff --git a/tests/pytests/unit/output/test_profile.py b/tests/pytests/unit/output/test_profile.py new file mode 100644 index 00000000000..8a6234c1821 --- /dev/null +++ b/tests/pytests/unit/output/test_profile.py @@ -0,0 +1,94 @@ +import pytest + +import salt.output.profile as profile + + +@pytest.fixture +def configure_loader_modules(): + return {profile: {"__opts__": {"extension_modules": "", "color": False}}} + + +def test_no_states_found(): + """ + Simulate the result of the "profile" outputter with state.apply. + i.e. salt-call --local state.apply --output=profile + """ + data = { + "local": { + "no_|-states_|-states_|-None": { + "result": False, + "comment": "No Top file or master_tops data matches found. Please see master log for details.", + "name": "No States", + "changes": {}, + "__run_num__": 0, + } + } + } + + expected_output = ( + " ---------------------------------------\n" + " | name | mod.fun | duration (ms) |\n" + " ---------------------------------------\n" + " | No States | no.None | -1.0000 |\n" + " ---------------------------------------" + ) + + ret = profile.output(data) + assert expected_output in ret + + +def test_no_matching_sls(): + """ + Simulate the result of the "profile" outputter with state.sls. + i.e. salt-call --local state.sls foo --output=profile + """ + data = {"local": ["No matching sls found for 'foo' in env 'base'"]} + + expected_output = ( + " ---------------------------------------------------------------------------\n" + " | name | mod.fun | duration (ms) |\n" + " ---------------------------------------------------------------------------\n" + " | <> | No matching sls found for 'foo' in env 'base'.No | -1.0000 |\n" + " ---------------------------------------------------------------------------" + ) + + ret = profile.output(data) + assert expected_output in ret + + +def test_output_with_grains_data(): + """ + Simulate the result of the "profile" outputter with grains data. + i.e. salt-call --local grains.items --output=profile + """ + grains_data = { + "local": { + "dns": {"nameservers": ["0.0.0.0", "1.1.1.1"], "search": ["dns.com"]}, + "fqdns": [], + "disks": ["sda"], + "ssds": ["nvme0n1"], + "shell": "/bin/bash", + "efi-secure-boot": False, + } + } + + ret = profile.output(grains_data) + expected_ret = ( + " ---------------------------------------------------------------------\n" + " | name | mod.fun | duration (ms) |\n" + " ---------------------------------------------------------------------\n" + " | <> | dns.dns | -1.0000 |\n" + " ---------------------------------------------------------------------\n" + " | disks | disks.disks | -1.0000 |\n" + " ---------------------------------------------------------------------\n" + " | efi-secure-boot | efi-secure-boot.efi-secure-boot | -1.0000 |\n" + " ---------------------------------------------------------------------\n" + " | fqdns | fqdns.fqdns | -1.0000 |\n" + " ---------------------------------------------------------------------\n" + " | shell | shell.shell | -1.0000 |\n" + " ---------------------------------------------------------------------\n" + " | ssds | ssds.ssds | -1.0000 |\n" + " ---------------------------------------------------------------------" + ) + + assert ret == expected_ret