diff --git a/changelog/62932.fixed b/changelog/62932.fixed index 3eb317d134b2..c4a737a7a0d2 100644 --- a/changelog/62932.fixed +++ b/changelog/62932.fixed @@ -31,3 +31,7 @@ but can be previewed now by setting the option `yaml_compatibility` to `3007`: instead of `yaml.Dumper`. * Dumping a YAML sequence with `salt.utils.yaml.IndentedSafeOrderedDumper` will be properly indented. + * Dumping an object with an unsupported type via `salt.utils.yaml.safe_dump()` + (or via `dump()` with the `SafeOrderedDumper` or `IndentedSafeOrderedDumper` + classes) will raise an exception. (Currently such objects produce a null + node.) diff --git a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst index f0118bb4fc88..a49d682a2f65 100644 --- a/doc/topics/troubleshooting/yaml_idiosyncrasies.rst +++ b/doc/topics/troubleshooting/yaml_idiosyncrasies.rst @@ -23,6 +23,15 @@ unexpected times. YAML compatibility warnings can be disabled by setting the ``yaml_compatibility_warnings`` option to False. +.. versionchanged:: 3007.0 + + Dumping an object of unsupported type to YAML with a one of the safe dumpers + (:py:func:`~salt.utils.yaml.safe_dump`, or :py:func:`~salt.utils.yaml.dump` + with the :py:class:`~salt.utils.yaml.SafeOrderedDumper` or + :py:class:`~salt.utils.yaml.IndentedSafeOrderedDumper` classes) now raises + an exception. Previously it produced a YAML ``NULL`` node. Set the + ``yaml_compatibility`` option to 3006 to revert to the previous behavior. + Spaces vs Tabs ============== diff --git a/salt/utils/yamldumper.py b/salt/utils/yamldumper.py index 54365f092a22..121ac038fa63 100644 --- a/salt/utils/yamldumper.py +++ b/salt/utils/yamldumper.py @@ -133,9 +133,7 @@ def _rep_default(self, data): return self.represent_scalar("tag:yaml.org,2002:null", "NULL") -# TODO: Why does this registration exist? Isn't it better to raise an exception -# for unsupported types? -_CommonMixin.add_representer(None, _CommonMixin._rep_default) +_CommonMixin.V3006.add_representer(None, _CommonMixin._rep_default) _CommonMixin.V3006.add_representer( OrderedDict, _CommonMixin._rep_ordereddict_as_plain_map ) diff --git a/tests/pytests/unit/utils/test_yaml.py b/tests/pytests/unit/utils/test_yaml.py index 8f53974f3666..59824b831d7a 100644 --- a/tests/pytests/unit/utils/test_yaml.py +++ b/tests/pytests/unit/utils/test_yaml.py @@ -237,6 +237,43 @@ def test_dump_tuple(mktuple, dumpercls): assert got == want +@pytest.mark.parametrize("obj", [type("Generated", (), {})()]) +@pytest.mark.parametrize( + "yaml_compatibility,dumpercls,want", + [ + ( + 3006, + salt_yaml.OrderedDumper, + "!!python/object:tests.pytests.unit.utils.test_yaml.Generated {}\n", + ), + # With v3006, sometimes it prints "..." on a new line as an end of + # document indicator depending on whether yaml.CSafeDumper is available + # on the system or not (yaml.CSafeDumper and yaml.SafeDumper do not + # always behave the same). + (3006, salt_yaml.SafeOrderedDumper, re.compile(r"NULL\n(?:\.\.\.\n)?")), + (3006, salt_yaml.IndentedSafeOrderedDumper, re.compile(r"NULL\n(?:\.\.\.\n)?")), + ( + 3007, + salt_yaml.OrderedDumper, + "!!python/object:tests.pytests.unit.utils.test_yaml.Generated {}\n", + ), + (3007, salt_yaml.SafeOrderedDumper, yaml.representer.RepresenterError), + (3007, salt_yaml.IndentedSafeOrderedDumper, yaml.representer.RepresenterError), + ], + indirect=["yaml_compatibility"], +) +def test_dump_unsupported(yaml_compatibility, dumpercls, obj, want): + if isinstance(want, type) and issubclass(want, Exception): + with pytest.raises(want): + salt_yaml.dump(obj, Dumper=dumpercls) + else: + got = salt_yaml.dump(obj, Dumper=dumpercls) + try: + assert want.fullmatch(got) + except AttributeError: + assert got == want + + def render_yaml(data): """ Takes a YAML string, puts it into a mock file, passes that to the YAML