diff --git a/changelog/66783.fixed.md b/changelog/66783.fixed.md new file mode 100644 index 000000000000..2bd08e3411d3 --- /dev/null +++ b/changelog/66783.fixed.md @@ -0,0 +1 @@ +fix yaml output diff --git a/salt/state.py b/salt/state.py index d60d78c334fe..e515e368b180 100644 --- a/salt/state.py +++ b/salt/state.py @@ -51,7 +51,7 @@ from salt.serializers.msgpack import deserialize as msgpack_deserialize from salt.serializers.msgpack import serialize as msgpack_serialize from salt.template import compile_template, compile_template_str -from salt.utils.odict import DefaultOrderedDict, OrderedDict +from salt.utils.odict import DefaultOrderedDict, HashableOrderedDict log = logging.getLogger(__name__) @@ -127,11 +127,6 @@ ).union(STATE_RUNTIME_KEYWORDS) -class HashableOrderedDict(OrderedDict): - def __hash__(self): - return id(self) - - def split_low_tag(tag): """ Take a low tag and split it back into the low dict that it came from diff --git a/salt/utils/odict.py b/salt/utils/odict.py index 2834f1d92469..11a3f3a30977 100644 --- a/salt/utils/odict.py +++ b/salt/utils/odict.py @@ -62,3 +62,8 @@ def __repr__(self, _repr_running={}): # pylint: disable=W0102 return "DefaultOrderedDict({}, {})".format( self.default_factory, super().__repr__() ) + + +class HashableOrderedDict(OrderedDict): + def __hash__(self): + return id(self) diff --git a/salt/utils/yamldumper.py b/salt/utils/yamldumper.py index 8c6e40394a35..8e694ab4a763 100644 --- a/salt/utils/yamldumper.py +++ b/salt/utils/yamldumper.py @@ -13,7 +13,7 @@ import yaml # pylint: disable=blacklisted-import import salt.utils.context -from salt.utils.odict import OrderedDict +from salt.utils.odict import HashableOrderedDict, OrderedDict try: from yaml import CDumper as Dumper @@ -71,7 +71,9 @@ def represent_undefined(dumper, data): OrderedDumper.add_representer(OrderedDict, represent_ordereddict) +OrderedDumper.add_representer(HashableOrderedDict, represent_ordereddict) SafeOrderedDumper.add_representer(OrderedDict, represent_ordereddict) +SafeOrderedDumper.add_representer(HashableOrderedDict, represent_ordereddict) SafeOrderedDumper.add_representer(None, represent_undefined) OrderedDumper.add_representer( diff --git a/tests/unit/utils/test_yamldumper.py b/tests/unit/utils/test_yamldumper.py index 9a1a6ab103ba..65c900ebb25e 100644 --- a/tests/unit/utils/test_yamldumper.py +++ b/tests/unit/utils/test_yamldumper.py @@ -2,7 +2,11 @@ Unit tests for salt.utils.yamldumper """ +from collections import OrderedDict, defaultdict + import salt.utils.yamldumper +from salt.utils.context import NamespacedDictWrapper +from salt.utils.odict import HashableOrderedDict from tests.support.unit import TestCase @@ -35,3 +39,80 @@ def test_yaml_safe_dump(self): salt.utils.yamldumper.safe_dump(data, default_flow_style=False) == "foo: bar\n" ) + + def test_yaml_ordered_dump(self): + """ + Test yaml.dump with OrderedDict + """ + data = OrderedDict([("foo", "bar"), ("baz", "qux")]) + exp_yaml = "{foo: bar, baz: qux}\n" + assert ( + salt.utils.yamldumper.dump(data, Dumper=salt.utils.yamldumper.OrderedDumper) + == exp_yaml + ) + + def test_yaml_safe_ordered_dump(self): + """ + Test yaml.safe_dump with OrderedDict + """ + data = OrderedDict([("foo", "bar"), ("baz", "qux")]) + exp_yaml = "{foo: bar, baz: qux}\n" + assert salt.utils.yamldumper.safe_dump(data) == exp_yaml + + def test_yaml_indent_safe_ordered_dump(self): + """ + Test yaml.dump with IndentedSafeOrderedDumper + """ + data = OrderedDict([("foo", ["bar", "baz"]), ("qux", "quux")]) + exp_yaml = "foo:\n- bar\n- baz\nqux: quux\n" + assert ( + salt.utils.yamldumper.dump( + data, + Dumper=salt.utils.yamldumper.IndentedSafeOrderedDumper, + default_flow_style=False, + ) + == exp_yaml + ) + + def test_yaml_defaultdict_dump(self): + """ + Test yaml.dump with defaultdict + """ + data = defaultdict(list) + data["foo"].append("bar") + exp_yaml = "foo: [bar]\n" + assert salt.utils.yamldumper.safe_dump(data) == exp_yaml + + def test_yaml_namespaced_dict_wrapper_dump(self): + """ + Test yaml.dump with NamespacedDictWrapper + """ + data = NamespacedDictWrapper({"test": {"foo": "bar"}}, "test") + exp_yaml = ( + "!!python/object/new:salt.utils.context.NamespacedDictWrapper\n" + "dictitems: {foo: bar}\n" + "state:\n" + " _NamespacedDictWrapper__dict:\n" + " test: {foo: bar}\n" + " pre_keys: !!python/tuple [test]\n" + ) + assert salt.utils.yamldumper.dump(data) == exp_yaml + + def test_yaml_undefined_dump(self): + """ + Test yaml.safe_dump with None + """ + data = {"foo": None} + exp_yaml = "{foo: null}\n" + assert salt.utils.yamldumper.safe_dump(data) == exp_yaml + + def test_yaml_hashable_ordered_dict_dump(self): + """ + Test yaml.dump with HashableOrderedDict + """ + data = HashableOrderedDict([("foo", "bar"), ("baz", "qux")]) + exp_yaml = "{foo: bar, baz: qux}\n" + assert ( + salt.utils.yamldumper.dump(data, Dumper=salt.utils.yamldumper.OrderedDumper) + == exp_yaml + )