add salt_monitor beacon

pr suggestions
This commit is contained in:
Jason Traub 2020-09-21 13:15:38 -07:00 committed by Megan Wilhite
parent ac4ecad325
commit e3cbba35f4
4 changed files with 310 additions and 0 deletions

View file

@ -31,6 +31,7 @@ beacon modules
pkg
proxy_example
ps
salt_monitor
salt_proxy
sensehat
service

View file

@ -0,0 +1,6 @@
=========================
salt.beacons.salt_monitor
=========================
.. automodule:: salt.beacons.salt_monitor
:members:

View file

@ -0,0 +1,118 @@
"""
A beacon to execute salt execution module functions. This beacon will fire only if the return data is "truthy".
The function return, funtion name and args and/or kwargs, will be passed as data in the event.
The configuration can accept a list of salt functions to execute every interval.
Make sure to allot enough time via 'interval' key to allow all salt functions to execute.
The salt functions will be executed sequentially.
The elements in list of functions can be either a simple string (with no arguments) or a dictionary with a single
key being the salt execution module and sub keys indicating args and / or kwargs.
See example config below.
.. code-block:: yaml
beacons:
salt_monitor:
- salt_fun:
- slsutil.renderer:
args:
- salt://states/apache.sls
kwargs:
- default_renderer: jinja
- test.ping
- interval: 3600 # seconds
"""
def _parse_args(args_kwargs_dict):
args = args_kwargs_dict.get("args", [])
kwargs = args_kwargs_dict.get("kwargs", {})
if kwargs:
_kwargs = {}
list(map(_kwargs.update, kwargs))
kwargs = _kwargs
return args, kwargs
def validate(config):
_config = {}
list(map(_config.update, config))
if isinstance(_config["salt_fun"], str):
# a simple str is taking as the single function with no args / kwargs
fun = _config["salt_fun"]
if fun not in __salt__:
return False, "{} not in __salt__".format(fun)
else:
for entry in _config["salt_fun"]:
if isinstance(entry, dict):
# check dict is of correct form
fun, args_kwargs_dict = next(iter(entry.items()))
for key in args_kwargs_dict:
if key == "args":
if not isinstance(args_kwargs_dict[key], list):
return (
False,
"args key for fun {} must be list".format(fun),
)
elif key == "kwargs":
if not isinstance(args_kwargs_dict[key], list):
return (
False,
"kwargs key for fun {} must be list of key value pairs".format(
fun
),
)
for key_value in args_kwargs_dict[key]:
if not isinstance(key_value, dict):
return (
False,
"{} is not a key / value pair".format(key_value),
)
else:
return (
False,
"key {} not allowed under fun {}".format(key, fun),
)
else:
# entry must be function itself
fun = entry
if fun not in __salt__:
return False, "{} not in __salt__".format(fun)
return True, "valid config"
def beacon(config):
events = []
_config = {}
list(map(_config.update, config))
if isinstance(_config["salt_fun"], str):
# support for single salt_fun with no args / kwargs supplied as str
fun = _config["salt_fun"]
ret = __salt__[fun]()
return [{"salt_fun": fun, "ret": ret}]
# else, we should have an iterable
for entry in _config["salt_fun"]:
if isinstance(entry, dict):
fun, args_kwargs_dict = list(entry.items())[0]
args, kwargs = _parse_args(args_kwargs_dict)
else:
fun = entry
args = ()
kwargs = {}
ret = __salt__[fun](*args, **kwargs)
if ret:
_ret = {"salt_fun": fun, "ret": ret}
if args:
_ret["args"] = args
if kwargs:
_ret["kwargs"] = kwargs
events.append(_ret)
return events

View file

