Accepting request 1085018 from home:agraul:branches:systemsmanagement:saltstack
remove leftover patches OBS-URL: https://build.opensuse.org/request/show/1085018 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=211
This commit is contained in:
parent
2686359b2c
commit
ab9c251387
@ -1,224 +0,0 @@
|
||||
From 1434a128559df8183c032af722dc3d187bda148a Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
|
||||
Date: Thu, 1 Sep 2022 14:46:24 +0300
|
||||
Subject: [PATCH] Add Amazon EC2 detection for virtual grains
|
||||
(bsc#1195624)
|
||||
|
||||
* Add ignore_retcode to quiet run functions
|
||||
|
||||
* Implement Amazon EC2 detection for virtual grains
|
||||
|
||||
* Add test for virtual grain detection of Amazon EC2
|
||||
|
||||
* Also detect the product of Amazon EC2 instance
|
||||
|
||||
* Add changelog entry
|
||||
---
|
||||
changelog/62539.added | 1 +
|
||||
salt/grains/core.py | 18 ++++
|
||||
salt/modules/cmdmod.py | 4 +
|
||||
tests/pytests/unit/grains/test_core.py | 117 +++++++++++++++++++++++++
|
||||
4 files changed, 140 insertions(+)
|
||||
create mode 100644 changelog/62539.added
|
||||
|
||||
diff --git a/changelog/62539.added b/changelog/62539.added
|
||||
new file mode 100644
|
||||
index 0000000000..5f402d61c2
|
||||
--- /dev/null
|
||||
+++ b/changelog/62539.added
|
||||
@@ -0,0 +1 @@
|
||||
+Implementation of Amazon EC2 instance detection and setting `virtual_subtype` grain accordingly including the product if possible to identify.
|
||||
diff --git a/salt/grains/core.py b/salt/grains/core.py
|
||||
index 23d8b8ea42..047c33ffd3 100644
|
||||
--- a/salt/grains/core.py
|
||||
+++ b/salt/grains/core.py
|
||||
@@ -1171,6 +1171,24 @@ def _virtual(osdata):
|
||||
if grains.get("virtual_subtype") and grains["virtual"] == "physical":
|
||||
grains["virtual"] = "virtual"
|
||||
|
||||
+ # Try to detect if the instance is running on Amazon EC2
|
||||
+ if grains["virtual"] in ("qemu", "kvm", "xen"):
|
||||
+ dmidecode = salt.utils.path.which("dmidecode")
|
||||
+ if dmidecode:
|
||||
+ ret = __salt__["cmd.run_all"](
|
||||
+ [dmidecode, "-t", "system"], ignore_retcode=True
|
||||
+ )
|
||||
+ output = ret["stdout"]
|
||||
+ if "Manufacturer: Amazon EC2" in output:
|
||||
+ grains["virtual_subtype"] = "Amazon EC2"
|
||||
+ product = re.match(
|
||||
+ r".*Product Name: ([^\r\n]*).*", output, flags=re.DOTALL
|
||||
+ )
|
||||
+ if product:
|
||||
+ grains["virtual_subtype"] = "Amazon EC2 ({})".format(product[1])
|
||||
+ elif re.match(r".*Version: [^\r\n]+\.amazon.*", output, flags=re.DOTALL):
|
||||
+ grains["virtual_subtype"] = "Amazon EC2"
|
||||
+
|
||||
for command in failed_commands:
|
||||
log.info(
|
||||
"Although '%s' was found in path, the current user "
|
||||
diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py
|
||||
index a26220718a..07b6e100d2 100644
|
||||
--- a/salt/modules/cmdmod.py
|
||||
+++ b/salt/modules/cmdmod.py
|
||||
@@ -932,6 +932,7 @@ def _run_quiet(
|
||||
success_retcodes=None,
|
||||
success_stdout=None,
|
||||
success_stderr=None,
|
||||
+ ignore_retcode=None,
|
||||
):
|
||||
"""
|
||||
Helper for running commands quietly for minion startup
|
||||
@@ -958,6 +959,7 @@ def _run_quiet(
|
||||
success_retcodes=success_retcodes,
|
||||
success_stdout=success_stdout,
|
||||
success_stderr=success_stderr,
|
||||
+ ignore_retcode=ignore_retcode,
|
||||
)["stdout"]
|
||||
|
||||
|
||||
@@ -980,6 +982,7 @@ def _run_all_quiet(
|
||||
success_retcodes=None,
|
||||
success_stdout=None,
|
||||
success_stderr=None,
|
||||
+ ignore_retcode=None,
|
||||
):
|
||||
|
||||
"""
|
||||
@@ -1012,6 +1015,7 @@ def _run_all_quiet(
|
||||
success_retcodes=success_retcodes,
|
||||
success_stdout=success_stdout,
|
||||
success_stderr=success_stderr,
|
||||
+ ignore_retcode=ignore_retcode,
|
||||
)
|
||||
|
||||
|
||||
diff --git a/tests/pytests/unit/grains/test_core.py b/tests/pytests/unit/grains/test_core.py
|
||||
index 5c43dbdb09..7c4ea1f17f 100644
|
||||
--- a/tests/pytests/unit/grains/test_core.py
|
||||
+++ b/tests/pytests/unit/grains/test_core.py
|
||||
@@ -2823,3 +2823,120 @@ def test_get_server_id():
|
||||
|
||||
with patch.dict(core.__opts__, {"id": "otherid"}):
|
||||
assert core.get_server_id() != expected
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skip_unless_on_linux
|
||||
+def test_virtual_set_virtual_ec2():
|
||||
+ osdata = {}
|
||||
+
|
||||
+ (
|
||||
+ osdata["kernel"],
|
||||
+ osdata["nodename"],
|
||||
+ osdata["kernelrelease"],
|
||||
+ osdata["kernelversion"],
|
||||
+ osdata["cpuarch"],
|
||||
+ _,
|
||||
+ ) = platform.uname()
|
||||
+
|
||||
+ which_mock = MagicMock(
|
||||
+ side_effect=[
|
||||
+ # Check with virt-what
|
||||
+ "/usr/sbin/virt-what",
|
||||
+ "/usr/sbin/virt-what",
|
||||
+ None,
|
||||
+ "/usr/sbin/dmidecode",
|
||||
+ # Check with systemd-detect-virt
|
||||
+ None,
|
||||
+ "/usr/bin/systemd-detect-virt",
|
||||
+ None,
|
||||
+ "/usr/sbin/dmidecode",
|
||||
+ # Check with systemd-detect-virt when no dmidecode available
|
||||
+ None,
|
||||
+ "/usr/bin/systemd-detect-virt",
|
||||
+ None,
|
||||
+ None,
|
||||
+ ]
|
||||
+ )
|
||||
+ cmd_run_all_mock = MagicMock(
|
||||
+ side_effect=[
|
||||
+ # Check with virt-what
|
||||
+ {"retcode": 0, "stderr": "", "stdout": "xen"},
|
||||
+ {
|
||||
+ "retcode": 0,
|
||||
+ "stderr": "",
|
||||
+ "stdout": "\n".join(
|
||||
+ [
|
||||
+ "dmidecode 3.2",
|
||||
+ "Getting SMBIOS data from sysfs.",
|
||||
+ "SMBIOS 2.7 present.",
|
||||
+ "",
|
||||
+ "Handle 0x0100, DMI type 1, 27 bytes",
|
||||
+ "System Information",
|
||||
+ " Manufacturer: Xen",
|
||||
+ " Product Name: HVM domU",
|
||||
+ " Version: 4.11.amazon",
|
||||
+ " Serial Number: 12345678-abcd-4321-dcba-0123456789ab",
|
||||
+ " UUID: 01234567-dcba-1234-abcd-abcdef012345",
|
||||
+ " Wake-up Type: Power Switch",
|
||||
+ " SKU Number: Not Specified",
|
||||
+ " Family: Not Specified",
|
||||
+ "",
|
||||
+ "Handle 0x2000, DMI type 32, 11 bytes",
|
||||
+ "System Boot Information",
|
||||
+ " Status: No errors detected",
|
||||
+ ]
|
||||
+ ),
|
||||
+ },
|
||||
+ # Check with systemd-detect-virt
|
||||
+ {"retcode": 0, "stderr": "", "stdout": "kvm"},
|
||||
+ {
|
||||
+ "retcode": 0,
|
||||
+ "stderr": "",
|
||||
+ "stdout": "\n".join(
|
||||
+ [
|
||||
+ "dmidecode 3.2",
|
||||
+ "Getting SMBIOS data from sysfs.",
|
||||
+ "SMBIOS 2.7 present.",
|
||||
+ "",
|
||||
+ "Handle 0x0001, DMI type 1, 27 bytes",
|
||||
+ "System Information",
|
||||
+ " Manufacturer: Amazon EC2",
|
||||
+ " Product Name: m5.large",
|
||||
+ " Version: Not Specified",
|
||||
+ " Serial Number: 01234567-dcba-1234-abcd-abcdef012345",
|
||||
+ " UUID: 12345678-abcd-4321-dcba-0123456789ab",
|
||||
+ " Wake-up Type: Power Switch",
|
||||
+ " SKU Number: Not Specified",
|
||||
+ " Family: Not Specified",
|
||||
+ ]
|
||||
+ ),
|
||||
+ },
|
||||
+ # Check with systemd-detect-virt when no dmidecode available
|
||||
+ {"retcode": 0, "stderr": "", "stdout": "kvm"},
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ with patch("salt.utils.path.which", which_mock), patch.dict(
|
||||
+ core.__salt__,
|
||||
+ {
|
||||
+ "cmd.run": salt.modules.cmdmod.run,
|
||||
+ "cmd.run_all": cmd_run_all_mock,
|
||||
+ "cmd.retcode": salt.modules.cmdmod.retcode,
|
||||
+ "smbios.get": salt.modules.smbios.get,
|
||||
+ },
|
||||
+ ):
|
||||
+
|
||||
+ virtual_grains = core._virtual(osdata.copy())
|
||||
+
|
||||
+ assert virtual_grains["virtual"] == "xen"
|
||||
+ assert virtual_grains["virtual_subtype"] == "Amazon EC2"
|
||||
+
|
||||
+ virtual_grains = core._virtual(osdata.copy())
|
||||
+
|
||||
+ assert virtual_grains["virtual"] == "kvm"
|
||||
+ assert virtual_grains["virtual_subtype"] == "Amazon EC2 (m5.large)"
|
||||
+
|
||||
+ virtual_grains = core._virtual(osdata.copy())
|
||||
+
|
||||
+ assert virtual_grains["virtual"] == "kvm"
|
||||
+ assert "virtual_subtype" not in virtual_grains
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,124 +0,0 @@
|
||||
From d1e9af256fa67cd792ce11e6e9c1e24a1fe2054f Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
|
||||
Date: Fri, 28 Oct 2022 13:19:46 +0300
|
||||
Subject: [PATCH] Align Amazon EC2 (Nitro) grains with upstream PR
|
||||
(bsc#1203685)
|
||||
|
||||
* Set virtual to Nitro for Amazon EC2 kvm instances
|
||||
|
||||
* Add few mocks to prevent false failing
|
||||
|
||||
possible in some specific environments
|
||||
|
||||
* Add one more possible test case returning Nitro
|
||||
---
|
||||
salt/grains/core.py | 8 +++++++-
|
||||
tests/pytests/unit/grains/test_core.py | 27 +++++++++++++++++++++++++-
|
||||
2 files changed, 33 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/salt/grains/core.py b/salt/grains/core.py
|
||||
index 76f3767ddf..f359c07432 100644
|
||||
--- a/salt/grains/core.py
|
||||
+++ b/salt/grains/core.py
|
||||
@@ -860,6 +860,10 @@ def _virtual(osdata):
|
||||
grains["virtual"] = "container"
|
||||
grains["virtual_subtype"] = "LXC"
|
||||
break
|
||||
+ elif "amazon" in output:
|
||||
+ grains["virtual"] = "Nitro"
|
||||
+ grains["virtual_subtype"] = "Amazon EC2"
|
||||
+ break
|
||||
elif command == "virt-what":
|
||||
for line in output.splitlines():
|
||||
if line in ("kvm", "qemu", "uml", "xen"):
|
||||
@@ -1174,7 +1178,7 @@ def _virtual(osdata):
|
||||
grains["virtual"] = "virtual"
|
||||
|
||||
# Try to detect if the instance is running on Amazon EC2
|
||||
- if grains["virtual"] in ("qemu", "kvm", "xen"):
|
||||
+ if grains["virtual"] in ("qemu", "kvm", "xen", "amazon"):
|
||||
dmidecode = salt.utils.path.which("dmidecode")
|
||||
if dmidecode:
|
||||
ret = __salt__["cmd.run_all"](
|
||||
@@ -1182,6 +1186,8 @@ def _virtual(osdata):
|
||||
)
|
||||
output = ret["stdout"]
|
||||
if "Manufacturer: Amazon EC2" in output:
|
||||
+ if grains["virtual"] != "xen":
|
||||
+ grains["virtual"] = "Nitro"
|
||||
grains["virtual_subtype"] = "Amazon EC2"
|
||||
product = re.match(
|
||||
r".*Product Name: ([^\r\n]*).*", output, flags=re.DOTALL
|
||||
diff --git a/tests/pytests/unit/grains/test_core.py b/tests/pytests/unit/grains/test_core.py
|
||||
index c06cdb2db0..6f3bef69f2 100644
|
||||
--- a/tests/pytests/unit/grains/test_core.py
|
||||
+++ b/tests/pytests/unit/grains/test_core.py
|
||||
@@ -2888,6 +2888,11 @@ def test_virtual_set_virtual_ec2():
|
||||
"/usr/bin/systemd-detect-virt",
|
||||
None,
|
||||
None,
|
||||
+ # Check with systemd-detect-virt returning amazon and no dmidecode available
|
||||
+ None,
|
||||
+ "/usr/bin/systemd-detect-virt",
|
||||
+ None,
|
||||
+ None,
|
||||
]
|
||||
)
|
||||
cmd_run_all_mock = MagicMock(
|
||||
@@ -2946,9 +2951,22 @@ def test_virtual_set_virtual_ec2():
|
||||
},
|
||||
# Check with systemd-detect-virt when no dmidecode available
|
||||
{"retcode": 0, "stderr": "", "stdout": "kvm"},
|
||||
+ # Check with systemd-detect-virt returning amazon and no dmidecode available
|
||||
+ {"retcode": 0, "stderr": "", "stdout": "amazon"},
|
||||
]
|
||||
)
|
||||
|
||||
+ def _mock_is_file(filename):
|
||||
+ if filename in (
|
||||
+ "/proc/1/cgroup",
|
||||
+ "/proc/cpuinfo",
|
||||
+ "/sys/devices/virtual/dmi/id/product_name",
|
||||
+ "/proc/xen/xsd_kva",
|
||||
+ "/proc/xen/capabilities",
|
||||
+ ):
|
||||
+ return False
|
||||
+ return True
|
||||
+
|
||||
with patch("salt.utils.path.which", which_mock), patch.dict(
|
||||
core.__salt__,
|
||||
{
|
||||
@@ -2957,6 +2975,8 @@ def test_virtual_set_virtual_ec2():
|
||||
"cmd.retcode": salt.modules.cmdmod.retcode,
|
||||
"smbios.get": salt.modules.smbios.get,
|
||||
},
|
||||
+ ), patch("os.path.isfile", _mock_is_file), patch(
|
||||
+ "os.path.isdir", return_value=False
|
||||
):
|
||||
|
||||
virtual_grains = core._virtual(osdata.copy())
|
||||
@@ -2966,7 +2986,7 @@ def test_virtual_set_virtual_ec2():
|
||||
|
||||
virtual_grains = core._virtual(osdata.copy())
|
||||
|
||||
- assert virtual_grains["virtual"] == "kvm"
|
||||
+ assert virtual_grains["virtual"] == "Nitro"
|
||||
assert virtual_grains["virtual_subtype"] == "Amazon EC2 (m5.large)"
|
||||
|
||||
virtual_grains = core._virtual(osdata.copy())
|
||||
@@ -2974,6 +2994,11 @@ def test_virtual_set_virtual_ec2():
|
||||
assert virtual_grains["virtual"] == "kvm"
|
||||
assert "virtual_subtype" not in virtual_grains
|
||||
|
||||
+ virtual_grains = core._virtual(osdata.copy())
|
||||
+
|
||||
+ assert virtual_grains["virtual"] == "Nitro"
|
||||
+ assert virtual_grains["virtual_subtype"] == "Amazon EC2"
|
||||
+
|
||||
|
||||
@pytest.mark.skip_on_windows
|
||||
def test_linux_proc_files_with_non_utf8_chars():
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,151 +0,0 @@
|
||||
From 6dc653b0cf8e6e043e13bea7009ded604ceb7b71 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
||||
<psuarezhernandez@suse.com>
|
||||
Date: Thu, 12 Jan 2023 15:43:56 +0000
|
||||
Subject: [PATCH] Allow entrypoint compatibility for
|
||||
importlib-metadata>=5.0.0 (#572)
|
||||
|
||||
add tests and make sure the compat code is in an else :)
|
||||
|
||||
changelog
|
||||
|
||||
switch to try/except
|
||||
|
||||
Co-authored-by: MKLeb <calebb@vmware.com>
|
||||
---
|
||||
changelog/62854.fixed | 1 +
|
||||
salt/utils/entrypoints.py | 15 +++--
|
||||
.../pytests/functional/loader/test_loader.py | 67 +++++++++++++++++--
|
||||
3 files changed, 74 insertions(+), 9 deletions(-)
|
||||
create mode 100644 changelog/62854.fixed
|
||||
|
||||
diff --git a/changelog/62854.fixed b/changelog/62854.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..13e6df4fe3
|
||||
--- /dev/null
|
||||
+++ b/changelog/62854.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Use select instead of iterating over entrypoints as a dictionary for importlib_metadata>=5.0.0
|
||||
diff --git a/salt/utils/entrypoints.py b/salt/utils/entrypoints.py
|
||||
index 3effa0b494..9452878ade 100644
|
||||
--- a/salt/utils/entrypoints.py
|
||||
+++ b/salt/utils/entrypoints.py
|
||||
@@ -38,13 +38,20 @@ def iter_entry_points(group, name=None):
|
||||
entry_points_listing = []
|
||||
entry_points = importlib_metadata.entry_points()
|
||||
|
||||
- for entry_point_group, entry_points_list in entry_points.items():
|
||||
- if entry_point_group != group:
|
||||
- continue
|
||||
- for entry_point in entry_points_list:
|
||||
+ try:
|
||||
+ for entry_point in entry_points.select(group=group):
|
||||
if name is not None and entry_point.name != name:
|
||||
continue
|
||||
entry_points_listing.append(entry_point)
|
||||
+ except AttributeError:
|
||||
+ # importlib-metadata<5.0.0
|
||||
+ for entry_point_group, entry_points_list in entry_points.items():
|
||||
+ if entry_point_group != group:
|
||||
+ continue
|
||||
+ for entry_point in entry_points_list:
|
||||
+ if name is not None and entry_point.name != name:
|
||||
+ continue
|
||||
+ entry_points_listing.append(entry_point)
|
||||
|
||||
return entry_points_listing
|
||||
|
||||
diff --git a/tests/pytests/functional/loader/test_loader.py b/tests/pytests/functional/loader/test_loader.py
|
||||
index 6dfd97b0e6..a13d90d5eb 100644
|
||||
--- a/tests/pytests/functional/loader/test_loader.py
|
||||
+++ b/tests/pytests/functional/loader/test_loader.py
|
||||
@@ -1,5 +1,4 @@
|
||||
import json
|
||||
-import sys
|
||||
|
||||
import pytest
|
||||
import salt.utils.versions
|
||||
@@ -143,10 +142,6 @@ def test_utils_loader_does_not_load_extensions(
|
||||
assert "foobar.echo" not in loader_functions
|
||||
|
||||
|
||||
-@pytest.mark.skipif(
|
||||
- sys.version_info < (3, 6),
|
||||
- reason="importlib-metadata>=3.3.0 does not exist for Py3.5",
|
||||
-)
|
||||
def test_extension_discovery_without_reload_with_importlib_metadata_installed(
|
||||
venv, salt_extension, salt_minion_factory
|
||||
):
|
||||
@@ -209,6 +204,68 @@ def test_extension_discovery_without_reload_with_importlib_metadata_installed(
|
||||
assert "foobar.echo2" in loader_functions
|
||||
|
||||
|
||||
+def test_extension_discovery_without_reload_with_importlib_metadata_5_installed(
|
||||
+ venv, salt_extension, salt_minion_factory
|
||||
+):
|
||||
+ # Install our extension into the virtualenv
|
||||
+ installed_packages = venv.get_installed_packages()
|
||||
+ assert salt_extension.name not in installed_packages
|
||||
+ venv.install("importlib-metadata>=3.3.0")
|
||||
+ code = """
|
||||
+ import sys
|
||||
+ import json
|
||||
+ import subprocess
|
||||
+ import salt._logging
|
||||
+ import salt.loader
|
||||
+
|
||||
+ extension_path = "{}"
|
||||
+
|
||||
+ minion_config = json.loads(sys.stdin.read())
|
||||
+ salt._logging.set_logging_options_dict(minion_config)
|
||||
+ salt._logging.setup_logging()
|
||||
+ loader = salt.loader.minion_mods(minion_config)
|
||||
+
|
||||
+ if "foobar.echo1" in loader:
|
||||
+ sys.exit(1)
|
||||
+
|
||||
+ # Install the extension
|
||||
+ proc = subprocess.run(
|
||||
+ [sys.executable, "-m", "pip", "install", extension_path],
|
||||
+ check=False,
|
||||
+ shell=False,
|
||||
+ stdout=subprocess.PIPE,
|
||||
+ )
|
||||
+ if proc.returncode != 0:
|
||||
+ sys.exit(2)
|
||||
+
|
||||
+ loader = salt.loader.minion_mods(minion_config)
|
||||
+ if "foobar.echo1" not in loader:
|
||||
+ sys.exit(3)
|
||||
+
|
||||
+ print(json.dumps(list(loader)))
|
||||
+ """.format(
|
||||
+ salt_extension.srcdir
|
||||
+ )
|
||||
+ ret = venv.run_code(
|
||||
+ code, input=json.dumps(salt_minion_factory.config.copy()), check=False
|
||||
+ )
|
||||
+ # Exitcode 1 - Extension was already installed
|
||||
+ # Exitcode 2 - Failed to install the extension
|
||||
+ # Exitcode 3 - Extension was not found within the same python process after being installed
|
||||
+ assert ret.returncode == 0
|
||||
+ installed_packages = venv.get_installed_packages()
|
||||
+ assert salt_extension.name in installed_packages
|
||||
+
|
||||
+ loader_functions = json.loads(ret.stdout)
|
||||
+
|
||||
+ # A non existing module should not appear in the loader
|
||||
+ assert "monty.python" not in loader_functions
|
||||
+
|
||||
+ # But our extension's modules should appear on the loader
|
||||
+ assert "foobar.echo1" in loader_functions
|
||||
+ assert "foobar.echo2" in loader_functions
|
||||
+
|
||||
+
|
||||
def test_extension_discovery_without_reload_with_bundled_importlib_metadata(
|
||||
venv, salt_extension, salt_minion_factory
|
||||
):
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,51 +0,0 @@
|
||||
From 5ed2295489fc13e48b981c323c846bde927cb800 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Graul <agraul@suse.com>
|
||||
Date: Fri, 21 Oct 2022 14:39:21 +0200
|
||||
Subject: [PATCH] Clarify pkg.installed pkg_verify documentation
|
||||
|
||||
There have been misunderstandings what the pkg_verify parameter does and
|
||||
bug reports that it does not work, based on the wrong assumption that
|
||||
this parameter changes the installation of new packages. The docstring
|
||||
also stated that it was only provided by `yum`, but `zypper` also
|
||||
provides this feature (actually it is `rpm` itself in both cases that
|
||||
does the verification check)
|
||||
|
||||
Related issue: https://github.com/saltstack/salt/issues/44878
|
||||
|
||||
(cherry picked from commit 2ed5f3c29d3b4313d904b7c081e5a29bf5e309c7)
|
||||
---
|
||||
salt/states/pkg.py | 17 +++++++++--------
|
||||
1 file changed, 9 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/salt/states/pkg.py b/salt/states/pkg.py
|
||||
index cda966a1e8..13532521d5 100644
|
||||
--- a/salt/states/pkg.py
|
||||
+++ b/salt/states/pkg.py
|
||||
@@ -1277,14 +1277,15 @@ def installed(
|
||||
|
||||
.. versionadded:: 2014.7.0
|
||||
|
||||
- For requested packages that are already installed and would not be
|
||||
- targeted for upgrade or downgrade, use pkg.verify to determine if any
|
||||
- of the files installed by the package have been altered. If files have
|
||||
- been altered, the reinstall option of pkg.install is used to force a
|
||||
- reinstall. Types to ignore can be passed to pkg.verify. Additionally,
|
||||
- ``verify_options`` can be used to modify further the behavior of
|
||||
- pkg.verify. See examples below. Currently, this option is supported
|
||||
- for the following pkg providers: :mod:`yumpkg <salt.modules.yumpkg>`.
|
||||
+ Use pkg.verify to check if already installed packages require
|
||||
+ reinstallion. Requested packages that are already installed and not
|
||||
+ targeted for up- or downgrade are verified with pkg.verify to determine
|
||||
+ if any file installed by the package have been modified or if package
|
||||
+ dependencies are not fulfilled. ``ignore_types`` and ``verify_options``
|
||||
+ can be passed to pkg.verify. See examples below. Currently, this option
|
||||
+ is supported for the following pkg providers:
|
||||
+ :mod:`yum <salt.modules.yumpkg>`,
|
||||
+ :mod:`zypperpkg <salt.modules.zypperpkg>`.
|
||||
|
||||
Examples:
|
||||
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
From dd147ab110e71ea0f1091923c9230ade01f226d4 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
|
||||
Date: Fri, 28 Oct 2022 13:19:23 +0300
|
||||
Subject: [PATCH] Detect module.run syntax
|
||||
|
||||
* Detect module run syntax version
|
||||
|
||||
* Update module.run docs and add changelog
|
||||
|
||||
* Add test for module.run without any args
|
||||
|
||||
Co-authored-by: Daniel A. Wozniak <dwozniak@saltstack.com>
|
||||
---
|
||||
changelog/58763.fixed | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
create mode 100644 changelog/58763.fixed
|
||||
|
||||
diff --git a/changelog/58763.fixed b/changelog/58763.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..53ee8304c0
|
||||
--- /dev/null
|
||||
+++ b/changelog/58763.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Detect new and legacy styles of calling module.run and support them both.
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,50 +0,0 @@
|
||||
From e328d2029c93153c519e10e9596c635f6f3febcf Mon Sep 17 00:00:00 2001
|
||||
From: Petr Pavlu <31453820+petrpavlu@users.noreply.github.com>
|
||||
Date: Fri, 8 Jul 2022 10:11:52 +0200
|
||||
Subject: [PATCH] Fix salt.states.file.managed() for follow_symlinks=True
|
||||
and test=True (bsc#1199372) (#535)
|
||||
|
||||
When managing file /etc/test as follows:
|
||||
> file /etc/test:
|
||||
> file.managed:
|
||||
> - name: /etc/test
|
||||
> - source: salt://config/test
|
||||
> - mode: 644
|
||||
> - follow_symlinks: True
|
||||
|
||||
and with /etc/test being a symlink to a different file, an invocation of
|
||||
"salt-call '*' state.apply test=True" can report that the file should be
|
||||
updated even when a subsequent run of the same command without the test
|
||||
parameter makes no changes.
|
||||
|
||||
The problem is that the test code path doesn't take correctly into
|
||||
account the follow_symlinks=True setting and ends up comparing
|
||||
permissions of the symlink instead of its target file.
|
||||
|
||||
The patch addresses the problem by extending functions
|
||||
salt.modules.file.check_managed(), check_managed_changes() and
|
||||
check_file_meta() to have the follow_symlinks parameter which gets
|
||||
propagated to the salt.modules.file.stats() call and by updating
|
||||
salt.states.file.managed() to forward the same parameter to
|
||||
salt.modules.file.check_managed_changes().
|
||||
|
||||
Fixes #62066.
|
||||
|
||||
[Cherry-picked from upstream commit
|
||||
95bfbe31a2dc54723af3f1783d40de152760fe1a.]
|
||||
---
|
||||
changelog/62066.fixed | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
create mode 100644 changelog/62066.fixed
|
||||
|
||||
diff --git a/changelog/62066.fixed b/changelog/62066.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..68216a03c1
|
||||
--- /dev/null
|
||||
+++ b/changelog/62066.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Fixed salt.states.file.managed() for follow_symlinks=True and test=True
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,183 +0,0 @@
|
||||
From 58317cda7a347581b495ab7fd71ce75f0740d8d6 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
|
||||
Date: Thu, 1 Sep 2022 14:44:26 +0300
|
||||
Subject: [PATCH] Fix state.apply in test mode with file state module on
|
||||
user/group checking (bsc#1202167)
|
||||
|
||||
* Do not fail on checking user/group in test mode
|
||||
|
||||
* fixes saltstack/salt#61846 reporting of errors in test mode
|
||||
|
||||
Co-authored-by: nicholasmhughes <nicholasmhughes@gmail.com>
|
||||
|
||||
* Add tests for _check_user usage
|
||||
|
||||
Co-authored-by: nicholasmhughes <nicholasmhughes@gmail.com>
|
||||
---
|
||||
changelog/61846.fixed | 1 +
|
||||
salt/states/file.py | 5 +++
|
||||
tests/pytests/unit/states/file/test_copy.py | 35 ++++++++++++++++
|
||||
.../unit/states/file/test_filestate.py | 42 +++++++++++++++++++
|
||||
.../pytests/unit/states/file/test_managed.py | 31 ++++++++++++++
|
||||
5 files changed, 114 insertions(+)
|
||||
create mode 100644 changelog/61846.fixed
|
||||
|
||||
diff --git a/changelog/61846.fixed b/changelog/61846.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..c4024efe9f
|
||||
--- /dev/null
|
||||
+++ b/changelog/61846.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Fix the reporting of errors for file.directory in test mode
|
||||
diff --git a/salt/states/file.py b/salt/states/file.py
|
||||
index 1083bb46d6..5cb58f5454 100644
|
||||
--- a/salt/states/file.py
|
||||
+++ b/salt/states/file.py
|
||||
@@ -379,6 +379,11 @@ def _check_user(user, group):
|
||||
gid = __salt__["file.group_to_gid"](group)
|
||||
if gid == "":
|
||||
err += "Group {} is not available".format(group)
|
||||
+ if err and __opts__["test"]:
|
||||
+ # Write the warning with error message, but prevent failing,
|
||||
+ # in case of applying the state in test mode.
|
||||
+ log.warning(err)
|
||||
+ return ""
|
||||
return err
|
||||
|
||||
|
||||
diff --git a/tests/pytests/unit/states/file/test_copy.py b/tests/pytests/unit/states/file/test_copy.py
|
||||
index ce7161f02d..a11adf5ae0 100644
|
||||
--- a/tests/pytests/unit/states/file/test_copy.py
|
||||
+++ b/tests/pytests/unit/states/file/test_copy.py
|
||||
@@ -205,3 +205,38 @@ def test_copy(tmp_path):
|
||||
)
|
||||
res = filestate.copy_(name, source, group=group, preserve=False)
|
||||
assert res == ret
|
||||
+
|
||||
+
|
||||
+def test_copy_test_mode_user_group_not_present():
|
||||
+ """
|
||||
+ Test file copy in test mode with no user or group existing
|
||||
+ """
|
||||
+ source = "/tmp/src_copy_no_user_group_test_mode"
|
||||
+ filename = "/tmp/copy_no_user_group_test_mode"
|
||||
+ with patch.dict(
|
||||
+ filestate.__salt__,
|
||||
+ {
|
||||
+ "file.group_to_gid": MagicMock(side_effect=["1234", "", ""]),
|
||||
+ "file.user_to_uid": MagicMock(side_effect=["", "4321", ""]),
|
||||
+ "file.get_mode": MagicMock(return_value="0644"),
|
||||
+ },
|
||||
+ ), patch.dict(filestate.__opts__, {"test": True}), patch.object(
|
||||
+ os.path, "exists", return_value=True
|
||||
+ ):
|
||||
+ ret = filestate.copy_(
|
||||
+ source, filename, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
+
|
||||
+ ret = filestate.copy_(
|
||||
+ source, filename, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
+
|
||||
+ ret = filestate.copy_(
|
||||
+ source, filename, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
diff --git a/tests/pytests/unit/states/file/test_filestate.py b/tests/pytests/unit/states/file/test_filestate.py
|
||||
index 2f9f369fb2..c373cb3449 100644
|
||||
--- a/tests/pytests/unit/states/file/test_filestate.py
|
||||
+++ b/tests/pytests/unit/states/file/test_filestate.py
|
||||
@@ -577,3 +577,45 @@ def test_mod_run_check_cmd():
|
||||
assert filestate.mod_run_check_cmd(cmd, filename) == ret
|
||||
|
||||
assert filestate.mod_run_check_cmd(cmd, filename)
|
||||
+
|
||||
+
|
||||
+def test_recurse_test_mode_user_group_not_present():
|
||||
+ """
|
||||
+ Test file recurse in test mode with no user or group existing
|
||||
+ """
|
||||
+ filename = "/tmp/recurse_no_user_group_test_mode"
|
||||
+ source = "salt://tmp/src_recurse_no_user_group_test_mode"
|
||||
+ mock_l = MagicMock(return_value=[])
|
||||
+ mock_emt = MagicMock(return_value=["tmp/src_recurse_no_user_group_test_mode"])
|
||||
+ with patch.dict(
|
||||
+ filestate.__salt__,
|
||||
+ {
|
||||
+ "file.group_to_gid": MagicMock(side_effect=["1234", "", ""]),
|
||||
+ "file.user_to_uid": MagicMock(side_effect=["", "4321", ""]),
|
||||
+ "file.get_mode": MagicMock(return_value="0644"),
|
||||
+ "file.source_list": MagicMock(return_value=[source, ""]),
|
||||
+ "cp.list_master_dirs": mock_emt,
|
||||
+ "cp.list_master": mock_l,
|
||||
+ },
|
||||
+ ), patch.dict(filestate.__opts__, {"test": True}), patch.object(
|
||||
+ os.path, "exists", return_value=True
|
||||
+ ), patch.object(
|
||||
+ os.path, "isdir", return_value=True
|
||||
+ ):
|
||||
+ ret = filestate.recurse(
|
||||
+ filename, source, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
+
|
||||
+ ret = filestate.recurse(
|
||||
+ filename, source, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
+
|
||||
+ ret = filestate.recurse(
|
||||
+ filename, source, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
diff --git a/tests/pytests/unit/states/file/test_managed.py b/tests/pytests/unit/states/file/test_managed.py
|
||||
index 9d9fb17717..0b341e09a9 100644
|
||||
--- a/tests/pytests/unit/states/file/test_managed.py
|
||||
+++ b/tests/pytests/unit/states/file/test_managed.py
|
||||
@@ -373,3 +373,34 @@ def test_managed():
|
||||
filestate.managed(name, user=user, group=group)
|
||||
== ret
|
||||
)
|
||||
+
|
||||
+
|
||||
+def test_managed_test_mode_user_group_not_present():
|
||||
+ """
|
||||
+ Test file managed in test mode with no user or group existing
|
||||
+ """
|
||||
+ filename = "/tmp/managed_no_user_group_test_mode"
|
||||
+ with patch.dict(
|
||||
+ filestate.__salt__,
|
||||
+ {
|
||||
+ "file.group_to_gid": MagicMock(side_effect=["1234", "", ""]),
|
||||
+ "file.user_to_uid": MagicMock(side_effect=["", "4321", ""]),
|
||||
+ },
|
||||
+ ), patch.dict(filestate.__opts__, {"test": True}):
|
||||
+ ret = filestate.managed(
|
||||
+ filename, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
+
|
||||
+ ret = filestate.managed(
|
||||
+ filename, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
+
|
||||
+ ret = filestate.managed(
|
||||
+ filename, group="nonexistinggroup", user="nonexistinguser"
|
||||
+ )
|
||||
+ assert ret["result"] is not False
|
||||
+ assert "is not available" not in ret["comment"]
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,37 +0,0 @@
|
||||
From 4cc528dadfbffdeb90df41bbd848d0c2c7efec78 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Graul <agraul@suse.com>
|
||||
Date: Tue, 12 Jul 2022 14:02:58 +0200
|
||||
Subject: [PATCH] Fix test_ipc unit tests
|
||||
|
||||
---
|
||||
tests/unit/transport/test_ipc.py | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/tests/unit/transport/test_ipc.py b/tests/unit/transport/test_ipc.py
|
||||
index 4a0a7c29e2..af001d9650 100644
|
||||
--- a/tests/unit/transport/test_ipc.py
|
||||
+++ b/tests/unit/transport/test_ipc.py
|
||||
@@ -105,8 +105,8 @@ class IPCMessagePubSubCase(salt.ext.tornado.testing.AsyncTestCase):
|
||||
self.stop()
|
||||
|
||||
# Now let both waiting data at once
|
||||
- client1.read_async(handler)
|
||||
- client2.read_async(handler)
|
||||
+ client1.read_async()
|
||||
+ client2.read_async()
|
||||
self.pub_channel.publish("TEST")
|
||||
self.wait()
|
||||
self.assertEqual(len(call_cnt), 2)
|
||||
@@ -148,7 +148,7 @@ class IPCMessagePubSubCase(salt.ext.tornado.testing.AsyncTestCase):
|
||||
pass
|
||||
|
||||
try:
|
||||
- ret1 = yield client1.read_async(handler)
|
||||
+ ret1 = yield client1.read_async()
|
||||
self.wait()
|
||||
except StreamClosedError as ex:
|
||||
assert False, "StreamClosedError was raised inside the Future"
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,103 +0,0 @@
|
||||
From 62b3e3491f283a5b5ac243e1f5904ad17e0353bc Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Ra=C3=BAl=20Osuna?=
|
||||
<17827278+raulillo82@users.noreply.github.com>
|
||||
Date: Tue, 31 Jan 2023 13:13:32 +0100
|
||||
Subject: [PATCH] Fixes pkg.version_cmp on openEuler systems and a few
|
||||
other OS flavors (#576)
|
||||
|
||||
* Fix bug/issue #540
|
||||
|
||||
* Fix bug/issue #540
|
||||
|
||||
* Add tests for __virtual__ function
|
||||
|
||||
* Minor changes to align with upstream
|
||||
---
|
||||
changelog/540.fixed | 1 +
|
||||
salt/modules/rpm_lowpkg.py | 12 +++++--
|
||||
tests/pytests/unit/modules/test_rpm_lowpkg.py | 31 +++++++++++++++++++
|
||||
3 files changed, 42 insertions(+), 2 deletions(-)
|
||||
create mode 100644 changelog/540.fixed
|
||||
|
||||
diff --git a/changelog/540.fixed b/changelog/540.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..50cb42bf40
|
||||
--- /dev/null
|
||||
+++ b/changelog/540.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Fix pkg.version_cmp on openEuler and a few other os flavors.
|
||||
diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py
|
||||
index c8e984c021..01cd575bc7 100644
|
||||
--- a/salt/modules/rpm_lowpkg.py
|
||||
+++ b/salt/modules/rpm_lowpkg.py
|
||||
@@ -62,14 +62,22 @@ def __virtual__():
|
||||
" grains.",
|
||||
)
|
||||
|
||||
- enabled = ("amazon", "xcp", "xenserver", "virtuozzolinux")
|
||||
+ enabled = (
|
||||
+ "amazon",
|
||||
+ "xcp",
|
||||
+ "xenserver",
|
||||
+ "virtuozzolinux",
|
||||
+ "virtuozzo",
|
||||
+ "issabel pbx",
|
||||
+ "openeuler",
|
||||
+ )
|
||||
|
||||
if os_family in ["redhat", "suse"] or os_grain in enabled:
|
||||
return __virtualname__
|
||||
return (
|
||||
False,
|
||||
"The rpm execution module failed to load: only available on redhat/suse type"
|
||||
- " systems or amazon, xcp or xenserver.",
|
||||
+ " systems or amazon, xcp, xenserver, virtuozzolinux, virtuozzo, issabel pbx or openeuler.",
|
||||
)
|
||||
|
||||
|
||||
diff --git a/tests/pytests/unit/modules/test_rpm_lowpkg.py b/tests/pytests/unit/modules/test_rpm_lowpkg.py
|
||||
index f19afa854e..e07f71eb61 100644
|
||||
--- a/tests/pytests/unit/modules/test_rpm_lowpkg.py
|
||||
+++ b/tests/pytests/unit/modules/test_rpm_lowpkg.py
|
||||
@@ -35,6 +35,37 @@ def _called_with_root(mock):
|
||||
def configure_loader_modules():
|
||||
return {rpm: {"rpm": MagicMock(return_value=MagicMock)}}
|
||||
|
||||
+def test___virtual___openeuler():
|
||||
+ patch_which = patch("salt.utils.path.which", return_value=True)
|
||||
+ with patch.dict(
|
||||
+ rpm.__grains__, {"os": "openEuler", "os_family": "openEuler"}
|
||||
+ ), patch_which:
|
||||
+ assert rpm.__virtual__() == "lowpkg"
|
||||
+
|
||||
+
|
||||
+def test___virtual___issabel_pbx():
|
||||
+ patch_which = patch("salt.utils.path.which", return_value=True)
|
||||
+ with patch.dict(
|
||||
+ rpm.__grains__, {"os": "Issabel Pbx", "os_family": "IssabeL PBX"}
|
||||
+ ), patch_which:
|
||||
+ assert rpm.__virtual__() == "lowpkg"
|
||||
+
|
||||
+
|
||||
+def test___virtual___virtuozzo():
|
||||
+ patch_which = patch("salt.utils.path.which", return_value=True)
|
||||
+ with patch.dict(
|
||||
+ rpm.__grains__, {"os": "virtuozzo", "os_family": "VirtuoZZO"}
|
||||
+ ), patch_which:
|
||||
+ assert rpm.__virtual__() == "lowpkg"
|
||||
+
|
||||
+
|
||||
+def test___virtual___with_no_rpm():
|
||||
+ patch_which = patch("salt.utils.path.which", return_value=False)
|
||||
+ ret = rpm.__virtual__()
|
||||
+ assert isinstance(ret, tuple)
|
||||
+ assert ret[0] is False
|
||||
+
|
||||
+
|
||||
|
||||
# 'list_pkgs' function tests: 2
|
||||
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,105 +0,0 @@
|
||||
From 7d5b1d2178d0573f137b9481ded85419a36998ff Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
||||
<psuarezhernandez@suse.com>
|
||||
Date: Thu, 6 Oct 2022 10:55:50 +0100
|
||||
Subject: [PATCH] fopen: Workaround bad buffering for binary mode (#563)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
A lot of code assumes Python 2.x behavior for buffering, in which 1 is a
|
||||
special value meaning line buffered.
|
||||
|
||||
Python 3 makes this value unusable, so fallback to the default buffering
|
||||
size, and report these calls to be fixed.
|
||||
|
||||
Fixes: https://github.com/saltstack/salt/issues/57584
|
||||
|
||||
Do not drop buffering from kwargs to avoid errors
|
||||
|
||||
Add unit test around linebuffering in binary mode
|
||||
|
||||
Add changelog file
|
||||
|
||||
Co-authored-by: Pablo Suárez Hernández <psuarezhernandez@suse.com>
|
||||
|
||||
Co-authored-by: Ismael Luceno <iluceno@suse.de>
|
||||
---
|
||||
changelog/62817.fixed | 1 +
|
||||
salt/utils/files.py | 8 ++++++++
|
||||
tests/pytests/unit/utils/test_files.py | 13 ++++++++++++-
|
||||
3 files changed, 21 insertions(+), 1 deletion(-)
|
||||
create mode 100644 changelog/62817.fixed
|
||||
|
||||
diff --git a/changelog/62817.fixed b/changelog/62817.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..ff335f2916
|
||||
--- /dev/null
|
||||
+++ b/changelog/62817.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Prevent annoying RuntimeWarning message about line buffering (buffering=1) not being supported in binary mode
|
||||
diff --git a/salt/utils/files.py b/salt/utils/files.py
|
||||
index 1cf636a753..3c57cce713 100644
|
||||
--- a/salt/utils/files.py
|
||||
+++ b/salt/utils/files.py
|
||||
@@ -6,6 +6,7 @@ Functions for working with files
|
||||
import codecs
|
||||
import contextlib
|
||||
import errno
|
||||
+import io
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
@@ -382,6 +383,13 @@ def fopen(*args, **kwargs):
|
||||
if not binary and not kwargs.get("newline", None):
|
||||
kwargs["newline"] = ""
|
||||
|
||||
+ # Workaround callers with bad buffering setting for binary files
|
||||
+ if kwargs.get("buffering") == 1 and "b" in kwargs.get("mode", ""):
|
||||
+ log.debug(
|
||||
+ "Line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used"
|
||||
+ )
|
||||
+ kwargs["buffering"] = io.DEFAULT_BUFFER_SIZE
|
||||
+
|
||||
f_handle = open(*args, **kwargs) # pylint: disable=resource-leakage
|
||||
|
||||
if is_fcntl_available():
|
||||
diff --git a/tests/pytests/unit/utils/test_files.py b/tests/pytests/unit/utils/test_files.py
|
||||
index fd88167b16..bd18bc5750 100644
|
||||
--- a/tests/pytests/unit/utils/test_files.py
|
||||
+++ b/tests/pytests/unit/utils/test_files.py
|
||||
@@ -4,11 +4,12 @@ Unit Tests for functions located in salt/utils/files.py
|
||||
|
||||
|
||||
import copy
|
||||
+import io
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import salt.utils.files
|
||||
-from tests.support.mock import patch
|
||||
+from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
def test_safe_rm():
|
||||
@@ -74,6 +75,16 @@ def test_fopen_with_disallowed_fds():
|
||||
)
|
||||
|
||||
|
||||
+def test_fopen_binary_line_buffering(tmp_path):
|
||||
+ tmp_file = os.path.join(tmp_path, "foobar")
|
||||
+ with patch("builtins.open") as open_mock, patch(
|
||||
+ "salt.utils.files.is_fcntl_available", MagicMock(return_value=False)
|
||||
+ ):
|
||||
+ salt.utils.files.fopen(os.path.join(tmp_path, "foobar"), mode="b", buffering=1)
|
||||
+ assert open_mock.called
|
||||
+ assert open_mock.call_args[1]["buffering"] == io.DEFAULT_BUFFER_SIZE
|
||||
+
|
||||
+
|
||||
def _create_temp_structure(temp_directory, structure):
|
||||
for folder, files in structure.items():
|
||||
current_directory = os.path.join(temp_directory, folder)
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,56 +0,0 @@
|
||||
From 7cac5f67eb0d586314f9e7c987b8a620e28eeac3 Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Mon, 27 Jun 2022 17:55:49 +0300
|
||||
Subject: [PATCH] Ignore erros on reading license files with dpkg_lowpkg
|
||||
(bsc#1197288)
|
||||
|
||||
* Ignore erros on reading license files with dpkg_lowpkg (bsc#1197288)
|
||||
|
||||
* Add test for license reading with dpkg_lowpkg
|
||||
---
|
||||
salt/modules/dpkg_lowpkg.py | 2 +-
|
||||
tests/pytests/unit/modules/test_dpkg_lowpkg.py | 18 ++++++++++++++++++
|
||||
2 files changed, 19 insertions(+), 1 deletion(-)
|
||||
create mode 100644 tests/pytests/unit/modules/test_dpkg_lowpkg.py
|
||||
|
||||
diff --git a/salt/modules/dpkg_lowpkg.py b/salt/modules/dpkg_lowpkg.py
|
||||
index afbd619490..2c25b1fb2a 100644
|
||||
--- a/salt/modules/dpkg_lowpkg.py
|
||||
+++ b/salt/modules/dpkg_lowpkg.py
|
||||
@@ -361,7 +361,7 @@ def _get_pkg_license(pkg):
|
||||
licenses = set()
|
||||
cpr = "/usr/share/doc/{}/copyright".format(pkg)
|
||||
if os.path.exists(cpr):
|
||||
- with salt.utils.files.fopen(cpr) as fp_:
|
||||
+ with salt.utils.files.fopen(cpr, errors="ignore") as fp_:
|
||||
for line in salt.utils.stringutils.to_unicode(fp_.read()).split(os.linesep):
|
||||
if line.startswith("License:"):
|
||||
licenses.add(line.split(":", 1)[1].strip())
|
||||
diff --git a/tests/pytests/unit/modules/test_dpkg_lowpkg.py b/tests/pytests/unit/modules/test_dpkg_lowpkg.py
|
||||
new file mode 100644
|
||||
index 0000000000..1a89660c02
|
||||
--- /dev/null
|
||||
+++ b/tests/pytests/unit/modules/test_dpkg_lowpkg.py
|
||||
@@ -0,0 +1,18 @@
|
||||
+import os
|
||||
+
|
||||
+import salt.modules.dpkg_lowpkg as dpkg
|
||||
+from tests.support.mock import MagicMock, mock_open, patch
|
||||
+
|
||||
+
|
||||
+def test_get_pkg_license():
|
||||
+ """
|
||||
+ Test _get_pkg_license for ignore errors on reading license from copyright files
|
||||
+ """
|
||||
+ license_read_mock = mock_open(read_data="")
|
||||
+ with patch.object(os.path, "exists", MagicMock(return_value=True)), patch(
|
||||
+ "salt.utils.files.fopen", license_read_mock
|
||||
+ ):
|
||||
+ dpkg._get_pkg_license("bash")
|
||||
+
|
||||
+ assert license_read_mock.calls[0].args[0] == "/usr/share/doc/bash/copyright"
|
||||
+ assert license_read_mock.calls[0].kwargs["errors"] == "ignore"
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,250 +0,0 @@
|
||||
From e4aff9ca68ce142c87ec875846d8916b9df8e6c5 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Graul <agraul@suse.com>
|
||||
Date: Fri, 21 Oct 2022 14:39:52 +0200
|
||||
Subject: [PATCH] Ignore extend declarations from excluded sls files
|
||||
|
||||
* Use both test sls files
|
||||
|
||||
(cherry picked from commit 3cb5f5a14ff68d0bd809a4adba7d820534d0f7c7)
|
||||
|
||||
* Test that excluded sls files can't extend others
|
||||
|
||||
(cherry picked from commit e91c1a608b3c016b2c30bf324e969cd097ddf776)
|
||||
|
||||
* Ignore extend declarations from excluded sls files
|
||||
|
||||
sls files that are excluded should not affect other sls files by
|
||||
extending their states. Exclude statements are processed very late in
|
||||
the state processing pipeline to ensure they are not overridden. By that
|
||||
time, extend declarations are already processed.
|
||||
|
||||
Luckily, it's not necessary to change much, during the extend
|
||||
declarations processing it is easy to check if the sls file that
|
||||
contains a given extend declaration is excluded.
|
||||
|
||||
(cherry picked from commit 856b23c45dd3be78d8879a0b0c4aa6356afea3cf)
|
||||
---
|
||||
changelog/62082.fixed | 1 +
|
||||
salt/state.py | 19 +++
|
||||
.../unit/state/test_state_highstate.py | 152 +++++++++++++++++-
|
||||
3 files changed, 171 insertions(+), 1 deletion(-)
|
||||
create mode 100644 changelog/62082.fixed
|
||||
|
||||
diff --git a/changelog/62082.fixed b/changelog/62082.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..02e5f5ff40
|
||||
--- /dev/null
|
||||
+++ b/changelog/62082.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Ignore extend declarations in sls files that are excluded.
|
||||
diff --git a/salt/state.py b/salt/state.py
|
||||
index 316dcdec63..f5579fbb69 100644
|
||||
--- a/salt/state.py
|
||||
+++ b/salt/state.py
|
||||
@@ -1680,6 +1680,25 @@ class State:
|
||||
else:
|
||||
name = ids[0][0]
|
||||
|
||||
+ sls_excludes = []
|
||||
+ # excluded sls are plain list items or dicts with an "sls" key
|
||||
+ for exclude in high.get("__exclude__", []):
|
||||
+ if isinstance(exclude, str):
|
||||
+ sls_excludes.append(exclude)
|
||||
+ elif exclude.get("sls"):
|
||||
+ sls_excludes.append(exclude["sls"])
|
||||
+
|
||||
+ if body.get("__sls__") in sls_excludes:
|
||||
+ log.debug(
|
||||
+ "Cannot extend ID '%s' in '%s:%s' because '%s:%s' is excluded.",
|
||||
+ name,
|
||||
+ body.get("__env__", "base"),
|
||||
+ body.get("__sls__", "base"),
|
||||
+ body.get("__env__", "base"),
|
||||
+ body.get("__sls__", "base"),
|
||||
+ )
|
||||
+ continue
|
||||
+
|
||||
for state, run in body.items():
|
||||
if state.startswith("__"):
|
||||
continue
|
||||
diff --git a/tests/pytests/unit/state/test_state_highstate.py b/tests/pytests/unit/state/test_state_highstate.py
|
||||
index 059f83fd9f..7c72cc8e09 100644
|
||||
--- a/tests/pytests/unit/state/test_state_highstate.py
|
||||
+++ b/tests/pytests/unit/state/test_state_highstate.py
|
||||
@@ -3,9 +3,11 @@
|
||||
"""
|
||||
|
||||
import logging
|
||||
+import textwrap
|
||||
|
||||
import pytest # pylint: disable=unused-import
|
||||
import salt.state
|
||||
+from salt.utils.odict import OrderedDict
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -180,7 +182,7 @@ def test_find_sls_ids_with_exclude(highstate, state_tree_dir):
|
||||
with pytest.helpers.temp_file(
|
||||
"slsfile1.sls", slsfile1, sls_dir
|
||||
), pytest.helpers.temp_file(
|
||||
- "slsfile1.sls", slsfile1, sls_dir
|
||||
+ "slsfile2.sls", slsfile2, sls_dir
|
||||
), pytest.helpers.temp_file(
|
||||
"stateB.sls", stateB, sls_dir
|
||||
), pytest.helpers.temp_file(
|
||||
@@ -196,3 +198,151 @@ def test_find_sls_ids_with_exclude(highstate, state_tree_dir):
|
||||
high, _ = highstate.render_highstate(matches)
|
||||
ret = salt.state.find_sls_ids("issue-47182.stateA.newer", high)
|
||||
assert ret == [("somestuff", "cmd")]
|
||||
+
|
||||
+
|
||||
+def test_dont_extend_in_excluded_sls_file(highstate, state_tree_dir):
|
||||
+ """
|
||||
+ See https://github.com/saltstack/salt/issues/62082#issuecomment-1245461333
|
||||
+ """
|
||||
+ top_sls = textwrap.dedent(
|
||||
+ """\
|
||||
+ base:
|
||||
+ '*':
|
||||
+ - test1
|
||||
+ - exclude
|
||||
+ """
|
||||
+ )
|
||||
+ exclude_sls = textwrap.dedent(
|
||||
+ """\
|
||||
+ exclude:
|
||||
+ - sls: test2
|
||||
+ """
|
||||
+ )
|
||||
+ test1_sls = textwrap.dedent(
|
||||
+ """\
|
||||
+ include:
|
||||
+ - test2
|
||||
+
|
||||
+ test1:
|
||||
+ cmd.run:
|
||||
+ - name: echo test1
|
||||
+ """
|
||||
+ )
|
||||
+ test2_sls = textwrap.dedent(
|
||||
+ """\
|
||||
+ extend:
|
||||
+ test1:
|
||||
+ cmd.run:
|
||||
+ - name: echo "override test1 in test2"
|
||||
+
|
||||
+ test2-id:
|
||||
+ cmd.run:
|
||||
+ - name: echo test2
|
||||
+ """
|
||||
+ )
|
||||
+ sls_dir = str(state_tree_dir)
|
||||
+ with pytest.helpers.temp_file(
|
||||
+ "top.sls", top_sls, sls_dir
|
||||
+ ), pytest.helpers.temp_file(
|
||||
+ "test1.sls", test1_sls, sls_dir
|
||||
+ ), pytest.helpers.temp_file(
|
||||
+ "test2.sls", test2_sls, sls_dir
|
||||
+ ), pytest.helpers.temp_file(
|
||||
+ "exclude.sls", exclude_sls, sls_dir
|
||||
+ ):
|
||||
+ # manually compile the high data, error checking is not needed in this
|
||||
+ # test case.
|
||||
+ top = highstate.get_top()
|
||||
+ matches = highstate.top_matches(top)
|
||||
+ high, _ = highstate.render_highstate(matches)
|
||||
+
|
||||
+ # high is mutated by call_high and the different "pipeline steps"
|
||||
+ assert high == OrderedDict(
|
||||
+ [
|
||||
+ (
|
||||
+ "__extend__",
|
||||
+ [
|
||||
+ {
|
||||
+ "test1": OrderedDict(
|
||||
+ [
|
||||
+ ("__sls__", "test2"),
|
||||
+ ("__env__", "base"),
|
||||
+ (
|
||||
+ "cmd",
|
||||
+ [
|
||||
+ OrderedDict(
|
||||
+ [
|
||||
+ (
|
||||
+ "name",
|
||||
+ 'echo "override test1 in test2"',
|
||||
+ )
|
||||
+ ]
|
||||
+ ),
|
||||
+ "run",
|
||||
+ ],
|
||||
+ ),
|
||||
+ ]
|
||||
+ )
|
||||
+ }
|
||||
+ ],
|
||||
+ ),
|
||||
+ (
|
||||
+ "test1",
|
||||
+ OrderedDict(
|
||||
+ [
|
||||
+ (
|
||||
+ "cmd",
|
||||
+ [
|
||||
+ OrderedDict([("name", "echo test1")]),
|
||||
+ "run",
|
||||
+ {"order": 10001},
|
||||
+ ],
|
||||
+ ),
|
||||
+ ("__sls__", "test1"),
|
||||
+ ("__env__", "base"),
|
||||
+ ]
|
||||
+ ),
|
||||
+ ),
|
||||
+ (
|
||||
+ "test2-id",
|
||||
+ OrderedDict(
|
||||
+ [
|
||||
+ (
|
||||
+ "cmd",
|
||||
+ [
|
||||
+ OrderedDict([("name", "echo test2")]),
|
||||
+ "run",
|
||||
+ {"order": 10000},
|
||||
+ ],
|
||||
+ ),
|
||||
+ ("__sls__", "test2"),
|
||||
+ ("__env__", "base"),
|
||||
+ ]
|
||||
+ ),
|
||||
+ ),
|
||||
+ ("__exclude__", [OrderedDict([("sls", "test2")])]),
|
||||
+ ]
|
||||
+ )
|
||||
+ highstate.state.call_high(high)
|
||||
+ # assert that the extend declaration was not applied
|
||||
+ assert high == OrderedDict(
|
||||
+ [
|
||||
+ (
|
||||
+ "test1",
|
||||
+ OrderedDict(
|
||||
+ [
|
||||
+ (
|
||||
+ "cmd",
|
||||
+ [
|
||||
+ OrderedDict([("name", "echo test1")]),
|
||||
+ "run",
|
||||
+ {"order": 10001},
|
||||
+ ],
|
||||
+ ),
|
||||
+ ("__sls__", "test1"),
|
||||
+ ("__env__", "base"),
|
||||
+ ]
|
||||
+ ),
|
||||
+ )
|
||||
+ ]
|
||||
+ )
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,214 +0,0 @@
|
||||
From 3f1b1180ba34e9ab3a4453248c733f11aa193f1b Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <Victor.Zhestkov@suse.com>
|
||||
Date: Wed, 14 Sep 2022 14:57:29 +0300
|
||||
Subject: [PATCH] Ignore non utf8 characters while reading files with
|
||||
core grains module (bsc#1202165)
|
||||
|
||||
* Ignore UnicodeDecodeError on reading files with core grains
|
||||
|
||||
* Add tests for non utf8 chars in cmdline
|
||||
|
||||
* Blacken modified lines
|
||||
|
||||
* Fix the tests
|
||||
|
||||
* Add changelog entry
|
||||
|
||||
* Change ignore to surrogateescape for kernelparameters
|
||||
|
||||
* Turn static test files to dynamic
|
||||
---
|
||||
changelog/62633.fixed | 1 +
|
||||
salt/grains/core.py | 12 ++-
|
||||
tests/pytests/unit/grains/test_core.py | 118 +++++++++++++++++++++++++
|
||||
3 files changed, 128 insertions(+), 3 deletions(-)
|
||||
create mode 100644 changelog/62633.fixed
|
||||
|
||||
diff --git a/changelog/62633.fixed b/changelog/62633.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..1ab74f9122
|
||||
--- /dev/null
|
||||
+++ b/changelog/62633.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Prevent possible tracebacks in core grains module by ignoring non utf8 characters in /proc/1/environ, /proc/1/cmdline, /proc/cmdline
|
||||
diff --git a/salt/grains/core.py b/salt/grains/core.py
|
||||
index 047c33ffd3..76f3767ddf 100644
|
||||
--- a/salt/grains/core.py
|
||||
+++ b/salt/grains/core.py
|
||||
@@ -1089,7 +1089,9 @@ def _virtual(osdata):
|
||||
if ("virtual_subtype" not in grains) or (grains["virtual_subtype"] != "LXC"):
|
||||
if os.path.isfile("/proc/1/environ"):
|
||||
try:
|
||||
- with salt.utils.files.fopen("/proc/1/environ", "r") as fhr:
|
||||
+ with salt.utils.files.fopen(
|
||||
+ "/proc/1/environ", "r", errors="ignore"
|
||||
+ ) as fhr:
|
||||
fhr_contents = fhr.read()
|
||||
if "container=lxc" in fhr_contents:
|
||||
grains["virtual"] = "container"
|
||||
@@ -1909,7 +1911,9 @@ def os_data():
|
||||
grains["init"] = "systemd"
|
||||
except OSError:
|
||||
try:
|
||||
- with salt.utils.files.fopen("/proc/1/cmdline") as fhr:
|
||||
+ with salt.utils.files.fopen(
|
||||
+ "/proc/1/cmdline", "r", errors="ignore"
|
||||
+ ) as fhr:
|
||||
init_cmdline = fhr.read().replace("\x00", " ").split()
|
||||
except OSError:
|
||||
pass
|
||||
@@ -3154,7 +3158,9 @@ def kernelparams():
|
||||
return {}
|
||||
else:
|
||||
try:
|
||||
- with salt.utils.files.fopen("/proc/cmdline", "r") as fhr:
|
||||
+ with salt.utils.files.fopen(
|
||||
+ "/proc/cmdline", "r", errors="surrogateescape"
|
||||
+ ) as fhr:
|
||||
cmdline = fhr.read()
|
||||
grains = {"kernelparams": []}
|
||||
for data in [
|
||||
diff --git a/tests/pytests/unit/grains/test_core.py b/tests/pytests/unit/grains/test_core.py
|
||||
index 7c4ea1f17f..c06cdb2db0 100644
|
||||
--- a/tests/pytests/unit/grains/test_core.py
|
||||
+++ b/tests/pytests/unit/grains/test_core.py
|
||||
@@ -11,6 +11,7 @@ import os
|
||||
import pathlib
|
||||
import platform
|
||||
import socket
|
||||
+import tempfile
|
||||
import textwrap
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -2738,6 +2739,38 @@ def test_kernelparams_return_linux(cmdline, expectation):
|
||||
assert core.kernelparams() == expectation
|
||||
|
||||
|
||||
+@pytest.mark.skip_unless_on_linux
|
||||
+def test_kernelparams_return_linux_non_utf8():
|
||||
+ _salt_utils_files_fopen = salt.utils.files.fopen
|
||||
+
|
||||
+ expected = {
|
||||
+ "kernelparams": [
|
||||
+ ("TEST_KEY1", "VAL1"),
|
||||
+ ("TEST_KEY2", "VAL2"),
|
||||
+ ("BOOTABLE_FLAG", "\udc80"),
|
||||
+ ("TEST_KEY_NOVAL", None),
|
||||
+ ("TEST_KEY3", "3"),
|
||||
+ ]
|
||||
+ }
|
||||
+
|
||||
+ with tempfile.TemporaryDirectory() as tempdir:
|
||||
+
|
||||
+ def _open_mock(file_name, *args, **kwargs):
|
||||
+ return _salt_utils_files_fopen(
|
||||
+ os.path.join(tempdir, "cmdline"), *args, **kwargs
|
||||
+ )
|
||||
+
|
||||
+ with salt.utils.files.fopen(
|
||||
+ os.path.join(tempdir, "cmdline"),
|
||||
+ "wb",
|
||||
+ ) as cmdline_fh, patch("salt.utils.files.fopen", _open_mock):
|
||||
+ cmdline_fh.write(
|
||||
+ b'TEST_KEY1=VAL1 TEST_KEY2=VAL2 BOOTABLE_FLAG="\x80" TEST_KEY_NOVAL TEST_KEY3=3\n'
|
||||
+ )
|
||||
+ cmdline_fh.close()
|
||||
+ assert core.kernelparams() == expected
|
||||
+
|
||||
+
|
||||
def test_linux_gpus():
|
||||
"""
|
||||
Test GPU detection on Linux systems
|
||||
@@ -2940,3 +2973,88 @@ def test_virtual_set_virtual_ec2():
|
||||
|
||||
assert virtual_grains["virtual"] == "kvm"
|
||||
assert "virtual_subtype" not in virtual_grains
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skip_on_windows
|
||||
+def test_linux_proc_files_with_non_utf8_chars():
|
||||
+ _salt_utils_files_fopen = salt.utils.files.fopen
|
||||
+
|
||||
+ empty_mock = MagicMock(return_value={})
|
||||
+
|
||||
+ with tempfile.TemporaryDirectory() as tempdir:
|
||||
+
|
||||
+ def _mock_open(filename, *args, **kwargs):
|
||||
+ return _salt_utils_files_fopen(
|
||||
+ os.path.join(tempdir, "cmdline-1"), *args, **kwargs
|
||||
+ )
|
||||
+
|
||||
+ with salt.utils.files.fopen(
|
||||
+ os.path.join(tempdir, "cmdline-1"),
|
||||
+ "wb",
|
||||
+ ) as cmdline_fh, patch("os.path.isfile", return_value=False), patch(
|
||||
+ "salt.utils.files.fopen", _mock_open
|
||||
+ ), patch.dict(
|
||||
+ core.__salt__,
|
||||
+ {
|
||||
+ "cmd.retcode": salt.modules.cmdmod.retcode,
|
||||
+ "cmd.run": MagicMock(return_value=""),
|
||||
+ },
|
||||
+ ), patch.object(
|
||||
+ core, "_linux_bin_exists", return_value=False
|
||||
+ ), patch.object(
|
||||
+ core, "_parse_lsb_release", return_value=empty_mock
|
||||
+ ), patch.object(
|
||||
+ core, "_parse_os_release", return_value=empty_mock
|
||||
+ ), patch.object(
|
||||
+ core, "_hw_data", return_value=empty_mock
|
||||
+ ), patch.object(
|
||||
+ core, "_virtual", return_value=empty_mock
|
||||
+ ), patch.object(
|
||||
+ core, "_bsd_cpudata", return_value=empty_mock
|
||||
+ ), patch.object(
|
||||
+ os, "stat", side_effect=OSError()
|
||||
+ ):
|
||||
+ cmdline_fh.write(
|
||||
+ b"/usr/lib/systemd/systemd\x00--switched-root\x00--system\x00--deserialize\x0028\x80\x00"
|
||||
+ )
|
||||
+ cmdline_fh.close()
|
||||
+ os_grains = core.os_data()
|
||||
+ assert os_grains != {}
|
||||
+
|
||||
+
|
||||
+@pytest.mark.skip_on_windows
|
||||
+def test_virtual_linux_proc_files_with_non_utf8_chars():
|
||||
+ _salt_utils_files_fopen = salt.utils.files.fopen
|
||||
+
|
||||
+ def _is_file_mock(filename):
|
||||
+ if filename == "/proc/1/environ":
|
||||
+ return True
|
||||
+ return False
|
||||
+
|
||||
+ with tempfile.TemporaryDirectory() as tempdir:
|
||||
+
|
||||
+ def _mock_open(filename, *args, **kwargs):
|
||||
+ return _salt_utils_files_fopen(
|
||||
+ os.path.join(tempdir, "environ"), *args, **kwargs
|
||||
+ )
|
||||
+
|
||||
+ with salt.utils.files.fopen(
|
||||
+ os.path.join(tempdir, "environ"),
|
||||
+ "wb",
|
||||
+ ) as environ_fh, patch("os.path.isfile", _is_file_mock), patch(
|
||||
+ "salt.utils.files.fopen", _mock_open
|
||||
+ ), patch.object(
|
||||
+ salt.utils.path, "which", MagicMock(return_value=None)
|
||||
+ ), patch.dict(
|
||||
+ core.__salt__,
|
||||
+ {
|
||||
+ "cmd.run_all": MagicMock(
|
||||
+ return_value={"retcode": 1, "stderr": "", "stdout": ""}
|
||||
+ ),
|
||||
+ "cmd.run": MagicMock(return_value=""),
|
||||
+ },
|
||||
+ ):
|
||||
+ environ_fh.write(b"KEY1=VAL1 KEY2=VAL2\x80 KEY2=VAL2")
|
||||
+ environ_fh.close()
|
||||
+ virt_grains = core._virtual({"kernel": "Linux"})
|
||||
+ assert virt_grains == {"virtual": "physical"}
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,63 +0,0 @@
|
||||
From f9fe9ea009915478ea8f7896dff2c281e68b5d36 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Yeray=20Guti=C3=A9rrez=20Cedr=C3=A9s?=
|
||||
<yeray.gutierrez@suse.com>
|
||||
Date: Fri, 14 Oct 2022 08:41:40 +0100
|
||||
Subject: [PATCH] Include stdout in error message for zypperpkg (#559)
|
||||
|
||||
---
|
||||
salt/modules/zypperpkg.py | 5 +++++
|
||||
tests/unit/modules/test_zypperpkg.py | 17 ++++++++++++++++-
|
||||
2 files changed, 21 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py
|
||||
index c787d4009d..5d745c432d 100644
|
||||
--- a/salt/modules/zypperpkg.py
|
||||
+++ b/salt/modules/zypperpkg.py
|
||||
@@ -339,6 +339,11 @@ class _Zypper:
|
||||
and self.__call_result["stderr"].strip()
|
||||
or ""
|
||||
)
|
||||
+ msg += (
|
||||
+ self.__call_result["stdout"]
|
||||
+ and self.__call_result["stdout"].strip()
|
||||
+ or ""
|
||||
+ )
|
||||
if msg:
|
||||
_error_msg.append(msg)
|
||||
else:
|
||||
diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py
|
||||
index 37d555844c..bcd001cd85 100644
|
||||
--- a/tests/unit/modules/test_zypperpkg.py
|
||||
+++ b/tests/unit/modules/test_zypperpkg.py
|
||||
@@ -207,11 +207,26 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin):
|
||||
):
|
||||
zypper.__zypper__.xml.call("crashme")
|
||||
|
||||
+ output_to_user_stdout = "Output to user to stdout"
|
||||
+ output_to_user_stderr = "Output to user to stderr"
|
||||
+ sniffer = RunSniffer(
|
||||
+ stdout=output_to_user_stdout, stderr=output_to_user_stderr, retcode=1
|
||||
+ )
|
||||
+ with patch.dict(
|
||||
+ "salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer}
|
||||
+ ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False):
|
||||
with self.assertRaisesRegex(
|
||||
- CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"
|
||||
+ CommandExecutionError,
|
||||
+ "^Zypper command failure: {}$".format(
|
||||
+ output_to_user_stderr + output_to_user_stdout
|
||||
+ ),
|
||||
):
|
||||
zypper.__zypper__.call("crashme again")
|
||||
|
||||
+ sniffer = RunSniffer(retcode=1)
|
||||
+ with patch.dict(
|
||||
+ "salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer}
|
||||
+ ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False):
|
||||
zypper.__zypper__.noraise.call("stay quiet")
|
||||
self.assertEqual(zypper.__zypper__.error_msg, "Check Zypper's logs.")
|
||||
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,414 +0,0 @@
|
||||
From 030e2cb20af09673d5f38d68bcb257c6c839a2f3 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Mach <daniel.mach@gmail.com>
|
||||
Date: Thu, 6 Oct 2022 11:58:23 +0200
|
||||
Subject: [PATCH] Make pass renderer configurable & other fixes (#532)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
* pass: Use a secure way of handling pass arguments
|
||||
|
||||
The original code would fail on pass paths with spaces,
|
||||
because they would be split into multiple arguments.
|
||||
|
||||
* pass: Strip only trailing newline characters from the secret
|
||||
|
||||
* pass: Do not modify $HOME env globally
|
||||
|
||||
Just set $HOME for calling the pass binary
|
||||
to avoid affecting anything outside the pass renderer.
|
||||
|
||||
* pass: Use pass executable path from _get_pass_exec()
|
||||
|
||||
* Make the pass renderer more configurable
|
||||
|
||||
1. Allow us to make the pass renderer fail during pillar rendering
|
||||
when a secret corresponding with a pass path cannot be fetched.
|
||||
For this we add a master config variable pass_strict_fetch.
|
||||
|
||||
2. Allow to have prefix for variables that should be processed
|
||||
with the pass renderer.
|
||||
For this we add a master config variable pass_variable_prefix.
|
||||
|
||||
3. Allow us to configure pass' GNUPGHOME and PASSWORD_STORE_DIR
|
||||
environmental variables.
|
||||
For this we add master config variables pass_gnupghome and pass_dir.
|
||||
|
||||
* Add tests for the pass renderer
|
||||
|
||||
* pass: Handle FileNotFoundError when pass binary is not available
|
||||
|
||||
Co-authored-by: Marcus Rückert <darix@nordisch.org>
|
||||
---
|
||||
changelog/62120.added | 4 +
|
||||
changelog/62120.fixed | 4 +
|
||||
salt/config/__init__.py | 12 ++
|
||||
salt/renderers/pass.py | 104 ++++++++++++--
|
||||
tests/pytests/unit/renderers/test_pass.py | 164 ++++++++++++++++++++++
|
||||
5 files changed, 274 insertions(+), 14 deletions(-)
|
||||
create mode 100644 changelog/62120.added
|
||||
create mode 100644 changelog/62120.fixed
|
||||
create mode 100644 tests/pytests/unit/renderers/test_pass.py
|
||||
|
||||
diff --git a/changelog/62120.added b/changelog/62120.added
|
||||
new file mode 100644
|
||||
index 0000000000..4303d124f0
|
||||
--- /dev/null
|
||||
+++ b/changelog/62120.added
|
||||
@@ -0,0 +1,4 @@
|
||||
+Config option pass_variable_prefix allows to distinguish variables that contain paths to pass secrets.
|
||||
+Config option pass_strict_fetch allows to error out when a secret cannot be fetched from pass.
|
||||
+Config option pass_dir allows setting the PASSWORD_STORE_DIR env for pass.
|
||||
+Config option pass_gnupghome allows setting the $GNUPGHOME env for pass.
|
||||
diff --git a/changelog/62120.fixed b/changelog/62120.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..22a9711383
|
||||
--- /dev/null
|
||||
+++ b/changelog/62120.fixed
|
||||
@@ -0,0 +1,4 @@
|
||||
+Pass executable path from _get_path_exec() is used when calling the program.
|
||||
+The $HOME env is no longer modified globally.
|
||||
+Only trailing newlines are stripped from the fetched secret.
|
||||
+Pass process arguments are handled in a secure way.
|
||||
diff --git a/salt/config/__init__.py b/salt/config/__init__.py
|
||||
index 7cdee12c4d..0cc0deb874 100644
|
||||
--- a/salt/config/__init__.py
|
||||
+++ b/salt/config/__init__.py
|
||||
@@ -967,6 +967,14 @@ VALID_OPTS = immutabletypes.freeze(
|
||||
# Use Adler32 hashing algorithm for server_id (default False until Sodium, "adler32" after)
|
||||
# Possible values are: False, adler32, crc32
|
||||
"server_id_use_crc": (bool, str),
|
||||
+ # pass renderer: Fetch secrets only for the template variables matching the prefix
|
||||
+ "pass_variable_prefix": str,
|
||||
+ # pass renderer: Whether to error out when unable to fetch a secret
|
||||
+ "pass_strict_fetch": bool,
|
||||
+ # pass renderer: Set GNUPGHOME env for Pass
|
||||
+ "pass_gnupghome": str,
|
||||
+ # pass renderer: Set PASSWORD_STORE_DIR env for Pass
|
||||
+ "pass_dir": str,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1608,6 +1616,10 @@ DEFAULT_MASTER_OPTS = immutabletypes.freeze(
|
||||
"fips_mode": False,
|
||||
"detect_remote_minions": False,
|
||||
"remote_minions_port": 22,
|
||||
+ "pass_variable_prefix": "",
|
||||
+ "pass_strict_fetch": False,
|
||||
+ "pass_gnupghome": "",
|
||||
+ "pass_dir": "",
|
||||
}
|
||||
)
|
||||
|
||||
diff --git a/salt/renderers/pass.py b/salt/renderers/pass.py
|
||||
index 71b1021b96..ba0f152c23 100644
|
||||
--- a/salt/renderers/pass.py
|
||||
+++ b/salt/renderers/pass.py
|
||||
@@ -45,6 +45,34 @@ Install pass binary
|
||||
|
||||
pass:
|
||||
pkg.installed
|
||||
+
|
||||
+Salt master configuration options
|
||||
+
|
||||
+.. code-block:: yaml
|
||||
+
|
||||
+ # If the prefix is *not* set (default behavior), all template variables are
|
||||
+ # considered for fetching secrets from Pass. Those that cannot be resolved
|
||||
+ # to a secret are passed through.
|
||||
+ #
|
||||
+ # If the prefix is set, only the template variables with matching prefix are
|
||||
+ # considered for fetching the secrets, other variables are passed through.
|
||||
+ #
|
||||
+ # For ease of use it is recommended to set the following options as well:
|
||||
+ # renderer: 'jinja|yaml|pass'
|
||||
+ # pass_strict_fetch: true
|
||||
+ #
|
||||
+ pass_variable_prefix: 'pass:'
|
||||
+
|
||||
+ # If set to 'true', error out when unable to fetch a secret for a template variable.
|
||||
+ pass_strict_fetch: true
|
||||
+
|
||||
+ # Set GNUPGHOME env for Pass.
|
||||
+ # Defaults to: ~/.gnupg
|
||||
+ pass_gnupghome: <path>
|
||||
+
|
||||
+ # Set PASSWORD_STORE_DIR env for Pass.
|
||||
+ # Defaults to: ~/.password-store
|
||||
+ pass_dir: <path>
|
||||
"""
|
||||
|
||||
|
||||
@@ -54,7 +82,7 @@ from os.path import expanduser
|
||||
from subprocess import PIPE, Popen
|
||||
|
||||
import salt.utils.path
|
||||
-from salt.exceptions import SaltRenderError
|
||||
+from salt.exceptions import SaltConfigurationError, SaltRenderError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,18 +103,71 @@ def _fetch_secret(pass_path):
|
||||
Fetch secret from pass based on pass_path. If there is
|
||||
any error, return back the original pass_path value
|
||||
"""
|
||||
- cmd = "pass show {}".format(pass_path.strip())
|
||||
- log.debug("Fetching secret: %s", cmd)
|
||||
+ pass_exec = _get_pass_exec()
|
||||
+
|
||||
+ # Make a backup in case we want to return the original value without stripped whitespaces
|
||||
+ original_pass_path = pass_path
|
||||
+
|
||||
+ # Remove the optional prefix from pass path
|
||||
+ pass_prefix = __opts__["pass_variable_prefix"]
|
||||
+ if pass_prefix:
|
||||
+ # If we do not see our prefix we do not want to process this variable
|
||||
+ # and we return the unmodified pass path
|
||||
+ if not pass_path.startswith(pass_prefix):
|
||||
+ return pass_path
|
||||
+
|
||||
+ # strip the prefix from the start of the string
|
||||
+ pass_path = pass_path[len(pass_prefix) :]
|
||||
+
|
||||
+ # The pass_strict_fetch option must be used with pass_variable_prefix
|
||||
+ pass_strict_fetch = __opts__["pass_strict_fetch"]
|
||||
+ if pass_strict_fetch and not pass_prefix:
|
||||
+ msg = "The 'pass_strict_fetch' option requires 'pass_variable_prefix' option enabled"
|
||||
+ raise SaltConfigurationError(msg)
|
||||
+
|
||||
+ # Remove whitespaces from the pass_path
|
||||
+ pass_path = pass_path.strip()
|
||||
|
||||
- proc = Popen(cmd.split(" "), stdout=PIPE, stderr=PIPE)
|
||||
- pass_data, pass_error = proc.communicate()
|
||||
+ cmd = [pass_exec, "show", pass_path]
|
||||
+ log.debug("Fetching secret: %s", " ".join(cmd))
|
||||
+
|
||||
+ # Make sure environment variable HOME is set, since Pass looks for the
|
||||
+ # password-store under ~/.password-store.
|
||||
+ env = os.environ.copy()
|
||||
+ env["HOME"] = expanduser("~")
|
||||
+
|
||||
+ pass_dir = __opts__["pass_dir"]
|
||||
+ if pass_dir:
|
||||
+ env["PASSWORD_STORE_DIR"] = pass_dir
|
||||
+
|
||||
+ pass_gnupghome = __opts__["pass_gnupghome"]
|
||||
+ if pass_gnupghome:
|
||||
+ env["GNUPGHOME"] = pass_gnupghome
|
||||
+
|
||||
+ try:
|
||||
+ proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env)
|
||||
+ pass_data, pass_error = proc.communicate()
|
||||
+ pass_returncode = proc.returncode
|
||||
+ except OSError as e:
|
||||
+ pass_data, pass_error = "", str(e)
|
||||
+ pass_returncode = 1
|
||||
|
||||
# The version of pass used during development sent output to
|
||||
# stdout instead of stderr even though its returncode was non zero.
|
||||
- if proc.returncode or not pass_data:
|
||||
- log.warning("Could not fetch secret: %s %s", pass_data, pass_error)
|
||||
- pass_data = pass_path
|
||||
- return pass_data.strip()
|
||||
+ if pass_returncode or not pass_data:
|
||||
+ try:
|
||||
+ pass_error = pass_error.decode("utf-8")
|
||||
+ except (AttributeError, ValueError):
|
||||
+ pass
|
||||
+ msg = "Could not fetch secret '{}' from the password store: {}".format(
|
||||
+ pass_path, pass_error
|
||||
+ )
|
||||
+ if pass_strict_fetch:
|
||||
+ raise SaltRenderError(msg)
|
||||
+ else:
|
||||
+ log.warning(msg)
|
||||
+ return original_pass_path
|
||||
+ return pass_data.rstrip("\r\n")
|
||||
|
||||
|
||||
def _decrypt_object(obj):
|
||||
@@ -108,9 +189,4 @@ def render(pass_info, saltenv="base", sls="", argline="", **kwargs):
|
||||
"""
|
||||
Fetch secret from pass based on pass_path
|
||||
"""
|
||||
- _get_pass_exec()
|
||||
-
|
||||
- # Make sure environment variable HOME is set, since Pass looks for the
|
||||
- # password-store under ~/.password-store.
|
||||
- os.environ["HOME"] = expanduser("~")
|
||||
return _decrypt_object(pass_info)
|
||||
diff --git a/tests/pytests/unit/renderers/test_pass.py b/tests/pytests/unit/renderers/test_pass.py
|
||||
new file mode 100644
|
||||
index 0000000000..74e822c7ec
|
||||
--- /dev/null
|
||||
+++ b/tests/pytests/unit/renderers/test_pass.py
|
||||
@@ -0,0 +1,164 @@
|
||||
+import importlib
|
||||
+
|
||||
+import pytest
|
||||
+
|
||||
+import salt.config
|
||||
+import salt.exceptions
|
||||
+from tests.support.mock import MagicMock, patch
|
||||
+
|
||||
+# "pass" is a reserved keyword, we need to import it differently
|
||||
+pass_ = importlib.import_module("salt.renderers.pass")
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def configure_loader_modules():
|
||||
+ return {
|
||||
+ pass_: {
|
||||
+ "__opts__": salt.config.DEFAULT_MASTER_OPTS.copy(),
|
||||
+ "_get_pass_exec": MagicMock(return_value="/usr/bin/pass"),
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+# The default behavior is that if fetching a secret from pass fails,
|
||||
+# the value is passed through. Even the trailing newlines are preserved.
|
||||
+def test_passthrough():
|
||||
+ pass_path = "secret\n"
|
||||
+ expected = pass_path
|
||||
+ result = pass_.render(pass_path)
|
||||
+
|
||||
+ assert result == expected
|
||||
+
|
||||
+
|
||||
+# Fetch a secret in the strict mode.
|
||||
+def test_strict_fetch():
|
||||
+ config = {
|
||||
+ "pass_variable_prefix": "pass:",
|
||||
+ "pass_strict_fetch": True,
|
||||
+ }
|
||||
+
|
||||
+ popen_mock = MagicMock(spec=pass_.Popen)
|
||||
+ popen_mock.return_value.communicate.return_value = ("password123456\n", "")
|
||||
+ popen_mock.return_value.returncode = 0
|
||||
+
|
||||
+ mocks = {
|
||||
+ "Popen": popen_mock,
|
||||
+ }
|
||||
+
|
||||
+ pass_path = "pass:secret"
|
||||
+ expected = "password123456"
|
||||
+ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks):
|
||||
+ result = pass_.render(pass_path)
|
||||
+
|
||||
+ assert result == expected
|
||||
+
|
||||
+
|
||||
+# Fail to fetch a secret in the strict mode.
|
||||
+def test_strict_fetch_fail():
|
||||
+ config = {
|
||||
+ "pass_variable_prefix": "pass:",
|
||||
+ "pass_strict_fetch": True,
|
||||
+ }
|
||||
+
|
||||
+ popen_mock = MagicMock(spec=pass_.Popen)
|
||||
+ popen_mock.return_value.communicate.return_value = ("", "Secret not found")
|
||||
+ popen_mock.return_value.returncode = 1
|
||||
+
|
||||
+ mocks = {
|
||||
+ "Popen": popen_mock,
|
||||
+ }
|
||||
+
|
||||
+ pass_path = "pass:secret"
|
||||
+ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks):
|
||||
+ with pytest.raises(salt.exceptions.SaltRenderError):
|
||||
+ pass_.render(pass_path)
|
||||
+
|
||||
+
|
||||
+# Passthrough a value that doesn't have a pass prefix.
|
||||
+def test_strict_fetch_passthrough():
|
||||
+ config = {
|
||||
+ "pass_variable_prefix": "pass:",
|
||||
+ "pass_strict_fetch": True,
|
||||
+ }
|
||||
+
|
||||
+ pass_path = "variable-without-pass-prefix\n"
|
||||
+ expected = pass_path
|
||||
+ with patch.dict(pass_.__opts__, config):
|
||||
+ result = pass_.render(pass_path)
|
||||
+
|
||||
+ assert result == expected
|
||||
+
|
||||
+
|
||||
+# Fetch a secret in the strict mode. The pass path contains spaces.
|
||||
+def test_strict_fetch_pass_path_with_spaces():
|
||||
+ config = {
|
||||
+ "pass_variable_prefix": "pass:",
|
||||
+ "pass_strict_fetch": True,
|
||||
+ }
|
||||
+
|
||||
+ popen_mock = MagicMock(spec=pass_.Popen)
|
||||
+ popen_mock.return_value.communicate.return_value = ("password123456\n", "")
|
||||
+ popen_mock.return_value.returncode = 0
|
||||
+
|
||||
+ mocks = {
|
||||
+ "Popen": popen_mock,
|
||||
+ }
|
||||
+
|
||||
+ pass_path = "pass:se cr et"
|
||||
+ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks):
|
||||
+ pass_.render(pass_path)
|
||||
+
|
||||
+ call_args, call_kwargs = popen_mock.call_args_list[0]
|
||||
+ assert call_args[0] == ["/usr/bin/pass", "show", "se cr et"]
|
||||
+
|
||||
+
|
||||
+# Fetch a secret in the strict mode. The secret contains leading and trailing whitespaces.
|
||||
+def test_strict_fetch_secret_with_whitespaces():
|
||||
+ config = {
|
||||
+ "pass_variable_prefix": "pass:",
|
||||
+ "pass_strict_fetch": True,
|
||||
+ }
|
||||
+
|
||||
+ popen_mock = MagicMock(spec=pass_.Popen)
|
||||
+ popen_mock.return_value.communicate.return_value = (" \tpassword123456\t \r\n", "")
|
||||
+ popen_mock.return_value.returncode = 0
|
||||
+
|
||||
+ mocks = {
|
||||
+ "Popen": popen_mock,
|
||||
+ }
|
||||
+
|
||||
+ pass_path = "pass:secret"
|
||||
+ expected = " \tpassword123456\t " # only the trailing newlines get striped
|
||||
+ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks):
|
||||
+ result = pass_.render(pass_path)
|
||||
+
|
||||
+ assert result == expected
|
||||
+
|
||||
+
|
||||
+# Test setting env variables based on config values:
|
||||
+# - pass_gnupghome -> GNUPGHOME
|
||||
+# - pass_dir -> PASSWORD_STORE_DIR
|
||||
+def test_env():
|
||||
+ config = {
|
||||
+ "pass_variable_prefix": "pass:",
|
||||
+ "pass_strict_fetch": True,
|
||||
+ "pass_gnupghome": "/path/to/gnupghome",
|
||||
+ "pass_dir": "/path/to/secretstore",
|
||||
+ }
|
||||
+
|
||||
+ popen_mock = MagicMock(spec=pass_.Popen)
|
||||
+ popen_mock.return_value.communicate.return_value = ("password123456\n", "")
|
||||
+ popen_mock.return_value.returncode = 0
|
||||
+
|
||||
+ mocks = {
|
||||
+ "Popen": popen_mock,
|
||||
+ }
|
||||
+
|
||||
+ pass_path = "pass:secret"
|
||||
+ expected = " \tpassword123456\t " # only the trailing newlines get striped
|
||||
+ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks):
|
||||
+ result = pass_.render(pass_path)
|
||||
+
|
||||
+ call_args, call_kwargs = popen_mock.call_args_list[0]
|
||||
+ assert call_kwargs["env"]["GNUPGHOME"] == config["pass_gnupghome"]
|
||||
+ assert call_kwargs["env"]["PASSWORD_STORE_DIR"] == config["pass_dir"]
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,30 +0,0 @@
|
||||
From 03ce925098fb96ad2f2f4b7d4c151ef63aede75f Mon Sep 17 00:00:00 2001
|
||||
From: Witek Bedyk <wbedyk@suse.com>
|
||||
Date: Thu, 19 May 2022 12:52:12 +0200
|
||||
Subject: [PATCH] Make sure SaltCacheLoader use correct fileclient (#519)
|
||||
|
||||
Backported from https://github.com/saltstack/salt/pull/61895
|
||||
|
||||
Signed-off-by: Witek Bedyk <witold.bedyk@suse.com>
|
||||
---
|
||||
salt/state.py | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/salt/state.py b/salt/state.py
|
||||
index db228228a7..316dcdec63 100644
|
||||
--- a/salt/state.py
|
||||
+++ b/salt/state.py
|
||||
@@ -4170,6 +4170,9 @@ class BaseHighState:
|
||||
)
|
||||
else:
|
||||
try:
|
||||
+ # Make sure SaltCacheLoader use correct fileclient
|
||||
+ if context is None:
|
||||
+ context = {"fileclient": self.client}
|
||||
state = compile_template(
|
||||
fn_,
|
||||
self.state.rend,
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,296 +0,0 @@
|
||||
From f2dc43cf1db3fee41e328c68545ccac2576021ca Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Mon, 27 Jun 2022 18:01:21 +0300
|
||||
Subject: [PATCH] Normalize package names once with pkg.installed/removed
|
||||
using yum (bsc#1195895)
|
||||
|
||||
* Normalize the package names only once on install/remove
|
||||
|
||||
* Add test for checking pkg.installed/removed with only normalisation
|
||||
|
||||
* Fix split_arch conditions
|
||||
|
||||
* Fix test_pkg
|
||||
---
|
||||
salt/modules/yumpkg.py | 18 ++-
|
||||
salt/states/pkg.py | 3 +-
|
||||
tests/pytests/unit/states/test_pkg.py | 177 +++++++++++++++++++++++++-
|
||||
3 files changed, 192 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py
|
||||
index 46f0b1f613..f52e084346 100644
|
||||
--- a/salt/modules/yumpkg.py
|
||||
+++ b/salt/modules/yumpkg.py
|
||||
@@ -1460,7 +1460,12 @@ def install(
|
||||
|
||||
try:
|
||||
pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"](
|
||||
- name, pkgs, sources, saltenv=saltenv, normalize=normalize, **kwargs
|
||||
+ name,
|
||||
+ pkgs,
|
||||
+ sources,
|
||||
+ saltenv=saltenv,
|
||||
+ normalize=normalize and kwargs.get("split_arch", True),
|
||||
+ **kwargs
|
||||
)
|
||||
except MinionError as exc:
|
||||
raise CommandExecutionError(exc)
|
||||
@@ -1612,7 +1617,10 @@ def install(
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
- if archpart in salt.utils.pkg.rpm.ARCHES:
|
||||
+ if archpart in salt.utils.pkg.rpm.ARCHES and (
|
||||
+ archpart != __grains__["osarch"]
|
||||
+ or kwargs.get("split_arch", True)
|
||||
+ ):
|
||||
arch = "." + archpart
|
||||
pkgname = namepart
|
||||
|
||||
@@ -2143,11 +2151,13 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613
|
||||
arch = ""
|
||||
pkgname = target
|
||||
try:
|
||||
- namepart, archpart = target.rsplit(".", 1)
|
||||
+ namepart, archpart = pkgname.rsplit(".", 1)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
- if archpart in salt.utils.pkg.rpm.ARCHES:
|
||||
+ if archpart in salt.utils.pkg.rpm.ARCHES and (
|
||||
+ archpart != __grains__["osarch"] or kwargs.get("split_arch", True)
|
||||
+ ):
|
||||
arch = "." + archpart
|
||||
pkgname = namepart
|
||||
# Since we don't always have the arch info, epoch information has to parsed out. But
|
||||
diff --git a/salt/states/pkg.py b/salt/states/pkg.py
|
||||
index ef4e062145..cda966a1e8 100644
|
||||
--- a/salt/states/pkg.py
|
||||
+++ b/salt/states/pkg.py
|
||||
@@ -1873,6 +1873,7 @@ def installed(
|
||||
normalize=normalize,
|
||||
update_holds=update_holds,
|
||||
ignore_epoch=ignore_epoch,
|
||||
+ split_arch=False,
|
||||
**kwargs
|
||||
)
|
||||
except CommandExecutionError as exc:
|
||||
@@ -2940,7 +2941,7 @@ def _uninstall(
|
||||
}
|
||||
|
||||
changes = __salt__["pkg.{}".format(action)](
|
||||
- name, pkgs=pkgs, version=version, **kwargs
|
||||
+ name, pkgs=pkgs, version=version, split_arch=False, **kwargs
|
||||
)
|
||||
new = __salt__["pkg.list_pkgs"](versions_as_list=True, **kwargs)
|
||||
failed = []
|
||||
diff --git a/tests/pytests/unit/states/test_pkg.py b/tests/pytests/unit/states/test_pkg.py
|
||||
index cba8201bda..ecb841e8ec 100644
|
||||
--- a/tests/pytests/unit/states/test_pkg.py
|
||||
+++ b/tests/pytests/unit/states/test_pkg.py
|
||||
@@ -2,6 +2,8 @@ import logging
|
||||
|
||||
import pytest
|
||||
import salt.modules.beacons as beaconmod
|
||||
+import salt.modules.pkg_resource as pkg_resource
|
||||
+import salt.modules.yumpkg as yumpkg
|
||||
import salt.states.beacon as beaconstate
|
||||
import salt.states.pkg as pkg
|
||||
import salt.utils.state as state_utils
|
||||
@@ -17,7 +19,7 @@ def configure_loader_modules():
|
||||
pkg: {
|
||||
"__env__": "base",
|
||||
"__salt__": {},
|
||||
- "__grains__": {"os": "CentOS"},
|
||||
+ "__grains__": {"os": "CentOS", "os_family": "RedHat"},
|
||||
"__opts__": {"test": False, "cachedir": ""},
|
||||
"__instance_id__": "",
|
||||
"__low__": {},
|
||||
@@ -25,6 +27,15 @@ def configure_loader_modules():
|
||||
},
|
||||
beaconstate: {"__salt__": {}, "__opts__": {}},
|
||||
beaconmod: {"__salt__": {}, "__opts__": {}},
|
||||
+ pkg_resource: {
|
||||
+ "__salt__": {},
|
||||
+ "__grains__": {"os": "CentOS", "os_family": "RedHat"},
|
||||
+ },
|
||||
+ yumpkg: {
|
||||
+ "__salt__": {},
|
||||
+ "__grains__": {"osarch": "x86_64", "osmajorrelease": 7},
|
||||
+ "__opts__": {},
|
||||
+ },
|
||||
}
|
||||
|
||||
|
||||
@@ -726,3 +737,167 @@ def test_held_unheld(package_manager):
|
||||
hold_mock.assert_not_called()
|
||||
unhold_mock.assert_any_call(name="held-test", pkgs=["baz"])
|
||||
unhold_mock.assert_any_call(name="held-test", pkgs=["bar"])
|
||||
+
|
||||
+
|
||||
+def test_installed_with_single_normalize():
|
||||
+ """
|
||||
+ Test pkg.installed with preventing multiple package name normalisation
|
||||
+ """
|
||||
+
|
||||
+ list_no_weird_installed = {
|
||||
+ "pkga": "1.0.1",
|
||||
+ "pkgb": "1.0.2",
|
||||
+ "pkgc": "1.0.3",
|
||||
+ }
|
||||
+ list_no_weird_installed_ver_list = {
|
||||
+ "pkga": ["1.0.1"],
|
||||
+ "pkgb": ["1.0.2"],
|
||||
+ "pkgc": ["1.0.3"],
|
||||
+ }
|
||||
+ list_with_weird_installed = {
|
||||
+ "pkga": "1.0.1",
|
||||
+ "pkgb": "1.0.2",
|
||||
+ "pkgc": "1.0.3",
|
||||
+ "weird-name-1.2.3-1234.5.6.test7tst.x86_64": "20220214-2.1",
|
||||
+ }
|
||||
+ list_with_weird_installed_ver_list = {
|
||||
+ "pkga": ["1.0.1"],
|
||||
+ "pkgb": ["1.0.2"],
|
||||
+ "pkgc": ["1.0.3"],
|
||||
+ "weird-name-1.2.3-1234.5.6.test7tst.x86_64": ["20220214-2.1"],
|
||||
+ }
|
||||
+ list_pkgs = MagicMock(
|
||||
+ side_effect=[
|
||||
+ # For the package with version specified
|
||||
+ list_no_weird_installed_ver_list,
|
||||
+ {},
|
||||
+ list_no_weird_installed,
|
||||
+ list_no_weird_installed_ver_list,
|
||||
+ list_with_weird_installed,
|
||||
+ list_with_weird_installed_ver_list,
|
||||
+ # For the package with no version specified
|
||||
+ list_no_weird_installed_ver_list,
|
||||
+ {},
|
||||
+ list_no_weird_installed,
|
||||
+ list_no_weird_installed_ver_list,
|
||||
+ list_with_weird_installed,
|
||||
+ list_with_weird_installed_ver_list,
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ salt_dict = {
|
||||
+ "pkg.install": yumpkg.install,
|
||||
+ "pkg.list_pkgs": list_pkgs,
|
||||
+ "pkg.normalize_name": yumpkg.normalize_name,
|
||||
+ "pkg_resource.version_clean": pkg_resource.version_clean,
|
||||
+ "pkg_resource.parse_targets": pkg_resource.parse_targets,
|
||||
+ }
|
||||
+
|
||||
+ with patch("salt.modules.yumpkg.list_pkgs", list_pkgs), patch(
|
||||
+ "salt.modules.yumpkg.version_cmp", MagicMock(return_value=0)
|
||||
+ ), patch(
|
||||
+ "salt.modules.yumpkg._call_yum", MagicMock(return_value={"retcode": 0})
|
||||
+ ) as call_yum_mock, patch.dict(
|
||||
+ pkg.__salt__, salt_dict
|
||||
+ ), patch.dict(
|
||||
+ pkg_resource.__salt__, salt_dict
|
||||
+ ), patch.dict(
|
||||
+ yumpkg.__salt__, salt_dict
|
||||
+ ), patch.dict(
|
||||
+ yumpkg.__grains__, {"os": "CentOS", "osarch": "x86_64", "osmajorrelease": 7}
|
||||
+ ), patch.object(
|
||||
+ yumpkg, "list_holds", MagicMock()
|
||||
+ ):
|
||||
+
|
||||
+ expected = {
|
||||
+ "weird-name-1.2.3-1234.5.6.test7tst.x86_64": {
|
||||
+ "old": "",
|
||||
+ "new": "20220214-2.1",
|
||||
+ }
|
||||
+ }
|
||||
+ ret = pkg.installed(
|
||||
+ "test_install",
|
||||
+ pkgs=[{"weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch": "20220214-2.1"}],
|
||||
+ )
|
||||
+ call_yum_mock.assert_called_once()
|
||||
+ assert (
|
||||
+ call_yum_mock.mock_calls[0].args[0][2]
|
||||
+ == "weird-name-1.2.3-1234.5.6.test7tst.x86_64-20220214-2.1"
|
||||
+ )
|
||||
+ assert ret["result"]
|
||||
+ assert ret["changes"] == expected
|
||||
+
|
||||
+
|
||||
+def test_removed_with_single_normalize():
|
||||
+ """
|
||||
+ Test pkg.removed with preventing multiple package name normalisation
|
||||
+ """
|
||||
+
|
||||
+ list_no_weird_installed = {
|
||||
+ "pkga": "1.0.1",
|
||||
+ "pkgb": "1.0.2",
|
||||
+ "pkgc": "1.0.3",
|
||||
+ }
|
||||
+ list_no_weird_installed_ver_list = {
|
||||
+ "pkga": ["1.0.1"],
|
||||
+ "pkgb": ["1.0.2"],
|
||||
+ "pkgc": ["1.0.3"],
|
||||
+ }
|
||||
+ list_with_weird_installed = {
|
||||
+ "pkga": "1.0.1",
|
||||
+ "pkgb": "1.0.2",
|
||||
+ "pkgc": "1.0.3",
|
||||
+ "weird-name-1.2.3-1234.5.6.test7tst.x86_64": "20220214-2.1",
|
||||
+ }
|
||||
+ list_with_weird_installed_ver_list = {
|
||||
+ "pkga": ["1.0.1"],
|
||||
+ "pkgb": ["1.0.2"],
|
||||
+ "pkgc": ["1.0.3"],
|
||||
+ "weird-name-1.2.3-1234.5.6.test7tst.x86_64": ["20220214-2.1"],
|
||||
+ }
|
||||
+ list_pkgs = MagicMock(
|
||||
+ side_effect=[
|
||||
+ list_with_weird_installed_ver_list,
|
||||
+ list_with_weird_installed,
|
||||
+ list_no_weird_installed,
|
||||
+ list_no_weird_installed_ver_list,
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+ salt_dict = {
|
||||
+ "pkg.remove": yumpkg.remove,
|
||||
+ "pkg.list_pkgs": list_pkgs,
|
||||
+ "pkg.normalize_name": yumpkg.normalize_name,
|
||||
+ "pkg_resource.parse_targets": pkg_resource.parse_targets,
|
||||
+ "pkg_resource.version_clean": pkg_resource.version_clean,
|
||||
+ }
|
||||
+
|
||||
+ with patch("salt.modules.yumpkg.list_pkgs", list_pkgs), patch(
|
||||
+ "salt.modules.yumpkg.version_cmp", MagicMock(return_value=0)
|
||||
+ ), patch(
|
||||
+ "salt.modules.yumpkg._call_yum", MagicMock(return_value={"retcode": 0})
|
||||
+ ) as call_yum_mock, patch.dict(
|
||||
+ pkg.__salt__, salt_dict
|
||||
+ ), patch.dict(
|
||||
+ pkg_resource.__salt__, salt_dict
|
||||
+ ), patch.dict(
|
||||
+ yumpkg.__salt__, salt_dict
|
||||
+ ):
|
||||
+
|
||||
+ expected = {
|
||||
+ "weird-name-1.2.3-1234.5.6.test7tst.x86_64": {
|
||||
+ "old": "20220214-2.1",
|
||||
+ "new": "",
|
||||
+ }
|
||||
+ }
|
||||
+ ret = pkg.removed(
|
||||
+ "test_remove",
|
||||
+ pkgs=[{"weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch": "20220214-2.1"}],
|
||||
+ )
|
||||
+ call_yum_mock.assert_called_once()
|
||||
+ assert (
|
||||
+ call_yum_mock.mock_calls[0].args[0][2]
|
||||
+ == "weird-name-1.2.3-1234.5.6.test7tst.x86_64-20220214-2.1"
|
||||
+ )
|
||||
+ assert ret["result"]
|
||||
+ assert ret["changes"] == expected
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,297 +0,0 @@
|
||||
From 4a9ec335e7da2f0e3314580e43075bb69fe90c38 Mon Sep 17 00:00:00 2001
|
||||
From: Witek Bedyk <witold.bedyk@suse.com>
|
||||
Date: Mon, 29 Aug 2022 14:16:00 +0200
|
||||
Subject: [PATCH] Retry if RPM lock is temporarily unavailable (#547)
|
||||
|
||||
* Retry if RPM lock is temporarily unavailable
|
||||
|
||||
Backported from saltstack/salt#62204
|
||||
|
||||
Signed-off-by: Witek Bedyk <witold.bedyk@suse.com>
|
||||
|
||||
* Sync formating fixes from upstream
|
||||
|
||||
Signed-off-by: Witek Bedyk <witold.bedyk@suse.com>
|
||||
---
|
||||
changelog/62204.fixed | 1 +
|
||||
salt/modules/zypperpkg.py | 117 +++++++++++++++++----------
|
||||
tests/unit/modules/test_zypperpkg.py | 45 ++++++++++-
|
||||
3 files changed, 115 insertions(+), 48 deletions(-)
|
||||
create mode 100644 changelog/62204.fixed
|
||||
|
||||
diff --git a/changelog/62204.fixed b/changelog/62204.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..59f1914593
|
||||
--- /dev/null
|
||||
+++ b/changelog/62204.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Fixed Zypper module failing on RPM lock file being temporarily unavailable.
|
||||
diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py
|
||||
index 2c36e2968a..c787d4009d 100644
|
||||
--- a/salt/modules/zypperpkg.py
|
||||
+++ b/salt/modules/zypperpkg.py
|
||||
@@ -14,6 +14,7 @@ Package support for openSUSE via the zypper package manager
|
||||
|
||||
import configparser
|
||||
import datetime
|
||||
+import errno
|
||||
import fnmatch
|
||||
import logging
|
||||
import os
|
||||
@@ -39,6 +40,9 @@ from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationEr
|
||||
# pylint: disable=import-error,redefined-builtin,no-name-in-module
|
||||
from salt.utils.versions import LooseVersion
|
||||
|
||||
+if salt.utils.files.is_fcntl_available():
|
||||
+ import fcntl
|
||||
+
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
HAS_ZYPP = False
|
||||
@@ -106,6 +110,7 @@ class _Zypper:
|
||||
XML_DIRECTIVES = ["-x", "--xmlout"]
|
||||
# ZYPPER_LOCK is not affected by --root
|
||||
ZYPPER_LOCK = "/var/run/zypp.pid"
|
||||
+ RPM_LOCK = "/var/lib/rpm/.rpm.lock"
|
||||
TAG_RELEASED = "zypper/released"
|
||||
TAG_BLOCKED = "zypper/blocked"
|
||||
|
||||
@@ -276,7 +281,7 @@ class _Zypper:
|
||||
and self.exit_code not in self.WARNING_EXIT_CODES
|
||||
)
|
||||
|
||||
- def _is_lock(self):
|
||||
+ def _is_zypper_lock(self):
|
||||
"""
|
||||
Is this is a lock error code?
|
||||
|
||||
@@ -284,6 +289,23 @@ class _Zypper:
|
||||
"""
|
||||
return self.exit_code == self.LOCK_EXIT_CODE
|
||||
|
||||
+ def _is_rpm_lock(self):
|
||||
+ """
|
||||
+ Is this an RPM lock error?
|
||||
+ """
|
||||
+ if salt.utils.files.is_fcntl_available():
|
||||
+ if self.exit_code > 0 and os.path.exists(self.RPM_LOCK):
|
||||
+ with salt.utils.files.fopen(self.RPM_LOCK, mode="w+") as rfh:
|
||||
+ try:
|
||||
+ fcntl.lockf(rfh, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
+ except OSError as err:
|
||||
+ if err.errno == errno.EAGAIN:
|
||||
+ return True
|
||||
+ else:
|
||||
+ fcntl.lockf(rfh, fcntl.LOCK_UN)
|
||||
+
|
||||
+ return False
|
||||
+
|
||||
def _is_xml_mode(self):
|
||||
"""
|
||||
Is Zypper's output is in XML format?
|
||||
@@ -306,7 +328,7 @@ class _Zypper:
|
||||
raise CommandExecutionError("No output result from Zypper?")
|
||||
|
||||
self.exit_code = self.__call_result["retcode"]
|
||||
- if self._is_lock():
|
||||
+ if self._is_zypper_lock() or self._is_rpm_lock():
|
||||
return False
|
||||
|
||||
if self._is_error():
|
||||
@@ -387,48 +409,11 @@ class _Zypper:
|
||||
if self._check_result():
|
||||
break
|
||||
|
||||
- if os.path.exists(self.ZYPPER_LOCK):
|
||||
- try:
|
||||
- with salt.utils.files.fopen(self.ZYPPER_LOCK) as rfh:
|
||||
- data = __salt__["ps.proc_info"](
|
||||
- int(rfh.readline()),
|
||||
- attrs=["pid", "name", "cmdline", "create_time"],
|
||||
- )
|
||||
- data["cmdline"] = " ".join(data["cmdline"])
|
||||
- data["info"] = "Blocking process created at {}.".format(
|
||||
- datetime.datetime.utcfromtimestamp(
|
||||
- data["create_time"]
|
||||
- ).isoformat()
|
||||
- )
|
||||
- data["success"] = True
|
||||
- except Exception as err: # pylint: disable=broad-except
|
||||
- data = {
|
||||
- "info": (
|
||||
- "Unable to retrieve information about blocking process: {}".format(
|
||||
- err.message
|
||||
- )
|
||||
- ),
|
||||
- "success": False,
|
||||
- }
|
||||
- else:
|
||||
- data = {
|
||||
- "info": "Zypper is locked, but no Zypper lock has been found.",
|
||||
- "success": False,
|
||||
- }
|
||||
-
|
||||
- if not data["success"]:
|
||||
- log.debug("Unable to collect data about blocking process.")
|
||||
- else:
|
||||
- log.debug("Collected data about blocking process.")
|
||||
-
|
||||
- __salt__["event.fire_master"](data, self.TAG_BLOCKED)
|
||||
- log.debug(
|
||||
- "Fired a Zypper blocked event to the master with the data: %s", data
|
||||
- )
|
||||
- log.debug("Waiting 5 seconds for Zypper gets released...")
|
||||
- time.sleep(5)
|
||||
- if not was_blocked:
|
||||
- was_blocked = True
|
||||
+ if self._is_zypper_lock():
|
||||
+ self._handle_zypper_lock_file()
|
||||
+ if self._is_rpm_lock():
|
||||
+ self._handle_rpm_lock_file()
|
||||
+ was_blocked = True
|
||||
|
||||
if was_blocked:
|
||||
__salt__["event.fire_master"](
|
||||
@@ -451,6 +436,50 @@ class _Zypper:
|
||||
or self.__call_result["stdout"]
|
||||
)
|
||||
|
||||
+ def _handle_zypper_lock_file(self):
|
||||
+ if os.path.exists(self.ZYPPER_LOCK):
|
||||
+ try:
|
||||
+ with salt.utils.files.fopen(self.ZYPPER_LOCK) as rfh:
|
||||
+ data = __salt__["ps.proc_info"](
|
||||
+ int(rfh.readline()),
|
||||
+ attrs=["pid", "name", "cmdline", "create_time"],
|
||||
+ )
|
||||
+ data["cmdline"] = " ".join(data["cmdline"])
|
||||
+ data["info"] = "Blocking process created at {}.".format(
|
||||
+ datetime.datetime.utcfromtimestamp(
|
||||
+ data["create_time"]
|
||||
+ ).isoformat()
|
||||
+ )
|
||||
+ data["success"] = True
|
||||
+ except Exception as err: # pylint: disable=broad-except
|
||||
+ data = {
|
||||
+ "info": (
|
||||
+ "Unable to retrieve information about "
|
||||
+ "blocking process: {}".format(err)
|
||||
+ ),
|
||||
+ "success": False,
|
||||
+ }
|
||||
+ else:
|
||||
+ data = {
|
||||
+ "info": "Zypper is locked, but no Zypper lock has been found.",
|
||||
+ "success": False,
|
||||
+ }
|
||||
+ if not data["success"]:
|
||||
+ log.debug("Unable to collect data about blocking process.")
|
||||
+ else:
|
||||
+ log.debug("Collected data about blocking process.")
|
||||
+ __salt__["event.fire_master"](data, self.TAG_BLOCKED)
|
||||
+ log.debug("Fired a Zypper blocked event to the master with the data: %s", data)
|
||||
+ log.debug("Waiting 5 seconds for Zypper gets released...")
|
||||
+ time.sleep(5)
|
||||
+
|
||||
+ def _handle_rpm_lock_file(self):
|
||||
+ data = {"info": "RPM is temporarily locked.", "success": True}
|
||||
+ __salt__["event.fire_master"](data, self.TAG_BLOCKED)
|
||||
+ log.debug("Fired an RPM blocked event to the master with the data: %s", data)
|
||||
+ log.debug("Waiting 5 seconds for RPM to get released...")
|
||||
+ time.sleep(5)
|
||||
+
|
||||
|
||||
__zypper__ = _Zypper()
|
||||
|
||||
diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py
|
||||
index 3f1560a385..37d555844c 100644
|
||||
--- a/tests/unit/modules/test_zypperpkg.py
|
||||
+++ b/tests/unit/modules/test_zypperpkg.py
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
|
||||
import configparser
|
||||
+import errno
|
||||
import io
|
||||
import os
|
||||
from xml.dom import minidom
|
||||
@@ -97,7 +98,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin):
|
||||
}
|
||||
with patch.dict(
|
||||
zypper.__salt__, {"cmd.run_all": MagicMock(return_value=ref_out)}
|
||||
- ):
|
||||
+ ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False):
|
||||
upgrades = zypper.list_upgrades(refresh=False)
|
||||
self.assertEqual(len(upgrades), 3)
|
||||
for pkg, version in {
|
||||
@@ -198,7 +199,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin):
|
||||
' type="error">Booya!</message></stream>'
|
||||
)
|
||||
sniffer = RunSniffer(stdout=stdout_xml_snippet, retcode=1)
|
||||
- with patch.dict("salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer}):
|
||||
+ with patch.dict(
|
||||
+ "salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer}
|
||||
+ ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False):
|
||||
with self.assertRaisesRegex(
|
||||
CommandExecutionError, "^Zypper command failure: Booya!$"
|
||||
):
|
||||
@@ -232,7 +235,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin):
|
||||
with patch.dict(
|
||||
"salt.modules.zypperpkg.__salt__",
|
||||
{"cmd.run_all": MagicMock(return_value=ref_out)},
|
||||
- ):
|
||||
+ ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False):
|
||||
with self.assertRaisesRegex(
|
||||
CommandExecutionError,
|
||||
"^Zypper command failure: Some handled zypper internal error{}Another"
|
||||
@@ -245,7 +248,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin):
|
||||
with patch.dict(
|
||||
"salt.modules.zypperpkg.__salt__",
|
||||
{"cmd.run_all": MagicMock(return_value=ref_out)},
|
||||
- ):
|
||||
+ ), patch.object(zypper.__zypper__, "_is_rpm_lock", return_value=False):
|
||||
with self.assertRaisesRegex(
|
||||
CommandExecutionError, "^Zypper command failure: Check Zypper's logs.$"
|
||||
):
|
||||
@@ -2064,3 +2067,37 @@ pattern() = package-c"""
|
||||
python_shell=False,
|
||||
env={"ZYPP_READONLY_HACK": "1"},
|
||||
)
|
||||
+
|
||||
+ def test_is_rpm_lock_no_error(self):
|
||||
+ with patch.object(os.path, "exists", return_value=True):
|
||||
+ self.assertFalse(zypper.__zypper__._is_rpm_lock())
|
||||
+
|
||||
+ def test_rpm_lock_does_not_exist(self):
|
||||
+ if salt.utils.files.is_fcntl_available():
|
||||
+ zypper.__zypper__.exit_code = 1
|
||||
+ with patch.object(
|
||||
+ os.path, "exists", return_value=False
|
||||
+ ) as mock_path_exists:
|
||||
+ self.assertFalse(zypper.__zypper__._is_rpm_lock())
|
||||
+ mock_path_exists.assert_called_with(zypper.__zypper__.RPM_LOCK)
|
||||
+ zypper.__zypper__._reset()
|
||||
+
|
||||
+ def test_rpm_lock_acquirable(self):
|
||||
+ if salt.utils.files.is_fcntl_available():
|
||||
+ zypper.__zypper__.exit_code = 1
|
||||
+ with patch.object(os.path, "exists", return_value=True), patch(
|
||||
+ "fcntl.lockf", side_effect=OSError(errno.EAGAIN, "")
|
||||
+ ) as lockf_mock, patch("salt.utils.files.fopen", mock_open()):
|
||||
+ self.assertTrue(zypper.__zypper__._is_rpm_lock())
|
||||
+ lockf_mock.assert_called()
|
||||
+ zypper.__zypper__._reset()
|
||||
+
|
||||
+ def test_rpm_lock_not_acquirable(self):
|
||||
+ if salt.utils.files.is_fcntl_available():
|
||||
+ zypper.__zypper__.exit_code = 1
|
||||
+ with patch.object(os.path, "exists", return_value=True), patch(
|
||||
+ "fcntl.lockf"
|
||||
+ ) as lockf_mock, patch("salt.utils.files.fopen", mock_open()):
|
||||
+ self.assertFalse(zypper.__zypper__._is_rpm_lock())
|
||||
+ self.assertEqual(lockf_mock.call_count, 2)
|
||||
+ zypper.__zypper__._reset()
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,87 +0,0 @@
|
||||
From d561491c48ee30472e0d4699ba389648ef0d863a Mon Sep 17 00:00:00 2001
|
||||
From: Victor Zhestkov <vzhestkov@suse.com>
|
||||
Date: Mon, 27 Jun 2022 18:02:31 +0300
|
||||
Subject: [PATCH] Set default target for pip from VENV_PIP_TARGET
|
||||
environment variable
|
||||
|
||||
* Use VENV_PIP_TARGET as a target for pkg.install
|
||||
|
||||
if set and no target specified on the call
|
||||
|
||||
* Add test for VENV_PIP_TARGET environment variable
|
||||
|
||||
* Changelog entry
|
||||
---
|
||||
changelog/62089.changed | 1 +
|
||||
salt/modules/pip.py | 6 +++++
|
||||
tests/pytests/unit/modules/test_pip.py | 31 ++++++++++++++++++++++++++
|
||||
3 files changed, 38 insertions(+)
|
||||
create mode 100644 changelog/62089.changed
|
||||
|
||||
diff --git a/changelog/62089.changed b/changelog/62089.changed
|
||||
new file mode 100644
|
||||
index 0000000000..09feb2e922
|
||||
--- /dev/null
|
||||
+++ b/changelog/62089.changed
|
||||
@@ -0,0 +1 @@
|
||||
+Use VENV_PIP_TARGET environment variable as a default target for pip if present.
|
||||
diff --git a/salt/modules/pip.py b/salt/modules/pip.py
|
||||
index da26416662..9410024fd5 100644
|
||||
--- a/salt/modules/pip.py
|
||||
+++ b/salt/modules/pip.py
|
||||
@@ -858,6 +858,12 @@ def install(
|
||||
if build:
|
||||
cmd.extend(["--build", build])
|
||||
|
||||
+ # Use VENV_PIP_TARGET environment variable value as target
|
||||
+ # if set and no target specified on the function call
|
||||
+ target_env = os.environ.get("VENV_PIP_TARGET", None)
|
||||
+ if target is None and target_env is not None:
|
||||
+ target = target_env
|
||||
+
|
||||
if target:
|
||||
cmd.extend(["--target", target])
|
||||
|
||||
diff --git a/tests/pytests/unit/modules/test_pip.py b/tests/pytests/unit/modules/test_pip.py
|
||||
index 405ec6c82e..ae9005d806 100644
|
||||
--- a/tests/pytests/unit/modules/test_pip.py
|
||||
+++ b/tests/pytests/unit/modules/test_pip.py
|
||||
@@ -1773,3 +1773,34 @@ def test_when_version_is_called_with_a_user_it_should_be_passed_to_undelying_run
|
||||
cwd=None,
|
||||
python_shell=False,
|
||||
)
|
||||
+
|
||||
+
|
||||
+def test_install_target_from_VENV_PIP_TARGET_in_resulting_command():
|
||||
+ pkg = "pep8"
|
||||
+ target = "/tmp/foo"
|
||||
+ target_env = "/tmp/bar"
|
||||
+ mock = MagicMock(return_value={"retcode": 0, "stdout": ""})
|
||||
+ environment = os.environ.copy()
|
||||
+ environment["VENV_PIP_TARGET"] = target_env
|
||||
+ with patch.dict(pip.__salt__, {"cmd.run_all": mock}), patch.object(
|
||||
+ os, "environ", environment
|
||||
+ ):
|
||||
+ pip.install(pkg)
|
||||
+ expected = [sys.executable, "-m", "pip", "install", "--target", target_env, pkg]
|
||||
+ mock.assert_called_with(
|
||||
+ expected,
|
||||
+ saltenv="base",
|
||||
+ runas=None,
|
||||
+ use_vt=False,
|
||||
+ python_shell=False,
|
||||
+ )
|
||||
+ mock.reset_mock()
|
||||
+ pip.install(pkg, target=target)
|
||||
+ expected = [sys.executable, "-m", "pip", "install", "--target", target, pkg]
|
||||
+ mock.assert_called_with(
|
||||
+ expected,
|
||||
+ saltenv="base",
|
||||
+ runas=None,
|
||||
+ use_vt=False,
|
||||
+ python_shell=False,
|
||||
+ )
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,362 +0,0 @@
|
||||
From cba6455bd0480bfb80c466a2b34a702a9afb5bd5 Mon Sep 17 00:00:00 2001
|
||||
From: Alexander Graul <agraul@suse.com>
|
||||
Date: Tue, 25 Jan 2022 17:20:55 +0100
|
||||
Subject: [PATCH] state.apply: don't check for cached pillar errors
|
||||
|
||||
state.apply request new pillar data from the server. This done to always
|
||||
have the most up-to-date pillar to work with. Previously, checking for
|
||||
pillar errors looked at both the new pillar and the in-memory pillar.
|
||||
The latter might contain pillar rendering errors even if the former does
|
||||
not.
|
||||
|
||||
For this reason, only the new pillar should be checked, not both.
|
||||
---
|
||||
changelog/52354.fixed | 1 +
|
||||
changelog/57180.fixed | 1 +
|
||||
changelog/59339.fixed | 1 +
|
||||
salt/modules/state.py | 17 ++-
|
||||
.../modules/state/test_state_pillar_errors.py | 131 ++++++++++++++++++
|
||||
.../pytests/unit/modules/state/test_state.py | 115 +++++----------
|
||||
6 files changed, 177 insertions(+), 89 deletions(-)
|
||||
create mode 100644 changelog/52354.fixed
|
||||
create mode 100644 changelog/57180.fixed
|
||||
create mode 100644 changelog/59339.fixed
|
||||
create mode 100644 tests/pytests/integration/modules/state/test_state_pillar_errors.py
|
||||
|
||||
diff --git a/changelog/52354.fixed b/changelog/52354.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..af885d77fa
|
||||
--- /dev/null
|
||||
+++ b/changelog/52354.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Don't check for cached pillar errors on state.apply
|
||||
diff --git a/changelog/57180.fixed b/changelog/57180.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..af885d77fa
|
||||
--- /dev/null
|
||||
+++ b/changelog/57180.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Don't check for cached pillar errors on state.apply
|
||||
diff --git a/changelog/59339.fixed b/changelog/59339.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..af885d77fa
|
||||
--- /dev/null
|
||||
+++ b/changelog/59339.fixed
|
||||
@@ -0,0 +1 @@
|
||||
+Don't check for cached pillar errors on state.apply
|
||||
diff --git a/salt/modules/state.py b/salt/modules/state.py
|
||||
index f214291328..c0feabe842 100644
|
||||
--- a/salt/modules/state.py
|
||||
+++ b/salt/modules/state.py
|
||||
@@ -106,18 +106,17 @@ def _set_retcode(ret, highstate=None):
|
||||
|
||||
def _get_pillar_errors(kwargs, pillar=None):
|
||||
"""
|
||||
- Checks all pillars (external and internal) for errors.
|
||||
- Return an error message, if anywhere or None.
|
||||
+ Check pillar for errors.
|
||||
+
|
||||
+ If a pillar is passed, it will be checked. Otherwise, the in-memory pillar
|
||||
+ will checked instead. Passing kwargs['force'] = True short cuts the check
|
||||
+ and always returns None, indicating no errors.
|
||||
|
||||
:param kwargs: dictionary of options
|
||||
- :param pillar: external pillar
|
||||
- :return: None or an error message
|
||||
+ :param pillar: pillar
|
||||
+ :return: None or a list of error messages
|
||||
"""
|
||||
- return (
|
||||
- None
|
||||
- if kwargs.get("force")
|
||||
- else (pillar or {}).get("_errors", __pillar__.get("_errors")) or None
|
||||
- )
|
||||
+ return None if kwargs.get("force") else (pillar or __pillar__).get("_errors")
|
||||
|
||||
|
||||
def _wait(jid):
|
||||
diff --git a/tests/pytests/integration/modules/state/test_state_pillar_errors.py b/tests/pytests/integration/modules/state/test_state_pillar_errors.py
|
||||
new file mode 100644
|
||||
index 0000000000..af65a05945
|
||||
--- /dev/null
|
||||
+++ b/tests/pytests/integration/modules/state/test_state_pillar_errors.py
|
||||
@@ -0,0 +1,131 @@
|
||||
+#!/usr/bin/python3
|
||||
+
|
||||
+import textwrap
|
||||
+
|
||||
+import pytest
|
||||
+from saltfactories.utils.functional import StateResult
|
||||
+
|
||||
+pytestmark = [
|
||||
+ pytest.mark.slow_test,
|
||||
+]
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope="module")
|
||||
+def reset_pillar(salt_call_cli):
|
||||
+ try:
|
||||
+ # Run tests
|
||||
+ yield
|
||||
+ finally:
|
||||
+ # Refresh pillar once all tests are done.
|
||||
+ ret = salt_call_cli.run("saltutil.refresh_pillar", wait=True)
|
||||
+ assert ret.exitcode == 0
|
||||
+ assert ret.json is True
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def testfile_path(tmp_path, base_env_state_tree_root_dir):
|
||||
+ testfile = tmp_path / "testfile"
|
||||
+ sls_contents = textwrap.dedent(
|
||||
+ """
|
||||
+ {}:
|
||||
+ file:
|
||||
+ - managed
|
||||
+ - source: salt://testfile
|
||||
+ - makedirs: true
|
||||
+ """.format(testfile)
|
||||
+ )
|
||||
+ with pytest.helpers.temp_file(
|
||||
+ "sls-id-test.sls", sls_contents, base_env_state_tree_root_dir
|
||||
+ ):
|
||||
+ yield testfile
|
||||
+
|
||||
+
|
||||
+@pytest.mark.usefixtures("testfile_path", "reset_pillar")
|
||||
+def test_state_apply_aborts_on_pillar_error(
|
||||
+ salt_cli,
|
||||
+ salt_minion,
|
||||
+ base_env_pillar_tree_root_dir,
|
||||
+):
|
||||
+ """
|
||||
+ Test state.apply with error in pillar.
|
||||
+ """
|
||||
+ pillar_top_file = textwrap.dedent(
|
||||
+ """
|
||||
+ base:
|
||||
+ '{}':
|
||||
+ - basic
|
||||
+ """
|
||||
+ ).format(salt_minion.id)
|
||||
+ basic_pillar_file = textwrap.dedent(
|
||||
+ """
|
||||
+ syntax_error
|
||||
+ """
|
||||
+ )
|
||||
+
|
||||
+ with pytest.helpers.temp_file(
|
||||
+ "top.sls", pillar_top_file, base_env_pillar_tree_root_dir
|
||||
+ ), pytest.helpers.temp_file(
|
||||
+ "basic.sls", basic_pillar_file, base_env_pillar_tree_root_dir
|
||||
+ ):
|
||||
+ expected_comment = [
|
||||
+ "Pillar failed to render with the following messages:",
|
||||
+ "SLS 'basic' does not render to a dictionary",
|
||||
+ ]
|
||||
+ shell_result = salt_cli.run(
|
||||
+ "state.apply", "sls-id-test", minion_tgt=salt_minion.id
|
||||
+ )
|
||||
+ assert shell_result.exitcode == 1
|
||||
+ assert shell_result.json == expected_comment
|
||||
+
|
||||
+
|
||||
+@pytest.mark.usefixtures("testfile_path", "reset_pillar")
|
||||
+def test_state_apply_continues_after_pillar_error_is_fixed(
|
||||
+ salt_cli,
|
||||
+ salt_minion,
|
||||
+ base_env_pillar_tree_root_dir,
|
||||
+):
|
||||
+ """
|
||||
+ Test state.apply with error in pillar.
|
||||
+ """
|
||||
+ pillar_top_file = textwrap.dedent(
|
||||
+ """
|
||||
+ base:
|
||||
+ '{}':
|
||||
+ - basic
|
||||
+ """.format(salt_minion.id)
|
||||
+ )
|
||||
+ basic_pillar_file_error = textwrap.dedent(
|
||||
+ """
|
||||
+ syntax_error
|
||||
+ """
|
||||
+ )
|
||||
+ basic_pillar_file = textwrap.dedent(
|
||||
+ """
|
||||
+ syntax_error: Fixed!
|
||||
+ """
|
||||
+ )
|
||||
+
|
||||
+ # save pillar render error in minion's in-memory pillar
|
||||
+ with pytest.helpers.temp_file(
|
||||
+ "top.sls", pillar_top_file, base_env_pillar_tree_root_dir
|
||||
+ ), pytest.helpers.temp_file(
|
||||
+ "basic.sls", basic_pillar_file_error, base_env_pillar_tree_root_dir
|
||||
+ ):
|
||||
+ shell_result = salt_cli.run(
|
||||
+ "saltutil.refresh_pillar", minion_tgt=salt_minion.id
|
||||
+ )
|
||||
+ assert shell_result.exitcode == 0
|
||||
+
|
||||
+ # run state.apply with fixed pillar render error
|
||||
+ with pytest.helpers.temp_file(
|
||||
+ "top.sls", pillar_top_file, base_env_pillar_tree_root_dir
|
||||
+ ), pytest.helpers.temp_file(
|
||||
+ "basic.sls", basic_pillar_file, base_env_pillar_tree_root_dir
|
||||
+ ):
|
||||
+ shell_result = salt_cli.run(
|
||||
+ "state.apply", "sls-id-test", minion_tgt=salt_minion.id
|
||||
+ )
|
||||
+ assert shell_result.exitcode == 0
|
||||
+ state_result = StateResult(shell_result.json)
|
||||
+ assert state_result.result is True
|
||||
+ assert state_result.changes == {"diff": "New file", "mode": "0644"}
|
||||
diff --git a/tests/pytests/unit/modules/state/test_state.py b/tests/pytests/unit/modules/state/test_state.py
|
||||
index 02fd2dd307..30cda303cc 100644
|
||||
--- a/tests/pytests/unit/modules/state/test_state.py
|
||||
+++ b/tests/pytests/unit/modules/state/test_state.py
|
||||
@@ -1,14 +1,16 @@
|
||||
"""
|
||||
:codeauthor: Rahul Handay <rahulha@saltstack.com>
|
||||
"""
|
||||
-
|
||||
import datetime
|
||||
import logging
|
||||
import os
|
||||
+from collections import namedtuple
|
||||
|
||||
import pytest
|
||||
+
|
||||
import salt.config
|
||||
import salt.loader
|
||||
+import salt.loader.context
|
||||
import salt.modules.config as config
|
||||
import salt.modules.state as state
|
||||
import salt.state
|
||||
@@ -1200,85 +1202,6 @@ def test_lock_saltenv():
|
||||
)
|
||||
|
||||
|
||||
-def test_get_pillar_errors_CC():
|
||||
- """
|
||||
- Test _get_pillar_errors function.
|
||||
- CC: External clean, Internal clean
|
||||
- :return:
|
||||
- """
|
||||
- for int_pillar, ext_pillar in [
|
||||
- ({"foo": "bar"}, {"fred": "baz"}),
|
||||
- ({"foo": "bar"}, None),
|
||||
- ({}, {"fred": "baz"}),
|
||||
- ]:
|
||||
- with patch("salt.modules.state.__pillar__", int_pillar):
|
||||
- for opts, res in [
|
||||
- ({"force": True}, None),
|
||||
- ({"force": False}, None),
|
||||
- ({}, None),
|
||||
- ]:
|
||||
- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar)
|
||||
-
|
||||
-
|
||||
-def test_get_pillar_errors_EC():
|
||||
- """
|
||||
- Test _get_pillar_errors function.
|
||||
- EC: External erroneous, Internal clean
|
||||
- :return:
|
||||
- """
|
||||
- errors = ["failure", "everywhere"]
|
||||
- for int_pillar, ext_pillar in [
|
||||
- ({"foo": "bar"}, {"fred": "baz", "_errors": errors}),
|
||||
- ({}, {"fred": "baz", "_errors": errors}),
|
||||
- ]:
|
||||
- with patch("salt.modules.state.__pillar__", int_pillar):
|
||||
- for opts, res in [
|
||||
- ({"force": True}, None),
|
||||
- ({"force": False}, errors),
|
||||
- ({}, errors),
|
||||
- ]:
|
||||
- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar)
|
||||
-
|
||||
-
|
||||
-def test_get_pillar_errors_EE():
|
||||
- """
|
||||
- Test _get_pillar_errors function.
|
||||
- CC: External erroneous, Internal erroneous
|
||||
- :return:
|
||||
- """
|
||||
- errors = ["failure", "everywhere"]
|
||||
- for int_pillar, ext_pillar in [
|
||||
- ({"foo": "bar", "_errors": errors}, {"fred": "baz", "_errors": errors})
|
||||
- ]:
|
||||
- with patch("salt.modules.state.__pillar__", int_pillar):
|
||||
- for opts, res in [
|
||||
- ({"force": True}, None),
|
||||
- ({"force": False}, errors),
|
||||
- ({}, errors),
|
||||
- ]:
|
||||
- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar)
|
||||
-
|
||||
-
|
||||
-def test_get_pillar_errors_CE():
|
||||
- """
|
||||
- Test _get_pillar_errors function.
|
||||
- CC: External clean, Internal erroneous
|
||||
- :return:
|
||||
- """
|
||||
- errors = ["failure", "everywhere"]
|
||||
- for int_pillar, ext_pillar in [
|
||||
- ({"foo": "bar", "_errors": errors}, {"fred": "baz"}),
|
||||
- ({"foo": "bar", "_errors": errors}, None),
|
||||
- ]:
|
||||
- with patch("salt.modules.state.__pillar__", int_pillar):
|
||||
- for opts, res in [
|
||||
- ({"force": True}, None),
|
||||
- ({"force": False}, errors),
|
||||
- ({}, errors),
|
||||
- ]:
|
||||
- assert res == state._get_pillar_errors(kwargs=opts, pillar=ext_pillar)
|
||||
-
|
||||
-
|
||||
def test_event():
|
||||
"""
|
||||
test state.event runner
|
||||
@@ -1318,3 +1241,35 @@ def test_event():
|
||||
if _expected in x.args[0]:
|
||||
found = True
|
||||
assert found is True
|
||||
+
|
||||
+
|
||||
+PillarPair = namedtuple("PillarPair", ["in_memory", "fresh"])
|
||||
+pillar_combinations = [
|
||||
+ (PillarPair({"foo": "bar"}, {"fred": "baz"}), None),
|
||||
+ (PillarPair({"foo": "bar"}, {"fred": "baz", "_errors": ["Failure"]}), ["Failure"]),
|
||||
+ (PillarPair({"foo": "bar"}, None), None),
|
||||
+ (PillarPair({"foo": "bar", "_errors": ["Failure"]}, None), ["Failure"]),
|
||||
+ (PillarPair({"foo": "bar", "_errors": ["Failure"]}, {"fred": "baz"}), None),
|
||||
+]
|
||||
+
|
||||
+
|
||||
+@pytest.mark.parametrize("pillar,expected_errors", pillar_combinations)
|
||||
+def test_get_pillar_errors(pillar: PillarPair, expected_errors):
|
||||
+ """
|
||||
+ test _get_pillar_errors function
|
||||
+
|
||||
+ There are three cases to consider:
|
||||
+ 1. kwargs['force'] is True -> None, no matter what's in pillar/__pillar__
|
||||
+ 2. pillar kwarg is available -> only check pillar, no matter what's in __pillar__
|
||||
+ 3. pillar kwarg is not available -> check __pillar__
|
||||
+ """
|
||||
+ ctx = salt.loader.context.LoaderContext()
|
||||
+ named_ctx = ctx.named_context("__pillar__", pillar.in_memory)
|
||||
+ with patch("salt.modules.state.__pillar__", named_ctx, create=True):
|
||||
+ assert (
|
||||
+ state._get_pillar_errors(kwargs={"force": True}, pillar=pillar.fresh)
|
||||
+ is None
|
||||
+ )
|
||||
+ assert (
|
||||
+ state._get_pillar_errors(kwargs={}, pillar=pillar.fresh) == expected_errors
|
||||
+ )
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
@ -1,105 +0,0 @@
|
||||
From 634e82874b17c38bd4d27c0c07a53c9e39e49968 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?=
|
||||
<psuarezhernandez@suse.com>
|
||||
Date: Wed, 9 Feb 2022 09:01:08 +0000
|
||||
Subject: [PATCH] state.orchestrate_single does not pass pillar=None
|
||||
(#488)
|
||||
|
||||
Passing a pillar to state.single can results in state functions not
|
||||
working when they don't accept a pillar keyword argument. One example
|
||||
where this is the case is salt.wait_for_event. When no pillar is
|
||||
provided, it does not need to be passed.
|
||||
|
||||
Co-authored-by: Alexander Graul <agraul@suse.com>
|
||||
---
|
||||
changelog/61092.fixed | 3 ++
|
||||
salt/runners/state.py | 10 ++++--
|
||||
tests/pytests/unit/runners/test_state.py | 41 ++++++++++++++++++++++++
|
||||
3 files changed, 51 insertions(+), 3 deletions(-)
|
||||
create mode 100644 changelog/61092.fixed
|
||||
create mode 100644 tests/pytests/unit/runners/test_state.py
|
||||
|
||||
diff --git a/changelog/61092.fixed b/changelog/61092.fixed
|
||||
new file mode 100644
|
||||
index 0000000000..6ca66839c9
|
||||
--- /dev/null
|
||||
+++ b/changelog/61092.fixed
|
||||
@@ -0,0 +1,3 @@
|
||||
+state.orchestrate_single only passes a pillar if it is set to the state
|
||||
+function. This allows it to be used with state functions that don't accept a
|
||||
+pillar keyword argument.
|
||||
diff --git a/salt/runners/state.py b/salt/runners/state.py
|
||||
index f8fc1b0944..5642204ce9 100644
|
||||
--- a/salt/runners/state.py
|
||||
+++ b/salt/runners/state.py
|
||||
@@ -150,12 +150,16 @@ def orchestrate_single(fun, name, test=None, queue=False, pillar=None, **kwargs)
|
||||
|
||||
salt-run state.orchestrate_single fun=salt.wheel name=key.list_all
|
||||
"""
|
||||
- if pillar is not None and not isinstance(pillar, dict):
|
||||
- raise SaltInvocationError("Pillar data must be formatted as a dictionary")
|
||||
+ if pillar is not None:
|
||||
+ if isinstance(pillar, dict):
|
||||
+ kwargs["pillar"] = pillar
|
||||
+ else:
|
||||
+ raise SaltInvocationError("Pillar data must be formatted as a dictionary")
|
||||
+
|
||||
__opts__["file_client"] = "local"
|
||||
minion = salt.minion.MasterMinion(__opts__)
|
||||
running = minion.functions["state.single"](
|
||||
- fun, name, test=None, queue=False, pillar=pillar, **kwargs
|
||||
+ fun, name, test=None, queue=False, **kwargs
|
||||
)
|
||||
ret = {minion.opts["id"]: running}
|
||||
__jid_event__.fire_event({"data": ret, "outputter": "highstate"}, "progress")
|
||||
diff --git a/tests/pytests/unit/runners/test_state.py b/tests/pytests/unit/runners/test_state.py
|
||||
new file mode 100644
|
||||
index 0000000000..df0a718a41
|
||||
--- /dev/null
|
||||
+++ b/tests/pytests/unit/runners/test_state.py
|
||||
@@ -0,0 +1,41 @@
|
||||
+#!/usr/bin/python3
|
||||
+
|
||||
+import pytest
|
||||
+from salt.runners import state as state_runner
|
||||
+from tests.support.mock import Mock, patch
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def configure_loader_modules():
|
||||
+ return {state_runner: {"__opts__": {}, "__jid_event__": Mock()}}
|
||||
+
|
||||
+
|
||||
+def test_orchestrate_single_passes_pillar():
|
||||
+ """
|
||||
+ test state.orchestrate_single passes given pillar to state.single
|
||||
+ """
|
||||
+ mock_master_minion = Mock()
|
||||
+ mock_state_single = Mock()
|
||||
+ mock_master_minion.functions = {"state.single": mock_state_single}
|
||||
+ mock_master_minion.opts = {"id": "dummy"}
|
||||
+ test_pillar = {"test_entry": "exists"}
|
||||
+ with patch("salt.minion.MasterMinion", Mock(return_value=mock_master_minion)):
|
||||
+ state_runner.orchestrate_single(
|
||||
+ fun="pillar.get", name="test_entry", pillar=test_pillar
|
||||
+ )
|
||||
+ assert mock_state_single.call_args.kwargs["pillar"] == test_pillar
|
||||
+
|
||||
+
|
||||
+def test_orchestrate_single_does_not_pass_none_pillar():
|
||||
+ """
|
||||
+ test state.orchestrate_single does not pass pillar=None to state.single
|
||||
+ """
|
||||
+ mock_master_minion = Mock()
|
||||
+ mock_state_single = Mock()
|
||||
+ mock_master_minion.functions = {"state.single": mock_state_single}
|
||||
+ mock_master_minion.opts = {"id": "dummy"}
|
||||
+ with patch("salt.minion.MasterMinion", Mock(return_value=mock_master_minion)):
|
||||
+ state_runner.orchestrate_single(
|
||||
+ fun="pillar.get", name="test_entry", pillar=None
|
||||
+ )
|
||||
+ assert "pillar" not in mock_state_single.call_args.kwargs
|
||||
--
|
||||
2.37.3
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user