mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40:20 +00:00
Add onchange configuration for script engine
This commit is contained in:
parent
4e1bbec71d
commit
448b5f519f
3 changed files with 116 additions and 7 deletions
1
changelog/62867.added
Normal file
1
changelog/62867.added
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Added onchange configuration for script engine
|
|
@ -10,13 +10,23 @@ Example Config
|
||||||
cmd: /some/script.py -a 1 -b 2
|
cmd: /some/script.py -a 1 -b 2
|
||||||
output: json
|
output: json
|
||||||
interval: 5
|
interval: 5
|
||||||
|
onchange: false
|
||||||
|
|
||||||
Script engine configs:
|
Script engine configs:
|
||||||
|
|
||||||
cmd: Script or command to execute
|
cmd
|
||||||
output: Any available saltstack deserializer
|
Script or command to execute
|
||||||
interval: How often in seconds to execute the command
|
|
||||||
|
|
||||||
|
output
|
||||||
|
Any available saltstack deserializer
|
||||||
|
|
||||||
|
interval
|
||||||
|
How often in seconds to execute the command
|
||||||
|
|
||||||
|
onchange
|
||||||
|
.. versionadded:: 3006
|
||||||
|
|
||||||
|
Only fire an event if the tag-specific output changes. Defaults to False.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
@ -53,7 +63,7 @@ def _get_serializer(output):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def start(cmd, output="json", interval=1):
|
def start(cmd, output="json", interval=1, onchange=False):
|
||||||
"""
|
"""
|
||||||
Parse stdout of a command and generate an event
|
Parse stdout of a command and generate an event
|
||||||
|
|
||||||
|
@ -80,6 +90,7 @@ def start(cmd, output="json", interval=1):
|
||||||
:param cmd: The command to execute
|
:param cmd: The command to execute
|
||||||
:param output: How to deserialize stdout of the script
|
:param output: How to deserialize stdout of the script
|
||||||
:param interval: How often to execute the script
|
:param interval: How often to execute the script
|
||||||
|
:param onchange: Only fire an event if the tag-specific output changes
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cmd = shlex.split(cmd)
|
cmd = shlex.split(cmd)
|
||||||
|
@ -96,7 +107,10 @@ def start(cmd, output="json", interval=1):
|
||||||
else:
|
else:
|
||||||
fire_master = __salt__["event.send"]
|
fire_master = __salt__["event.send"]
|
||||||
|
|
||||||
while True:
|
if onchange:
|
||||||
|
events = {}
|
||||||
|
|
||||||
|
while _running():
|
||||||
|
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
|
@ -116,8 +130,12 @@ def start(cmd, output="json", interval=1):
|
||||||
data["id"] = __opts__["id"]
|
data["id"] = __opts__["id"]
|
||||||
|
|
||||||
if tag:
|
if tag:
|
||||||
|
if onchange and tag in events and events[tag] == data:
|
||||||
|
continue
|
||||||
log.info("script engine firing event with tag %s", tag)
|
log.info("script engine firing event with tag %s", tag)
|
||||||
fire_master(tag=tag, data=data)
|
fire_master(tag=tag, data=data)
|
||||||
|
if onchange:
|
||||||
|
events[tag] = data
|
||||||
|
|
||||||
log.debug("Closing script with pid %d", proc.pid)
|
log.debug("Closing script with pid %d", proc.pid)
|
||||||
proc.stdout.close()
|
proc.stdout.close()
|
||||||
|
@ -132,3 +150,8 @@ def start(cmd, output="json", interval=1):
|
||||||
proc.terminate()
|
proc.terminate()
|
||||||
|
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
|
|
||||||
|
|
||||||
|
# helper to test the start function
|
||||||
|
def _running():
|
||||||
|
return True
|
||||||
|
|
|
@ -2,17 +2,20 @@
|
||||||
unit tests for the script engine
|
unit tests for the script engine
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import salt.config
|
import salt.config
|
||||||
import salt.engines.script as script
|
import salt.engines.script as script
|
||||||
from salt.exceptions import CommandExecutionError
|
from salt.exceptions import CommandExecutionError
|
||||||
from tests.support.mock import patch
|
from tests.support.mock import Mock, patch
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def configure_loader_modules():
|
def configure_loader_modules():
|
||||||
opts = salt.config.DEFAULT_MASTER_OPTS
|
opts = copy.deepcopy(salt.config.DEFAULT_MASTER_OPTS)
|
||||||
|
opts["id"] = "test"
|
||||||
return {script: {"__opts__": opts}}
|
return {script: {"__opts__": opts}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,3 +48,85 @@ def test__read_stdout_terminates_properly():
|
||||||
popen_mock.stdout.readline.return_value = b""
|
popen_mock.stdout.readline.return_value = b""
|
||||||
with pytest.raises(StopIteration):
|
with pytest.raises(StopIteration):
|
||||||
next(script._read_stdout(popen_mock))
|
next(script._read_stdout(popen_mock))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def serializer():
|
||||||
|
with patch("salt.engines.script._get_serializer", autospec=True) as get_serializer:
|
||||||
|
serializer = Mock()
|
||||||
|
get_serializer.return_value = serializer
|
||||||
|
serializer.deserialize.side_effect = lambda x: x
|
||||||
|
yield serializer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=[1])
|
||||||
|
def runs(request):
|
||||||
|
runs = Mock()
|
||||||
|
runs.side_effect = request.param * [True] + [False]
|
||||||
|
with patch("salt.engines.script._running", runs):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def event_send():
|
||||||
|
event = Mock()
|
||||||
|
with patch("salt.utils.event.get_master_event") as get_master:
|
||||||
|
get_master.fire_event = event
|
||||||
|
with patch.dict(script.__salt__, {"event.send": event}):
|
||||||
|
yield event
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def raw_event():
|
||||||
|
with patch("salt.engines.script._read_stdout") as stdout:
|
||||||
|
yield stdout
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def proc():
|
||||||
|
with patch("salt.engines.script.subprocess.Popen") as popen:
|
||||||
|
proc = Mock()
|
||||||
|
proc.wait.return_value = False
|
||||||
|
proc.pid = 1337
|
||||||
|
popen.return_value = proc
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def sleep():
|
||||||
|
with patch("time.sleep"):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def event():
|
||||||
|
return {"tag": "test", "data": {"foo": "bar", "id": "test"}}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def new_event():
|
||||||
|
return {"tag": "test", "data": {"foo": "baz", "id": "test"}}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures(
|
||||||
|
"proc", "serializer", "runs", "sleep", "event_send", "raw_event"
|
||||||
|
)
|
||||||
|
class TestStart:
|
||||||
|
def test_start(self, event, raw_event, event_send):
|
||||||
|
raw_event.return_value = [event]
|
||||||
|
script.start("cmd")
|
||||||
|
event_send.assert_called_once_with(tag=event["tag"], data=event["data"])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("runs", [10], indirect=True)
|
||||||
|
def test_start_onchange_no_change(self, event, raw_event, event_send):
|
||||||
|
raw_event.side_effect = 10 * [[event]]
|
||||||
|
script.start("cmd", onchange=True)
|
||||||
|
event_send.assert_called_once_with(tag=event["tag"], data=event["data"])
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("runs", [8], indirect=True)
|
||||||
|
def test_start_onchange_with_change(self, event, new_event, raw_event, event_send):
|
||||||
|
raw_event.side_effect = 3 * [[event]] + 5 * [[new_event]]
|
||||||
|
script.start("cmd", onchange=True)
|
||||||
|
assert event_send.call_count == 2
|
||||||
|
event_send.assert_any_call(tag=event["tag"], data=event["data"])
|
||||||
|
event_send.assert_called_with(tag=new_event["tag"], data=new_event["data"])
|
||||||
|
|
Loading…
Add table
Reference in a new issue