Merge 3006.x into master

This commit is contained in:
Pedro Algarvio 2023-10-22 12:20:30 +01:00
commit 6d17f6e8ca
No known key found for this signature in database
GPG key ID: BB36BF6584A298FF
9 changed files with 638 additions and 5 deletions

1
changelog/65027.fixed.md Normal file
View file

@ -0,0 +1 @@
Ensure sync from _grains occurs before attempting pillar compilation in case custom grain used in pillar file

View file

@ -41,12 +41,16 @@ log = logging.getLogger(__name__)
MAX_FILENAME_LENGTH = 255
def get_file_client(opts, pillar=False):
def get_file_client(opts, pillar=False, force_local=False):
"""
Read in the ``file_client`` option and return the correct type of file
server
"""
client = opts.get("file_client", "remote")
if force_local:
client = "local"
else:
client = opts.get("file_client", "remote")
if pillar and client == "local":
client = "pillar"
return {"remote": RemoteClient, "local": FSClient, "pillar": PillarClient}.get(

View file

@ -46,6 +46,7 @@ import salt.utils.dictdiffer
import salt.utils.dictupdate
import salt.utils.error
import salt.utils.event
import salt.utils.extmods
import salt.utils.files
import salt.utils.jid
import salt.utils.minion
@ -114,6 +115,29 @@ log = logging.getLogger(__name__)
# 6. Handle publications
def _sync_grains(opts):
# need sync of custom grains as may be used in pillar compilation
# if coming up initially and remote client, the first sync _grains
# doesn't have opts["master_uri"] set yet during the sync, so need
# to force local, otherwise will throw an exception when attempting
# to retrieve opts["master_uri"] when retrieving key for remote communication
# in addition opts sometimes does not contain extmod_whitelist and extmod_blacklist
# hence set those to defaults, empty dict, if not part of opts, as ref'd in
# salt.utils.extmod sync function
if opts.get("extmod_whitelist", None) is None:
opts["extmod_whitelist"] = {}
if opts.get("extmod_blacklist", None) is None:
opts["extmod_blacklist"] = {}
if opts.get("file_client", "remote") == "remote" and not opts.get(
"master_uri", None
):
salt.utils.extmods.sync(opts, "grains", force_local=True)
else:
salt.utils.extmods.sync(opts, "grains")
def resolve_dns(opts, fallback=True):
"""
Resolves the master_ip and master_uri options
@ -921,6 +945,7 @@ class SMinion(MinionBase):
# Late setup of the opts grains, so we can log from the grains module
import salt.loader
_sync_grains(opts)
opts["grains"] = salt.loader.grains(opts)
super().__init__(opts)
@ -3935,6 +3960,8 @@ class SProxyMinion(SMinion):
salt '*' sys.reload_modules
"""
# need sync of custom grains as may be used in pillar compilation
salt.utils.extmods.sync(self.opts, "grains")
self.opts["grains"] = salt.loader.grains(self.opts)
self.opts["pillar"] = salt.pillar.get_pillar(
self.opts,

View file

@ -32,7 +32,14 @@ def _listdir_recursively(rootdir):
return file_list
def sync(opts, form, saltenv=None, extmod_whitelist=None, extmod_blacklist=None):
def sync(
opts,
form,
saltenv=None,
extmod_whitelist=None,
extmod_blacklist=None,
force_local=False,
):
"""
Sync custom modules into the extension_modules directory
"""
@ -75,7 +82,9 @@ def sync(opts, form, saltenv=None, extmod_whitelist=None, extmod_blacklist=None)
"Cannot create cache module directory %s. Check permissions.",
mod_dir,
)
with salt.fileclient.get_file_client(opts) as fileclient:
with salt.fileclient.get_file_client(
opts, pillar=False, force_local=force_local
) as fileclient:
for sub_env in saltenv:
log.info("Syncing %s for environment '%s'", form, sub_env)
cache = []

View file

@ -429,3 +429,74 @@ def test_local_salt_call_no_function_no_retcode(salt_call_cli):
assert "test" in ret.data
assert ret.data["test"] == "'test' is not available."
assert "test.echo" in ret.data
def test_state_highstate_custom_grains(salt_master, salt_minion_factory):
"""
This test ensure that custom grains in salt://_grains are loaded before pillar compilation
to ensure that any use of custom grains in pillar files are available, this implies that
a sync of grains occurs before loading the regular /etc/salt/grains or configuration file
grains, as well as the usual grains.
Note: cannot use salt_minion and salt_call_cli, since these will be loaded before
the pillar and custom_grains files are written, hence using salt_minion_factory.
"""
pillar_top_sls = """
base:
'*':
- defaults
"""
pillar_defaults_sls = """
mypillar: "{{ grains['custom_grain'] }}"
"""
salt_top_sls = """
base:
'*':
- test
"""
salt_test_sls = """
"donothing":
test.nop: []
"""
salt_custom_grains_py = """
def main():
return {'custom_grain': 'test_value'}
"""
assert salt_master.is_running()
with salt_minion_factory.started():
salt_minion = salt_minion_factory
salt_call_cli = salt_minion_factory.salt_call_cli()
with salt_minion.pillar_tree.base.temp_file(
"top.sls", pillar_top_sls
), salt_minion.pillar_tree.base.temp_file(
"defaults.sls", pillar_defaults_sls
), salt_minion.state_tree.base.temp_file(
"top.sls", salt_top_sls
), salt_minion.state_tree.base.temp_file(
"test.sls", salt_test_sls
), salt_minion.state_tree.base.temp_file(
"_grains/custom_grain.py", salt_custom_grains_py
):
ret = salt_call_cli.run("--local", "state.highstate")
assert ret.returncode == 0
ret = salt_call_cli.run("--local", "pillar.items")
assert ret.returncode == 0
assert ret.data
pillar_items = ret.data
assert "mypillar" in pillar_items
assert pillar_items["mypillar"] == "test_value"
def test_salt_call_versions(salt_call_cli, caplog):
"""
Call test.versions without '--local' to test grains
are sync'd without any missing keys in opts
"""
with caplog.at_level(logging.DEBUG):
ret = salt_call_cli.run("test.versions")
assert ret.returncode == 0
assert "Failed to sync grains module: 'master_uri'" not in caplog.messages

View file

@ -456,6 +456,103 @@ def test_owner():
assert aptpkg.owner(*paths) == "wget"
def test_owner_no_path():
"""
Test owner when path is not passed
"""
ret = aptpkg.owner()
assert ret == ""
def test_owner_doesnotexist():
"""
Test owner when the path does not exist
"""
mock = MagicMock(return_value="")
with patch.dict(aptpkg.__salt__, {"cmd.run_stdout": mock}):
ret = aptpkg.owner("/doesnotexist")
assert ret == ""
def test_get_http_proxy_url_username_passwd():
"""
Test _get_http_proxy_url when username and passwod set
"""
host = "repo.saltproject.io"
port = "888"
user = "user"
passwd = "password"
mock_conf = MagicMock()
mock_conf.side_effect = [host, port, user, passwd]
patch_conf = patch.dict(aptpkg.__salt__, {"config.option": mock_conf})
with patch_conf:
ret = aptpkg._get_http_proxy_url()
assert ret == f"http://{user}:{passwd}@{host}:{port}"
def test_get_http_proxy_url():
"""
Test basic functionality for _get_http_proxy_url
"""
host = "repo.saltproject.io"
port = "888"
user = ""
passwd = ""
mock_conf = MagicMock()
mock_conf.side_effect = [host, port, user, passwd]
patch_conf = patch.dict(aptpkg.__salt__, {"config.option": mock_conf})
with patch_conf:
ret = aptpkg._get_http_proxy_url()
assert ret == f"http://{host}:{port}"
def test_get_http_proxy_url_empty():
"""
Test _get_http_proxy_Url when host and port are empty
"""
host = ""
port = ""
user = ""
passwd = ""
mock_conf = MagicMock()
mock_conf.side_effect = [host, port, user, passwd]
patch_conf = patch.dict(aptpkg.__salt__, {"config.option": mock_conf})
with patch_conf:
ret = aptpkg._get_http_proxy_url()
assert ret == ""
def test_list_upgrades():
"""
Test basic functinoality for list_upgrades
"""
patch_data = patch("salt.utils.data.is_true", return_value=True)
patch_refresh = patch("salt.modules.aptpkg.refresh_db")
apt_ret = {
"pid": 2791,
"retcode": 0,
"stdout": "Reading package lists...\nBuilding dependency tree...\nReading state information...\nCalculating upgrade...\nThe following NEW packages will be installed:\n linux-cloud-tools-5.15.0-86 linux-cloud-tools-5.15.0-86-generic\n linux-headers-5.15.0-86 linux-headers-5.15.0-86-generic\n linux-image-5.15.0-86-generic linux-modules-5.15.0-86-generic\n linux-modules-extra-5.15.0-86-generic\nThe following packages have been kept back:\n libnetplan0 libsgutils2-2 netplan. io sg3-utils sg3-utils-udev\nThe following packages will be upgraded:\n linux-cloud-tools-virtual linux-generic linux-headers-generic\n linux-image-generic\n4 upgraded, 7 newly installed, 0 to remove and 5 not upgraded.\nInst linux-cloud-tools-5.15.0-86 (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nInst linux-cloud-tools-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nInst linux-cloud-tools-virtual [5.15.0.69.67] (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nInst linux-modules-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64]) []\nInst linux-image-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nInst linux-modules-extra-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nInst linux-generic [5.15.0.69.67] (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64]) []\nInst linux-image-generic [5.15.0.69.67] (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64]) []\nInst linux-headers-5.15.0-86 (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [all]) []\nInst linux-headers-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64]) []\nInst linux-headers-generic [5.15.0.69.67] (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-cloud-tools-5.15.0-86 (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-cloud-tools-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-cloud-tools-virtual (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-modules-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-image-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-modules-extra-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-generic (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-image-generic (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-headers-5.15.0-86 (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [all])\nConf linux-headers-5.15.0-86-generic (5.15.0-86.96 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])\nConf linux-headers-generic (5.15.0.86.83 Ubuntu:22.04/jammy-updates, Ubuntu:22.04/jammy-security [amd64])",
"stderr": "Running scope as unit: run-r014f3eae66364254b1cdacf701f1ab73.scope",
}
mock_apt = MagicMock(return_value=apt_ret)
patch_apt = patch("salt.modules.aptpkg._call_apt", mock_apt)
with patch_data, patch_refresh, patch_apt:
ret = aptpkg.list_upgrades(dist_upgrade=False)
assert ret == {
"linux-cloud-tools-5.15.0-86": "5.15.0-86.96",
"linux-cloud-tools-5.15.0-86-generic": "5.15.0-86.96",
"linux-cloud-tools-virtual": "5.15.0.86.83",
"linux-modules-5.15.0-86-generic": "5.15.0-86.96",
"linux-image-5.15.0-86-generic": "5.15.0-86.96",
"linux-modules-extra-5.15.0-86-generic": "5.15.0-86.96",
"linux-generic": "5.15.0.86.83",
"linux-image-generic": "5.15.0.86.83",
"linux-headers-5.15.0-86": "5.15.0-86.96",
"linux-headers-5.15.0-86-generic": "5.15.0-86.96",
"linux-headers-generic": "5.15.0.86.83",
}
def test_refresh_db(apt_q_update_var):
"""
Test - Updates the APT database to latest packages based upon repositories.
@ -1559,6 +1656,130 @@ def test_latest_version_names_empty():
assert ret == ""
def test_latest_version_fromrepo():
"""
test latest_version when `fromrepo` is passed in as a kwarg
"""
version = "5.15.0.86.83"
fromrepo = "jammy-updates"
list_ret = {"linux-cloud-tools-virtual": [version]}
apt_ret = {
"pid": 4361,
"retcode": 0,
"stdout": "linux-cloud-tools-virtual:\n"
f"Installed: 5.15.0.69.67\n Candidate: {version}\n Version"
f"table:\n {version} 990\n 990"
f"https://mirrors.edge.kernel.org/ubuntu {fromrepo}/main amd64"
"Packages\n 500 https://mirrors.edge.kernel.org/ubuntu"
"jammy-security/main amd64 Packages\n ***5.15.0.69.67 100\n"
"100 /var/lib/dpkg/status\n 5.15.0.25.27 500\n 500"
"https://mirrors.edge.kernel.org/ubuntu jammy/main amd64 Packages",
"stderr": "",
}
mock_apt = MagicMock(return_value=apt_ret)
patch_apt = patch("salt.modules.aptpkg._call_apt", mock_apt)
mock_list_pkgs = MagicMock(return_value=list_ret)
patch_list_pkgs = patch("salt.modules.aptpkg.list_pkgs", mock_list_pkgs)
with patch_apt, patch_list_pkgs:
ret = aptpkg.latest_version(
"linux-cloud-tools-virtual",
fromrepo=fromrepo,
refresh=False,
show_installed=True,
)
assert ret == version
assert mock_apt.call_args == call(
[
"apt-cache",
"-q",
"policy",
"linux-cloud-tools-virtual",
"-o",
f"APT::Default-Release={fromrepo}",
],
scope=False,
)
def test_latest_version_fromrepo_multiple_names():
"""
test latest_version when multiple names of pkgs are pased
"""
version = "5.15.0.86.83"
fromrepo = "jammy-updates"
list_ret = {
"linux-cloud-tools-virtual": ["5.15.0.69.67"],
"linux-generic": ["5.15.0.69.67"],
}
apt_ret_cloud = {
"pid": 4361,
"retcode": 0,
"stdout": "linux-cloud-tools-virtual:\n"
f"Installed: 5.15.0.69.67\n Candidate: {version}\n Version"
f"table:\n {version} 990\n 990"
f"https://mirrors.edge.kernel.org/ubuntu {fromrepo}/main amd64"
"Packages\n 500 https://mirrors.edge.kernel.org/ubuntu"
"jammy-security/main amd64 Packages\n ***5.15.0.69.67 100\n"
"100 /var/lib/dpkg/status\n 5.15.0.25.27 500\n 500"
"https://mirrors.edge.kernel.org/ubuntu jammy/main amd64 Packages",
"stderr": "",
}
apt_ret_generic = {
"pid": 4821,
"retcode": 0,
"stdout": "linux-generic:\n"
f"Installed: 5.15.0.69.67\n Candidate: {version}\n"
f"Version table:\n {version} 990\n 990"
"https://mirrors.edge.kernel.org/ubuntu"
"jammy-updates/main amd64 Packages\n 500"
"https://mirrors.edge.kernel.org/ubuntu"
"jammy-security/main amd64 Packages\n *** 5.15.0.69.67"
"100\n 100 /var/lib/dpkg/status\n 5.15.0.25.27"
"500\n 500 https://mirrors.edge.kernel.org/ubuntu"
"jammy/main amd64 Packages",
"stderr": "",
}
mock_apt = MagicMock()
mock_apt.side_effect = [apt_ret_cloud, apt_ret_generic]
patch_apt = patch("salt.modules.aptpkg._call_apt", mock_apt)
mock_list_pkgs = MagicMock(return_value=list_ret)
patch_list_pkgs = patch("salt.modules.aptpkg.list_pkgs", mock_list_pkgs)
with patch_apt, patch_list_pkgs:
ret = aptpkg.latest_version(
"linux-cloud-tools-virtual",
"linux-generic",
fromrepo=fromrepo,
refresh=False,
show_installed=True,
)
assert ret == {"linux-cloud-tools-virtual": version, "linux-generic": version}
assert mock_apt.call_args_list == [
call(
[
"apt-cache",
"-q",
"policy",
"linux-cloud-tools-virtual",
"-o",
"APT::Default-Release=jammy-updates",
],
scope=False,
),
call(
[
"apt-cache",
"-q",
"policy",
"linux-generic",
"-o",
"APT::Default-Release=jammy-updates",
],
scope=False,
),
]
def test_hold():
"""
test aptpkg.hold() when passing in the name of a package

View file

@ -0,0 +1,201 @@
"""
Test functions in state.py that are not a part of a class
"""
import pytest
import salt.state
from salt.utils.odict import OrderedDict
pytestmark = [
pytest.mark.core_test,
]
def test_state_args():
"""
Testing state.state_args when this state is being used:
/etc/foo.conf:
file.managed:
- contents: "blah"
- mkdirs: True
- user: ch3ll
- group: ch3ll
- mode: 755
/etc/bar.conf:
file.managed:
- use:
- file: /etc/foo.conf
"""
id_ = "/etc/bar.conf"
state = "file"
high = OrderedDict(
[
(
"/etc/foo.conf",
OrderedDict(
[
(
"file",
[
OrderedDict([("contents", "blah")]),
OrderedDict([("mkdirs", True)]),
OrderedDict([("user", "ch3ll")]),
OrderedDict([("group", "ch3ll")]),
OrderedDict([("mode", 755)]),
"managed",
{"order": 10000},
],
),
("__sls__", "test"),
("__env__", "base"),
]
),
),
(
"/etc/bar.conf",
OrderedDict(
[
(
"file",
[
OrderedDict(
[
(
"use",
[OrderedDict([("file", "/etc/foo.conf")])],
)
]
),
"managed",
{"order": 10001},
],
),
("__sls__", "test"),
("__env__", "base"),
]
),
),
]
)
ret = salt.state.state_args(id_, state, high)
assert ret == {"order", "use"}
def test_state_args_id_not_high():
"""
Testing state.state_args when id_ is not in high
"""
id_ = "/etc/bar.conf2"
state = "file"
high = OrderedDict(
[
(
"/etc/foo.conf",
OrderedDict(
[
(
"file",
[
OrderedDict([("contents", "blah")]),
OrderedDict([("mkdirs", True)]),
OrderedDict([("user", "ch3ll")]),
OrderedDict([("group", "ch3ll")]),
OrderedDict([("mode", 755)]),
"managed",
{"order": 10000},
],
),
("__sls__", "test"),
("__env__", "base"),
]
),
),
(
"/etc/bar.conf",
OrderedDict(
[
(
"file",
[
OrderedDict(
[
(
"use",
[OrderedDict([("file", "/etc/foo.conf")])],
)
]
),
"managed",
{"order": 10001},
],
),
("__sls__", "test"),
("__env__", "base"),
]
),
),
]
)
ret = salt.state.state_args(id_, state, high)
assert ret == set()
def test_state_args_state_not_high():
"""
Testing state.state_args when state is not in high date
"""
id_ = "/etc/bar.conf"
state = "file2"
high = OrderedDict(
[
(
"/etc/foo.conf",
OrderedDict(
[
(
"file",
[
OrderedDict([("contents", "blah")]),
OrderedDict([("mkdirs", True)]),
OrderedDict([("user", "ch3ll")]),
OrderedDict([("group", "ch3ll")]),
OrderedDict([("mode", 755)]),
"managed",
{"order": 10000},
],
),
("__sls__", "test"),
("__env__", "base"),
]
),
),
(
"/etc/bar.conf",
OrderedDict(
[
(
"file",
[
OrderedDict(
[
(
"use",
[OrderedDict([("file", "/etc/foo.conf")])],
)
]
),
"managed",
{"order": 10001},
],
),
("__sls__", "test"),
("__env__", "base"),
]
),
),
]
)
ret = salt.state.state_args(id_, state, high)
assert ret == set()

View file

@ -1302,3 +1302,37 @@ def test_check_refresh_pillar(minion_opts, caplog):
state_obj.check_refresh(data, ret)
mock_refresh.assert_called_once()
assert "Refreshing pillar..." in caplog.text
def test_module_refresh_runtimeerror(minion_opts, caplog):
"""
test module_refresh when runtimerror occurs
"""
mock_importlib = MagicMock()
mock_importlib.side_effect = RuntimeError("Error")
patch_importlib = patch("importlib.reload", mock_importlib)
patch_pillar = patch("salt.state.State._gather_pillar", return_value="")
with patch_importlib, patch_pillar:
state_obj = salt.state.State(minion_opts)
state_obj.module_refresh()
assert (
"Error encountered during module reload. Modules were not reloaded."
in caplog.text
)
def test_module_refresh_typeerror(minion_opts, caplog):
"""
test module_refresh when typeerror occurs
"""
mock_importlib = MagicMock()
mock_importlib.side_effect = TypeError("Error")
patch_importlib = patch("importlib.reload", mock_importlib)
patch_pillar = patch("salt.state.State._gather_pillar", return_value="")
with patch_importlib, patch_pillar:
state_obj = salt.state.State(minion_opts)
state_obj.module_refresh()
assert (
"Error encountered during module reload. Modules were not reloaded."
in caplog.text
)

View file

@ -8,7 +8,7 @@ import textwrap
import pytest # pylint: disable=unused-import
import salt.state
from salt.utils.odict import OrderedDict
from salt.utils.odict import DefaultOrderedDict, OrderedDict
log = logging.getLogger(__name__)
@ -352,3 +352,68 @@ def test_dont_extend_in_excluded_sls_file(highstate, state_tree_dir):
)
]
)
def test_verify_tops(highstate):
"""
test basic functionality of verify_tops
"""
tops = DefaultOrderedDict(OrderedDict)
tops["base"] = OrderedDict([("*", ["test", "test2"])])
matches = highstate.verify_tops(tops)
# [] means there where no errors when verifying tops
assert matches == []
def test_verify_tops_not_dict(highstate):
"""
test verify_tops when top data is not a dict
"""
matches = highstate.verify_tops(["base", "test", "test2"])
assert matches == ["Top data was not formed as a dict"]
def test_verify_tops_env_empty(highstate):
"""
test verify_tops when the environment is empty
"""
tops = DefaultOrderedDict(OrderedDict)
tops[""] = OrderedDict([("*", ["test", "test2"])])
matches = highstate.verify_tops(tops)
assert matches == ["Empty saltenv statement in top file"]
def test_verify_tops_sls_not_list(highstate):
"""
test verify_tops when the sls files are not a list
"""
tops = DefaultOrderedDict(OrderedDict)
tops["base"] = OrderedDict([("*", "test test2")])
matches = highstate.verify_tops(tops)
# [] means there where no errors when verifying tops
assert matches == ["Malformed topfile (state declarations not formed as a list)"]
def test_verify_tops_match(highstate):
"""
test basic functionality of verify_tops when using a matcher
like `match: glob`.
"""
tops = DefaultOrderedDict(OrderedDict)
tops["base"] = OrderedDict(
[("*", [OrderedDict([("match", "glob")]), "test", "test2"])]
)
matches = highstate.verify_tops(tops)
# [] means there where no errors when verifying tops
assert matches == []
def test_verify_tops_match_none(highstate):
"""
test basic functionality of verify_tops when using a matcher
when it is empty, like `match: ""`.
"""
tops = DefaultOrderedDict(OrderedDict)
tops["base"] = OrderedDict([("*", [OrderedDict([("match", "")]), "test", "test2"])])
matches = highstate.verify_tops(tops)
assert "Improperly formatted top file matcher in saltenv" in matches[0]