@ -0,0 +1,185 @@
# pylint: disable=E8231
# Salt libs
import salt.beacons.salt_monitor as salt_monitor
from tests.support.mixins import LoaderModuleMockMixin
# Salt testing libs
from tests.support.unit import TestCase
TEST_CONFIG = [
{
"config": [{"salt_fun": ["test.ping", "test.version"]}],
"expected_validate": (True, "valid config"),
"expected_beacon": [
{"salt_fun": "test.ping", "ret": True},
{"salt_fun": "test.version", "ret": "3000"},
],
},
{
"config": [
{"salt_fun": [{"cmd.run": {"args": ["echo hello world",]}}, "test.version"]}
], # *args behaves weird on list with single string, does it happen in yaml?
"expected_validate": (True, "valid config"),
"expected_beacon": [
{
"salt_fun": "cmd.run",
"ret": (("echo hello world",), {}),
"args": ["echo hello world"],
},
{"salt_fun": "test.version", "ret": "3000"},
],
},
{
"config": [
{
"salt_fun": [
{
"cmd.run": {
"args": ["echo hello world",],
"kwargs": [{"shell": "ps"}],
}
}
]
}
],
"expected_validate": (True, "valid config"),
"expected_beacon": [
{
"salt_fun": "cmd.run",
"ret": (("echo hello world",), {"shell": "ps"}),
"args": ["echo hello world"],
"kwargs": {"shell": "ps"},
}
],
},
{
"config": [
{"salt_fun": [{"cmd.run": {"args": "echo hello world"}}, "test.version"]}
],
"expected_validate": (False, "args key for fun cmd.run must be list"),
"expected_beacon": None, # None != []
},
{
"config": [
{
"salt_fun": [
{
"cmd.run": {
"args": ["echo hello world"],
"kwargs": {"shell": "ps"},
}
}
]
}
],
"expected_validate": (
False,
"kwargs key for fun cmd.run must be list of key value pairs",
),
"expected_beacon": None,
},
{
"config": [
{
"salt_fun": [
{"cmd.run": {"args": ["echo hello world"], "kwargs": ["shell=ps"]}}
]
}
],
"expected_validate": (
False,
"{} is not a key / value pair".format("shell=ps"),
),
"expected_beacon": None,
},
{
"config": [
{
"salt_fun": [
{
"cmd.run": {
"args": ["echo hello world"],
"kwargs": [{"shell": "ps"}],
"bg": True,
}
}
]
}
],
"expected_validate": (False, "key bg not allowed under fun cmd.run"),
"expected_beacon": None,
},
{
"config": [
{
"salt_fun": [
{"bogus.fun": {"args": ["echo hello world"]}},
"test.version",
]
}
],
"expected_validate": (False, "bogus.fun not in __salt__"),
"expected_beacon": None,
},
{
"config": [{"salt_fun": ["test.ping", "test.false"]}],
"expected_validate": (True, "valid config"),
"expected_beacon": [{"salt_fun": "test.ping", "ret": True}],
},
{
"config": [{"salt_fun": ["test.false"]}],
"expected_validate": (True, "valid config"),
"expected_beacon": [],
},
{
"config": [{"salt_fun": "test.ping"}],
"expected_validate": (True, "valid config"),
"expected_beacon": [{"salt_fun": "test.ping", "ret": True}],
},
]
def mock_test_ping():
return True
def mock_test_version():
return "3000"
def mock_test(*args, **kwargs):
return args, kwargs
def mock_test_false():
return False
class SaltBeaconTestCase(TestCase, LoaderModuleMockMixin):
"""
Test case for salt.beacons.salt_monitor
"""
def setup_loader_modules(self):
return {
salt_monitor: {
"__salt__": {
"test.ping": mock_test_ping,
"test.version": mock_test_version,
"cmd.run": mock_test,
"test.false": mock_test_false,
}
}
}
def test_validate(self):
for i, config in enumerate(TEST_CONFIG):
valid = salt_monitor.validate(config["config"])
self.assertEqual(valid, config["expected_validate"])
def test_beacon(self):
for config in TEST_CONFIG:
if config["expected_beacon"] is None:
continue
events = salt_monitor.beacon(config["config"])
self.assertEqual(events, config["expected_beacon"])