Merge branch '3007.x' of github.com:saltstack/salt into hotfix/merge-forward-into-3007.x

This commit is contained in:
Pedro Algarvio 2024-04-03 09:19:29 +01:00
commit 87db565b70
No known key found for this signature in database
GPG key ID: BB36BF6584A298FF
15 changed files with 140 additions and 421 deletions

View file

@ -176,7 +176,7 @@ jobs:
- name: Run Changed Tests
id: run-fast-changed-tests
if: ${{ fromJSON(inputs.testrun)['type'] != 'full' && fromJSON(inputs.testrun)['selected_tests']['fast'] == false }}
if: ${{ fromJSON(inputs.testrun)['type'] != 'full' }}
env:
SKIP_REQUIREMENTS_INSTALL: "1"
PRINT_TEST_SELECTION: "0"

View file

@ -199,7 +199,7 @@ jobs:
- name: Run Changed Tests
id: run-fast-changed-tests
if: ${{ fromJSON(inputs.testrun)['type'] != 'full' && fromJSON(inputs.testrun)['selected_tests']['fast'] == false }}
if: ${{ fromJSON(inputs.testrun)['type'] != 'full' }}
run: |
tools --timestamps --no-output-timeout-secs=1800 --timeout-secs=14400 vm test --skip-requirements-install \
--nox-session=${{ inputs.nox-session }} --rerun-failures -E SALT_TRANSPORT ${{ matrix.fips && '--fips ' || '' }}${{ inputs.distro-slug }} \

View file

@ -1,3 +0,0 @@
Added the ability to pass a version of chocolatey to install to the
chocolatey.bootstrap function. Also added states to bootstrap and
unbootstrap chocolatey.

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

@ -0,0 +1 @@
Fixed pillar.ls doesn't accept kwargs

View file

@ -14,7 +14,6 @@ from requests.structures import CaseInsensitiveDict
import salt.utils.data
import salt.utils.platform
import salt.utils.win_dotnet
from salt.exceptions import (
CommandExecutionError,
CommandNotFoundError,
@ -127,26 +126,16 @@ def _find_chocolatey():
raise CommandExecutionError(err)
def chocolatey_version(refresh=False):
def chocolatey_version():
"""
Returns the version of Chocolatey installed on the minion.
Args:
refresh (bool):
Refresh the cached version of chocolatey
.. versionadded:: 3008.0
CLI Example:
.. code-block:: bash
salt '*' chocolatey.chocolatey_version
"""
if refresh:
__context__.pop("chocolatey._version", False)
if "chocolatey._version" in __context__:
return __context__["chocolatey._version"]
@ -158,7 +147,7 @@ def chocolatey_version(refresh=False):
return __context__["chocolatey._version"]
def bootstrap(force=False, source=None, version=None):
def bootstrap(force=False, source=None):
"""
Download and install the latest version of the Chocolatey package manager
via the official bootstrap.
@ -177,11 +166,6 @@ def bootstrap(force=False, source=None, version=None):
and .NET requirements must already be met on the target. This shouldn't
be a problem on Windows versions 2012/8 and later
.. note::
If you're installing chocolatey version 2.0+ the system requires .NET
4.8. Installing this requires a reboot, therefore this module will not
automatically install .NET 4.8.
Args:
force (bool):
@ -198,12 +182,6 @@ def bootstrap(force=False, source=None, version=None):
.. versionadded:: 3001
version (str):
The version of chocolatey to install. The latest version is
installed if this value is ``None``. Default is ``None``
.. versionadded:: 3008.0
Returns:
str: The stdout of the Chocolatey installation script
@ -220,9 +198,6 @@ def bootstrap(force=False, source=None, version=None):
# To bootstrap Chocolatey from a file on C:\\Temp
salt '*' chocolatey.bootstrap source=C:\\Temp\\chocolatey.nupkg
# To bootstrap Chocolatey version 1.4.0
salt '*' chocolatey.bootstrap version=1.4.0
"""
# Check if Chocolatey is already present in the path
try:
@ -297,7 +272,7 @@ def bootstrap(force=False, source=None, version=None):
# Check that .NET v4.0+ is installed
# Windows 7 / Windows Server 2008 R2 and below do not come with at least
# .NET v4.0 installed
if not salt.utils.win_dotnet.version_at_least(version="4"):
if not __utils__["dotnet.version_at_least"](version="4"):
# It took until .NET v4.0 for Microsoft got the hang of making
# installers, this should work under any version of Windows
url = "http://download.microsoft.com/download/1/B/E/1BE39E79-7E39-46A3-96FF-047F95396215/dotNetFx40_Full_setup.exe"
@ -361,21 +336,10 @@ def bootstrap(force=False, source=None, version=None):
f"Failed to find Chocolatey installation script: {script}"
)
# You tell the chocolatey install script which version to install by setting
# an environment variable
if version:
env = {"chocolateyVersion": version}
else:
env = None
# Run the Chocolatey bootstrap
log.debug("Installing Chocolatey: %s", script)
result = __salt__["cmd.script"](
source=script,
cwd=os.path.dirname(script),
shell="powershell",
python_shell=True,
env=env,
script, cwd=os.path.dirname(script), shell="powershell", python_shell=True
)
if result["retcode"] != 0:
err = "Bootstrapping Chocolatey failed: {}".format(result["stderr"])

