mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Support master tops on masterless minions
Also, make salt-ssh master tops behave like regular ones, i.e. merge the returns of multiple master top modules for the same environment.
This commit is contained in:
parent
d7f20320d5
commit
d7338a0e79
9 changed files with 162 additions and 36 deletions
1
changelog/65479.added.md
Normal file
1
changelog/65479.added.md
Normal file
|
@ -0,0 +1 @@
|
|||
Added support for master top modules on masterless minions
|
1
changelog/65480.fixed.md
Normal file
1
changelog/65480.fixed.md
Normal file
|
@ -0,0 +1 @@
|
|||
Made salt-ssh merge master top returns for the same environment
|
|
@ -12,6 +12,10 @@ The old `external_nodes` option has been removed. The master tops system
|
|||
provides a pluggable and extendable replacement for it, allowing for multiple
|
||||
different subsystems to provide top file data.
|
||||
|
||||
.. versionchanged:: 3007.0
|
||||
|
||||
Masterless minions now support master top modules as well.
|
||||
|
||||
Using the new `master_tops` option is simple:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
|
|
@ -74,7 +74,6 @@ class SSHHighState(salt.state.BaseHighState):
|
|||
salt.state.BaseHighState.__init__(self, opts)
|
||||
self.state = SSHState(opts, pillar, wrapper, context=context)
|
||||
self.matchers = salt.loader.matchers(self.opts)
|
||||
self.tops = salt.loader.tops(self.opts)
|
||||
|
||||
self._pydsl_all_decls = {}
|
||||
self._pydsl_render_stack = []
|
||||
|
@ -92,32 +91,7 @@ class SSHHighState(salt.state.BaseHighState):
|
|||
"""
|
||||
Evaluate master_tops locally
|
||||
"""
|
||||
if "id" not in self.opts:
|
||||
log.error("Received call for external nodes without an id")
|
||||
return {}
|
||||
if not salt.utils.verify.valid_id(self.opts, self.opts["id"]):
|
||||
return {}
|
||||
# Evaluate all configured master_tops interfaces
|
||||
|
||||
grains = {}
|
||||
ret = {}
|
||||
|
||||
if "grains" in self.opts:
|
||||
grains = self.opts["grains"]
|
||||
for fun in self.tops:
|
||||
if fun not in self.opts.get("master_tops", {}):
|
||||
continue
|
||||
try:
|
||||
ret.update(self.tops[fun](opts=self.opts, grains=grains))
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
# If anything happens in the top generation, log it and move on
|
||||
log.error(
|
||||
"Top function %s failed with error %s for minion %s",
|
||||
fun,
|
||||
exc,
|
||||
self.opts["id"],
|
||||
)
|
||||
return ret
|
||||
return self._local_master_tops()
|
||||
|
||||
def destroy(self):
|
||||
if self.client:
|
||||
|
|
|
@ -70,13 +70,15 @@ __proxyenabled__ = ["*"]
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
TOP_ENVS_CKEY = "saltutil._top_file_envs"
|
||||
|
||||
|
||||
def _get_top_file_envs():
|
||||
"""
|
||||
Get all environments from the top file
|
||||
"""
|
||||
try:
|
||||
return __context__["saltutil._top_file_envs"]
|
||||
return __context__[TOP_ENVS_CKEY]
|
||||
except KeyError:
|
||||
with salt.state.HighState(__opts__, initial_pillar=__pillar__.value()) as st_:
|
||||
try:
|
||||
|
@ -87,7 +89,7 @@ def _get_top_file_envs():
|
|||
envs = "base"
|
||||
except SaltRenderError as exc:
|
||||
raise CommandExecutionError(f"Unable to render top file(s): {exc}")
|
||||
__context__["saltutil._top_file_envs"] = envs
|
||||
__context__[TOP_ENVS_CKEY] = envs
|
||||
return envs
|
||||
|
||||
|
||||
|
@ -244,10 +246,6 @@ def sync_sdb(saltenv=None, extmod_whitelist=None, extmod_blacklist=None):
|
|||
<states-top>` will be checked for sdb modules to sync. If no top files
|
||||
are found, then the ``base`` environment will be synced.
|
||||
|
||||
refresh : False
|
||||
This argument has no affect and is included for consistency with the
|
||||
other sync functions.
|
||||
|
||||
extmod_whitelist : None
|
||||
comma-separated list of modules to sync
|
||||
|
||||
|
@ -473,8 +471,7 @@ def sync_renderers(
|
|||
refresh : True
|
||||
If ``True``, refresh the available execution modules on the minion.
|
||||
This refresh will be performed even if no new renderers are synced.
|
||||
Set to ``False`` to prevent this refresh. Set to ``False`` to prevent
|
||||
this refresh.
|
||||
Set to ``False`` to prevent this refresh.
|
||||
|
||||
extmod_whitelist : None
|
||||
comma-separated list of modules to sync
|
||||
|
@ -973,6 +970,57 @@ def sync_pillar(
|
|||
return ret
|
||||
|
||||
|
||||
def sync_tops(
|
||||
saltenv=None,
|
||||
refresh=True,
|
||||
extmod_whitelist=None,
|
||||
extmod_blacklist=None,
|
||||
):
|
||||
"""
|
||||
.. versionadded:: 3007.0
|
||||
|
||||
Sync master tops from ``salt://_tops`` to the minion.
|
||||
|
||||
saltenv
|
||||
The fileserver environment from which to sync. To sync from more than
|
||||
one environment, pass a comma-separated list.
|
||||
|
||||
If not passed, then all environments configured in the :ref:`top files
|
||||
<states-top>` will be checked for master tops to sync. If no top files
|
||||
are found, then the ``base`` environment will be synced.
|
||||
|
||||
refresh : True
|
||||
Refresh this module's cache containing the environments from which
|
||||
extension modules are synced when ``saltenv`` is not specified.
|
||||
This refresh will be performed even if no new master tops are synced.
|
||||
Set to ``False`` to prevent this refresh.
|
||||
|
||||
extmod_whitelist : None
|
||||
comma-separated list of modules to sync
|
||||
|
||||
extmod_blacklist : None
|
||||
comma-separated list of modules to blacklist based on type
|
||||
|
||||
.. note::
|
||||
This function will raise an error if executed on a traditional (i.e.
|
||||
not masterless) minion
|
||||
|
||||
CLI Examples:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' saltutil.sync_tops
|
||||
salt '*' saltutil.sync_tops saltenv=dev
|
||||
"""
|
||||
if __opts__["file_client"] != "local":
|
||||
raise CommandExecutionError(
|
||||
"Master top modules can only be synced to masterless minions"
|
||||
)
|
||||
if refresh:
|
||||
__context__.pop(TOP_ENVS_CKEY, None)
|
||||
return _sync("tops", saltenv, extmod_whitelist, extmod_blacklist)
|
||||
|
||||
|
||||
def sync_executors(
|
||||
saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist=None
|
||||
):
|
||||
|
@ -1071,6 +1119,13 @@ def sync_all(
|
|||
clean_pillar_cache=False,
|
||||
):
|
||||
"""
|
||||
.. versionchanged:: 3007.0
|
||||
|
||||
On masterless minions, master top modules are now synced as well.
|
||||
When ``refresh`` is set to ``True``, this module's cache containing
|
||||
the environments from which extension modules are synced when
|
||||
``saltenv`` is not specified will be refreshed.
|
||||
|
||||
.. versionchanged:: 2015.8.11,2016.3.2
|
||||
On masterless minions, pillar modules are now synced, and refreshed
|
||||
when ``refresh`` is set to ``True``.
|
||||
|
@ -1081,7 +1136,9 @@ def sync_all(
|
|||
|
||||
refresh : True
|
||||
Also refresh the execution modules and recompile pillar data available
|
||||
to the minion. This refresh will be performed even if no new dynamic
|
||||
to the minion. If this is a masterless minion, also refresh the environments
|
||||
from which extension modules are synced after syncing master tops.
|
||||
This refresh will be performed even if no new dynamic
|
||||
modules are synced. Set to ``False`` to prevent this refresh.
|
||||
|
||||
.. important::
|
||||
|
@ -1121,6 +1178,9 @@ def sync_all(
|
|||
"""
|
||||
log.debug("Syncing all")
|
||||
ret = {}
|
||||
if __opts__["file_client"] == "local":
|
||||
# Sync tops first since this might influence the other syncs
|
||||
ret["tops"] = sync_tops(saltenv, refresh, extmod_whitelist, extmod_blacklist)
|
||||
ret["clouds"] = sync_clouds(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||
ret["beacons"] = sync_beacons(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||
ret["modules"] = sync_modules(saltenv, False, extmod_whitelist, extmod_blacklist)
|
||||
|
|
|
@ -43,6 +43,7 @@ import salt.utils.msgpack
|
|||
import salt.utils.platform
|
||||
import salt.utils.process
|
||||
import salt.utils.url
|
||||
import salt.utils.verify
|
||||
|
||||
# Explicit late import to avoid circular import. DO NOT MOVE THIS.
|
||||
import salt.utils.yamlloader as yamlloader
|
||||
|
@ -4243,8 +4244,43 @@ class BaseHighState:
|
|||
Get results from the master_tops system. Override this function if the
|
||||
execution of the master_tops needs customization.
|
||||
"""
|
||||
if self.opts.get("file_client", "remote") == "local":
|
||||
return self._local_master_tops()
|
||||
return self.client.master_tops()
|
||||
|
||||
def _local_master_tops(self):
|
||||
# return early if we got nothing to do
|
||||
if "master_tops" not in self.opts:
|
||||
return {}
|
||||
if "id" not in self.opts:
|
||||
log.error("Received call for external nodes without an id")
|
||||
return {}
|
||||
if not salt.utils.verify.valid_id(self.opts, self.opts["id"]):
|
||||
return {}
|
||||
if getattr(self, "tops", None) is None:
|
||||
self.tops = salt.loader.tops(self.opts)
|
||||
grains = {}
|
||||
ret = {}
|
||||
|
||||
if "grains" in self.opts:
|
||||
grains = self.opts["grains"]
|
||||
for fun in self.tops:
|
||||
if fun not in self.opts["master_tops"]:
|
||||
continue
|
||||
try:
|
||||
ret = salt.utils.dictupdate.merge(
|
||||
ret, self.tops[fun](opts=self.opts, grains=grains), merge_lists=True
|
||||
)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
# If anything happens in the top generation, log it and move on
|
||||
log.error(
|
||||
"Top function %s failed with error %s for minion %s",
|
||||
fun,
|
||||
exc,
|
||||
self.opts["id"],
|
||||
)
|
||||
return ret
|
||||
|
||||
def load_dynamic(self, matches):
|
||||
"""
|
||||
If autoload_dynamic_modules is True then automatically load the
|
||||
|
|
|
@ -309,6 +309,20 @@ def sync_states(name, **kwargs):
|
|||
return _sync_single(name, "states", **kwargs)
|
||||
|
||||
|
||||
def sync_tops(name, **kwargs):
|
||||
"""
|
||||
Performs the same task as saltutil.sync_tops module
|
||||
See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
sync_everything:
|
||||
saltutil.sync_tops:
|
||||
- refresh: True
|
||||
"""
|
||||
return _sync_single(name, "tops", **kwargs)
|
||||
|
||||
|
||||
def sync_thorium(name, **kwargs):
|
||||
"""
|
||||
Performs the same task as saltutil.sync_thorium module
|
||||
|
|
33
tests/pytests/functional/state/test_masterless_tops.py
Normal file
33
tests/pytests/functional/state/test_masterless_tops.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from tests.support.runtests import RUNTIME_VARS
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def minion_config_overrides():
|
||||
return {"master_tops": {"master_tops_test": True}}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", autouse=True)
|
||||
def _master_tops_test(state_tree, loaders):
|
||||
mod_contents = (
|
||||
Path(RUNTIME_VARS.FILES) / "extension_modules" / "tops" / "master_tops_test.py"
|
||||
).read_text()
|
||||
try:
|
||||
with pytest.helpers.temp_file(
|
||||
"master_tops_test.py", mod_contents, state_tree / "_tops"
|
||||
):
|
||||
res = loaders.modules.saltutil.sync_tops()
|
||||
assert "tops.master_tops_test" in res
|
||||
yield
|
||||
finally:
|
||||
loaders.modules.saltutil.sync_tops()
|
||||
|
||||
|
||||
def test_masterless_master_tops(loaders):
|
||||
res = loaders.modules.state.show_top()
|
||||
assert res
|
||||
assert "base" in res
|
||||
assert "master_tops_test" in res["base"]
|
|
@ -37,6 +37,7 @@ def test_saltutil_sync_all_nochange():
|
|||
"matchers": [],
|
||||
"serializers": [],
|
||||
"wrapper": [],
|
||||
"tops": [],
|
||||
}
|
||||
state_id = "somename"
|
||||
state_result = {
|
||||
|
@ -73,6 +74,7 @@ def test_saltutil_sync_all_test():
|
|||
"matchers": [],
|
||||
"serializers": [],
|
||||
"wrapper": [],
|
||||
"tops": [],
|
||||
}
|
||||
state_id = "somename"
|
||||
state_result = {
|
||||
|
@ -110,6 +112,7 @@ def test_saltutil_sync_all_change():
|
|||
"matchers": [],
|
||||
"serializers": [],
|
||||
"wrapper": [],
|
||||
"tops": [],
|
||||
}
|
||||
state_id = "somename"
|
||||
state_result = {
|
||||
|
|
Loading…
Add table
Reference in a new issue