Merge branch 'master' into remove_payload_Serial

This commit is contained in:
Charles McMarrow 2023-06-27 23:28:57 -05:00 committed by GitHub
commit 94763bb50b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 261 additions and 116 deletions

1
changelog/45450.added.md Normal file
View file

@ -0,0 +1 @@
Added syncing of custom salt-ssh wrappers

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

@ -0,0 +1 @@
Fixed issue uninstalling duplicate packages in ``win_appx`` execution module

View file

@ -0,0 +1 @@
Remove netmiko_conn and pyeapi_conn from salt.modules.napalm_mod

View file

@ -0,0 +1 @@
changed 'gpg_decrypt_must_succeed' default from False to True

View file

@ -10,12 +10,12 @@ exist on the subject, to either execute in an imperative fashion where things
are executed in the order in which they are defined, or in a declarative
fashion where dependencies need to be mapped between objects.
Imperative ordering is finite and generally considered easier to write, but
Imperative ordering is deterministic and generally considered easier to write, but
declarative ordering is much more powerful and flexible but generally considered
more difficult to create.
Salt has been created to get the best of both worlds. States are evaluated in
a finite order, which guarantees that states are always executed in the same
a deterministic order, which guarantees that states are always executed in the same
order, and the states runtime is declarative, making Salt fully aware of
dependencies via the `requisite` system.
@ -26,7 +26,7 @@ State Auto Ordering
.. versionadded: 0.17.0
Salt always executes states in a finite manner, meaning that they will always
Salt always executes states in a deterministic manner, meaning that they will always
execute in the same order regardless of the system that is executing them. This
evaluation order makes it easy to know what order the states will be executed in,
but it is important to note that the requisite system will override the ordering

View file