View file

@ -189,7 +189,7 @@ def get(
return ret
def items(*args, **kwargs):
def items(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
"""
Calls the master for a fresh pillar and generates the pillar data on the
fly
@ -242,17 +242,15 @@ def items(*args, **kwargs):
"""
# Preserve backwards compatibility
if args:
return item(*args)
return item(*args, pillarenv=pillarenv, saltenv=saltenv)
pillarenv = kwargs.get("pillarenv")
if pillarenv is None:
if __opts__.get("pillarenv_from_saltenv", False):
pillarenv = kwargs.get("saltenv") or __opts__["saltenv"]
pillarenv = saltenv or __opts__["saltenv"]
else:
pillarenv = __opts__["pillarenv"]
pillar_override = kwargs.get("pillar")
pillar_enc = kwargs.get("pillar_enc")
pillar_override = pillar
if pillar_override and pillar_enc:
try:
@ -277,7 +275,56 @@ def items(*args, **kwargs):
# Allow pillar.data to also be used to return pillar data
data = salt.utils.functools.alias_function(items, "data")
def data(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
"""
Calls the master for a fresh pillar, generates the pillar data on the
fly (same as :py:func:`items`)
pillar
If specified, allows for a dictionary of pillar data to be made
available to pillar and ext_pillar rendering. these pillar variables
will also override any variables of the same name in pillar or
ext_pillar.
pillar_enc
If specified, the data passed in the ``pillar`` argument will be passed
through this renderer to decrypt it.
.. note::
This will decrypt on the minion side, so the specified renderer
must be set up on the minion for this to work. Alternatively,
pillar data can be decrypted master-side. For more information, see
the :ref:`Pillar Encryption <pillar-encryption>` documentation.
Pillar data that is decrypted master-side, is not decrypted until
the end of pillar compilation though, so minion-side decryption
will be necessary if the encrypted pillar data must be made
available in an decrypted state pillar/ext_pillar rendering.
pillarenv
Pass a specific pillar environment from which to compile pillar data.
If not specified, then the minion's :conf_minion:`pillarenv` option is
not used, and if that also is not specified then all configured pillar
environments will be merged into a single pillar dictionary and
returned.
saltenv
Included only for compatibility with
:conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored.
CLI Examples:
.. code-block:: bash
salt '*' pillar.data
"""
return items(
*args,
pillar=pillar,
pillar_enc=pillar_enc,
pillarenv=pillarenv,
saltenv=saltenv,
)
def _obfuscate_inner(var):
@ -296,7 +343,7 @@ def _obfuscate_inner(var):
return f"<{var.__class__.__name__}>"
def obfuscate(*args, **kwargs):
def obfuscate(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
"""
.. versionadded:: 2015.8.0
@ -323,18 +370,57 @@ def obfuscate(*args, **kwargs):
salt '*' pillar.obfuscate
"""
return _obfuscate_inner(items(*args, **kwargs))
return _obfuscate_inner(
items(
*args,
pillar=pillar,
pillar_enc=pillar_enc,
pillarenv=pillarenv,
saltenv=saltenv,
)
)
# naming chosen for consistency with grains.ls, although it breaks the short
# identifier rule.
def ls(*args):
def ls(*args, pillar=None, pillar_enc=None, pillarenv=None, saltenv=None):
"""
.. versionadded:: 2015.8.0
Calls the master for a fresh pillar, generates the pillar data on the
fly (same as :py:func:`items`), but only shows the available main keys.
pillar
If specified, allows for a dictionary of pillar data to be made
available to pillar and ext_pillar rendering. these pillar variables
will also override any variables of the same name in pillar or
ext_pillar.
pillar_enc
If specified, the data passed in the ``pillar`` argument will be passed
through this renderer to decrypt it.
.. note::
This will decrypt on the minion side, so the specified renderer
must be set up on the minion for this to work. Alternatively,
pillar data can be decrypted master-side. For more information, see
the :ref:`Pillar Encryption <pillar-encryption>` documentation.
Pillar data that is decrypted master-side, is not decrypted until
the end of pillar compilation though, so minion-side decryption
will be necessary if the encrypted pillar data must be made
available in an decrypted state pillar/ext_pillar rendering.
pillarenv
Pass a specific pillar environment from which to compile pillar data.
If not specified, then the minion's :conf_minion:`pillarenv` option is
not used, and if that also is not specified then all configured pillar
environments will be merged into a single pillar dictionary and
returned.
saltenv
Included only for compatibility with
:conf_minion:`pillarenv_from_saltenv`, and is otherwise ignored.
CLI Examples:
.. code-block:: bash
@ -342,10 +428,18 @@ def ls(*args):
salt '*' pillar.ls
"""
return list(items(*args))
return list(
items(
*args,
pillar=pillar,
pillar_enc=pillar_enc,
pillarenv=pillarenv,
saltenv=saltenv,
)
)
def item(*args, **kwargs):
def item(*args, default=None, delimiter=None, pillarenv=None, saltenv=None):
"""
.. versionadded:: 0.16.2
@ -399,10 +493,12 @@ def item(*args, **kwargs):
salt '*' pillar.item foo bar baz
"""
ret = {}
default = kwargs.get("default", "")
delimiter = kwargs.get("delimiter", DEFAULT_TARGET_DELIM)
pillarenv = kwargs.get("pillarenv", None)
saltenv = kwargs.get("saltenv", None)
if default is None:
default = ""
if delimiter is None:
delimiter = DEFAULT_TARGET_DELIM
pillar_dict = (
__pillar__
@ -413,7 +509,7 @@ def item(*args, **kwargs):
try:
for arg in args:
ret[arg] = salt.utils.data.traverse_dict_and_list(
pillar_dict, arg, default, delimiter
data=pillar_dict, key=arg, default=default, delimiter=delimiter
)
except KeyError:
pass

View file

@ -11,7 +11,7 @@ Manage Windows Packages using Chocolatey
import salt.utils.data
import salt.utils.versions
from salt.exceptions import CommandExecutionError, SaltInvocationError
from salt.exceptions import SaltInvocationError
def __virtual__():
@ -513,182 +513,3 @@ def source_present(
ret["changes"] = salt.utils.data.compare_dicts(pre_install, post_install)
return ret
def bootstrapped(name, force=False, source=None, version=None):
"""
.. versionadded:: 3008.0
Ensure chocolatey is installed on the system.
You can't upgrade an existing installation with this state. You must use
chocolatey to upgrade chocolatey.
For example:
.. code-block:: bash
choco upgrade chocolatey --version 2.2.0
Args:
name (str):
The name of the state that installs chocolatey. Required for all
states. This is ignored.
force (bool):
Run the bootstrap process even if Chocolatey is found in the path.
.. note::
If chocolatey is already installed this will just re-run the
install script over the existing version. The ``version``
parameter is ignored.
source (str):
The location of the ``.nupkg`` file or ``.ps1`` file to run from an
alternate location. This can be one of the following types of URLs:
- salt://
- http(s)://
- ftp://
- file:// - A local file on the system
version (str):
The version of chocolatey to install. The latest version is
installed if this value is ``None``. Default is ``None``
Example:
.. code-block:: yaml
# Bootstrap the latest version of chocolatey
bootstrap_chocolatey:
chocolatey.bootstrapped
# Bootstrap the latest version of chocolatey
# If chocolatey is already present, re-run the install script
bootstrap_chocolatey:
chocolatey.bootstrapped:
- force: True
# Bootstrap Chocolatey version 1.4.0
bootstrap_chocolatey:
chocolatey.bootstrapped:
- version: 1.4.0
# Bootstrap Chocolatey from a local file
bootstrap_chocolatey:
chocolatey.bootstrapped:
- source: C:\\Temp\\chocolatey.nupkg
# Bootstrap Chocolatey from a file on the salt master
bootstrap_chocolatey:
chocolatey.bootstrapped:
- source: salt://Temp/chocolatey.nupkg
"""
ret = {"name": name, "result": True, "changes": {}, "comment": ""}
try:
old = __salt__["chocolatey.chocolatey_version"]()
except CommandExecutionError:
old = None
# Try to predict what will happen
if old:
if force:
ret["comment"] = (
f"Chocolatey {old} will be reinstalled\n"
'Use "choco upgrade chocolatey --version 2.1.0" to change the version'
)
else:
# You can't upgrade chocolatey using the install script, you have to use
# chocolatey itself
ret["comment"] = (
f"Chocolatey {old} is already installed.\n"
'Use "choco upgrade chocolatey --version 2.1.0" to change the version'
)
return ret
else:
if version is None:
ret["comment"] = "The latest version of Chocolatey will be installed"
else:
ret["comment"] = f"Chocolatey {version} will be installed"
if __opts__["test"]:
ret["result"] = None
return ret
__salt__["chocolatey.bootstrap"](force=force, source=source, version=version)
try:
new = __salt__["chocolatey.chocolatey_version"](refresh=True)
except CommandExecutionError:
new = None
if new is None:
ret["comment"] = f"Failed to install chocolatey {new}"
ret["result"] = False
else:
if salt.utils.versions.version_cmp(old, new) == 0:
ret["comment"] = f"Re-installed chocolatey {new}"
else:
ret["comment"] = f"Installed chocolatey {new}"
ret["changes"] = {"old": old, "new": new}
return ret
def unbootstrapped(name):
"""
.. versionadded:: 3008.0
Ensure chocolatey is removed from the system.
Args:
name (str):
The name of the state that uninstalls chocolatey. Required for all
states. This is ignored.
Example:
.. code-block:: yaml
# Uninstall chocolatey
uninstall_chocolatey:
chocolatey.unbootstrapped
"""
ret = {"name": name, "result": True, "changes": {}, "comment": ""}
try:
old = __salt__["chocolatey.chocolatey_version"]()
except CommandExecutionError:
old = None
if old is None:
ret["comment"] = "Chocolatey not found on this system"
return ret
ret["comment"] = f"Chocolatey {old} will be removed"
if __opts__["test"]:
ret["result"] = None
return ret
__salt__["chocolatey.unbootstrap"]()
try:
new = __salt__["chocolatey.chocolatey_version"](refresh=True)
except CommandExecutionError:
new = None
if new is None:
ret["comment"] = f"Uninstalled chocolatey {old}"
ret["changes"] = {"new": new, "old": old}
else:
ret["comment"] = f"Failed to uninstall chocolatey {old}\nFound version {new}"
ret["result"] = False
return ret

View file

@ -252,7 +252,7 @@ def get_function_argspec(func, is_class_method=None):
defaults = []
varargs = keywords = None
for param in sig.parameters.values():
if param.kind == param.POSITIONAL_OR_KEYWORD:
if param.kind in [param.POSITIONAL_OR_KEYWORD, param.KEYWORD_ONLY]:
args.append(param.name)
if param.default is not inspect._empty:
defaults.append(param.default)

View file

@ -1,47 +0,0 @@
import pytest
from salt.exceptions import CommandExecutionError
pytestmark = [
pytest.mark.destructive_test,
pytest.mark.skip_unless_on_windows,
pytest.mark.slow_test,
pytest.mark.windows_whitelisted,
]
@pytest.fixture(scope="module")
def chocolatey(modules):
return modules.chocolatey
@pytest.fixture()
def clean(chocolatey):
chocolatey.unbootstrap()
try:
chocolatey_version = chocolatey.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version is None
try:
yield
finally:
chocolatey.unbootstrap()
def test_bootstrap(chocolatey, clean):
chocolatey.bootstrap()
try:
chocolatey_version = chocolatey.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version is not None
def test_bootstrap_version(chocolatey, clean):
chocolatey.bootstrap(version="1.4.0")
try:
chocolatey_version = chocolatey.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version == "1.4.0"

View file

@ -1,104 +0,0 @@
import pytest
from salt.exceptions import CommandExecutionError
pytestmark = [
pytest.mark.destructive_test,
pytest.mark.skip_unless_on_windows,
pytest.mark.slow_test,
pytest.mark.windows_whitelisted,
]
@pytest.fixture(scope="module")
def chocolatey(states):
yield states.chocolatey
@pytest.fixture(scope="module")
def chocolatey_mod(modules):
yield modules.chocolatey
@pytest.fixture()
def clean(chocolatey_mod):
chocolatey_mod.unbootstrap()
try:
chocolatey_version = chocolatey_mod.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version is None
try:
yield
finally:
chocolatey_mod.unbootstrap()
@pytest.fixture()
def installed(chocolatey_mod):
chocolatey_mod.bootstrap(force=True)
try:
chocolatey_version = chocolatey_mod.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version is not None
try:
yield
finally:
chocolatey_mod.unbootstrap()
def test_bootstrapped(chocolatey, chocolatey_mod, clean):
ret = chocolatey.bootstrapped(name="junk name")
assert "Installed chocolatey" in ret.comment
assert ret.result is True
try:
chocolatey_version = chocolatey_mod.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version is not None
def test_bootstrapped_test_true(chocolatey, clean):
ret = chocolatey.bootstrapped(name="junk name", test=True)
assert ret.result is None
assert ret.comment == "The latest version of Chocolatey will be installed"
def test_bootstrapped_version(chocolatey, chocolatey_mod, clean):
ret = chocolatey.bootstrapped(name="junk_name", version="1.4.0")
assert ret.comment == "Installed chocolatey 1.4.0"
assert ret.result is True
try:
chocolatey_version = chocolatey_mod.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version == "1.4.0"
def test_bootstrapped_version_test_true(chocolatey, chocolatey_mod, clean):
ret = chocolatey.bootstrapped(name="junk_name", version="1.4.0", test=True)
assert ret.comment == "Chocolatey 1.4.0 will be installed"
def test_unbootstrapped_installed(chocolatey, chocolatey_mod, installed):
ret = chocolatey.unbootstrapped(name="junk_name")
assert "Uninstalled chocolatey" in ret.comment
assert ret.result is True
try:
chocolatey_version = chocolatey_mod.chocolatey_version(refresh=True)
except CommandExecutionError:
chocolatey_version = None
assert chocolatey_version is None
def test_unbootstrapped_installed_test_true(chocolatey, chocolatey_mod, installed):
ret = chocolatey.unbootstrapped(name="junk_name", test=True)
assert "will be removed" in ret.comment
assert ret.result is None
def test_unbootstrapped_clean(chocolatey, chocolatey_mod, clean):
ret = chocolatey.unbootstrapped(name="junk_name")
assert ret.comment == "Chocolatey not found on this system"
assert ret.result is True

View file

@ -1,5 +1,5 @@
"""
Functional tests for chocolatey state with Chocolatey < 2.0
Functional tests for chocolatey state
"""
import os

View file

@ -1,5 +1,5 @@
"""
Functional tests for chocolatey state with Chocolatey 2.0+
Functional tests for chocolatey state
"""
import os

View file

@ -7,10 +7,14 @@ import os
import pytest
import salt.modules.chocolatey as chocolatey
import salt.utils
import salt.utils.platform
from tests.support.mock import MagicMock, patch
pytestmark = [
pytest.mark.skip_unless_on_windows,
pytest.mark.skipif(
not salt.utils.platform.is_windows(), reason="Not a Windows system"
)
]
@ -64,7 +68,7 @@ def test__clear_context(choco_path):
}
with patch.dict(chocolatey.__context__, context):
chocolatey._clear_context()
# Did it clear all chocolatey items from __context__?
# Did it clear all chocolatey items from __context__P?
assert chocolatey.__context__ == {}
@ -329,25 +333,3 @@ def test_list_windowsfeatures_post_2_0_0():
chocolatey.list_windowsfeatures()
expected_call = [choco_path, "search", "--source", "windowsfeatures"]
mock_run.assert_called_with(expected_call, python_shell=False)
def test_chocolatey_version():
context = {
"chocolatey._version": "0.9.9",
}
with patch.dict(chocolatey.__context__, context):
result = chocolatey.chocolatey_version()
expected = "0.9.9"
assert result == expected
def test_chocolatey_version_refresh():
context = {"chocolatey._version": "0.9.9"}
mock_find = MagicMock(return_value="some_path")
mock_run = MagicMock(return_value="2.2.0")
with patch.dict(chocolatey.__context__, context), patch.object(
chocolatey, "_find_chocolatey", mock_find
), patch.dict(chocolatey.__salt__, {"cmd.run": mock_run}):
result = chocolatey.chocolatey_version(refresh=True)
expected = "2.2.0"
assert result == expected

View file

@ -43,7 +43,10 @@ def test_obfuscate_with_kwargs(pillar_value):
) as pillar_items_mock:
ret = pillarmod.obfuscate(saltenv="saltenv")
# ensure the kwargs are passed along to pillar.items
assert call(saltenv="saltenv") in pillar_items_mock.mock_calls
assert (
call(pillar=None, pillar_enc=None, pillarenv=None, saltenv="saltenv")
in pillar_items_mock.mock_calls
)
assert ret == dict(a="<int>", b="<str>")
@ -180,3 +183,9 @@ def test_pillar_keys():
test_key = "7:11"
res = pillarmod.keys(test_key)
assert res == ["12"]
def test_ls_pass_kwargs(pillar_value):
with patch("salt.modules.pillar.items", MagicMock(return_value=pillar_value)):
ls = sorted(pillarmod.ls(pillarenv="base"))
assert ls == ["a", "b"]