Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/57999.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed an ``AttributeError`` (``'OrderedDict' object has no attribute 'startswith'``) from the state compiler when a ``*_in`` requisite triggered the name-resolution fallback scan while an ``exclude`` (by id or sls) was in effect. The scan now skips non-state entries such as ``__exclude__``.
6 changes: 6 additions & 0 deletions salt/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -2058,6 +2058,12 @@ def requisite_in(self, high):
else:
found = False
for _id in iter(high):
# Skip non-state entries such as
# __exclude__ (a list), whose values
# are not state bodies -- mirrors the
# guard in the top-level loop above.
if not isinstance(high[_id], dict):
continue
for st8 in [
_st8
for _st8 in iter(high[_id])
Expand Down
49 changes: 48 additions & 1 deletion tests/pytests/unit/state/test_state_compiler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
:codeauthor: Nicole Thomas <nicole@saltstack.com>
:codeauthor: Nicole Thomas <nicole@saltstack.com>
"""

import logging
Expand Down Expand Up @@ -96,6 +96,53 @@ def test_render_error_on_invalid_requisite(minion_opts):
state_obj.call_high(high_data)


def test_requisite_in_with_exclude_does_not_raise(minion_opts):
"""
requisite_in must not crash when ``__exclude__`` is present and a ``*_in``
requisite forces the name-resolution fallback scan, which iterates every
top-level entry in the high data -- including ``__exclude__`` (a list).
Regression test for #57999 (AttributeError: 'OrderedDict' object has no
attribute 'startswith').
"""
with patch("salt.state.State._gather_pillar"):
high_data = {
"create_file": salt.state.HashableOrderedDict(
[
("file", ["managed", {"name": "/tmp/57999"}]),
("__sls__", "issue_57999"),
("__env__", "base"),
]
),
"notify_on_change": salt.state.HashableOrderedDict(
[
(
"cmd",
[
"run",
{"name": "true"},
# Bare-string target that is not an id, so
# requisite_in falls back to scanning every entry in
# high (by name) -- the scan that used to choke on
# the __exclude__ list.
{"onchanges_in": ["not-an-existing-id"]},
],
),
("__sls__", "issue_57999"),
("__env__", "base"),
]
),
"__exclude__": [{"id": "create_file"}],
}
state_obj = salt.state.State(minion_opts)
# Prior to the fix this raised AttributeError on the __exclude__ entry.
high_ret, errors = state_obj.requisite_in(high_data)
assert errors == []
# __exclude__ is left for apply_exclude to consume later; the real
# states are untouched by requisite_in.
assert "create_file" in high_ret
assert "notify_on_change" in high_ret


def test_verify_onlyif_parse(minion_opts):
low_data = {
"onlyif": [{"fun": "test.arg", "args": ["arg1", "arg2"]}],
Expand Down
Loading