@ -144,7 +144,7 @@ SDB ``salt.sdb`` (:ref:`index <all-salt.sdb>`) ``
Serializer ``salt.serializers`` (:ref:`index <all-salt.serializers>`) ``serializers`` [#no-fs]_ ``serializers_dirs``
SPM pkgdb ``salt.spm.pkgdb`` ``pkgdb`` [#no-fs]_ ``pkgdb_dirs``
SPM pkgfiles ``salt.spm.pkgfiles`` ``pkgfiles`` [#no-fs]_ ``pkgfiles_dirs``
SSH Wrapper ``salt.client.ssh.wrapper`` ``wrapper`` [#no-fs]_ ``wrapper_dirs``
SSH Wrapper ``salt.client.ssh.wrapper`` ``wrapper`` ``wrapper_dirs``
State ``salt.states`` (:ref:`index <all-salt.states>`) ``states`` ``states_dirs``
Thorium ``salt.thorium`` (:ref:`index <all-salt.thorium>`) ``thorium`` ``thorium_dirs``
Tokens ``salt.tokens`` ``tokens`` ``tokens_dirs``

View file

@ -10,8 +10,8 @@ Many of the most powerful and useful engineering solutions are founded on
simple principles. Salt States strive to do just that: K.I.S.S. (Keep It
Stupidly Simple)
The core of the Salt State system is the SLS, or **S**\ a\ **L**\ t
**S**\ tate file. The SLS is a representation of the state in which
The core of the Salt State system is the SLS, or **S**\ tructured **L**\ ayered **S**\ tate.
The SLS is a representation of the state in which
a system should be in, and is set up to contain this data in a simple format.
This is often called configuration management.

View file

@ -1089,7 +1089,7 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze(
"decrypt_pillar_delimiter": ":",
"decrypt_pillar_default": "gpg",
"decrypt_pillar_renderers": ["gpg"],
"gpg_decrypt_must_succeed": False,
"gpg_decrypt_must_succeed": True,
# Update intervals
"roots_update_interval": DEFAULT_INTERVAL,
"gitfs_update_interval": DEFAULT_INTERVAL,
@ -1329,7 +1329,7 @@ DEFAULT_MASTER_OPTS = immutabletypes.freeze(
"decrypt_pillar_delimiter": ":",
"decrypt_pillar_default": "gpg",
"decrypt_pillar_renderers": ["gpg"],
"gpg_decrypt_must_succeed": False,
"gpg_decrypt_must_succeed": True,
"thoriumenv": None,
"thorium_top": "top.sls",
"thorium_interval": 0.5,
@ -2013,7 +2013,7 @@ def _read_conf_file(path):
try:
conf_opts = salt.utils.yaml.safe_load(conf_file) or {}
except salt.utils.yaml.YAMLError as err:
message = "Error parsing configuration file: {} - {}".format(path, err)
message = f"Error parsing configuration file: {path} - {err}"
log.error(message)
if path.endswith("_schedule.conf"):
# Create empty dictionary of config options
@ -2110,7 +2110,7 @@ def load_config(path, env_var, default_path=None, exit_on_config_errors=True):
# If the configuration file is missing, attempt to copy the template,
# after removing the first header line.
if not os.path.isfile(path):
template = "{}.template".format(path)
template = f"{path}.template"
if os.path.isfile(template):
log.debug("Writing %s based on %s", path, template)
with salt.utils.files.fopen(path, "w") as out:
@ -2780,7 +2780,7 @@ def apply_cloud_config(overrides, defaults=None):
if alias not in config["providers"]:
config["providers"][alias] = {}
detail["provider"] = "{}:{}".format(alias, driver)
detail["provider"] = f"{alias}:{driver}"
config["providers"][alias][driver] = detail
elif isinstance(details, dict):
if "driver" not in details:
@ -2797,7 +2797,7 @@ def apply_cloud_config(overrides, defaults=None):
if alias not in config["providers"]:
config["providers"][alias] = {}
details["provider"] = "{}:{}".format(alias, driver)
details["provider"] = f"{alias}:{driver}"
config["providers"][alias][driver] = details
# Migrate old configuration
@ -3068,7 +3068,7 @@ def apply_cloud_providers_config(overrides, defaults=None):
for entry in val:
if "driver" not in entry:
entry["driver"] = "-only-extendable-{}".format(ext_count)
entry["driver"] = f"-only-extendable-{ext_count}"
ext_count += 1
if key not in providers:
@ -3111,7 +3111,7 @@ def apply_cloud_providers_config(overrides, defaults=None):
details["driver"], provider_alias, alias, provider
)
)
details["extends"] = "{}:{}".format(alias, provider)
details["extends"] = f"{alias}:{provider}"
# change provider details '-only-extendable-' to extended
# provider name
details["driver"] = provider
@ -3132,10 +3132,10 @@ def apply_cloud_providers_config(overrides, defaults=None):
)
else:
if driver in providers.get(extends):
details["extends"] = "{}:{}".format(extends, driver)
details["extends"] = f"{extends}:{driver}"
elif "-only-extendable-" in providers.get(extends):
details["extends"] = "{}:{}".format(
extends, "-only-extendable-{}".format(ext_count)
extends, f"-only-extendable-{ext_count}"
)
else:
# We're still not aware of what we're trying to extend
@ -3849,7 +3849,7 @@ def _update_discovery_config(opts):
for key in opts["discovery"]:
if key not in discovery_config:
raise salt.exceptions.SaltConfigurationError(
"Unknown discovery option: {}".format(key)
f"Unknown discovery option: {key}"
)
if opts.get("__role") != "minion":
for key in ["attempts", "pause", "match"]:

View file

@ -742,36 +742,6 @@ def netmiko_config(*config_commands, **kwargs):
return __salt__["netmiko.send_config"](config_commands=config_commands, **kwargs)
@proxy_napalm_wrap
def netmiko_conn(**kwargs):
"""
.. versionadded:: 2019.2.0
Return the connection object with the network device, over Netmiko, passing
the authentication details from the existing NAPALM connection.
.. warning::
This function is not suitable for CLI usage, more rather to be used
in various Salt modules.
USAGE Example:
.. code-block:: python
conn = __salt__['napalm.netmiko_conn']()
res = conn.send_command('show interfaces')
conn.disconnect()
"""
salt.utils.versions.warn_until(
3007,
"This 'napalm_mod.netmiko_conn' function as been deprecated and "
"will be removed in the {version} release, as such, it has been "
"made an internal function since it is not suitable for CLI usage",
)
return _netmiko_conn(**kwargs)
@proxy_napalm_wrap
def junos_rpc(cmd=None, dest=None, format=None, **kwargs):
"""
@ -1139,36 +1109,6 @@ def pyeapi_call(method, *args, **kwargs):
return __salt__["pyeapi.call"](method, *args, **pyeapi_kwargs)
@proxy_napalm_wrap
def pyeapi_conn(**kwargs):
"""
.. versionadded:: 2019.2.0
Return the connection object with the Arista switch, over ``pyeapi``,
passing the authentication details from the existing NAPALM connection.
.. warning::
This function is not suitable for CLI usage, more rather to be used in
various Salt modules, to reusing the established connection, as in
opposite to opening a new connection for each task.
Usage example:
.. code-block:: python
conn = __salt__['napalm.pyeapi_conn']()
res1 = conn.run_commands('show version')
res2 = conn.get_config(as_string=True)
"""
salt.utils.versions.warn_until(
3007,
"This 'napalm_mod.pyeapi_conn' function as been deprecated and "
"will be removed in the {version} release, as such, it has been "
"made an internal function since it is not suitable for CLI usage",
)
return _pyeapi_conn(**kwargs)
@proxy_napalm_wrap
def pyeapi_config(
commands=None,
@ -1839,7 +1779,11 @@ def config_diff_text(
@depends(HAS_SCP)
def scp_get(
remote_path, local_path="", recursive=False, preserve_times=False, **kwargs
remote_path,
local_path="",
recursive=False,
preserve_times=False,
**kwargs,
):
"""
.. versionadded:: 2019.2.0

View file

@ -86,9 +86,7 @@ def _get_top_file_envs():
else:
envs = "base"
except SaltRenderError as exc:
raise CommandExecutionError(
"Unable to render top file(s): {}".format(exc)
)
raise CommandExecutionError(f"Unable to render top file(s): {exc}")
__context__["saltutil._top_file_envs"] = envs
return envs
@ -164,7 +162,7 @@ def update(version=None):
try:
version = app.find_update()
except urllib.error.URLError as exc:
ret["_error"] = "Could not connect to update_url. Error: {}".format(exc)
ret["_error"] = f"Could not connect to update_url. Error: {exc}"
return ret
if not version:
ret["_error"] = "No updates available"
@ -172,21 +170,21 @@ def update(version=None):
try:
app.fetch_version(version)
except EskyVersionError as exc:
ret["_error"] = "Unable to fetch version {}. Error: {}".format(version, exc)
ret["_error"] = f"Unable to fetch version {version}. Error: {exc}"
return ret
try:
app.install_version(version)
except EskyVersionError as exc:
ret["_error"] = "Unable to install version {}. Error: {}".format(version, exc)
ret["_error"] = f"Unable to install version {version}. Error: {exc}"
return ret
try:
app.cleanup()
except Exception as exc: # pylint: disable=broad-except
ret["_error"] = "Unable to cleanup. Error: {}".format(exc)
ret["_error"] = f"Unable to cleanup. Error: {exc}"
restarted = {}
for service in __opts__["update_restart_services"]:
restarted[service] = __salt__["service.restart"](service)
ret["comment"] = "Updated from {} to {}".format(oldversion, version)
ret["comment"] = f"Updated from {oldversion} to {version}"
ret["restarted"] = restarted
return ret
@ -1016,6 +1014,55 @@ def sync_executors(
return ret
def sync_wrapper(
saltenv=None, refresh=True, extmod_whitelist=None, extmod_blacklist=None
):
"""
.. versionadded:: 3007.0
Sync salt-ssh wrapper modules from ``salt://_wrapper`` 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 wrappers to sync. If no top files
are found, then the ``base`` environment will be synced.
refresh : True
If ``True``, refresh the available wrapper modules on the minion.
This refresh will be performed even if no wrappers are synced.
Set to ``False`` to prevent this refresh.
extmod_whitelist : None
comma-seperated list of modules to sync
extmod_blacklist : None
comma-seperated 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_wrapper
salt '*' saltutil.sync_wrapper saltenv=dev
salt '*' saltutil.sync_wrapper saltenv=base,dev
"""
if __opts__["file_client"] != "local":
raise CommandExecutionError(
"Wrapper modules can only be synced to masterless minions"
)
ret = _sync("wrapper", saltenv, extmod_whitelist, extmod_blacklist)
if refresh:
refresh_modules()
return ret
def sync_all(
saltenv=None,
refresh=True,
@ -1105,6 +1152,9 @@ def sync_all(
ret["matchers"] = sync_matchers(saltenv, False, extmod_whitelist, extmod_blacklist)
if __opts__["file_client"] == "local":
ret["pillar"] = sync_pillar(saltenv, False, extmod_whitelist, extmod_blacklist)
ret["wrapper"] = sync_wrapper(
saltenv, False, extmod_whitelist, extmod_blacklist
)
if refresh:
# we don't need to call refresh_modules here because it's done by refresh_pillar
refresh_pillar(clean_cache=clean_pillar_cache)
@ -1402,7 +1452,7 @@ def find_cached_job(jid):
" enable cache_jobs on this minion"
)
else:
return "Local jobs cache directory {} not found".format(job_dir)
return f"Local jobs cache directory {job_dir} not found"
path = os.path.join(job_dir, "return.p")
with salt.utils.files.fopen(path, "rb") as fp_:
buf = fp_.read()
@ -1604,7 +1654,7 @@ def _exec(
kwarg,
batch=False,
subset=False,
**kwargs
**kwargs,
):
fcn_ret = {}
seen = 0
@ -1660,7 +1710,7 @@ def cmd(
ret="",
kwarg=None,
ssh=False,
**kwargs
**kwargs,
):
"""
.. versionchanged:: 2017.7.0
@ -1681,7 +1731,7 @@ def cmd(
# if return is empty, we may have not used the right conf,
# try with the 'minion relative master configuration counter part
# if available
master_cfgfile = "{}master".format(cfgfile[:-6]) # remove 'minion'
master_cfgfile = f"{cfgfile[:-6]}master" # remove 'minion'
if (
not fcn_ret
and cfgfile.endswith("{}{}".format(os.path.sep, "minion"))
@ -1704,7 +1754,7 @@ def cmd_iter(
ret="",
kwarg=None,
ssh=False,
**kwargs
**kwargs,
):
"""
.. versionchanged:: 2017.7.0

View file

@ -48,6 +48,7 @@ import logging
import salt.utils.platform
import salt.utils.win_pwsh
import salt.utils.win_reg
from salt.exceptions import CommandExecutionError
log = logging.getLogger(__name__)
@ -261,10 +262,15 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
frameworks=frameworks,
bundles=True,
)
if bundle and bundle["IsBundle"]:
log.debug(f'Found bundle: {bundle["PackageFullName"]}')
remove_name = bundle["PackageFullName"]
if isinstance(bundle, list):
# There may be multiple packages that match the query
# Let's remove each one individually
for item in bundle:
remove_package(item)
else:
if bundle and bundle["IsBundle"]:
log.debug(f'Found bundle: {bundle["PackageFullName"]}')
remove_name = bundle["PackageFullName"]
if deprovision_only:
log.debug("Deprovisioning package: %s", remove_name)
remove_cmd = (
@ -273,7 +279,16 @@ def remove(query=None, include_store=False, frameworks=False, deprovision_only=F
else:
log.debug("Removing package: %s", remove_name)
remove_cmd = f"Remove-AppxPackage -AllUsers -Package {remove_name}"
salt.utils.win_pwsh.run_dict(remove_cmd)
try:
salt.utils.win_pwsh.run_dict(remove_cmd)
except CommandExecutionError as exc:
# Some packages may be in a partially updated state
# In that case, there will be 2 entries with the same name
# The old one will not have an installer and will throw an error
# We should be safe just logging the message
# This is really hard to replicate
log.debug(f"There was an error removing package: {remove_name}")
log.debug(exc)
if isinstance(packages, list):
log.debug("Removing %s packages", len(packages))

View file

@ -439,11 +439,6 @@ def _decrypt_ciphertext(cipher):
decrypt_error,
)
)
else:
salt.utils.versions.warn_until(
3007,
"After the Chlorine release of Salt, gpg_decrypt_must_succeed will default to True.",
)
return cipher
else:
if __opts__.get("gpg_cache"):

View file

@ -146,6 +146,11 @@ def sync_all(saltenv="base", extmod_whitelist=None, extmod_blacklist=None):
extmod_whitelist=extmod_whitelist,
extmod_blacklist=extmod_blacklist,
)
ret["wrapper"] = sync_wrapper(
saltenv=saltenv,
extmod_whitelist=extmod_whitelist,
extmod_blacklist=extmod_blacklist,
)
ret["roster"] = sync_roster(
saltenv=saltenv,
extmod_whitelist=extmod_whitelist,
@ -835,3 +840,34 @@ def sync_executors(saltenv="base", extmod_whitelist=None, extmod_blacklist=None)
extmod_whitelist=extmod_whitelist,
extmod_blacklist=extmod_blacklist,
)[0]
def sync_wrapper(saltenv="base", extmod_whitelist=None, extmod_blacklist=None):
"""
.. versionadded:: 3007.0
Sync salt-ssh wrapper modules from ``salt://_wrapper`` to the master.
saltenv : base
The fileserver environment from which to sync. To sync from more than
one environment, pass a comma-separated list.
extmod_whitelist : None
comma-seperated list of modules to sync
extmod_blacklist : None
comma-seperated list of modules to blacklist based on type
CLI Example:
.. code-block:: bash
salt-run saltutil.sync_wrapper
"""
return salt.utils.extmods.sync(
__opts__,
"wrapper",
saltenv=saltenv,
extmod_whitelist=extmod_whitelist,
extmod_blacklist=extmod_blacklist,
)[0]

View file

@ -29,18 +29,18 @@ def _sync_single(name, module, **kwargs):
if __opts__["test"]:
ret["result"] = None
ret["comment"] = "saltutil.sync_{} would have been run".format(module)
ret["comment"] = f"saltutil.sync_{module} would have been run"
return ret
try:
sync_status = __salt__["saltutil.sync_{}".format(module)](**kwargs)
sync_status = __salt__[f"saltutil.sync_{module}"](**kwargs)
if sync_status:
ret["changes"][module] = sync_status
ret["comment"] = "Updated {}.".format(module)
ret["comment"] = f"Updated {module}."
except Exception as e: # pylint: disable=broad-except
log.error("Failed to run saltutil.sync_%s: %s", module, e)
ret["result"] = False
ret["comment"] = "Failed to run sync_{}: {}".format(module, e)
ret["comment"] = f"Failed to run sync_{module}: {e}"
return ret
if not ret["changes"]:
@ -76,7 +76,7 @@ def sync_all(name, **kwargs):
except Exception as e: # pylint: disable=broad-except
log.error("Failed to run saltutil.sync_all: %s", e)
ret["result"] = False
ret["comment"] = "Failed to run sync_all: {}".format(e)
ret["comment"] = f"Failed to run sync_all: {e}"
return ret
if not ret["changes"]:
@ -349,3 +349,19 @@ def sync_serializers(name, **kwargs):
- refresh: True
"""
return _sync_single(name, "serializers", **kwargs)
def sync_wrapper(name, **kwargs):
"""
.. versionadded:: 3007.0
Performs the same task as saltutil.sync_wrapper module
See :mod:`saltutil module for full list of options <salt.modules.saltutil>`
.. code-block:: yaml
sync_everything:
saltutil.sync_wrapper:
- refresh: True
"""
return _sync_single(name, "wrapper", **kwargs)

View file

@ -31,6 +31,7 @@ def get_module_types():
"tokens",
"serializers",
"executors",
"wrapper",
"roster",
]
return module_types
@ -66,6 +67,7 @@ def module_sync_functions():
"tokens": "eauth_tokens",
"serializers": "serializers",
"executors": "executors",
"wrapper": "wrapper",
"roster": "roster",
}
@ -76,7 +78,7 @@ def test_sync(
"""
Ensure modules are synced when various sync functions are called
"""
module_name = "hello_sync_{}".format(module_type)
module_name = f"hello_sync_{module_type}"
module_contents = """
def __virtual__():
return "hello"
@ -85,17 +87,17 @@ def world():
return "world"
"""
test_moduledir = salt_master.state_tree.base.paths[0] / "_{}".format(module_type)
test_moduledir = salt_master.state_tree.base.paths[0] / f"_{module_type}"
test_moduledir.mkdir(parents=True, exist_ok=True)
module_tempfile = salt_master.state_tree.base.temp_file(
"_{}/{}.py".format(module_type, module_name), module_contents
f"_{module_type}/{module_name}.py", module_contents
)
with module_tempfile, test_moduledir:
salt_cmd = "saltutil.sync_{}".format(module_sync_functions[module_type])
salt_cmd = f"saltutil.sync_{module_sync_functions[module_type]}"
ret = salt_run_cli.run(salt_cmd)
assert ret.returncode == 0
assert "{}.hello".format(module_type) in ret.stdout
assert f"{module_type}.hello" in ret.stdout
def _write_module_dir_and_file(module_type, salt_minion, salt_master):
@ -111,11 +113,11 @@ def world():
return "world"
"""
test_moduledir = salt_master.state_tree.base.paths[0] / "_{}".format(module_type)
test_moduledir = salt_master.state_tree.base.paths[0] / f"_{module_type}"
test_moduledir.mkdir(parents=True, exist_ok=True)
module_tempfile = salt_master.state_tree.base.temp_file(
"_{}/{}.py".format(module_type, module_name), module_contents
f"_{module_type}/{module_name}.py", module_contents
)
return module_tempfile
@ -139,4 +141,4 @@ def test_sync_all(salt_run_cli, salt_minion, salt_master):
assert ret.returncode == 0
for module_type in get_module_types():
assert "{}.hello".format(module_type) in ret.stdout
assert f"{module_type}.hello" in ret.stdout

View file

@ -63,6 +63,30 @@ def _state_tree(salt_master, tmp_path):
yield
@pytest.fixture
def custom_wrapper(salt_run_cli, base_env_state_tree_root_dir):
module_contents = r"""\
def __virtual__():
return "grains_custom"
def items():
return __grains__.value()
"""
module_dir = base_env_state_tree_root_dir / "_wrapper"
module_tempfile = pytest.helpers.temp_file(
"grains_custom.py", module_contents, module_dir
)
try:
with module_tempfile:
ret = salt_run_cli.run("saltutil.sync_wrapper")
assert ret.returncode == 0
assert "wrapper.grains_custom" in ret.data
yield
finally:
ret = salt_run_cli.run("saltutil.sync_wrapper")
assert ret.returncode == 0
@pytest.mark.usefixtures("_state_tree")
def test_state_apply(salt_ssh_cli):
ret = salt_ssh_cli.run("state.apply", "core")
@ -77,3 +101,14 @@ def test_state_highstate(salt_ssh_cli):
assert ret.returncode == 0
state_result = StateResult(ret.data)
assert state_result.result is True
@pytest.mark.usefixtures("custom_wrapper")
def test_custom_wrapper(salt_ssh_cli):
ret = salt_ssh_cli.run(
"grains_custom.items",
)
assert ret.returncode == 0
assert ret.data
assert "id" in ret.data
assert ret.data["id"] in ("localhost", "127.0.0.1")

View file

@ -1,7 +1,7 @@
import pytest
import salt.modules.win_appx as win_appx
from tests.support.mock import MagicMock, patch
from tests.support.mock import MagicMock, call, patch
pytestmark = [
pytest.mark.windows_whitelisted,
@ -136,6 +136,51 @@ def test_remove():
mock_run_dict.assert_called_with(cmd)
def test_remove_duplicate():
mock_run_dict = MagicMock()
mock_list_return_1 = [
{
"Name": "Microsoft.BingWeather",
"PackageFullName": "Microsoft.BingWeather_full_name_1",
"IsBundle": False,
},
{
"Name": "Microsoft.BingWeather",
"PackageFullName": "Microsoft.BingWeather_full_name_2",
"IsBundle": False,
},
]
mock_list_return_2 = [
{
"Name": "Microsoft.BingWeather",
"PackageFullName": "Microsoft.BingWeather_full_name_1",
"IsBundle": True,
},
{
"Name": "Microsoft.BingWeather",
"PackageFullName": "Microsoft.BingWeather_full_name_2",
"IsBundle": True,
},
]
mock_list_return_3 = [
{
"Name": "Microsoft.BingWeather",
"PackageFullName": "Microsoft.BingWeather_full_name_2",
"IsBundle": True,
},
]
mock_list = MagicMock(
side_effect=[mock_list_return_1, mock_list_return_2, mock_list_return_3]
)
with patch("salt.utils.win_pwsh.run_dict", mock_run_dict), patch.object(
win_appx, "list_", mock_list
):
assert win_appx.remove("*bingweather*") is True
cmd_1 = "Remove-AppxPackage -AllUsers -Package Microsoft.BingWeather_full_name_1"
cmd_2 = "Remove-AppxPackage -AllUsers -Package Microsoft.BingWeather_full_name_2"
mock_run_dict.assert_has_calls([call(cmd_1), call(cmd_2)])
def test_remove_deprovision_only():
mock_run_dict = MagicMock()
mock_list_return = {

View file

@ -36,6 +36,7 @@ def test_saltutil_sync_all_nochange():
"pillar": [],
"matchers": [],
"serializers": [],
"wrapper": [],
}
state_id = "somename"
state_result = {
@ -71,6 +72,7 @@ def test_saltutil_sync_all_test():
"pillar": [],
"matchers": [],
"serializers": [],
"wrapper": [],
}
state_id = "somename"
state_result = {
@ -107,6 +109,7 @@ def test_saltutil_sync_all_change():
"pillar": [],
"matchers": [],
"serializers": [],
"wrapper": [],
}
state_id = "somename"
state_result = {
@ -139,4 +142,4 @@ def test_saltutil_sync_states_should_match_saltutil_module():
for fn in module_functions:
assert (
fn in state_functions
), "modules.saltutil.{} has no matching state in states.saltutil".format(fn)
), f"modules.saltutil.{fn} has no matching state in states.saltutil"