From 27aee8f99b4f5aa78e3a4f4c59a11c4ebfb3fe4b15d052f7e15bb0b6e6397ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yeray=20Guti=C3=A9rrez=20Cedr=C3=A9s?= Date: Thu, 10 Aug 2023 11:32:54 +0000 Subject: [PATCH] Accepting request 1103185 from home:agraul:branches:systemsmanagement:saltstack - Create minion_id with reproducible mtime - Fix detection of Salt codename by "salt_version" execution module - Fix regression: multiple values for keyword argument 'saltenv' (bsc#1212844) - Fix the regression of user.present state when group is unset (bsc#1212855) - Fix zypper repositories always being reconfigured - Fix utf8 handling in 'pass' renderer and make it more robust - Prevent _pygit2.GitError: error loading known_hosts when $HOME is not set (bsc#1210994) - Fix ModuleNotFoundError and other issues raised by salt-support module (bsc#1211591) - tornado: Fix an open redirect in StaticFileHandler (CVE-2023-28370, bsc#1211741) - Make master_tops compatible with Salt 3000 and older minions (bsc#1212516) (bsc#1212517) - Avoid failures due transactional_update module not available in Salt 3006.0 (bsc#1211754) - Avoid conflicts with Salt dependencies versions (bsc#1211612) - Added: * fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch * fix-the-regression-of-user.present-state-when-group-.patch * make-master_tops-compatible-with-salt-3000-and-older.patch * avoid-conflicts-with-dependencies-versions-bsc-12116.patch * tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch * fix-regression-multiple-values-for-keyword-argument-.patch * zypper-pkgrepo-alreadyconfigured-585.patch * mark-salt-3006-as-released-586.patch * fix-some-issues-detected-in-salt-support-cli-module-.patch * define-__virtualname__-for-transactional_update-modu.patch * 3006.0-prevent-_pygit2.giterror-error-loading-known_.patch OBS-URL: https://build.opensuse.org/request/show/1103185 OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=212 --- ...pygit2.giterror-error-loading-known_.patch | 71 +++ _lastrevision | 2 +- ...with-dependencies-versions-bsc-12116.patch | 47 ++ ...name__-for-transactional_update-modu.patch | 39 ++ ...ultiple-values-for-keyword-argument-.patch | 253 +++++++++ ...detected-in-salt-support-cli-module-.patch | 118 +++++ ...on-of-user.present-state-when-group-.patch | 154 ++++++ ...g-in-pass-renderer-and-make-it-more-.patch | 181 +++++++ ...-compatible-with-salt-3000-and-older.patch | 37 ++ mark-salt-3006-as-released-586.patch | 480 ++++++++++++++++++ salt.changes | 29 ++ salt.spec | 24 +- ...pen-redirect-in-staticfilehandler-cv.patch | 35 ++ zypper-pkgrepo-alreadyconfigured-585.patch | 366 +++++++++++++ 14 files changed, 1834 insertions(+), 2 deletions(-) create mode 100644 3006.0-prevent-_pygit2.giterror-error-loading-known_.patch create mode 100644 avoid-conflicts-with-dependencies-versions-bsc-12116.patch create mode 100644 define-__virtualname__-for-transactional_update-modu.patch create mode 100644 fix-regression-multiple-values-for-keyword-argument-.patch create mode 100644 fix-some-issues-detected-in-salt-support-cli-module-.patch create mode 100644 fix-the-regression-of-user.present-state-when-group-.patch create mode 100644 fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch create mode 100644 make-master_tops-compatible-with-salt-3000-and-older.patch create mode 100644 mark-salt-3006-as-released-586.patch create mode 100644 tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch create mode 100644 zypper-pkgrepo-alreadyconfigured-585.patch diff --git a/3006.0-prevent-_pygit2.giterror-error-loading-known_.patch b/3006.0-prevent-_pygit2.giterror-error-loading-known_.patch new file mode 100644 index 0000000..e498f5c --- /dev/null +++ b/3006.0-prevent-_pygit2.giterror-error-loading-known_.patch @@ -0,0 +1,71 @@ +From 40a57afc65e71835127a437248ed655404cff0e8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 27 Jun 2023 11:24:39 +0100 +Subject: [PATCH] 3006.0: Prevent _pygit2.GitError: error loading + known_hosts when $HOME is not set (bsc#1210994) (#588) + +* Prevent _pygit2.GitError: error loading known_hosts when $HOME is not set + +* Add unit test to cover case of unset home +--- + salt/utils/gitfs.py | 5 +++++ + tests/unit/utils/test_gitfs.py | 14 ++++++++++++++ + 2 files changed, 19 insertions(+) + +diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py +index cc9895d8ab..38e84f38aa 100644 +--- a/salt/utils/gitfs.py ++++ b/salt/utils/gitfs.py +@@ -34,6 +34,7 @@ import salt.utils.stringutils + import salt.utils.url + import salt.utils.user + import salt.utils.versions ++import salt.syspaths + from salt.config import DEFAULT_MASTER_OPTS as _DEFAULT_MASTER_OPTS + from salt.exceptions import FileserverConfigError, GitLockError, get_error_message + from salt.utils.event import tagify +@@ -1867,6 +1868,10 @@ class Pygit2(GitProvider): + # pruning only available in pygit2 >= 0.26.2 + pass + try: ++ # Make sure $HOME env variable is set to prevent ++ # _pygit2.GitError: error loading known_hosts in some libgit2 versions. ++ if "HOME" not in os.environ: ++ os.environ["HOME"] = salt.syspaths.HOME_DIR + fetch_results = origin.fetch(**fetch_kwargs) + except GitError as exc: # pylint: disable=broad-except + exc_str = get_error_message(exc).lower() +diff --git a/tests/unit/utils/test_gitfs.py b/tests/unit/utils/test_gitfs.py +index b99da3ef91..7c400b69af 100644 +--- a/tests/unit/utils/test_gitfs.py ++++ b/tests/unit/utils/test_gitfs.py +@@ -14,6 +14,7 @@ import salt.utils.gitfs + import salt.utils.platform + import tests.support.paths + from salt.exceptions import FileserverConfigError ++from tests.support.helpers import patched_environ + from tests.support.mixins import AdaptedConfigurationTestCaseMixin + from tests.support.mock import MagicMock, patch + from tests.support.unit import TestCase +@@ -335,3 +336,16 @@ class TestPygit2(TestCase): + self.assertIn(provider.cachedir, provider.checkout()) + provider.branch = "does_not_exist" + self.assertIsNone(provider.checkout()) ++ ++ def test_checkout_with_home_env_unset(self): ++ remote = os.path.join(tests.support.paths.TMP, "pygit2-repo") ++ cache = os.path.join(tests.support.paths.TMP, "pygit2-repo-cache") ++ self._prepare_remote_repository(remote) ++ provider = self._prepare_cache_repository(remote, cache) ++ provider.remotecallbacks = None ++ provider.credentials = None ++ with patched_environ(__cleanup__=["HOME"]): ++ self.assertTrue("HOME" not in os.environ) ++ provider.init_remote() ++ provider.fetch() ++ self.assertTrue("HOME" in os.environ) +-- +2.41.0 + + diff --git a/_lastrevision b/_lastrevision index 4bb9bbe..282ec6c 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -2c8ae68c3fb161fd84005ed1b58abf7865ba646f \ No newline at end of file +26c7f283aef34794887afd5a4199be1018c5c592 \ No newline at end of file diff --git a/avoid-conflicts-with-dependencies-versions-bsc-12116.patch b/avoid-conflicts-with-dependencies-versions-bsc-12116.patch new file mode 100644 index 0000000..fd31505 --- /dev/null +++ b/avoid-conflicts-with-dependencies-versions-bsc-12116.patch @@ -0,0 +1,47 @@ +From 8e9f2587aea52c1d0a5c07d5f9bb77a23ae4d4a6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 23 May 2023 10:40:02 +0100 +Subject: [PATCH] Avoid conflicts with dependencies versions + (bsc#1211612) (#581) + +This commit fixes the Salt requirements file that are used to +generate the "requires.txt" file that is included in Salt egginfo +in order to be consistent with the installed packages +of Salt dependencies. + +This prevents issues when resolving and validating Salt dependencies +with "pkg_resources" Python module. +--- + requirements/base.txt | 2 +- + requirements/zeromq.txt | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/requirements/base.txt b/requirements/base.txt +index c19d8804a2..437aa01d31 100644 +--- a/requirements/base.txt ++++ b/requirements/base.txt +@@ -6,7 +6,7 @@ MarkupSafe + requests>=1.0.0 + distro>=1.0.1 + psutil>=5.0.0 +-packaging>=21.3 ++packaging>=17.1 + looseversion + # We need contextvars for salt-ssh + contextvars +diff --git a/requirements/zeromq.txt b/requirements/zeromq.txt +index 1e9a815c1b..23d1ef25dc 100644 +--- a/requirements/zeromq.txt ++++ b/requirements/zeromq.txt +@@ -1,5 +1,5 @@ + -r base.txt + -r crypto.txt + +-pyzmq>=20.0.0 ++pyzmq>=17.1.2 + pyzmq==25.0.2 ; sys_platform == "win32" +-- +2.39.2 + + diff --git a/define-__virtualname__-for-transactional_update-modu.patch b/define-__virtualname__-for-transactional_update-modu.patch new file mode 100644 index 0000000..b6509b5 --- /dev/null +++ b/define-__virtualname__-for-transactional_update-modu.patch @@ -0,0 +1,39 @@ +From f02e97df14e4927efbb5ddd3a2bbc5a650330b9e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Fri, 26 May 2023 16:50:51 +0100 +Subject: [PATCH] Define __virtualname__ for transactional_update module + (#582) + +This prevent problems with LazyLoader when importing this module, +which was wrongly exposing functions for this module under "state.*" +--- + salt/modules/transactional_update.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/transactional_update.py b/salt/modules/transactional_update.py +index 6493966782..658ebccc6b 100644 +--- a/salt/modules/transactional_update.py ++++ b/salt/modules/transactional_update.py +@@ -285,6 +285,8 @@ from salt.modules.state import _check_queue, _prior_running_states, _wait, runni + + __func_alias__ = {"apply_": "apply"} + ++__virtualname__ = "transactional_update" ++ + log = logging.getLogger(__name__) + + +@@ -300,7 +302,7 @@ def __virtual__(): + _prior_running_states, globals() + ) + running = salt.utils.functools.namespaced_function(running, globals()) +- return True ++ return __virtualname__ + else: + return (False, "Module transactional_update requires a transactional system") + +-- +2.39.2 + + diff --git a/fix-regression-multiple-values-for-keyword-argument-.patch b/fix-regression-multiple-values-for-keyword-argument-.patch new file mode 100644 index 0000000..c548387 --- /dev/null +++ b/fix-regression-multiple-values-for-keyword-argument-.patch @@ -0,0 +1,253 @@ +From c25c8081ded775f3574b0bc999d809ce14701ba5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Thu, 3 Aug 2023 10:07:28 +0100 +Subject: [PATCH] Fix regression: multiple values for keyword argument + 'saltenv' (bsc#1212844) (#590) + +* fix passing wrong keyword arguments to cp.cache_file in pkg.installed with sources + +* Drop `**kwargs` usage and be explicit about the supported keyword arguments. + +Signed-off-by: Pedro Algarvio + +* Add regression test for https://github.com/saltstack/salt/issues/64118 + +Signed-off-by: Pedro Algarvio + +* Add changelog file + +Signed-off-by: Pedro Algarvio + +--------- + +Signed-off-by: Pedro Algarvio +Co-authored-by: Massimiliano Torromeo +Co-authored-by: Pedro Algarvio +--- + changelog/64118.fixed.md | 1 + + salt/modules/win_pkg.py | 25 +++++++----- + salt/states/pkg.py | 4 +- + tests/pytests/unit/modules/test_win_pkg.py | 2 +- + tests/pytests/unit/states/test_pkg.py | 46 +++++++++++++++++++--- + 5 files changed, 62 insertions(+), 16 deletions(-) + create mode 100644 changelog/64118.fixed.md + +diff --git a/changelog/64118.fixed.md b/changelog/64118.fixed.md +new file mode 100644 +index 0000000000..e7251827e9 +--- /dev/null ++++ b/changelog/64118.fixed.md +@@ -0,0 +1 @@ ++Stop passing `**kwargs` and be explicit about the keyword arguments to pass, namely, to `cp.cache_file` call in `salt.states.pkg` +diff --git a/salt/modules/win_pkg.py b/salt/modules/win_pkg.py +index 3aa7c7919a..e80dd19322 100644 +--- a/salt/modules/win_pkg.py ++++ b/salt/modules/win_pkg.py +@@ -1298,7 +1298,7 @@ def _repo_process_pkg_sls(filename, short_path_name, ret, successful_verbose): + successful_verbose[short_path_name] = [] + + +-def _get_source_sum(source_hash, file_path, saltenv, **kwargs): ++def _get_source_sum(source_hash, file_path, saltenv, verify_ssl=True): + """ + Extract the hash sum, whether it is in a remote hash file, or just a string. + """ +@@ -1315,7 +1315,7 @@ def _get_source_sum(source_hash, file_path, saltenv, **kwargs): + # The source_hash is a file on a server + try: + cached_hash_file = __salt__["cp.cache_file"]( +- source_hash, saltenv, verify_ssl=kwargs.get("verify_ssl", True) ++ source_hash, saltenv=saltenv, verify_ssl=verify_ssl + ) + except MinionError as exc: + log.exception("Failed to cache %s", source_hash, exc_info=exc) +@@ -1671,7 +1671,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs): + try: + cached_file = __salt__["cp.cache_file"]( + cache_file, +- saltenv, ++ saltenv=saltenv, + verify_ssl=kwargs.get("verify_ssl", True), + ) + except MinionError as exc: +@@ -1686,7 +1686,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs): + try: + cached_file = __salt__["cp.cache_file"]( + cache_file, +- saltenv, ++ saltenv=saltenv, + verify_ssl=kwargs.get("verify_ssl", True), + ) + except MinionError as exc: +@@ -1706,7 +1706,9 @@ def install(name=None, refresh=False, pkgs=None, **kwargs): + # It's not cached. Cache it, mate. + try: + cached_pkg = __salt__["cp.cache_file"]( +- installer, saltenv, verify_ssl=kwargs.get("verify_ssl", True) ++ installer, ++ saltenv=saltenv, ++ verify_ssl=kwargs.get("verify_ssl", True), + ) + except MinionError as exc: + msg = "Failed to cache {}".format(installer) +@@ -1730,7 +1732,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs): + try: + cached_pkg = __salt__["cp.cache_file"]( + installer, +- saltenv, ++ saltenv=saltenv, + verify_ssl=kwargs.get("verify_ssl", True), + ) + except MinionError as exc: +@@ -1754,7 +1756,12 @@ def install(name=None, refresh=False, pkgs=None, **kwargs): + # Compare the hash sums + source_hash = pkginfo[version_num].get("source_hash", False) + if source_hash: +- source_sum = _get_source_sum(source_hash, cached_pkg, saltenv, **kwargs) ++ source_sum = _get_source_sum( ++ source_hash, ++ cached_pkg, ++ saltenv=saltenv, ++ verify_ssl=kwargs.get("verify_ssl", True), ++ ) + log.debug( + "pkg.install: Source %s hash: %s", + source_sum["hash_type"], +@@ -2126,7 +2133,7 @@ def remove(name=None, pkgs=None, **kwargs): + try: + cached_pkg = __salt__["cp.cache_file"]( + uninstaller, +- saltenv, ++ saltenv=saltenv, + verify_ssl=kwargs.get("verify_ssl", True), + ) + except MinionError as exc: +@@ -2150,7 +2157,7 @@ def remove(name=None, pkgs=None, **kwargs): + try: + cached_pkg = __salt__["cp.cache_file"]( + uninstaller, +- saltenv, ++ saltenv=saltenv, + verify_ssl=kwargs.get("verify_ssl", True), + ) + except MinionError as exc: +diff --git a/salt/states/pkg.py b/salt/states/pkg.py +index 12fbc87a1a..a605b23107 100644 +--- a/salt/states/pkg.py ++++ b/salt/states/pkg.py +@@ -760,7 +760,9 @@ def _find_install_targets( + err = "Unable to cache {0}: {1}" + try: + cached_path = __salt__["cp.cache_file"]( +- version_string, saltenv=kwargs["saltenv"], **kwargs ++ version_string, ++ saltenv=kwargs["saltenv"], ++ verify_ssl=kwargs.get("verify_ssl", True), + ) + except CommandExecutionError as exc: + problems.append(err.format(version_string, exc)) +diff --git a/tests/pytests/unit/modules/test_win_pkg.py b/tests/pytests/unit/modules/test_win_pkg.py +index 76234fb77e..6d435f00a5 100644 +--- a/tests/pytests/unit/modules/test_win_pkg.py ++++ b/tests/pytests/unit/modules/test_win_pkg.py +@@ -262,7 +262,7 @@ def test_pkg_install_verify_ssl_false(): + result = win_pkg.install(name="nsis", version="3.02", verify_ssl=False) + mock_cp.assert_called_once_with( + "http://download.sourceforge.net/project/nsis/NSIS%203/3.02/nsis-3.02-setup.exe", +- "base", ++ saltenv="base", + verify_ssl=False, + ) + assert expected == result +diff --git a/tests/pytests/unit/states/test_pkg.py b/tests/pytests/unit/states/test_pkg.py +index b852f27b00..f58be11011 100644 +--- a/tests/pytests/unit/states/test_pkg.py ++++ b/tests/pytests/unit/states/test_pkg.py +@@ -3,6 +3,7 @@ import logging + import pytest + + import salt.modules.beacons as beaconmod ++import salt.modules.cp as cp + import salt.modules.pkg_resource as pkg_resource + import salt.modules.yumpkg as yumpkg + import salt.states.beacon as beaconstate +@@ -15,19 +16,28 @@ log = logging.getLogger(__name__) + + + @pytest.fixture +-def configure_loader_modules(): ++def configure_loader_modules(minion_opts): + return { ++ cp: { ++ "__opts__": minion_opts, ++ }, + pkg: { + "__env__": "base", + "__salt__": {}, + "__grains__": {"os": "CentOS", "os_family": "RedHat"}, +- "__opts__": {"test": False, "cachedir": ""}, ++ "__opts__": minion_opts, + "__instance_id__": "", + "__low__": {}, + "__utils__": {"state.gen_tag": state_utils.gen_tag}, + }, +- beaconstate: {"__salt__": {}, "__opts__": {}}, +- beaconmod: {"__salt__": {}, "__opts__": {}}, ++ beaconstate: { ++ "__salt__": {}, ++ "__opts__": minion_opts, ++ }, ++ beaconmod: { ++ "__salt__": {}, ++ "__opts__": minion_opts, ++ }, + pkg_resource: { + "__salt__": {}, + "__grains__": {"os": "CentOS", "os_family": "RedHat"}, +@@ -35,7 +45,7 @@ def configure_loader_modules(): + yumpkg: { + "__salt__": {}, + "__grains__": {"osarch": "x86_64", "osmajorrelease": 7}, +- "__opts__": {}, ++ "__opts__": minion_opts, + }, + } + +@@ -563,6 +573,32 @@ def test_installed_with_changes_test_true(list_pkgs): + assert ret["changes"] == expected + + ++def test_installed_with_sources(list_pkgs, tmp_path): ++ """ ++ Test pkg.installed with passing `sources` ++ """ ++ ++ list_pkgs = MagicMock(return_value=list_pkgs) ++ pkg_source = tmp_path / "pkga-package-0.3.0.deb" ++ ++ with patch.dict( ++ pkg.__salt__, ++ { ++ "cp.cache_file": cp.cache_file, ++ "pkg.list_pkgs": list_pkgs, ++ "pkg_resource.pack_sources": pkg_resource.pack_sources, ++ "lowpkg.bin_pkg_info": MagicMock(), ++ }, ++ ), patch("salt.fileclient.get_file_client", return_value=MagicMock()): ++ try: ++ ret = pkg.installed("install-pkgd", sources=[{"pkga": str(pkg_source)}]) ++ assert ret["result"] is False ++ except TypeError as exc: ++ if "got multiple values for keyword argument 'saltenv'" in str(exc): ++ pytest.fail(f"TypeError should have not been raised: {exc}") ++ raise exc from None ++ ++ + @pytest.mark.parametrize("action", ["removed", "purged"]) + def test_removed_purged_with_changes_test_true(list_pkgs, action): + """ +-- +2.41.0 + + diff --git a/fix-some-issues-detected-in-salt-support-cli-module-.patch b/fix-some-issues-detected-in-salt-support-cli-module-.patch new file mode 100644 index 0000000..d0de813 --- /dev/null +++ b/fix-some-issues-detected-in-salt-support-cli-module-.patch @@ -0,0 +1,118 @@ +From 38de9af6bd243d35464713e0ee790255d3b40a7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Fri, 23 Jun 2023 13:02:51 +0100 +Subject: [PATCH] Fix some issues detected in "salt-support" CLI, module + and tests (bsc#1211591) (#580) + +* saltsupport: avoid debug traceback due missing import + +* Use yaml and json wrappers provides by Salt utils + +* Remove unnecessary call to deprecated setup_logfile_logger + +* Move unittest saltsupport tests to proper place + +* Fix test assertion error due wrong capturing of message +--- + salt/cli/support/__init__.py | 4 ++-- + salt/cli/support/collector.py | 6 ++---- + tests/{pytests => }/unit/cli/test_support.py | 0 + tests/unit/modules/test_saltsupport.py | 6 +++--- + 4 files changed, 7 insertions(+), 9 deletions(-) + rename tests/{pytests => }/unit/cli/test_support.py (100%) + +diff --git a/salt/cli/support/__init__.py b/salt/cli/support/__init__.py +index 59c2609e07..0a7da72e93 100644 +--- a/salt/cli/support/__init__.py ++++ b/salt/cli/support/__init__.py +@@ -6,7 +6,7 @@ import os + + import jinja2 + import salt.exceptions +-import yaml ++import salt.utils.yaml + + log = logging.getLogger(__name__) + +@@ -48,7 +48,7 @@ def get_profile(profile, caller, runner): + try: + rendered_template = _render_profile(profile_path, caller, runner) + log.debug("\n{d}\n{t}\n{d}\n".format(d="-" * 80, t=rendered_template)) +- data.update(yaml.load(rendered_template)) ++ data.update(salt.utils.yaml.load(rendered_template)) + except Exception as ex: + log.debug(ex, exc_info=True) + raise salt.exceptions.SaltException( +diff --git a/salt/cli/support/collector.py b/salt/cli/support/collector.py +index 1879cc5220..0ba987580c 100644 +--- a/salt/cli/support/collector.py ++++ b/salt/cli/support/collector.py +@@ -1,6 +1,5 @@ + import builtins as exceptions + import copy +-import json + import logging + import os + import sys +@@ -16,10 +15,10 @@ import salt.cli.support.intfunc + import salt.cli.support.localrunner + import salt.defaults.exitcodes + import salt.exceptions +-import salt.ext.six as six + import salt.output.table_out + import salt.runner + import salt.utils.files ++import salt.utils.json + import salt.utils.parsers + import salt.utils.platform + import salt.utils.process +@@ -169,7 +168,7 @@ class SupportDataCollector: + content = None + + if content is None: +- data = json.loads(json.dumps(data)) ++ data = salt.utils.json.loads(salt.utils.json.dumps(data)) + if isinstance(data, dict) and data.get("return"): + data = data.get("return") + content = yaml.safe_dump(data, default_flow_style=False, indent=4) +@@ -506,7 +505,6 @@ class SaltSupport(salt.utils.parsers.SaltSupportOptionParser): + self.out.error(ex) + else: + if self.config["log_level"] not in ("quiet",): +- self.setup_logfile_logger() + salt.utils.verify.verify_log(self.config) + salt.cli.support.log = log # Pass update logger so trace is available + +diff --git a/tests/pytests/unit/cli/test_support.py b/tests/unit/cli/test_support.py +similarity index 100% +rename from tests/pytests/unit/cli/test_support.py +rename to tests/unit/cli/test_support.py +diff --git a/tests/unit/modules/test_saltsupport.py b/tests/unit/modules/test_saltsupport.py +index 4ef04246b9..2afdd69b3e 100644 +--- a/tests/unit/modules/test_saltsupport.py ++++ b/tests/unit/modules/test_saltsupport.py +@@ -251,8 +251,8 @@ professor: Farnsworth + with pytest.raises(salt.exceptions.SaltInvocationError) as err: + support.sync("group-name") + assert ( +- ' Support archive "/mnt/storage/three-support-222-222.bz2" was not found' +- in str(err) ++ 'Support archive "/mnt/storage/three-support-222-222.bz2" was not found' ++ in str(err.value) + ) + + @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) +@@ -274,7 +274,7 @@ professor: Farnsworth + + with pytest.raises(salt.exceptions.SaltInvocationError) as err: + support.sync("group-name", name="lost.bz2") +- assert ' Support archive "lost.bz2" was not found' in str(err) ++ assert 'Support archive "lost.bz2" was not found' in str(err.value) + + @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) + @patch("os.path.exists", MagicMock(return_value=False)) +-- +2.41.0 + + diff --git a/fix-the-regression-of-user.present-state-when-group-.patch b/fix-the-regression-of-user.present-state-when-group-.patch new file mode 100644 index 0000000..669caee --- /dev/null +++ b/fix-the-regression-of-user.present-state-when-group-.patch @@ -0,0 +1,154 @@ +From 502354be32fcff9b0607f6e435ca8825a4c2cd56 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Thu, 3 Aug 2023 11:07:03 +0200 +Subject: [PATCH] Fix the regression of user.present state when group is + unset (#589) + +* Fix user.present state when group is unset + +* Fix user unit test + +--------- + +Co-authored-by: Megan Wilhite +--- + changelog/64211.fixed.md | 1 + + salt/states/user.py | 2 +- + tests/pytests/functional/states/test_user.py | 74 +++++++++++++++++++- + tests/pytests/unit/states/test_user.py | 2 + + 4 files changed, 76 insertions(+), 3 deletions(-) + create mode 100644 changelog/64211.fixed.md + +diff --git a/changelog/64211.fixed.md b/changelog/64211.fixed.md +new file mode 100644 +index 0000000000..26b39acf02 +--- /dev/null ++++ b/changelog/64211.fixed.md +@@ -0,0 +1 @@ ++Fix user.present state when groups is unset to ensure the groups are unchanged, as documented. +diff --git a/salt/states/user.py b/salt/states/user.py +index ed2d5a05f4..929afb2cd1 100644 +--- a/salt/states/user.py ++++ b/salt/states/user.py +@@ -100,7 +100,7 @@ def _changes( + + change = {} + wanted_groups = sorted(set((groups or []) + (optional_groups or []))) +- if not remove_groups: ++ if not remove_groups or groups is None and not optional_groups: + wanted_groups = sorted(set(wanted_groups + lusr["groups"])) + if uid and lusr["uid"] != uid: + change["uid"] = uid +diff --git a/tests/pytests/functional/states/test_user.py b/tests/pytests/functional/states/test_user.py +index 09d34da168..96b1ec55c8 100644 +--- a/tests/pytests/functional/states/test_user.py ++++ b/tests/pytests/functional/states/test_user.py +@@ -117,7 +117,6 @@ def test_user_present_when_home_dir_does_not_18843(states, existing_account): + ret = states.user.present( + name=existing_account.username, + home=existing_account.info.home, +- remove_groups=False, + ) + assert ret.result is True + assert pathlib.Path(existing_account.info.home).is_dir() +@@ -228,7 +227,6 @@ def test_user_present_unicode(states, username, subtests): + roomnumber="①②③", + workphone="١٢٣٤", + homephone="६७८", +- remove_groups=False, + ) + assert ret.result is True + +@@ -429,3 +427,75 @@ def test_user_present_change_optional_groups( + user_info = modules.user.info(username) + assert user_info + assert user_info["groups"] == [group_1.name] ++ ++ ++@pytest.mark.skip_unless_on_linux(reason="underlying functionality only runs on Linux") ++def test_user_present_no_groups(modules, states, username): ++ """ ++ test user.present when groups arg is not ++ included by the group is created in another ++ state. Re-run the states to ensure there are ++ not changes and it is idempotent. ++ """ ++ groups = ["testgroup1", "testgroup2"] ++ try: ++ ret = states.group.present(name=username, gid=61121) ++ assert ret.result is True ++ ++ ret = states.user.present( ++ name=username, ++ uid=61121, ++ gid=61121, ++ ) ++ assert ret.result is True ++ assert ret.changes["groups"] == [username] ++ assert ret.changes["name"] == username ++ ++ ret = states.group.present( ++ name=groups[0], ++ members=[username], ++ ) ++ assert ret.changes["members"] == [username] ++ ++ ret = states.group.present( ++ name=groups[1], ++ members=[username], ++ ) ++ assert ret.changes["members"] == [username] ++ ++ user_info = modules.user.info(username) ++ assert user_info ++ assert user_info["groups"] == [username, groups[0], groups[1]] ++ ++ # run again, expecting no changes ++ ret = states.group.present(name=username) ++ assert ret.result is True ++ assert ret.changes == {} ++ ++ ret = states.user.present( ++ name=username, ++ ) ++ assert ret.result is True ++ assert ret.changes == {} ++ ++ ret = states.group.present( ++ name=groups[0], ++ members=[username], ++ ) ++ assert ret.result is True ++ assert ret.changes == {} ++ ++ ret = states.group.present( ++ name=groups[1], ++ members=[username], ++ ) ++ assert ret.result is True ++ assert ret.changes == {} ++ ++ user_info = modules.user.info(username) ++ assert user_info ++ assert user_info["groups"] == [username, groups[0], groups[1]] ++ finally: ++ for group in groups: ++ ret = states.group.absent(name=group) ++ assert ret.result is True +diff --git a/tests/pytests/unit/states/test_user.py b/tests/pytests/unit/states/test_user.py +index 94e69d70ed..d50d16e3be 100644 +--- a/tests/pytests/unit/states/test_user.py ++++ b/tests/pytests/unit/states/test_user.py +@@ -189,6 +189,8 @@ def test_present_uid_gid_change(): + "user.chgid": Mock(), + "file.group_to_gid": mock_group_to_gid, + "file.gid_to_group": mock_gid_to_group, ++ "group.info": MagicMock(return_value=after), ++ "user.chgroups": MagicMock(return_value=True), + } + with patch.dict(user.__grains__, {"kernel": "Linux"}), patch.dict( + user.__salt__, dunder_salt +-- +2.41.0 + + diff --git a/fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch b/fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch new file mode 100644 index 0000000..ae97a49 --- /dev/null +++ b/fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch @@ -0,0 +1,181 @@ +From 027cbef223616f5ab6c73e60bcaa9f9e81a6ce67 Mon Sep 17 00:00:00 2001 +From: Daniel Mach +Date: Wed, 28 Jun 2023 16:39:42 +0200 +Subject: [PATCH] Fix utf8 handling in 'pass' renderer and make it more + robust (#579) + +* Migrate string formatting in 'pass' renderer to a f-string + +* Fix utf8 handling in 'pass' renderer and make it more robust +--- + changelog/64300.fixed.md | 1 + + salt/renderers/pass.py | 12 +-- + tests/pytests/unit/renderers/test_pass.py | 99 +++++++++++++++++++++++ + 3 files changed, 103 insertions(+), 9 deletions(-) + create mode 100644 changelog/64300.fixed.md + +diff --git a/changelog/64300.fixed.md b/changelog/64300.fixed.md +new file mode 100644 +index 0000000000..4418db1d04 +--- /dev/null ++++ b/changelog/64300.fixed.md +@@ -0,0 +1 @@ ++Fix utf8 handling in 'pass' renderer +diff --git a/salt/renderers/pass.py b/salt/renderers/pass.py +index ba0f152c23..ae75bba443 100644 +--- a/salt/renderers/pass.py ++++ b/salt/renderers/pass.py +@@ -145,23 +145,17 @@ def _fetch_secret(pass_path): + env["GNUPGHOME"] = pass_gnupghome + + try: +- proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) ++ proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env, encoding="utf-8") + pass_data, pass_error = proc.communicate() + pass_returncode = proc.returncode +- except OSError as e: ++ except (OSError, UnicodeDecodeError) as e: + pass_data, pass_error = "", str(e) + pass_returncode = 1 + + # The version of pass used during development sent output to + # stdout instead of stderr even though its returncode was non zero. + if pass_returncode or not pass_data: +- try: +- pass_error = pass_error.decode("utf-8") +- except (AttributeError, ValueError): +- pass +- msg = "Could not fetch secret '{}' from the password store: {}".format( +- pass_path, pass_error +- ) ++ msg = f"Could not fetch secret '{pass_path}' from the password store: {pass_error}" + if pass_strict_fetch: + raise SaltRenderError(msg) + else: +diff --git a/tests/pytests/unit/renderers/test_pass.py b/tests/pytests/unit/renderers/test_pass.py +index 1e2ebb7ea8..f7c79e1fe1 100644 +--- a/tests/pytests/unit/renderers/test_pass.py ++++ b/tests/pytests/unit/renderers/test_pass.py +@@ -1,8 +1,12 @@ + import importlib ++import os ++import shutil ++import tempfile + + import pytest + + import salt.exceptions ++import salt.utils.files + from tests.support.mock import MagicMock, patch + + # "pass" is a reserved keyword, we need to import it differently +@@ -19,6 +23,47 @@ def configure_loader_modules(master_opts): + } + + ++@pytest.fixture() ++def pass_executable(request): ++ tmp_dir = tempfile.mkdtemp(prefix="salt_pass_") ++ pass_path = os.path.join(tmp_dir, "pass") ++ with salt.utils.files.fopen(pass_path, "w") as f: ++ f.write("#!/bin/sh\n") ++ # return path path wrapped into unicode characters ++ # pass args ($1, $2) are ("show", ) ++ f.write('echo "α>>> $2 <<<β"\n') ++ os.chmod(pass_path, 0o755) ++ yield pass_path ++ shutil.rmtree(tmp_dir) ++ ++ ++@pytest.fixture() ++def pass_executable_error(request): ++ tmp_dir = tempfile.mkdtemp(prefix="salt_pass_") ++ pass_path = os.path.join(tmp_dir, "pass") ++ with salt.utils.files.fopen(pass_path, "w") as f: ++ f.write("#!/bin/sh\n") ++ # return error message with unicode characters ++ f.write('echo "ERROR: αβγ" >&2\n') ++ f.write("exit 1\n") ++ os.chmod(pass_path, 0o755) ++ yield pass_path ++ shutil.rmtree(tmp_dir) ++ ++ ++@pytest.fixture() ++def pass_executable_invalid_utf8(request): ++ tmp_dir = tempfile.mkdtemp(prefix="salt_pass_") ++ pass_path = os.path.join(tmp_dir, "pass") ++ with salt.utils.files.fopen(pass_path, "wb") as f: ++ f.write(b"#!/bin/sh\n") ++ # return invalid utf-8 sequence ++ f.write(b'echo "\x80\x81"\n') ++ os.chmod(pass_path, 0o755) ++ yield pass_path ++ shutil.rmtree(tmp_dir) ++ ++ + # The default behavior is that if fetching a secret from pass fails, + # the value is passed through. Even the trailing newlines are preserved. + def test_passthrough(): +@@ -161,3 +206,57 @@ def test_env(): + call_args, call_kwargs = popen_mock.call_args_list[0] + assert call_kwargs["env"]["GNUPGHOME"] == config["pass_gnupghome"] + assert call_kwargs["env"]["PASSWORD_STORE_DIR"] == config["pass_dir"] ++ ++ ++@pytest.mark.skip_on_windows(reason="Not supported on Windows") ++def test_utf8(pass_executable): ++ config = { ++ "pass_variable_prefix": "pass:", ++ "pass_strict_fetch": True, ++ } ++ mocks = { ++ "_get_pass_exec": MagicMock(return_value=pass_executable), ++ } ++ ++ pass_path = "pass:secret" ++ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks): ++ result = pass_.render(pass_path) ++ assert result == "α>>> secret <<<β" ++ ++ ++@pytest.mark.skip_on_windows(reason="Not supported on Windows") ++def test_utf8_error(pass_executable_error): ++ config = { ++ "pass_variable_prefix": "pass:", ++ "pass_strict_fetch": True, ++ } ++ mocks = { ++ "_get_pass_exec": MagicMock(return_value=pass_executable_error), ++ } ++ ++ pass_path = "pass:secret" ++ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks): ++ with pytest.raises( ++ salt.exceptions.SaltRenderError, ++ match=r"Could not fetch secret 'secret' from the password store: ERROR: αβγ", ++ ): ++ result = pass_.render(pass_path) ++ ++ ++@pytest.mark.skip_on_windows(reason="Not supported on Windows") ++def test_invalid_utf8(pass_executable_invalid_utf8): ++ config = { ++ "pass_variable_prefix": "pass:", ++ "pass_strict_fetch": True, ++ } ++ mocks = { ++ "_get_pass_exec": MagicMock(return_value=pass_executable_invalid_utf8), ++ } ++ ++ pass_path = "pass:secret" ++ with patch.dict(pass_.__opts__, config), patch.dict(pass_.__dict__, mocks): ++ with pytest.raises( ++ salt.exceptions.SaltRenderError, ++ match=r"Could not fetch secret 'secret' from the password store: 'utf-8' codec can't decode byte 0x80 in position 0: invalid start byte", ++ ): ++ result = pass_.render(pass_path) +-- +2.41.0 + + diff --git a/make-master_tops-compatible-with-salt-3000-and-older.patch b/make-master_tops-compatible-with-salt-3000-and-older.patch new file mode 100644 index 0000000..bfe217d --- /dev/null +++ b/make-master_tops-compatible-with-salt-3000-and-older.patch @@ -0,0 +1,37 @@ +From 53a5a62191b81c6838c3041cf95ffeb12fbab5b5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Mon, 19 Jun 2023 15:35:41 +0100 +Subject: [PATCH] Make master_tops compatible with Salt 3000 and older + minions (bsc#1212516) (bsc#1212517) (#587) + +--- + salt/master.py | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/salt/master.py b/salt/master.py +index da1eb8cef5..fc243ef674 100644 +--- a/salt/master.py ++++ b/salt/master.py +@@ -1213,6 +1213,7 @@ class AESFuncs(TransportMethods): + "_dir_list", + "_symlink_list", + "_file_envs", ++ "_ext_nodes", # To keep compatibility with old Salt minion versions + ) + + def __init__(self, opts, context=None): +@@ -1412,6 +1413,9 @@ class AESFuncs(TransportMethods): + return {} + return self.masterapi._master_tops(load, skip_verify=True) + ++ # Needed so older minions can request master_tops ++ _ext_nodes = _master_tops ++ + def _master_opts(self, load): + """ + Return the master options to the minion +-- +2.41.0 + + diff --git a/mark-salt-3006-as-released-586.patch b/mark-salt-3006-as-released-586.patch new file mode 100644 index 0000000..c37ca75 --- /dev/null +++ b/mark-salt-3006-as-released-586.patch @@ -0,0 +1,480 @@ +From c1408333364ac25ff5d316afa9674f7687217b0c Mon Sep 17 00:00:00 2001 +From: Dominik Gedon +Date: Thu, 3 Aug 2023 11:08:21 +0200 +Subject: [PATCH] Mark Salt 3006 as released (#586) +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +* Mark Salt 3006 as released + +Without this, commands like + +``` +salt '*' salt_version.equal 'Sulfur' +``` + +will not work properly and return False although Salt 3006 is used. + +Signed-off-by: Dominik Gedon + +* Fix detection of Salt codename by salt_version module + +* Fix mess with version detection bad version definition + +* Add some new and fix unit tests + +* Fix SaltStackVersion string for new versions format + +* Do not crash when passing numbers to 'salt_version.get_release_number' + +* Fix salt_version execution module documentation + +--------- + +Signed-off-by: Dominik Gedon +Co-authored-by: Pablo Suárez Hernández +--- + salt/modules/salt_version.py | 8 +- + salt/version.py | 218 +++++++++--------- + .../pytests/unit/modules/test_salt_version.py | 55 ++++- + tests/pytests/unit/test_version.py | 10 +- + 4 files changed, 176 insertions(+), 115 deletions(-) + +diff --git a/salt/modules/salt_version.py b/salt/modules/salt_version.py +index 1b5421fee4..99dae5f61a 100644 +--- a/salt/modules/salt_version.py ++++ b/salt/modules/salt_version.py +@@ -20,7 +20,7 @@ A simple example might be something like the following: + .. code-block:: jinja + + {# a boolean check #} +- {% set option_deprecated = salt['salt_version.less_than']("3001") %} ++ {% set option_deprecated = salt['salt_version.less_than']("Sodium") %} + + {% if option_deprecated %} + +@@ -35,6 +35,7 @@ import logging + + import salt.utils.versions + import salt.version ++from salt.exceptions import CommandExecutionError + + log = logging.getLogger(__name__) + +@@ -51,7 +52,7 @@ def __virtual__(): + def get_release_number(name): + """ + Returns the release number of a given release code name in a +- ``MAJOR.PATCH`` format. ++ ``MAJOR.PATCH`` format (for Salt versions < 3000) or ``MAJOR`` for newer Salt versions. + + If the release name has not been given an assigned release number, the + function returns a string. If the release cannot be found, it returns +@@ -66,6 +67,9 @@ def get_release_number(name): + + salt '*' salt_version.get_release_number 'Oxygen' + """ ++ if not isinstance(name, str): ++ raise CommandExecutionError("'name' argument must be a string") ++ + name = name.lower() + version_map = salt.version.SaltStackVersion.LNAMES + version = version_map.get(name) +diff --git a/salt/version.py b/salt/version.py +index 67719bd020..44372830b2 100644 +--- a/salt/version.py ++++ b/salt/version.py +@@ -77,109 +77,109 @@ class SaltVersionsInfo(type): + ALUMINIUM = SaltVersion("Aluminium" , info=3003, released=True) + SILICON = SaltVersion("Silicon" , info=3004, released=True) + PHOSPHORUS = SaltVersion("Phosphorus" , info=3005, released=True) +- SULFUR = SaltVersion("Sulfur" , info=(3006, 0)) +- CHLORINE = SaltVersion("Chlorine" , info=(3007, 0)) +- ARGON = SaltVersion("Argon" , info=(3008, 0)) +- POTASSIUM = SaltVersion("Potassium" , info=(3009, 0)) +- CALCIUM = SaltVersion("Calcium" , info=(3010, 0)) +- SCANDIUM = SaltVersion("Scandium" , info=(3011, 0)) +- TITANIUM = SaltVersion("Titanium" , info=(3012, 0)) +- VANADIUM = SaltVersion("Vanadium" , info=(3013, 0)) +- CHROMIUM = SaltVersion("Chromium" , info=(3014, 0)) +- MANGANESE = SaltVersion("Manganese" , info=(3015, 0)) +- IRON = SaltVersion("Iron" , info=(3016, 0)) +- COBALT = SaltVersion("Cobalt" , info=(3017, 0)) +- NICKEL = SaltVersion("Nickel" , info=(3018, 0)) +- COPPER = SaltVersion("Copper" , info=(3019, 0)) +- ZINC = SaltVersion("Zinc" , info=(3020, 0)) +- GALLIUM = SaltVersion("Gallium" , info=(3021, 0)) +- GERMANIUM = SaltVersion("Germanium" , info=(3022, 0)) +- ARSENIC = SaltVersion("Arsenic" , info=(3023, 0)) +- SELENIUM = SaltVersion("Selenium" , info=(3024, 0)) +- BROMINE = SaltVersion("Bromine" , info=(3025, 0)) +- KRYPTON = SaltVersion("Krypton" , info=(3026, 0)) +- RUBIDIUM = SaltVersion("Rubidium" , info=(3027, 0)) +- STRONTIUM = SaltVersion("Strontium" , info=(3028, 0)) +- YTTRIUM = SaltVersion("Yttrium" , info=(3029, 0)) +- ZIRCONIUM = SaltVersion("Zirconium" , info=(3030, 0)) +- NIOBIUM = SaltVersion("Niobium" , info=(3031, 0)) +- MOLYBDENUM = SaltVersion("Molybdenum" , info=(3032, 0)) +- TECHNETIUM = SaltVersion("Technetium" , info=(3033, 0)) +- RUTHENIUM = SaltVersion("Ruthenium" , info=(3034, 0)) +- RHODIUM = SaltVersion("Rhodium" , info=(3035, 0)) +- PALLADIUM = SaltVersion("Palladium" , info=(3036, 0)) +- SILVER = SaltVersion("Silver" , info=(3037, 0)) +- CADMIUM = SaltVersion("Cadmium" , info=(3038, 0)) +- INDIUM = SaltVersion("Indium" , info=(3039, 0)) +- TIN = SaltVersion("Tin" , info=(3040, 0)) +- ANTIMONY = SaltVersion("Antimony" , info=(3041, 0)) +- TELLURIUM = SaltVersion("Tellurium" , info=(3042, 0)) +- IODINE = SaltVersion("Iodine" , info=(3043, 0)) +- XENON = SaltVersion("Xenon" , info=(3044, 0)) +- CESIUM = SaltVersion("Cesium" , info=(3045, 0)) +- BARIUM = SaltVersion("Barium" , info=(3046, 0)) +- LANTHANUM = SaltVersion("Lanthanum" , info=(3047, 0)) +- CERIUM = SaltVersion("Cerium" , info=(3048, 0)) +- PRASEODYMIUM = SaltVersion("Praseodymium" , info=(3049, 0)) +- NEODYMIUM = SaltVersion("Neodymium" , info=(3050, 0)) +- PROMETHIUM = SaltVersion("Promethium" , info=(3051, 0)) +- SAMARIUM = SaltVersion("Samarium" , info=(3052, 0)) +- EUROPIUM = SaltVersion("Europium" , info=(3053, 0)) +- GADOLINIUM = SaltVersion("Gadolinium" , info=(3054, 0)) +- TERBIUM = SaltVersion("Terbium" , info=(3055, 0)) +- DYSPROSIUM = SaltVersion("Dysprosium" , info=(3056, 0)) +- HOLMIUM = SaltVersion("Holmium" , info=(3057, 0)) +- ERBIUM = SaltVersion("Erbium" , info=(3058, 0)) +- THULIUM = SaltVersion("Thulium" , info=(3059, 0)) +- YTTERBIUM = SaltVersion("Ytterbium" , info=(3060, 0)) +- LUTETIUM = SaltVersion("Lutetium" , info=(3061, 0)) +- HAFNIUM = SaltVersion("Hafnium" , info=(3062, 0)) +- TANTALUM = SaltVersion("Tantalum" , info=(3063, 0)) +- TUNGSTEN = SaltVersion("Tungsten" , info=(3064, 0)) +- RHENIUM = SaltVersion("Rhenium" , info=(3065, 0)) +- OSMIUM = SaltVersion("Osmium" , info=(3066, 0)) +- IRIDIUM = SaltVersion("Iridium" , info=(3067, 0)) +- PLATINUM = SaltVersion("Platinum" , info=(3068, 0)) +- GOLD = SaltVersion("Gold" , info=(3069, 0)) +- MERCURY = SaltVersion("Mercury" , info=(3070, 0)) +- THALLIUM = SaltVersion("Thallium" , info=(3071, 0)) +- LEAD = SaltVersion("Lead" , info=(3072, 0)) +- BISMUTH = SaltVersion("Bismuth" , info=(3073, 0)) +- POLONIUM = SaltVersion("Polonium" , info=(3074, 0)) +- ASTATINE = SaltVersion("Astatine" , info=(3075, 0)) +- RADON = SaltVersion("Radon" , info=(3076, 0)) +- FRANCIUM = SaltVersion("Francium" , info=(3077, 0)) +- RADIUM = SaltVersion("Radium" , info=(3078, 0)) +- ACTINIUM = SaltVersion("Actinium" , info=(3079, 0)) +- THORIUM = SaltVersion("Thorium" , info=(3080, 0)) +- PROTACTINIUM = SaltVersion("Protactinium" , info=(3081, 0)) +- URANIUM = SaltVersion("Uranium" , info=(3082, 0)) +- NEPTUNIUM = SaltVersion("Neptunium" , info=(3083, 0)) +- PLUTONIUM = SaltVersion("Plutonium" , info=(3084, 0)) +- AMERICIUM = SaltVersion("Americium" , info=(3085, 0)) +- CURIUM = SaltVersion("Curium" , info=(3086, 0)) +- BERKELIUM = SaltVersion("Berkelium" , info=(3087, 0)) +- CALIFORNIUM = SaltVersion("Californium" , info=(3088, 0)) +- EINSTEINIUM = SaltVersion("Einsteinium" , info=(3089, 0)) +- FERMIUM = SaltVersion("Fermium" , info=(3090, 0)) +- MENDELEVIUM = SaltVersion("Mendelevium" , info=(3091, 0)) +- NOBELIUM = SaltVersion("Nobelium" , info=(3092, 0)) +- LAWRENCIUM = SaltVersion("Lawrencium" , info=(3093, 0)) +- RUTHERFORDIUM = SaltVersion("Rutherfordium", info=(3094, 0)) +- DUBNIUM = SaltVersion("Dubnium" , info=(3095, 0)) +- SEABORGIUM = SaltVersion("Seaborgium" , info=(3096, 0)) +- BOHRIUM = SaltVersion("Bohrium" , info=(3097, 0)) +- HASSIUM = SaltVersion("Hassium" , info=(3098, 0)) +- MEITNERIUM = SaltVersion("Meitnerium" , info=(3099, 0)) +- DARMSTADTIUM = SaltVersion("Darmstadtium" , info=(3100, 0)) +- ROENTGENIUM = SaltVersion("Roentgenium" , info=(3101, 0)) +- COPERNICIUM = SaltVersion("Copernicium" , info=(3102, 0)) +- NIHONIUM = SaltVersion("Nihonium" , info=(3103, 0)) +- FLEROVIUM = SaltVersion("Flerovium" , info=(3104, 0)) +- MOSCOVIUM = SaltVersion("Moscovium" , info=(3105, 0)) +- LIVERMORIUM = SaltVersion("Livermorium" , info=(3106, 0)) +- TENNESSINE = SaltVersion("Tennessine" , info=(3107, 0)) +- OGANESSON = SaltVersion("Oganesson" , info=(3108, 0)) ++ SULFUR = SaltVersion("Sulfur" , info=3006, released=True) ++ CHLORINE = SaltVersion("Chlorine" , info=3007) ++ ARGON = SaltVersion("Argon" , info=3008) ++ POTASSIUM = SaltVersion("Potassium" , info=3009) ++ CALCIUM = SaltVersion("Calcium" , info=3010) ++ SCANDIUM = SaltVersion("Scandium" , info=3011) ++ TITANIUM = SaltVersion("Titanium" , info=3012) ++ VANADIUM = SaltVersion("Vanadium" , info=3013) ++ CHROMIUM = SaltVersion("Chromium" , info=3014) ++ MANGANESE = SaltVersion("Manganese" , info=3015) ++ IRON = SaltVersion("Iron" , info=3016) ++ COBALT = SaltVersion("Cobalt" , info=3017) ++ NICKEL = SaltVersion("Nickel" , info=3018) ++ COPPER = SaltVersion("Copper" , info=3019) ++ ZINC = SaltVersion("Zinc" , info=3020) ++ GALLIUM = SaltVersion("Gallium" , info=3021) ++ GERMANIUM = SaltVersion("Germanium" , info=3022) ++ ARSENIC = SaltVersion("Arsenic" , info=3023) ++ SELENIUM = SaltVersion("Selenium" , info=3024) ++ BROMINE = SaltVersion("Bromine" , info=3025) ++ KRYPTON = SaltVersion("Krypton" , info=3026) ++ RUBIDIUM = SaltVersion("Rubidium" , info=3027) ++ STRONTIUM = SaltVersion("Strontium" , info=3028) ++ YTTRIUM = SaltVersion("Yttrium" , info=3029) ++ ZIRCONIUM = SaltVersion("Zirconium" , info=3030) ++ NIOBIUM = SaltVersion("Niobium" , info=3031) ++ MOLYBDENUM = SaltVersion("Molybdenum" , info=3032) ++ TECHNETIUM = SaltVersion("Technetium" , info=3033) ++ RUTHENIUM = SaltVersion("Ruthenium" , info=3034) ++ RHODIUM = SaltVersion("Rhodium" , info=3035) ++ PALLADIUM = SaltVersion("Palladium" , info=3036) ++ SILVER = SaltVersion("Silver" , info=3037) ++ CADMIUM = SaltVersion("Cadmium" , info=3038) ++ INDIUM = SaltVersion("Indium" , info=3039) ++ TIN = SaltVersion("Tin" , info=3040) ++ ANTIMONY = SaltVersion("Antimony" , info=3041) ++ TELLURIUM = SaltVersion("Tellurium" , info=3042) ++ IODINE = SaltVersion("Iodine" , info=3043) ++ XENON = SaltVersion("Xenon" , info=3044) ++ CESIUM = SaltVersion("Cesium" , info=3045) ++ BARIUM = SaltVersion("Barium" , info=3046) ++ LANTHANUM = SaltVersion("Lanthanum" , info=3047) ++ CERIUM = SaltVersion("Cerium" , info=3048) ++ PRASEODYMIUM = SaltVersion("Praseodymium" , info=3049) ++ NEODYMIUM = SaltVersion("Neodymium" , info=3050) ++ PROMETHIUM = SaltVersion("Promethium" , info=3051) ++ SAMARIUM = SaltVersion("Samarium" , info=3052) ++ EUROPIUM = SaltVersion("Europium" , info=3053) ++ GADOLINIUM = SaltVersion("Gadolinium" , info=3054) ++ TERBIUM = SaltVersion("Terbium" , info=3055) ++ DYSPROSIUM = SaltVersion("Dysprosium" , info=3056) ++ HOLMIUM = SaltVersion("Holmium" , info=3057) ++ ERBIUM = SaltVersion("Erbium" , info=3058) ++ THULIUM = SaltVersion("Thulium" , info=3059) ++ YTTERBIUM = SaltVersion("Ytterbium" , info=3060) ++ LUTETIUM = SaltVersion("Lutetium" , info=3061) ++ HAFNIUM = SaltVersion("Hafnium" , info=3062) ++ TANTALUM = SaltVersion("Tantalum" , info=3063) ++ TUNGSTEN = SaltVersion("Tungsten" , info=3064) ++ RHENIUM = SaltVersion("Rhenium" , info=3065) ++ OSMIUM = SaltVersion("Osmium" , info=3066) ++ IRIDIUM = SaltVersion("Iridium" , info=3067) ++ PLATINUM = SaltVersion("Platinum" , info=3068) ++ GOLD = SaltVersion("Gold" , info=3069) ++ MERCURY = SaltVersion("Mercury" , info=3070) ++ THALLIUM = SaltVersion("Thallium" , info=3071) ++ LEAD = SaltVersion("Lead" , info=3072) ++ BISMUTH = SaltVersion("Bismuth" , info=3073) ++ POLONIUM = SaltVersion("Polonium" , info=3074) ++ ASTATINE = SaltVersion("Astatine" , info=3075) ++ RADON = SaltVersion("Radon" , info=3076) ++ FRANCIUM = SaltVersion("Francium" , info=3077) ++ RADIUM = SaltVersion("Radium" , info=3078) ++ ACTINIUM = SaltVersion("Actinium" , info=3079) ++ THORIUM = SaltVersion("Thorium" , info=3080) ++ PROTACTINIUM = SaltVersion("Protactinium" , info=3081) ++ URANIUM = SaltVersion("Uranium" , info=3082) ++ NEPTUNIUM = SaltVersion("Neptunium" , info=3083) ++ PLUTONIUM = SaltVersion("Plutonium" , info=3084) ++ AMERICIUM = SaltVersion("Americium" , info=3085) ++ CURIUM = SaltVersion("Curium" , info=3086) ++ BERKELIUM = SaltVersion("Berkelium" , info=3087) ++ CALIFORNIUM = SaltVersion("Californium" , info=3088) ++ EINSTEINIUM = SaltVersion("Einsteinium" , info=3089) ++ FERMIUM = SaltVersion("Fermium" , info=3090) ++ MENDELEVIUM = SaltVersion("Mendelevium" , info=3091) ++ NOBELIUM = SaltVersion("Nobelium" , info=3092) ++ LAWRENCIUM = SaltVersion("Lawrencium" , info=3093) ++ RUTHERFORDIUM = SaltVersion("Rutherfordium", info=3094) ++ DUBNIUM = SaltVersion("Dubnium" , info=3095) ++ SEABORGIUM = SaltVersion("Seaborgium" , info=3096) ++ BOHRIUM = SaltVersion("Bohrium" , info=3097) ++ HASSIUM = SaltVersion("Hassium" , info=3098) ++ MEITNERIUM = SaltVersion("Meitnerium" , info=3099) ++ DARMSTADTIUM = SaltVersion("Darmstadtium" , info=3100) ++ ROENTGENIUM = SaltVersion("Roentgenium" , info=3101) ++ COPERNICIUM = SaltVersion("Copernicium" , info=3102) ++ NIHONIUM = SaltVersion("Nihonium" , info=3103) ++ FLEROVIUM = SaltVersion("Flerovium" , info=3104) ++ MOSCOVIUM = SaltVersion("Moscovium" , info=3105) ++ LIVERMORIUM = SaltVersion("Livermorium" , info=3106) ++ TENNESSINE = SaltVersion("Tennessine" , info=3107) ++ OGANESSON = SaltVersion("Oganesson" , info=3108) + # <---- Please refrain from fixing whitespace ----------------------------------- + # The idea is to keep this readable. + # ------------------------------------------------------------------------------- +@@ -323,9 +323,7 @@ class SaltStackVersion: + self.mbugfix = mbugfix + self.pre_type = pre_type + self.pre_num = pre_num +- if self.can_have_dot_zero(major): +- vnames_key = (major, 0) +- elif self.new_version(major): ++ if self.new_version(major): + vnames_key = (major,) + else: + vnames_key = (major, minor) +@@ -476,8 +474,12 @@ class SaltStackVersion: + version_string = self.string + if self.sse: + version_string += " Enterprise" +- if (self.major, self.minor) in self.RMATCH: +- version_string += " ({})".format(self.RMATCH[(self.major, self.minor)]) ++ if self.new_version(self.major): ++ rmatch_key = (self.major,) ++ else: ++ rmatch_key = (self.major, self.minor) ++ if rmatch_key in self.RMATCH: ++ version_string += " ({})".format(self.RMATCH[rmatch_key]) + return version_string + + @property +diff --git a/tests/pytests/unit/modules/test_salt_version.py b/tests/pytests/unit/modules/test_salt_version.py +index 6d734f6a76..4b7a7cd073 100644 +--- a/tests/pytests/unit/modules/test_salt_version.py ++++ b/tests/pytests/unit/modules/test_salt_version.py +@@ -2,8 +2,11 @@ + Unit tests for salt/modules/salt_version.py + """ + ++import pytest ++ + import salt.modules.salt_version as salt_version + import salt.version ++from salt.exceptions import CommandExecutionError + from tests.support.mock import MagicMock, patch + + +@@ -21,7 +24,7 @@ def test_mocked_objects(): + for k, v in salt.version.SaltStackVersion.LNAMES.items(): + assert k == k.lower() + assert isinstance(v, tuple) +- if sv.new_version(major=v[0]) and not sv.can_have_dot_zero(major=v[0]): ++ if sv.new_version(major=v[0]): + assert len(v) == 1 + else: + assert len(v) == 2 +@@ -64,6 +67,13 @@ def test_get_release_number_success_new_version(): + assert salt_version.get_release_number("Neon") == "3000" + + ++def test_get_release_number_success_new_version_with_dot(): ++ """ ++ Test that a version is returned for new versioning (3006) ++ """ ++ assert salt_version.get_release_number("Sulfur") == "3006" ++ ++ + def test_equal_success(): + """ + Test that the current version is equal to the codename +@@ -83,6 +93,16 @@ def test_equal_success_new_version(): + assert salt_version.equal("foo") is True + + ++def test_equal_success_new_version_with_dot(): ++ """ ++ Test that the current version is equal to the codename ++ while using the new versioning ++ """ ++ with patch("salt.version.SaltStackVersion", MagicMock(return_value="3006.1")): ++ with patch("salt.version.SaltStackVersion.LNAMES", {"foo": (3006,)}): ++ assert salt_version.equal("foo") is True ++ ++ + def test_equal_older_codename(): + """ + Test that when an older codename is passed in, the function returns False. +@@ -142,6 +162,17 @@ def test_greater_than_success_new_version(): + assert salt_version.greater_than("Nitrogen") is True + + ++def test_greater_than_success_new_version_with_dot(): ++ """ ++ Test that the current version is newer than the codename ++ """ ++ with patch( ++ "salt.modules.salt_version.get_release_number", MagicMock(return_value="3000") ++ ): ++ with patch("salt.version.SaltStackVersion", MagicMock(return_value="3006.0")): ++ assert salt_version.greater_than("Neon") is True ++ ++ + def test_greater_than_with_equal_codename(): + """ + Test that when an equal codename is passed in, the function returns False. +@@ -200,6 +231,28 @@ def test_less_than_success_new_version(): + assert salt_version.less_than("Fluorine") is True + + ++def test_less_than_success_new_version_with_dot(): ++ """ ++ Test that when a newer codename is passed in, the function returns True ++ using new version ++ """ ++ with patch("salt.version.SaltStackVersion", MagicMock(return_value="2018.3.2")): ++ with patch( ++ "salt.modules.salt_version.get_release_number", ++ MagicMock(return_value="3006"), ++ ): ++ assert salt_version.less_than("Fluorine") is True ++ ++ ++def test_less_than_do_not_crash_when_input_is_a_number(): ++ """ ++ Test that less_than do not crash when unexpected inputs ++ """ ++ with patch("salt.version.SaltStackVersion", MagicMock(return_value="2018.3.2")): ++ with pytest.raises(CommandExecutionError): ++ salt_version.less_than(1234) ++ ++ + def test_less_than_with_equal_codename(): + """ + Test that when an equal codename is passed in, the function returns False. +diff --git a/tests/pytests/unit/test_version.py b/tests/pytests/unit/test_version.py +index 73befea4cf..1cb94c619c 100644 +--- a/tests/pytests/unit/test_version.py ++++ b/tests/pytests/unit/test_version.py +@@ -187,7 +187,7 @@ def test_string_new_version_minor(): + ver = SaltStackVersion(major=maj_ver, minor=min_ver) + assert ver.minor == min_ver + assert not ver.bugfix +- assert ver.string == "{}.{}".format(maj_ver, min_ver) ++ assert ver.string == f"{maj_ver}.{min_ver}" + + + def test_string_new_version_minor_as_string(): +@@ -201,13 +201,13 @@ def test_string_new_version_minor_as_string(): + ver = SaltStackVersion(major=maj_ver, minor=min_ver) + assert ver.minor == int(min_ver) + assert not ver.bugfix +- assert ver.string == "{}.{}".format(maj_ver, min_ver) ++ assert ver.string == f"{maj_ver}.{min_ver}" + + # This only seems to happen on a cloned repo without its tags + maj_ver = "3000" + min_ver = "" + ver = SaltStackVersion(major=maj_ver, minor=min_ver) +- assert ver.minor is None, "{!r} is not {!r}".format(ver.minor, min_ver) ++ assert ver.minor is None, f"{ver.minor!r} is not {min_ver!r}" + assert not ver.bugfix + assert ver.string == maj_ver + +@@ -222,7 +222,7 @@ def test_string_old_version(): + min_ver = "2" + ver = SaltStackVersion(major=maj_ver, minor=min_ver) + assert ver.bugfix == 0 +- assert ver.string == "{}.{}.0".format(maj_ver, min_ver) ++ assert ver.string == f"{maj_ver}.{min_ver}.0" + + + @pytest.mark.parametrize( +@@ -537,6 +537,8 @@ def test_versions_report_no_extensions_available(): + ("3000.1", "3000.1", "Neon"), + ("3005", "3005", "Phosphorus"), + ("3006", "3006.0", "Sulfur"), ++ ("3006.0", "3006.0", "Sulfur"), ++ ("3006.1", "3006.1", "Sulfur"), + ("3015.1", "3015.1", "Manganese"), + ("3109.3", "3109.3", None), + ], +-- +2.41.0 + + diff --git a/salt.changes b/salt.changes index 40688b2..06c360d 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,32 @@ +------------------------------------------------------------------- +Wed Aug 9 15:13:50 UTC 2023 - Alexander Graul + +- Create minion_id with reproducible mtime +- Fix detection of Salt codename by "salt_version" execution module +- Fix regression: multiple values for keyword argument 'saltenv' (bsc#1212844) +- Fix the regression of user.present state when group is unset (bsc#1212855) +- Fix zypper repositories always being reconfigured +- Fix utf8 handling in 'pass' renderer and make it more robust +- Prevent _pygit2.GitError: error loading known_hosts when $HOME is not set (bsc#1210994) +- Fix ModuleNotFoundError and other issues raised by salt-support module (bsc#1211591) +- tornado: Fix an open redirect in StaticFileHandler (CVE-2023-28370, bsc#1211741) +- Make master_tops compatible with Salt 3000 and older minions (bsc#1212516) (bsc#1212517) +- Avoid failures due transactional_update module not available in Salt 3006.0 (bsc#1211754) +- Avoid conflicts with Salt dependencies versions (bsc#1211612) + +- Added: + * fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch + * fix-the-regression-of-user.present-state-when-group-.patch + * make-master_tops-compatible-with-salt-3000-and-older.patch + * avoid-conflicts-with-dependencies-versions-bsc-12116.patch + * tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch + * fix-regression-multiple-values-for-keyword-argument-.patch + * zypper-pkgrepo-alreadyconfigured-585.patch + * mark-salt-3006-as-released-586.patch + * fix-some-issues-detected-in-salt-support-cli-module-.patch + * define-__virtualname__-for-transactional_update-modu.patch + * 3006.0-prevent-_pygit2.giterror-error-loading-known_.patch + ------------------------------------------------------------------- Fri May 5 08:29:26 UTC 2023 - Alexander Graul diff --git a/salt.spec b/salt.spec index 184e180..fe3aff9 100644 --- a/salt.spec +++ b/salt.spec @@ -267,6 +267,28 @@ Patch60: skip-package-names-without-colon-bsc-1208691-578.patch Patch61: fix-version-detection-and-avoid-building-and-testing.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64113 Patch62: make-sure-the-file-client-is-destroyed-upon-used.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/581 +Patch63: avoid-conflicts-with-dependencies-versions-bsc-12116.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64369 +Patch64: define-__virtualname__-for-transactional_update-modu.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/587 +Patch65: make-master_tops-compatible-with-salt-3000-and-older.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/583 +Patch66: tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/580 +Patch67: fix-some-issues-detected-in-salt-support-cli-module-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64510 +Patch68: 3006.0-prevent-_pygit2.giterror-error-loading-known_.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64300 +Patch69: fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/63403 +Patch70: zypper-pkgrepo-alreadyconfigured-585.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64530 +Patch71: fix-the-regression-of-user.present-state-when-group-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64179 +Patch72: fix-regression-multiple-values-for-keyword-argument-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64554 +Patch73: mark-salt-3006-as-released-586.patch ### IMPORTANT: The line below is used as a snippet marker. Do not touch it. ### SALT PATCHES LIST END @@ -844,7 +866,7 @@ install -Dpm 0755 scripts/suse/watchdog/salt-daemon-watcher %{buildroot}%{_bindi # ## install config files install -Dpm 0640 conf/minion %{buildroot}%{_sysconfdir}/salt/minion -install -Dpm 0640 /dev/null %{buildroot}%{_sysconfdir}/salt/minion_id +touch -m 0640 -r conf/minion %{buildroot}%{_sysconfdir}/salt/minion_id # ghost file install -Dpm 0640 conf/master %{buildroot}%{_sysconfdir}/salt/master install -Dpm 0640 conf/roster %{buildroot}%{_sysconfdir}/salt/roster install -Dpm 0640 conf/cloud %{buildroot}%{_sysconfdir}/salt/cloud diff --git a/tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch b/tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch new file mode 100644 index 0000000..57fa413 --- /dev/null +++ b/tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch @@ -0,0 +1,35 @@ +From 78f5a76315891168d24e923d2b08211baefefb4f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Thu, 22 Jun 2023 16:36:20 +0100 +Subject: [PATCH] tornado: Fix an open redirect in StaticFileHandler + (CVE-2023-28370, bsc#1211741) (#583) + +--- + salt/ext/tornado/web.py | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/salt/ext/tornado/web.py b/salt/ext/tornado/web.py +index 60bde695d3..97fadcf87d 100644 +--- a/salt/ext/tornado/web.py ++++ b/salt/ext/tornado/web.py +@@ -2544,6 +2544,15 @@ class StaticFileHandler(RequestHandler): + # but there is some prefix to the path that was already + # trimmed by the routing + if not self.request.path.endswith("/"): ++ if self.request.path.startswith("//"): ++ # A redirect with two initial slashes is a "protocol-relative" URL. ++ # This means the next path segment is treated as a hostname instead ++ # of a part of the path, making this effectively an open redirect. ++ # Reject paths starting with two slashes to prevent this. ++ # This is only reachable under certain configurations. ++ raise HTTPError( ++ 403, "cannot redirect path with two initial slashes" ++ ) + self.redirect(self.request.path + "/", permanent=True) + return + absolute_path = os.path.join(absolute_path, self.default_filename) +-- +2.41.0 + + diff --git a/zypper-pkgrepo-alreadyconfigured-585.patch b/zypper-pkgrepo-alreadyconfigured-585.patch new file mode 100644 index 0000000..8a6b1e8 --- /dev/null +++ b/zypper-pkgrepo-alreadyconfigured-585.patch @@ -0,0 +1,366 @@ +From 6b6ba4bdbd4b4c52a46bf3d0bcdbaca6b47534d1 Mon Sep 17 00:00:00 2001 +From: Georg +Date: Wed, 28 Jun 2023 16:39:30 +0200 +Subject: [PATCH] Zypper pkgrepo alreadyconfigured (#585) + +* Fix zypper repository reconfiguration + +See https://github.com/saltstack/salt/issues/63402 for issue details. + +Signed-off-by: Georg Pfuetzenreuter + +* Functional pkgrepo tests for SUSE + +Signed-off-by: Georg Pfuetzenreuter + +* Change pkgrepo state to use f-strings + +Follow new styling rules. + +Signed-off-by: Georg Pfuetzenreuter + +--------- + +Signed-off-by: Georg Pfuetzenreuter +Signed-off-by: Georg Pfuetzenreuter +--- + changelog/63402.fixed.md | 1 + + salt/states/pkgrepo.py | 27 ++- + .../functional/states/pkgrepo/test_suse.py | 219 ++++++++++++++++++ + 3 files changed, 235 insertions(+), 12 deletions(-) + create mode 100644 changelog/63402.fixed.md + create mode 100644 tests/pytests/functional/states/pkgrepo/test_suse.py + +diff --git a/changelog/63402.fixed.md b/changelog/63402.fixed.md +new file mode 100644 +index 0000000000..c38715738a +--- /dev/null ++++ b/changelog/63402.fixed.md +@@ -0,0 +1 @@ ++Repaired zypper repositories being reconfigured without changes +diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py +index c2d23f95bb..f041644287 100644 +--- a/salt/states/pkgrepo.py ++++ b/salt/states/pkgrepo.py +@@ -464,7 +464,7 @@ def managed(name, ppa=None, copr=None, aptkey=True, **kwargs): + pre = __salt__["pkg.get_repo"](repo=repo, **kwargs) + except CommandExecutionError as exc: + ret["result"] = False +- ret["comment"] = "Failed to examine repo '{}': {}".format(name, exc) ++ ret["comment"] = f"Failed to examine repo '{name}': {exc}" + return ret + + # This is because of how apt-sources works. This pushes distro logic +@@ -500,7 +500,10 @@ def managed(name, ppa=None, copr=None, aptkey=True, **kwargs): + else: + break + else: +- break ++ if kwarg in ("comps", "key_url"): ++ break ++ else: ++ continue + elif kwarg in ("comps", "key_url"): + if sorted(sanitizedkwargs[kwarg]) != sorted(pre[kwarg]): + break +@@ -546,7 +549,7 @@ def managed(name, ppa=None, copr=None, aptkey=True, **kwargs): + break + else: + ret["result"] = True +- ret["comment"] = "Package repo '{}' already configured".format(name) ++ ret["comment"] = f"Package repo '{name}' already configured" + return ret + + if __opts__["test"]: +@@ -581,7 +584,7 @@ def managed(name, ppa=None, copr=None, aptkey=True, **kwargs): + # This is another way to pass information back from the mod_repo + # function. + ret["result"] = False +- ret["comment"] = "Failed to configure repo '{}': {}".format(name, exc) ++ ret["comment"] = f"Failed to configure repo '{name}': {exc}" + return ret + + try: +@@ -597,10 +600,10 @@ def managed(name, ppa=None, copr=None, aptkey=True, **kwargs): + ret["changes"] = {"repo": repo} + + ret["result"] = True +- ret["comment"] = "Configured package repo '{}'".format(name) ++ ret["comment"] = f"Configured package repo '{name}'" + except Exception as exc: # pylint: disable=broad-except + ret["result"] = False +- ret["comment"] = "Failed to confirm config of repo '{}': {}".format(name, exc) ++ ret["comment"] = f"Failed to confirm config of repo '{name}': {exc}" + + # Clear cache of available packages, if present, since changes to the + # repositories may change the packages that are available. +@@ -700,11 +703,11 @@ def absent(name, **kwargs): + repo = __salt__["pkg.get_repo"](stripname, **kwargs) + except CommandExecutionError as exc: + ret["result"] = False +- ret["comment"] = "Failed to configure repo '{}': {}".format(name, exc) ++ ret["comment"] = f"Failed to configure repo '{name}': {exc}" + return ret + + if not repo: +- ret["comment"] = "Package repo {} is absent".format(name) ++ ret["comment"] = f"Package repo {name} is absent" + ret["result"] = True + return ret + +@@ -727,7 +730,7 @@ def absent(name, **kwargs): + repos = __salt__["pkg.list_repos"]() + if stripname not in repos: + ret["changes"]["repo"] = name +- ret["comment"] = "Removed repo {}".format(name) ++ ret["comment"] = f"Removed repo {name}" + + if not remove_key: + ret["result"] = True +@@ -736,14 +739,14 @@ def absent(name, **kwargs): + removed_keyid = __salt__["pkg.del_repo_key"](stripname, **kwargs) + except (CommandExecutionError, SaltInvocationError) as exc: + ret["result"] = False +- ret["comment"] += ", but failed to remove key: {}".format(exc) ++ ret["comment"] += f", but failed to remove key: {exc}" + else: + ret["result"] = True + ret["changes"]["keyid"] = removed_keyid +- ret["comment"] += ", and keyid {}".format(removed_keyid) ++ ret["comment"] += f", and keyid {removed_keyid}" + else: + ret["result"] = False +- ret["comment"] = "Failed to remove repo {}".format(name) ++ ret["comment"] = f"Failed to remove repo {name}" + + return ret + +diff --git a/tests/pytests/functional/states/pkgrepo/test_suse.py b/tests/pytests/functional/states/pkgrepo/test_suse.py +new file mode 100644 +index 0000000000..19ba928ce6 +--- /dev/null ++++ b/tests/pytests/functional/states/pkgrepo/test_suse.py +@@ -0,0 +1,219 @@ ++import pytest ++ ++pytestmark = [ ++ pytest.mark.destructive_test, ++ pytest.mark.skip_if_not_root, ++] ++ ++ ++@pytest.fixture ++def pkgrepo(states, grains): ++ if grains["os_family"] != "Suse": ++ raise pytest.skip.Exception( ++ "Test is only applicable to SUSE based operating systems", ++ _use_item_location=True, ++ ) ++ return states.pkgrepo ++ ++ ++@pytest.fixture ++def suse_state_tree(grains, pkgrepo, state_tree): ++ managed_sls_contents = """ ++ salttest: ++ pkgrepo.managed: ++ - enabled: 1 ++ - gpgcheck: 1 ++ - comments: ++ - '# Salt Test' ++ - refresh: 1 ++ {% if grains['osmajorrelease'] == 15 %} ++ - baseurl: https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15-SP4/standard/ ++ - humanname: openSUSE Backports for SLE 15 SP4 ++ - gpgkey: https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15-SP4/standard/repodata/repomd.xml.key ++ {% elif grains['osfullname'] == 'openSUSE Tumbleweed' %} ++ - baseurl: http://download.opensuse.org/tumbleweed/repo/oss/ ++ - humanname: openSUSE Tumbleweed OSS ++ - gpgkey: https://download.opensuse.org/tumbleweed/repo/oss/repodata/repomd.xml.key ++ {% endif %} ++ """ ++ ++ absent_sls_contents = """ ++ salttest: ++ pkgrepo: ++ - absent ++ """ ++ ++ modified_sls_contents = """ ++ salttest: ++ pkgrepo.managed: ++ - enabled: 1 ++ - gpgcheck: 1 ++ - comments: ++ - '# Salt Test (modified)' ++ - refresh: 1 ++ {% if grains['osmajorrelease'] == 15 %} ++ - baseurl: https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15-SP4/standard/ ++ - humanname: Salt modified Backports ++ - gpgkey: https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15-SP4/standard/repodata/repomd.xml.key ++ {% elif grains['osfullname'] == 'openSUSE Tumbleweed' %} ++ - baseurl: http://download.opensuse.org/tumbleweed/repo/oss/ ++ - humanname: Salt modified OSS ++ - gpgkey: https://download.opensuse.org/tumbleweed/repo/oss/repodata/repomd.xml.key ++ {% endif %} ++ """ ++ ++ managed_state_file = pytest.helpers.temp_file( ++ "pkgrepo/managed.sls", managed_sls_contents, state_tree ++ ) ++ absent_state_file = pytest.helpers.temp_file( ++ "pkgrepo/absent.sls", absent_sls_contents, state_tree ++ ) ++ modified_state_file = pytest.helpers.temp_file( ++ "pkgrepo/modified.sls", modified_sls_contents, state_tree ++ ) ++ ++ try: ++ with managed_state_file, absent_state_file, modified_state_file: ++ yield ++ finally: ++ pass ++ ++ ++@pytest.mark.requires_salt_states("pkgrepo.managed", "pkgrepo.absent") ++def test_pkgrepo_managed_absent(grains, modules, subtests, suse_state_tree): ++ """ ++ Test adding and removing a repository ++ """ ++ add_repo_test_passed = False ++ ++ def _run(name, test=False): ++ return modules.state.sls( ++ mods=name, ++ test=test, ++ ) ++ ++ with subtests.test("Add repository"): ++ ret = _run("pkgrepo.managed") ++ assert ret.failed is False ++ for state in ret: ++ assert state.result is True ++ add_repo_test_passed = True ++ ++ if add_repo_test_passed is False: ++ pytest.skip("Adding the repository failed, skipping removal tests.") ++ ++ with subtests.test("Remove repository, test"): ++ ret = _run("pkgrepo.absent", test=True) ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == {} ++ assert state.comment.startswith("Package repo 'salttest' will be removed.") ++ assert state.result is None ++ ++ with subtests.test("Remove repository"): ++ ret = _run("pkgrepo.absent") ++ assert ret.failed is False ++ for state in ret: ++ assert state.result is True ++ ++ with subtests.test("Remove repository again, test"): ++ ret = _run("pkgrepo.absent", test=True) ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == {} ++ assert state.comment == "Package repo salttest is absent" ++ assert state.result is True ++ ++ with subtests.test("Remove repository again"): ++ ret = _run("pkgrepo.absent") ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == {} ++ assert state.comment == "Package repo salttest is absent" ++ assert state.result is True ++ ++ ++@pytest.mark.requires_salt_states("pkgrepo.managed") ++def test_pkgrepo_managed_modify(grains, modules, subtests, suse_state_tree): ++ """ ++ Test adding and modifying a repository ++ """ ++ add_repo_test_passed = False ++ ++ def _run(name, test=False): ++ return modules.state.sls( ++ mods=name, ++ test=test, ++ ) ++ ++ with subtests.test("Add repository, test"): ++ ret = _run("pkgrepo.managed", test=True) ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == {"repo": "salttest"} ++ assert state.comment.startswith( ++ "Package repo 'salttest' would be configured." ++ ) ++ assert state.result is None ++ ++ with subtests.test("Add repository"): ++ ret = _run("pkgrepo.managed") ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == {"repo": "salttest"} ++ assert state.comment == "Configured package repo 'salttest'" ++ assert state.result is True ++ add_repo_test_passed = True ++ ++ if add_repo_test_passed is False: ++ pytest.skip("Adding the repository failed, skipping modification tests.") ++ ++ with subtests.test("Add repository again, test"): ++ ret = _run("pkgrepo.managed", test=True) ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == {} ++ assert state.comment == "Package repo 'salttest' already configured" ++ assert state.result is True ++ ++ with subtests.test("Add repository again"): ++ ret = _run("pkgrepo.managed") ++ assert ret.failed is False ++ for state in ret: ++ assert state.result is True ++ assert state.changes == {} ++ assert state.comment == "Package repo 'salttest' already configured" ++ ++ with subtests.test("Modify repository, test"): ++ ret = _run("pkgrepo.modified", test=True) ++ assert ret.failed is False ++ for state in ret: ++ assert state.changes == { ++ "comments": {"new": ["# Salt Test (modified)"], "old": None}, ++ "refresh": {"new": 1, "old": None}, ++ "gpgkey": { ++ "new": "https://download.opensuse.org/repositories/openSUSE:/Backports:/SLE-15-SP4/standard/repodata/repomd.xml.key", ++ "old": None, ++ }, ++ "name": { ++ "new": "Salt modified Backports", ++ "old": "openSUSE Backports for SLE 15 SP4", ++ }, ++ } ++ assert state.comment.startswith( ++ "Package repo 'salttest' would be configured." ++ ) ++ assert state.result is None ++ ++ with subtests.test("Modify repository"): ++ ret = _run("pkgrepo.modified") ++ assert ret.failed is False ++ for state in ret: ++ assert state.result is True ++ assert state.changes == { ++ "name": { ++ "new": "Salt modified Backports", ++ "old": "openSUSE Backports for SLE 15 SP4", ++ } ++ } ++ assert state.comment == "Configured package repo 'salttest'" +-- +2.41.0 + +