Skip to content

Commit fa5de69

Browse files
Uniquify requisite_in'd states checks.
Fix #68408
1 parent 5bdbaae commit fa5de69

File tree

2 files changed

+299
-32
lines changed

2 files changed

+299
-32
lines changed

salt/state.py

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1903,6 +1903,41 @@ def apply_exclude(self, high):
19031903
high.pop(id_)
19041904
return high
19051905

1906+
@staticmethod
1907+
def _add_to_extend(
1908+
extend,
1909+
id_to_extend,
1910+
state_mod_name,
1911+
req_type,
1912+
req_id,
1913+
req_state_mod_name,
1914+
):
1915+
"""
1916+
Add requisiste `req_id` as a requisite for `id_to_extend`
1917+
"""
1918+
1919+
if id_to_extend not in extend:
1920+
# The state does not exist yet: create it
1921+
extend[id_to_extend] = HashableOrderedDict()
1922+
1923+
if (req_states := extend[id_to_extend].get(req_state_mod_name)) is None:
1924+
# The requisited state is not present yet, create it and initialize it
1925+
extend[id_to_extend][req_state_mod_name] = [
1926+
{req_type: [{state_mod_name: req_id}]}
1927+
]
1928+
return
1929+
1930+
# Lookup req_type if req_states
1931+
for state_arg in req_states:
1932+
if (req_items := state_arg.get(req_type)) is not None:
1933+
for req_item in req_items:
1934+
# (state_mode_name, req_id) is already defined as a requisiste
1935+
if req_item.get(state_mod_name) == req_id:
1936+
break
1937+
else:
1938+
# Extending again
1939+
state_arg[req_type].append({state_mod_name: req_id})
1940+
19061941
def requisite_in(self, high):
19071942
"""
19081943
Extend the data reference with requisite_in arguments
@@ -1953,11 +1988,7 @@ def requisite_in(self, high):
19531988
if isinstance(items, dict):
19541989
# Formatted as a single req_in
19551990
for _state, name in items.items():
1956-
19571991
# Not a use requisite_in
1958-
found = False
1959-
if name not in extend:
1960-
extend[name] = HashableOrderedDict()
19611992
if "." in _state:
19621993
errors.append(
19631994
"Invalid requisite in {}: {} for "
@@ -1971,21 +2002,13 @@ def requisite_in(self, high):
19712002
)
19722003
)
19732004
_state = _state.split(".")[0]
1974-
if _state not in extend[name]:
1975-
extend[name][_state] = []
2005+
found = self._add_to_extend(
2006+
extend, name, _state, rkey, id_, state
2007+
)
19762008
extend[name]["__env__"] = body["__env__"]
19772009
extend[name]["__sls__"] = body["__sls__"]
1978-
for ind in range(len(extend[name][_state])):
1979-
if next(iter(extend[name][_state][ind])) == rkey:
1980-
# Extending again
1981-
extend[name][_state][ind][rkey].append(
1982-
{state: id_}
1983-
)
1984-
found = True
19852010
if found:
19862011
continue
1987-
# The rkey is not present yet, create it
1988-
extend[name][_state].append({rkey: [{state: id_}]})
19892012

19902013
if isinstance(items, list):
19912014
# Formed as a list of requisite additions
@@ -2123,27 +2146,15 @@ def requisite_in(self, high):
21232146
continue
21242147
extend[id_][state].append(arg)
21252148
continue
2126-
found = False
2127-
if name not in extend:
2128-
extend[name] = HashableOrderedDict()
2129-
if _state not in extend[name]:
2130-
extend[name][_state] = []
2149+
found = self._add_to_extend(
2150+
extend, name, _state, rkey, id_, state
2151+
)
2152+
21312153
extend[name]["__env__"] = body["__env__"]
21322154
extend[name]["__sls__"] = body["__sls__"]
2133-
for ind in range(len(extend[name][_state])):
2134-
if (
2135-
next(iter(extend[name][_state][ind]))
2136-
== rkey
2137-
):
2138-
# Extending again
2139-
extend[name][_state][ind][rkey].append(
2140-
{state: id_}
2141-
)
2142-
found = True
2155+
21432156
if found:
21442157
continue
2145-
# The rkey is not present yet, create it
2146-
extend[name][_state].append({rkey: [{state: id_}]})
21472158
high["__extend__"] = []
21482159
for key, val in extend.items():
21492160
high["__extend__"].append({key: val})
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import logging
2+
from copy import deepcopy
3+
4+
import pytest
5+
6+
import salt.config
7+
import salt.state
8+
from salt.utils.odict import HashableOrderedDict
9+
10+
log = logging.getLogger(__name__)
11+
12+
13+
pytestmark = [
14+
pytest.mark.core_test,
15+
]
16+
17+
18+
@pytest.fixture
19+
def minion_config(minion_opts):
20+
minion_opts["file_client"] = "local"
21+
minion_opts["id"] = "foo01"
22+
return minion_opts
23+
24+
25+
single_extend_test_cases = [
26+
(
27+
{}, # extend
28+
"bar", # id_to_extend
29+
"file", # state_mod_name
30+
"require", # req_type
31+
"foo", # req_id
32+
"file", # req_state_mod_name
33+
{
34+
"bar": HashableOrderedDict({"file": [{"require": [{"file": "foo"}]}]})
35+
}, # expected
36+
)
37+
]
38+
39+
simple_extend_test_cases = [
40+
(
41+
{}, # extend
42+
"bar", # id_to_extend
43+
"file", # state_mod_name
44+
"require", # req_type
45+
"foo", # req_id
46+
"file", # req_state_mod_name
47+
{"bar": {"file": [{"require": [{"file": "foo"}]}]}}, # expected
48+
),
49+
(
50+
{ # extend
51+
"bar": {
52+
"file": [{"require": [{"file": "foo"}]}],
53+
"__env__": "base",
54+
"__sls__": "test.foo",
55+
},
56+
"baz": {
57+
"file": [{"require": [{"file": "foo"}]}],
58+
"__env__": "base",
59+
"__sls__": "test.foo",
60+
},
61+
},
62+
"baz", # id_to_extend
63+
"file", # state_mod_name
64+
"require", # req_type
65+
"foo", # req_id
66+
"file", # req_state_mod_name
67+
{ # expected
68+
"bar": {
69+
"file": [{"require": [{"file": "foo"}]}],
70+
"__env__": "base",
71+
"__sls__": "test.foo",
72+
},
73+
"baz": {
74+
"file": [{"require": [{"file": "foo"}]}],
75+
"__env__": "base",
76+
"__sls__": "test.foo",
77+
},
78+
},
79+
),
80+
(
81+
{ # extend
82+
"/tmp/bar": HashableOrderedDict(
83+
[
84+
("file", [{"require": [{"file": "foo"}]}]),
85+
("__env__", "base"),
86+
("__sls__", "test.foo"),
87+
]
88+
),
89+
"baz": HashableOrderedDict(
90+
[
91+
("file", [{"require": [{"file": "foo"}]}]),
92+
("__env__", "base"),
93+
("__sls__", "test.foo"),
94+
]
95+
),
96+
"foo": HashableOrderedDict(
97+
[("file", [{"prerequired": [{"pkg": "quux-pkg"}]}])]
98+
),
99+
"quux-pkg": HashableOrderedDict(
100+
[
101+
("file", [{"prereq": [{"pkg": "foo"}]}]),
102+
("__env__", "base"),
103+
("__sls__", "test.foo"),
104+
]
105+
),
106+
},
107+
"/tmp/baz", # id_to_extend
108+
"file", # state_mod_name
109+
"require", # req_type
110+
"bar", # req_id
111+
"file", # req_state_mod_name
112+
{ # expected
113+
"/tmp/bar": HashableOrderedDict(
114+
[
115+
("file", [{"require": [{"file": "foo"}]}]),
116+
("__env__", "base"),
117+
("__sls__", "test.foo"),
118+
]
119+
),
120+
"baz": HashableOrderedDict(
121+
[
122+
("file", [{"require": [{"file": "foo"}]}]),
123+
("__env__", "base"),
124+
("__sls__", "test.foo"),
125+
]
126+
),
127+
"foo": HashableOrderedDict(
128+
[("file", [{"prerequired": [{"pkg": "quux-pkg"}]}])]
129+
),
130+
"quux-pkg": HashableOrderedDict(
131+
[
132+
("file", [{"prereq": [{"pkg": "foo"}]}]),
133+
("__env__", "base"),
134+
("__sls__", "test.foo"),
135+
]
136+
),
137+
"/tmp/baz": HashableOrderedDict(
138+
[("file", [{"require": [{"file": "bar"}]}])]
139+
),
140+
},
141+
),
142+
(
143+
{
144+
"bar": HashableOrderedDict(
145+
[
146+
("file", [{"require": [{"file": "foo"}]}]),
147+
("__env__", "base"),
148+
("__sls__", "test.foo"),
149+
]
150+
),
151+
"baz": HashableOrderedDict(
152+
[
153+
("file", [{"require": [{"file": "foo"}]}]),
154+
("__env__", "base"),
155+
("__sls__", "test.foo"),
156+
]
157+
),
158+
"foo": HashableOrderedDict(
159+
[("file", [{"prerequired": [{"pkg": "quux"}]}])]
160+
),
161+
"quux": HashableOrderedDict(
162+
[
163+
("file", [{"prereq": [{"pkg": "foo"}]}]),
164+
("__env__", "base"),
165+
("__sls__", "test.foo"),
166+
]
167+
),
168+
},
169+
"baz",
170+
"file",
171+
"require",
172+
"bar",
173+
"file",
174+
{
175+
"bar": HashableOrderedDict(
176+
[
177+
("file", [{"require": [{"file": "foo"}]}]),
178+
("__env__", "base"),
179+
("__sls__", "test.foo"),
180+
]
181+
),
182+
"baz": HashableOrderedDict(
183+
[
184+
("file", [{"require": [{"file": "foo"}, {"file": "bar"}]}]),
185+
("__env__", "base"),
186+
("__sls__", "test.foo"),
187+
]
188+
),
189+
"foo": HashableOrderedDict(
190+
[("file", [{"prerequired": [{"pkg": "quux"}]}])]
191+
),
192+
"quux": HashableOrderedDict(
193+
[
194+
("file", [{"prereq": [{"pkg": "foo"}]}]),
195+
("__env__", "base"),
196+
("__sls__", "test.foo"),
197+
]
198+
),
199+
},
200+
),
201+
(
202+
{
203+
"bar": HashableOrderedDict(
204+
[
205+
("file", [{"require": [{"file": "foo"}]}]),
206+
("__env__", "base"),
207+
("__sls__", "test.foo"),
208+
]
209+
),
210+
"baz": HashableOrderedDict(
211+
[
212+
("file", [{"require": [{"file": "foo"}]}]),
213+
("__env__", "base"),
214+
("__sls__", "test.foo"),
215+
]
216+
),
217+
},
218+
"baz",
219+
"file",
220+
"require",
221+
"bar",
222+
"file",
223+
{
224+
"bar": HashableOrderedDict(
225+
[
226+
("file", [{"require": [{"file": "foo"}]}]),
227+
("__env__", "base"),
228+
("__sls__", "test.foo"),
229+
]
230+
),
231+
"baz": HashableOrderedDict(
232+
[
233+
("file", [{"require": [{"file": "foo"}, {"file": "bar"}]}]),
234+
("__env__", "base"),
235+
("__sls__", "test.foo"),
236+
]
237+
),
238+
},
239+
),
240+
]
241+
242+
243+
@pytest.mark.parametrize(
244+
"extend,id_to_extend,state_mod_name,req_type,req_id,req_state_mod_name,expected",
245+
simple_extend_test_cases,
246+
)
247+
def test_simple_extend(
248+
extend, id_to_extend, state_mod_name, req_type, req_id, req_state_mod_name, expected
249+
):
250+
# local copy of extend, as it is modified by _add_to_extend
251+
_extend = deepcopy(extend)
252+
253+
salt.state.State._add_to_extend(
254+
_extend, id_to_extend, state_mod_name, req_type, req_id, req_state_mod_name
255+
)
256+
assert _extend == expected

0 commit comments

Comments
 (0)