diff --git a/_lastrevision b/_lastrevision index 1cf8c6b..5c1d88e 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -597049dd3d38cffb3d6e555ded591bc36ed09a58 \ No newline at end of file +cc4e56e0465b20664e2f24bfe7034e5fee37232f \ No newline at end of file diff --git a/_service b/_service index 792f133..dbfc8cf 100644 --- a/_service +++ b/_service @@ -3,7 +3,7 @@ https://github.com/openSUSE/salt-packaging.git salt package - MU/5.0.1 + MU/5.0.2 git diff --git a/allow-namedloadercontexts-to-be-returned-from-loader.patch b/allow-namedloadercontexts-to-be-returned-from-loader.patch new file mode 100644 index 0000000..ca9720c --- /dev/null +++ b/allow-namedloadercontexts-to-be-returned-from-loader.patch @@ -0,0 +1,73 @@ +From 1be3f92ef3bf14e47340e2e075291204b3e75e98 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Wed, 25 Sep 2024 14:07:42 +0300 +Subject: [PATCH] Allow NamedLoaderContexts to be returned from loader + +It is useful in some cases to return NamedLoaderContexts from loaded +functions. Instead of choking or requireing implimenters to call the +context's value() method before being de-scoped, detect when a +NamedLoaderContext has been returned and return the value from the +current context. + +Co-authored-by: Daniel A. Wozniak +--- + salt/loader/lazy.py | 5 ++++- + tests/pytests/integration/modules/test_config.py | 8 ++++++++ + tests/pytests/unit/loader/test_loader.py | 13 +++++++++++++ + 3 files changed, 25 insertions(+), 1 deletion(-) + create mode 100644 tests/pytests/integration/modules/test_config.py + +diff --git a/salt/loader/lazy.py b/salt/loader/lazy.py +index 5de995d446..b7fd97f0e1 100644 +--- a/salt/loader/lazy.py ++++ b/salt/loader/lazy.py +@@ -1246,7 +1246,10 @@ class LazyLoader(salt.utils.lazy.LazyDict): + self.parent_loader = current_loader + token = salt.loader.context.loader_ctxvar.set(self) + try: +- return _func_or_method(*args, **kwargs) ++ ret = _func_or_method(*args, **kwargs) ++ if isinstance(ret, salt.loader.context.NamedLoaderContext): ++ ret = ret.value() ++ return ret + finally: + self.parent_loader = None + salt.loader.context.loader_ctxvar.reset(token) +diff --git a/tests/pytests/integration/modules/test_config.py b/tests/pytests/integration/modules/test_config.py +new file mode 100644 +index 0000000000..afdf470605 +--- /dev/null ++++ b/tests/pytests/integration/modules/test_config.py +@@ -0,0 +1,8 @@ ++import pytest ++ ++ ++@pytest.mark.slow_test ++def test_config_items(salt_cli, salt_minion): ++ ret = salt_cli.run("config.items", minion_tgt=salt_minion.id) ++ assert ret.returncode == 0 ++ assert isinstance(ret.data, dict) +diff --git a/tests/pytests/unit/loader/test_loader.py b/tests/pytests/unit/loader/test_loader.py +index 86348749db..aba605f42a 100644 +--- a/tests/pytests/unit/loader/test_loader.py ++++ b/tests/pytests/unit/loader/test_loader.py +@@ -62,3 +62,16 @@ def test_raw_mod_functions(): + ret = salt.loader.raw_mod(opts, "grains", "get") + for k, v in ret.items(): + assert isinstance(v, salt.loader.lazy.LoadedFunc) ++ ++ ++def test_return_named_context_from_loaded_func(tmp_path): ++ opts = { ++ "optimization_order": [0], ++ } ++ contents = """ ++ def foobar(): ++ return __test__ ++ """ ++ with pytest.helpers.temp_file("mymod.py", contents, directory=tmp_path): ++ loader = salt.loader.LazyLoader([tmp_path], opts, pack={"__test__": "meh"}) ++ assert loader["mymod.foobar"]() == "meh" +-- +2.46.1 + diff --git a/avoid-crash-on-wrong-output-of-systemctl-version-bsc.patch b/avoid-crash-on-wrong-output-of-systemctl-version-bsc.patch new file mode 100644 index 0000000..f726aba --- /dev/null +++ b/avoid-crash-on-wrong-output-of-systemctl-version-bsc.patch @@ -0,0 +1,153 @@ +From b2faa019f0f5aa03b03e6c54c9aa60b7f6aa4f91 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Fri, 30 Aug 2024 14:35:33 +0200 +Subject: [PATCH] Avoid crash on wrong output of systemctl version + (bsc#1229539) + +* Better handling output of systemctl --version + +* Add more cases to test grains.core._systemd +--- + salt/grains/core.py | 27 +++++++- + tests/pytests/unit/grains/test_core.py | 89 ++++++++++++++++++++++++++ + 2 files changed, 113 insertions(+), 3 deletions(-) + +diff --git a/salt/grains/core.py b/salt/grains/core.py +index 4454c303fe..98bbd3868e 100644 +--- a/salt/grains/core.py ++++ b/salt/grains/core.py +@@ -2432,10 +2432,31 @@ def _systemd(): + """ + Return the systemd grain + """ +- systemd_info = __salt__["cmd.run"]("systemctl --version").splitlines() ++ systemd_version = "UNDEFINED" ++ systemd_features = "" ++ try: ++ systemd_output = __salt__["cmd.run_all"]("systemctl --version") ++ except Exception: # pylint: disable=broad-except ++ log.error("Exception while executing `systemctl --version`", exc_info=True) ++ return { ++ "version": systemd_version, ++ "features": systemd_features, ++ } ++ if systemd_output.get("retcode") == 0: ++ systemd_info = systemd_output.get("stdout", "").splitlines() ++ try: ++ if systemd_info[0].startswith("systemd "): ++ systemd_version = systemd_info[0].split()[1] ++ systemd_features = systemd_info[1] ++ except IndexError: ++ pass ++ if systemd_version == "UNDEFINED" or systemd_features == "": ++ log.error( ++ "Unexpected output returned by `systemctl --version`: %s", systemd_output ++ ) + return { +- "version": systemd_info[0].split()[1], +- "features": systemd_info[1], ++ "version": systemd_version, ++ "features": systemd_features, + } + + +diff --git a/tests/pytests/unit/grains/test_core.py b/tests/pytests/unit/grains/test_core.py +index 36545287b9..b64b8c4bf8 100644 +--- a/tests/pytests/unit/grains/test_core.py ++++ b/tests/pytests/unit/grains/test_core.py +@@ -3593,3 +3593,92 @@ def test_virtual_set_virtual_ec2(): + + assert virtual_grains["virtual"] == "Nitro" + assert virtual_grains["virtual_subtype"] == "Amazon EC2" ++ ++ ++@pytest.mark.parametrize( ++ "systemd_data,expected", ++ ( ++ ( ++ { ++ "pid": 1234, ++ "retcode": 0, ++ "stdout": "systemd 254 (254.3-1)\n+PAM +AUDIT -SELINUX -APPARMOR -IMA +SMACK " ++ "+SECCOMP +GCRYPT +GNUTLS +OPENSSL +ACL +BLKID +CURL +ELFUTILS " ++ "+FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP +LIBFDISK +PCRE2 " ++ "-PWQUALITY +P11KIT -QRENCODE +TPM2 +BZIP2 +LZ4 +XZ +ZLIB +ZSTD " ++ "+BPF_FRAMEWORK +XKBCOMMON +UTMP -SYSVINIT default-hierarchy=unified", ++ "stderr": "", ++ }, ++ { ++ "version": "254", ++ "features": "+PAM +AUDIT -SELINUX -APPARMOR -IMA +SMACK +SECCOMP +GCRYPT +GNUTLS +OPENSSL " ++ "+ACL +BLKID +CURL +ELFUTILS +FIDO2 +IDN2 -IDN +IPTC +KMOD +LIBCRYPTSETUP " ++ "+LIBFDISK +PCRE2 -PWQUALITY +P11KIT -QRENCODE +TPM2 +BZIP2 +LZ4 +XZ " ++ "+ZLIB +ZSTD +BPF_FRAMEWORK +XKBCOMMON +UTMP -SYSVINIT default-hierarchy=unified", ++ }, ++ ), ++ ( ++ { ++ "pid": 2345, ++ "retcode": 1, ++ "stdout": "", ++ "stderr": "some garbage in the output", ++ }, ++ { ++ "version": "UNDEFINED", ++ "features": "", ++ }, ++ ), ++ ( ++ { ++ "pid": 3456, ++ "retcode": 0, ++ "stdout": "unexpected stdout\none more line", ++ "stderr": "", ++ }, ++ { ++ "version": "UNDEFINED", ++ "features": "", ++ }, ++ ), ++ ( ++ { ++ "pid": 4567, ++ "retcode": 0, ++ "stdout": "", ++ "stderr": "", ++ }, ++ { ++ "version": "UNDEFINED", ++ "features": "", ++ }, ++ ), ++ ( ++ Exception("Some exception on calling `systemctl --version`"), ++ { ++ "version": "UNDEFINED", ++ "features": "", ++ }, ++ ), ++ ), ++) ++def test__systemd(systemd_data, expected): ++ """ ++ test _systemd ++ """ ++ ++ def mock_run_all_systemd(_): ++ if isinstance(systemd_data, Exception): ++ raise systemd_data ++ return systemd_data ++ ++ with patch.dict( ++ core.__salt__, ++ { ++ "cmd.run_all": mock_run_all_systemd, ++ }, ++ ): ++ ret = core._systemd() ++ assert "version" in ret ++ assert "features" in ret ++ assert ret == expected +-- +2.46.0 + diff --git a/avoid-explicit-reading-of-etc-salt-minion-bsc-122035.patch b/avoid-explicit-reading-of-etc-salt-minion-bsc-122035.patch new file mode 100644 index 0000000..fba9803 --- /dev/null +++ b/avoid-explicit-reading-of-etc-salt-minion-bsc-122035.patch @@ -0,0 +1,27 @@ +From bbdb56932845dceb47332a4c967c13a9a78b88bc Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Wed, 25 Sep 2024 14:08:20 +0300 +Subject: [PATCH] Avoid explicit reading of /etc/salt/minion + (bsc#1220357) + +Co-authored-by: Daniel A. Wozniak +--- + salt/utils/azurearm.py | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/salt/utils/azurearm.py b/salt/utils/azurearm.py +index 276cbb66b3..9ae128273c 100644 +--- a/salt/utils/azurearm.py ++++ b/salt/utils/azurearm.py +@@ -47,8 +47,6 @@ try: + except ImportError: + HAS_AZURE = False + +-__opts__ = salt.config.minion_config("/etc/salt/minion") +-__salt__ = salt.loader.minion_mods(__opts__) + + log = logging.getLogger(__name__) + +-- +2.46.1 + diff --git a/fix-deprecated-code-677.patch b/fix-deprecated-code-677.patch new file mode 100644 index 0000000..b64f339 --- /dev/null +++ b/fix-deprecated-code-677.patch @@ -0,0 +1,166 @@ +From d5f3df07783d8aaf3a897ca2f209e662973b930c Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Wed, 4 Sep 2024 13:11:33 +0200 +Subject: [PATCH] Fix deprecated code (#677) + +Due to SUSE's extended support policy, we won't remove +code from Salt until next major release. +--- + salt/_logging/handlers.py | 6 +++--- + salt/log/__init__.py | 2 +- + salt/log/handlers/__init__.py | 2 +- + salt/log/mixins.py | 2 +- + salt/log/setup.py | 4 ++-- + salt/modules/aptpkg.py | 2 +- + salt/modules/cassandra_mod.py | 2 +- + salt/returners/cassandra_return.py | 2 +- + salt/returners/django_return.py | 2 +- + 9 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/salt/_logging/handlers.py b/salt/_logging/handlers.py +index f4b0b6fec3d..5a1a1613137 100644 +--- a/salt/_logging/handlers.py ++++ b/salt/_logging/handlers.py +@@ -36,7 +36,7 @@ class TemporaryLoggingHandler(logging.NullHandler): + + def __init__(self, level=logging.NOTSET, max_queue_size=10000): + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}.TemporaryLoggingHandler'. " + "'{name}.TemporaryLoggingHandler' will go away after " + "{{date}}.".format(name=__name__), +@@ -225,7 +225,7 @@ if sys.version_info < (3, 7): + def __init__(self, queue): # pylint: disable=useless-super-delegation + super().__init__(queue) + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}.QueueHandler' and instead " + "use 'logging.handlers.QueueHandler'. " + "'{name}.QueueHandler' will go away after " +@@ -283,7 +283,7 @@ else: + def __init__(self, queue): # pylint: disable=useless-super-delegation + super().__init__(queue) + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}.QueueHandler' and instead " + "use 'logging.handlers.QueueHandler'. " + "'{name}.QueueHandler' will go away after " +diff --git a/salt/log/__init__.py b/salt/log/__init__.py +index 3458474f2ca..69bfa8ed15b 100644 +--- a/salt/log/__init__.py ++++ b/salt/log/__init__.py +@@ -24,7 +24,7 @@ from salt.log.setup import ( + from salt.utils.versions import warn_until_date + + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}' and instead use 'salt._logging'. " + "'{name}' will go away after {{date}}.".format(name=__name__), + stacklevel=3, +diff --git a/salt/log/handlers/__init__.py b/salt/log/handlers/__init__.py +index 8bc740e20f1..55cf10cdb78 100644 +--- a/salt/log/handlers/__init__.py ++++ b/salt/log/handlers/__init__.py +@@ -12,7 +12,7 @@ from salt._logging.handlers import ( + from salt.utils.versions import warn_until_date + + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}' and instead use 'salt._logging.handlers'. " + "'{name}' will go away after {{date}}.".format(name=__name__), + ) +diff --git a/salt/log/mixins.py b/salt/log/mixins.py +index 6619b564198..65f5ed7f78a 100644 +--- a/salt/log/mixins.py ++++ b/salt/log/mixins.py +@@ -11,7 +11,7 @@ from salt.utils.versions import warn_until_date + # pylint: enable=unused-import + + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}' and instead use 'salt._logging.mixins'. " + "'{name}' will go away after {{date}}.".format(name=__name__), + ) +diff --git a/salt/log/setup.py b/salt/log/setup.py +index 74bd7bbd3e1..f4c80b0f280 100644 +--- a/salt/log/setup.py ++++ b/salt/log/setup.py +@@ -21,7 +21,7 @@ from salt._logging.impl import set_log_record_factory as setLogRecordFactory + from salt.utils.versions import warn_until_date + + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using '{name}' and instead use 'salt._logging'. " + "'{name}' will go away after {{date}}. Do note however that " + "'salt._logging' is now considered a non public implementation " +@@ -34,7 +34,7 @@ def _deprecated_warning(func): + @wraps(func) + def wrapper(*args, **kwargs): + warn_until_date( +- "20240101", ++ "20260101", + "Please stop using 'salt.log.setup.{name}()' as it no longer does anything and " + "will go away after {{date}}.".format(name=func.__qualname__), + stacklevel=4, +diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py +index ad5450c4151..cd40aea54f1 100644 +--- a/salt/modules/aptpkg.py ++++ b/salt/modules/aptpkg.py +@@ -3128,7 +3128,7 @@ def expand_repo_def(**kwargs): + NOT USABLE IN THE CLI + """ + warn_until_date( +- "20250101", ++ "20260101", + "The pkg.expand_repo_def function is deprecated and set for removal " + "after {date}. This is only unsed internally by the apt pkg state " + "module. If that's not the case, please file an new issue requesting " +diff --git a/salt/modules/cassandra_mod.py b/salt/modules/cassandra_mod.py +index 029fd08fb9b..db9c8821920 100644 +--- a/salt/modules/cassandra_mod.py ++++ b/salt/modules/cassandra_mod.py +@@ -45,7 +45,7 @@ def __virtual__(): + ) + + warn_until_date( +- "20240101", ++ "20260101", + "The cassandra returner is broken and deprecated, and will be removed" + " after {date}. Use the cassandra_cql returner instead", + ) +diff --git a/salt/returners/cassandra_return.py b/salt/returners/cassandra_return.py +index ac01a4e46cb..5fcc00ee8ce 100644 +--- a/salt/returners/cassandra_return.py ++++ b/salt/returners/cassandra_return.py +@@ -53,7 +53,7 @@ def __virtual__(): + if not HAS_PYCASSA: + return False, "Could not import cassandra returner; pycassa is not installed." + warn_until_date( +- "20240101", ++ "20260101", + "The cassandra returner is broken and deprecated, and will be removed" + " after {date}. Use the cassandra_cql returner instead", + ) +diff --git a/salt/returners/django_return.py b/salt/returners/django_return.py +index 36386875552..474653f3831 100644 +--- a/salt/returners/django_return.py ++++ b/salt/returners/django_return.py +@@ -57,7 +57,7 @@ __virtualname__ = "django" + + def __virtual__(): + warn_until_date( +- "20240101", ++ "20260101", + "The django returner is broken and deprecated, and will be removed" + " after {date}.", + ) +-- +2.46.0 + diff --git a/fix-test_debian-to-work-in-our-infrastructure-676.patch b/fix-test_debian-to-work-in-our-infrastructure-676.patch new file mode 100644 index 0000000..d639188 --- /dev/null +++ b/fix-test_debian-to-work-in-our-infrastructure-676.patch @@ -0,0 +1,25 @@ +From a6d27a6f50bbbea539ec64bf96a5b9755e32bf69 Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Wed, 4 Sep 2024 13:11:05 +0200 +Subject: [PATCH] Fix test_debian to work in our infrastructure (#676) + +--- + tests/pytests/functional/states/pkgrepo/test_debian.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/pytests/functional/states/pkgrepo/test_debian.py b/tests/pytests/functional/states/pkgrepo/test_debian.py +index 87716706d5e..7bda100b634 100644 +--- a/tests/pytests/functional/states/pkgrepo/test_debian.py ++++ b/tests/pytests/functional/states/pkgrepo/test_debian.py +@@ -205,7 +205,7 @@ def ubuntu_state_tree(system_aptsources, state_tree, grains): + - dist: {{ codename }} + - file: /etc/apt/sources.list.d/firefox-beta.list + - keyid: CE49EC21 +- - keyserver: keyserver.ubuntu.com ++ - keyserver: hkp://keyserver.ubuntu.com:80 + {%- endif %} + + {%- if backports %}{%- do ubuntu_repos.append('kubuntu-ppa') %} +-- +2.46.0 + diff --git a/fix-test_system-flaky-setup_teardown-fn.patch b/fix-test_system-flaky-setup_teardown-fn.patch new file mode 100644 index 0000000..b3cbcd0 --- /dev/null +++ b/fix-test_system-flaky-setup_teardown-fn.patch @@ -0,0 +1,44 @@ +From 5567f2bd51d66b7797c986cf64f79f71ca57eb63 Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Wed, 4 Sep 2024 13:10:44 +0200 +Subject: [PATCH] Fix test_system flaky setup_teardown fn + +--- + tests/pytests/functional/modules/test_system.py | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/tests/pytests/functional/modules/test_system.py b/tests/pytests/functional/modules/test_system.py +index 2cd03a3a3e4..270aafbe2cd 100644 +--- a/tests/pytests/functional/modules/test_system.py ++++ b/tests/pytests/functional/modules/test_system.py +@@ -4,10 +4,12 @@ import os + import signal + import subprocess + import textwrap ++import time + + import pytest + + import salt.utils.files ++from salt.exceptions import CommandExecutionError + + INSIDE_CONTAINER = os.getenv("HOSTNAME", "") == "salt-test-container" + +@@ -80,7 +82,13 @@ def setup_teardown_vars(file, service, system): + file.remove("/etc/machine-info") + + if _systemd_timesyncd_available_: +- res = service.start("systemd-timesyncd") ++ try: ++ res = service.start("systemd-timesyncd") ++ except CommandExecutionError: ++ # We possibly did too many restarts in too short time ++ # Wait 10s (default systemd timeout) and try again ++ time.sleep(10) ++ res = service.start("systemd-timesyncd") + assert res + + +-- +2.46.0 + diff --git a/fix-the-selinux-context-for-salt-minion-service-bsc-.patch b/fix-the-selinux-context-for-salt-minion-service-bsc-.patch new file mode 100644 index 0000000..a4281ff --- /dev/null +++ b/fix-the-selinux-context-for-salt-minion-service-bsc-.patch @@ -0,0 +1,83 @@ +From d933c8f0795fdada84a01a2cc754586fa720993d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 10 Sep 2024 13:46:09 +0100 +Subject: [PATCH] Fix the SELinux context for Salt Minion service + (bsc#1219041) (#670) + +Currently there are no SELinux policies for Salt. + +By default, the Salt Minion service runs as 'unconfined_service_t' when +SELinux is enabled. This works fine in most cases but generates a problem +then trying to transition to an 'unconfined_t', i.a. when running +"cmd.run .... runas=nobody". Then we see this denied in audit logs: + +type=AVC msg=audit(1722870119.142:718): avc: denied { transition } for pid=3421 comm="su" path="/usr/bin/bash" dev="vda3" ino=28565 scontext=system_u:system_r:unconfined_service_t:s0 tcontext=unconfined_u:unconfined_r:unconfined_t:s0 tclass=process permissive=0 + +(This happens for cmd.run at the time of trying to invoke a shell as a +different user to gather the environment variables from this particular +user) + +Fixing the SELinuxContext for the Salt Minion systemd service to a +general 'unconfined_t' workarounds this situation. + +SELinuxContext attribute was added on systemd version 209. +--- + pkg/common/salt-minion.service | 1 + + pkg/old/deb/salt-minion.service | 1 + + pkg/old/suse/salt-minion.service | 1 + + pkg/old/suse/salt-minion.service.rhel7 | 1 + + 4 files changed, 4 insertions(+) + +diff --git a/pkg/common/salt-minion.service b/pkg/common/salt-minion.service +index 69aff18c583..696d0263c39 100644 +--- a/pkg/common/salt-minion.service ++++ b/pkg/common/salt-minion.service +@@ -9,6 +9,7 @@ Type=notify + NotifyAccess=all + LimitNOFILE=8192 + ExecStart=/usr/bin/salt-minion ++SELinuxContext=system_u:system_r:unconfined_t:s0 + + [Install] + WantedBy=multi-user.target +diff --git a/pkg/old/deb/salt-minion.service b/pkg/old/deb/salt-minion.service +index 7e6cf146549..b0ad82c1334 100644 +--- a/pkg/old/deb/salt-minion.service ++++ b/pkg/old/deb/salt-minion.service +@@ -8,6 +8,7 @@ KillMode=process + NotifyAccess=all + LimitNOFILE=8192 + ExecStart=/usr/bin/salt-minion ++SELinuxContext=system_u:system_r:unconfined_t:s0 + + [Install] + WantedBy=multi-user.target +diff --git a/pkg/old/suse/salt-minion.service b/pkg/old/suse/salt-minion.service +index 12f28314cb1..b99ef063522 100644 +--- a/pkg/old/suse/salt-minion.service ++++ b/pkg/old/suse/salt-minion.service +@@ -10,6 +10,7 @@ ExecStart=/usr/bin/salt-minion + KillMode=process + Restart=on-failure + RestartSec=15 ++SELinuxContext=system_u:system_r:unconfined_t:s0 + + [Install] + WantedBy=multi-user.target +diff --git a/pkg/old/suse/salt-minion.service.rhel7 b/pkg/old/suse/salt-minion.service.rhel7 +index 69172677140..92cc66d32f4 100644 +--- a/pkg/old/suse/salt-minion.service.rhel7 ++++ b/pkg/old/suse/salt-minion.service.rhel7 +@@ -9,6 +9,7 @@ ExecStart=/usr/bin/salt-minion + KillMode=process + Restart=on-failure + RestartSec=15 ++SELinuxContext=system_u:system_r:unconfined_t:s0 + + [Install] + WantedBy=multi-user.target +-- +2.46.0 + + diff --git a/fix-x509-test-fails-on-old-openssl-systems-682.patch b/fix-x509-test-fails-on-old-openssl-systems-682.patch new file mode 100644 index 0000000..7e6ae6c --- /dev/null +++ b/fix-x509-test-fails-on-old-openssl-systems-682.patch @@ -0,0 +1,261 @@ +From 7daf461528c90776b8f865cd58d20e23bd5b6f3f Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Wed, 2 Oct 2024 09:09:34 +0200 +Subject: [PATCH] Fix x509 test fails on old openssl systems (#682) + +--- + .../functional/modules/test_x509_v2.py | 41 +++++++++++++---- + .../pytests/functional/states/test_x509_v2.py | 44 +++++++++++++++---- + .../scenarios/performance/test_performance.py | 8 +++- + 3 files changed, 75 insertions(+), 18 deletions(-) + +diff --git a/tests/pytests/functional/modules/test_x509_v2.py b/tests/pytests/functional/modules/test_x509_v2.py +index 2e8152d04a..7de8f3b01f 100644 +--- a/tests/pytests/functional/modules/test_x509_v2.py ++++ b/tests/pytests/functional/modules/test_x509_v2.py +@@ -681,8 +681,13 @@ def test_create_certificate_self_signed(x509, algo, request): + privkey = request.getfixturevalue(f"{algo}_privkey") + try: + res = x509.create_certificate(signing_private_key=privkey, CN="success") +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ except salt.exceptions.CommandExecutionError as e: ++ if "Could not load PEM-encoded" in e.error: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ else: ++ raise e + assert res.startswith("-----BEGIN CERTIFICATE-----") + cert = _get_cert(res) + assert cert.subject.rfc4514_string() == "CN=success" +@@ -754,8 +759,13 @@ def test_create_certificate_from_privkey(x509, ca_key, ca_cert, algo, request): + private_key=privkey, + CN="success", + ) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ except salt.exceptions.CommandExecutionError as e: ++ if "Could not load PEM-encoded" in e.error: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ else: ++ raise e + assert res.startswith("-----BEGIN CERTIFICATE-----") + cert = _get_cert(res) + assert cert.subject.rfc4514_string() == "CN=success" +@@ -802,8 +812,13 @@ def test_create_certificate_from_pubkey(x509, ca_key, ca_cert, algo, request): + public_key=pubkey, + CN="success", + ) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ except salt.exceptions.CommandExecutionError as e: ++ if "Could not load PEM-encoded" in e.error: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ else: ++ raise e + assert res.startswith("-----BEGIN CERTIFICATE-----") + cert = _get_cert(res) + assert cert.subject.rfc4514_string() == "CN=success" +@@ -1341,8 +1356,13 @@ def test_create_csr(x509, algo, request): + privkey = request.getfixturevalue(f"{algo}_privkey") + try: + res = x509.create_csr(private_key=privkey) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ except salt.exceptions.CommandExecutionError as e: ++ if "Could not load PEM-encoded" in e.error: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ else: ++ raise e + assert res.startswith("-----BEGIN CERTIFICATE REQUEST-----") + + +@@ -1402,7 +1422,7 @@ def test_create_csr_raw(x509, rsa_privkey): + def test_create_private_key(x509, algo): + try: + res = x509.create_private_key(algo=algo) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + assert res.startswith("-----BEGIN PRIVATE KEY-----") + +@@ -1413,7 +1433,7 @@ def test_create_private_key_with_passphrase(x509, algo): + passphrase = "hunter2" + try: + res = x509.create_private_key(algo=algo, passphrase=passphrase) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + assert res.startswith("-----BEGIN ENCRYPTED PRIVATE KEY-----") + # ensure it can be loaded +@@ -1465,8 +1485,13 @@ def test_get_private_key_size(x509, algo, expected, request): + privkey = request.getfixturevalue(f"{algo}_privkey") + try: + res = x509.get_private_key_size(privkey) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ except salt.exceptions.CommandExecutionError as e: ++ if "Could not load PEM-encoded" in e.error: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") ++ else: ++ raise e + assert res == expected + + +@@ -1612,7 +1637,7 @@ def test_verify_signature(x509, algo, request): + wrong_privkey = request.getfixturevalue(f"{algo}_privkey") + try: + privkey = x509.create_private_key(algo=algo) +- except UnsupportedAlgorithm: ++ except (UnsupportedAlgorithm, NotImplementedError): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + cert = x509.create_certificate(signing_private_key=privkey) + assert x509.verify_signature(cert, privkey) +diff --git a/tests/pytests/functional/states/test_x509_v2.py b/tests/pytests/functional/states/test_x509_v2.py +index 47a1c555f8..139f7b1906 100644 +--- a/tests/pytests/functional/states/test_x509_v2.py ++++ b/tests/pytests/functional/states/test_x509_v2.py +@@ -574,9 +574,9 @@ def existing_cert(x509, cert_args, ca_key, rsa_privkey, request): + ca_key, + encoding=cert_args.get("encoding", "pem"), + passphrase=cert_args.get("pkcs12_passphrase"), +- subject=subject +- if "signing_policy" not in cert_args +- else "CN=from_signing_policy", ++ subject=( ++ subject if "signing_policy" not in cert_args else "CN=from_signing_policy" ++ ), + ) + yield cert_args["name"] + +@@ -694,8 +694,12 @@ def existing_csr_exts(x509, csr_args, csr_args_exts, ca_key, rsa_privkey, reques + def existing_pk(x509, pk_args, request): + pk_args.update(request.param) + ret = x509.private_key_managed(**pk_args) +- if ret.result == False and "UnsupportedAlgorithm" in ret.comment: +- pytest.skip(f"Algorithm '{pk_args['algo']}' is not supported on this OpenSSL version") ++ if ret.result == False and ( ++ "UnsupportedAlgorithm" in ret.comment or "NotImplementedError" in ret.comment ++ ): ++ pytest.skip( ++ f"Algorithm '{pk_args['algo']}' is not supported on this OpenSSL version" ++ ) + _assert_pk_basic( + ret, + pk_args.get("algo", "rsa"), +@@ -1054,6 +1058,8 @@ def test_certificate_managed_days_valid_does_not_override_days_remaining( + def test_certificate_managed_privkey_change(x509, cert_args, ec_privkey, ca_key): + cert_args["private_key"] = ec_privkey + ret = x509.certificate_managed(**cert_args) ++ if ret.result == False and "NotImplementedError" in ret.comment: ++ pytest.skip("Current OpenSSL does not support 'ec' algorithm") + _assert_cert_basic(ret, cert_args["name"], ec_privkey, ca_key) + assert ret.changes["private_key"] + +@@ -1237,6 +1243,8 @@ def test_certificate_managed_wrong_ca_key( + cert_args["private_key"] = ec_privkey + cert_args["signing_private_key"] = rsa_privkey + ret = x509.certificate_managed(**cert_args) ++ if ret.result == False and "NotImplementedError" in ret.comment: ++ pytest.skip("Current OpenSSL does not support 'ec' algorithm") + assert ret.result is False + assert not ret.changes + assert "Signing private key does not match the certificate" in ret.comment +@@ -1917,6 +1925,8 @@ def test_csr_managed_existing_invalid_version(x509, csr_args, rsa_privkey): + def test_csr_managed_privkey_change(x509, csr_args, ec_privkey): + csr_args["private_key"] = ec_privkey + ret = x509.csr_managed(**csr_args) ++ if ret.result == False and "NotImplementedError" in ret.comment: ++ pytest.skip("Current OpenSSL does not support 'ec' algorithm") + _assert_csr_basic(ret, ec_privkey) + assert ret.changes["private_key"] + +@@ -2141,11 +2151,14 @@ def test_private_key_managed(x509, pk_args, algo, encoding, passphrase): + pytest.skip( + "PKCS12 serialization of Edwards-curve keys requires cryptography v37" + ) ++ + pk_args["algo"] = algo + pk_args["encoding"] = encoding + pk_args["passphrase"] = passphrase + ret = x509.private_key_managed(**pk_args) +- if ret.result == False and "UnsupportedAlgorithm" in ret.comment: ++ if ret.result == False and ( ++ "UnsupportedAlgorithm" in ret.comment or "NotImplementedError" in ret.comment ++ ): + pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + _assert_pk_basic(ret, algo, encoding, passphrase) + +@@ -2155,6 +2168,8 @@ def test_private_key_managed_keysize(x509, pk_args, algo, keysize): + pk_args["algo"] = algo + pk_args["keysize"] = keysize + ret = x509.private_key_managed(**pk_args) ++ if ret.result == False and "NotImplementedError" in ret.comment: ++ pytest.skip("Current OpenSSL does not support 'ec' algorithm") + pk = _assert_pk_basic(ret, algo) + assert pk.key_size == keysize + +@@ -2174,8 +2189,12 @@ def test_private_key_managed_keysize(x509, pk_args, algo, keysize): + ) + def test_private_key_managed_existing(x509, pk_args): + ret = x509.private_key_managed(**pk_args) +- if ret.result == False and "UnsupportedAlgorithm" in ret.comment: +- pytest.skip(f"Algorithm '{pk_args['algo']}' is not supported on this OpenSSL version") ++ if ret.result == False and ( ++ "UnsupportedAlgorithm" in ret.comment or "NotImplementedError" in ret.comment ++ ): ++ pytest.skip( ++ f"Algorithm '{pk_args['algo']}' is not supported on this OpenSSL version" ++ ) + _assert_not_changed(ret) + + +@@ -2382,6 +2401,8 @@ def test_private_key_managed_follow_symlinks_changes( + pk_args["encoding"] = encoding + pk_args["algo"] = "ec" + ret = x509.private_key_managed(**pk_args) ++ if ret.result == False and "NotImplementedError" in ret.comment: ++ pytest.skip("Current OpenSSL does not support 'ec' algorithm") + assert ret.changes + assert Path(ret.name).is_symlink() == follow + +@@ -2722,7 +2743,12 @@ def _get_cert(cert, encoding="pem", passphrase=None): + def _belongs_to(cert_or_pubkey, privkey): + if isinstance(cert_or_pubkey, cx509.Certificate): + cert_or_pubkey = cert_or_pubkey.public_key() +- return x509util.is_pair(cert_or_pubkey, x509util.load_privkey(privkey)) ++ try: ++ return x509util.is_pair(cert_or_pubkey, x509util.load_privkey(privkey)) ++ except NotImplementedError: ++ pytest.skip( ++ "This OpenSSL version does not support current cryptographic algorithm" ++ ) + + + def _signed_by(cert, privkey): +diff --git a/tests/pytests/scenarios/performance/test_performance.py b/tests/pytests/scenarios/performance/test_performance.py +index 85b92ed986..6319e26ce1 100644 +--- a/tests/pytests/scenarios/performance/test_performance.py ++++ b/tests/pytests/scenarios/performance/test_performance.py +@@ -10,7 +10,13 @@ from saltfactories.utils import random_string + + from salt.version import SaltVersionsInfo, __version__ + +-pytestmark = [pytest.mark.skip_if_binaries_missing("docker")] ++pytestmark = [ ++ pytest.mark.skip_if_binaries_missing("docker"), ++ pytest.mark.skipif( ++ os.environ.get("GITHUB_ACTIONS", "") == "true", ++ reason="Cannot spawn containers in GH actions run", ++ ), ++] + + + class ContainerMaster(SaltDaemon, master.SaltMaster): +-- +2.46.1 + diff --git a/improve-error-handling-with-different-openssl-versio.patch b/improve-error-handling-with-different-openssl-versio.patch new file mode 100644 index 0000000..4d16812 --- /dev/null +++ b/improve-error-handling-with-different-openssl-versio.patch @@ -0,0 +1,98 @@ +From 4e226426d0897f2d9dc64891ced78487b181d40e Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Fri, 30 Aug 2024 14:33:51 +0200 +Subject: [PATCH] Improve error handling with different OpenSSL + versions + +* Make error checking of x509 more flexible + +for most recent cryptography and openSSL versions + +* Add test for different exception value on loading private key + +* Add fix for test_privkey_new_with_prereq on old OpenSSL +--- + salt/utils/x509.py | 3 +- + .../pytests/functional/states/test_x509_v2.py | 29 +++++++++++++++++++ + .../integration/states/test_x509_v2.py | 7 +++++ + 3 files changed, 38 insertions(+), 1 deletion(-) + +diff --git a/salt/utils/x509.py b/salt/utils/x509.py +index 5b2ae15882..f9fdca64d9 100644 +--- a/salt/utils/x509.py ++++ b/salt/utils/x509.py +@@ -695,7 +695,8 @@ def load_privkey(pk, passphrase=None, get_encoding=False): + return pk, "pem", None + return pk + except ValueError as err: +- if "Bad decrypt" in str(err): ++ str_err = str(err) ++ if "Bad decrypt" in str_err or "Could not deserialize key data" in str_err: + raise SaltInvocationError( + "Bad decrypt - is the password correct?" + ) from err +diff --git a/tests/pytests/functional/states/test_x509_v2.py b/tests/pytests/functional/states/test_x509_v2.py +index 929be014cd..47a1c555f8 100644 +--- a/tests/pytests/functional/states/test_x509_v2.py ++++ b/tests/pytests/functional/states/test_x509_v2.py +@@ -3,6 +3,8 @@ from pathlib import Path + + import pytest + ++from tests.support.mock import patch ++ + try: + import cryptography + import cryptography.x509 as cx509 +@@ -2826,3 +2828,30 @@ def _get_privkey(pk, encoding="pem", passphrase=None): + pk = base64.b64decode(pk) + return pkcs12.load_pkcs12(pk, passphrase).key + raise ValueError("Need correct encoding") ++ ++ ++@pytest.mark.usefixtures("existing_pk") ++@pytest.mark.parametrize("existing_pk", [{"passphrase": "password"}], indirect=True) ++def test_exceptions_on_calling_load_pem_private_key(x509, pk_args): ++ pk_args["passphrase"] = "hunter1" ++ pk_args["overwrite"] = True ++ ++ with patch( ++ "cryptography.hazmat.primitives.serialization.load_pem_private_key", ++ side_effect=ValueError("Bad decrypt. Incorrect password?"), ++ ): ++ ret = x509.private_key_managed(**pk_args) ++ _assert_pk_basic(ret, "rsa", passphrase="hunter1") ++ ++ with patch( ++ "cryptography.hazmat.primitives.serialization.load_pem_private_key", ++ side_effect=ValueError( ++ "Could not deserialize key data. The data may be in an incorrect format, " ++ "the provided password may be incorrect, " ++ "it may be encrypted with an unsupported algorithm, " ++ "or it may be an unsupported key type " ++ "(e.g. EC curves with explicit parameters)." ++ ), ++ ): ++ ret = x509.private_key_managed(**pk_args) ++ _assert_pk_basic(ret, "rsa", passphrase="hunter1") +diff --git a/tests/pytests/integration/states/test_x509_v2.py b/tests/pytests/integration/states/test_x509_v2.py +index 4f94341295..ad8d904c92 100644 +--- a/tests/pytests/integration/states/test_x509_v2.py ++++ b/tests/pytests/integration/states/test_x509_v2.py +@@ -195,6 +195,13 @@ Certificate: + """ + with x509_salt_master.state_tree.base.temp_file("manage_cert.sls", state): + ret = x509_salt_call_cli.run("state.apply", "manage_cert") ++ if ( ++ ret.returncode == 1 ++ and "NotImplementedError: ECDSA keys with unnamed curves" in ret.stdout ++ ): ++ pytest.skip( ++ "The version of OpenSSL doesn't support ECDSA keys with unnamed curves" ++ ) + assert ret.returncode == 0 + assert ret.data[next(iter(ret.data))]["changes"] + assert (tmp_path / "priv.key").exists() +-- +2.46.0 + diff --git a/join-masters-if-it-is-a-list-671.patch b/join-masters-if-it-is-a-list-671.patch new file mode 100644 index 0000000..2f384ab --- /dev/null +++ b/join-masters-if-it-is-a-list-671.patch @@ -0,0 +1,105 @@ +From 94973ee85d766d7e98d02d89f4c81e59b36cb716 Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Thu, 29 Aug 2024 10:01:12 +0200 +Subject: [PATCH] Join masters if it is a list (#671) + +Co-authored-by: Twangboy +--- + changelog/64170.fixed.md | 2 + + salt/utils/cloud.py | 10 +++++ + tests/pytests/unit/utils/test_cloud.py | 52 ++++++++++++++++++++++++++ + 3 files changed, 64 insertions(+) + create mode 100644 changelog/64170.fixed.md + +diff --git a/changelog/64170.fixed.md b/changelog/64170.fixed.md +new file mode 100644 +index 0000000000..1d20355bf1 +--- /dev/null ++++ b/changelog/64170.fixed.md +@@ -0,0 +1,2 @@ ++Fixed issue in salt-cloud so that multiple masters specified in the cloud ++are written to the minion config properly +diff --git a/salt/utils/cloud.py b/salt/utils/cloud.py +index b7208dc4a6..a084313059 100644 +--- a/salt/utils/cloud.py ++++ b/salt/utils/cloud.py +@@ -1202,6 +1202,16 @@ def wait_for_passwd( + time.sleep(trysleep) + + ++def _format_master_param(master): ++ """ ++ If the master is a list, we need to convert it to a comma delimited string ++ Otherwise, we just return master ++ """ ++ if isinstance(master, list): ++ return ",".join(master) ++ return master ++ ++ + def deploy_windows( + host, + port=445, +diff --git a/tests/pytests/unit/utils/test_cloud.py b/tests/pytests/unit/utils/test_cloud.py +index 550b63c974..db9d258d39 100644 +--- a/tests/pytests/unit/utils/test_cloud.py ++++ b/tests/pytests/unit/utils/test_cloud.py +@@ -605,3 +605,55 @@ def test_deploy_script_ssh_timeout(): + ssh_kwargs = root_cmd.call_args.kwargs + assert "ssh_timeout" in ssh_kwargs + assert ssh_kwargs["ssh_timeout"] == 34 ++ ++ ++@pytest.mark.parametrize( ++ "master,expected", ++ [ ++ (None, None), ++ ("single_master", "single_master"), ++ (["master1", "master2", "master3"], "master1,master2,master3"), ++ ], ++) ++def test__format_master_param(master, expected): ++ result = cloud._format_master_param(master) ++ assert result == expected ++ ++ ++@pytest.mark.skip_unless_on_windows(reason="Only applicable for Windows.") ++@pytest.mark.parametrize( ++ "master,expected", ++ [ ++ (None, None), ++ ("single_master", "single_master"), ++ (["master1", "master2", "master3"], "master1,master2,master3"), ++ ], ++) ++def test_deploy_windows_master(master, expected): ++ """ ++ Test deploy_windows with master parameter ++ """ ++ mock_true = MagicMock(return_value=True) ++ mock_tuple = MagicMock(return_value=(0, 0, 0)) ++ with patch("salt.utils.smb.get_conn", MagicMock()), patch( ++ "salt.utils.smb.mkdirs", MagicMock() ++ ), patch("salt.utils.smb.put_file", MagicMock()), patch( ++ "salt.utils.smb.delete_file", MagicMock() ++ ), patch( ++ "salt.utils.smb.delete_directory", MagicMock() ++ ), patch( ++ "time.sleep", MagicMock() ++ ), patch.object( ++ cloud, "wait_for_port", mock_true ++ ), patch.object( ++ cloud, "fire_event", MagicMock() ++ ), patch.object( ++ cloud, "wait_for_psexecsvc", mock_true ++ ), patch.object( ++ cloud, "run_psexec_command", mock_tuple ++ ) as mock: ++ cloud.deploy_windows(host="test", win_installer="install.exe", master=master) ++ expected_cmd = "c:\\salttemp\\install.exe" ++ expected_args = "/S /master={} /minion-name=None".format(expected) ++ assert mock.call_args_list[0].args[0] == expected_cmd ++ assert mock.call_args_list[0].args[1] == expected_args +-- +2.44.0 + diff --git a/make-tests-compatible-with-venv-bundle.patch b/make-tests-compatible-with-venv-bundle.patch new file mode 100644 index 0000000..10e4022 --- /dev/null +++ b/make-tests-compatible-with-venv-bundle.patch @@ -0,0 +1,883 @@ +From 25c3df7713bd2a19a0980358fa72c1c48a08a1f4 Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Wed, 7 Aug 2024 10:28:07 +0200 +Subject: [PATCH] Make tests compatible with venv bundle + +Co-authored-by: cmcmarrow +--- + tests/pytests/functional/modules/test_sdb.py | 1 + + tests/pytests/functional/modules/test_yaml.py | 2 +- + .../rthooks/test_salt_utils_vt_terminal.py | 22 +++++-- + .../pyinstaller/rthooks/test_subprocess.py | 22 +++++-- + .../utils/yamllint/test_yamllint.py | 2 +- + tests/pytests/unit/modules/test_pip.py | 63 +++++++++++++------ + .../unit/modules/test_transactional_update.py | 13 ++-- + tests/pytests/unit/states/test_pkgrepo.py | 3 +- + tests/pytests/unit/test_fileserver.py | 8 +-- + tests/pytests/unit/utils/test_gitfs.py | 18 ++++++ + tests/pytests/unit/utils/test_msgpack.py | 2 +- + tests/pytests/unit/utils/test_pycrypto.py | 25 ++++---- + tests/unit/test_config.py | 20 +++++- + tests/unit/utils/test_sdb.py | 2 +- + tests/unit/utils/test_templates.py | 34 ++++++++++ + 15 files changed, 177 insertions(+), 60 deletions(-) + +diff --git a/tests/pytests/functional/modules/test_sdb.py b/tests/pytests/functional/modules/test_sdb.py +index 5519bf8ab57..837e7515d30 100644 +--- a/tests/pytests/functional/modules/test_sdb.py ++++ b/tests/pytests/functional/modules/test_sdb.py +@@ -16,6 +16,7 @@ def minion_config_overrides(): + } + + ++@pytest.mark.skip("Great module migration") + @pytest.mark.parametrize( + "expected_value", + ( +diff --git a/tests/pytests/functional/modules/test_yaml.py b/tests/pytests/functional/modules/test_yaml.py +index 2a8fbc113ff..9aad0dfdc8c 100644 +--- a/tests/pytests/functional/modules/test_yaml.py ++++ b/tests/pytests/functional/modules/test_yaml.py +@@ -13,7 +13,7 @@ try: + import salt.modules.yaml + import salt.utils.yamllint + +- YAMLLINT_AVAILABLE = True ++ YAMLLINT_AVAILABLE = salt.utils.yamllint.has_yamllint() + except ImportError: + YAMLLINT_AVAILABLE = False + +diff --git a/tests/pytests/functional/utils/pyinstaller/rthooks/test_salt_utils_vt_terminal.py b/tests/pytests/functional/utils/pyinstaller/rthooks/test_salt_utils_vt_terminal.py +index c45b5730a8e..ea687c0776d 100644 +--- a/tests/pytests/functional/utils/pyinstaller/rthooks/test_salt_utils_vt_terminal.py ++++ b/tests/pytests/functional/utils/pyinstaller/rthooks/test_salt_utils_vt_terminal.py +@@ -8,6 +8,9 @@ import salt.utils.pyinstaller.rthooks._overrides as overrides + from tests.support import mock + from tests.support.helpers import PatchedEnviron + ++LD_LIBRARY_PATH = "" ++if os.environ.get('VIRTUAL_ENV'): ++ LD_LIBRARY_PATH = f"{os.environ.get('VIRTUAL_ENV')}/lib" + + @pytest.fixture(params=("LD_LIBRARY_PATH", "LIBPATH")) + def envvar(request): +@@ -17,9 +20,14 @@ def envvar(request): + @pytest.fixture + def meipass(envvar): + with mock.patch("salt.utils.pyinstaller.rthooks._overrides.sys") as patched_sys: +- patched_sys._MEIPASS = "{}_VALUE".format(envvar) +- assert overrides.sys._MEIPASS == "{}_VALUE".format(envvar) +- yield "{}_VALUE".format(envvar) ++ ld_path_mock_val = f"{envvar}_VALUE" ++ if envvar == "LD_LIBRARY_PATH" and LD_LIBRARY_PATH: ++ # venv-minion python wrapper hardcodes LD_LIB_PATH that ++ # we cannot overwrite from the testsuite ++ ld_path_mock_val = LD_LIBRARY_PATH ++ patched_sys._MEIPASS = ld_path_mock_val ++ assert overrides.sys._MEIPASS == ld_path_mock_val ++ yield ld_path_mock_val + assert not hasattr(sys, "_MEIPASS") + assert not hasattr(overrides.sys, "_MEIPASS") + +@@ -111,7 +119,8 @@ def test_vt_terminal_environ_cleanup(envvar, meipass): + returned_env = json.loads(buffer_o) + assert returned_env != original_env + assert envvar in returned_env +- assert returned_env[envvar] == "" ++ envvar_value = LD_LIBRARY_PATH if envvar == "LD_LIBRARY_PATH" else "" ++ assert returned_env[envvar] == envvar_value + + + def test_vt_terminal_environ_cleanup_passed_directly_not_removed(envvar, meipass): +@@ -139,4 +148,7 @@ def test_vt_terminal_environ_cleanup_passed_directly_not_removed(envvar, meipass + returned_env = json.loads(buffer_o) + assert returned_env != original_env + assert envvar in returned_env +- assert returned_env[envvar] == envvar ++ envvar_val = envvar ++ if LD_LIBRARY_PATH and envvar == "LD_LIBRARY_PATH": ++ envvar_val = LD_LIBRARY_PATH ++ assert returned_env[envvar] == envvar_val +diff --git a/tests/pytests/functional/utils/pyinstaller/rthooks/test_subprocess.py b/tests/pytests/functional/utils/pyinstaller/rthooks/test_subprocess.py +index 836e392d016..e4b5420d5e3 100644 +--- a/tests/pytests/functional/utils/pyinstaller/rthooks/test_subprocess.py ++++ b/tests/pytests/functional/utils/pyinstaller/rthooks/test_subprocess.py +@@ -9,6 +9,9 @@ import salt.utils.pyinstaller.rthooks._overrides as overrides + from tests.support import mock + from tests.support.helpers import PatchedEnviron + ++LD_LIBRARY_PATH = "" ++if os.environ.get('VIRTUAL_ENV'): ++ LD_LIBRARY_PATH = f"{os.environ.get('VIRTUAL_ENV')}/lib" + + @pytest.fixture(params=("LD_LIBRARY_PATH", "LIBPATH")) + def envvar(request): +@@ -18,9 +21,14 @@ def envvar(request): + @pytest.fixture + def meipass(envvar): + with mock.patch("salt.utils.pyinstaller.rthooks._overrides.sys") as patched_sys: +- patched_sys._MEIPASS = "{}_VALUE".format(envvar) +- assert overrides.sys._MEIPASS == "{}_VALUE".format(envvar) +- yield "{}_VALUE".format(envvar) ++ ld_path_mock_val = f"{envvar}_VALUE" ++ if envvar == "LD_LIBRARY_PATH" and LD_LIBRARY_PATH: ++ # venv-minion python wrapper hardcodes LD_LIB_PATH that ++ # we cannot overwrite from the testsuite ++ ld_path_mock_val = LD_LIBRARY_PATH ++ patched_sys._MEIPASS = ld_path_mock_val ++ assert overrides.sys._MEIPASS == ld_path_mock_val ++ yield ld_path_mock_val + assert not hasattr(sys, "_MEIPASS") + assert not hasattr(overrides.sys, "_MEIPASS") + +@@ -88,7 +96,8 @@ def test_subprocess_popen_environ_cleanup(envvar, meipass): + returned_env = json.loads(stdout) + assert returned_env != original_env + assert envvar in returned_env +- assert returned_env[envvar] == "" ++ envvar_value = LD_LIBRARY_PATH if envvar == "LD_LIBRARY_PATH" else "" ++ assert returned_env[envvar] == envvar_value + + + def test_subprocess_popen_environ_cleanup_passed_directly_not_removed(envvar, meipass): +@@ -108,4 +117,7 @@ def test_subprocess_popen_environ_cleanup_passed_directly_not_removed(envvar, me + returned_env = json.loads(stdout) + assert returned_env != original_env + assert envvar in returned_env +- assert returned_env[envvar] == envvar ++ envvar_val = envvar ++ if LD_LIBRARY_PATH and envvar == "LD_LIBRARY_PATH": ++ envvar_val = LD_LIBRARY_PATH ++ assert returned_env[envvar] == envvar_val +diff --git a/tests/pytests/functional/utils/yamllint/test_yamllint.py b/tests/pytests/functional/utils/yamllint/test_yamllint.py +index 403c6fc610e..3c730523c4d 100644 +--- a/tests/pytests/functional/utils/yamllint/test_yamllint.py ++++ b/tests/pytests/functional/utils/yamllint/test_yamllint.py +@@ -7,7 +7,7 @@ import salt.utils.versions as versions + try: + import salt.utils.yamllint as yamllint + +- YAMLLINT_AVAILABLE = True ++ YAMLLINT_AVAILABLE = yamllint.has_yamllint() + except ImportError: + YAMLLINT_AVAILABLE = False + +diff --git a/tests/pytests/unit/modules/test_pip.py b/tests/pytests/unit/modules/test_pip.py +index 4b2da77786b..fbe0dc5f1cf 100644 +--- a/tests/pytests/unit/modules/test_pip.py ++++ b/tests/pytests/unit/modules/test_pip.py +@@ -15,6 +15,10 @@ MISSING_SETUP_PY_FILE = not os.path.exists( + os.path.join(RUNTIME_VARS.CODE_DIR, "setup.py") + ) + ++TARGET = [] ++if os.environ.get('VENV_PIP_TARGET'): ++ TARGET = ["--target", os.environ.get('VENV_PIP_TARGET')] ++ + + class FakeFopen: + def __init__(self, filename): +@@ -102,6 +106,7 @@ def test_install_frozen_app(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + pkg, + ] + mock.assert_called_with( +@@ -123,6 +128,7 @@ def test_install_source_app(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + pkg, + ] + mock.assert_called_with( +@@ -143,6 +149,7 @@ def test_fix4361(python_binary): + "install", + "--requirement", + "requirements.txt", ++ *TARGET, + ] + mock.assert_called_with( + expected_cmd, +@@ -169,7 +176,7 @@ def test_install_multiple_editable(python_binary): + "git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting", + ] + +- expected = [*python_binary, "install"] ++ expected = [*python_binary, "install", *TARGET] + for item in editables: + expected.extend(["--editable", item]) + +@@ -205,7 +212,7 @@ def test_install_multiple_pkgs_and_editables(python_binary): + "git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting", + ] + +- expected = [*python_binary, "install"] ++ expected = [*python_binary, "install", *TARGET] + expected.extend(pkgs) + for item in editables: + expected.extend(["--editable", item]) +@@ -241,6 +248,7 @@ def test_install_multiple_pkgs_and_editables(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + pkgs[0], + "--editable", + editables[0], +@@ -268,7 +276,7 @@ def test_issue5940_install_multiple_pip_mirrors(python_binary): + expected = [*python_binary, "install", "--use-mirrors"] + for item in mirrors: + expected.extend(["--mirrors", item]) +- expected.append("pep8") ++ expected = [*expected, *TARGET, "pep8"] + + # Passing mirrors as a list + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) +@@ -300,6 +308,7 @@ def test_issue5940_install_multiple_pip_mirrors(python_binary): + "--use-mirrors", + "--mirrors", + mirrors[0], ++ *TARGET, + "pep8", + ] + +@@ -327,7 +336,7 @@ def test_install_with_multiple_find_links(python_binary): + expected = [*python_binary, "install"] + for item in find_links: + expected.extend(["--find-links", item]) +- expected.append(pkg) ++ expected = [*expected, *TARGET, pkg] + + # Passing mirrors as a list + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) +@@ -370,6 +379,7 @@ def test_install_with_multiple_find_links(python_binary): + "install", + "--find-links", + find_links[0], ++ *TARGET, + pkg, + ] + +@@ -435,6 +445,7 @@ def test_install_cached_requirements_used(python_binary): + "install", + "--requirement", + "my_cached_reqs", ++ *TARGET, + ] + mock.assert_called_with( + expected, +@@ -491,6 +502,7 @@ def test_install_log_argument_in_resulting_command(python_binary): + "install", + "--log", + log_path, ++ *TARGET, + pkg, + ] + mock.assert_called_with( +@@ -521,7 +533,7 @@ def test_install_timeout_argument_in_resulting_command(python_binary): + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, timeout=10) + mock.assert_called_with( +- expected + [10, pkg], ++ expected + [10, *TARGET, pkg], + saltenv="base", + runas=None, + use_vt=False, +@@ -533,7 +545,7 @@ def test_install_timeout_argument_in_resulting_command(python_binary): + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, timeout="10") + mock.assert_called_with( +- expected + ["10", pkg], ++ expected + ["10", *TARGET, pkg], + saltenv="base", + runas=None, + use_vt=False, +@@ -557,6 +569,7 @@ def test_install_index_url_argument_in_resulting_command(python_binary): + "install", + "--index-url", + index_url, ++ *TARGET, + pkg, + ] + mock.assert_called_with( +@@ -579,6 +592,7 @@ def test_install_extra_index_url_argument_in_resulting_command(python_binary): + "install", + "--extra-index-url", + extra_index_url, ++ *TARGET, + pkg, + ] + mock.assert_called_with( +@@ -595,7 +609,7 @@ def test_install_no_index_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, no_index=True) +- expected = [*python_binary, "install", "--no-index", pkg] ++ expected = [*python_binary, "install", "--no-index", *TARGET, pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -611,7 +625,7 @@ def test_install_build_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, build=build) +- expected = [*python_binary, "install", "--build", build, pkg] ++ expected = [*python_binary, "install", "--build", build, *TARGET, pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -646,6 +660,7 @@ def test_install_download_argument_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + "--download", + download, + pkg, +@@ -664,7 +679,7 @@ def test_install_no_download_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, no_download=True) +- expected = [*python_binary, "install", "--no-download", pkg] ++ expected = [*python_binary, "install", *TARGET, "--no-download", pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -691,6 +706,7 @@ def test_install_download_cache_dir_arguments_in_resulting_command(python_binary + expected = [ + *python_binary, + "install", ++ *TARGET, + cmd_arg, + download_cache, + pkg, +@@ -720,7 +736,7 @@ def test_install_source_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, source=source) +- expected = [*python_binary, "install", "--source", source, pkg] ++ expected = [*python_binary, "install", *TARGET, "--source", source, pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -739,6 +755,7 @@ def test_install_exists_action_argument_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + "--exists-action", + action, + pkg, +@@ -761,7 +778,7 @@ def test_install_install_options_argument_in_resulting_command(python_binary): + install_options = ["--exec-prefix=/foo/bar", "--install-scripts=/foo/bar/bin"] + pkg = "pep8" + +- expected = [*python_binary, "install"] ++ expected = [*python_binary, "install", *TARGET] + for item in install_options: + expected.extend(["--install-option", item]) + expected.append(pkg) +@@ -797,6 +814,7 @@ def test_install_install_options_argument_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + "--install-option", + install_options[0], + pkg, +@@ -814,7 +832,7 @@ def test_install_global_options_argument_in_resulting_command(python_binary): + global_options = ["--quiet", "--no-user-cfg"] + pkg = "pep8" + +- expected = [*python_binary, "install"] ++ expected = [*python_binary, "install", *TARGET] + for item in global_options: + expected.extend(["--global-option", item]) + expected.append(pkg) +@@ -850,6 +868,7 @@ def test_install_global_options_argument_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + "--global-option", + global_options[0], + pkg, +@@ -868,7 +887,7 @@ def test_install_upgrade_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, upgrade=True) +- expected = [*python_binary, "install", "--upgrade", pkg] ++ expected = [*python_binary, "install", *TARGET, "--upgrade", pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -886,6 +905,7 @@ def test_install_force_reinstall_argument_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + "--force-reinstall", + pkg, + ] +@@ -906,6 +926,7 @@ def test_install_ignore_installed_argument_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + "--ignore-installed", + pkg, + ] +@@ -923,7 +944,7 @@ def test_install_no_deps_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, no_deps=True) +- expected = [*python_binary, "install", "--no-deps", pkg] ++ expected = [*python_binary, "install", *TARGET, "--no-deps", pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -938,7 +959,7 @@ def test_install_no_install_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, no_install=True) +- expected = [*python_binary, "install", "--no-install", pkg] ++ expected = [*python_binary, "install", *TARGET, "--no-install", pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -954,7 +975,7 @@ def test_install_proxy_argument_in_resulting_command(python_binary): + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + pip.install(pkg, proxy=proxy) +- expected = [*python_binary, "install", "--proxy", proxy, pkg] ++ expected = [*python_binary, "install", "--proxy", proxy, *TARGET, pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -981,7 +1002,7 @@ def test_install_proxy_false_argument_in_resulting_command(python_binary): + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + with patch.dict(pip.__opts__, config_mock): + pip.install(pkg, proxy=proxy) +- expected = [*python_binary, "install", pkg] ++ expected = [*python_binary, "install", *TARGET, pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -1012,6 +1033,7 @@ def test_install_global_proxy_in_resulting_command(python_binary): + "install", + "--proxy", + proxy, ++ *TARGET, + pkg, + ] + mock.assert_called_with( +@@ -1032,6 +1054,7 @@ def test_install_multiple_requirements_arguments_in_resulting_command(python_bin + expected = [*python_binary, "install"] + for item in cached_reqs: + expected.extend(["--requirement", item]) ++ expected.extend(TARGET) + + # Passing option as a list + mock = MagicMock(return_value={"retcode": 0, "stdout": ""}) +@@ -1068,6 +1091,7 @@ def test_install_multiple_requirements_arguments_in_resulting_command(python_bin + "install", + "--requirement", + cached_reqs[0], ++ *TARGET, + ] + mock.assert_called_with( + expected, +@@ -1088,6 +1112,7 @@ def test_install_extra_args_arguments_in_resulting_command(python_binary): + expected = [ + *python_binary, + "install", ++ *TARGET, + pkg, + "--latest-pip-kwarg", + "param", +@@ -1604,7 +1629,7 @@ def test_install_pre_argument_in_resulting_command(python_binary): + with patch.dict(pip.__salt__, {"cmd.run_all": mock}): + with patch("salt.modules.pip.version", MagicMock(return_value="1.3")): + pip.install(pkg, pre_releases=True) +- expected = [*python_binary, "install", pkg] ++ expected = [*python_binary, "install", *TARGET, pkg] + mock.assert_called_with( + expected, + saltenv="base", +@@ -1620,7 +1645,7 @@ def test_install_pre_argument_in_resulting_command(python_binary): + ): + with patch("salt.modules.pip._get_pip_bin", MagicMock(return_value=["pip"])): + pip.install(pkg, pre_releases=True) +- expected = ["pip", "install", "--pre", pkg] ++ expected = ["pip", "install", *TARGET, "--pre", pkg] + mock_run_all.assert_called_with( + expected, + saltenv="base", +diff --git a/tests/pytests/unit/modules/test_transactional_update.py b/tests/pytests/unit/modules/test_transactional_update.py +index dbd72fd74bf..e0ef2abd0f3 100644 +--- a/tests/pytests/unit/modules/test_transactional_update.py ++++ b/tests/pytests/unit/modules/test_transactional_update.py +@@ -1,3 +1,4 @@ ++import os + import pytest + + import salt.loader.context +@@ -10,6 +11,10 @@ pytestmark = [ + pytest.mark.skip_on_windows(reason="Not supported on Windows"), + ] + ++SALT_CALL_BINARY = "salt-call" ++if os.environ.get('VIRTUAL_ENV'): ++ SALT_CALL_BINARY = f"{os.environ.get('VIRTUAL_ENV')}/bin/salt-call" ++ + + @pytest.fixture + def configure_loader_modules(): +@@ -379,7 +384,7 @@ def test_call_fails_function(): + "--continue", + "--quiet", + "run", +- "salt-call", ++ SALT_CALL_BINARY, + "--out", + "json", + "-l", +@@ -411,7 +416,7 @@ def test_call_success_no_reboot(): + "--continue", + "--quiet", + "run", +- "salt-call", ++ SALT_CALL_BINARY, + "--out", + "json", + "-l", +@@ -454,7 +459,7 @@ def test_call_success_reboot(): + "--continue", + "--quiet", + "run", +- "salt-call", ++ SALT_CALL_BINARY, + "--out", + "json", + "-l", +@@ -488,7 +493,7 @@ def test_call_success_parameters(): + "--continue", + "--quiet", + "run", +- "salt-call", ++ SALT_CALL_BINARY, + "--out", + "json", + "-l", +diff --git a/tests/pytests/unit/states/test_pkgrepo.py b/tests/pytests/unit/states/test_pkgrepo.py +index 5f540bd2454..14d17ad3f9f 100644 +--- a/tests/pytests/unit/states/test_pkgrepo.py ++++ b/tests/pytests/unit/states/test_pkgrepo.py +@@ -1,7 +1,6 @@ + """ + :codeauthor: Tyler Johnson + """ +- + import pytest + + import salt.states.pkgrepo as pkgrepo +@@ -390,7 +389,7 @@ def test_migrated_wrong_method(): + with patch.dict(pkgrepo.__grains__, grains), patch.dict( + pkgrepo.__salt__, salt_mock + ): +- assert pkgrepo.migrated("/mnt", method_="magic") == { ++ assert pkgrepo.migrated("/mnt", method="magic") == { + "name": "/mnt", + "result": False, + "changes": {}, +diff --git a/tests/pytests/unit/test_fileserver.py b/tests/pytests/unit/test_fileserver.py +index 8dd3ea0a27d..49be3967dc4 100644 +--- a/tests/pytests/unit/test_fileserver.py ++++ b/tests/pytests/unit/test_fileserver.py +@@ -75,9 +75,7 @@ def test_file_server_url_escape(tmp_path): + opts = { + "fileserver_backend": ["roots"], + "extension_modules": "", +- "optimization_order": [ +- 0, +- ], ++ "optimization_order": [0, 1], + "file_roots": { + "base": [fileroot], + }, +@@ -102,9 +100,7 @@ def test_file_server_serve_url_escape(tmp_path): + opts = { + "fileserver_backend": ["roots"], + "extension_modules": "", +- "optimization_order": [ +- 0, +- ], ++ "optimization_order": [0, 1], + "file_roots": { + "base": [fileroot], + }, +diff --git a/tests/pytests/unit/utils/test_gitfs.py b/tests/pytests/unit/utils/test_gitfs.py +index 2bf627049f9..bd7d74cb2b2 100644 +--- a/tests/pytests/unit/utils/test_gitfs.py ++++ b/tests/pytests/unit/utils/test_gitfs.py +@@ -3,6 +3,7 @@ import time + + import pytest + ++import salt.config + import salt.fileserver.gitfs + import salt.utils.gitfs + from salt.exceptions import FileserverConfigError +@@ -24,6 +25,23 @@ if HAS_PYGIT2: + import pygit2 + + ++@pytest.fixture ++def minion_opts(tmp_path): ++ """ ++ Default minion configuration with relative temporary paths to not require root permissions. ++ """ ++ root_dir = tmp_path / "minion" ++ opts = salt.config.DEFAULT_MINION_OPTS.copy() ++ opts["__role"] = "minion" ++ opts["root_dir"] = str(root_dir) ++ for name in ("cachedir", "pki_dir", "sock_dir", "conf_dir"): ++ dirpath = root_dir / name ++ dirpath.mkdir(parents=True) ++ opts[name] = str(dirpath) ++ opts["log_file"] = "logs/minion.log" ++ return opts ++ ++ + @pytest.mark.parametrize( + "role_name,role_class", + ( +diff --git a/tests/pytests/unit/utils/test_msgpack.py b/tests/pytests/unit/utils/test_msgpack.py +index a09b6e5b8b1..3d0b9d7fc8c 100644 +--- a/tests/pytests/unit/utils/test_msgpack.py ++++ b/tests/pytests/unit/utils/test_msgpack.py +@@ -3,7 +3,7 @@ import pytest + import salt.utils.msgpack + from tests.support.mock import MagicMock, patch + +- ++@pytest.mark.skipif(salt.utils.msgpack.version < (1, 0, 0), reason="Test requires msgpack version >= 1.0.0") + def test_load_encoding(tmp_path): + """ + test when using msgpack version >= 1.0.0 we +diff --git a/tests/pytests/unit/utils/test_pycrypto.py b/tests/pytests/unit/utils/test_pycrypto.py +index 693ad10e240..9e0b58d1b35 100644 +--- a/tests/pytests/unit/utils/test_pycrypto.py ++++ b/tests/pytests/unit/utils/test_pycrypto.py +@@ -57,21 +57,20 @@ def test_gen_hash_crypt(algorithm, expected): + """ + Test gen_hash with crypt library + """ +- with patch("salt.utils.pycrypto.methods", {}): +- ret = salt.utils.pycrypto.gen_hash( +- crypt_salt=expected["salt"], password=passwd, algorithm=algorithm +- ) +- assert ret == expected["hashed"] ++ ret = salt.utils.pycrypto.gen_hash( ++ crypt_salt=expected["salt"], password=passwd, algorithm=algorithm ++ ) ++ assert ret == expected["hashed"] + +- ret = salt.utils.pycrypto.gen_hash( +- crypt_salt=expected["badsalt"], password=passwd, algorithm=algorithm +- ) +- assert ret != expected["hashed"] ++ ret = salt.utils.pycrypto.gen_hash( ++ crypt_salt=expected["badsalt"], password=passwd, algorithm=algorithm ++ ) ++ assert ret != expected["hashed"] + +- ret = salt.utils.pycrypto.gen_hash( +- crypt_salt=None, password=passwd, algorithm=algorithm +- ) +- assert ret != expected["hashed"] ++ ret = salt.utils.pycrypto.gen_hash( ++ crypt_salt=None, password=passwd, algorithm=algorithm ++ ) ++ assert ret != expected["hashed"] + + + @pytest.mark.skipif(not salt.utils.pycrypto.HAS_CRYPT, reason="crypt not available") +diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py +index 5cc58c273d0..6995b01c892 100644 +--- a/tests/unit/test_config.py ++++ b/tests/unit/test_config.py +@@ -83,9 +83,12 @@ class SampleConfTest(DefaultConfigsBase, TestCase): + """ + master_config = SAMPLE_CONF_DIR + "master" + ret = salt.config._read_conf_file(master_config) ++ # openSUSE modified the default config in ++ # https://github.com/opensuse/salt/commit/6ffbf7fcc178f32c670b177b25ed64658c59f1bf ++ expected_config = {"user": "salt", "syndic_user": "salt"} + self.assertEqual( + ret, +- {}, ++ expected_config, + "Sample config file '{}' must be commented out.".format(master_config), + ) + +@@ -347,7 +350,10 @@ class ConfigTestCase(TestCase, AdaptedConfigurationTestCaseMixin): + + with patched_environ(SALT_MINION_CONFIG=env_fpath): + # Should load from env variable, not the default configuration file +- config = salt.config.minion_config("{}/minion".format(CONFIG_DIR)) ++ # Override defaults from venv-minion conf ++ defaults = salt.config.DEFAULT_MINION_OPTS.copy() ++ defaults["default_include"] = "" ++ config = salt.config.minion_config("{}/minion".format(CONFIG_DIR), defaults=defaults) + self.assertEqual(config["log_file"], env_fpath) + + root_dir = os.path.join(tempdir, "foo", "bar") +@@ -1946,6 +1952,11 @@ class APIConfigTestCase(DefaultConfigsBase, TestCase): + if salt.utils.platform.is_windows(): + expected = "{}\\var\\log\\salt\\api".format(RUNTIME_VARS.TMP_ROOT_DIR) + ++ if os.environ.get("VIRTUAL_ENV"): ++ # venv bundle configures --salt-logs-dir=%{_localstatedir}/log ++ # in the RPM spec file ++ expected = expected.replace("/salt/api", "/api") ++ + ret = salt.config.api_config("/some/fake/path") + self.assertEqual(ret["log_file"], expected) + +@@ -2017,6 +2028,11 @@ class APIConfigTestCase(DefaultConfigsBase, TestCase): + mock_pid = "c:\\mock\\root\\var\\run\\salt-api.pid" + mock_master_config["root_dir"] = "c:\\mock\\root" + ++ if os.environ.get("VIRTUAL_ENV"): ++ # venv bundle configures --salt-logs-dir=%{_localstatedir}/log ++ # in the RPM spec file ++ mock_log = mock_log.replace("/salt", "") ++ + with patch( + "salt.config.client_config", MagicMock(return_value=mock_master_config) + ): +diff --git a/tests/unit/utils/test_sdb.py b/tests/unit/utils/test_sdb.py +index 87886cbc521..69cbda07beb 100644 +--- a/tests/unit/utils/test_sdb.py ++++ b/tests/unit/utils/test_sdb.py +@@ -49,7 +49,7 @@ class SdbTestCase(TestCase, LoaderModuleMockMixin): + # test with SQLite database write and read + + def test_sqlite_get_found(self): +- expected = {b"name": b"testone", b"number": 46} ++ expected = {"name": "testone", "number": 46} + sdb.sdb_set("sdb://test_sdb_data/test1", expected, self.sdb_opts) + resp = sdb.sdb_get("sdb://test_sdb_data/test1", self.sdb_opts) + self.assertEqual(resp, expected) +diff --git a/tests/unit/utils/test_templates.py b/tests/unit/utils/test_templates.py +index 264b4ae801d..604395f5e08 100644 +--- a/tests/unit/utils/test_templates.py ++++ b/tests/unit/utils/test_templates.py +@@ -1,6 +1,7 @@ + """ + Unit tests for salt.utils.templates.py + """ ++ + import logging + import os + import sys +@@ -22,6 +23,20 @@ try: + except ImportError: + HAS_CHEETAH = False + ++try: ++ import genshi as _ ++ ++ HAS_GENSHI = True ++except ImportError: ++ HAS_GENSHI = False ++ ++try: ++ import mako as _ ++ ++ HAS_MAKO = True ++except ImportError: ++ HAS_MAKO = False ++ + log = logging.getLogger(__name__) + + +@@ -83,16 +98,19 @@ class RenderTestCase(TestCase): + assert res == expected + + ### Tests for mako template ++ @pytest.mark.skipif(not HAS_MAKO, reason="Mako module not available for testing") + def test_render_mako_sanity(self): + tmpl = """OK""" + res = salt.utils.templates.render_mako_tmpl(tmpl, dict(self.context)) + self.assertEqual(res, "OK") + ++ @pytest.mark.skipif(not HAS_MAKO, reason="Mako module not available for testing") + def test_render_mako_evaluate(self): + tmpl = """${ "OK" }""" + res = salt.utils.templates.render_mako_tmpl(tmpl, dict(self.context)) + self.assertEqual(res, "OK") + ++ @pytest.mark.skipif(not HAS_MAKO, reason="Mako module not available for testing") + def test_render_mako_evaluate_multi(self): + tmpl = """ + % if 1: +@@ -103,6 +121,7 @@ class RenderTestCase(TestCase): + stripped = res.strip() + self.assertEqual(stripped, "OK") + ++ @pytest.mark.skipif(not HAS_MAKO, reason="Mako module not available for testing") + def test_render_mako_variable(self): + tmpl = """${ var }""" + +@@ -152,21 +171,33 @@ class RenderTestCase(TestCase): + self.assertEqual(res, "OK") + + ### Tests for genshi template (xml-based) ++ @pytest.mark.skipif( ++ not HAS_GENSHI, reason="Genshi module not available for testing" ++ ) + def test_render_genshi_sanity(self): + tmpl = """OK""" + res = salt.utils.templates.render_genshi_tmpl(tmpl, dict(self.context)) + self.assertEqual(res, "OK") + ++ @pytest.mark.skipif( ++ not HAS_GENSHI, reason="Genshi module not available for testing" ++ ) + def test_render_genshi_evaluate(self): + tmpl = """${ "OK" }""" + res = salt.utils.templates.render_genshi_tmpl(tmpl, dict(self.context)) + self.assertEqual(res, "OK") + ++ @pytest.mark.skipif( ++ not HAS_GENSHI, reason="Genshi module not available for testing" ++ ) + def test_render_genshi_evaluate_condition(self): + tmpl = """OK""" + res = salt.utils.templates.render_genshi_tmpl(tmpl, dict(self.context)) + self.assertEqual(res, "OK") + ++ @pytest.mark.skipif( ++ not HAS_GENSHI, reason="Genshi module not available for testing" ++ ) + def test_render_genshi_variable(self): + tmpl = """$var""" + +@@ -175,6 +206,9 @@ class RenderTestCase(TestCase): + res = salt.utils.templates.render_genshi_tmpl(tmpl, ctx) + self.assertEqual(res, "OK") + ++ @pytest.mark.skipif( ++ not HAS_GENSHI, reason="Genshi module not available for testing" ++ ) + def test_render_genshi_variable_replace(self): + tmpl = """not ok""" + +-- +2.46.0 + diff --git a/prevent-using-syncwrapper-with-no-reason.patch b/prevent-using-syncwrapper-with-no-reason.patch new file mode 100644 index 0000000..a2db38f --- /dev/null +++ b/prevent-using-syncwrapper-with-no-reason.patch @@ -0,0 +1,25 @@ +From 936c298c177a50783b080c445745bedf77050cb0 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Wed, 25 Sep 2024 14:04:40 +0300 +Subject: [PATCH] Prevent using SyncWrapper with no reason + +--- + salt/channel/server.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/channel/server.py b/salt/channel/server.py +index b6d51fef08..f1b6f701a9 100644 +--- a/salt/channel/server.py ++++ b/salt/channel/server.py +@@ -86,7 +86,7 @@ class ReqServerChannel: + # other things needed for _auth + # Create the event manager + self.event = salt.utils.event.get_master_event( +- self.opts, self.opts["sock_dir"], listen=False ++ self.opts, self.opts["sock_dir"], listen=False, io_loop=io_loop + ) + self.auto_key = salt.daemons.masterapi.AutoKey(self.opts) + # only create a con_cache-client if the con_cache is active +-- +2.46.1 + diff --git a/remove-redundant-run_func-from-salt.master.mworker._.patch b/remove-redundant-run_func-from-salt.master.mworker._.patch new file mode 100644 index 0000000..5fd4641 --- /dev/null +++ b/remove-redundant-run_func-from-salt.master.mworker._.patch @@ -0,0 +1,224 @@ +From ff789d88541954e4fc1678dff728bc6a3ea7472e Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Fri, 30 Aug 2024 14:30:27 +0200 +Subject: [PATCH] Remove redundant run_func from + salt.master.MWorker._handle_aes + +* New request context + +* Fix docs + +* Remove redundant run_func from salt.master.MWorker._handle_aes + +* Get rid of run_func in salt.Minion._target + +--------- + +Co-authored-by: Daniel A. Wozniak +--- + doc/topics/releases/3007.0.rst | 0 + salt/_logging/impl.py | 15 ++++++--- + salt/master.py | 12 ++----- + salt/minion.py | 10 ++---- + salt/utils/ctx.py | 60 +++++++++++----------------------- + 5 files changed, 34 insertions(+), 63 deletions(-) + create mode 100644 doc/topics/releases/3007.0.rst + +diff --git a/doc/topics/releases/3007.0.rst b/doc/topics/releases/3007.0.rst +new file mode 100644 +index 0000000000..e69de29bb2 +diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py +index 1d71cb8be8..4d1ebd2495 100644 +--- a/salt/_logging/impl.py ++++ b/salt/_logging/impl.py +@@ -26,6 +26,8 @@ GARBAGE = logging.GARBAGE = 1 + QUIET = logging.QUIET = 1000 + + import salt.defaults.exitcodes # isort:skip pylint: disable=unused-import ++import salt.utils.ctx ++ + from salt._logging.handlers import DeferredStreamHandler # isort:skip + from salt._logging.handlers import RotatingFileHandler # isort:skip + from salt._logging.handlers import StreamHandler # isort:skip +@@ -33,7 +35,6 @@ from salt._logging.handlers import SysLogHandler # isort:skip + from salt._logging.handlers import WatchedFileHandler # isort:skip + from salt._logging.mixins import LoggingMixinMeta # isort:skip + from salt.exceptions import LoggingRuntimeError # isort:skip +-from salt.utils.ctx import RequestContext # isort:skip + from salt.utils.immutabletypes import freeze, ImmutableDict # isort:skip + from salt.utils.textformat import TextFormat # isort:skip + +@@ -242,10 +243,14 @@ class SaltLoggingClass(LOGGING_LOGGER_CLASS, metaclass=LoggingMixinMeta): + if extra is None: + extra = {} + +- # pylint: disable=no-member +- current_jid = RequestContext.current.get("data", {}).get("jid", None) +- log_fmt_jid = RequestContext.current.get("opts", {}).get("log_fmt_jid", None) +- # pylint: enable=no-member ++ current_jid = ( ++ salt.utils.ctx.get_request_context().get("data", {}).get("jid", None) ++ ) ++ log_fmt_jid = ( ++ salt.utils.ctx.get_request_context() ++ .get("opts", {}) ++ .get("log_fmt_jid", None) ++ ) + + if current_jid is not None: + extra["jid"] = current_jid +diff --git a/salt/master.py b/salt/master.py +index d7182d10b5..49cfb68860 100644 +--- a/salt/master.py ++++ b/salt/master.py +@@ -38,6 +38,7 @@ import salt.state + import salt.utils.args + import salt.utils.atomicfile + import salt.utils.crypt ++import salt.utils.ctx + import salt.utils.event + import salt.utils.files + import salt.utils.gitfs +@@ -58,10 +59,8 @@ import salt.wheel + from salt.cli.batch_async import BatchAsync, batch_async_required + from salt.config import DEFAULT_INTERVAL + from salt.defaults import DEFAULT_TARGET_DELIM +-from salt.ext.tornado.stack_context import StackContext + from salt.transport import TRANSPORTS + from salt.utils.channel import iter_transport_opts +-from salt.utils.ctx import RequestContext + from salt.utils.debug import ( + enable_sigusr1_handler, + enable_sigusr2_handler, +@@ -1108,13 +1107,8 @@ class MWorker(salt.utils.process.SignalHandlingProcess): + start = time.time() + self.stats[cmd]["runs"] += 1 + +- def run_func(data): +- return self.aes_funcs.run_func(data["cmd"], data) +- +- with StackContext( +- functools.partial(RequestContext, {"data": data, "opts": self.opts}) +- ): +- ret = run_func(data) ++ with salt.utils.ctx.request_context({"data": data, "opts": self.opts}): ++ ret = self.aes_funcs.run_func(data["cmd"], data) + + if self.opts["master_stats"]: + self._post_stats(start, cmd) +diff --git a/salt/minion.py b/salt/minion.py +index 2ccd0cd5a9..e21a017cfd 100644 +--- a/salt/minion.py ++++ b/salt/minion.py +@@ -39,6 +39,7 @@ import salt.transport + import salt.utils.args + import salt.utils.context + import salt.utils.crypt ++import salt.utils.ctx + import salt.utils.data + import salt.utils.dictdiffer + import salt.utils.dictupdate +@@ -70,7 +71,6 @@ from salt.exceptions import ( + SaltSystemExit, + ) + from salt.template import SLS_ENCODING +-from salt.utils.ctx import RequestContext + from salt.utils.debug import enable_sigusr1_handler + from salt.utils.event import tagify + from salt.utils.network import parse_host_port +@@ -1805,18 +1805,12 @@ class Minion(MinionBase): + uid = salt.utils.user.get_uid(user=opts.get("user", None)) + minion_instance.proc_dir = get_proc_dir(opts["cachedir"], uid=uid) + +- def run_func(minion_instance, opts, data): ++ with salt.utils.ctx.request_context({"data": data, "opts": opts}): + if isinstance(data["fun"], tuple) or isinstance(data["fun"], list): + return Minion._thread_multi_return(minion_instance, opts, data) + else: + return Minion._thread_return(minion_instance, opts, data) + +- with salt.ext.tornado.stack_context.StackContext( +- functools.partial(RequestContext, {"data": data, "opts": opts}) +- ): +- with salt.ext.tornado.stack_context.StackContext(minion_instance.ctx): +- run_func(minion_instance, opts, data) +- + def _execute_job_function( + self, function_name, function_args, executors, opts, data + ): +diff --git a/salt/utils/ctx.py b/salt/utils/ctx.py +index a9c0931bd8..2f4b5b4c9b 100644 +--- a/salt/utils/ctx.py ++++ b/salt/utils/ctx.py +@@ -1,49 +1,27 @@ +-import threading ++import contextlib + ++try: ++ # Try the stdlib C extension first ++ import _contextvars as contextvars ++except ImportError: ++ # Py<3.7 ++ import contextvars + +-class ClassProperty(property): +- """ +- Use a classmethod as a property +- http://stackoverflow.com/a/1383402/1258307 +- """ ++DEFAULT_CTX_VAR = "request_ctxvar" ++request_ctxvar = contextvars.ContextVar(DEFAULT_CTX_VAR) + +- def __get__(self, cls, owner): +- return self.fget.__get__(None, owner)() # pylint: disable=no-member + +- +-class RequestContext: ++@contextlib.contextmanager ++def request_context(data): + """ +- A context manager that saves some per-thread state globally. +- Intended for use with Tornado's StackContext. +- https://gist.github.com/simon-weber/7755289 +- Simply import this class into any module and access the current request handler by this +- class's class method property 'current'. If it returns None, there's no active request. +- .. code:: python +- from raas.utils.ctx import RequestContext +- current_request_handler = RequestContext.current ++ A context manager that sets and un-sets the loader context + """ ++ tok = request_ctxvar.set(data) ++ try: ++ yield ++ finally: ++ request_ctxvar.reset(tok) + +- _state = threading.local() +- _state.current_request = {} +- +- def __init__(self, current_request): +- self._current_request = current_request +- +- @ClassProperty +- @classmethod +- def current(cls): +- if not hasattr(cls._state, "current_request"): +- return {} +- return cls._state.current_request +- +- def __enter__(self): +- self._prev_request = self.__class__.current +- self.__class__._state.current_request = self._current_request +- +- def __exit__(self, *exc): +- self.__class__._state.current_request = self._prev_request +- del self._prev_request +- return False + +- def __call__(self): +- return self ++def get_request_context(): ++ return request_ctxvar.get({}) +-- +2.46.0 + diff --git a/replace-use-of-pygit2-deprecated-and-removed-1.15.0-.patch b/replace-use-of-pygit2-deprecated-and-removed-1.15.0-.patch new file mode 100644 index 0000000..789a7ea --- /dev/null +++ b/replace-use-of-pygit2-deprecated-and-removed-1.15.0-.patch @@ -0,0 +1,122 @@ +From 3f3c8d80427c9d90bea5fbca785b210260d33a0f Mon Sep 17 00:00:00 2001 +From: Marek Czernek +Date: Wed, 21 Aug 2024 16:15:02 +0200 +Subject: [PATCH] Replace use of pygit2 deprecated and removed (1.15.0) + oid with id (#673) + +Co-authored-by: David Murphy +--- + salt/utils/gitfs.py | 31 ++++++++++++++++--------------- + 1 file changed, 16 insertions(+), 15 deletions(-) + +diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py +index 061647edaca..f3902c1f19a 100644 +--- a/salt/utils/gitfs.py ++++ b/salt/utils/gitfs.py +@@ -1683,7 +1683,7 @@ class Pygit2(GitProvider): + # remote ref. + self.repo.checkout(checkout_ref) + if branch: +- self.repo.reset(oid, pygit2.GIT_RESET_HARD) ++ self.repo.reset(pygit2_id, pygit2.GIT_RESET_HARD) + return True + except GitLockError as exc: + if exc.errno == errno.EEXIST: +@@ -1714,11 +1714,11 @@ class Pygit2(GitProvider): + tag_ref = "refs/tags/" + tgt_ref + if remote_ref in refs: + # Get commit id for the remote ref +- oid = self.peel(self.repo.lookup_reference(remote_ref)).id ++ pygit2_id = self.peel(self.repo.lookup_reference(remote_ref)).id + if local_ref not in refs: + # No local branch for this remote, so create one and point + # it at the commit id of the remote ref +- self.repo.create_reference(local_ref, oid) ++ self.repo.create_reference(local_ref, pygit2_id) + + try: + target_sha = self.peel(self.repo.lookup_reference(remote_ref)).hex +@@ -1749,7 +1749,8 @@ class Pygit2(GitProvider): + # cachedir). + head_ref = local_head.target + # If head_ref is not a string, it will point to a +- # pygit2.Oid object and we are in detached HEAD mode. ++ # pygit2.id object (oid is deprecated and removed) and ++ # we are in detached HEAD mode. + # Therefore, there is no need to add a local reference. If + # head_ref == local_ref, then the local reference for HEAD + # in refs/heads/ already exists and again, no need to add. +@@ -1918,10 +1919,10 @@ class Pygit2(GitProvider): + the empty directories within it in the "blobs" list + """ + for entry in iter(tree): +- if entry.oid not in self.repo: ++ if entry.id not in self.repo: + # Entry is a submodule, skip it + continue +- blob = self.repo[entry.oid] ++ blob = self.repo[entry.id] + if not isinstance(blob, pygit2.Tree): + continue + blobs.append( +@@ -1940,8 +1941,8 @@ class Pygit2(GitProvider): + return ret + if self.root(tgt_env): + try: +- oid = tree[self.root(tgt_env)].oid +- tree = self.repo[oid] ++ pygit2_id = tree[self.root(tgt_env)].id ++ tree = self.repo[pygit2_id] + except KeyError: + return ret + if not isinstance(tree, pygit2.Tree): +@@ -2056,17 +2057,17 @@ class Pygit2(GitProvider): + the file paths and symlink info in the "blobs" dict + """ + for entry in iter(tree): +- if entry.oid not in self.repo: ++ if entry.id not in self.repo: + # Entry is a submodule, skip it + continue +- obj = self.repo[entry.oid] ++ obj = self.repo[entry.id] + if isinstance(obj, pygit2.Blob): + repo_path = salt.utils.path.join( + prefix, entry.name, use_posixpath=True + ) + blobs.setdefault("files", []).append(repo_path) + if stat.S_ISLNK(tree[entry.name].filemode): +- link_tgt = self.repo[tree[entry.name].oid].data ++ link_tgt = self.repo[tree[entry.name].id].data + blobs.setdefault("symlinks", {})[repo_path] = link_tgt + elif isinstance(obj, pygit2.Tree): + _traverse( +@@ -2085,8 +2086,8 @@ class Pygit2(GitProvider): + try: + # This might need to be changed to account for a root that + # spans more than one directory +- oid = tree[self.root(tgt_env)].oid +- tree = self.repo[oid] ++ pygit2_id = tree[self.root(tgt_env)].id ++ tree = self.repo[pygit2_id] + except KeyError: + return files, symlinks + if not isinstance(tree, pygit2.Tree): +@@ -2130,12 +2131,12 @@ class Pygit2(GitProvider): + # path's object ID will be the target of the symlink. Follow + # the symlink and set path to the location indicated + # in the blob data. +- link_tgt = self.repo[entry.oid].data ++ link_tgt = self.repo[entry.id].data + path = salt.utils.path.join( + os.path.dirname(path), link_tgt, use_posixpath=True + ) + else: +- blob = self.repo[entry.oid] ++ blob = self.repo[entry.id] + if isinstance(blob, pygit2.Tree): + # Path is a directory, not a file. + blob = None +-- +2.46.0 + diff --git a/revert-the-change-making-reactor-less-blocking-bsc-1.patch b/revert-the-change-making-reactor-less-blocking-bsc-1.patch new file mode 100644 index 0000000..3212dca --- /dev/null +++ b/revert-the-change-making-reactor-less-blocking-bsc-1.patch @@ -0,0 +1,106 @@ +From c00801d2f9807e49769d0e0d848ec12be555dbc1 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Wed, 25 Sep 2024 14:07:05 +0300 +Subject: [PATCH] Revert the change making reactor less blocking + (bsc#1230322) + +This reverts commit 0d35f09288700f5c961567442c3fcc25838b8de4. +--- + salt/utils/reactor.py | 45 ++++++++++++++++--------------------------- + 1 file changed, 17 insertions(+), 28 deletions(-) + +diff --git a/salt/utils/reactor.py b/salt/utils/reactor.py +index 78adad34da..19420a51cf 100644 +--- a/salt/utils/reactor.py ++++ b/salt/utils/reactor.py +@@ -1,12 +1,10 @@ + """ + Functions which implement running reactor jobs + """ +- + import fnmatch + import glob + import logging + import os +-from threading import Lock + + import salt.client + import salt.defaults.exitcodes +@@ -196,6 +194,13 @@ class Reactor(salt.utils.process.SignalHandlingProcess, salt.state.Compiler): + self.resolve_aliases(chunks) + return chunks + ++ def call_reactions(self, chunks): ++ """ ++ Execute the reaction state ++ """ ++ for chunk in chunks: ++ self.wrap.run(chunk) ++ + def run(self): + """ + Enter into the server loop +@@ -213,7 +218,7 @@ class Reactor(salt.utils.process.SignalHandlingProcess, salt.state.Compiler): + ) as event: + self.wrap = ReactWrap(self.opts) + +- for data in event.iter_events(full=True, auto_reconnect=True): ++ for data in event.iter_events(full=True): + # skip all events fired by ourselves + if data["data"].get("user") == self.wrap.event_user: + continue +@@ -263,9 +268,15 @@ class Reactor(salt.utils.process.SignalHandlingProcess, salt.state.Compiler): + if not self.is_leader: + continue + else: +- self.wrap.call_reactions( +- data, self.list_reactors, self.reactions +- ) ++ reactors = self.list_reactors(data["tag"]) ++ if not reactors: ++ continue ++ chunks = self.reactions(data["tag"], data["data"], reactors) ++ if chunks: ++ try: ++ self.call_reactions(chunks) ++ except SystemExit: ++ log.warning("Exit ignored by reactor") + + + class ReactWrap: +@@ -286,7 +297,6 @@ class ReactWrap: + + def __init__(self, opts): + self.opts = opts +- self._run_lock = Lock() + if ReactWrap.client_cache is None: + ReactWrap.client_cache = salt.utils.cache.CacheDict( + opts["reactor_refresh_interval"] +@@ -470,24 +480,3 @@ class ReactWrap: + Wrap LocalCaller to execute remote exec functions locally on the Minion + """ + self.client_cache["caller"].cmd(fun, *kwargs["arg"], **kwargs["kwarg"]) +- +- def _call_reactions(self, data, list_reactors, get_reactions): +- reactors = list_reactors(data["tag"]) +- if not reactors: +- return +- chunks = get_reactions(data["tag"], data["data"], reactors) +- if not chunks: +- return +- with self._run_lock: +- try: +- for chunk in chunks: +- self.run(chunk) +- except Exception as exc: # pylint: disable=broad-except +- log.error( +- "Exception while calling the reactions: %s", exc, exc_info=True +- ) +- +- def call_reactions(self, data, list_reactors, get_reactions): +- return self.pool.fire_async( +- self._call_reactions, args=(data, list_reactors, get_reactions) +- ) +-- +2.46.1 + diff --git a/salt.changes b/salt.changes index 7ac6ade..1506adb 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,44 @@ +------------------------------------------------------------------- +Wed Oct 2 12:09:33 UTC 2024 - Yeray Gutiérrez Cedrés + +- Fix failing x509 tests with OpenSSL < 1.1 +- Avoid explicit reading of /etc/salt/minion (bsc#1220357) +- Allow NamedLoaderContexts to be returned from loader +- Revert the change making reactor less blocking (bsc#1230322) +- Use --cachedir for extension_modules in salt-call (bsc#1226141) +- Prevent using SyncWrapper with no reason +- Fix the SELinux context for Salt Minion service (bsc#1219041) +- Set contextvars as a build requirement for package +- Increase warn_until_date date for code we still support +- The test_debian test now uses port 80 for ubuntu keyserver +- Fix too frequent systemd service restart in test_system test +- Avoid crash on wrong output of systemctl version (bsc#1229539) +- Improve error handling with different OpenSSL versions +- Remove redundant run_func from salt.master.MWorker._handle_aes +- Fix cloud minion configuration for multiple masters (bsc#1229109) +- Use Pygit2 id instead of deprecated oid in gitfs +- Fix few failing tests to work with both Salt and Salt bundle +- Skip testing unsupported OpenSSL crypto algorithms + +- Added: + * skip-more-tests-related-to-old-openssl-algorithms.patch + * fix-the-selinux-context-for-salt-minion-service-bsc-.patch + * allow-namedloadercontexts-to-be-returned-from-loader.patch + * join-masters-if-it-is-a-list-671.patch + * remove-redundant-run_func-from-salt.master.mworker._.patch + * prevent-using-syncwrapper-with-no-reason.patch + * fix-test_debian-to-work-in-our-infrastructure-676.patch + * fix-x509-test-fails-on-old-openssl-systems-682.patch + * make-tests-compatible-with-venv-bundle.patch + * avoid-crash-on-wrong-output-of-systemctl-version-bsc.patch + * revert-the-change-making-reactor-less-blocking-bsc-1.patch + * avoid-explicit-reading-of-etc-salt-minion-bsc-122035.patch + * improve-error-handling-with-different-openssl-versio.patch + * replace-use-of-pygit2-deprecated-and-removed-1.15.0-.patch + * fix-test_system-flaky-setup_teardown-fn.patch + * use-cachedir-for-extension_modules-in-salt-call-bsc-.patch + * fix-deprecated-code-677.patch + ------------------------------------------------------------------- Fri Aug 2 09:00:07 UTC 2024 - Yeray Gutiérrez Cedrés diff --git a/salt.spec b/salt.spec index 374f1f8..ebeeae4 100644 --- a/salt.spec +++ b/salt.spec @@ -410,6 +410,44 @@ Patch124: some-more-small-tests-fixes-enhancements-661.patch Patch125: test_vultrpy-adjust-test-expectation-to-prevent-fail.patch # PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/66698 Patch126: firewalld-normalize-new-rich-rules-before-comparing-.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/669 +Patch127: skip-more-tests-related-to-old-openssl-algorithms.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/662 +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66730 +Patch128: make-tests-compatible-with-venv-bundle.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66743 +Patch129: replace-use-of-pygit2-deprecated-and-removed-1.15.0-.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/671 +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64173 +Patch130: join-masters-if-it-is-a-list-671.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66509 +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/0c3ebc0795f9c2adec90118281343cae3070e0f6 +Patch131: remove-redundant-run_func-from-salt.master.mworker._.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66818 +Patch132: improve-error-handling-with-different-openssl-versio.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66856 +Patch133: avoid-crash-on-wrong-output-of-systemctl-version-bsc.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66861 +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/675 +Patch134: fix-test_system-flaky-setup_teardown-fn.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/676 +Patch135: fix-test_debian-to-work-in-our-infrastructure-676.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/677 +Patch136: fix-deprecated-code-677.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66780 +Patch137: fix-the-selinux-context-for-salt-minion-service-bsc-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66510 +Patch138: prevent-using-syncwrapper-with-no-reason.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66742 +Patch139: use-cachedir-for-extension_modules-in-salt-call-bsc-.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/680 +Patch140: revert-the-change-making-reactor-less-blocking-bsc-1.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/66649 +Patch141: allow-namedloadercontexts-to-be-returned-from-loader.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/d54407ba6dc664e5e5f3f613e27ae24f828c9648 +Patch142: avoid-explicit-reading-of-etc-salt-minion-bsc-122035.patch +# PATCH-FIX_UPSTREAM: https://github.com/openSUSE/salt/pull/682 +Patch143: fix-x509-test-fails-on-old-openssl-systems-682.patch ### IMPORTANT: The line below is used as a snippet marker. Do not touch it. ### SALT PATCHES LIST END @@ -528,6 +566,7 @@ BuildRequires: python3-requests >= 1.0.0 BuildRequires: python3-distro BuildRequires: python3-looseversion BuildRequires: python3-packaging +BuildRequires: python3-contextvars # requirements/zeromq.txt %if %{with test} diff --git a/skip-more-tests-related-to-old-openssl-algorithms.patch b/skip-more-tests-related-to-old-openssl-algorithms.patch new file mode 100644 index 0000000..485db8a --- /dev/null +++ b/skip-more-tests-related-to-old-openssl-algorithms.patch @@ -0,0 +1,97 @@ +From 63ff8ce775eec43b2f768b72fba4154c7832b1f7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Yeray=20Guti=C3=A9rrez=20Cedr=C3=A9s?= + +Date: Wed, 7 Aug 2024 08:54:24 +0100 +Subject: [PATCH] Skip more tests related to old OpenSSL algorithms + +* Skip more tests related to old OpenSSL algorithms + +* Check the comment from state apply ret instead of exception + +--------- + +Co-authored-by: vzhestkov +--- + tests/pytests/functional/modules/test_x509_v2.py | 10 ++++++++-- + tests/pytests/functional/states/test_x509_v2.py | 9 +++++++++ + 2 files changed, 17 insertions(+), 2 deletions(-) + +diff --git a/tests/pytests/functional/modules/test_x509_v2.py b/tests/pytests/functional/modules/test_x509_v2.py +index c060ad2971c..2e8152d04a3 100644 +--- a/tests/pytests/functional/modules/test_x509_v2.py ++++ b/tests/pytests/functional/modules/test_x509_v2.py +@@ -1400,7 +1400,10 @@ def test_create_csr_raw(x509, rsa_privkey): + @pytest.mark.slow_test + @pytest.mark.parametrize("algo", ["rsa", "ec", "ed25519", "ed448"]) + def test_create_private_key(x509, algo): +- res = x509.create_private_key(algo=algo) ++ try: ++ res = x509.create_private_key(algo=algo) ++ except UnsupportedAlgorithm: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + assert res.startswith("-----BEGIN PRIVATE KEY-----") + + +@@ -1408,7 +1411,10 @@ def test_create_private_key(x509, algo): + @pytest.mark.parametrize("algo", ["rsa", "ec", "ed25519", "ed448"]) + def test_create_private_key_with_passphrase(x509, algo): + passphrase = "hunter2" +- res = x509.create_private_key(algo=algo, passphrase=passphrase) ++ try: ++ res = x509.create_private_key(algo=algo, passphrase=passphrase) ++ except UnsupportedAlgorithm: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + assert res.startswith("-----BEGIN ENCRYPTED PRIVATE KEY-----") + # ensure it can be loaded + x509.get_private_key_size(res, passphrase=passphrase) +diff --git a/tests/pytests/functional/states/test_x509_v2.py b/tests/pytests/functional/states/test_x509_v2.py +index e74bdd73f37..929be014cdb 100644 +--- a/tests/pytests/functional/states/test_x509_v2.py ++++ b/tests/pytests/functional/states/test_x509_v2.py +@@ -6,6 +6,7 @@ import pytest + try: + import cryptography + import cryptography.x509 as cx509 ++ from cryptography.exceptions import UnsupportedAlgorithm + from cryptography.hazmat.primitives import hashes + from cryptography.hazmat.primitives.asymmetric import ec, ed448, ed25519, rsa + from cryptography.hazmat.primitives.serialization import ( +@@ -691,6 +692,8 @@ def existing_csr_exts(x509, csr_args, csr_args_exts, ca_key, rsa_privkey, reques + def existing_pk(x509, pk_args, request): + pk_args.update(request.param) + ret = x509.private_key_managed(**pk_args) ++ if ret.result == False and "UnsupportedAlgorithm" in ret.comment: ++ pytest.skip(f"Algorithm '{pk_args['algo']}' is not supported on this OpenSSL version") + _assert_pk_basic( + ret, + pk_args.get("algo", "rsa"), +@@ -2140,6 +2143,8 @@ def test_private_key_managed(x509, pk_args, algo, encoding, passphrase): + pk_args["encoding"] = encoding + pk_args["passphrase"] = passphrase + ret = x509.private_key_managed(**pk_args) ++ if ret.result == False and "UnsupportedAlgorithm" in ret.comment: ++ pytest.skip(f"Algorithm '{algo}' is not supported on this OpenSSL version") + _assert_pk_basic(ret, algo, encoding, passphrase) + + +@@ -2167,6 +2172,8 @@ def test_private_key_managed_keysize(x509, pk_args, algo, keysize): + ) + def test_private_key_managed_existing(x509, pk_args): + ret = x509.private_key_managed(**pk_args) ++ if ret.result == False and "UnsupportedAlgorithm" in ret.comment: ++ pytest.skip(f"Algorithm '{pk_args['algo']}' is not supported on this OpenSSL version") + _assert_not_changed(ret) + + +@@ -2194,6 +2201,8 @@ def test_private_key_managed_existing_new_with_passphrase_change(x509, pk_args): + def test_private_key_managed_algo_change(x509, pk_args): + pk_args["algo"] = "ed25519" + ret = x509.private_key_managed(**pk_args) ++ if ret.result == False and "UnsupportedAlgorithm" in ret.comment: ++ pytest.skip("Algorithm 'ed25519' is not supported on this OpenSSL version") + _assert_pk_basic(ret, "ed25519") + + +-- +2.46.0 + diff --git a/use-cachedir-for-extension_modules-in-salt-call-bsc-.patch b/use-cachedir-for-extension_modules-in-salt-call-bsc-.patch new file mode 100644 index 0000000..859a926 --- /dev/null +++ b/use-cachedir-for-extension_modules-in-salt-call-bsc-.patch @@ -0,0 +1,110 @@ +From 2fb453d04b8abe765a964174ae77d57398f2877a Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Wed, 25 Sep 2024 14:06:19 +0300 +Subject: [PATCH] Use --cachedir for extension_modules in salt-call + (bsc#1226141) + +* Use --cachedir parameter for extension_modules with salt-call + +* Add test to check extension_modules value alignment +--- + salt/cli/call.py | 11 +++++++- + salt/config/__init__.py | 6 +++++ + tests/pytests/unit/cli/test_salt_call.py | 32 ++++++++++++++++++++++++ + 3 files changed, 48 insertions(+), 1 deletion(-) + create mode 100644 tests/pytests/unit/cli/test_salt_call.py + +diff --git a/salt/cli/call.py b/salt/cli/call.py +index 932dc61681..be3ded77e6 100644 +--- a/salt/cli/call.py ++++ b/salt/cli/call.py +@@ -3,7 +3,7 @@ import os + import salt.cli.caller + import salt.defaults.exitcodes + import salt.utils.parsers +-from salt.config import _expand_glob_path ++from salt.config import _expand_glob_path, prepend_root_dir + + + class SaltCall(salt.utils.parsers.SaltCallOptionParser): +@@ -37,6 +37,15 @@ class SaltCall(salt.utils.parsers.SaltCallOptionParser): + if self.options.master: + self.config["master"] = self.options.master + ++ if self.options.cachedir and self.config.get( ++ "extension_modules" ++ ) == os.path.join(self.config.get("__cachedir"), "extmods"): ++ # Override `extension_modules`, but only in case if it was autogenerated ++ cache_dir = os.path.abspath(self.options.cachedir) ++ self.config["cachedir"] = cache_dir ++ self.config["extension_modules"] = os.path.join(cache_dir, "extmods") ++ prepend_root_dir(self.config, ["cachedir", "extension_modules"]) ++ + caller = salt.cli.caller.Caller.factory(self.config) + + if self.options.doc: +diff --git a/salt/config/__init__.py b/salt/config/__init__.py +index 68f2b0f674..b3cd5d85ae 100644 +--- a/salt/config/__init__.py ++++ b/salt/config/__init__.py +@@ -1,6 +1,7 @@ + """ + All salt configuration loading and defaults should be in this module + """ ++ + import codecs + import glob + import logging +@@ -3841,6 +3842,11 @@ def apply_minion_config( + _update_ssl_config(opts) + _update_discovery_config(opts) + ++ # Store original `cachedir` value, before overriding, ++ # to make overriding more accurate. ++ if "__cachedir" not in opts: ++ opts["__cachedir"] = opts["cachedir"] ++ + return opts + + +diff --git a/tests/pytests/unit/cli/test_salt_call.py b/tests/pytests/unit/cli/test_salt_call.py +new file mode 100644 +index 0000000000..078f2af70d +--- /dev/null ++++ b/tests/pytests/unit/cli/test_salt_call.py +@@ -0,0 +1,32 @@ ++import os ++ ++from salt.cli.call import SaltCall ++from tests.support.mock import MagicMock, patch ++ ++ ++def test_passing_cachedir_to_extension_modules(temp_salt_minion): ++ """ ++ Test passing `cachedir` CLI parameter to `extension_modules` opts ++ """ ++ test_cache_dir = os.path.join(temp_salt_minion.config["root_dir"], "new_cache_tmp") ++ with patch( ++ "sys.argv", ++ [ ++ "salt-call", ++ "--local", ++ "--config-dir", ++ temp_salt_minion.config["root_dir"], ++ "--cachedir", ++ test_cache_dir, ++ "test.true", ++ ], ++ ), patch("salt.utils.verify.verify_files", MagicMock()), patch( ++ "salt._logging.impl.setup_logfile_handler", MagicMock() ++ ): ++ salt_call = SaltCall() ++ with patch("salt.cli.caller.Caller.factory", MagicMock()) as caller_mock: ++ salt_call.run() ++ assert salt_call.config["cachedir"] == test_cache_dir ++ assert salt_call.config["extension_modules"] == os.path.join( ++ test_cache_dir, "extmods" ++ ) +-- +2.46.1 +