mirror of
https://github.com/saltstack/salt.git
synced 2025-04-15 09:10:20 +00:00
fixes saltstack/salt#62446 add global_state_conditions handling
This commit is contained in:
parent
93f871df58
commit
d258b87a08
6 changed files with 132 additions and 3 deletions
1
changelog/62446.added
Normal file
1
changelog/62446.added
Normal file
|
@ -0,0 +1 @@
|
|||
Add ability to provide conditions which convert normal state actions to no-op when true
|
|
@ -600,6 +600,14 @@
|
|||
# - require
|
||||
# - require_in
|
||||
|
||||
# If set, this parameter expects a dictionary of state module names as keys
|
||||
# and list of conditions which must be satisfied in order to run any functions
|
||||
# in that state module.
|
||||
#
|
||||
#global_state_conditions:
|
||||
# "*": ["G@global_noop:false"]
|
||||
# service: ["not G@virtual_subtype:chroot"]
|
||||
|
||||
##### File Directory Settings #####
|
||||
##########################################
|
||||
# The Salt Minion can redirect all file server operations to a local directory,
|
||||
|
|
|
@ -2472,6 +2472,21 @@ default configuration set up at install time.
|
|||
|
||||
snapper_states_config: root
|
||||
|
||||
``global_state_conditions``
|
||||
-------------------------
|
||||
|
||||
Default: ``None``
|
||||
|
||||
If set, this parameter expects a dictionary of state module names as keys and
|
||||
list of conditions which must be satisfied in order to run any functions in that
|
||||
state module.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
global_state_conditions:
|
||||
"*": ["G@global_noop:false"]
|
||||
service: ["not G@virtual_subtype:chroot"]
|
||||
|
||||
File Directory Settings
|
||||
=======================
|
||||
|
||||
|
|
|
@ -955,6 +955,7 @@ VALID_OPTS = immutabletypes.freeze(
|
|||
# client via the Salt API
|
||||
"netapi_allow_raw_shell": bool,
|
||||
"disabled_requisites": (str, list),
|
||||
"global_state_conditions": dict,
|
||||
# Feature flag config
|
||||
"features": dict,
|
||||
"fips_mode": bool,
|
||||
|
@ -1273,6 +1274,7 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze(
|
|||
"schedule": {},
|
||||
"ssh_merge_pillar": True,
|
||||
"disabled_requisites": [],
|
||||
"global_state_conditions": None,
|
||||
"reactor_niceness": None,
|
||||
"fips_mode": False,
|
||||
}
|
||||
|
|
|
@ -787,6 +787,36 @@ class State:
|
|||
self.inject_globals = {}
|
||||
self.mocked = mocked
|
||||
|
||||
def _match_global_state_conditions(self, full, state, name):
|
||||
"""
|
||||
Return ``None`` if global state conditions are met. Otherwise, pass a
|
||||
return dictionary which effectively creates a no-op outcome.
|
||||
|
||||
This operation is "explicit allow", in that ANY state and condition
|
||||
combination which matches will allow the state to be run.
|
||||
"""
|
||||
matches = []
|
||||
ret = None
|
||||
ret_dict = {
|
||||
"name": name,
|
||||
"comment": "Failed to meet global state conditions. State not called.",
|
||||
"changes": {},
|
||||
"result": None,
|
||||
}
|
||||
if isinstance(self.opts.get("global_state_conditions"), dict):
|
||||
for state_match, conditions in self.opts["global_state_conditions"].items():
|
||||
if state_match in ["*", full, state]:
|
||||
if isinstance(conditions, str):
|
||||
conditions = [conditions]
|
||||
if isinstance(conditions, list):
|
||||
matches.extend(
|
||||
self.functions["match.compound"](condition)
|
||||
for condition in conditions
|
||||
)
|
||||
if matches and not any(matches):
|
||||
ret = ret_dict
|
||||
return ret
|
||||
|
||||
def _gather_pillar(self):
|
||||
"""
|
||||
Whenever a state run starts, gather the pillar data fresh
|
||||
|
@ -2046,7 +2076,11 @@ class State:
|
|||
instance.format_slots(cdata)
|
||||
tag = _gen_tag(low)
|
||||
try:
|
||||
ret = instance.states[cdata["full"]](*cdata["args"], **cdata["kwargs"])
|
||||
ret = instance._match_global_state_conditions(
|
||||
cdata["full"], low["state"], name
|
||||
)
|
||||
if not ret:
|
||||
ret = instance.states[cdata["full"]](*cdata["args"], **cdata["kwargs"])
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
log.debug(
|
||||
"An exception occurred in this state: %s",
|
||||
|
@ -2305,9 +2339,13 @@ class State:
|
|||
ret = self.call_parallel(cdata, low)
|
||||
else:
|
||||
self.format_slots(cdata)
|
||||
ret = self.states[cdata["full"]](
|
||||
*cdata["args"], **cdata["kwargs"]
|
||||
ret = self._match_global_state_conditions(
|
||||
cdata["full"], low["state"], low["name"]
|
||||
)
|
||||
if not ret:
|
||||
ret = self.states[cdata["full"]](
|
||||
*cdata["args"], **cdata["kwargs"]
|
||||
)
|
||||
self.states.inject_globals = {}
|
||||
if (
|
||||
"check_cmd" in low
|
||||
|
|
65
tests/pytests/unit/state/test_global_state_conditions.py
Normal file
65
tests/pytests/unit/state/test_global_state_conditions.py
Normal file
|
@ -0,0 +1,65 @@
|
|||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
import salt.config
|
||||
import salt.state
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def minion_config():
|
||||
cfg = salt.config.DEFAULT_MINION_OPTS.copy()
|
||||
cfg["file_client"] = "local"
|
||||
cfg["id"] = "foo01"
|
||||
return cfg
|
||||
|
||||
|
||||
def test_global_state_conditions_unconfigured(minion_config):
|
||||
state_obj = salt.state.State(minion_config)
|
||||
ret = state_obj._match_global_state_conditions(
|
||||
"test.succeed_with_changes", "test", "mytest"
|
||||
)
|
||||
assert ret is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize("condition", [["foo01"], "foo01"])
|
||||
def test_global_state_conditions_match(minion_config, condition):
|
||||
minion_config["global_state_conditions"] = {
|
||||
"test": condition,
|
||||
}
|
||||
state_obj = salt.state.State(minion_config)
|
||||
ret = state_obj._match_global_state_conditions(
|
||||
"test.succeed_with_changes", "test", "mytest"
|
||||
)
|
||||
assert ret is None
|
||||
|
||||
|
||||
def test_global_state_conditions_no_match(minion_config):
|
||||
minion_config["global_state_conditions"] = {
|
||||
"test.succeed_with_changes": ["bar01"],
|
||||
}
|
||||
state_obj = salt.state.State(minion_config)
|
||||
ret = state_obj._match_global_state_conditions(
|
||||
"test.succeed_with_changes", "test", "mytest"
|
||||
)
|
||||
assert ret == {
|
||||
"changes": {},
|
||||
"comment": "Failed to meet global state conditions. State not called.",
|
||||
"name": "mytest",
|
||||
"result": None,
|
||||
}
|
||||
|
||||
|
||||
def test_global_state_conditions_match_one_of_many(minion_config):
|
||||
minion_config["global_state_conditions"] = {
|
||||
"test.succeed_with_changes": ["bar01"],
|
||||
"test": ["baz01"],
|
||||
"*": ["foo01"],
|
||||
}
|
||||
state_obj = salt.state.State(minion_config)
|
||||
ret = state_obj._match_global_state_conditions(
|
||||
"test.succeed_with_changes", "test", "mytest"
|
||||
)
|
||||
assert ret is None
|
Loading…
Add table
Reference in a new issue