commit 101832fd70f4e8c101ac1649c723c8d5d6c12b22 Author: Adrian Schröter Date: Fri Sep 22 09:44:49 2023 +0200 Sync from SUSE:ALP:Source:Standard:1.0 salt revision 7acf5f38fdb4dfcaa487fd53bc3679fb diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fecc750 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,23 @@ +## Default LFS +*.7z filter=lfs diff=lfs merge=lfs -text +*.bsp filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.gem filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.jar filter=lfs diff=lfs merge=lfs -text +*.lz filter=lfs diff=lfs merge=lfs -text +*.lzma filter=lfs diff=lfs merge=lfs -text +*.obscpio filter=lfs diff=lfs merge=lfs -text +*.oxt filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.rpm filter=lfs diff=lfs merge=lfs -text +*.tbz filter=lfs diff=lfs merge=lfs -text +*.tbz2 filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.txz filter=lfs diff=lfs merge=lfs -text +*.whl filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zst filter=lfs diff=lfs merge=lfs -text diff --git a/3005.1-implement-zypper-removeptf-573.patch b/3005.1-implement-zypper-removeptf-573.patch new file mode 100644 index 0000000..cfc5453 --- /dev/null +++ b/3005.1-implement-zypper-removeptf-573.patch @@ -0,0 +1,505 @@ +From 327a5e5b24c4fa047df44b245abd672e02999cca Mon Sep 17 00:00:00 2001 +From: Michael Calmer +Date: Mon, 23 Jan 2023 14:33:26 +0100 +Subject: [PATCH] 3005.1 implement zypper removeptf (#573) + +* handle ptf packages inside of normal pkg.remove function + +* add testcase for remove and removeptf + +* add changelog + +* adapt old tests to changed function + +* Update Docs + +Co-authored-by: Megan Wilhite +--- + changelog/63442.added | 1 + + salt/modules/zypperpkg.py | 38 +- + tests/pytests/unit/modules/test_zypperpkg.py | 356 ++++++++++++++++++- + tests/unit/modules/test_zypperpkg.py | 1 + + 4 files changed, 394 insertions(+), 2 deletions(-) + create mode 100644 changelog/63442.added + +diff --git a/changelog/63442.added b/changelog/63442.added +new file mode 100644 +index 0000000000..ad81b2f9d5 +--- /dev/null ++++ b/changelog/63442.added +@@ -0,0 +1 @@ ++implement removal of ptf packages in zypper pkg module +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 051f8f72c7..44f2cdbd3a 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -2073,17 +2073,21 @@ def _uninstall(inclusion_detection, name=None, pkgs=None, root=None): + except MinionError as exc: + raise CommandExecutionError(exc) + ++ ptfpackages = _find_ptf_packages(pkg_params.keys(), root=root) + includes = _detect_includes(pkg_params.keys(), inclusion_detection) + old = list_pkgs(root=root, includes=includes) + targets = [] + for target in pkg_params: ++ if target in ptfpackages: ++ # ptfpackages needs special handling ++ continue + # Check if package version set to be removed is actually installed: + # old[target] contains a comma-separated list of installed versions + if target in old and pkg_params[target] in old[target].split(","): + targets.append(target + "-" + pkg_params[target]) + elif target in old and not pkg_params[target]: + targets.append(target) +- if not targets: ++ if not targets and not ptfpackages: + return {} + + systemd_scope = _systemd_scope() +@@ -2095,6 +2099,13 @@ def _uninstall(inclusion_detection, name=None, pkgs=None, root=None): + ) + targets = targets[500:] + ++ # handle ptf packages ++ while ptfpackages: ++ __zypper__(systemd_scope=systemd_scope, root=root).call( ++ "removeptf", "--allow-downgrade", *ptfpackages[:500] ++ ) ++ ptfpackages = ptfpackages[500:] ++ + _clean_cache() + new = list_pkgs(root=root, includes=includes) + ret = salt.utils.data.compare_dicts(old, new) +@@ -2183,6 +2194,11 @@ def remove( + salt '*' pkg.remove + salt '*' pkg.remove ,, + salt '*' pkg.remove pkgs='["foo", "bar"]' ++ ++ .. versionchanged:: 3007 ++ Can now remove also PTF packages which require a different handling in the backend. ++ ++ Can now remove also PTF packages which require a different handling in the backend. + """ + return _uninstall(inclusion_detection, name=name, pkgs=pkgs, root=root) + +@@ -2658,6 +2674,26 @@ def _get_visible_patterns(root=None): + return patterns + + ++def _find_ptf_packages(pkgs, root=None): ++ """ ++ Find ptf packages in "pkgs" and return them as list ++ """ ++ ptfs = [] ++ cmd = ["rpm"] ++ if root: ++ cmd.extend(["--root", root]) ++ cmd.extend(["-q", "--qf", "%{NAME}: [%{PROVIDES} ]\n"]) ++ cmd.extend(pkgs) ++ output = __salt__["cmd.run"](cmd) ++ for line in output.splitlines(): ++ if not line.strip(): ++ continue ++ pkg, provides = line.split(":", 1) ++ if "ptf()" in provides: ++ ptfs.append(pkg) ++ return ptfs ++ ++ + def _get_installed_patterns(root=None): + """ + List all installed patterns. +diff --git a/tests/pytests/unit/modules/test_zypperpkg.py b/tests/pytests/unit/modules/test_zypperpkg.py +index 91132b7277..c996662e1c 100644 +--- a/tests/pytests/unit/modules/test_zypperpkg.py ++++ b/tests/pytests/unit/modules/test_zypperpkg.py +@@ -11,7 +11,7 @@ import pytest + import salt.modules.pkg_resource as pkg_resource + import salt.modules.zypperpkg as zypper + from salt.exceptions import CommandExecutionError, SaltInvocationError +-from tests.support.mock import MagicMock, mock_open, patch ++from tests.support.mock import MagicMock, mock_open, call, patch + + + @pytest.fixture +@@ -27,6 +27,11 @@ def configure_loader_modules(): + } + + ++@pytest.fixture(autouse=True) ++def fresh_zypper_instance(): ++ zypper.__zypper__ = zypper._Zypper() ++ ++ + def test_list_pkgs_no_context(): + """ + Test packages listing. +@@ -395,3 +400,352 @@ def test_del_repo_key(): + with patch.dict(zypper.__salt__, salt_mock): + assert zypper.del_repo_key(keyid="keyid", root="/mnt") + salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") ++ ++@pytest.mark.parametrize( ++ "zypper_version,lowpkg_version_cmp,expected_inst_avc,expected_dup_avc", ++ [ ++ ("0.5", [-1, -1], False, False), ++ ("1.11.34", [0, -1], False, True), ++ ("1.14.8", [0, 0], True, True), ++ ], ++) ++def test_refresh_zypper_flags( ++ zypper_version, lowpkg_version_cmp, expected_inst_avc, expected_dup_avc ++): ++ with patch( ++ "salt.modules.zypperpkg.version", MagicMock(return_value=zypper_version) ++ ), patch.dict( ++ zypper.__salt__, ++ {"lowpkg.version_cmp": MagicMock(side_effect=lowpkg_version_cmp)}, ++ ): ++ _zypper = zypper._Zypper() ++ _zypper.refresh_zypper_flags() ++ assert _zypper.inst_avc == expected_inst_avc ++ assert _zypper.dup_avc == expected_dup_avc ++ ++ ++@pytest.mark.parametrize( ++ "inst_avc,dup_avc,avc,allowvendorchange_param,novendorchange_param,expected", ++ [ ++ # inst_avc = True, dup_avc = True ++ (True, True, False, False, False, True), ++ (True, True, False, True, False, True), ++ (True, True, False, False, True, False), ++ (True, True, False, True, True, True), ++ # inst_avc = False, dup_avc = True ++ (False, True, False, False, False, True), ++ (False, True, False, True, False, True), ++ (False, True, False, False, True, False), ++ (False, True, False, True, True, True), ++ # inst_avc = False, dup_avc = False ++ (False, False, False, False, False, False), ++ (False, False, False, True, False, False), ++ (False, False, False, False, True, False), ++ (False, False, False, True, True, False), ++ ], ++) ++@patch("salt.modules.zypperpkg._Zypper.refresh_zypper_flags", MagicMock()) ++def test_allow_vendor_change( ++ inst_avc, ++ dup_avc, ++ avc, ++ allowvendorchange_param, ++ novendorchange_param, ++ expected, ++): ++ _zypper = zypper._Zypper() ++ _zypper.inst_avc = inst_avc ++ _zypper.dup_avc = dup_avc ++ _zypper.avc = avc ++ _zypper.allow_vendor_change(allowvendorchange_param, novendorchange_param) ++ assert _zypper.avc == expected ++ ++ ++@pytest.mark.parametrize( ++ "package,pre_version,post_version,fromrepo_param,name_param,pkgs_param,diff_attr_param", ++ [ ++ ("vim", "1.1", "1.2", [], "", [], "all"), ++ ("kernel-default", "1.1", "1.1,1.2", ["dummy", "dummy2"], "", [], None), ++ ("vim", "1.1", "1.2", [], "vim", [], None), ++ ], ++) ++@patch.object(zypper, "refresh_db", MagicMock(return_value=True)) ++def test_upgrade( ++ package, ++ pre_version, ++ post_version, ++ fromrepo_param, ++ name_param, ++ pkgs_param, ++ diff_attr_param, ++): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call" ++ ) as zypper_mock, patch.object( ++ zypper, ++ "list_pkgs", ++ MagicMock(side_effect=[{package: pre_version}, {package: post_version}]), ++ ) as list_pkgs_mock: ++ expected_call = ["update", "--auto-agree-with-licenses"] ++ for repo in fromrepo_param: ++ expected_call.extend(["--repo", repo]) ++ ++ if pkgs_param: ++ expected_call.extend(pkgs_param) ++ elif name_param: ++ expected_call.append(name_param) ++ ++ result = zypper.upgrade( ++ name=name_param, ++ pkgs=pkgs_param, ++ fromrepo=fromrepo_param, ++ diff_attr=diff_attr_param, ++ ) ++ zypper_mock.assert_any_call(*expected_call) ++ assert result == {package: {"old": pre_version, "new": post_version}} ++ list_pkgs_mock.assert_any_call(root=None, attr=diff_attr_param) ++ ++ ++@pytest.mark.parametrize( ++ "package,pre_version,post_version,fromrepo_param", ++ [ ++ ("vim", "1.1", "1.2", []), ++ ("emacs", "1.1", "1.2", ["Dummy", "Dummy2"]), ++ ], ++) ++@patch.object(zypper, "refresh_db", MagicMock(return_value=True)) ++def test_dist_upgrade(package, pre_version, post_version, fromrepo_param): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call" ++ ) as zypper_mock, patch.object( ++ zypper, ++ "list_pkgs", ++ MagicMock(side_effect=[{package: pre_version}, {package: post_version}]), ++ ): ++ expected_call = ["dist-upgrade", "--auto-agree-with-licenses"] ++ ++ for repo in fromrepo_param: ++ expected_call.extend(["--from", repo]) ++ ++ result = zypper.upgrade(dist_upgrade=True, fromrepo=fromrepo_param) ++ zypper_mock.assert_any_call(*expected_call) ++ assert result == {package: {"old": pre_version, "new": post_version}} ++ ++ ++@pytest.mark.parametrize( ++ "package,pre_version,post_version,dup_avc,novendorchange_param,allowvendorchange_param,vendor_change", ++ [ ++ # dup_avc = True, both params = default -> no vendor change ++ ("vim", "1.1", "1.2", True, True, False, False), ++ # dup_avc = True, allowvendorchange = True -> vendor change ++ ( ++ "emacs", ++ "1.1", ++ "1.2", ++ True, ++ True, ++ True, ++ True, ++ ), ++ # dup_avc = True, novendorchange = False -> vendor change ++ ("joe", "1.1", "1.2", True, False, False, True), ++ # dup_avc = True, both params = toggled -> vendor change ++ ("kate", "1.1", "1.2", True, False, True, True), ++ # dup_avc = False -> no vendor change ++ ( ++ "gedit", ++ "1.1", ++ "1.2", ++ False, ++ False, ++ True, ++ False ++ ), ++ ], ++) ++@patch.object(zypper, "refresh_db", MagicMock(return_value=True)) ++def test_dist_upgrade_vendorchange( ++ package, ++ pre_version, ++ post_version, ++ dup_avc, ++ novendorchange_param, ++ allowvendorchange_param, ++ vendor_change ++): ++ cmd_run_mock = MagicMock(return_value={"retcode": 0, "stdout": None}) ++ with patch.object( ++ zypper, ++ "list_pkgs", ++ MagicMock(side_effect=[{package: pre_version}, {package: post_version}]), ++ ), patch("salt.modules.zypperpkg.__zypper__.refresh_zypper_flags",), patch.dict( ++ zypper.__salt__, {"cmd.run_all": cmd_run_mock} ++ ): ++ expected_cmd = ["zypper", "--non-interactive", "--no-refresh", "dist-upgrade"] ++ # --allow-vendor-change is injected right after "dist-upgrade" ++ if vendor_change: ++ expected_cmd.append("--allow-vendor-change") ++ expected_cmd.append("--auto-agree-with-licenses") ++ ++ zypper.__zypper__.dup_avc = dup_avc ++ zypper.upgrade( ++ dist_upgrade=True, ++ allowvendorchange=allowvendorchange_param, ++ novendorchange=novendorchange_param, ++ ) ++ cmd_run_mock.assert_any_call( ++ expected_cmd, output_loglevel="trace", python_shell=False, env={} ++ ) ++ ++ ++@pytest.mark.parametrize( ++ "package,pre_version,post_version,fromrepo_param", ++ [ ++ ("vim", "1.1", "1.1", []), ++ ("emacs", "1.1", "1.1", ["Dummy", "Dummy2"]), ++ ], ++) ++@patch.object(zypper, "refresh_db", MagicMock(return_value=True)) ++def test_dist_upgrade_dry_run(package, pre_version, post_version, fromrepo_param): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call" ++ ) as zypper_mock, patch.object( ++ zypper, ++ "list_pkgs", ++ MagicMock(side_effect=[{package: pre_version}, {package: post_version}]), ++ ): ++ expected_call = ["dist-upgrade", "--auto-agree-with-licenses", "--dry-run"] ++ ++ for repo in fromrepo_param: ++ expected_call.extend(["--from", repo]) ++ ++ zypper.upgrade(dist_upgrade=True, dryrun=True, fromrepo=fromrepo_param) ++ zypper_mock.assert_any_call(*expected_call) ++ # dryrun=True causes two calls, one with a trailing --debug-solver flag ++ expected_call.append("--debug-solver") ++ zypper_mock.assert_any_call(*expected_call) ++ ++ ++@patch.object(zypper, "refresh_db", MagicMock(return_value=True)) ++def test_dist_upgrade_failure(): ++ zypper_output = textwrap.dedent( ++ """\ ++ Loading repository data... ++ Reading installed packages... ++ Computing distribution upgrade... ++ Use 'zypper repos' to get the list of defined repositories. ++ Repository 'DUMMY' not found by its alias, number, or URI. ++ """ ++ ) ++ call_spy = MagicMock() ++ zypper_mock = MagicMock() ++ zypper_mock.stdout = zypper_output ++ zypper_mock.stderr = "" ++ zypper_mock.exit_code = 3 ++ zypper_mock.noraise.call = call_spy ++ with patch("salt.modules.zypperpkg.__zypper__", zypper_mock), patch.object( ++ zypper, "list_pkgs", MagicMock(side_effect=[{"vim": 1.1}, {"vim": 1.1}]) ++ ): ++ expected_call = [ ++ "dist-upgrade", ++ "--auto-agree-with-licenses", ++ "--from", ++ "Dummy", ++ ] ++ ++ with pytest.raises(CommandExecutionError) as exc: ++ zypper.upgrade(dist_upgrade=True, fromrepo=["Dummy"]) ++ call_spy.assert_called_with(*expected_call) ++ ++ assert exc.exception.info["changes"] == {} ++ assert exc.exception.info["result"]["stdout"] == zypper_output ++ ++ ++def test_remove_multiple_pkgs_with_ptf(): ++ call_spy = MagicMock() ++ zypper_mock = MagicMock() ++ zypper_mock.stdout = "" ++ zypper_mock.stderr = "" ++ zypper_mock.exit_code = 0 ++ zypper_mock.call = call_spy ++ ++ rpm_output = textwrap.dedent( ++ """ ++ vim: vi vim vim(x86-64) vim-base vim-enhanced vim-python vim_client ++ ptf-12345: ptf() ptf-12345 ++ """ ++ ) ++ rpm_mock = MagicMock(side_effect=[rpm_output]) ++ ++ with patch( ++ "salt.modules.zypperpkg.__zypper__", MagicMock(return_value=zypper_mock) ++ ), patch.object( ++ zypper, ++ "list_pkgs", ++ MagicMock(side_effect=[{"vim": "0.18.0", "ptf-12345": "1"}, {}]), ++ ), patch.dict( ++ zypper.__salt__, {"cmd.run": rpm_mock} ++ ): ++ expected_calls = [ ++ call( ++ "remove", ++ "vim", ++ ), ++ call( ++ "removeptf", ++ "--allow-downgrade", ++ "ptf-12345", ++ ), ++ ] ++ ++ result = zypper.remove(name="vim,ptf-12345") ++ call_spy.assert_has_calls(expected_calls, any_order=False) ++ assert result["vim"]["new"] == "", result ++ assert result["vim"]["old"] == "0.18.0", result ++ assert result["ptf-12345"]["new"] == "", result ++ assert result["ptf-12345"]["old"] == "1", result ++ ++ ++def test_remove_ptf(): ++ call_spy = MagicMock() ++ zypper_mock = MagicMock() ++ zypper_mock.stdout = "" ++ zypper_mock.stderr = "" ++ zypper_mock.exit_code = 0 ++ zypper_mock.call = call_spy ++ ++ rpm_mock = MagicMock( ++ side_effect=[ ++ "vim: vi vim vim(x86-64) vim-base vim-enhanced vim-python vim_client", ++ "ptf-12345: ptf() ptf-12345", ++ ] ++ ) ++ ++ with patch( ++ "salt.modules.zypperpkg.__zypper__", MagicMock(return_value=zypper_mock) ++ ), patch.object( ++ zypper, ++ "list_pkgs", ++ MagicMock(side_effect=[{"vim": "0.18.0"}, {}, {"ptf-12345": "1"}, {}]), ++ ), patch.dict( ++ zypper.__salt__, {"cmd.run": rpm_mock} ++ ): ++ expected_call_vim = [ ++ "remove", ++ "vim", ++ ] ++ expected_call_ptf = [ ++ "removeptf", ++ "--allow-downgrade", ++ "ptf-12345", ++ ] ++ ++ result = zypper.remove(name="vim") ++ call_spy.assert_called_with(*expected_call_vim) ++ assert result["vim"]["new"] == "", result ++ assert result["vim"]["old"] == "0.18.0", result ++ ++ result = zypper.remove(name="ptf-12345") ++ call_spy.assert_called_with(*expected_call_ptf) ++ assert result["ptf-12345"]["new"] == "", result ++ assert result["ptf-12345"]["old"] == "1", result +diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py +index f5b6d74b6f..6e5ca88895 100644 +--- a/tests/unit/modules/test_zypperpkg.py ++++ b/tests/unit/modules/test_zypperpkg.py +@@ -1953,6 +1953,7 @@ Repository 'DUMMY' not found by its alias, number, or URI. + # If config.get starts being used elsewhere, we'll need to write a + # side_effect function. + patches = { ++ "cmd.run": MagicMock(return_value="vim: vi vim\npico: pico"), + "cmd.run_all": MagicMock(return_value=cmd_out), + "pkg_resource.parse_targets": MagicMock(return_value=parsed_targets), + "pkg_resource.stringify": MagicMock(), +-- +2.39.2 + + 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..119d50d --- /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/README.SUSE b/README.SUSE new file mode 100644 index 0000000..10986b1 --- /dev/null +++ b/README.SUSE @@ -0,0 +1,31 @@ +Salt-master as non-root user +============================ + +With this version of salt the salt-master will run as salt user. + +Why an extra user +================= + +While the current setup runs the master as root user, this is considered a security issue +and not in line with the other configuration management tools (eg. puppet) which runs as a +dedicated user. + +How can I undo the change +========================= + +If you would like to make the change before you can do the following steps manually: +1. change the user parameter in the master configuration + user: root +2. update the file permissions: + as root: chown -R root /etc/salt /var/cache/salt /var/log/salt /var/run/salt +3. restart the salt-master daemon: + as root: rcsalt-master restart or systemctl restart salt-master + +NOTE +==== + +Running the salt-master daemon as a root user is considers by some a security risk, but +running as root, enables the pam external auth system, as this system needs root access to check authentication. + +For more information: +http://docs.saltstack.com/en/latest/ref/configuration/nonroot.html \ No newline at end of file diff --git a/_lastrevision b/_lastrevision new file mode 100644 index 0000000..e5f69c4 --- /dev/null +++ b/_lastrevision @@ -0,0 +1 @@ +3becea2e5b00beff724c22a8ae320d4567031c7b \ No newline at end of file diff --git a/_service b/_service new file mode 100644 index 0000000..ec5018b --- /dev/null +++ b/_service @@ -0,0 +1,20 @@ + + + https://github.com/openSUSE/salt-packaging.git + salt + package + release/3006.0 + git + + + *package*.tar + */* + + + codeload.github.com + openSUSE/salt/tar.gz/v3006.0-suse + v3006.0.tar.gz + + + + diff --git a/activate-all-beacons-sources-config-pillar-grains.patch b/activate-all-beacons-sources-config-pillar-grains.patch new file mode 100644 index 0000000..f2bdba5 --- /dev/null +++ b/activate-all-beacons-sources-config-pillar-grains.patch @@ -0,0 +1,28 @@ +From f2938966bd1fcb46df0f202f5a86729ab190565a Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Tue, 17 Oct 2017 16:52:33 +0200 +Subject: [PATCH] Activate all beacons sources: config/pillar/grains + +--- + salt/minion.py | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/salt/minion.py b/salt/minion.py +index 6237fcc4b7..2f905e4a4f 100644 +--- a/salt/minion.py ++++ b/salt/minion.py +@@ -503,9 +503,7 @@ class MinionBase: + the pillar or grains changed + """ + if "config.merge" in functions: +- b_conf = functions["config.merge"]( +- "beacons", self.opts["beacons"], omit_opts=True +- ) ++ b_conf = functions["config.merge"]("beacons", self.opts["beacons"]) + if b_conf: + return self.beacons.process( + b_conf, self.opts["grains"] +-- +2.39.2 + + diff --git a/add-custom-suse-capabilities-as-grains.patch b/add-custom-suse-capabilities-as-grains.patch new file mode 100644 index 0000000..dfea19b --- /dev/null +++ b/add-custom-suse-capabilities-as-grains.patch @@ -0,0 +1,30 @@ +From 311d4e320527158b6ff88604b45e15f0dc2bfa62 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 12:59:43 +0100 +Subject: [PATCH] Add custom SUSE capabilities as Grains + +Add new custom SUSE capability for saltutil state module +--- + salt/grains/extra.py | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/salt/grains/extra.py b/salt/grains/extra.py +index 300052f1ee..f2504dbf19 100644 +--- a/salt/grains/extra.py ++++ b/salt/grains/extra.py +@@ -96,3 +96,11 @@ def uefi(): + def transactional(): + """Determine if the system is transactional.""" + return {"transactional": bool(salt.utils.path.which("transactional-update"))} ++ ++ ++def suse_backported_capabilities(): ++ return { ++ '__suse_reserved_pkg_all_versions_support': True, ++ '__suse_reserved_pkg_patches_support': True, ++ '__suse_reserved_saltutil_states_support': True ++ } +-- +2.39.2 + + diff --git a/add-environment-variable-to-know-if-yum-is-invoked-f.patch b/add-environment-variable-to-know-if-yum-is-invoked-f.patch new file mode 100644 index 0000000..9f6b077 --- /dev/null +++ b/add-environment-variable-to-know-if-yum-is-invoked-f.patch @@ -0,0 +1,83 @@ +From d7682d1bc67ccdd63022c63b2d3229f8ab40d52b Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 12:57:21 +0100 +Subject: [PATCH] Add environment variable to know if yum is invoked from + Salt(bsc#1057635) + +--- + salt/modules/yumpkg.py | 23 +++++++++++++++++------ + 1 file changed, 17 insertions(+), 6 deletions(-) + +diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py +index 4d0070f21a..b362d30bf4 100644 +--- a/salt/modules/yumpkg.py ++++ b/salt/modules/yumpkg.py +@@ -964,7 +964,9 @@ def list_repo_pkgs(*args, **kwargs): + None + if _yum() != "yum" + else LooseVersion( +- __salt__["cmd.run"](["yum", "--version"], python_shell=False) ++ __salt__["cmd.run"]( ++ ["yum", "--version"], python_shell=False, env={"SALT_RUNNING": "1"} ++ ) + .splitlines()[0] + .strip() + ) +@@ -2474,7 +2476,9 @@ def list_holds(pattern=__HOLD_PATTERN, full=True): + """ + _check_versionlock() + +- out = __salt__["cmd.run"]([_yum(), "versionlock", "list"], python_shell=False) ++ out = __salt__["cmd.run"]( ++ [_yum(), "versionlock", "list"], python_shell=False, env={"SALT_RUNNING": "1"} ++ ) + ret = [] + for line in salt.utils.itertools.split(out, "\n"): + match = _get_hold(line, pattern=pattern, full=full) +@@ -2542,7 +2546,10 @@ def group_list(): + } + + out = __salt__["cmd.run_stdout"]( +- [_yum(), "grouplist", "hidden"], output_loglevel="trace", python_shell=False ++ [_yum(), "grouplist", "hidden"], ++ output_loglevel="trace", ++ python_shell=False, ++ env={"SALT_RUNNING": "1"}, + ) + key = None + for line in salt.utils.itertools.split(out, "\n"): +@@ -2613,7 +2620,9 @@ def group_info(name, expand=False, ignore_groups=None): + ret[pkgtype] = set() + + cmd = [_yum(), "--quiet", "groupinfo", name] +- out = __salt__["cmd.run_stdout"](cmd, output_loglevel="trace", python_shell=False) ++ out = __salt__["cmd.run_stdout"]( ++ cmd, output_loglevel="trace", python_shell=False, env={"SALT_RUNNING": "1"} ++ ) + + g_info = {} + for line in salt.utils.itertools.split(out, "\n"): +@@ -3342,7 +3351,9 @@ def download(*packages, **kwargs): + + cmd = ["yumdownloader", "-q", "--destdir={}".format(CACHE_DIR)] + cmd.extend(packages) +- __salt__["cmd.run"](cmd, output_loglevel="trace", python_shell=False) ++ __salt__["cmd.run"]( ++ cmd, output_loglevel="trace", python_shell=False, env={"SALT_RUNNING": "1"} ++ ) + ret = {} + for dld_result in os.listdir(CACHE_DIR): + if not dld_result.endswith(".rpm"): +@@ -3418,7 +3429,7 @@ def _get_patches(installed_only=False): + patches = {} + + cmd = [_yum(), "--quiet", "updateinfo", "list", "all"] +- ret = __salt__["cmd.run_stdout"](cmd, python_shell=False) ++ ret = __salt__["cmd.run_stdout"](cmd, python_shell=False, env={"SALT_RUNNING": "1"}) + parsing_errors = False + + for line in salt.utils.itertools.split(ret, os.linesep): +-- +2.39.2 + + diff --git a/add-migrated-state-and-gpg-key-management-functions-.patch b/add-migrated-state-and-gpg-key-management-functions-.patch new file mode 100644 index 0000000..550385c --- /dev/null +++ b/add-migrated-state-and-gpg-key-management-functions-.patch @@ -0,0 +1,1216 @@ +From c5236dadcffc24c00181c10ac4cf56020371c538 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 18:40:40 +0100 +Subject: [PATCH] Add "migrated" state and GPG key management functions + (#290) + +* rpm_lowpkg: add API for GPG keys + +* zypperpkg: do not quote the repo name + +* pkgrepo: add migrated function + +* pkg: unify apt and rpm API for key repo + +aptpkg is the virtual package "pkg" for Debian, and contains some API +for key management. + +This patch add a similar API for zypperpkg and yumpkg, also part of the +same virtual package, based on the counterpart from rpm_lowpkg API. + +Convert test to pytests +--- + salt/modules/aptpkg.py | 4 +- + salt/modules/rpm_lowpkg.py | 151 +++++++ + salt/modules/yumpkg.py | 88 ++++ + salt/modules/zypperpkg.py | 88 ++++ + salt/states/pkgrepo.py | 207 +++++++++ + tests/pytests/unit/modules/test_yumpkg.py | 44 +- + tests/pytests/unit/modules/test_zypperpkg.py | 45 +- + tests/pytests/unit/states/test_pkgrepo.py | 448 +++++++++++++++++++ + 8 files changed, 1070 insertions(+), 5 deletions(-) + +diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py +index 3289f6604d..9885e9fb60 100644 +--- a/salt/modules/aptpkg.py ++++ b/salt/modules/aptpkg.py +@@ -2197,7 +2197,7 @@ def _parse_repo_keys_output(cmd_ret): + return ret + + +-def get_repo_keys(aptkey=True, keydir=None): ++def get_repo_keys(aptkey=True, keydir=None, **kwargs): + """ + .. versionadded:: 2017.7.0 + +@@ -2305,6 +2305,7 @@ def add_repo_key( + aptkey=True, + keydir=None, + keyfile=None, ++ **kwargs + ): + """ + .. versionadded:: 2017.7.0 +@@ -2358,7 +2359,6 @@ def add_repo_key( + if not salt.utils.path.which("apt-key"): + aptkey = False + cmd = ["apt-key"] +- kwargs = {} + + # If the keyid is provided or determined, check it against the existing + # repo key ids to determine whether it needs to be imported. +diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py +index 4cd137c258..b360ec8df3 100644 +--- a/salt/modules/rpm_lowpkg.py ++++ b/salt/modules/rpm_lowpkg.py +@@ -865,3 +865,154 @@ def checksum(*paths, **kwargs): + ) + + return ret ++ ++ ++def list_gpg_keys(info=False, root=None): ++ """Return the list of all the GPG keys stored in the RPM database ++ ++ .. versionadded:: TBD ++ ++ info ++ get the key information, returing a dictionary instead of a ++ list ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' lowpkg.list_gpg_keys ++ salt '*' lowpkg.list_gpg_keys info=True ++ ++ """ ++ cmd = ["rpm"] ++ if root: ++ cmd.extend(["--root", root]) ++ cmd.extend(["-qa", "gpg-pubkey*"]) ++ keys = __salt__["cmd.run_stdout"](cmd, python_shell=False).splitlines() ++ if info: ++ return {key: info_gpg_key(key, root=root) for key in keys} ++ else: ++ return keys ++ ++ ++def info_gpg_key(key, root=None): ++ """Return a dictionary with the information of a GPG key parsed ++ ++ .. versionadded:: TBD ++ ++ key ++ key identificatior ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' lowpkg.info_gpg_key gpg-pubkey-3dbdc284-53674dd4 ++ ++ """ ++ cmd = ["rpm"] ++ if root: ++ cmd.extend(["--root", root]) ++ cmd.extend(["-qi", key]) ++ info = __salt__["cmd.run_stdout"](cmd, python_shell=False) ++ ++ res = {} ++ # The parser algorithm is very ad-hoc. Works under the ++ # expectation that all the fields are of the type "key: value" in ++ # a single line, except "Description", that will be composed of ++ # multiple lines. Note that even if the official `rpm` makes this ++ # field the last one, other (like openSUSE) exted it with more ++ # fields. ++ in_description = False ++ description = [] ++ for line in info.splitlines(): ++ if line.startswith("Description"): ++ in_description = True ++ elif in_description: ++ description.append(line) ++ if line.startswith("-----END"): ++ res["Description"] = "\n".join(description) ++ in_description = False ++ elif line: ++ key, _, value = line.partition(":") ++ value = value.strip() ++ if "Date" in key: ++ try: ++ value = datetime.datetime.strptime( ++ value, "%a %d %b %Y %H:%M:%S %p %Z" ++ ) ++ except ValueError: ++ pass ++ elif "Size" in key: ++ try: ++ value = int(value) ++ except TypeError: ++ pass ++ elif "(none)" in value: ++ value = None ++ res[key.strip()] = value ++ return res ++ ++ ++def import_gpg_key(key, root=None): ++ """Import a new key into the key storage ++ ++ .. versionadded:: TBD ++ ++ key ++ public key block content ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' lowpkg.import_gpg_key "-----BEGIN ..." ++ ++ """ ++ key_file = salt.utils.files.mkstemp() ++ with salt.utils.files.fopen(key_file, "w") as f: ++ f.write(key) ++ ++ cmd = ["rpm"] ++ if root: ++ cmd.extend(["--root", root]) ++ cmd.extend(["--import", key_file]) ++ ret = __salt__["cmd.retcode"](cmd) ++ ++ os.remove(key_file) ++ ++ return ret == 0 ++ ++ ++def remove_gpg_key(key, root=None): ++ """Remove a key from the key storage ++ ++ .. versionadded:: TBD ++ ++ key ++ key identificatior ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' lowpkg.remove_gpg_key gpg-pubkey-3dbdc284-53674dd4 ++ ++ """ ++ cmd = ["rpm"] ++ if root: ++ cmd.extend(["--root", root]) ++ cmd.extend(["-e", key]) ++ return __salt__["cmd.retcode"](cmd) == 0 +diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py +index b362d30bf4..b2be251a40 100644 +--- a/salt/modules/yumpkg.py ++++ b/salt/modules/yumpkg.py +@@ -3535,3 +3535,91 @@ def services_need_restart(**kwargs): + services.add(service) + + return list(services) ++ ++ ++def get_repo_keys(info=False, root=None, **kwargs): ++ """Return the list of all the GPG keys stored in the RPM database ++ ++ .. versionadded:: TBD ++ ++ info ++ get the key information, returing a dictionary instead of a ++ list ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' pkg.get_repo_keys ++ salt '*' pkg.get_repo_keys info=True ++ ++ """ ++ return __salt__["lowpkg.list_gpg_keys"](info, root) ++ ++ ++def add_repo_key(path=None, text=None, root=None, saltenv="base", **kwargs): ++ """Import a new key into the key storage ++ ++ .. versionadded:: TBD ++ ++ path ++ the path of the key file to import ++ ++ text ++ the key data to import, in string form ++ ++ root ++ use root as top level directory (default: "/") ++ ++ saltenv ++ the environment the key file resides in ++ ++ CLI Examples: ++ ++ .. code-block:: bash ++ ++ salt '*' pkg.add_repo_key 'salt://apt/sources/test.key' ++ salt '*' pkg.add_repo_key text="'$KEY1'" ++ ++ """ ++ if not path and not text: ++ raise SaltInvocationError("Provide a key to add") ++ ++ if path and text: ++ raise SaltInvocationError("Add a key via path or key") ++ ++ if path: ++ cache_path = __salt__["cp.cache_file"](path, saltenv) ++ ++ if not cache_path: ++ log.error("Unable to get cached copy of file: %s", path) ++ return False ++ ++ with salt.utils.files.fopen(cache_path, "r") as f: ++ text = f.read() ++ ++ return __salt__["lowpkg.import_gpg_key"](text, root) ++ ++ ++def del_repo_key(keyid, root=None, **kwargs): ++ """Remove a key from the key storage ++ ++ .. versionadded:: TBD ++ ++ keyid ++ key identificatior ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Examples: ++ ++ .. code-block:: bash ++ ++ salt '*' pkg.del_repo_key keyid=gpg-pubkey-3dbdc284-53674dd4 ++ ++ """ ++ return __salt__["lowpkg.remove_gpg_key"](keyid, root) +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 2da470bea3..318c871b37 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -3261,3 +3261,91 @@ def services_need_restart(root=None, **kwargs): + services = zypper_output.split() + + return services ++ ++ ++def get_repo_keys(info=False, root=None, **kwargs): ++ """Return the list of all the GPG keys stored in the RPM database ++ ++ .. versionadded:: TBD ++ ++ info ++ get the key information, returing a dictionary instead of a ++ list ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' pkg.get_repo_keys ++ salt '*' pkg.get_repo_keys info=True ++ ++ """ ++ return __salt__["lowpkg.list_gpg_keys"](info, root) ++ ++ ++def add_repo_key(path=None, text=None, root=None, saltenv="base", **kwargs): ++ """Import a new key into the key storage ++ ++ .. versionadded:: TBD ++ ++ path ++ the path of the key file to import ++ ++ text ++ the key data to import, in string form ++ ++ root ++ use root as top level directory (default: "/") ++ ++ saltenv ++ the environment the key file resides in ++ ++ CLI Examples: ++ ++ .. code-block:: bash ++ ++ salt '*' pkg.add_repo_key 'salt://apt/sources/test.key' ++ salt '*' pkg.add_repo_key text="'$KEY1'" ++ ++ """ ++ if not path and not text: ++ raise SaltInvocationError("Provide a key to add") ++ ++ if path and text: ++ raise SaltInvocationError("Add a key via path or key") ++ ++ if path: ++ cache_path = __salt__["cp.cache_file"](path, saltenv) ++ ++ if not cache_path: ++ log.error("Unable to get cached copy of file: %s", path) ++ return False ++ ++ with salt.utils.files.fopen(cache_path, "r") as f: ++ text = f.read() ++ ++ return __salt__["lowpkg.import_gpg_key"](text, root) ++ ++ ++def del_repo_key(keyid, root=None, **kwargs): ++ """Remove a key from the key storage ++ ++ .. versionadded:: TBD ++ ++ keyid ++ key identificatior ++ ++ root ++ use root as top level directory (default: "/") ++ ++ CLI Examples: ++ ++ .. code-block:: bash ++ ++ salt '*' pkg.del_repo_key keyid=gpg-pubkey-3dbdc284-53674dd4 ++ ++ """ ++ return __salt__["lowpkg.remove_gpg_key"](keyid, root) +diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py +index 67a50c3ca0..c2d23f95bb 100644 +--- a/salt/states/pkgrepo.py ++++ b/salt/states/pkgrepo.py +@@ -118,6 +118,7 @@ Using ``aptkey: False`` with ``keyserver`` and ``keyid``: + """ + + ++import os + import sys + + import salt.utils.data +@@ -745,3 +746,209 @@ def absent(name, **kwargs): + ret["comment"] = "Failed to remove repo {}".format(name) + + return ret ++ ++ ++def _normalize_repo(repo): ++ """Normalize the get_repo information""" ++ # `pkg.get_repo()` specific virtual module implementation is ++ # parsing the information directly from the repository ++ # configuration file, and can be different from the ones that ++ # `pkg.mod_repo()` accepts ++ ++ # If the field is not present will be dropped ++ suse = { ++ # "alias": "repo", ++ "name": "humanname", ++ "priority": "priority", ++ "enabled": "enabled", ++ "autorefresh": "refresh", ++ "gpgcheck": "gpgcheck", ++ "keepackages": "cache", ++ "baseurl": "url", ++ } ++ translator = { ++ "Suse": suse, ++ } ++ table = translator.get(__grains__["os_family"], {}) ++ return {table[k]: v for k, v in repo.items() if k in table} ++ ++ ++def _normalize_key(key): ++ """Normalize the info_gpg_key information""" ++ ++ # If the field is not present will be dropped ++ rpm = { ++ "Description": "key", ++ } ++ translator = { ++ "Suse": rpm, ++ "RedHat": rpm, ++ } ++ table = translator.get(__grains__["os_family"], {}) ++ return {table[k]: v for k, v in key.items() if k in table} ++ ++ ++def _repos_keys_migrate_drop(root, keys, drop): ++ """Helper function to calculate repost and key migrations""" ++ ++ def _d2s(d): ++ """Serialize a dict and store in a set""" ++ return { ++ (k, tuple((_k, _v) for _k, _v in sorted(v.items()))) ++ for k, v in sorted(d.items()) ++ } ++ ++ src_repos = _d2s( ++ {k: _normalize_repo(v) for k, v in __salt__["pkg.list_repos"]().items()} ++ ) ++ # There is no guarantee that the target repository is even initialized ++ try: ++ tgt_repos = _d2s( ++ { ++ k: _normalize_repo(v) ++ for k, v in __salt__["pkg.list_repos"](root=root).items() ++ } ++ ) ++ except Exception: # pylint: disable=broad-except ++ tgt_repos = set() ++ ++ src_keys = set() ++ tgt_keys = set() ++ if keys: ++ src_keys = _d2s( ++ { ++ k: _normalize_key(v) ++ for k, v in __salt__["lowpkg.list_gpg_keys"](info=True).items() ++ } ++ ) ++ try: ++ tgt_keys = _d2s( ++ { ++ k: _normalize_key(v) ++ for k, v in __salt__["lowpkg.list_gpg_keys"]( ++ info=True, root=root ++ ).items() ++ } ++ ) ++ except Exception: # pylint: disable=broad-except ++ pass ++ ++ repos_to_migrate = src_repos - tgt_repos ++ repos_to_drop = tgt_repos - src_repos if drop else set() ++ ++ keys_to_migrate = src_keys - tgt_keys ++ keys_to_drop = tgt_keys - src_keys if drop else set() ++ ++ return (repos_to_migrate, repos_to_drop, keys_to_migrate, keys_to_drop) ++ ++ ++def _copy_repository_to(root): ++ repo = { ++ "Suse": ["/etc/zypp/repos.d"], ++ "RedHat": ["/etc/yum.conf", "/etc/yum.repos.d"], ++ } ++ for src in repo.get(__grains__["os_family"], []): ++ dst = os.path.join(root, os.path.relpath(src, os.path.sep)) ++ __salt__["file.copy"](src=src, dst=dst, recurse=True) ++ ++ ++def migrated(name, keys=True, drop=False, method=None, **kwargs): ++ """Migrate a repository from one directory to another, including the ++ GPG keys if requested ++ ++ .. versionadded:: TBD ++ ++ name ++ Directory were to migrate the repositories. For example, if we ++ are booting from a USB key and we mounted the rootfs in ++ "/mnt", the repositories will live in "/mnt/etc/yum.repos.d" ++ or in "/etc/zypp/repos.d", depending on the system. For both ++ cases the expected value for "name" would be "/mnt" ++ ++ keys ++ If is is True, will migrate all the keys ++ ++ drop ++ If True, the target repositories that do not exist in the ++ source will be dropped ++ ++ method ++ If None or "salt", it will use the Salt API to migrate the ++ repositories, if "copy", it will copy the repository files ++ directly ++ ++ """ ++ ret = {"name": name, "result": False, "changes": {}, "comment": ""} ++ ++ if __grains__["os_family"] not in ("Suse",): ++ ret["comment"] = "Migration not supported for this platform" ++ return ret ++ ++ if keys and "lowpkg.import_gpg_key" not in __salt__: ++ ret["comment"] = "Keys cannot be migrated for this platform" ++ return ret ++ ++ if method not in (None, "salt", "copy"): ++ ret["comment"] = "Migration method not supported" ++ return ret ++ ++ ( ++ repos_to_migrate, ++ repos_to_drop, ++ keys_to_migrate, ++ keys_to_drop, ++ ) = _repos_keys_migrate_drop(name, keys, drop) ++ ++ if not any((repos_to_migrate, repos_to_drop, keys_to_migrate, keys_to_drop)): ++ ret["result"] = True ++ ret["comment"] = "Repositories are already migrated" ++ return ret ++ ++ if __opts__["test"]: ++ ret["result"] = None ++ ret["comment"] = "There are keys or repositories to migrate or drop" ++ ret["changes"] = { ++ "repos to migrate": [repo for repo, _ in repos_to_migrate], ++ "repos to drop": [repo for repo, _ in repos_to_drop], ++ "keys to migrate": [key for key, _ in keys_to_migrate], ++ "keys to drop": [key for key, _ in keys_to_drop], ++ } ++ return ret ++ ++ for repo, repo_info in repos_to_migrate: ++ if method == "copy": ++ _copy_repository_to(name) ++ else: ++ __salt__["pkg.mod_repo"](repo, **dict(repo_info), root=name) ++ for repo, _ in repos_to_drop: ++ __salt__["pkg.del_repo"](repo, root=name) ++ ++ for _, key_info in keys_to_migrate: ++ __salt__["lowpkg.import_gpg_key"](dict(key_info)["key"], root=name) ++ for key, _ in keys_to_drop: ++ __salt__["lowpkg.remove_gpg_key"](key, root=name) ++ ++ ( ++ rem_repos_to_migrate, ++ rem_repos_to_drop, ++ rem_keys_to_migrate, ++ rem_keys_to_drop, ++ ) = _repos_keys_migrate_drop(name, keys, drop) ++ ++ if any( ++ (rem_repos_to_migrate, rem_repos_to_drop, rem_keys_to_migrate, rem_keys_to_drop) ++ ): ++ ret["result"] = False ++ ret["comment"] = "Migration of repositories failed" ++ return ret ++ ++ ret["result"] = True ++ ret["comment"] = "Repositories synchronized" ++ ret["changes"] = { ++ "repos migrated": [repo for repo, _ in repos_to_migrate], ++ "repos dropped": [repo for repo, _ in repos_to_drop], ++ "keys migrated": [key for key, _ in keys_to_migrate], ++ "keys dropped": [key for key, _ in keys_to_drop], ++ } ++ ++ return ret +diff --git a/tests/pytests/unit/modules/test_yumpkg.py b/tests/pytests/unit/modules/test_yumpkg.py +index 1354ee5d2d..45c62d793d 100644 +--- a/tests/pytests/unit/modules/test_yumpkg.py ++++ b/tests/pytests/unit/modules/test_yumpkg.py +@@ -9,7 +9,7 @@ import salt.modules.rpm_lowpkg as rpm + import salt.modules.yumpkg as yumpkg + import salt.utils.platform + from salt.exceptions import CommandExecutionError, SaltInvocationError +-from tests.support.mock import MagicMock, Mock, call, patch ++from tests.support.mock import MagicMock, Mock, call, mock_open, patch + + log = logging.getLogger(__name__) + +@@ -1908,6 +1908,48 @@ def test_get_repo_with_non_existent_repo(list_repos_var): + assert ret == expected, ret + + ++def test_get_repo_keys(): ++ salt_mock = {"lowpkg.list_gpg_keys": MagicMock(return_value=True)} ++ with patch.dict(yumpkg.__salt__, salt_mock): ++ assert yumpkg.get_repo_keys(info=True, root="/mnt") ++ salt_mock["lowpkg.list_gpg_keys"].assert_called_once_with(True, "/mnt") ++ ++ ++def test_add_repo_key_fail(): ++ with pytest.raises(SaltInvocationError): ++ yumpkg.add_repo_key() ++ ++ with pytest.raises(SaltInvocationError): ++ yumpkg.add_repo_key(path="path", text="text") ++ ++ ++def test_add_repo_key_path(): ++ salt_mock = { ++ "cp.cache_file": MagicMock(return_value="path"), ++ "lowpkg.import_gpg_key": MagicMock(return_value=True), ++ } ++ with patch("salt.utils.files.fopen", mock_open(read_data="text")), patch.dict( ++ yumpkg.__salt__, salt_mock ++ ): ++ assert yumpkg.add_repo_key(path="path", root="/mnt") ++ salt_mock["cp.cache_file"].assert_called_once_with("path", "base") ++ salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") ++ ++ ++def test_add_repo_key_text(): ++ salt_mock = {"lowpkg.import_gpg_key": MagicMock(return_value=True)} ++ with patch.dict(yumpkg.__salt__, salt_mock): ++ assert yumpkg.add_repo_key(text="text", root="/mnt") ++ salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") ++ ++ ++def test_del_repo_key(): ++ salt_mock = {"lowpkg.remove_gpg_key": MagicMock(return_value=True)} ++ with patch.dict(yumpkg.__salt__, salt_mock): ++ assert yumpkg.del_repo_key(keyid="keyid", root="/mnt") ++ salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") ++ ++ + def test_pkg_update_dnf(): + """ + Tests that the proper CLI options are added when obsoletes=False +diff --git a/tests/pytests/unit/modules/test_zypperpkg.py b/tests/pytests/unit/modules/test_zypperpkg.py +index 1e2d6ea443..91132b7277 100644 +--- a/tests/pytests/unit/modules/test_zypperpkg.py ++++ b/tests/pytests/unit/modules/test_zypperpkg.py +@@ -10,8 +10,8 @@ import pytest + + import salt.modules.pkg_resource as pkg_resource + import salt.modules.zypperpkg as zypper +-from salt.exceptions import CommandExecutionError +-from tests.support.mock import MagicMock, patch ++from salt.exceptions import CommandExecutionError, SaltInvocationError ++from tests.support.mock import MagicMock, mock_open, patch + + + @pytest.fixture +@@ -354,3 +354,44 @@ def test_dist_upgrade_failure(): + + assert exc.exception.info["changes"] == {} + assert exc.exception.info["result"]["stdout"] == zypper_output ++ ++def test_get_repo_keys(): ++ salt_mock = {"lowpkg.list_gpg_keys": MagicMock(return_value=True)} ++ with patch.dict(zypper.__salt__, salt_mock): ++ assert zypper.get_repo_keys(info=True, root="/mnt") ++ salt_mock["lowpkg.list_gpg_keys"].assert_called_once_with(True, "/mnt") ++ ++ ++def test_add_repo_key_fail(): ++ with pytest.raises(SaltInvocationError): ++ zypper.add_repo_key() ++ ++ with pytest.raises(SaltInvocationError): ++ zypper.add_repo_key(path="path", text="text") ++ ++ ++def test_add_repo_key_path(): ++ salt_mock = { ++ "cp.cache_file": MagicMock(return_value="path"), ++ "lowpkg.import_gpg_key": MagicMock(return_value=True), ++ } ++ with patch("salt.utils.files.fopen", mock_open(read_data="text")), patch.dict( ++ zypper.__salt__, salt_mock ++ ): ++ assert zypper.add_repo_key(path="path", root="/mnt") ++ salt_mock["cp.cache_file"].assert_called_once_with("path", "base") ++ salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") ++ ++ ++def test_add_repo_key_text(): ++ salt_mock = {"lowpkg.import_gpg_key": MagicMock(return_value=True)} ++ with patch.dict(zypper.__salt__, salt_mock): ++ assert zypper.add_repo_key(text="text", root="/mnt") ++ salt_mock["lowpkg.import_gpg_key"].assert_called_once_with("text", "/mnt") ++ ++ ++def test_del_repo_key(): ++ salt_mock = {"lowpkg.remove_gpg_key": MagicMock(return_value=True)} ++ with patch.dict(zypper.__salt__, salt_mock): ++ assert zypper.del_repo_key(keyid="keyid", root="/mnt") ++ salt_mock["lowpkg.remove_gpg_key"].assert_called_once_with("keyid", "/mnt") +diff --git a/tests/pytests/unit/states/test_pkgrepo.py b/tests/pytests/unit/states/test_pkgrepo.py +index c572583d19..5f540bd245 100644 +--- a/tests/pytests/unit/states/test_pkgrepo.py ++++ b/tests/pytests/unit/states/test_pkgrepo.py +@@ -72,3 +72,451 @@ def test_managed_insecure_key(): + ret["comment"] + == "Cannot have 'key_url' using http with 'allow_insecure_key' set to True" + ) ++ ++ ++def test__normalize_repo_suse(): ++ repo = { ++ "name": "repo name", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ } ++ grains = {"os_family": "Suse"} ++ with patch.dict(pkgrepo.__grains__, grains): ++ assert pkgrepo._normalize_repo(repo) == { ++ "humanname": "repo name", ++ "refresh": True, ++ "priority": 0, ++ } ++ ++ ++def test__normalize_key_rpm(): ++ key = {"Description": "key", "Date": "Date", "Other": "Other"} ++ for os_family in ("Suse", "RedHat"): ++ grains = {"os_family": os_family} ++ with patch.dict(pkgrepo.__grains__, grains): ++ assert pkgrepo._normalize_key(key) == {"key": "key"} ++ ++ ++def test__repos_keys_migrate_drop_migrate_to_empty(): ++ src_repos = { ++ "repo-1": { ++ "name": "repo name 1", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ }, ++ "repo-2": { ++ "name": "repo name 2", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": False, ++ }, ++ } ++ tgt_repos = {} ++ ++ src_keys = { ++ "key1": {"Description": "key1", "Other": "Other1"}, ++ "key2": {"Description": "key2", "Other": "Other2"}, ++ } ++ tgt_keys = {} ++ ++ grains = {"os_family": "Suse"} ++ salt_mock = { ++ "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), ++ "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ assert pkgrepo._repos_keys_migrate_drop("/mnt", False, False) == ( ++ { ++ ( ++ "repo-1", ++ ( ++ ("humanname", "repo name 1"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ ( ++ "repo-2", ++ ( ++ ("humanname", "repo name 2"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ set(), ++ set(), ++ set(), ++ ) ++ ++ ++def test__repos_keys_migrate_drop_migrate_to_empty_keys(): ++ src_repos = { ++ "repo-1": { ++ "name": "repo name 1", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ }, ++ "repo-2": { ++ "name": "repo name 2", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": False, ++ }, ++ } ++ tgt_repos = {} ++ ++ src_keys = { ++ "key1": {"Description": "key1", "Other": "Other1"}, ++ "key2": {"Description": "key2", "Other": "Other2"}, ++ } ++ tgt_keys = {} ++ ++ grains = {"os_family": "Suse"} ++ salt_mock = { ++ "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), ++ "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ assert pkgrepo._repos_keys_migrate_drop("/mnt", True, False) == ( ++ { ++ ( ++ "repo-1", ++ ( ++ ("humanname", "repo name 1"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ ( ++ "repo-2", ++ ( ++ ("humanname", "repo name 2"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ set(), ++ {("key1", (("key", "key1"),)), ("key2", (("key", "key2"),))}, ++ set(), ++ ) ++ ++ ++def test__repos_keys_migrate_drop_migrate_to_populated_no_drop(): ++ src_repos = { ++ "repo-1": { ++ "name": "repo name 1", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ }, ++ "repo-2": { ++ "name": "repo name 2", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": False, ++ }, ++ } ++ tgt_repos = { ++ "repo-1": { ++ "name": "repo name 1", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ }, ++ "repo-3": { ++ "name": "repo name 3", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": False, ++ }, ++ } ++ ++ src_keys = { ++ "key1": {"Description": "key1", "Other": "Other1"}, ++ "key2": {"Description": "key2", "Other": "Other2"}, ++ } ++ tgt_keys = { ++ "key1": {"Description": "key1", "Other": "Other1"}, ++ "key3": {"Description": "key3", "Other": "Other2"}, ++ } ++ ++ grains = {"os_family": "Suse"} ++ salt_mock = { ++ "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), ++ "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ assert pkgrepo._repos_keys_migrate_drop("/mnt", True, False) == ( ++ { ++ ( ++ "repo-2", ++ ( ++ ("humanname", "repo name 2"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ set(), ++ {("key2", (("key", "key2"),))}, ++ set(), ++ ) ++ ++ ++def test__repos_keys_migrate_drop_migrate_to_populated_drop(): ++ src_repos = { ++ "repo-1": { ++ "name": "repo name 1", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ }, ++ "repo-2": { ++ "name": "repo name 2", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": False, ++ }, ++ } ++ tgt_repos = { ++ "repo-1": { ++ "name": "repo name 1", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": True, ++ }, ++ "repo-3": { ++ "name": "repo name 3", ++ "autorefresh": True, ++ "priority": 0, ++ "pkg_gpgcheck": False, ++ }, ++ } ++ ++ src_keys = { ++ "key1": {"Description": "key1", "Other": "Other1"}, ++ "key2": {"Description": "key2", "Other": "Other2"}, ++ } ++ tgt_keys = { ++ "key1": {"Description": "key1", "Other": "Other1"}, ++ "key3": {"Description": "key3", "Other": "Other2"}, ++ } ++ ++ grains = {"os_family": "Suse"} ++ salt_mock = { ++ "pkg.list_repos": MagicMock(side_effect=[src_repos, tgt_repos]), ++ "lowpkg.list_gpg_keys": MagicMock(side_effect=[src_keys, tgt_keys]), ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ assert pkgrepo._repos_keys_migrate_drop("/mnt", True, True) == ( ++ { ++ ( ++ "repo-2", ++ ( ++ ("humanname", "repo name 2"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ { ++ ( ++ "repo-3", ++ ( ++ ("humanname", "repo name 3"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ {("key2", (("key", "key2"),))}, ++ {("key3", (("key", "key3"),))}, ++ ) ++ ++ ++@pytest.mark.skip_on_windows(reason="Not a Windows test") ++def test__copy_repository_to_suse(): ++ grains = {"os_family": "Suse"} ++ salt_mock = {"file.copy": MagicMock()} ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ pkgrepo._copy_repository_to("/mnt") ++ salt_mock["file.copy"].assert_called_with( ++ src="/etc/zypp/repos.d", dst="/mnt/etc/zypp/repos.d", recurse=True ++ ) ++ ++ ++def test_migrated_non_supported_platform(): ++ grains = {"os_family": "Debian"} ++ with patch.dict(pkgrepo.__grains__, grains): ++ assert pkgrepo.migrated("/mnt") == { ++ "name": "/mnt", ++ "result": False, ++ "changes": {}, ++ "comment": "Migration not supported for this platform", ++ } ++ ++ ++def test_migrated_missing_keys_api(): ++ grains = {"os_family": "Suse"} ++ with patch.dict(pkgrepo.__grains__, grains): ++ assert pkgrepo.migrated("/mnt") == { ++ "name": "/mnt", ++ "result": False, ++ "changes": {}, ++ "comment": "Keys cannot be migrated for this platform", ++ } ++ ++ ++def test_migrated_wrong_method(): ++ grains = {"os_family": "Suse"} ++ salt_mock = { ++ "lowpkg.import_gpg_key": True, ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ assert pkgrepo.migrated("/mnt", method_="magic") == { ++ "name": "/mnt", ++ "result": False, ++ "changes": {}, ++ "comment": "Migration method not supported", ++ } ++ ++ ++@patch( ++ "salt.states.pkgrepo._repos_keys_migrate_drop", ++ MagicMock(return_value=(set(), set(), set(), set())), ++) ++def test_migrated_empty(): ++ grains = {"os_family": "Suse"} ++ salt_mock = { ++ "lowpkg.import_gpg_key": True, ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__salt__, salt_mock ++ ): ++ assert pkgrepo.migrated("/mnt") == { ++ "name": "/mnt", ++ "result": True, ++ "changes": {}, ++ "comment": "Repositories are already migrated", ++ } ++ ++ ++def test_migrated(): ++ _repos_keys_migrate_drop = MagicMock() ++ _repos_keys_migrate_drop.side_effect = [ ++ ( ++ { ++ ( ++ "repo-1", ++ ( ++ ("humanname", "repo name 1"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ { ++ ( ++ "repo-2", ++ ( ++ ("humanname", "repo name 2"), ++ ("priority", 0), ++ ("refresh", True), ++ ), ++ ), ++ }, ++ {("key1", (("key", "key1"),))}, ++ {("key2", (("key", "key2"),))}, ++ ), ++ (set(), set(), set(), set()), ++ ] ++ ++ grains = {"os_family": "Suse"} ++ opts = {"test": False} ++ salt_mock = { ++ "pkg.mod_repo": MagicMock(), ++ "pkg.del_repo": MagicMock(), ++ "lowpkg.import_gpg_key": MagicMock(), ++ "lowpkg.remove_gpg_key": MagicMock(), ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__opts__, opts ++ ), patch.dict(pkgrepo.__salt__, salt_mock), patch( ++ "salt.states.pkgrepo._repos_keys_migrate_drop", _repos_keys_migrate_drop ++ ): ++ assert pkgrepo.migrated("/mnt", True, True) == { ++ "name": "/mnt", ++ "result": True, ++ "changes": { ++ "repos migrated": ["repo-1"], ++ "repos dropped": ["repo-2"], ++ "keys migrated": ["key1"], ++ "keys dropped": ["key2"], ++ }, ++ "comment": "Repositories synchronized", ++ } ++ salt_mock["pkg.mod_repo"].assert_called_with( ++ "repo-1", humanname="repo name 1", priority=0, refresh=True, root="/mnt" ++ ) ++ salt_mock["pkg.del_repo"].assert_called_with("repo-2", root="/mnt") ++ salt_mock["lowpkg.import_gpg_key"].assert_called_with("key1", root="/mnt") ++ salt_mock["lowpkg.remove_gpg_key"].assert_called_with("key2", root="/mnt") ++ ++ ++def test_migrated_test(): ++ _repos_keys_migrate_drop = MagicMock() ++ _repos_keys_migrate_drop.return_value = ( ++ { ++ ( ++ "repo-1", ++ (("humanname", "repo name 1"), ("priority", 0), ("refresh", True)), ++ ), ++ }, ++ { ++ ( ++ "repo-2", ++ (("humanname", "repo name 2"), ("priority", 0), ("refresh", True)), ++ ), ++ }, ++ {("key1", (("key", "key1"),))}, ++ {("key2", (("key", "key2"),))}, ++ ) ++ ++ grains = {"os_family": "Suse"} ++ opts = {"test": True} ++ salt_mock = { ++ "lowpkg.import_gpg_key": True, ++ } ++ with patch.dict(pkgrepo.__grains__, grains), patch.dict( ++ pkgrepo.__opts__, opts ++ ), patch.dict(pkgrepo.__salt__, salt_mock), patch( ++ "salt.states.pkgrepo._repos_keys_migrate_drop", _repos_keys_migrate_drop ++ ): ++ assert pkgrepo.migrated("/mnt", True, True) == { ++ "name": "/mnt", ++ "result": None, ++ "changes": { ++ "repos to migrate": ["repo-1"], ++ "repos to drop": ["repo-2"], ++ "keys to migrate": ["key1"], ++ "keys to drop": ["key2"], ++ }, ++ "comment": "There are keys or repositories to migrate or drop", ++ } +-- +2.39.2 + + diff --git a/add-publish_batch-to-clearfuncs-exposed-methods.patch b/add-publish_batch-to-clearfuncs-exposed-methods.patch new file mode 100644 index 0000000..2be359b --- /dev/null +++ b/add-publish_batch-to-clearfuncs-exposed-methods.patch @@ -0,0 +1,26 @@ +From 3ef2071daf7a415f2c43e1339affe2b7cad93b3e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Thu, 28 May 2020 09:37:08 +0100 +Subject: [PATCH] Add publish_batch to ClearFuncs exposed methods + +--- + salt/master.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/salt/master.py b/salt/master.py +index 2a526b4f21..a0552fa232 100644 +--- a/salt/master.py ++++ b/salt/master.py +@@ -1960,6 +1960,7 @@ class ClearFuncs(TransportMethods): + expose_methods = ( + "ping", + "publish", ++ "publish_batch", + "get_token", + "mk_token", + "wheel", +-- +2.39.2 + + diff --git a/add-salt-ssh-support-with-venv-salt-minion-3004-493.patch b/add-salt-ssh-support-with-venv-salt-minion-3004-493.patch new file mode 100644 index 0000000..1eb2092 --- /dev/null +++ b/add-salt-ssh-support-with-venv-salt-minion-3004-493.patch @@ -0,0 +1,795 @@ +From 3fd6c0c6793632c819fb5f8fb3b3538463eaaccc Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Thu, 24 Feb 2022 16:52:24 +0300 +Subject: [PATCH] Add salt-ssh support with venv-salt-minion - 3004 + (#493) + +* Add salt-ssh support with venv-salt-minion + +* Add some comments and drop the commented line + +* Fix return in check_venv_hash_file + +* Convert all script parameters to strings + +* Reduce the size of minion response + +Minion response contains SSH_PY_CODE wrapped to base64. +This fix reduces the size of the response in DEBUG logging + +* Make VENV_HASH_FILE global + +* Pass the context to roster modules + +* Avoid race condition on loading roster modules + +* Prevent simultaneous to salt-ssh minion + +* Make ssh session grace time configurable + +* Prevent possible segfault by GC + +* Revert "Avoid race condition on loading roster modules" + +This reverts commit 8ff822a162cc494d3528184aef983ad20e09f4e2. + +* Prevent deadlocks with importlib on using LazyLoader + +* Make logging on salt-ssh errors more informative + +* Add comments about using salt.loader.LOAD_LOCK + +* Fix test_loader test + +* Prevent deadlocks on using logging + +* Use collections.deque instead of list for salt-ssh + +Suggested by @agraul + +* Get proper exitstatus from salt.utils.vt.Terminal + +to prevent empty event returns due to improperly detecting +the child process as failed + +* Do not run pre flight script for raw_shell +--- + salt/_logging/impl.py | 55 +++++++----- + salt/client/ssh/__init__.py | 157 ++++++++++++++++++++++++++++----- + salt/client/ssh/client.py | 7 +- + salt/client/ssh/shell.py | 8 ++ + salt/client/ssh/ssh_py_shim.py | 108 +++++++++++++---------- + salt/loader/__init__.py | 31 ++++++- + salt/netapi/__init__.py | 3 +- + salt/roster/__init__.py | 6 +- + tests/unit/test_loader.py | 2 +- + 9 files changed, 278 insertions(+), 99 deletions(-) + +diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py +index cc18f49a9e..e050f43caf 100644 +--- a/salt/_logging/impl.py ++++ b/salt/_logging/impl.py +@@ -14,6 +14,7 @@ import re + import socket + import sys + import traceback ++import threading + import types + import urllib.parse + +@@ -104,6 +105,10 @@ DFLT_LOG_DATEFMT_LOGFILE = "%Y-%m-%d %H:%M:%S" + DFLT_LOG_FMT_CONSOLE = "[%(levelname)-8s] %(message)s" + DFLT_LOG_FMT_LOGFILE = "%(asctime)s,%(msecs)03d [%(name)-17s:%(lineno)-4d][%(levelname)-8s][%(process)d] %(message)s" + ++# LOG_LOCK is used to prevent deadlocks on using logging ++# in combination with multiprocessing with salt-api ++LOG_LOCK = threading.Lock() ++ + + class SaltLogRecord(logging.LogRecord): + def __init__(self, *args, **kwargs): +@@ -270,27 +275,35 @@ class SaltLoggingClass(LOGGING_LOGGER_CLASS, metaclass=LoggingMixinMeta): + else: + extra["exc_info_on_loglevel"] = exc_info_on_loglevel + +- if sys.version_info < (3, 8): +- LOGGING_LOGGER_CLASS._log( +- self, +- level, +- msg, +- args, +- exc_info=exc_info, +- extra=extra, +- stack_info=stack_info, +- ) +- else: +- LOGGING_LOGGER_CLASS._log( +- self, +- level, +- msg, +- args, +- exc_info=exc_info, +- extra=extra, +- stack_info=stack_info, +- stacklevel=stacklevel, +- ) ++ try: ++ LOG_LOCK.acquire() ++ if sys.version_info < (3,): ++ LOGGING_LOGGER_CLASS._log( ++ self, level, msg, args, exc_info=exc_info, extra=extra ++ ) ++ elif sys.version_info < (3, 8): ++ LOGGING_LOGGER_CLASS._log( ++ self, ++ level, ++ msg, ++ args, ++ exc_info=exc_info, ++ extra=extra, ++ stack_info=stack_info, ++ ) ++ else: ++ LOGGING_LOGGER_CLASS._log( ++ self, ++ level, ++ msg, ++ args, ++ exc_info=exc_info, ++ extra=extra, ++ stack_info=stack_info, ++ stacklevel=stacklevel, ++ ) ++ finally: ++ LOG_LOCK.release() + + def makeRecord( + self, +diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py +index 19089ce8ad..e6837df4e5 100644 +--- a/salt/client/ssh/__init__.py ++++ b/salt/client/ssh/__init__.py +@@ -6,11 +6,13 @@ import base64 + import binascii + import copy + import datetime ++import gc + import getpass + import hashlib + import logging + import multiprocessing + import os ++import psutil + import queue + import re + import shlex +@@ -20,6 +22,7 @@ import tarfile + import tempfile + import time + import uuid ++from collections import deque + + import salt.client.ssh.shell + import salt.client.ssh.wrapper +@@ -47,6 +50,7 @@ import salt.utils.url + import salt.utils.verify + from salt._logging import LOG_LEVELS + from salt._logging.mixins import MultiprocessingStateMixin ++from salt._logging.impl import LOG_LOCK + from salt.template import compile_template + from salt.utils.process import Process + from salt.utils.zeromq import zmq +@@ -146,15 +150,26 @@ if [ "$SUDO" ] && [ "$SUDO_USER" ] + then SUDO="$SUDO -u $SUDO_USER" + fi + EX_PYTHON_INVALID={EX_THIN_PYTHON_INVALID} +-PYTHON_CMDS="python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python" ++set +x ++SSH_PY_CODE='import base64; ++ exec(base64.b64decode("""{{SSH_PY_CODE}}""").decode("utf-8"))' ++if [ -n "$DEBUG" ] ++ then set -x ++fi ++PYTHON_CMDS="/var/tmp/venv-salt-minion/bin/python python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python" + for py_cmd in $PYTHON_CMDS + do + if command -v "$py_cmd" >/dev/null 2>&1 && "$py_cmd" -c "import sys; sys.exit(not (sys.version_info >= (2, 6)));" + then + py_cmd_path=`"$py_cmd" -c 'from __future__ import print_function;import sys; print(sys.executable);'` + cmdpath=`command -v $py_cmd 2>/dev/null || which $py_cmd 2>/dev/null` ++ cmdpath=`readlink -f $cmdpath` + if file $cmdpath | grep "shell script" > /dev/null + then ++ if echo $cmdpath | grep venv-salt-minion > /dev/null ++ then ++ exec $SUDO "$cmdpath" -c "$SSH_PY_CODE" ++ fi + ex_vars="'PATH', 'LD_LIBRARY_PATH', 'MANPATH', \ + 'XDG_DATA_DIRS', 'PKG_CONFIG_PATH'" + export `$py_cmd -c \ +@@ -166,13 +181,9 @@ do + exec $SUDO PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH \ + MANPATH=$MANPATH XDG_DATA_DIRS=$XDG_DATA_DIRS \ + PKG_CONFIG_PATH=$PKG_CONFIG_PATH \ +- "$py_cmd_path" -c \ +- 'import base64; +- exec(base64.b64decode("""{{SSH_PY_CODE}}""").decode("utf-8"))' ++ "$py_cmd_path" -c "$SSH_PY_CODE" + else +- exec $SUDO "$py_cmd_path" -c \ +- 'import base64; +- exec(base64.b64decode("""{{SSH_PY_CODE}}""").decode("utf-8"))' ++ exec $SUDO "$py_cmd_path" -c "$SSH_PY_CODE" + fi + exit 0 + else +@@ -189,6 +200,9 @@ EOF'''.format( + ] + ) + ++# The file on a salt-ssh minion used to identify if Salt Bundle was deployed ++VENV_HASH_FILE = "/var/tmp/venv-salt-minion/venv-hash.txt" ++ + if not salt.utils.platform.is_windows() and not salt.utils.platform.is_junos(): + shim_file = os.path.join(os.path.dirname(__file__), "ssh_py_shim.py") + if not os.path.exists(shim_file): +@@ -209,7 +223,7 @@ class SSH(MultiprocessingStateMixin): + + ROSTER_UPDATE_FLAG = "#__needs_update" + +- def __init__(self, opts): ++ def __init__(self, opts, context=None): + self.__parsed_rosters = {SSH.ROSTER_UPDATE_FLAG: True} + pull_sock = os.path.join(opts["sock_dir"], "master_event_pull.ipc") + if os.path.exists(pull_sock) and zmq: +@@ -236,7 +250,9 @@ class SSH(MultiprocessingStateMixin): + else "glob" + ) + self._expand_target() +- self.roster = salt.roster.Roster(self.opts, self.opts.get("roster", "flat")) ++ self.roster = salt.roster.Roster( ++ self.opts, self.opts.get("roster", "flat"), context=context ++ ) + self.targets = self.roster.targets(self.opts["tgt"], self.tgt_type) + if not self.targets: + self._update_targets() +@@ -316,6 +332,13 @@ class SSH(MultiprocessingStateMixin): + extended_cfg=self.opts.get("ssh_ext_alternatives"), + ) + self.mods = mod_data(self.fsclient) ++ self.cache = salt.cache.Cache(self.opts) ++ self.master_id = self.opts["id"] ++ self.max_pid_wait = int(self.opts.get("ssh_max_pid_wait", 600)) ++ self.session_flock_file = os.path.join( ++ self.opts["cachedir"], "salt-ssh.session.lock" ++ ) ++ self.ssh_session_grace_time = int(self.opts.get("ssh_session_grace_time", 3)) + + # __setstate__ and __getstate__ are only used on spawning platforms. + def __setstate__(self, state): +@@ -546,6 +569,8 @@ class SSH(MultiprocessingStateMixin): + """ + Run the routine in a "Thread", put a dict on the queue + """ ++ LOG_LOCK.release() ++ salt.loader.LOAD_LOCK.release() + opts = copy.deepcopy(opts) + single = Single( + opts, +@@ -585,7 +610,7 @@ class SSH(MultiprocessingStateMixin): + """ + que = multiprocessing.Queue() + running = {} +- target_iter = self.targets.__iter__() ++ targets_queue = deque(self.targets.keys()) + returned = set() + rets = set() + init = False +@@ -594,11 +619,43 @@ class SSH(MultiprocessingStateMixin): + log.error("No matching targets found in roster.") + break + if len(running) < self.opts.get("ssh_max_procs", 25) and not init: +- try: +- host = next(target_iter) +- except StopIteration: ++ if targets_queue: ++ host = targets_queue.popleft() ++ else: + init = True + continue ++ with salt.utils.files.flopen(self.session_flock_file, "w"): ++ cached_session = self.cache.fetch("salt-ssh/session", host) ++ if cached_session is not None and "ts" in cached_session: ++ prev_session_running = time.time() - cached_session["ts"] ++ if ( ++ "pid" in cached_session ++ and cached_session.get("master_id", self.master_id) ++ == self.master_id ++ ): ++ pid_running = ( ++ False ++ if cached_session["pid"] == 0 ++ else psutil.pid_exists(cached_session["pid"]) ++ ) ++ if ( ++ pid_running and prev_session_running < self.max_pid_wait ++ ) or ( ++ not pid_running ++ and prev_session_running < self.ssh_session_grace_time ++ ): ++ targets_queue.append(host) ++ time.sleep(0.3) ++ continue ++ self.cache.store( ++ "salt-ssh/session", ++ host, ++ { ++ "pid": 0, ++ "master_id": self.master_id, ++ "ts": time.time(), ++ }, ++ ) + for default in self.defaults: + if default not in self.targets[host]: + self.targets[host][default] = self.defaults[default] +@@ -630,8 +687,38 @@ class SSH(MultiprocessingStateMixin): + mine, + ) + routine = Process(target=self.handle_routine, args=args) +- routine.start() ++ # Explicitly call garbage collector to prevent possible segfault ++ # in salt-api child process. (bsc#1188607) ++ gc.collect() ++ try: ++ # salt.loader.LOAD_LOCK is used to prevent deadlock ++ # with importlib in combination with using multiprocessing (bsc#1182851) ++ # If the salt-api child process is creating while LazyLoader instance ++ # is loading module, new child process gets the lock for this module acquired. ++ # Touching this module with importlib inside child process leads to deadlock. ++ # ++ # salt.loader.LOAD_LOCK is used to prevent salt-api child process creation ++ # while creating new instance of LazyLoader ++ # salt.loader.LOAD_LOCK must be released explicitly in self.handle_routine ++ salt.loader.LOAD_LOCK.acquire() ++ # The same solution applied to fix logging deadlock ++ # LOG_LOCK must be released explicitly in self.handle_routine ++ LOG_LOCK.acquire() ++ routine.start() ++ finally: ++ LOG_LOCK.release() ++ salt.loader.LOAD_LOCK.release() + running[host] = {"thread": routine} ++ with salt.utils.files.flopen(self.session_flock_file, "w"): ++ self.cache.store( ++ "salt-ssh/session", ++ host, ++ { ++ "pid": routine.pid, ++ "master_id": self.master_id, ++ "ts": time.time(), ++ }, ++ ) + continue + ret = {} + try: +@@ -662,12 +749,27 @@ class SSH(MultiprocessingStateMixin): + ) + ret = {"id": host, "ret": error} + log.error(error) ++ log.error( ++ "PID %s did not return any data for host '%s'", ++ running[host]["thread"].pid, ++ host, ++ ) + yield {ret["id"]: ret["ret"]} + running[host]["thread"].join() + rets.add(host) + for host in rets: + if host in running: + running.pop(host) ++ with salt.utils.files.flopen(self.session_flock_file, "w"): ++ self.cache.store( ++ "salt-ssh/session", ++ host, ++ { ++ "pid": 0, ++ "master_id": self.master_id, ++ "ts": time.time(), ++ }, ++ ) + if len(rets) >= len(self.targets): + break + # Sleep when limit or all threads started +@@ -1036,14 +1138,24 @@ class Single: + return False + return True + ++ def check_venv_hash_file(self): ++ """ ++ check if the venv exists on the remote machine ++ """ ++ stdout, stderr, retcode = self.shell.exec_cmd( ++ "test -f {}".format(VENV_HASH_FILE) ++ ) ++ return retcode == 0 ++ + def deploy(self): + """ + Deploy salt-thin + """ +- self.shell.send( +- self.thin, +- os.path.join(self.thin_dir, "salt-thin.tgz"), +- ) ++ if not self.check_venv_hash_file(): ++ self.shell.send( ++ self.thin, ++ os.path.join(self.thin_dir, "salt-thin.tgz"), ++ ) + self.deploy_ext() + return True + +@@ -1071,8 +1183,9 @@ class Single: + Returns tuple of (stdout, stderr, retcode) + """ + stdout = stderr = retcode = None ++ raw_shell = self.opts.get("raw_shell", False) + +- if self.ssh_pre_flight: ++ if self.ssh_pre_flight and not raw_shell: + if not self.opts.get("ssh_run_pre_flight", False) and self.check_thin_dir(): + log.info( + "%s thin dir already exists. Not running ssh_pre_flight script", +@@ -1086,14 +1199,16 @@ class Single: + stdout, stderr, retcode = self.run_ssh_pre_flight() + if retcode != 0: + log.error( +- "Error running ssh_pre_flight script %s", self.ssh_pre_file ++ "Error running ssh_pre_flight script %s for host '%s'", ++ self.ssh_pre_file, ++ self.target["host"], + ) + return stdout, stderr, retcode + log.info( + "Successfully ran the ssh_pre_flight script: %s", self.ssh_pre_file + ) + +- if self.opts.get("raw_shell", False): ++ if raw_shell: + cmd_str = " ".join([self._escape_arg(arg) for arg in self.argv]) + stdout, stderr, retcode = self.shell.exec_cmd(cmd_str) + +diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py +index be9247cb15..0b67598fc6 100644 +--- a/salt/client/ssh/client.py ++++ b/salt/client/ssh/client.py +@@ -108,7 +108,7 @@ class SSHClient: + return sane_kwargs + + def _prep_ssh( +- self, tgt, fun, arg=(), timeout=None, tgt_type="glob", kwarg=None, **kwargs ++ self, tgt, fun, arg=(), timeout=None, tgt_type="glob", kwarg=None, context=None, **kwargs + ): + """ + Prepare the arguments +@@ -123,7 +123,7 @@ class SSHClient: + opts["selected_target_option"] = tgt_type + opts["tgt"] = tgt + opts["arg"] = arg +- return salt.client.ssh.SSH(opts) ++ return salt.client.ssh.SSH(opts, context=context) + + def cmd_iter( + self, +@@ -160,7 +160,7 @@ class SSHClient: + final.update(ret) + return final + +- def cmd_sync(self, low): ++ def cmd_sync(self, low, context=None): + """ + Execute a salt-ssh call synchronously. + +@@ -193,6 +193,7 @@ class SSHClient: + low.get("timeout"), + low.get("tgt_type"), + low.get("kwarg"), ++ context=context, + **kwargs + ) + +diff --git a/salt/client/ssh/shell.py b/salt/client/ssh/shell.py +index cfa82d13c2..bc1ad034df 100644 +--- a/salt/client/ssh/shell.py ++++ b/salt/client/ssh/shell.py +@@ -464,6 +464,14 @@ class Shell: + if stdout: + old_stdout = stdout + time.sleep(0.01) ++ if term.exitstatus is None: ++ try: ++ term.wait() ++ except: # pylint: disable=broad-except ++ # It's safe to put the broad exception handling here ++ # as we just need to ensure the child process in term finished ++ # to get proper term.exitstatus instead of None ++ pass + return ret_stdout, ret_stderr, term.exitstatus + finally: + term.close(terminate=True, kill=True) +diff --git a/salt/client/ssh/ssh_py_shim.py b/salt/client/ssh/ssh_py_shim.py +index b77749f495..293ea1b7fa 100644 +--- a/salt/client/ssh/ssh_py_shim.py ++++ b/salt/client/ssh/ssh_py_shim.py +@@ -279,56 +279,72 @@ def main(argv): # pylint: disable=W0613 + """ + Main program body + """ +- thin_path = os.path.join(OPTIONS.saltdir, THIN_ARCHIVE) +- if os.path.isfile(thin_path): +- if OPTIONS.checksum != get_hash(thin_path, OPTIONS.hashfunc): +- need_deployment() +- unpack_thin(thin_path) +- # Salt thin now is available to use +- else: +- if not sys.platform.startswith("win"): +- scpstat = subprocess.Popen(["/bin/sh", "-c", "command -v scp"]).wait() +- if scpstat != 0: +- sys.exit(EX_SCP_NOT_FOUND) +- +- if os.path.exists(OPTIONS.saltdir) and not os.path.isdir(OPTIONS.saltdir): +- sys.stderr.write( +- 'ERROR: salt path "{0}" exists but is not a directory\n'.format( +- OPTIONS.saltdir ++ ++ virt_env = os.getenv("VIRTUAL_ENV", None) ++ # VIRTUAL_ENV environment variable is defined by venv-salt-minion wrapper ++ # it's used to check if the shim is running under this wrapper ++ venv_salt_call = None ++ if virt_env and "venv-salt-minion" in virt_env: ++ venv_salt_call = os.path.join(virt_env, "bin", "salt-call") ++ if not os.path.exists(venv_salt_call): ++ venv_salt_call = None ++ elif not os.path.exists(OPTIONS.saltdir): ++ os.makedirs(OPTIONS.saltdir) ++ cache_dir = os.path.join(OPTIONS.saltdir, "running_data", "var", "cache") ++ os.makedirs(os.path.join(cache_dir, "salt")) ++ os.symlink("salt", os.path.relpath(os.path.join(cache_dir, "venv-salt-minion"))) ++ ++ if venv_salt_call is None: ++ # Use Salt thin only if Salt Bundle (venv-salt-minion) is not available ++ thin_path = os.path.join(OPTIONS.saltdir, THIN_ARCHIVE) ++ if os.path.isfile(thin_path): ++ if OPTIONS.checksum != get_hash(thin_path, OPTIONS.hashfunc): ++ need_deployment() ++ unpack_thin(thin_path) ++ # Salt thin now is available to use ++ else: ++ if not sys.platform.startswith("win"): ++ scpstat = subprocess.Popen(["/bin/sh", "-c", "command -v scp"]).wait() ++ if scpstat != 0: ++ sys.exit(EX_SCP_NOT_FOUND) ++ ++ if os.path.exists(OPTIONS.saltdir) and not os.path.isdir(OPTIONS.saltdir): ++ sys.stderr.write( ++ 'ERROR: salt path "{0}" exists but is' ++ " not a directory\n".format(OPTIONS.saltdir) + ) +- ) +- sys.exit(EX_CANTCREAT) ++ sys.exit(EX_CANTCREAT) + +- if not os.path.exists(OPTIONS.saltdir): +- need_deployment() ++ if not os.path.exists(OPTIONS.saltdir): ++ need_deployment() + +- code_checksum_path = os.path.normpath( +- os.path.join(OPTIONS.saltdir, "code-checksum") +- ) +- if not os.path.exists(code_checksum_path) or not os.path.isfile( +- code_checksum_path +- ): +- sys.stderr.write( +- "WARNING: Unable to locate current code checksum: {0}.\n".format( +- code_checksum_path +- ) ++ code_checksum_path = os.path.normpath( ++ os.path.join(OPTIONS.saltdir, "code-checksum") + ) +- need_deployment() +- with open(code_checksum_path, "r") as vpo: +- cur_code_cs = vpo.readline().strip() +- if cur_code_cs != OPTIONS.code_checksum: +- sys.stderr.write( +- "WARNING: current code checksum {0} is different to {1}.\n".format( +- cur_code_cs, OPTIONS.code_checksum ++ if not os.path.exists(code_checksum_path) or not os.path.isfile( ++ code_checksum_path ++ ): ++ sys.stderr.write( ++ "WARNING: Unable to locate current code checksum: {0}.\n".format( ++ code_checksum_path ++ ) + ) +- ) +- need_deployment() +- # Salt thin exists and is up-to-date - fall through and use it ++ need_deployment() ++ with open(code_checksum_path, "r") as vpo: ++ cur_code_cs = vpo.readline().strip() ++ if cur_code_cs != OPTIONS.code_checksum: ++ sys.stderr.write( ++ "WARNING: current code checksum {0} is different to {1}.\n".format( ++ cur_code_cs, OPTIONS.code_checksum ++ ) ++ ) ++ need_deployment() ++ # Salt thin exists and is up-to-date - fall through and use it + +- salt_call_path = os.path.join(OPTIONS.saltdir, "salt-call") +- if not os.path.isfile(salt_call_path): +- sys.stderr.write('ERROR: thin is missing "{0}"\n'.format(salt_call_path)) +- need_deployment() ++ salt_call_path = os.path.join(OPTIONS.saltdir, "salt-call") ++ if not os.path.isfile(salt_call_path): ++ sys.stderr.write('ERROR: thin is missing "{0}"\n'.format(salt_call_path)) ++ need_deployment() + + with open(os.path.join(OPTIONS.saltdir, "minion"), "w") as config: + config.write(OPTIONS.config + "\n") +@@ -351,8 +367,8 @@ def main(argv): # pylint: disable=W0613 + argv_prepared = ARGS + + salt_argv = [ +- get_executable(), +- salt_call_path, ++ sys.executable if venv_salt_call is not None else get_executable(), ++ venv_salt_call if venv_salt_call is not None else salt_call_path, + "--retcode-passthrough", + "--local", + "--metadata", +diff --git a/salt/loader/__init__.py b/salt/loader/__init__.py +index 72a5e54401..32f8a7702c 100644 +--- a/salt/loader/__init__.py ++++ b/salt/loader/__init__.py +@@ -9,6 +9,7 @@ import inspect + import logging + import os + import re ++import threading + import time + import types + +@@ -31,7 +32,7 @@ from salt.exceptions import LoaderError + from salt.template import check_render_pipe_str + from salt.utils import entrypoints + +-from .lazy import SALT_BASE_PATH, FilterDictWrapper, LazyLoader ++from .lazy import SALT_BASE_PATH, FilterDictWrapper, LazyLoader as _LazyLoader + + log = logging.getLogger(__name__) + +@@ -81,6 +82,18 @@ SALT_INTERNAL_LOADERS_PATHS = ( + str(SALT_BASE_PATH / "wheel"), + ) + ++LOAD_LOCK = threading.Lock() ++ ++ ++def LazyLoader(*args, **kwargs): ++ # This wrapper is used to prevent deadlocks with importlib (bsc#1182851) ++ # LOAD_LOCK is also used directly in salt.client.ssh.SSH ++ try: ++ LOAD_LOCK.acquire() ++ return _LazyLoader(*args, **kwargs) ++ finally: ++ LOAD_LOCK.release() ++ + + def static_loader( + opts, +@@ -725,7 +738,7 @@ def fileserver(opts, backends, loaded_base_name=None): + ) + + +-def roster(opts, runner=None, utils=None, whitelist=None, loaded_base_name=None): ++def roster(opts, runner=None, utils=None, whitelist=None, loaded_base_name=None, context=None): + """ + Returns the roster modules + +@@ -736,12 +749,15 @@ def roster(opts, runner=None, utils=None, whitelist=None, loaded_base_name=None) + :param str loaded_base_name: The imported modules namespace when imported + by the salt loader. + """ ++ if context is None: ++ context = {} ++ + return LazyLoader( + _module_dirs(opts, "roster"), + opts, + tag="roster", + whitelist=whitelist, +- pack={"__runner__": runner, "__utils__": utils}, ++ pack={"__runner__": runner, "__utils__": utils, "__context__": context}, + extra_module_dirs=utils.module_dirs if utils else None, + loaded_base_name=loaded_base_name, + ) +@@ -933,7 +949,14 @@ def render( + ) + rend = FilterDictWrapper(ret, ".render") + +- if not check_render_pipe_str( ++ def _check_render_pipe_str(pipestr, renderers, blacklist, whitelist): ++ try: ++ LOAD_LOCK.acquire() ++ return check_render_pipe_str(pipestr, renderers, blacklist, whitelist) ++ finally: ++ LOAD_LOCK.release() ++ ++ if not _check_render_pipe_str( + opts["renderer"], rend, opts["renderer_blacklist"], opts["renderer_whitelist"] + ): + err = ( +diff --git a/salt/netapi/__init__.py b/salt/netapi/__init__.py +index a89c1a19af..8a28c48460 100644 +--- a/salt/netapi/__init__.py ++++ b/salt/netapi/__init__.py +@@ -79,6 +79,7 @@ class NetapiClient: + self.loadauth = salt.auth.LoadAuth(apiopts) + self.key = salt.daemons.masterapi.access_keys(apiopts) + self.ckminions = salt.utils.minions.CkMinions(apiopts) ++ self.context = {} + + def _is_master_running(self): + """ +@@ -245,7 +246,7 @@ class NetapiClient: + with salt.client.ssh.client.SSHClient( + mopts=self.opts, disable_custom_roster=True + ) as client: +- return client.cmd_sync(kwargs) ++ return client.cmd_sync(kwargs, context=self.context) + + def runner(self, fun, timeout=None, full_return=False, **kwargs): + """ +diff --git a/salt/roster/__init__.py b/salt/roster/__init__.py +index fc7339d785..ea23d550d7 100644 +--- a/salt/roster/__init__.py ++++ b/salt/roster/__init__.py +@@ -59,7 +59,7 @@ class Roster: + minion aware + """ + +- def __init__(self, opts, backends="flat"): ++ def __init__(self, opts, backends="flat", context=None): + self.opts = opts + if isinstance(backends, list): + self.backends = backends +@@ -71,7 +71,9 @@ class Roster: + self.backends = ["flat"] + utils = salt.loader.utils(self.opts) + runner = salt.loader.runner(self.opts, utils=utils) +- self.rosters = salt.loader.roster(self.opts, runner=runner, utils=utils) ++ self.rosters = salt.loader.roster( ++ self.opts, runner=runner, utils=utils, context=context ++ ) + + def _gen_back(self): + """ +diff --git a/tests/unit/test_loader.py b/tests/unit/test_loader.py +index cf33903320..1b616375b3 100644 +--- a/tests/unit/test_loader.py ++++ b/tests/unit/test_loader.py +@@ -1697,7 +1697,7 @@ class LazyLoaderRefreshFileMappingTest(TestCase): + cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy) + + def setUp(self): +- class LazyLoaderMock(salt.loader.LazyLoader): ++ class LazyLoaderMock(salt.loader._LazyLoader): + pass + + self.LOADER_CLASS = LazyLoaderMock +-- +2.39.2 + + diff --git a/add-sleep-on-exception-handling-on-minion-connection.patch b/add-sleep-on-exception-handling-on-minion-connection.patch new file mode 100644 index 0000000..47cf213 --- /dev/null +++ b/add-sleep-on-exception-handling-on-minion-connection.patch @@ -0,0 +1,41 @@ +From bad9e783e1a6923d85bdb1477a2e9766887a511e Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Thu, 18 Feb 2021 14:49:38 +0300 +Subject: [PATCH] Add sleep on exception handling on minion connection + attempt to the master (bsc#1174855) (#321) + +* Async batch implementation fix + +* Add sleep on exception handling on minion connection attempt to the master (bsc#1174855) +--- + salt/minion.py | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/salt/minion.py b/salt/minion.py +index 2f905e4a4f..c3b65f16c3 100644 +--- a/salt/minion.py ++++ b/salt/minion.py +@@ -1123,6 +1123,9 @@ class MinionManager(MinionBase): + last = 0 # never have we signed in + auth_wait = minion.opts["acceptance_wait_time"] + failed = False ++ retry_wait = 1 ++ retry_wait_inc = 1 ++ max_retry_wait = 20 + while True: + try: + if minion.opts.get("beacons_before_connect", False): +@@ -1161,6 +1164,9 @@ class MinionManager(MinionBase): + minion.opts["master"], + exc_info=True, + ) ++ yield salt.ext.tornado.gen.sleep(retry_wait) ++ if retry_wait < max_retry_wait: ++ retry_wait += retry_wait_inc + + # Multi Master Tune In + def tune_in(self): +-- +2.39.2 + + diff --git a/add-standalone-configuration-file-for-enabling-packa.patch b/add-standalone-configuration-file-for-enabling-packa.patch new file mode 100644 index 0000000..44a6901 --- /dev/null +++ b/add-standalone-configuration-file-for-enabling-packa.patch @@ -0,0 +1,26 @@ +From 94e702e83c05814296ea8987a722b71e99117360 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 22 May 2019 13:00:46 +0100 +Subject: [PATCH] Add standalone configuration file for enabling package + formulas + +--- + conf/suse/standalone-formulas-configuration.conf | 4 ++++ + 1 file changed, 4 insertions(+) + create mode 100644 conf/suse/standalone-formulas-configuration.conf + +diff --git a/conf/suse/standalone-formulas-configuration.conf b/conf/suse/standalone-formulas-configuration.conf +new file mode 100644 +index 0000000000..94d05fb2ee +--- /dev/null ++++ b/conf/suse/standalone-formulas-configuration.conf +@@ -0,0 +1,4 @@ ++file_roots: ++ base: ++ - /usr/share/salt-formulas/states ++ - /srv/salt +-- +2.39.2 + + diff --git a/add-support-for-gpgautoimport-539.patch b/add-support-for-gpgautoimport-539.patch new file mode 100644 index 0000000..33296b2 --- /dev/null +++ b/add-support-for-gpgautoimport-539.patch @@ -0,0 +1,369 @@ +From 2e103365c50fe42a72de3e9d57c3fdbee47454aa Mon Sep 17 00:00:00 2001 +From: Michael Calmer +Date: Fri, 8 Jul 2022 10:15:37 +0200 +Subject: [PATCH] add support for gpgautoimport (#539) + +* add support for gpgautoimport to refresh_db in the zypperpkg module + +* call refresh_db function from mod_repo + +* call refresh_db with kwargs where possible + +* ignore no repos defined exit code + +* fix zypperpkg test after adding more success return codes +--- + salt/modules/zypperpkg.py | 47 +++++++--- + tests/unit/modules/test_zypperpkg.py | 124 +++++++++++++++++++++++---- + 2 files changed, 140 insertions(+), 31 deletions(-) + +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 318c871b37..051f8f72c7 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -623,7 +623,7 @@ def list_upgrades(refresh=True, root=None, **kwargs): + salt '*' pkg.list_upgrades + """ + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + ret = dict() + cmd = ["list-updates"] +@@ -737,7 +737,7 @@ def info_available(*names, **kwargs): + + # Refresh db before extracting the latest package + if kwargs.get("refresh", True): +- refresh_db(root) ++ refresh_db(root, **kwargs) + + pkg_info = [] + batch = names[:] +@@ -1439,7 +1439,6 @@ def mod_repo(repo, **kwargs): + cmd_opt.append(kwargs.get("name")) + + if kwargs.get("gpgautoimport") is True: +- global_cmd_opt.append("--gpg-auto-import-keys") + call_refresh = True + + if cmd_opt: +@@ -1451,8 +1450,8 @@ def mod_repo(repo, **kwargs): + # when used with "zypper ar --refresh" or "zypper mr --refresh" + # --gpg-auto-import-keys is not doing anything + # so we need to specifically refresh here with --gpg-auto-import-keys +- refresh_opts = global_cmd_opt + ["refresh"] + [repo] +- __zypper__(root=root).xml.call(*refresh_opts) ++ kwargs.update({"repos": repo}) ++ refresh_db(root=root, **kwargs) + elif not added and not cmd_opt: + comment = "Specified arguments did not result in modification of repo" + +@@ -1463,7 +1462,7 @@ def mod_repo(repo, **kwargs): + return repo + + +-def refresh_db(force=None, root=None): ++def refresh_db(force=None, root=None, **kwargs): + """ + Trigger a repository refresh by calling ``zypper refresh``. Refresh will run + with ``--force`` if the "force=True" flag is passed on the CLI or +@@ -1474,6 +1473,17 @@ def refresh_db(force=None, root=None): + + {'': Bool} + ++ gpgautoimport : False ++ If set to True, automatically trust and import public GPG key for ++ the repository. ++ ++ .. versionadded:: 3005 ++ ++ repos ++ Refresh just the specified repos ++ ++ .. versionadded:: 3005 ++ + root + operate on a different root directory. + +@@ -1494,11 +1504,22 @@ def refresh_db(force=None, root=None): + salt.utils.pkg.clear_rtag(__opts__) + ret = {} + refresh_opts = ["refresh"] ++ global_opts = [] + if force is None: + force = __pillar__.get("zypper", {}).get("refreshdb_force", True) + if force: + refresh_opts.append("--force") +- out = __zypper__(root=root).refreshable.call(*refresh_opts) ++ repos = kwargs.get("repos", []) ++ refresh_opts.extend([repos] if not isinstance(repos, list) else repos) ++ ++ if kwargs.get("gpgautoimport", False): ++ global_opts.append("--gpg-auto-import-keys") ++ ++ # We do the actual call to zypper refresh. ++ # We ignore retcode 6 which is returned when there are no repositories defined. ++ out = __zypper__(root=root).refreshable.call( ++ *global_opts, *refresh_opts, success_retcodes=[0, 6] ++ ) + + for line in out.splitlines(): + if not line: +@@ -1683,7 +1704,7 @@ def install( + 'arch': ''}}} + """ + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + try: + pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"]( +@@ -1980,7 +2001,7 @@ def upgrade( + cmd_update.insert(0, "--no-gpg-checks") + + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + if dryrun: + cmd_update.append("--dry-run") +@@ -2808,7 +2829,7 @@ def search(criteria, refresh=False, **kwargs): + root = kwargs.get("root", None) + + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + cmd = ["search"] + if kwargs.get("match") == "exact": +@@ -2959,7 +2980,7 @@ def download(*packages, **kwargs): + + refresh = kwargs.get("refresh", False) + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + pkg_ret = {} + for dld_result in ( +@@ -3111,7 +3132,7 @@ def list_patches(refresh=False, root=None, **kwargs): + salt '*' pkg.list_patches + """ + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + return _get_patches(root=root) + +@@ -3205,7 +3226,7 @@ def resolve_capabilities(pkgs, refresh=False, root=None, **kwargs): + salt '*' pkg.resolve_capabilities resolve_capabilities=True w3m_ssl + """ + if refresh: +- refresh_db(root) ++ refresh_db(root, **kwargs) + + ret = list() + for pkg in pkgs: +diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py +index e85c93da3b..f5b6d74b6f 100644 +--- a/tests/unit/modules/test_zypperpkg.py ++++ b/tests/unit/modules/test_zypperpkg.py +@@ -377,7 +377,12 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + run_out = {"stderr": "", "stdout": "\n".join(ref_out), "retcode": 0} + + zypper_mock = MagicMock(return_value=run_out) +- call_kwargs = {"output_loglevel": "trace", "python_shell": False, "env": {}} ++ call_kwargs = { ++ "output_loglevel": "trace", ++ "python_shell": False, ++ "env": {}, ++ "success_retcodes": [0, 6], ++ } + with patch.dict(zypper.__salt__, {"cmd.run_all": zypper_mock}): + with patch.object(salt.utils.pkg, "clear_rtag", Mock()): + result = zypper.refresh_db() +@@ -395,6 +400,73 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + zypper_mock.assert_called_with( + ["zypper", "--non-interactive", "refresh", "--force"], **call_kwargs + ) ++ zypper.refresh_db(gpgautoimport=True) ++ zypper_mock.assert_called_with( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--gpg-auto-import-keys", ++ "refresh", ++ "--force", ++ ], ++ **call_kwargs ++ ) ++ zypper.refresh_db(gpgautoimport=True, force=True) ++ zypper_mock.assert_called_with( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--gpg-auto-import-keys", ++ "refresh", ++ "--force", ++ ], ++ **call_kwargs ++ ) ++ zypper.refresh_db(gpgautoimport=True, force=False) ++ zypper_mock.assert_called_with( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--gpg-auto-import-keys", ++ "refresh", ++ ], ++ **call_kwargs ++ ) ++ zypper.refresh_db( ++ gpgautoimport=True, ++ refresh=True, ++ repos="mock-repo-name", ++ root=None, ++ url="http://repo.url/some/path", ++ ) ++ zypper_mock.assert_called_with( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--gpg-auto-import-keys", ++ "refresh", ++ "--force", ++ "mock-repo-name", ++ ], ++ **call_kwargs ++ ) ++ zypper.refresh_db( ++ gpgautoimport=True, ++ repos="mock-repo-name", ++ root=None, ++ url="http://repo.url/some/path", ++ ) ++ zypper_mock.assert_called_with( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--gpg-auto-import-keys", ++ "refresh", ++ "--force", ++ "mock-repo-name", ++ ], ++ **call_kwargs ++ ) + + def test_info_installed(self): + """ +@@ -2082,18 +2154,23 @@ Repository 'DUMMY' not found by its alias, number, or URI. + + url = self.new_repo_config["url"] + name = self.new_repo_config["name"] +- with zypper_patcher: ++ with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: + zypper.mod_repo(name, **{"url": url, "gpgautoimport": True}) + self.assertEqual( + zypper.__zypper__(root=None).xml.call.call_args_list, + [ + call("ar", url, name), +- call("--gpg-auto-import-keys", "refresh", name), + ], + ) + self.assertTrue( + zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 + ) ++ refreshmock.assert_called_once_with( ++ gpgautoimport=True, ++ repos=name, ++ root=None, ++ url="http://repo.url/some/path", ++ ) + + def test_repo_noadd_nomod_ref(self): + """ +@@ -2112,15 +2189,17 @@ Repository 'DUMMY' not found by its alias, number, or URI. + "salt.modules.zypperpkg", **self.zypper_patcher_config + ) + +- with zypper_patcher: ++ with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: + zypper.mod_repo(name, **{"url": url, "gpgautoimport": True}) +- self.assertEqual( +- zypper.__zypper__(root=None).xml.call.call_args_list, +- [call("--gpg-auto-import-keys", "refresh", name)], +- ) + self.assertTrue( + zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 + ) ++ refreshmock.assert_called_once_with( ++ gpgautoimport=True, ++ repos=name, ++ root=None, ++ url="http://repo.url/some/path", ++ ) + + def test_repo_add_mod_ref(self): + """ +@@ -2133,10 +2212,10 @@ Repository 'DUMMY' not found by its alias, number, or URI. + zypper_patcher = patch.multiple( + "salt.modules.zypperpkg", **self.zypper_patcher_config + ) +- + url = self.new_repo_config["url"] + name = self.new_repo_config["name"] +- with zypper_patcher: ++ ++ with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: + zypper.mod_repo( + name, **{"url": url, "refresh": True, "gpgautoimport": True} + ) +@@ -2144,11 +2223,17 @@ Repository 'DUMMY' not found by its alias, number, or URI. + zypper.__zypper__(root=None).xml.call.call_args_list, + [ + call("ar", url, name), +- call("--gpg-auto-import-keys", "refresh", name), + ], + ) + zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( +- "--gpg-auto-import-keys", "mr", "--refresh", name ++ "mr", "--refresh", name ++ ) ++ refreshmock.assert_called_once_with( ++ gpgautoimport=True, ++ refresh=True, ++ repos=name, ++ root=None, ++ url="http://repo.url/some/path", + ) + + def test_repo_noadd_mod_ref(self): +@@ -2168,16 +2253,19 @@ Repository 'DUMMY' not found by its alias, number, or URI. + "salt.modules.zypperpkg", **self.zypper_patcher_config + ) + +- with zypper_patcher: ++ with zypper_patcher, patch.object(zypper, "refresh_db", Mock()) as refreshmock: + zypper.mod_repo( + name, **{"url": url, "refresh": True, "gpgautoimport": True} + ) +- self.assertEqual( +- zypper.__zypper__(root=None).xml.call.call_args_list, +- [call("--gpg-auto-import-keys", "refresh", name)], +- ) + zypper.__zypper__(root=None).refreshable.xml.call.assert_called_once_with( +- "--gpg-auto-import-keys", "mr", "--refresh", name ++ "mr", "--refresh", name ++ ) ++ refreshmock.assert_called_once_with( ++ gpgautoimport=True, ++ refresh=True, ++ repos=name, ++ root=None, ++ url="http://repo.url/some/path", + ) + + def test_wildcard_to_query_match_all(self): +-- +2.39.2 + + diff --git a/allow-vendor-change-option-with-zypper.patch b/allow-vendor-change-option-with-zypper.patch new file mode 100644 index 0000000..9a9f419 --- /dev/null +++ b/allow-vendor-change-option-with-zypper.patch @@ -0,0 +1,841 @@ +From a36d6524e530eca32966f46597c88dbfd4b90e78 Mon Sep 17 00:00:00 2001 +From: Martin Seidl +Date: Tue, 27 Oct 2020 16:12:29 +0100 +Subject: [PATCH] Allow vendor change option with zypper +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Fix novendorchange option (#284) + +* Fixed novendorchange handling in zypperpkg + +* refactor handling of novendorchange and fix tests + +add patch support for allow vendor change option with zypper + +Revert "add patch support for allow vendor change option with zypper" + +This reverts commit cee4cc182b4740c912861c712dea7bc44eb70ffb. + +Allow vendor change option with zypper (#313) + +* add patch support for allow vendor change option with zypper + +* adjust unit tests vendor change refactor, dropping cli arg + +* Fix pr issues + +Co-authored-by: Pablo Suárez Hernández + +* Fix unit test for allow vendor change on upgrade + +* Add unit test with unsupported zypper version + +Co-authored-by: Pablo Suárez Hernández + +Move vendor change logic to zypper class (#355) + +* move vendor change logic to zypper class + +* fix thing in zypperkg + +* refactor unit tests + +* Fix for syntax error + +* Fix mocking issue in unit test + +* fix issues with pr + +* Fix for zypperpkg unit test after refactor of vendorchangeflags + +Co-authored-by: Pablo Suárez Hernández + +* fix docs for vendor change options + +* Fix doc strings, and clean up tests + +Co-authored-by: Jochen Breuer +Co-authored-by: Pablo Suárez Hernández +--- + salt/modules/zypperpkg.py | 105 ++++-- + tests/unit/modules/test_zypperpkg.py | 532 ++++++++++++++++++++++++++- + 2 files changed, 612 insertions(+), 25 deletions(-) + +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 4bb10f445a..2da470bea3 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -36,6 +36,8 @@ import salt.utils.stringutils + import salt.utils.systemd + import salt.utils.versions + from salt.exceptions import CommandExecutionError, MinionError, SaltInvocationError ++ ++# pylint: disable=import-error,redefined-builtin,no-name-in-module + from salt.utils.versions import LooseVersion + + if salt.utils.files.is_fcntl_available(): +@@ -140,6 +142,13 @@ class _Zypper: + self.__systemd_scope = False + self.__root = None + ++ # Dist upgrade vendor change support (SLE12+) ++ self.dup_avc = False ++ # Install/Patch/Upgrade vendor change support (SLE15+) ++ self.inst_avc = False ++ # Flag if allow vendor change should be allowed ++ self.avc = False ++ + # Call status + self.__called = False + +@@ -184,6 +193,8 @@ class _Zypper: + self.__no_raise = True + elif item == "refreshable": + self.__refresh = True ++ elif item == "allow_vendor_change": ++ return self.__allow_vendor_change + elif item == "call": + return self.__call + else: +@@ -224,6 +235,33 @@ class _Zypper: + def pid(self): + return self.__call_result.get("pid", "") + ++ def __allow_vendor_change(self, allowvendorchange, novendorchange): ++ if allowvendorchange or not novendorchange: ++ self.refresh_zypper_flags() ++ if self.dup_avc or self.inst_avc: ++ log.info("Enabling vendor change") ++ self.avc = True ++ else: ++ log.warning( ++ "Enabling/Disabling vendor changes is not supported on this Zypper version" ++ ) ++ return self ++ ++ def refresh_zypper_flags(self): ++ try: ++ zypp_version = version("zypper") ++ # zypper version 1.11.34 in SLE12 update supports vendor change for only dist upgrade ++ if version_cmp(zypp_version, "1.11.34") >= 0: ++ # zypper version supports vendor change for dist upgrade ++ self.dup_avc = True ++ # zypper version 1.14.8 in SLE15 update supports vendor change in install/patch/upgrading ++ if version_cmp(zypp_version, "1.14.8") >= 0: ++ self.inst_avc = True ++ else: ++ log.error("Failed to compare Zypper version") ++ except Exception as ex: ++ log.error("Unable to get Zypper version: {}".format(ex)) ++ + def _is_error(self): + """ + Is this is an error code? +@@ -362,6 +400,15 @@ class _Zypper: + if self.__systemd_scope: + cmd.extend(["systemd-run", "--scope"]) + cmd.extend(self.__cmd) ++ ++ if self.avc: ++ for i in ["install", "upgrade", "dist-upgrade"]: ++ if i in cmd: ++ if i == "install" and self.inst_avc: ++ cmd.insert(cmd.index(i) + 1, "--allow-vendor-change") ++ elif i in ["upgrade", "dist-upgrade"] and self.dup_avc: ++ cmd.insert(cmd.index(i) + 1, "--allow-vendor-change") ++ + log.debug("Calling Zypper: %s", " ".join(cmd)) + self.__call_result = __salt__["cmd.run_all"](cmd, **kwargs) + if self._check_result(): +@@ -1490,6 +1537,8 @@ def install( + no_recommends=False, + root=None, + inclusion_detection=False, ++ novendorchange=True, ++ allowvendorchange=False, + **kwargs + ): + """ +@@ -1537,6 +1586,13 @@ def install( + skip_verify + Skip the GPG verification check (e.g., ``--no-gpg-checks``) + ++ novendorchange ++ DEPRECATED(use allowvendorchange): If set to True, do not allow vendor changes. Default: True ++ ++ allowvendorchange ++ If set to True, vendor change is allowed. Default: False ++ If both allowvendorchange and novendorchange are passed, only allowvendorchange is used. ++ + version + Can be either a version number, or the combination of a comparison + operator (<, >, <=, >=, =) and a version number (ex. '>1.2.3-4'). +@@ -1702,6 +1758,7 @@ def install( + cmd_install.append( + kwargs.get("resolve_capabilities") and "--capability" or "--name" + ) ++ # Install / patching / upgrade with vendor change support is only in SLE 15+ opensuse Leap 15+ + + if not refresh: + cmd_install.insert(0, "--no-refresh") +@@ -1738,6 +1795,7 @@ def install( + systemd_scope=systemd_scope, + root=root, + ) ++ .allow_vendor_change(allowvendorchange, novendorchange) + .call(*cmd) + .splitlines() + ): +@@ -1750,7 +1808,9 @@ def install( + while downgrades: + cmd = cmd_install + ["--force"] + downgrades[:500] + downgrades = downgrades[500:] +- __zypper__(no_repo_failure=ignore_repo_failure, root=root).call(*cmd) ++ __zypper__(no_repo_failure=ignore_repo_failure, root=root).allow_vendor_change( ++ allowvendorchange, novendorchange ++ ).call(*cmd) + + _clean_cache() + new = ( +@@ -1783,7 +1843,8 @@ def upgrade( + dryrun=False, + dist_upgrade=False, + fromrepo=None, +- novendorchange=False, ++ novendorchange=True, ++ allowvendorchange=False, + skip_verify=False, + no_recommends=False, + root=None, +@@ -1844,7 +1905,11 @@ def upgrade( + Specify a list of package repositories to upgrade from. Default: None + + novendorchange +- If set to True, no allow vendor changes. Default: False ++ DEPRECATED(use allowvendorchange): If set to True, do not allow vendor changes. Default: True ++ ++ allowvendorchange ++ If set to True, vendor change is allowed. Default: False ++ If both allowvendorchange and novendorchange are passed, only allowvendorchange is used. + + skip_verify + Skip the GPG verification check (e.g., ``--no-gpg-checks``) +@@ -1927,28 +1992,18 @@ def upgrade( + cmd_update.extend(["--from" if dist_upgrade else "--repo", repo]) + log.info("Targeting repos: %s", fromrepo) + +- if dist_upgrade: +- if novendorchange: +- # TODO: Grains validation should be moved to Zypper class +- if __grains__["osrelease_info"][0] > 11: +- cmd_update.append("--no-allow-vendor-change") +- log.info("Disabling vendor changes") +- else: +- log.warning( +- "Disabling vendor changes is not supported on this Zypper version" +- ) ++ if no_recommends: ++ cmd_update.append("--no-recommends") ++ log.info("Disabling recommendations") + +- if no_recommends: +- cmd_update.append("--no-recommends") +- log.info("Disabling recommendations") ++ if dryrun: ++ # Creates a solver test case for debugging. ++ log.info("Executing debugsolver and performing a dry-run dist-upgrade") ++ __zypper__(systemd_scope=_systemd_scope(), root=root).allow_vendor_change( ++ allowvendorchange, novendorchange ++ ).noraise.call(*cmd_update + ["--debug-solver"]) + +- if dryrun: +- # Creates a solver test case for debugging. +- log.info("Executing debugsolver and performing a dry-run dist-upgrade") +- __zypper__(systemd_scope=_systemd_scope(), root=root).noraise.call( +- *cmd_update + ["--debug-solver"] +- ) +- else: ++ if not dist_upgrade: + if name or pkgs: + try: + (pkg_params, _) = __salt__["pkg_resource.parse_targets"]( +@@ -1962,7 +2017,9 @@ def upgrade( + + old = list_pkgs(root=root, attr=diff_attr) + +- __zypper__(systemd_scope=_systemd_scope(), root=root).noraise.call(*cmd_update) ++ __zypper__(systemd_scope=_systemd_scope(), root=root).allow_vendor_change( ++ allowvendorchange, novendorchange ++ ).noraise.call(*cmd_update) + _clean_cache() + new = list_pkgs(root=root, attr=diff_attr) + ret = salt.utils.data.compare_dicts(old, new) +diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py +index 5e4c967520..e85c93da3b 100644 +--- a/tests/unit/modules/test_zypperpkg.py ++++ b/tests/unit/modules/test_zypperpkg.py +@@ -137,6 +137,7 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + stdout_xml_snippet = '' + sniffer = RunSniffer(stdout=stdout_xml_snippet) ++ zypper.__zypper__._reset() + with patch.dict("salt.modules.zypperpkg.__salt__", {"cmd.run_all": sniffer}): + self.assertEqual(zypper.__zypper__.call("foo"), stdout_xml_snippet) + self.assertEqual(len(sniffer.calls), 1) +@@ -628,13 +629,495 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + {"vim": "7.4.326-2.62", "fakepkg": ""}, + ) + ++ def test_upgrade_without_vendor_change(self): ++ """ ++ Dist-upgrade without vendor change option. ++ """ ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}]), ++ ): ++ ret = zypper.upgrade(dist_upgrade=True) ++ self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) ++ zypper_mock.assert_any_call( ++ "dist-upgrade", "--auto-agree-with-licenses", ++ ) ++ ++ def test_refresh_zypper_flags(self): ++ zypper.__zypper__._reset() ++ with patch( ++ "salt.modules.zypperpkg.version", MagicMock(return_value="0.5") ++ ), patch.dict( ++ zypper.__salt__, {"lowpkg.version_cmp": MagicMock(side_effect=[-1, -1])} ++ ): ++ zypper.__zypper__.refresh_zypper_flags() ++ assert zypper.__zypper__.inst_avc == False ++ assert zypper.__zypper__.dup_avc == False ++ with patch( ++ "salt.modules.zypperpkg.version", MagicMock(return_value="1.11.34") ++ ), patch.dict( ++ zypper.__salt__, {"lowpkg.version_cmp": MagicMock(side_effect=[0, -1])} ++ ): ++ zypper.__zypper__.refresh_zypper_flags() ++ assert zypper.__zypper__.inst_avc == False ++ assert zypper.__zypper__.dup_avc == True ++ with patch( ++ "salt.modules.zypperpkg.version", MagicMock(return_value="1.14.8") ++ ), patch.dict( ++ zypper.__salt__, {"lowpkg.version_cmp": MagicMock(side_effect=[0, 0])} ++ ): ++ zypper.__zypper__.refresh_zypper_flags() ++ assert zypper.__zypper__.inst_avc == True ++ assert zypper.__zypper__.dup_avc == True ++ ++ @patch("salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock()) ++ def test_allow_vendor_change_function(self): ++ zypper.__zypper__._reset() ++ zypper.__zypper__.inst_avc = True ++ zypper.__zypper__.dup_avc = True ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(False, False) ++ assert zypper.__zypper__.avc == True ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(True, False) ++ assert zypper.__zypper__.avc == True ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(False, True) ++ assert zypper.__zypper__.avc == False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(True, True) ++ assert zypper.__zypper__.avc == True ++ ++ zypper.__zypper__._reset() ++ zypper.__zypper__.inst_avc = False ++ zypper.__zypper__.dup_avc = True ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(False, False) ++ assert zypper.__zypper__.avc == True ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(True, False) ++ assert zypper.__zypper__.avc == True ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(False, True) ++ assert zypper.__zypper__.avc == False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(True, True) ++ assert zypper.__zypper__.avc == True ++ ++ zypper.__zypper__._reset() ++ zypper.__zypper__.inst_avc = False ++ zypper.__zypper__.dup_avc = False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(False, False) ++ assert zypper.__zypper__.avc == False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(True, False) ++ assert zypper.__zypper__.avc == False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(False, True) ++ assert zypper.__zypper__.avc == False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.allow_vendor_change(True, True) ++ assert zypper.__zypper__.avc == False ++ ++ @patch( ++ "salt.utils.environment.get_module_environment", ++ MagicMock(return_value={"SALT_RUNNING": "1"}), ++ ) ++ def test_zypper_call_dist_upgrade_with_avc_true(self): ++ cmd_run_mock = MagicMock(return_value={"retcode": 0, "stdout": None}) ++ zypper.__zypper__._reset() ++ with patch.dict(zypper.__salt__, {"cmd.run_all": cmd_run_mock}), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch("salt.modules.zypperpkg.__zypper__._reset", MagicMock()): ++ zypper.__zypper__.dup_avc = True ++ zypper.__zypper__.avc = True ++ zypper.__zypper__.call("dist-upgrade") ++ cmd_run_mock.assert_any_call( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--no-refresh", ++ "dist-upgrade", ++ "--allow-vendor-change", ++ ], ++ output_loglevel="trace", ++ python_shell=False, ++ env={"SALT_RUNNING": "1"}, ++ ) ++ ++ @patch( ++ "salt.utils.environment.get_module_environment", ++ MagicMock(return_value={"SALT_RUNNING": "1"}), ++ ) ++ def test_zypper_call_dist_upgrade_with_avc_false(self): ++ cmd_run_mock = MagicMock(return_value={"retcode": 0, "stdout": None}) ++ zypper.__zypper__._reset() ++ with patch.dict(zypper.__salt__, {"cmd.run_all": cmd_run_mock}), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch("salt.modules.zypperpkg.__zypper__._reset", MagicMock()): ++ zypper.__zypper__.dup_avc = False ++ zypper.__zypper__.avc = False ++ zypper.__zypper__.call("dist-upgrade") ++ cmd_run_mock.assert_any_call( ++ ["zypper", "--non-interactive", "--no-refresh", "dist-upgrade",], ++ output_loglevel="trace", ++ python_shell=False, ++ env={"SALT_RUNNING": "1"}, ++ ) ++ ++ @patch( ++ "salt.utils.environment.get_module_environment", ++ MagicMock(return_value={"SALT_RUNNING": "1"}), ++ ) ++ def test_zypper_call_install_with_avc_true(self): ++ cmd_run_mock = MagicMock(return_value={"retcode": 0, "stdout": None}) ++ zypper.__zypper__._reset() ++ with patch.dict(zypper.__salt__, {"cmd.run_all": cmd_run_mock}), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch("salt.modules.zypperpkg.__zypper__._reset", MagicMock()): ++ zypper.__zypper__.inst_avc = True ++ zypper.__zypper__.avc = True ++ zypper.__zypper__.call("install") ++ cmd_run_mock.assert_any_call( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--no-refresh", ++ "install", ++ "--allow-vendor-change", ++ ], ++ output_loglevel="trace", ++ python_shell=False, ++ env={"SALT_RUNNING": "1"}, ++ ) ++ ++ @patch( ++ "salt.utils.environment.get_module_environment", ++ MagicMock(return_value={"SALT_RUNNING": "1"}), ++ ) ++ def test_zypper_call_install_with_avc_false(self): ++ cmd_run_mock = MagicMock(return_value={"retcode": 0, "stdout": None}) ++ zypper.__zypper__._reset() ++ with patch.dict(zypper.__salt__, {"cmd.run_all": cmd_run_mock}), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch("salt.modules.zypperpkg.__zypper__._reset", MagicMock()): ++ zypper.__zypper__.inst_avc = False ++ zypper.__zypper__.dup_avc = True ++ zypper.__zypper__.avc = True ++ zypper.__zypper__.call("install") ++ cmd_run_mock.assert_any_call( ++ ["zypper", "--non-interactive", "--no-refresh", "install",], ++ output_loglevel="trace", ++ python_shell=False, ++ env={"SALT_RUNNING": "1"}, ++ ) ++ ++ def test_upgrade_with_novendorchange_true(self): ++ """ ++ Dist-upgrade without vendor change option. ++ """ ++ zypper.__zypper__._reset() ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ) as refresh_flags_mock, patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}]), ++ ): ++ ret = zypper.upgrade(dist_upgrade=True, novendorchange=True) ++ refresh_flags_mock.assert_not_called() ++ zypper_mock.assert_any_call( ++ "dist-upgrade", "--auto-agree-with-licenses", ++ ) ++ ++ def test_upgrade_with_novendorchange_false(self): ++ """ ++ Perform dist-upgrade with novendorchange set to False. ++ """ ++ zypper.__zypper__._reset() ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ zypper.__zypper__.inst_avc = True ++ zypper.__zypper__.dup_avc = True ++ with patch.dict( ++ zypper.__salt__, ++ { ++ "pkg_resource.version": MagicMock(return_value="1.15"), ++ "lowpkg.version_cmp": MagicMock(return_value=1), ++ }, ++ ): ++ ret = zypper.upgrade( ++ dist_upgrade=True, ++ dryrun=True, ++ fromrepo=["Dummy", "Dummy2"], ++ novendorchange=False, ++ ) ++ assert zypper.__zypper__.avc == True ++ ++ def test_upgrade_with_allowvendorchange_true(self): ++ """ ++ Perform dist-upgrade with allowvendorchange set to True. ++ """ ++ zypper.__zypper__._reset() ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ with patch.dict( ++ zypper.__salt__, ++ { ++ "pkg_resource.version": MagicMock(return_value="1.15"), ++ "lowpkg.version_cmp": MagicMock(return_value=1), ++ }, ++ ): ++ ++ zypper.__zypper__.inst_avc = True ++ zypper.__zypper__.dup_avc = True ++ ret = zypper.upgrade( ++ dist_upgrade=True, ++ dryrun=True, ++ fromrepo=["Dummy", "Dummy2"], ++ allowvendorchange=True, ++ ) ++ assert zypper.__zypper__.avc == True ++ ++ def test_upgrade_with_allowvendorchange_false(self): ++ """ ++ Perform dist-upgrade with allowvendorchange set to False. ++ """ ++ zypper.__zypper__._reset() ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ), patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ with patch.dict( ++ zypper.__salt__, ++ { ++ "pkg_resource.version": MagicMock(return_value="1.15"), ++ "lowpkg.version_cmp": MagicMock(return_value=1), ++ }, ++ ): ++ ++ zypper.__zypper__.inst_avc = True ++ zypper.__zypper__.dup_avc = True ++ ret = zypper.upgrade( ++ dist_upgrade=True, ++ dryrun=True, ++ fromrepo=["Dummy", "Dummy2"], ++ allowvendorchange=False, ++ ) ++ assert zypper.__zypper__.avc == False ++ ++ def test_upgrade_old_zypper(self): ++ zypper.__zypper__._reset() ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg.__zypper__.refresh_zypper_flags", MagicMock() ++ ) as refresh_flags_mock, patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ with patch.dict( ++ zypper.__salt__, ++ { ++ "pkg_resource.version": MagicMock(return_value="1.11"), ++ "lowpkg.version_cmp": MagicMock(return_value=-1), ++ }, ++ ): ++ zypper.__zypper__.inst_avc = False ++ zypper.__zypper__.dup_avc = False ++ ret = zypper.upgrade( ++ dist_upgrade=True, ++ dryrun=True, ++ fromrepo=["Dummy", "Dummy2"], ++ novendorchange=False, ++ ) ++ zypper.__zypper__.avc = False ++ ++ def test_upgrade_success(self): ++ """ ++ Test system upgrade and dist-upgrade success. ++ ++ :return: ++ """ ++ with patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ with patch( ++ "salt.modules.zypperpkg.__zypper__.noraise.call", MagicMock() ++ ) as zypper_mock: ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.2"}]), ++ ): ++ ret = zypper.upgrade() ++ self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.2"}}) ++ zypper_mock.assert_any_call("update", "--auto-agree-with-licenses") ++ ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock( ++ side_effect=[ ++ {"kernel-default": "1.1"}, ++ {"kernel-default": "1.1,1.2"}, ++ ] ++ ), ++ ): ++ ret = zypper.upgrade() ++ self.assertDictEqual( ++ ret, {"kernel-default": {"old": "1.1", "new": "1.1,1.2"}} ++ ) ++ zypper_mock.assert_any_call("update", "--auto-agree-with-licenses") ++ ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1,1.2"}]), ++ ): ++ ret = zypper.upgrade() ++ self.assertDictEqual(ret, {"vim": {"old": "1.1", "new": "1.1,1.2"}}) ++ zypper_mock.assert_any_call("update", "--auto-agree-with-licenses") ++ ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ ret = zypper.upgrade(dist_upgrade=True, dryrun=True) ++ zypper_mock.assert_any_call( ++ "dist-upgrade", "--auto-agree-with-licenses", "--dry-run" ++ ) ++ zypper_mock.assert_any_call( ++ "dist-upgrade", ++ "--auto-agree-with-licenses", ++ "--dry-run", ++ "--debug-solver", ++ ) ++ ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ ret = zypper.upgrade( ++ dist_upgrade=False, fromrepo=["Dummy", "Dummy2"], dryrun=False ++ ) ++ zypper_mock.assert_any_call( ++ "update", ++ "--auto-agree-with-licenses", ++ "--repo", ++ "Dummy", ++ "--repo", ++ "Dummy2", ++ ) ++ ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ ret = zypper.upgrade( ++ dist_upgrade=True, ++ dryrun=True, ++ fromrepo=["Dummy", "Dummy2"], ++ novendorchange=True, ++ ) ++ zypper_mock.assert_any_call( ++ "dist-upgrade", ++ "--auto-agree-with-licenses", ++ "--dry-run", ++ "--from", ++ "Dummy", ++ "--from", ++ "Dummy2", ++ ) ++ zypper_mock.assert_any_call( ++ "dist-upgrade", ++ "--auto-agree-with-licenses", ++ "--dry-run", ++ "--from", ++ "Dummy", ++ "--from", ++ "Dummy2", ++ "--debug-solver", ++ ) ++ ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ ret = zypper.upgrade( ++ dist_upgrade=False, fromrepo=["Dummy", "Dummy2"], dryrun=False ++ ) ++ zypper_mock.assert_any_call( ++ "update", ++ "--auto-agree-with-licenses", ++ "--repo", ++ "Dummy", ++ "--repo", ++ "Dummy2", ++ ) ++ + def test_upgrade_kernel(self): + """ + Test kernel package upgrade success. + + :return: + """ +- with patch.dict(zypper.__grains__, {"osrelease_info": [12, 1]}), patch( ++ with patch( + "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) + ), patch( + "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) +@@ -672,6 +1155,53 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + }, + ) + ++ def test_upgrade_failure(self): ++ """ ++ Test system upgrade failure. ++ ++ :return: ++ """ ++ zypper_out = """ ++Loading repository data... ++Reading installed packages... ++Computing distribution upgrade... ++Use 'zypper repos' to get the list of defined repositories. ++Repository 'DUMMY' not found by its alias, number, or URI. ++""" ++ ++ class FailingZypperDummy: ++ def __init__(self): ++ self.stdout = zypper_out ++ self.stderr = "" ++ self.pid = 1234 ++ self.exit_code = 555 ++ self.noraise = MagicMock() ++ self.allow_vendor_change = self ++ self.SUCCESS_EXIT_CODES = [0] ++ ++ def __call__(self, *args, **kwargs): ++ return self ++ ++ with patch( ++ "salt.modules.zypperpkg.__zypper__", FailingZypperDummy() ++ ) as zypper_mock, patch( ++ "salt.modules.zypperpkg.refresh_db", MagicMock(return_value=True) ++ ), patch( ++ "salt.modules.zypperpkg._systemd_scope", MagicMock(return_value=False) ++ ): ++ zypper_mock.noraise.call = MagicMock() ++ with patch( ++ "salt.modules.zypperpkg.list_pkgs", ++ MagicMock(side_effect=[{"vim": "1.1"}, {"vim": "1.1"}]), ++ ): ++ with self.assertRaises(CommandExecutionError) as cmd_exc: ++ ret = zypper.upgrade(dist_upgrade=True, fromrepo=["DUMMY"]) ++ self.assertEqual(cmd_exc.exception.info["changes"], {}) ++ self.assertEqual(cmd_exc.exception.info["result"]["stdout"], zypper_out) ++ zypper_mock.noraise.call.assert_called_with( ++ "dist-upgrade", "--auto-agree-with-licenses", "--from", "DUMMY", ++ ) ++ + def test_upgrade_available(self): + """ + Test whether or not an upgrade is available for a given package. +-- +2.39.2 + + diff --git a/async-batch-implementation.patch b/async-batch-implementation.patch new file mode 100644 index 0000000..7795305 --- /dev/null +++ b/async-batch-implementation.patch @@ -0,0 +1,1149 @@ +From 76e69d9ef729365db1b0f1798f5f8a038d2065fc Mon Sep 17 00:00:00 2001 +From: Mihai Dinca +Date: Fri, 16 Nov 2018 17:05:29 +0100 +Subject: [PATCH] Async batch implementation +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Add find_job checks + +Check if should close on all events + +Make batch_delay a request parameter + +Allow multiple event handlers + +Use config value for gather_job_timeout when not in payload + +Add async batch unittests + +Allow metadata to pass + +Pass metadata only to batch jobs + +Add the metadata to the start/done events + +Pass only metadata not all **kwargs + +Add separate batch presence_ping timeout + +Fix async batch race conditions + +Close batching when there is no next batch + +Add 'batch_presence_ping_timeout' and 'batch_presence_ping_gather_job_timeout' parameters for synchronous batching + +Fix async-batch multiple done events + +Fix memory leak produced by batch async find_jobs mechanism (bsc#1140912) + +Multiple fixes: + +- use different JIDs per find_job +- fix bug in detection of find_job returns +- fix timeout passed from request payload +- better cleanup at the end of batching + +Co-authored-by: Pablo Suárez Hernández + +Improve batch_async to release consumed memory (bsc#1140912) + +Use current IOLoop for the LocalClient instance of BatchAsync (bsc#1137642) + +Fix failing unit tests for batch async + +Remove unnecessary yield causing BadYieldError (bsc#1154620) + +Fixing StreamClosed issue + +Fix batch_async obsolete test + +batch_async: avoid using fnmatch to match event (#217) + +Batch Async: Catch exceptions and safety unregister and close instances + +Fix unit tests for batch async after refactor + +Changed imports to vendored Tornado + +Async batch implementation fix (#320) + +Remove deprecated usage of NO_MOCK and NO_MOCK_REASON +--- + salt/auth/__init__.py | 2 + + salt/cli/batch.py | 109 ++++-- + salt/cli/batch_async.py | 315 +++++++++++++++++ + salt/cli/support/profiles/__init__.py | 5 +- + salt/client/__init__.py | 45 +-- + salt/master.py | 20 ++ + salt/transport/ipc.py | 9 +- + salt/utils/event.py | 8 +- + tests/pytests/unit/cli/test_batch_async.py | 386 +++++++++++++++++++++ + 9 files changed, 841 insertions(+), 58 deletions(-) + create mode 100644 salt/cli/batch_async.py + create mode 100644 tests/pytests/unit/cli/test_batch_async.py + +diff --git a/salt/auth/__init__.py b/salt/auth/__init__.py +index 331baab211..b0f0c0ac6c 100644 +--- a/salt/auth/__init__.py ++++ b/salt/auth/__init__.py +@@ -49,6 +49,8 @@ AUTH_INTERNAL_KEYWORDS = frozenset( + "print_event", + "raw", + "yield_pub_data", ++ "batch", ++ "batch_delay", + ] + ) + +diff --git a/salt/cli/batch.py b/salt/cli/batch.py +index 8e1547c61d..fcd3f571d5 100644 +--- a/salt/cli/batch.py ++++ b/salt/cli/batch.py +@@ -13,9 +13,88 @@ import salt.exceptions + import salt.output + import salt.utils.stringutils + ++# pylint: disable=import-error,no-name-in-module,redefined-builtin ++ + log = logging.getLogger(__name__) + + ++def get_bnum(opts, minions, quiet): ++ """ ++ Return the active number of minions to maintain ++ """ ++ partition = lambda x: float(x) / 100.0 * len(minions) ++ try: ++ if isinstance(opts["batch"], str) and "%" in opts["batch"]: ++ res = partition(float(opts["batch"].strip("%"))) ++ if res < 1: ++ return int(math.ceil(res)) ++ else: ++ return int(res) ++ else: ++ return int(opts["batch"]) ++ except ValueError: ++ if not quiet: ++ salt.utils.stringutils.print_cli( ++ "Invalid batch data sent: {}\nData must be in the " ++ "form of %10, 10% or 3".format(opts["batch"]) ++ ) ++ ++ ++def batch_get_opts( ++ tgt, fun, batch, parent_opts, arg=(), tgt_type="glob", ret="", kwarg=None, **kwargs ++): ++ # We need to re-import salt.utils.args here ++ # even though it has already been imported. ++ # when cmd_batch is called via the NetAPI ++ # the module is unavailable. ++ import salt.utils.args ++ ++ arg = salt.utils.args.condition_input(arg, kwarg) ++ opts = { ++ "tgt": tgt, ++ "fun": fun, ++ "arg": arg, ++ "tgt_type": tgt_type, ++ "ret": ret, ++ "batch": batch, ++ "failhard": kwargs.get("failhard", parent_opts.get("failhard", False)), ++ "raw": kwargs.get("raw", False), ++ } ++ ++ if "timeout" in kwargs: ++ opts["timeout"] = kwargs["timeout"] ++ if "gather_job_timeout" in kwargs: ++ opts["gather_job_timeout"] = kwargs["gather_job_timeout"] ++ if "batch_wait" in kwargs: ++ opts["batch_wait"] = int(kwargs["batch_wait"]) ++ ++ for key, val in parent_opts.items(): ++ if key not in opts: ++ opts[key] = val ++ ++ opts["batch_presence_ping_timeout"] = kwargs.get( ++ "batch_presence_ping_timeout", opts["timeout"] ++ ) ++ opts["batch_presence_ping_gather_job_timeout"] = kwargs.get( ++ "batch_presence_ping_gather_job_timeout", opts["gather_job_timeout"] ++ ) ++ ++ return opts ++ ++ ++def batch_get_eauth(kwargs): ++ eauth = {} ++ if "eauth" in kwargs: ++ eauth["eauth"] = kwargs.pop("eauth") ++ if "username" in kwargs: ++ eauth["username"] = kwargs.pop("username") ++ if "password" in kwargs: ++ eauth["password"] = kwargs.pop("password") ++ if "token" in kwargs: ++ eauth["token"] = kwargs.pop("token") ++ return eauth ++ ++ + class Batch: + """ + Manage the execution of batch runs +@@ -39,6 +118,7 @@ class Batch: + self.pub_kwargs = eauth if eauth else {} + self.quiet = quiet + self.options = _parser ++ self.minions = set() + # Passing listen True to local client will prevent it from purging + # cahced events while iterating over the batches. + self.local = salt.client.get_local_client(opts["conf_file"], listen=True) +@@ -51,7 +131,7 @@ class Batch: + self.opts["tgt"], + "test.ping", + [], +- self.opts["timeout"], ++ self.opts.get("batch_presence_ping_timeout", self.opts["timeout"]), + ] + + selected_target_option = self.opts.get("selected_target_option", None) +@@ -62,7 +142,12 @@ class Batch: + + self.pub_kwargs["yield_pub_data"] = True + ping_gen = self.local.cmd_iter( +- *args, gather_job_timeout=self.opts["gather_job_timeout"], **self.pub_kwargs ++ *args, ++ gather_job_timeout=self.opts.get( ++ "batch_presence_ping_gather_job_timeout", ++ self.opts["gather_job_timeout"], ++ ), ++ **self.pub_kwargs + ) + + # Broadcast to targets +@@ -87,25 +172,7 @@ class Batch: + return (list(fret), ping_gen, nret.difference(fret)) + + def get_bnum(self): +- """ +- Return the active number of minions to maintain +- """ +- partition = lambda x: float(x) / 100.0 * len(self.minions) +- try: +- if isinstance(self.opts["batch"], str) and "%" in self.opts["batch"]: +- res = partition(float(self.opts["batch"].strip("%"))) +- if res < 1: +- return int(math.ceil(res)) +- else: +- return int(res) +- else: +- return int(self.opts["batch"]) +- except ValueError: +- if not self.quiet: +- salt.utils.stringutils.print_cli( +- "Invalid batch data sent: {}\nData must be in the " +- "form of %10, 10% or 3".format(self.opts["batch"]) +- ) ++ return get_bnum(self.opts, self.minions, self.quiet) + + def __update_wait(self, wait): + now = datetime.now() +diff --git a/salt/cli/batch_async.py b/salt/cli/batch_async.py +new file mode 100644 +index 0000000000..09aa85258b +--- /dev/null ++++ b/salt/cli/batch_async.py +@@ -0,0 +1,315 @@ ++""" ++Execute a job on the targeted minions by using a moving window of fixed size `batch`. ++""" ++ ++import gc ++ ++# pylint: enable=import-error,no-name-in-module,redefined-builtin ++import logging ++ ++import salt.client ++import salt.ext.tornado ++import tornado ++from salt.cli.batch import batch_get_eauth, batch_get_opts, get_bnum ++ ++log = logging.getLogger(__name__) ++ ++ ++class BatchAsync: ++ """ ++ Run a job on the targeted minions by using a moving window of fixed size `batch`. ++ ++ ``BatchAsync`` is used to execute a job on the targeted minions by keeping ++ the number of concurrent running minions to the size of `batch` parameter. ++ ++ The control parameters are: ++ - batch: number/percentage of concurrent running minions ++ - batch_delay: minimum wait time between batches ++ - batch_presence_ping_timeout: time to wait for presence pings before starting the batch ++ - gather_job_timeout: `find_job` timeout ++ - timeout: time to wait before firing a `find_job` ++ ++ When the batch stars, a `start` event is fired: ++ - tag: salt/batch//start ++ - data: { ++ "available_minions": self.minions, ++ "down_minions": targeted_minions - presence_ping_minions ++ } ++ ++ When the batch ends, an `done` event is fired: ++ - tag: salt/batch//done ++ - data: { ++ "available_minions": self.minions, ++ "down_minions": targeted_minions - presence_ping_minions ++ "done_minions": self.done_minions, ++ "timedout_minions": self.timedout_minions ++ } ++ """ ++ ++ def __init__(self, parent_opts, jid_gen, clear_load): ++ ioloop = salt.ext.tornado.ioloop.IOLoop.current() ++ self.local = salt.client.get_local_client( ++ parent_opts["conf_file"], io_loop=ioloop ++ ) ++ if "gather_job_timeout" in clear_load["kwargs"]: ++ clear_load["gather_job_timeout"] = clear_load["kwargs"].pop( ++ "gather_job_timeout" ++ ) ++ else: ++ clear_load["gather_job_timeout"] = self.local.opts["gather_job_timeout"] ++ self.batch_presence_ping_timeout = clear_load["kwargs"].get( ++ "batch_presence_ping_timeout", None ++ ) ++ self.batch_delay = clear_load["kwargs"].get("batch_delay", 1) ++ self.opts = batch_get_opts( ++ clear_load.pop("tgt"), ++ clear_load.pop("fun"), ++ clear_load["kwargs"].pop("batch"), ++ self.local.opts, ++ **clear_load ++ ) ++ self.eauth = batch_get_eauth(clear_load["kwargs"]) ++ self.metadata = clear_load["kwargs"].get("metadata", {}) ++ self.minions = set() ++ self.targeted_minions = set() ++ self.timedout_minions = set() ++ self.done_minions = set() ++ self.active = set() ++ self.initialized = False ++ self.jid_gen = jid_gen ++ self.ping_jid = jid_gen() ++ self.batch_jid = jid_gen() ++ self.find_job_jid = jid_gen() ++ self.find_job_returned = set() ++ self.ended = False ++ self.event = salt.utils.event.get_event( ++ "master", ++ self.opts["sock_dir"], ++ self.opts["transport"], ++ opts=self.opts, ++ listen=True, ++ io_loop=ioloop, ++ keep_loop=True, ++ ) ++ self.scheduled = False ++ self.patterns = set() ++ ++ def __set_event_handler(self): ++ ping_return_pattern = "salt/job/{}/ret/*".format(self.ping_jid) ++ batch_return_pattern = "salt/job/{}/ret/*".format(self.batch_jid) ++ self.event.subscribe(ping_return_pattern, match_type="glob") ++ self.event.subscribe(batch_return_pattern, match_type="glob") ++ self.patterns = { ++ (ping_return_pattern, "ping_return"), ++ (batch_return_pattern, "batch_run"), ++ } ++ self.event.set_event_handler(self.__event_handler) ++ ++ def __event_handler(self, raw): ++ if not self.event: ++ return ++ try: ++ mtag, data = self.event.unpack(raw, self.event.serial) ++ for (pattern, op) in self.patterns: ++ if mtag.startswith(pattern[:-1]): ++ minion = data["id"] ++ if op == "ping_return": ++ self.minions.add(minion) ++ if self.targeted_minions == self.minions: ++ self.event.io_loop.spawn_callback(self.start_batch) ++ elif op == "find_job_return": ++ if data.get("return", None): ++ self.find_job_returned.add(minion) ++ elif op == "batch_run": ++ if minion in self.active: ++ self.active.remove(minion) ++ self.done_minions.add(minion) ++ self.event.io_loop.spawn_callback(self.schedule_next) ++ except Exception as ex: ++ log.error("Exception occured while processing event: {}".format(ex)) ++ ++ def _get_next(self): ++ to_run = ( ++ self.minions.difference(self.done_minions) ++ .difference(self.active) ++ .difference(self.timedout_minions) ++ ) ++ next_batch_size = min( ++ len(to_run), # partial batch (all left) ++ self.batch_size - len(self.active), # full batch or available slots ++ ) ++ return set(list(to_run)[:next_batch_size]) ++ ++ def check_find_job(self, batch_minions, jid): ++ if self.event: ++ find_job_return_pattern = "salt/job/{}/ret/*".format(jid) ++ self.event.unsubscribe(find_job_return_pattern, match_type="glob") ++ self.patterns.remove((find_job_return_pattern, "find_job_return")) ++ ++ timedout_minions = batch_minions.difference( ++ self.find_job_returned ++ ).difference(self.done_minions) ++ self.timedout_minions = self.timedout_minions.union(timedout_minions) ++ self.active = self.active.difference(self.timedout_minions) ++ running = batch_minions.difference(self.done_minions).difference( ++ self.timedout_minions ++ ) ++ ++ if timedout_minions: ++ self.schedule_next() ++ ++ if self.event and running: ++ self.find_job_returned = self.find_job_returned.difference(running) ++ self.event.io_loop.spawn_callback(self.find_job, running) ++ ++ @salt.ext.tornado.gen.coroutine ++ def find_job(self, minions): ++ if self.event: ++ not_done = minions.difference(self.done_minions).difference( ++ self.timedout_minions ++ ) ++ try: ++ if not_done: ++ jid = self.jid_gen() ++ find_job_return_pattern = "salt/job/{}/ret/*".format(jid) ++ self.patterns.add((find_job_return_pattern, "find_job_return")) ++ self.event.subscribe(find_job_return_pattern, match_type="glob") ++ ret = yield self.local.run_job_async( ++ not_done, ++ "saltutil.find_job", ++ [self.batch_jid], ++ "list", ++ gather_job_timeout=self.opts["gather_job_timeout"], ++ jid=jid, ++ **self.eauth ++ ) ++ yield salt.ext.tornado.gen.sleep(self.opts["gather_job_timeout"]) ++ if self.event: ++ self.event.io_loop.spawn_callback( ++ self.check_find_job, not_done, jid ++ ) ++ except Exception as ex: ++ log.error( ++ "Exception occured handling batch async: {}. Aborting execution.".format( ++ ex ++ ) ++ ) ++ self.close_safe() ++ ++ @salt.ext.tornado.gen.coroutine ++ def start(self): ++ if self.event: ++ self.__set_event_handler() ++ ping_return = yield self.local.run_job_async( ++ self.opts["tgt"], ++ "test.ping", ++ [], ++ self.opts.get( ++ "selected_target_option", self.opts.get("tgt_type", "glob") ++ ), ++ gather_job_timeout=self.opts["gather_job_timeout"], ++ jid=self.ping_jid, ++ metadata=self.metadata, ++ **self.eauth ++ ) ++ self.targeted_minions = set(ping_return["minions"]) ++ # start batching even if not all minions respond to ping ++ yield salt.ext.tornado.gen.sleep( ++ self.batch_presence_ping_timeout or self.opts["gather_job_timeout"] ++ ) ++ if self.event: ++ self.event.io_loop.spawn_callback(self.start_batch) ++ ++ @salt.ext.tornado.gen.coroutine ++ def start_batch(self): ++ if not self.initialized: ++ self.batch_size = get_bnum(self.opts, self.minions, True) ++ self.initialized = True ++ data = { ++ "available_minions": self.minions, ++ "down_minions": self.targeted_minions.difference(self.minions), ++ "metadata": self.metadata, ++ } ++ ret = self.event.fire_event( ++ data, "salt/batch/{}/start".format(self.batch_jid) ++ ) ++ if self.event: ++ self.event.io_loop.spawn_callback(self.run_next) ++ ++ @salt.ext.tornado.gen.coroutine ++ def end_batch(self): ++ left = self.minions.symmetric_difference( ++ self.done_minions.union(self.timedout_minions) ++ ) ++ if not left and not self.ended: ++ self.ended = True ++ data = { ++ "available_minions": self.minions, ++ "down_minions": self.targeted_minions.difference(self.minions), ++ "done_minions": self.done_minions, ++ "timedout_minions": self.timedout_minions, ++ "metadata": self.metadata, ++ } ++ self.event.fire_event(data, "salt/batch/{}/done".format(self.batch_jid)) ++ ++ # release to the IOLoop to allow the event to be published ++ # before closing batch async execution ++ yield salt.ext.tornado.gen.sleep(1) ++ self.close_safe() ++ ++ def close_safe(self): ++ for (pattern, label) in self.patterns: ++ self.event.unsubscribe(pattern, match_type="glob") ++ self.event.remove_event_handler(self.__event_handler) ++ self.event = None ++ self.local = None ++ self.ioloop = None ++ del self ++ gc.collect() ++ ++ @salt.ext.tornado.gen.coroutine ++ def schedule_next(self): ++ if not self.scheduled: ++ self.scheduled = True ++ # call later so that we maybe gather more returns ++ yield salt.ext.tornado.gen.sleep(self.batch_delay) ++ if self.event: ++ self.event.io_loop.spawn_callback(self.run_next) ++ ++ @salt.ext.tornado.gen.coroutine ++ def run_next(self): ++ self.scheduled = False ++ next_batch = self._get_next() ++ if next_batch: ++ self.active = self.active.union(next_batch) ++ try: ++ ret = yield self.local.run_job_async( ++ next_batch, ++ self.opts["fun"], ++ self.opts["arg"], ++ "list", ++ raw=self.opts.get("raw", False), ++ ret=self.opts.get("return", ""), ++ gather_job_timeout=self.opts["gather_job_timeout"], ++ jid=self.batch_jid, ++ metadata=self.metadata, ++ ) ++ ++ yield salt.ext.tornado.gen.sleep(self.opts["timeout"]) ++ ++ # The batch can be done already at this point, which means no self.event ++ if self.event: ++ self.event.io_loop.spawn_callback(self.find_job, set(next_batch)) ++ except Exception as ex: ++ log.error("Error in scheduling next batch: %s. Aborting execution", ex) ++ self.active = self.active.difference(next_batch) ++ self.close_safe() ++ else: ++ yield self.end_batch() ++ gc.collect() ++ ++ def __del__(self): ++ self.local = None ++ self.event = None ++ self.ioloop = None ++ gc.collect() +diff --git a/salt/cli/support/profiles/__init__.py b/salt/cli/support/profiles/__init__.py +index b86aef30b8..4ae6d07b13 100644 +--- a/salt/cli/support/profiles/__init__.py ++++ b/salt/cli/support/profiles/__init__.py +@@ -1,4 +1,3 @@ +-# coding=utf-8 +-''' ++""" + Profiles for salt-support. +-''' ++""" +diff --git a/salt/client/__init__.py b/salt/client/__init__.py +index 7ce8963b8f..bcda56c9b4 100644 +--- a/salt/client/__init__.py ++++ b/salt/client/__init__.py +@@ -594,38 +594,20 @@ class LocalClient: + import salt.cli.batch + import salt.utils.args + +- arg = salt.utils.args.condition_input(arg, kwarg) +- opts = { +- "tgt": tgt, +- "fun": fun, +- "arg": arg, +- "tgt_type": tgt_type, +- "ret": ret, +- "batch": batch, +- "failhard": kwargs.get("failhard", self.opts.get("failhard", False)), +- "raw": kwargs.get("raw", False), +- } ++ opts = salt.cli.batch.batch_get_opts( ++ tgt, ++ fun, ++ batch, ++ self.opts, ++ arg=arg, ++ tgt_type=tgt_type, ++ ret=ret, ++ kwarg=kwarg, ++ **kwargs ++ ) ++ ++ eauth = salt.cli.batch.batch_get_eauth(kwargs) + +- if "timeout" in kwargs: +- opts["timeout"] = kwargs["timeout"] +- if "gather_job_timeout" in kwargs: +- opts["gather_job_timeout"] = kwargs["gather_job_timeout"] +- if "batch_wait" in kwargs: +- opts["batch_wait"] = int(kwargs["batch_wait"]) +- +- eauth = {} +- if "eauth" in kwargs: +- eauth["eauth"] = kwargs.pop("eauth") +- if "username" in kwargs: +- eauth["username"] = kwargs.pop("username") +- if "password" in kwargs: +- eauth["password"] = kwargs.pop("password") +- if "token" in kwargs: +- eauth["token"] = kwargs.pop("token") +- +- for key, val in self.opts.items(): +- if key not in opts: +- opts[key] = val + batch = salt.cli.batch.Batch(opts, eauth=eauth, quiet=True) + for ret, _ in batch.run(): + yield ret +@@ -1826,6 +1808,7 @@ class LocalClient: + "key": self.key, + "tgt_type": tgt_type, + "ret": ret, ++ "timeout": timeout, + "jid": jid, + } + +diff --git a/salt/master.py b/salt/master.py +index 9d2239bffb..2a526b4f21 100644 +--- a/salt/master.py ++++ b/salt/master.py +@@ -19,6 +19,7 @@ import time + import salt.acl + import salt.auth + import salt.channel.server ++import salt.cli.batch_async + import salt.client + import salt.client.ssh.client + import salt.crypt +@@ -2153,6 +2154,22 @@ class ClearFuncs(TransportMethods): + return False + return self.loadauth.get_tok(clear_load["token"]) + ++ def publish_batch(self, clear_load, minions, missing): ++ batch_load = {} ++ batch_load.update(clear_load) ++ batch = salt.cli.batch_async.BatchAsync( ++ self.local.opts, ++ functools.partial(self._prep_jid, clear_load, {}), ++ batch_load, ++ ) ++ ioloop = salt.ext.tornado.ioloop.IOLoop.current() ++ ioloop.add_callback(batch.start) ++ ++ return { ++ "enc": "clear", ++ "load": {"jid": batch.batch_jid, "minions": minions, "missing": missing}, ++ } ++ + def publish(self, clear_load): + """ + This method sends out publications to the minions, it can only be used +@@ -2297,6 +2314,9 @@ class ClearFuncs(TransportMethods): + ), + }, + } ++ if extra.get("batch", None): ++ return self.publish_batch(clear_load, minions, missing) ++ + jid = self._prep_jid(clear_load, extra) + if jid is None: + return {"enc": "clear", "load": {"error": "Master failed to assign jid"}} +diff --git a/salt/transport/ipc.py b/salt/transport/ipc.py +index ca13a498e3..3a3f0c7a5f 100644 +--- a/salt/transport/ipc.py ++++ b/salt/transport/ipc.py +@@ -659,6 +659,7 @@ class IPCMessageSubscriber(IPCClient): + self._read_stream_future = None + self._saved_data = [] + self._read_in_progress = Lock() ++ self.callbacks = set() + + @salt.ext.tornado.gen.coroutine + def _read(self, timeout, callback=None): +@@ -764,8 +765,12 @@ class IPCMessageSubscriber(IPCClient): + return self._saved_data.pop(0) + return self.io_loop.run_sync(lambda: self._read(timeout)) + ++ def __run_callbacks(self, raw): ++ for callback in self.callbacks: ++ self.io_loop.spawn_callback(callback, raw) ++ + @salt.ext.tornado.gen.coroutine +- def read_async(self, callback): ++ def read_async(self): + """ + Asynchronously read messages and invoke a callback when they are ready. + +@@ -783,7 +788,7 @@ class IPCMessageSubscriber(IPCClient): + except Exception as exc: # pylint: disable=broad-except + log.error("Exception occurred while Subscriber connecting: %s", exc) + yield salt.ext.tornado.gen.sleep(1) +- yield self._read(None, callback) ++ yield self._read(None, self.__run_callbacks) + + def close(self): + """ +diff --git a/salt/utils/event.py b/salt/utils/event.py +index a07ad513b1..869e12a140 100644 +--- a/salt/utils/event.py ++++ b/salt/utils/event.py +@@ -946,6 +946,10 @@ class SaltEvent: + # Minion fired a bad retcode, fire an event + self._fire_ret_load_specific_fun(load) + ++ def remove_event_handler(self, event_handler): ++ if event_handler in self.subscriber.callbacks: ++ self.subscriber.callbacks.remove(event_handler) ++ + def set_event_handler(self, event_handler): + """ + Invoke the event_handler callback each time an event arrives. +@@ -954,8 +958,10 @@ class SaltEvent: + + if not self.cpub: + self.connect_pub() ++ ++ self.subscriber.callbacks.add(event_handler) + # This will handle reconnects +- return self.subscriber.read_async(event_handler) ++ return self.subscriber.read_async() + + # pylint: disable=W1701 + def __del__(self): +diff --git a/tests/pytests/unit/cli/test_batch_async.py b/tests/pytests/unit/cli/test_batch_async.py +new file mode 100644 +index 0000000000..c0b708de76 +--- /dev/null ++++ b/tests/pytests/unit/cli/test_batch_async.py +@@ -0,0 +1,386 @@ ++import salt.ext.tornado ++from salt.cli.batch_async import BatchAsync ++from salt.ext.tornado.testing import AsyncTestCase ++from tests.support.mock import MagicMock, patch ++from tests.support.unit import TestCase, skipIf ++ ++ ++class AsyncBatchTestCase(AsyncTestCase, TestCase): ++ def setUp(self): ++ self.io_loop = self.get_new_ioloop() ++ opts = { ++ "batch": "1", ++ "conf_file": {}, ++ "tgt": "*", ++ "timeout": 5, ++ "gather_job_timeout": 5, ++ "batch_presence_ping_timeout": 1, ++ "transport": None, ++ "sock_dir": "", ++ } ++ ++ with patch("salt.client.get_local_client", MagicMock(return_value=MagicMock())): ++ with patch( ++ "salt.cli.batch_async.batch_get_opts", MagicMock(return_value=opts) ++ ): ++ self.batch = BatchAsync( ++ opts, ++ MagicMock(side_effect=["1234", "1235", "1236"]), ++ { ++ "tgt": "", ++ "fun": "", ++ "kwargs": {"batch": "", "batch_presence_ping_timeout": 1}, ++ }, ++ ) ++ ++ def test_ping_jid(self): ++ self.assertEqual(self.batch.ping_jid, "1234") ++ ++ def test_batch_jid(self): ++ self.assertEqual(self.batch.batch_jid, "1235") ++ ++ def test_find_job_jid(self): ++ self.assertEqual(self.batch.find_job_jid, "1236") ++ ++ def test_batch_size(self): ++ """ ++ Tests passing batch value as a number ++ """ ++ self.batch.opts = {"batch": "2", "timeout": 5} ++ self.batch.minions = {"foo", "bar"} ++ self.batch.start_batch() ++ self.assertEqual(self.batch.batch_size, 2) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_batch_start_on_batch_presence_ping_timeout(self): ++ self.batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({"minions": ["foo", "bar"]}) ++ self.batch.local.run_job_async.return_value = future ++ ret = self.batch.start() ++ # assert start_batch is called later with batch_presence_ping_timeout as param ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.start_batch,), ++ ) ++ # assert test.ping called ++ self.assertEqual( ++ self.batch.local.run_job_async.call_args[0], ("*", "test.ping", [], "glob") ++ ) ++ # assert targeted_minions == all minions matched by tgt ++ self.assertEqual(self.batch.targeted_minions, {"foo", "bar"}) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_batch_start_on_gather_job_timeout(self): ++ self.batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({"minions": ["foo", "bar"]}) ++ self.batch.local.run_job_async.return_value = future ++ self.batch.batch_presence_ping_timeout = None ++ ret = self.batch.start() ++ # assert start_batch is called later with gather_job_timeout as param ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.start_batch,), ++ ) ++ ++ def test_batch_fire_start_event(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.opts = {"batch": "2", "timeout": 5} ++ self.batch.event = MagicMock() ++ self.batch.metadata = {"mykey": "myvalue"} ++ self.batch.start_batch() ++ self.assertEqual( ++ self.batch.event.fire_event.call_args[0], ++ ( ++ { ++ "available_minions": {"foo", "bar"}, ++ "down_minions": set(), ++ "metadata": self.batch.metadata, ++ }, ++ "salt/batch/1235/start", ++ ), ++ ) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_start_batch_calls_next(self): ++ self.batch.run_next = MagicMock(return_value=MagicMock()) ++ self.batch.event = MagicMock() ++ self.batch.start_batch() ++ self.assertEqual(self.batch.initialized, True) ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], (self.batch.run_next,) ++ ) ++ ++ def test_batch_fire_done_event(self): ++ self.batch.targeted_minions = {"foo", "baz", "bar"} ++ self.batch.minions = {"foo", "bar"} ++ self.batch.done_minions = {"foo"} ++ self.batch.timedout_minions = {"bar"} ++ self.batch.event = MagicMock() ++ self.batch.metadata = {"mykey": "myvalue"} ++ old_event = self.batch.event ++ self.batch.end_batch() ++ self.assertEqual( ++ old_event.fire_event.call_args[0], ++ ( ++ { ++ "available_minions": {"foo", "bar"}, ++ "done_minions": self.batch.done_minions, ++ "down_minions": {"baz"}, ++ "timedout_minions": self.batch.timedout_minions, ++ "metadata": self.batch.metadata, ++ }, ++ "salt/batch/1235/done", ++ ), ++ ) ++ ++ def test_batch__del__(self): ++ batch = BatchAsync(MagicMock(), MagicMock(), MagicMock()) ++ event = MagicMock() ++ batch.event = event ++ batch.__del__() ++ self.assertEqual(batch.local, None) ++ self.assertEqual(batch.event, None) ++ self.assertEqual(batch.ioloop, None) ++ ++ def test_batch_close_safe(self): ++ batch = BatchAsync(MagicMock(), MagicMock(), MagicMock()) ++ event = MagicMock() ++ batch.event = event ++ batch.patterns = { ++ ("salt/job/1234/ret/*", "find_job_return"), ++ ("salt/job/4321/ret/*", "find_job_return"), ++ } ++ batch.close_safe() ++ self.assertEqual(batch.local, None) ++ self.assertEqual(batch.event, None) ++ self.assertEqual(batch.ioloop, None) ++ self.assertEqual(len(event.unsubscribe.mock_calls), 2) ++ self.assertEqual(len(event.remove_event_handler.mock_calls), 1) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_batch_next(self): ++ self.batch.event = MagicMock() ++ self.batch.opts["fun"] = "my.fun" ++ self.batch.opts["arg"] = [] ++ self.batch._get_next = MagicMock(return_value={"foo", "bar"}) ++ self.batch.batch_size = 2 ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({"minions": ["foo", "bar"]}) ++ self.batch.local.run_job_async.return_value = future ++ self.batch.run_next() ++ self.assertEqual( ++ self.batch.local.run_job_async.call_args[0], ++ ({"foo", "bar"}, "my.fun", [], "list"), ++ ) ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.find_job, {"foo", "bar"}), ++ ) ++ self.assertEqual(self.batch.active, {"bar", "foo"}) ++ ++ def test_next_batch(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), {"foo", "bar"}) ++ ++ def test_next_batch_one_done(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.done_minions = {"bar"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), {"foo"}) ++ ++ def test_next_batch_one_done_one_active(self): ++ self.batch.minions = {"foo", "bar", "baz"} ++ self.batch.done_minions = {"bar"} ++ self.batch.active = {"baz"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), {"foo"}) ++ ++ def test_next_batch_one_done_one_active_one_timedout(self): ++ self.batch.minions = {"foo", "bar", "baz", "faz"} ++ self.batch.done_minions = {"bar"} ++ self.batch.active = {"baz"} ++ self.batch.timedout_minions = {"faz"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), {"foo"}) ++ ++ def test_next_batch_bigger_size(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.batch_size = 3 ++ self.assertEqual(self.batch._get_next(), {"foo", "bar"}) ++ ++ def test_next_batch_all_done(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.done_minions = {"foo", "bar"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), set()) ++ ++ def test_next_batch_all_active(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.active = {"foo", "bar"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), set()) ++ ++ def test_next_batch_all_timedout(self): ++ self.batch.minions = {"foo", "bar"} ++ self.batch.timedout_minions = {"foo", "bar"} ++ self.batch.batch_size = 2 ++ self.assertEqual(self.batch._get_next(), set()) ++ ++ def test_batch__event_handler_ping_return(self): ++ self.batch.targeted_minions = {"foo"} ++ self.batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) ++ ) ++ self.batch.start() ++ self.assertEqual(self.batch.minions, set()) ++ self.batch._BatchAsync__event_handler(MagicMock()) ++ self.assertEqual(self.batch.minions, {"foo"}) ++ self.assertEqual(self.batch.done_minions, set()) ++ ++ def test_batch__event_handler_call_start_batch_when_all_pings_return(self): ++ self.batch.targeted_minions = {"foo"} ++ self.batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) ++ ) ++ self.batch.start() ++ self.batch._BatchAsync__event_handler(MagicMock()) ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.start_batch,), ++ ) ++ ++ def test_batch__event_handler_not_call_start_batch_when_not_all_pings_return(self): ++ self.batch.targeted_minions = {"foo", "bar"} ++ self.batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) ++ ) ++ self.batch.start() ++ self.batch._BatchAsync__event_handler(MagicMock()) ++ self.assertEqual(len(self.batch.event.io_loop.spawn_callback.mock_calls), 0) ++ ++ def test_batch__event_handler_batch_run_return(self): ++ self.batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1235/ret/foo", {"id": "foo"})) ++ ) ++ self.batch.start() ++ self.batch.active = {"foo"} ++ self.batch._BatchAsync__event_handler(MagicMock()) ++ self.assertEqual(self.batch.active, set()) ++ self.assertEqual(self.batch.done_minions, {"foo"}) ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.schedule_next,), ++ ) ++ ++ def test_batch__event_handler_find_job_return(self): ++ self.batch.event = MagicMock( ++ unpack=MagicMock( ++ return_value=( ++ "salt/job/1236/ret/foo", ++ {"id": "foo", "return": "deadbeaf"}, ++ ) ++ ) ++ ) ++ self.batch.start() ++ self.batch.patterns.add(("salt/job/1236/ret/*", "find_job_return")) ++ self.batch._BatchAsync__event_handler(MagicMock()) ++ self.assertEqual(self.batch.find_job_returned, {"foo"}) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_batch_run_next_end_batch_when_no_next(self): ++ self.batch.end_batch = MagicMock() ++ self.batch._get_next = MagicMock(return_value={}) ++ self.batch.run_next() ++ self.assertEqual(len(self.batch.end_batch.mock_calls), 1) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_batch_find_job(self): ++ self.batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({}) ++ self.batch.local.run_job_async.return_value = future ++ self.batch.minions = {"foo", "bar"} ++ self.batch.jid_gen = MagicMock(return_value="1234") ++ salt.ext.tornado.gen.sleep = MagicMock(return_value=future) ++ self.batch.find_job({"foo", "bar"}) ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.check_find_job, {"foo", "bar"}, "1234"), ++ ) ++ ++ @salt.ext.tornado.testing.gen_test ++ def test_batch_find_job_with_done_minions(self): ++ self.batch.done_minions = {"bar"} ++ self.batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({}) ++ self.batch.local.run_job_async.return_value = future ++ self.batch.minions = {"foo", "bar"} ++ self.batch.jid_gen = MagicMock(return_value="1234") ++ salt.ext.tornado.gen.sleep = MagicMock(return_value=future) ++ self.batch.find_job({"foo", "bar"}) ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.check_find_job, {"foo"}, "1234"), ++ ) ++ ++ def test_batch_check_find_job_did_not_return(self): ++ self.batch.event = MagicMock() ++ self.batch.active = {"foo"} ++ self.batch.find_job_returned = set() ++ self.batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} ++ self.batch.check_find_job({"foo"}, jid="1234") ++ self.assertEqual(self.batch.find_job_returned, set()) ++ self.assertEqual(self.batch.active, set()) ++ self.assertEqual(len(self.batch.event.io_loop.add_callback.mock_calls), 0) ++ ++ def test_batch_check_find_job_did_return(self): ++ self.batch.event = MagicMock() ++ self.batch.find_job_returned = {"foo"} ++ self.batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} ++ self.batch.check_find_job({"foo"}, jid="1234") ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.find_job, {"foo"}), ++ ) ++ ++ def test_batch_check_find_job_multiple_states(self): ++ self.batch.event = MagicMock() ++ # currently running minions ++ self.batch.active = {"foo", "bar"} ++ ++ # minion is running and find_job returns ++ self.batch.find_job_returned = {"foo"} ++ ++ # minion started running but find_job did not return ++ self.batch.timedout_minions = {"faz"} ++ ++ # minion finished ++ self.batch.done_minions = {"baz"} ++ ++ # both not yet done but only 'foo' responded to find_job ++ not_done = {"foo", "bar"} ++ ++ self.batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} ++ self.batch.check_find_job(not_done, jid="1234") ++ ++ # assert 'bar' removed from active ++ self.assertEqual(self.batch.active, {"foo"}) ++ ++ # assert 'bar' added to timedout_minions ++ self.assertEqual(self.batch.timedout_minions, {"bar", "faz"}) ++ ++ # assert 'find_job' schedueled again only for 'foo' ++ self.assertEqual( ++ self.batch.event.io_loop.spawn_callback.call_args[0], ++ (self.batch.find_job, {"foo"}), ++ ) ++ ++ def test_only_on_run_next_is_scheduled(self): ++ self.batch.event = MagicMock() ++ self.batch.scheduled = True ++ self.batch.schedule_next() ++ self.assertEqual(len(self.batch.event.io_loop.spawn_callback.mock_calls), 0) +-- +2.39.2 + + 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..ec8160f --- /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/avoid-excessive-syslogging-by-watchdog-cronjob-58.patch b/avoid-excessive-syslogging-by-watchdog-cronjob-58.patch new file mode 100644 index 0000000..9200230 --- /dev/null +++ b/avoid-excessive-syslogging-by-watchdog-cronjob-58.patch @@ -0,0 +1,26 @@ +From 4d8c88d6e467c22ea74738743de5be6577f81085 Mon Sep 17 00:00:00 2001 +From: Hubert Mantel +Date: Mon, 27 Nov 2017 13:55:13 +0100 +Subject: [PATCH] avoid excessive syslogging by watchdog cronjob (#58) + +--- + pkg/old/suse/salt-minion | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/pkg/old/suse/salt-minion b/pkg/old/suse/salt-minion +index 2e418094ed..73a91ebd62 100755 +--- a/pkg/old/suse/salt-minion ++++ b/pkg/old/suse/salt-minion +@@ -55,7 +55,7 @@ WATCHDOG_CRON="/etc/cron.d/salt-minion" + + set_watchdog() { + if [ ! -f $WATCHDOG_CRON ]; then +- echo -e '* * * * * root /usr/bin/salt-daemon-watcher --with-init\n' > $WATCHDOG_CRON ++ echo -e '-* * * * * root /usr/bin/salt-daemon-watcher --with-init\n' > $WATCHDOG_CRON + # Kick the watcher for 1 minute immediately, because cron will wake up only afterwards + /usr/bin/salt-daemon-watcher --with-init & disown + fi +-- +2.39.2 + + diff --git a/bsc-1176024-fix-file-directory-user-and-group-owners.patch b/bsc-1176024-fix-file-directory-user-and-group-owners.patch new file mode 100644 index 0000000..6318b90 --- /dev/null +++ b/bsc-1176024-fix-file-directory-user-and-group-owners.patch @@ -0,0 +1,112 @@ +From 2ca37fe7d2a03ad86ed738f2636fe240b9f4467e Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Tue, 6 Oct 2020 12:36:41 +0300 +Subject: [PATCH] bsc#1176024: Fix file/directory user and group + ownership containing UTF-8 characters (#275) + +* Fix check_perm typos of file module + +* Fix UTF8 support for user/group ownership operations with file module and state + +* Fix UTF8 support for user/group ownership operations with file module and state + +Co-authored-by: Victor Zhestkov +--- + salt/modules/file.py | 20 ++++++++++---------- + salt/states/file.py | 12 ++++++++++-- + 2 files changed, 20 insertions(+), 12 deletions(-) + +diff --git a/salt/modules/file.py b/salt/modules/file.py +index 69d7992f5a..4612d65511 100644 +--- a/salt/modules/file.py ++++ b/salt/modules/file.py +@@ -245,7 +245,7 @@ def group_to_gid(group): + try: + if isinstance(group, int): + return group +- return grp.getgrnam(group).gr_gid ++ return grp.getgrnam(salt.utils.stringutils.to_str(group)).gr_gid + except KeyError: + return "" + +@@ -336,7 +336,7 @@ def user_to_uid(user): + try: + if isinstance(user, int): + return user +- return pwd.getpwnam(user).pw_uid ++ return pwd.getpwnam(salt.utils.stringutils.to_str(user)).pw_uid + except KeyError: + return "" + +@@ -5133,8 +5133,8 @@ def check_perms( + salt.utils.platform.is_windows() and not user_to_uid(user) == cur["uid"] + ) or ( + not salt.utils.platform.is_windows() +- and not user == cur["user"] +- and not user == cur["uid"] ++ and not salt.utils.stringutils.to_str(user) == cur["user"] ++ and not salt.utils.stringutils.to_str(user) == cur["uid"] + ): + perms["cuser"] = user + +@@ -5143,8 +5143,8 @@ def check_perms( + salt.utils.platform.is_windows() and not group_to_gid(group) == cur["gid"] + ) or ( + not salt.utils.platform.is_windows() +- and not group == cur["group"] +- and not group == cur["gid"] ++ and not salt.utils.stringutils.to_str(group) == cur["group"] ++ and not salt.utils.stringutils.to_str(group) == cur["gid"] + ): + perms["cgroup"] = group + +@@ -5188,8 +5188,8 @@ def check_perms( + salt.utils.platform.is_windows() and not user_to_uid(user) == post["uid"] + ) or ( + not salt.utils.platform.is_windows() +- and not user == post["user"] +- and not user == post["uid"] ++ and not salt.utils.stringutils.to_str(user) == post["user"] ++ and not salt.utils.stringutils.to_str(user) == post["uid"] + ): + if __opts__["test"] is True: + ret["changes"]["user"] = user +@@ -5204,8 +5204,8 @@ def check_perms( + salt.utils.platform.is_windows() and not group_to_gid(group) == post["gid"] + ) or ( + not salt.utils.platform.is_windows() +- and not group == post["group"] +- and not group == post["gid"] ++ and not salt.utils.stringutils.to_str(group) == post["group"] ++ and not salt.utils.stringutils.to_str(group) == post["gid"] + ): + if __opts__["test"] is True: + ret["changes"]["group"] = group +diff --git a/salt/states/file.py b/salt/states/file.py +index 9f32151b8b..024e5e34ce 100644 +--- a/salt/states/file.py ++++ b/salt/states/file.py +@@ -864,9 +864,17 @@ def _check_dir_meta(name, user, group, mode, follow_symlinks=False): + if not stats: + changes["directory"] = "new" + return changes +- if user is not None and user != stats["user"] and user != stats.get("uid"): ++ if ( ++ user is not None ++ and salt.utils.stringutils.to_str(user) != stats["user"] ++ and user != stats.get("uid") ++ ): + changes["user"] = user +- if group is not None and group != stats["group"] and group != stats.get("gid"): ++ if ( ++ group is not None ++ and salt.utils.stringutils.to_str(group) != stats["group"] ++ and group != stats.get("gid") ++ ): + changes["group"] = group + # Normalize the dir mode + smode = salt.utils.files.normalize_mode(stats["mode"]) +-- +2.39.2 + + diff --git a/change-the-delimeters-to-prevent-possible-tracebacks.patch b/change-the-delimeters-to-prevent-possible-tracebacks.patch new file mode 100644 index 0000000..ca7ec19 --- /dev/null +++ b/change-the-delimeters-to-prevent-possible-tracebacks.patch @@ -0,0 +1,30 @@ +From b7a554e2dec3351c91c237497fe37cbc30d664bd Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Thu, 1 Sep 2022 14:42:24 +0300 +Subject: [PATCH] Change the delimeters to prevent possible tracebacks on + some packages with dpkg_lowpkg + +* Use another separator on query to dpkg-query + +* Fix the test test_dpkg_lowpkg::test_info +--- + salt/modules/dpkg_lowpkg.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/modules/dpkg_lowpkg.py b/salt/modules/dpkg_lowpkg.py +index 4d716c8772..78990492cf 100644 +--- a/salt/modules/dpkg_lowpkg.py ++++ b/salt/modules/dpkg_lowpkg.py +@@ -347,7 +347,7 @@ def _get_pkg_info(*packages, **kwargs): + if build_date: + pkg_data["build_date"] = build_date + pkg_data["build_date_time_t"] = build_date_t +- pkg_data["description"] = pkg_descr.split(":", 1)[-1] ++ pkg_data["description"] = pkg_descr + ret.append(pkg_data) + + return ret +-- +2.39.2 + + diff --git a/control-the-collection-of-lvm-grains-via-config.patch b/control-the-collection-of-lvm-grains-via-config.patch new file mode 100644 index 0000000..e857498 --- /dev/null +++ b/control-the-collection-of-lvm-grains-via-config.patch @@ -0,0 +1,37 @@ +From fcb43735942ca1b796f656d5647e49a93f770bb2 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 10 Jan 2023 15:04:01 +0100 +Subject: [PATCH] Control the collection of lvm grains via config + +lvm grain collection can take a long time on systems with a lot of +volumes and volume groups. On one server we measured ~3 minutes, which +is way too long for grains. + +This change is backwards-compatible, leaving the lvm grain collection +enabled by default. Users with a lot of lvm volumes/volume groups can +disable these grains in the minion config by setting + + enable_lvm_grains: False +--- + salt/grains/lvm.py | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/salt/grains/lvm.py b/salt/grains/lvm.py +index 586b187ddb..f5c406cb44 100644 +--- a/salt/grains/lvm.py ++++ b/salt/grains/lvm.py +@@ -17,6 +17,10 @@ __salt__ = { + log = logging.getLogger(__name__) + + ++def __virtual__(): ++ return __opts__.get("enable_lvm_grains", True) ++ ++ + def lvm(): + """ + Return list of LVM devices +-- +2.39.2 + + diff --git a/debian-info_installed-compatibility-50453.patch b/debian-info_installed-compatibility-50453.patch new file mode 100644 index 0000000..417454b --- /dev/null +++ b/debian-info_installed-compatibility-50453.patch @@ -0,0 +1,351 @@ +From 2fbc5b580661b094cf79cc5da0860745b72088e4 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 25 Jan 2022 17:08:57 +0100 +Subject: [PATCH] Debian info_installed compatibility (#50453) + +Remove unused variable + +Get unit ticks installation time + +Pass on unix ticks installation date time + +Implement function to figure out package build time + +Unify arch attribute + +Add 'attr' support. + +Use attr parameter in aptpkg + +Add 'all_versions' output structure backward compatibility + +Fix docstring + +Add UT for generic test of function 'info' + +Add UT for 'info' function with the parameter 'attr' + +Add UT for info_installed's 'attr' param + +Fix docstring + +Add returned type check + +Add UT for info_installed with 'all_versions=True' output structure + +Refactor UT for 'owner' function + +Refactor UT: move to decorators, add more checks + +Schedule TODO for next refactoring of UT 'show' function + +Refactor UT: get rid of old assertion way, flatten tests + +Refactor UT: move to native assertions, cleanup noise, flatten complexity for better visibility what is tested + +Lintfix: too many empty lines + +Adjust architecture getter according to the lowpkg info + +Fix wrong Git merge: missing function signature + +Reintroducing reverted changes + +Reintroducing changes from commit e20362f6f053eaa4144583604e6aac3d62838419 +that got partially reverted by this commit: +https://github.com/openSUSE/salt/commit/d0ef24d113bdaaa29f180031b5da384cffe08c64#diff-820e6ce667fe3afddbc1b9cf1682fdef +--- + salt/modules/aptpkg.py | 24 ++++- + salt/modules/dpkg_lowpkg.py | 110 ++++++++++++++++++---- + tests/pytests/unit/modules/test_aptpkg.py | 52 ++++++++++ + 3 files changed, 167 insertions(+), 19 deletions(-) + +diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py +index 8e89744b5e..938e37cc9e 100644 +--- a/salt/modules/aptpkg.py ++++ b/salt/modules/aptpkg.py +@@ -3440,6 +3440,15 @@ def info_installed(*names, **kwargs): + + .. versionadded:: 2016.11.3 + ++ attr ++ Comma-separated package attributes. If no 'attr' is specified, all available attributes returned. ++ ++ Valid attributes are: ++ version, vendor, release, build_date, build_date_time_t, install_date, install_date_time_t, ++ build_host, group, source_rpm, arch, epoch, size, license, signature, packager, url, summary, description. ++ ++ .. versionadded:: Neon ++ + CLI Example: + + .. code-block:: bash +@@ -3450,11 +3459,19 @@ def info_installed(*names, **kwargs): + """ + kwargs = salt.utils.args.clean_kwargs(**kwargs) + failhard = kwargs.pop("failhard", True) ++ kwargs.pop("errors", None) # Only for compatibility with RPM ++ attr = kwargs.pop("attr", None) # Package attributes to return ++ all_versions = kwargs.pop( ++ "all_versions", False ++ ) # This is for backward compatible structure only ++ + if kwargs: + salt.utils.args.invalid_kwargs(kwargs) + + ret = dict() +- for pkg_name, pkg_nfo in __salt__["lowpkg.info"](*names, failhard=failhard).items(): ++ for pkg_name, pkg_nfo in __salt__["lowpkg.info"]( ++ *names, failhard=failhard, attr=attr ++ ).items(): + t_nfo = dict() + if pkg_nfo.get("status", "ii")[1] != "i": + continue # return only packages that are really installed +@@ -3475,7 +3492,10 @@ def info_installed(*names, **kwargs): + else: + t_nfo[key] = value + +- ret[pkg_name] = t_nfo ++ if all_versions: ++ ret.setdefault(pkg_name, []).append(t_nfo) ++ else: ++ ret[pkg_name] = t_nfo + + return ret + +diff --git a/salt/modules/dpkg_lowpkg.py b/salt/modules/dpkg_lowpkg.py +index eefd852c51..4d716c8772 100644 +--- a/salt/modules/dpkg_lowpkg.py ++++ b/salt/modules/dpkg_lowpkg.py +@@ -234,6 +234,44 @@ def file_dict(*packages, **kwargs): + return {"errors": errors, "packages": ret} + + ++def _get_pkg_build_time(name): ++ """ ++ Get package build time, if possible. ++ ++ :param name: ++ :return: ++ """ ++ iso_time = iso_time_t = None ++ changelog_dir = os.path.join("/usr/share/doc", name) ++ if os.path.exists(changelog_dir): ++ for fname in os.listdir(changelog_dir): ++ try: ++ iso_time_t = int(os.path.getmtime(os.path.join(changelog_dir, fname))) ++ iso_time = ( ++ datetime.datetime.utcfromtimestamp(iso_time_t).isoformat() + "Z" ++ ) ++ break ++ except OSError: ++ pass ++ ++ # Packager doesn't care about Debian standards, therefore Plan B: brute-force it. ++ if not iso_time: ++ for pkg_f_path in __salt__["cmd.run"]( ++ "dpkg-query -L {}".format(name) ++ ).splitlines(): ++ if "changelog" in pkg_f_path.lower() and os.path.exists(pkg_f_path): ++ try: ++ iso_time_t = int(os.path.getmtime(pkg_f_path)) ++ iso_time = ( ++ datetime.datetime.utcfromtimestamp(iso_time_t).isoformat() + "Z" ++ ) ++ break ++ except OSError: ++ pass ++ ++ return iso_time, iso_time_t ++ ++ + def _get_pkg_info(*packages, **kwargs): + """ + Return list of package information. If 'packages' parameter is empty, +@@ -257,7 +295,7 @@ def _get_pkg_info(*packages, **kwargs): + cmd = ( + "dpkg-query -W -f='package:" + bin_var + "\\n" + "revision:${binary:Revision}\\n" +- "architecture:${Architecture}\\n" ++ "arch:${Architecture}\\n" + "maintainer:${Maintainer}\\n" + "summary:${Summary}\\n" + "source:${source:Package}\\n" +@@ -299,10 +337,17 @@ def _get_pkg_info(*packages, **kwargs): + key, value = pkg_info_line.split(":", 1) + if value: + pkg_data[key] = value +- install_date = _get_pkg_install_time(pkg_data.get("package")) +- if install_date: +- pkg_data["install_date"] = install_date +- pkg_data["description"] = pkg_descr ++ install_date, install_date_t = _get_pkg_install_time( ++ pkg_data.get("package"), pkg_data.get("arch") ++ ) ++ if install_date: ++ pkg_data["install_date"] = install_date ++ pkg_data["install_date_time_t"] = install_date_t # Unix ticks ++ build_date, build_date_t = _get_pkg_build_time(pkg_data.get("package")) ++ if build_date: ++ pkg_data["build_date"] = build_date ++ pkg_data["build_date_time_t"] = build_date_t ++ pkg_data["description"] = pkg_descr.split(":", 1)[-1] + ret.append(pkg_data) + + return ret +@@ -327,24 +372,34 @@ def _get_pkg_license(pkg): + return ", ".join(sorted(licenses)) + + +-def _get_pkg_install_time(pkg): ++def _get_pkg_install_time(pkg, arch): + """ + Return package install time, based on the /var/lib/dpkg/info/.list + + :return: + """ +- iso_time = None ++ iso_time = iso_time_t = None ++ loc_root = "/var/lib/dpkg/info" + if pkg is not None: +- location = "/var/lib/dpkg/info/{}.list".format(pkg) +- if os.path.exists(location): +- iso_time = ( +- datetime.datetime.utcfromtimestamp( +- int(os.path.getmtime(location)) +- ).isoformat() +- + "Z" +- ) ++ locations = [] ++ if arch is not None and arch != "all": ++ locations.append(os.path.join(loc_root, "{}:{}.list".format(pkg, arch))) + +- return iso_time ++ locations.append(os.path.join(loc_root, "{}.list".format(pkg))) ++ for location in locations: ++ try: ++ iso_time_t = int(os.path.getmtime(location)) ++ iso_time = ( ++ datetime.datetime.utcfromtimestamp(iso_time_t).isoformat() + "Z" ++ ) ++ break ++ except OSError: ++ pass ++ ++ if iso_time is None: ++ log.debug('Unable to get package installation time for package "%s".', pkg) ++ ++ return iso_time, iso_time_t + + + def _get_pkg_ds_avail(): +@@ -394,6 +449,15 @@ def info(*packages, **kwargs): + + .. versionadded:: 2016.11.3 + ++ attr ++ Comma-separated package attributes. If no 'attr' is specified, all available attributes returned. ++ ++ Valid attributes are: ++ version, vendor, release, build_date, build_date_time_t, install_date, install_date_time_t, ++ build_host, group, source_rpm, arch, epoch, size, license, signature, packager, url, summary, description. ++ ++ .. versionadded:: Neon ++ + CLI Example: + + .. code-block:: bash +@@ -408,6 +472,10 @@ def info(*packages, **kwargs): + + kwargs = salt.utils.args.clean_kwargs(**kwargs) + failhard = kwargs.pop("failhard", True) ++ attr = kwargs.pop("attr", None) or None ++ if attr: ++ attr = attr.split(",") ++ + if kwargs: + salt.utils.args.invalid_kwargs(kwargs) + +@@ -435,6 +503,14 @@ def info(*packages, **kwargs): + lic = _get_pkg_license(pkg["package"]) + if lic: + pkg["license"] = lic +- ret[pkg["package"]] = pkg ++ ++ # Remove keys that aren't in attrs ++ pkg_name = pkg["package"] ++ if attr: ++ for k in list(pkg.keys())[:]: ++ if k not in attr: ++ del pkg[k] ++ ++ ret[pkg_name] = pkg + + return ret +diff --git a/tests/pytests/unit/modules/test_aptpkg.py b/tests/pytests/unit/modules/test_aptpkg.py +index b69402578a..4226957eeb 100644 +--- a/tests/pytests/unit/modules/test_aptpkg.py ++++ b/tests/pytests/unit/modules/test_aptpkg.py +@@ -360,6 +360,58 @@ def test_info_installed(lowpkg_info_var): + assert len(aptpkg.info_installed()) == 1 + + ++def test_info_installed_attr(lowpkg_info_var): ++ """ ++ Test info_installed 'attr'. ++ This doesn't test 'attr' behaviour per se, since the underlying function is in dpkg. ++ The test should simply not raise exceptions for invalid parameter. ++ ++ :return: ++ """ ++ expected_pkg = { ++ "url": "http://www.gnu.org/software/wget/", ++ "packager": "Ubuntu Developers ", ++ "name": "wget", ++ "install_date": "2016-08-30T22:20:15Z", ++ "description": "retrieves files from the web", ++ "version": "1.15-1ubuntu1.14.04.2", ++ "architecture": "amd64", ++ "group": "web", ++ "source": "wget", ++ } ++ mock = MagicMock(return_value=lowpkg_info_var) ++ with patch.dict(aptpkg.__salt__, {"lowpkg.info": mock}): ++ ret = aptpkg.info_installed("wget", attr="foo,bar") ++ assert ret["wget"] == expected_pkg ++ ++ ++def test_info_installed_all_versions(lowpkg_info_var): ++ """ ++ Test info_installed 'all_versions'. ++ Since Debian won't return same name packages with the different names, ++ this should just return different structure, backward compatible with ++ the RPM equivalents. ++ ++ :return: ++ """ ++ expected_pkg = { ++ "url": "http://www.gnu.org/software/wget/", ++ "packager": "Ubuntu Developers ", ++ "name": "wget", ++ "install_date": "2016-08-30T22:20:15Z", ++ "description": "retrieves files from the web", ++ "version": "1.15-1ubuntu1.14.04.2", ++ "architecture": "amd64", ++ "group": "web", ++ "source": "wget", ++ } ++ mock = MagicMock(return_value=lowpkg_info_var) ++ with patch.dict(aptpkg.__salt__, {"lowpkg.info": mock}): ++ ret = aptpkg.info_installed("wget", all_versions=True) ++ assert isinstance(ret, dict) ++ assert ret["wget"] == [expected_pkg] ++ ++ + def test_owner(): + """ + Test - Return the name of the package that owns the file. +-- +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..ee5ceb3 --- /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/dnfnotify-pkgset-plugin-implementation-3002.2-450.patch b/dnfnotify-pkgset-plugin-implementation-3002.2-450.patch new file mode 100644 index 0000000..03d5f96 --- /dev/null +++ b/dnfnotify-pkgset-plugin-implementation-3002.2-450.patch @@ -0,0 +1,130 @@ +From c2a35c0c0aac093d0cc35181c1fda0162e22ac4c Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Mon, 8 Nov 2021 18:09:53 +0300 +Subject: [PATCH] dnfnotify pkgset plugin implementation - 3002.2 (#450) + +* dnfnotify pkgset plugin implementation + +* Fix failing check + +* Add error reporting if not possible to save cookie + +* Try to create dir if not exists + +* Show the exception message instead of file name + +* Fix isort +--- + scripts/suse/dnf/plugins/README.md | 21 +++++++++ + scripts/suse/dnf/plugins/dnfnotify.conf | 2 + + scripts/suse/dnf/plugins/dnfnotify.py | 60 +++++++++++++++++++++++++ + 3 files changed, 83 insertions(+) + create mode 100644 scripts/suse/dnf/plugins/README.md + create mode 100644 scripts/suse/dnf/plugins/dnfnotify.conf + create mode 100644 scripts/suse/dnf/plugins/dnfnotify.py + +diff --git a/scripts/suse/dnf/plugins/README.md b/scripts/suse/dnf/plugins/README.md +new file mode 100644 +index 0000000000..b19428608e +--- /dev/null ++++ b/scripts/suse/dnf/plugins/README.md +@@ -0,0 +1,21 @@ ++## What it is ++ ++Plugin which provides a notification mechanism to Salt, if DNF is ++used outside of it. ++ ++## Installation ++ ++Configuration files are going to: ++ ++ `/etc/dnf/plugins/[name].conf` ++ ++Plugin itself goes to: ++ ++ `%{python_sitelib}/dnf-plugins/[name].py` ++ The path to dnf-plugins directory is Python version dependant. ++ ++## Permissions ++ ++User: root ++Group: root ++Mode: 644 +diff --git a/scripts/suse/dnf/plugins/dnfnotify.conf b/scripts/suse/dnf/plugins/dnfnotify.conf +new file mode 100644 +index 0000000000..e7002aa3e9 +--- /dev/null ++++ b/scripts/suse/dnf/plugins/dnfnotify.conf +@@ -0,0 +1,2 @@ ++[main] ++enabled = 1 +diff --git a/scripts/suse/dnf/plugins/dnfnotify.py b/scripts/suse/dnf/plugins/dnfnotify.py +new file mode 100644 +index 0000000000..6e9df85f71 +--- /dev/null ++++ b/scripts/suse/dnf/plugins/dnfnotify.py +@@ -0,0 +1,60 @@ ++import hashlib ++import os ++ ++import dnf ++from dnfpluginscore import _, logger ++ ++ ++class DnfNotifyPlugin(dnf.Plugin): ++ def __init__(self, base, cli): ++ super().__init__(base, cli) ++ self.base = base ++ self.cookie_file = "/var/cache/salt/minion/rpmdb.cookie" ++ if os.path.exists("/var/lib/rpm/rpmdb.sqlite"): ++ self.rpmdb_file = "/var/lib/rpm/rpmdb.sqlite" ++ else: ++ self.rpmdb_file = "/var/lib/rpm/Packages" ++ ++ def transaction(self): ++ if "SALT_RUNNING" not in os.environ: ++ try: ++ ck_dir = os.path.dirname(self.cookie_file) ++ if not os.path.exists(ck_dir): ++ os.makedirs(ck_dir) ++ with open(self.cookie_file, "w") as ck_fh: ++ ck_fh.write( ++ "{chksum} {mtime}\n".format( ++ chksum=self._get_checksum(), mtime=self._get_mtime() ++ ) ++ ) ++ except OSError as e: ++ logger.error(_("Unable to save cookie file: %s"), e) ++ ++ def _get_mtime(self): ++ """ ++ Get the modified time of the RPM Database. ++ ++ Returns: ++ Unix ticks ++ """ ++ return ( ++ os.path.exists(self.rpmdb_file) ++ and int(os.path.getmtime(self.rpmdb_file)) ++ or 0 ++ ) ++ ++ def _get_checksum(self): ++ """ ++ Get the checksum of the RPM Database. ++ ++ Returns: ++ hexdigest ++ """ ++ digest = hashlib.sha256() ++ with open(self.rpmdb_file, "rb") as rpm_db_fh: ++ while True: ++ buff = rpm_db_fh.read(0x1000) ++ if not buff: ++ break ++ digest.update(buff) ++ return digest.hexdigest() +-- +2.39.2 + + diff --git a/do-not-fail-on-bad-message-pack-message-bsc-1213441-.patch b/do-not-fail-on-bad-message-pack-message-bsc-1213441-.patch new file mode 100644 index 0000000..e973b28 --- /dev/null +++ b/do-not-fail-on-bad-message-pack-message-bsc-1213441-.patch @@ -0,0 +1,155 @@ +From da544d7ab09899717e57a02321928ceaf3c6465c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 22 Aug 2023 11:43:46 +0100 +Subject: [PATCH] Do not fail on bad message pack message (bsc#1213441, + CVE-2023-20897) (#595) + +* Do not fail on bad message pack message + +Fix unit test after backporting to openSUSE/release/3006.0 + +* Better error message when inconsistent decoded payload + +--------- + +Co-authored-by: Daniel A. Wozniak +--- + salt/channel/server.py | 10 +++ + salt/transport/zeromq.py | 6 +- + tests/pytests/unit/transport/test_zeromq.py | 69 +++++++++++++++++++++ + 3 files changed, 84 insertions(+), 1 deletion(-) + +diff --git a/salt/channel/server.py b/salt/channel/server.py +index a2117f2934..b6d51fef08 100644 +--- a/salt/channel/server.py ++++ b/salt/channel/server.py +@@ -22,6 +22,7 @@ import salt.utils.minions + import salt.utils.platform + import salt.utils.stringutils + import salt.utils.verify ++from salt.exceptions import SaltDeserializationError + from salt.utils.cache import CacheCli + + try: +@@ -252,6 +253,15 @@ class ReqServerChannel: + return False + + def _decode_payload(self, payload): ++ # Sometimes msgpack deserialization of random bytes could be successful, ++ # so we need to ensure payload in good shape to process this function. ++ if ( ++ not isinstance(payload, dict) ++ or "enc" not in payload ++ or "load" not in payload ++ ): ++ raise SaltDeserializationError("bad load received on socket!") ++ + # we need to decrypt it + if payload["enc"] == "aes": + try: +diff --git a/salt/transport/zeromq.py b/salt/transport/zeromq.py +index 3ec7f7726c..7cc6b9987f 100644 +--- a/salt/transport/zeromq.py ++++ b/salt/transport/zeromq.py +@@ -428,7 +428,11 @@ class RequestServer(salt.transport.base.DaemonizedRequestServer): + + @salt.ext.tornado.gen.coroutine + def handle_message(self, stream, payload): +- payload = self.decode_payload(payload) ++ try: ++ payload = self.decode_payload(payload) ++ except salt.exceptions.SaltDeserializationError: ++ self.stream.send(self.encode_payload({"msg": "bad load"})) ++ return + # XXX: Is header really needed? + reply = yield self.message_handler(payload) + self.stream.send(self.encode_payload(reply)) +diff --git a/tests/pytests/unit/transport/test_zeromq.py b/tests/pytests/unit/transport/test_zeromq.py +index 10bb4917b8..c7cbc53864 100644 +--- a/tests/pytests/unit/transport/test_zeromq.py ++++ b/tests/pytests/unit/transport/test_zeromq.py +@@ -11,6 +11,7 @@ import threading + import time + import uuid + ++import msgpack + import pytest + + import salt.channel.client +@@ -1404,3 +1405,71 @@ async def test_req_chan_auth_v2_new_minion_without_master_pub(pki_dir, io_loop): + assert "sig" in ret + ret = client.auth.handle_signin_response(signin_payload, ret) + assert ret == "retry" ++ ++ ++async def test_req_server_garbage_request(io_loop): ++ """ ++ Validate invalid msgpack messages will not raise exceptions in the ++ RequestServers's message handler. ++ """ ++ opts = salt.config.master_config("") ++ request_server = salt.transport.zeromq.RequestServer(opts) ++ ++ def message_handler(payload): ++ return payload ++ ++ request_server.post_fork(message_handler, io_loop) ++ ++ byts = msgpack.dumps({"foo": "bar"}) ++ badbyts = byts[:3] + b"^M" + byts[3:] ++ ++ valid_response = msgpack.dumps({"msg": "bad load"}) ++ ++ with MagicMock() as stream: ++ request_server.stream = stream ++ ++ try: ++ await request_server.handle_message(stream, badbyts) ++ except Exception as exc: # pylint: disable=broad-except ++ pytest.fail("Exception was raised {}".format(exc)) ++ ++ request_server.stream.send.assert_called_once_with(valid_response) ++ ++ ++async def test_req_chan_bad_payload_to_decode(pki_dir, io_loop): ++ opts = { ++ "master_uri": "tcp://127.0.0.1:4506", ++ "interface": "127.0.0.1", ++ "ret_port": 4506, ++ "ipv6": False, ++ "sock_dir": ".", ++ "pki_dir": str(pki_dir.joinpath("minion")), ++ "id": "minion", ++ "__role": "minion", ++ "keysize": 4096, ++ "max_minions": 0, ++ "auto_accept": False, ++ "open_mode": False, ++ "key_pass": None, ++ "publish_port": 4505, ++ "auth_mode": 1, ++ "acceptance_wait_time": 3, ++ "acceptance_wait_time_max": 3, ++ } ++ SMaster.secrets["aes"] = { ++ "secret": multiprocessing.Array( ++ ctypes.c_char, ++ salt.utils.stringutils.to_bytes(salt.crypt.Crypticle.generate_key_string()), ++ ), ++ "reload": salt.crypt.Crypticle.generate_key_string, ++ } ++ master_opts = dict(opts, pki_dir=str(pki_dir.joinpath("master"))) ++ master_opts["master_sign_pubkey"] = False ++ server = salt.channel.server.ReqServerChannel.factory(master_opts) ++ ++ with pytest.raises(salt.exceptions.SaltDeserializationError): ++ server._decode_payload(None) ++ with pytest.raises(salt.exceptions.SaltDeserializationError): ++ server._decode_payload({}) ++ with pytest.raises(salt.exceptions.SaltDeserializationError): ++ server._decode_payload(12345) +-- +2.41.0 + + diff --git a/do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch b/do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch new file mode 100644 index 0000000..68dd077 --- /dev/null +++ b/do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch @@ -0,0 +1,46 @@ +From 4060d4cd24ac0fbcf83c1521553921d76c070a57 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Fri, 21 Sep 2018 17:31:39 +0200 +Subject: [PATCH] Do not load pip state if there is no 3rd party + dependencies + +Safe import 3rd party dependency +--- + salt/modules/pip.py | 13 ++++++++++++- + 1 file changed, 12 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/pip.py b/salt/modules/pip.py +index c4de0c2984..a60bdca0bb 100644 +--- a/salt/modules/pip.py ++++ b/salt/modules/pip.py +@@ -96,6 +96,12 @@ import salt.utils.url + import salt.utils.versions + from salt.exceptions import CommandExecutionError, CommandNotFoundError + ++try: ++ import pkg_resources ++except ImportError: ++ pkg_resources = None ++ ++ + # This needs to be named logger so we don't shadow it in pip.install + logger = logging.getLogger(__name__) # pylint: disable=invalid-name + +@@ -114,7 +120,12 @@ def __virtual__(): + entire filesystem. If it's not installed in a conventional location, the + user is required to provide the location of pip each time it is used. + """ +- return "pip" ++ if pkg_resources is None: ++ ret = False, 'Package dependency "pkg_resource" is missing' ++ else: ++ ret = "pip" ++ ++ return ret + + + def _pip_bin_env(cwd, bin_env): +-- +2.39.2 + + diff --git a/don-t-use-shell-sbin-nologin-in-requisites.patch b/don-t-use-shell-sbin-nologin-in-requisites.patch new file mode 100644 index 0000000..6dec710 --- /dev/null +++ b/don-t-use-shell-sbin-nologin-in-requisites.patch @@ -0,0 +1,39 @@ +From da6adc6984f21c0d93afff0b0ff55d0eb0ee3e9f Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 17 Aug 2021 11:52:00 +0200 +Subject: [PATCH] Don't use shell="/sbin/nologin" in requisites + +Using shell="/sbin/nologin" in an onlyif/unless requisite does not +really make sense since the condition can't be run. shell=/sbin/nologin +is also a common argument, e.g. for user.present. + +Fixes: bsc#1188259 +--- + salt/state.py | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/salt/state.py b/salt/state.py +index cb434a91e7..cda84a0fcb 100644 +--- a/salt/state.py ++++ b/salt/state.py +@@ -986,9 +986,14 @@ class State: + cmd_opts[run_cmd_arg] = low_data.get(run_cmd_arg) + + if "shell" in low_data and "shell" not in cmd_opts_exclude: +- cmd_opts["shell"] = low_data["shell"] ++ shell = low_data["shell"] + elif "shell" in self.opts["grains"]: +- cmd_opts["shell"] = self.opts["grains"].get("shell") ++ shell = self.opts["grains"].get("shell") ++ else: ++ shell = None ++ # /sbin/nologin always causes the onlyif / unless cmd to fail ++ if shell is not None and shell != "/sbin/nologin": ++ cmd_opts["shell"] = shell + + if "onlyif" in low_data: + _ret = self._run_check_onlyif(low_data, cmd_opts) +-- +2.39.2 + + diff --git a/drop-serial-from-event.unpack-in-cli.batch_async.patch b/drop-serial-from-event.unpack-in-cli.batch_async.patch new file mode 100644 index 0000000..d352056 --- /dev/null +++ b/drop-serial-from-event.unpack-in-cli.batch_async.patch @@ -0,0 +1,34 @@ +From e7ef0b5a46cc69a9237033d8dc4dbc60c0802a20 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Mon, 31 Jan 2022 10:24:26 +0100 +Subject: [PATCH] Drop serial from event.unpack in cli.batch_async + +--- + salt/cli/batch_async.py | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/salt/cli/batch_async.py b/salt/cli/batch_async.py +index 09aa85258b..1012ce37cc 100644 +--- a/salt/cli/batch_async.py ++++ b/salt/cli/batch_async.py +@@ -9,7 +9,6 @@ import logging + + import salt.client + import salt.ext.tornado +-import tornado + from salt.cli.batch import batch_get_eauth, batch_get_opts, get_bnum + + log = logging.getLogger(__name__) +@@ -109,7 +108,7 @@ class BatchAsync: + if not self.event: + return + try: +- mtag, data = self.event.unpack(raw, self.event.serial) ++ mtag, data = self.event.unpack(raw) + for (pattern, op) in self.patterns: + if mtag.startswith(pattern[:-1]): + minion = data["id"] +-- +2.39.2 + + diff --git a/early-feature-support-config.patch b/early-feature-support-config.patch new file mode 100644 index 0000000..782ed93 --- /dev/null +++ b/early-feature-support-config.patch @@ -0,0 +1,3784 @@ +From 7b47e6f19b38d773a6ec744209753f3d29b094ea Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 16:40:45 +0100 +Subject: [PATCH] early feature: support-config + +Add support script function + +Add salt-support starter + +Initial support wrapper + +Add data collector skeleton + +Add default scenario of the support configuration + +Add main flow for the collector. + +Move support library to its own package + +Add default support collection scenario + +Add logging + +Handle CLI error. + +Update format of the default support scenario + +Default archive name + +Finalise local data collection + +Write archive from memory objects. + +Add colored console outputter for salt-support. + +Use colored outputter + +Add message output class + +Remove try/except capture from the scripts and move to the runner directly + +Implement output highlighter methods for CLI output + +Move scenarios to profiles + +Get return section from the output. Tolerate raw data. + +Implement internal data collector + +Add network stack examination to the default profile + +Add an internal filetree function + +Add a method to discard current session + +Add a method to link a static file to the resulting archive + +Implement internal function caller + +Add internal functions + +Add default root for the one-file support data + +Set output device + +Separate dynamic data and static files on the fs + +Update color theme + +Add ident to the error message + +Report rejected files with the ident + +Reuse system error exceptions and reduce stat on the file check + +Use socket name of the host machine + +Add options for profile and archive settings + +Use archive name from options. + +Get profile by config/options + +Cleanup broken archive on crash/exception + +Use profile from the options/configuration + +Add more colored messages :-) + +Initial implementation of get static profiles + +Update docstring + +Move PostgreSQL profile to its own + +Handle profile listing, do not yield sys.exit on specific module + +Add network profile + +Add Salt's profile + +Uncomment package profile + +Allow several profiles to be specified + +Remove comments, add parameter to get more profiles + +Implement existing configuration finder + +Add options to handle unit configurations + +Pre-parse options prior run() to choose proper configuration target + +Handle arg parse generic errors, unit mis-choose + +Let cleanup be aware of pre-config state + +Fix imports + +Handle exit codes properly + +Allow to overwrite existing archive + +Use py2/3 exceptions equally + +Include exit exception on debugging + +Render profiles as Jinja2, add basic recursive caller to the template of the profile + +Add "users" profile + +Implement basic caller for the profile template + +Add table output renderer + +Fix typo + +Remove table outputter + +Allow default outputters and specify outputters inside the profile + +Remove group.getent from the loop per each user + +Add table outputter to network profile + +Add text outputter to hostname/fqdn data + +Remove network part from the default profile. Add text/table outputters. + +Fix Py3 compat + +Collect status (initial) + +Avoid irrelevant to profile files + +Add job profiles + +Add profile template trace + +Add inspection through the runners + +Allow parameters in callers and runners + +Handle non-dict iterables + +Highlight template content in the trace log + +Add return extractor from the local call returns + +Move local runner to its own namespace + +Lintfix: PEP8 + +Remove duplicate code + +Fix caller return + +Add description tag to the scenario + +Add generic colored message + +Add wrapping function. NOTE: it should be refactored with the other similar functions + +Print description while processing the scenario + +Turn off default profile and print help instead + +Move command-line check before collector + +Do not verify archive if help needs to be printed + +Add console output unit test for indent output + +Fix docstring + +Rename test class + +Refactor test to add setup/teardown + +Add unit test to verify indent + +Use direct constants instead of encoded strings + +Add unit test for color indent rotation check + +Add a test case for Collector class + +Add unit test for closing the archive + +Add unit test for add/write sections on the collector object + +Add test for linking an external file + +Cleanup tests on tear-down method + +Add call count check + +Add unit test for support collection section discard + +Add unittest for SaltSupport's function config preparation + +Fix docstring + +Add unit test for local caller + +Add unit test for local runner + +Add unit test for internal function call + +Add unit test for getting an action description from the action meta + +Add unit test for internal function call + +Add unit test for return extration + +Add unit test for determine action type from the action meta + +Add unit test for cleanup routine + +Fix typo of method name + +Add unit test for check existing archive + +Add test suite for profile testing + +Add unit test for default profile is YAML-parseable + +Add unit test for user template profile rendering + +Update unit test for all non-template profiles parse check + +Add function to render a Jinja2 template by name + +Use template rendering function + +Add unit test on jobs-trace template for runner + +Move function above the tests + +Add current logfile, if defined in configuration + +Bugfix: ignore logfile, if path was not found or not defined or is None + +Lintfix: iteration over .keys() + +Remove template "salt" from non-template checks + +Lintfix: use salt.utils.files.fopen for resource leak prevention + +Lintfix: PEP8 E302: expected 2 blank lines, found 0 + +Lintfix: use salt.utils.files.fopen instead of open + +Lintfix: PEP8 E303: too many blank lines (3) + +Lintfix: Uses of an external blacklisted import 'six': Please use 'import salt.ext.six as six' + +Lintfix: use salt.utils.files.fopen instead of open + +Fix unit tests + +Fix six import + +Mute pylint: file handler explicitly needed + +Lintfix: explicitly close filehandle + +Lintfix: mute fopen warning + +Remove development stub. Ughh... + +Removed blacklist of pkg_resources + +Make profiles a package. + +Add UTF-8 encoding + +Add a docstring + +Support-config non-root permission issues fixes (U#50095) + +Do not crash if there is no configuration available at all + +Handle CLI and log errors + +Catch overwriting exiting archive error by other users + +Suppress excessive tracebacks on error log level + +Add multi-file support and globbing to the filetree (U#50018) + +Add more possible logs + +Support multiple files grabbing + +Collect system logs and boot logs + +Support globbing in filetree + +Add supportconfig module for remote calls and SaltSSH + +Add log collector for remote purposes + +Implement default archive name + +Fix imports + +Implement runner function + +Remove targets data collector function as it is now called by a module instead + +Add external method decorator marker + +Add utility class for detecting exportable methods + +Mark run method as an external function + +Implement function setter + +Fix imports + +Setup config from __opts__ + +Use utility class + +Remove utils class + +Allow specify profile from the API parameter directly + +Rename module by virtual name + +Bypass parent subclass + +Implement profiles listing (local only for now) + +Specify profile from the state/call + +Set default or personalised archive name + +Add archives lister + +Add personalised name element to the archive name + +Use proper args/kwargs to the exported function + +Add archives deletion function + +Change log level when debugging rendered profiles + +Add ability to directly pass profile source when taking local data + +Add pillar profile support + +Remove extra-line + +Fix header + +Change output format for deleting archives + +Refactor logger output format + +Add time/milliseconds to each log notification + +Fix imports + +Switch output destination by context + +Add last archive function + +Lintfix + +Return consistent type + +Change output format for deleted archives report + +Implement report archive syncing to the reporting node + +Send multiple files at once via rsync, instead of send one after another + +Add sync stats formatter + +Change signature: cleanup -> move. Update docstring. + +Flush empty data from the output format + +Report archfiles activity + +Refactor imports + +Do not remove retcode if it is EX_OK + +Do not raise rsync error for undefined archives. + +Update header + +Add salt-support state module + +Move all functions into a callable class object + +Support __call__ function in state and command modules as default entrance that does not need to be specified in SLS state syntax + +Access from the outside only allowed class methods + +Pre-create destination of the archive, preventing single archive copied as a group name + +Handle functions exceptions + +Add unit test scaffold + +Add LogCollector UT for testing regular message + +Add LogCollector UT for testing INFO message + +Add LogCollector UT for testing WARNING message + +Replace hardcoded variables with defined constants + +Add LogCollector UT for testing ERROR message + +Test title attribute in msg method of LogCollector + +Add UT for LogCollector on highlighter method + +Add UT for LogCollector on put method + +Fix docstrings + +Add UT for archive name generator + +Add UT for custom archive name + +Fix docstring for the UT + +Add UT for checking profiles list format + +Add Unit Test for existing archives listing + +Add UT for the last archive function + +Create instance of the support class + +Add UT for successfully deleting all archives + +Add UT for deleting archives with failures + +Add UI for formatting sync stats and order preservation + +Add UT for testing sync failure when no archives has been specified + +Add UT for last picked archive has not found + +Add UT for last specified archive was not found + +Bugfix: do not create an array with None element in it + +Fix UT for found bugfix + +Add UT for syncing no archives failure + +Add UT for sync function + +Add UT for run support function + +Fix docstring for function "run" + +lintfix: use 'salt.support.mock' and 'patch()' + +Rewrite subdirectory creation and do not rely on Python3-only code + +Lintfix: remove unused imports + +Lintfix: regexp strings + +Break-down oneliner if/else clause + +Use ordered dictionary to preserve order of the state. + +This has transparent effect to the current process: OrderedDict is the +same as just Python dict, except it is preserving order of the state +chunks. + +Refactor state processing class. + +Add __call__ function to process single-id syntax + +Add backward-compatibility with default SLS syntax (id-per-call) + +Lintfix: E1120 no value in argument 'name' for class constructor + +Remove unused import + +Check last function by full name +--- + doc/ref/modules/all/index.rst | 1 + + doc/ref/states/all/index.rst | 1 + + salt/cli/support/__init__.py | 76 +++ + salt/cli/support/collector.py | 563 ++++++++++++++++++++++ + salt/cli/support/console.py | 184 +++++++ + salt/cli/support/intfunc.py | 51 ++ + salt/cli/support/localrunner.py | 33 ++ + salt/cli/support/profiles/__init__.py | 4 + + salt/cli/support/profiles/default.yml | 78 +++ + salt/cli/support/profiles/jobs-active.yml | 3 + + salt/cli/support/profiles/jobs-last.yml | 3 + + salt/cli/support/profiles/jobs-trace.yml | 7 + + salt/cli/support/profiles/network.yml | 27 ++ + salt/cli/support/profiles/postgres.yml | 11 + + salt/cli/support/profiles/salt.yml | 9 + + salt/cli/support/profiles/users.yml | 22 + + salt/loader/lazy.py | 6 +- + salt/modules/saltsupport.py | 405 ++++++++++++++++ + salt/scripts.py | 15 + + salt/state.py | 38 +- + salt/states/saltsupport.py | 225 +++++++++ + salt/utils/args.py | 3 +- + salt/utils/decorators/__init__.py | 24 + + salt/utils/parsers.py | 114 +++++ + scripts/salt-support | 11 + + setup.py | 2 + + tests/pytests/unit/cli/test_support.py | 553 +++++++++++++++++++++ + tests/unit/modules/test_saltsupport.py | 496 +++++++++++++++++++ + 28 files changed, 2958 insertions(+), 7 deletions(-) + create mode 100644 salt/cli/support/__init__.py + create mode 100644 salt/cli/support/collector.py + create mode 100644 salt/cli/support/console.py + create mode 100644 salt/cli/support/intfunc.py + create mode 100644 salt/cli/support/localrunner.py + create mode 100644 salt/cli/support/profiles/__init__.py + create mode 100644 salt/cli/support/profiles/default.yml + create mode 100644 salt/cli/support/profiles/jobs-active.yml + create mode 100644 salt/cli/support/profiles/jobs-last.yml + create mode 100644 salt/cli/support/profiles/jobs-trace.yml + create mode 100644 salt/cli/support/profiles/network.yml + create mode 100644 salt/cli/support/profiles/postgres.yml + create mode 100644 salt/cli/support/profiles/salt.yml + create mode 100644 salt/cli/support/profiles/users.yml + create mode 100644 salt/modules/saltsupport.py + create mode 100644 salt/states/saltsupport.py + create mode 100755 scripts/salt-support + create mode 100644 tests/pytests/unit/cli/test_support.py + create mode 100644 tests/unit/modules/test_saltsupport.py + +diff --git a/doc/ref/modules/all/index.rst b/doc/ref/modules/all/index.rst +index cbd8b0cdc5..abd40e0bc7 100644 +--- a/doc/ref/modules/all/index.rst ++++ b/doc/ref/modules/all/index.rst +@@ -416,6 +416,7 @@ execution modules + salt_version + saltcheck + saltcloudmod ++ saltsupport + saltutil + schedule + scp_mod +diff --git a/doc/ref/states/all/index.rst b/doc/ref/states/all/index.rst +index 13ff645b59..7a062c227b 100644 +--- a/doc/ref/states/all/index.rst ++++ b/doc/ref/states/all/index.rst +@@ -283,6 +283,7 @@ state modules + rvm + salt_proxy + saltmod ++ saltsupport + saltutil + schedule + selinux +diff --git a/salt/cli/support/__init__.py b/salt/cli/support/__init__.py +new file mode 100644 +index 0000000000..59c2609e07 +--- /dev/null ++++ b/salt/cli/support/__init__.py +@@ -0,0 +1,76 @@ ++""" ++Get default scenario of the support. ++""" ++import logging ++import os ++ ++import jinja2 ++import salt.exceptions ++import yaml ++ ++log = logging.getLogger(__name__) ++ ++ ++def _render_profile(path, caller, runner): ++ """ ++ Render profile as Jinja2. ++ :param path: ++ :return: ++ """ ++ env = jinja2.Environment( ++ loader=jinja2.FileSystemLoader(os.path.dirname(path)), trim_blocks=False ++ ) ++ return ( ++ env.get_template(os.path.basename(path)) ++ .render(salt=caller, runners=runner) ++ .strip() ++ ) ++ ++ ++def get_profile(profile, caller, runner): ++ """ ++ Get profile. ++ ++ :param profile: ++ :return: ++ """ ++ profiles = profile.split(",") ++ data = {} ++ for profile in profiles: ++ if os.path.basename(profile) == profile: ++ profile = profile.split(".")[0] # Trim extension if someone added it ++ profile_path = os.path.join( ++ os.path.dirname(__file__), "profiles", profile + ".yml" ++ ) ++ else: ++ profile_path = profile ++ if os.path.exists(profile_path): ++ 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)) ++ except Exception as ex: ++ log.debug(ex, exc_info=True) ++ raise salt.exceptions.SaltException( ++ "Rendering profile failed: {}".format(ex) ++ ) ++ else: ++ raise salt.exceptions.SaltException( ++ 'Profile "{}" is not found.'.format(profile) ++ ) ++ ++ return data ++ ++ ++def get_profiles(config): ++ """ ++ Get available profiles. ++ ++ :return: ++ """ ++ profiles = [] ++ for profile_name in os.listdir(os.path.join(os.path.dirname(__file__), "profiles")): ++ if profile_name.endswith(".yml"): ++ profiles.append(profile_name.split(".")[0]) ++ ++ return sorted(profiles) +diff --git a/salt/cli/support/collector.py b/salt/cli/support/collector.py +new file mode 100644 +index 0000000000..1879cc5220 +--- /dev/null ++++ b/salt/cli/support/collector.py +@@ -0,0 +1,563 @@ ++import builtins as exceptions ++import copy ++import json ++import logging ++import os ++import sys ++import tarfile ++import time ++from io import BytesIO ++from io import IOBase as file ++ ++import salt.cli.caller ++import salt.cli.support ++import salt.cli.support.console ++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.parsers ++import salt.utils.platform ++import salt.utils.process ++import salt.utils.stringutils ++import salt.utils.verify ++import yaml ++ ++salt.output.table_out.__opts__ = {} ++log = logging.getLogger(__name__) ++ ++ ++class SupportDataCollector: ++ """ ++ Data collector. It behaves just like another outputter, ++ except it grabs the data to the archive files. ++ """ ++ ++ def __init__(self, name, output): ++ """ ++ constructor of the data collector ++ :param name: ++ :param path: ++ :param format: ++ """ ++ self.archive_path = name ++ self.__default_outputter = output ++ self.__format = format ++ self.__arch = None ++ self.__current_section = None ++ self.__current_section_name = None ++ self.__default_root = time.strftime("%Y.%m.%d-%H.%M.%S-snapshot") ++ self.out = salt.cli.support.console.MessagesOutput() ++ ++ def open(self): ++ """ ++ Opens archive. ++ :return: ++ """ ++ if self.__arch is not None: ++ raise salt.exceptions.SaltException("Archive already opened.") ++ self.__arch = tarfile.TarFile.bz2open(self.archive_path, "w") ++ ++ def close(self): ++ """ ++ Closes the archive. ++ :return: ++ """ ++ if self.__arch is None: ++ raise salt.exceptions.SaltException("Archive already closed") ++ self._flush_content() ++ self.__arch.close() ++ self.__arch = None ++ ++ def _flush_content(self): ++ """ ++ Flush content to the archive ++ :return: ++ """ ++ if self.__current_section is not None: ++ buff = BytesIO() ++ buff._dirty = False ++ for action_return in self.__current_section: ++ for title, ret_data in action_return.items(): ++ if isinstance(ret_data, file): ++ self.out.put(ret_data.name, indent=4) ++ self.__arch.add(ret_data.name, arcname=ret_data.name) ++ else: ++ buff.write(salt.utils.stringutils.to_bytes(title + "\n")) ++ buff.write( ++ salt.utils.stringutils.to_bytes(("-" * len(title)) + "\n\n") ++ ) ++ buff.write(salt.utils.stringutils.to_bytes(ret_data)) ++ buff.write(salt.utils.stringutils.to_bytes("\n\n\n")) ++ buff._dirty = True ++ if buff._dirty: ++ buff.seek(0) ++ tar_info = tarfile.TarInfo( ++ name="{}/{}".format( ++ self.__default_root, self.__current_section_name ++ ) ++ ) ++ if not hasattr(buff, "getbuffer"): # Py2's BytesIO is older ++ buff.getbuffer = buff.getvalue ++ tar_info.size = len(buff.getbuffer()) ++ self.__arch.addfile(tarinfo=tar_info, fileobj=buff) ++ ++ def add(self, name): ++ """ ++ Start a new section. ++ :param name: ++ :return: ++ """ ++ if self.__current_section: ++ self._flush_content() ++ self.discard_current(name) ++ ++ def discard_current(self, name=None): ++ """ ++ Discard current section ++ :return: ++ """ ++ self.__current_section = [] ++ self.__current_section_name = name ++ ++ def _printout(self, data, output): ++ """ ++ Use salt outputter to printout content. ++ ++ :return: ++ """ ++ opts = {"extension_modules": "", "color": False} ++ try: ++ printout = salt.output.get_printout(output, opts)(data) ++ if printout is not None: ++ return printout.rstrip() ++ except (KeyError, AttributeError, TypeError) as err: ++ log.debug(err, exc_info=True) ++ try: ++ printout = salt.output.get_printout("nested", opts)(data) ++ if printout is not None: ++ return printout.rstrip() ++ except (KeyError, AttributeError, TypeError) as err: ++ log.debug(err, exc_info=True) ++ printout = salt.output.get_printout("raw", opts)(data) ++ if printout is not None: ++ return printout.rstrip() ++ ++ return salt.output.try_printout(data, output, opts) ++ ++ def write(self, title, data, output=None): ++ """ ++ Add a data to the current opened section. ++ :return: ++ """ ++ if not isinstance(data, (dict, list, tuple)): ++ data = {"raw-content": str(data)} ++ output = output or self.__default_outputter ++ ++ if output != "null": ++ try: ++ if isinstance(data, dict) and "return" in data: ++ data = data["return"] ++ content = self._printout(data, output) ++ except Exception: # Fall-back to just raw YAML ++ content = None ++ else: ++ content = None ++ ++ if content is None: ++ data = json.loads(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) ++ ++ self.__current_section.append({title: content}) ++ ++ def link(self, title, path): ++ """ ++ Add a static file on the file system. ++ ++ :param title: ++ :param path: ++ :return: ++ """ ++ # The filehandler needs to be explicitly passed here, so PyLint needs to accept that. ++ # pylint: disable=W8470 ++ if not isinstance(path, file): ++ path = salt.utils.files.fopen(path) ++ self.__current_section.append({title: path}) ++ # pylint: enable=W8470 ++ ++ ++class SaltSupport(salt.utils.parsers.SaltSupportOptionParser): ++ """ ++ Class to run Salt Support subsystem. ++ """ ++ ++ RUNNER_TYPE = "run" ++ CALL_TYPE = "call" ++ ++ def _setup_fun_config(self, fun_conf): ++ """ ++ Setup function configuration. ++ ++ :param conf: ++ :return: ++ """ ++ conf = copy.deepcopy(self.config) ++ conf["file_client"] = "local" ++ conf["fun"] = "" ++ conf["arg"] = [] ++ conf["kwarg"] = {} ++ conf["cache_jobs"] = False ++ conf["print_metadata"] = False ++ conf.update(fun_conf) ++ conf["fun"] = conf["fun"].split(":")[-1] # Discard typing prefix ++ ++ return conf ++ ++ def _get_runner(self, conf): ++ """ ++ Get & setup runner. ++ ++ :param conf: ++ :return: ++ """ ++ conf = self._setup_fun_config(copy.deepcopy(conf)) ++ if not getattr(self, "_runner", None): ++ self._runner = salt.cli.support.localrunner.LocalRunner(conf) ++ else: ++ self._runner.opts = conf ++ return self._runner ++ ++ def _get_caller(self, conf): ++ """ ++ Get & setup caller from the factory. ++ ++ :param conf: ++ :return: ++ """ ++ conf = self._setup_fun_config(copy.deepcopy(conf)) ++ if not getattr(self, "_caller", None): ++ self._caller = salt.cli.caller.Caller.factory(conf) ++ else: ++ self._caller.opts = conf ++ return self._caller ++ ++ def _local_call(self, call_conf): ++ """ ++ Execute local call ++ """ ++ try: ++ ret = self._get_caller(call_conf).call() ++ except SystemExit: ++ ret = "Data is not available at this moment" ++ self.out.error(ret) ++ except Exception as ex: ++ ret = "Unhandled exception occurred: {}".format(ex) ++ log.debug(ex, exc_info=True) ++ self.out.error(ret) ++ ++ return ret ++ ++ def _local_run(self, run_conf): ++ """ ++ Execute local runner ++ ++ :param run_conf: ++ :return: ++ """ ++ try: ++ ret = self._get_runner(run_conf).run() ++ except SystemExit: ++ ret = "Runner is not available at this moment" ++ self.out.error(ret) ++ except Exception as ex: ++ ret = "Unhandled exception occurred: {}".format(ex) ++ log.debug(ex, exc_info=True) ++ ++ return ret ++ ++ def _internal_function_call(self, call_conf): ++ """ ++ Call internal function. ++ ++ :param call_conf: ++ :return: ++ """ ++ ++ def stub(*args, **kwargs): ++ message = "Function {} is not available".format(call_conf["fun"]) ++ self.out.error(message) ++ log.debug( ++ 'Attempt to run "{fun}" with {arg} arguments and {kwargs} parameters.'.format( ++ **call_conf ++ ) ++ ) ++ return message ++ ++ return getattr(salt.cli.support.intfunc, call_conf["fun"], stub)( ++ self.collector, *call_conf["arg"], **call_conf["kwargs"] ++ ) ++ ++ def _get_action(self, action_meta): ++ """ ++ Parse action and turn into a calling point. ++ :param action_meta: ++ :return: ++ """ ++ conf = { ++ "fun": list(action_meta.keys())[0], ++ "arg": [], ++ "kwargs": {}, ++ } ++ if not len(conf["fun"].split(".")) - 1: ++ conf["salt.int.intfunc"] = True ++ ++ action_meta = action_meta[conf["fun"]] ++ info = action_meta.get("info", "Action for {}".format(conf["fun"])) ++ for arg in action_meta.get("args") or []: ++ if not isinstance(arg, dict): ++ conf["arg"].append(arg) ++ else: ++ conf["kwargs"].update(arg) ++ ++ return info, action_meta.get("output"), conf ++ ++ def collect_internal_data(self): ++ """ ++ Dumps current running pillars, configuration etc. ++ :return: ++ """ ++ section = "configuration" ++ self.out.put(section) ++ self.collector.add(section) ++ self.out.put("Saving config", indent=2) ++ self.collector.write("General Configuration", self.config) ++ self.out.put("Saving pillars", indent=2) ++ self.collector.write( ++ "Active Pillars", self._local_call({"fun": "pillar.items"}) ++ ) ++ ++ section = "highstate" ++ self.out.put(section) ++ self.collector.add(section) ++ self.out.put("Saving highstate", indent=2) ++ self.collector.write( ++ "Rendered highstate", self._local_call({"fun": "state.show_highstate"}) ++ ) ++ ++ def _extract_return(self, data): ++ """ ++ Extracts return data from the results. ++ ++ :param data: ++ :return: ++ """ ++ if isinstance(data, dict): ++ data = data.get("return", data) ++ ++ return data ++ ++ def collect_local_data(self, profile=None, profile_source=None): ++ """ ++ Collects master system data. ++ :return: ++ """ ++ ++ def call(func, *args, **kwargs): ++ """ ++ Call wrapper for templates ++ :param func: ++ :return: ++ """ ++ return self._extract_return( ++ self._local_call({"fun": func, "arg": args, "kwarg": kwargs}) ++ ) ++ ++ def run(func, *args, **kwargs): ++ """ ++ Runner wrapper for templates ++ :param func: ++ :return: ++ """ ++ return self._extract_return( ++ self._local_run({"fun": func, "arg": args, "kwarg": kwargs}) ++ ) ++ ++ scenario = profile_source or salt.cli.support.get_profile( ++ profile or self.config["support_profile"], call, run ++ ) ++ for category_name in scenario: ++ self.out.put(category_name) ++ self.collector.add(category_name) ++ for action in scenario[category_name]: ++ if not action: ++ continue ++ action_name = next(iter(action)) ++ if not isinstance(action[action_name], str): ++ info, output, conf = self._get_action(action) ++ action_type = self._get_action_type( ++ action ++ ) # run: for runners ++ if action_type == self.RUNNER_TYPE: ++ self.out.put("Running {}".format(info.lower()), indent=2) ++ self.collector.write(info, self._local_run(conf), output=output) ++ elif action_type == self.CALL_TYPE: ++ if not conf.get("salt.int.intfunc"): ++ self.out.put("Collecting {}".format(info.lower()), indent=2) ++ self.collector.write( ++ info, self._local_call(conf), output=output ++ ) ++ else: ++ self.collector.discard_current() ++ self._internal_function_call(conf) ++ else: ++ self.out.error( ++ 'Unknown action type "{}" for action: {}'.format( ++ action_type, action ++ ) ++ ) ++ else: ++ # TODO: This needs to be moved then to the utils. ++ # But the code is not yet there (other PRs) ++ self.out.msg( ++ "\n".join(salt.cli.support.console.wrap(action[action_name])), ++ ident=2, ++ ) ++ ++ def _get_action_type(self, action): ++ """ ++ Get action type. ++ :param action: ++ :return: ++ """ ++ action_name = next(iter(action or {"": None})) ++ if ":" not in action_name: ++ action_name = "{}:{}".format(self.CALL_TYPE, action_name) ++ ++ return action_name.split(":")[0] or None ++ ++ def _cleanup(self): ++ """ ++ Cleanup if crash/exception ++ :return: ++ """ ++ if ( ++ hasattr(self, "config") ++ and self.config.get("support_archive") ++ and os.path.exists(self.config["support_archive"]) ++ ): ++ self.out.warning("Terminated earlier, cleaning up") ++ try: ++ os.unlink(self.config["support_archive"]) ++ except Exception as err: ++ log.debug(err) ++ self.out.error("{} while cleaning up.".format(err)) ++ ++ def _check_existing_archive(self): ++ """ ++ Check if archive exists or not. If exists and --force was not specified, ++ bail out. Otherwise remove it and move on. ++ ++ :return: ++ """ ++ if os.path.exists(self.config["support_archive"]): ++ if self.config["support_archive_force_overwrite"]: ++ self.out.warning( ++ "Overwriting existing archive: {}".format( ++ self.config["support_archive"] ++ ) ++ ) ++ try: ++ os.unlink(self.config["support_archive"]) ++ except Exception as err: ++ log.debug(err) ++ self.out.error( ++ "{} while trying to overwrite existing archive.".format(err) ++ ) ++ ret = True ++ else: ++ self.out.warning( ++ "File {} already exists.".format(self.config["support_archive"]) ++ ) ++ ret = False ++ else: ++ ret = True ++ ++ return ret ++ ++ def run(self): ++ exit_code = salt.defaults.exitcodes.EX_OK ++ self.out = salt.cli.support.console.MessagesOutput() ++ try: ++ self.parse_args() ++ except (Exception, SystemExit) as ex: ++ if not isinstance(ex, exceptions.SystemExit): ++ exit_code = salt.defaults.exitcodes.EX_GENERIC ++ self.out.error(ex) ++ elif isinstance(ex, exceptions.SystemExit): ++ exit_code = ex.code ++ else: ++ exit_code = salt.defaults.exitcodes.EX_GENERIC ++ 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 ++ ++ if self.config["support_profile_list"]: ++ self.out.put("List of available profiles:") ++ for idx, profile in enumerate( ++ salt.cli.support.get_profiles(self.config) ++ ): ++ msg_template = " {}. ".format(idx + 1) + "{}" ++ self.out.highlight(msg_template, profile) ++ exit_code = salt.defaults.exitcodes.EX_OK ++ elif self.config["support_show_units"]: ++ self.out.put("List of available units:") ++ for idx, unit in enumerate(self.find_existing_configs(None)): ++ msg_template = " {}. ".format(idx + 1) + "{}" ++ self.out.highlight(msg_template, unit) ++ exit_code = salt.defaults.exitcodes.EX_OK ++ else: ++ if not self.config["support_profile"]: ++ self.print_help() ++ raise SystemExit() ++ ++ if self._check_existing_archive(): ++ try: ++ self.collector = SupportDataCollector( ++ self.config["support_archive"], ++ output=self.config["support_output_format"], ++ ) ++ except Exception as ex: ++ self.out.error(ex) ++ exit_code = salt.defaults.exitcodes.EX_GENERIC ++ log.debug(ex, exc_info=True) ++ else: ++ try: ++ self.collector.open() ++ self.collect_local_data() ++ self.collect_internal_data() ++ self.collector.close() ++ ++ archive_path = self.collector.archive_path ++ self.out.highlight( ++ '\nSupport data has been written to "{}" file.\n', ++ archive_path, ++ _main="YELLOW", ++ ) ++ except Exception as ex: ++ self.out.error(ex) ++ log.debug(ex, exc_info=True) ++ exit_code = salt.defaults.exitcodes.EX_SOFTWARE ++ ++ if exit_code: ++ self._cleanup() ++ ++ sys.exit(exit_code) +diff --git a/salt/cli/support/console.py b/salt/cli/support/console.py +new file mode 100644 +index 0000000000..266b645479 +--- /dev/null ++++ b/salt/cli/support/console.py +@@ -0,0 +1,184 @@ ++""" ++Collection of tools to report messages to console. ++ ++NOTE: This is subject to incorporate other formatting bits ++ from all around everywhere and then to be moved to utils. ++""" ++ ++ ++import os ++import sys ++import textwrap ++ ++import salt.utils.color ++ ++ ++class IndentOutput: ++ """ ++ Paint different indends in different output. ++ """ ++ ++ def __init__(self, conf=None, device=sys.stdout): ++ if conf is None: ++ conf = {0: "CYAN", 2: "GREEN", 4: "LIGHT_BLUE", 6: "BLUE"} ++ self._colors_conf = conf ++ self._device = device ++ self._colors = salt.utils.color.get_colors() ++ self._default_color = "GREEN" ++ self._default_hl_color = "LIGHT_GREEN" ++ ++ def put(self, message, indent=0): ++ """ ++ Print message with an indent. ++ ++ :param message: ++ :param indent: ++ :return: ++ """ ++ color = self._colors_conf.get( ++ indent + indent % 2, self._colors_conf.get(0, self._default_color) ++ ) ++ ++ for chunk in [" " * indent, self._colors[color], message, self._colors["ENDC"]]: ++ self._device.write(str(chunk)) ++ self._device.write(os.linesep) ++ self._device.flush() ++ ++ ++class MessagesOutput(IndentOutput): ++ """ ++ Messages output to the CLI. ++ """ ++ ++ def msg(self, message, title=None, title_color=None, color="BLUE", ident=0): ++ """ ++ Hint message. ++ ++ :param message: ++ :param title: ++ :param title_color: ++ :param color: ++ :param ident: ++ :return: ++ """ ++ if title and not title_color: ++ title_color = color ++ if title_color and not title: ++ title_color = None ++ ++ self.__colored_output(title, message, title_color, color, ident=ident) ++ ++ def info(self, message, ident=0): ++ """ ++ Write an info message to the CLI. ++ ++ :param message: ++ :param ident: ++ :return: ++ """ ++ self.__colored_output("Info", message, "GREEN", "LIGHT_GREEN", ident=ident) ++ ++ def warning(self, message, ident=0): ++ """ ++ Write a warning message to the CLI. ++ ++ :param message: ++ :param ident: ++ :return: ++ """ ++ self.__colored_output("Warning", message, "YELLOW", "LIGHT_YELLOW", ident=ident) ++ ++ def error(self, message, ident=0): ++ """ ++ Write an error message to the CLI. ++ ++ :param message: ++ :param ident ++ :return: ++ """ ++ self.__colored_output("Error", message, "RED", "LIGHT_RED", ident=ident) ++ ++ def __colored_output(self, title, message, title_color, message_color, ident=0): ++ if title and not title.endswith(":"): ++ _linesep = title.endswith(os.linesep) ++ title = "{}:{}".format(title.strip(), _linesep and os.linesep or " ") ++ ++ for chunk in [ ++ title_color and self._colors[title_color] or None, ++ " " * ident, ++ title, ++ self._colors[message_color], ++ message, ++ self._colors["ENDC"], ++ ]: ++ if chunk: ++ self._device.write(str(chunk)) ++ self._device.write(os.linesep) ++ self._device.flush() ++ ++ def highlight(self, message, *values, **colors): ++ """ ++ Highlighter works the way that message parameter is a template, ++ the "values" is a list of arguments going one after another as values there. ++ And so the "colors" should designate either highlight color or alternate for each. ++ ++ Example: ++ ++ highlight('Hello {}, there! It is {}.', 'user', 'daytime', _main='GREEN', _highlight='RED') ++ highlight('Hello {}, there! It is {}.', 'user', 'daytime', _main='GREEN', _highlight='RED', 'daytime'='YELLOW') ++ ++ First example will highlight all the values in the template with the red color. ++ Second example will highlight the second value with the yellow color. ++ ++ Usage: ++ ++ colors: ++ _main: Sets the main color (or default is used) ++ _highlight: Sets the alternative color for everything ++ 'any phrase' that is the same in the "values" can override color. ++ ++ :param message: ++ :param formatted: ++ :param colors: ++ :return: ++ """ ++ ++ m_color = colors.get("_main", self._default_color) ++ h_color = colors.get("_highlight", self._default_hl_color) ++ ++ _values = [] ++ for value in values: ++ _values.append( ++ "{p}{c}{r}".format( ++ p=self._colors[colors.get(value, h_color)], ++ c=value, ++ r=self._colors[m_color], ++ ) ++ ) ++ self._device.write( ++ "{s}{m}{e}".format( ++ s=self._colors[m_color], ++ m=message.format(*_values), ++ e=self._colors["ENDC"], ++ ) ++ ) ++ self._device.write(os.linesep) ++ self._device.flush() ++ ++ ++def wrap(txt, width=80, ident=0): ++ """ ++ Wrap text to the required dimensions and clean it up, prepare for display. ++ ++ :param txt: ++ :param width: ++ :return: ++ """ ++ ident = " " * ident ++ txt = (txt or "").replace(os.linesep, " ").strip() ++ ++ wrapper = textwrap.TextWrapper() ++ wrapper.fix_sentence_endings = False ++ wrapper.initial_indent = wrapper.subsequent_indent = ident ++ ++ return wrapper.wrap(txt) +diff --git a/salt/cli/support/intfunc.py b/salt/cli/support/intfunc.py +new file mode 100644 +index 0000000000..a9f76a6003 +--- /dev/null ++++ b/salt/cli/support/intfunc.py +@@ -0,0 +1,51 @@ ++""" ++Internal functions. ++""" ++# Maybe this needs to be a modules in a future? ++ ++import glob ++import os ++ ++import salt.utils.files ++from salt.cli.support.console import MessagesOutput ++ ++out = MessagesOutput() ++ ++ ++def filetree(collector, *paths): ++ """ ++ Add all files in the tree. If the "path" is a file, ++ only that file will be added. ++ ++ :param path: File or directory ++ :return: ++ """ ++ _paths = [] ++ # Unglob ++ for path in paths: ++ _paths += glob.glob(path) ++ for path in set(_paths): ++ if not path: ++ out.error("Path not defined", ident=2) ++ elif not os.path.exists(path): ++ out.warning("Path {} does not exists".format(path)) ++ else: ++ # The filehandler needs to be explicitly passed here, so PyLint needs to accept that. ++ # pylint: disable=W8470 ++ if os.path.isfile(path): ++ filename = os.path.basename(path) ++ try: ++ file_ref = salt.utils.files.fopen(path) # pylint: disable=W ++ out.put("Add {}".format(filename), indent=2) ++ collector.add(filename) ++ collector.link(title=path, path=file_ref) ++ except Exception as err: ++ out.error(err, ident=4) ++ # pylint: enable=W8470 ++ else: ++ try: ++ for fname in os.listdir(path): ++ fname = os.path.join(path, fname) ++ filetree(collector, [fname]) ++ except Exception as err: ++ out.error(err, ident=4) +diff --git a/salt/cli/support/localrunner.py b/salt/cli/support/localrunner.py +new file mode 100644 +index 0000000000..ad10eda0b0 +--- /dev/null ++++ b/salt/cli/support/localrunner.py +@@ -0,0 +1,33 @@ ++""" ++Local Runner ++""" ++ ++import logging ++ ++import salt.runner ++import salt.utils.platform ++import salt.utils.process ++ ++log = logging.getLogger(__name__) ++ ++ ++class LocalRunner(salt.runner.Runner): ++ """ ++ Runner class that changes its default behaviour. ++ """ ++ ++ def _proc_function(self, fun, low, user, tag, jid, daemonize=True): ++ """ ++ Same as original _proc_function in AsyncClientMixin, ++ except it calls "low" without firing a print event. ++ """ ++ if daemonize and not salt.utils.platform.is_windows(): ++ salt.log.setup.shutdown_multiprocessing_logging() ++ salt.utils.process.daemonize() ++ salt.log.setup.setup_multiprocessing_logging() ++ ++ low["__jid__"] = jid ++ low["__user__"] = user ++ low["__tag__"] = tag ++ ++ return self.low(fun, low, print_event=False, full_return=False) +diff --git a/salt/cli/support/profiles/__init__.py b/salt/cli/support/profiles/__init__.py +new file mode 100644 +index 0000000000..b86aef30b8 +--- /dev/null ++++ b/salt/cli/support/profiles/__init__.py +@@ -0,0 +1,4 @@ ++# coding=utf-8 ++''' ++Profiles for salt-support. ++''' +diff --git a/salt/cli/support/profiles/default.yml b/salt/cli/support/profiles/default.yml +new file mode 100644 +index 0000000000..3defb5eef3 +--- /dev/null ++++ b/salt/cli/support/profiles/default.yml +@@ -0,0 +1,78 @@ ++sysinfo: ++ - description: | ++ Get the Salt grains of the current system. ++ - grains.items: ++ info: System grains ++ ++packages: ++ - description: | ++ Fetch list of all the installed packages. ++ - pkg.list_pkgs: ++ info: Installed packages ++ ++repositories: ++ - pkg.list_repos: ++ info: Available repositories ++ ++upgrades: ++ - pkg.list_upgrades: ++ info: Possible upgrades ++ ++## TODO: Some data here belongs elsewhere and also is duplicated ++status: ++ - status.version: ++ info: Status version ++ - status.cpuinfo: ++ info: CPU information ++ - status.cpustats: ++ info: CPU stats ++ - status.diskstats: ++ info: Disk stats ++ - status.loadavg: ++ info: Average load of the current system ++ - status.uptime: ++ info: Uptime of the machine ++ - status.meminfo: ++ info: Information about memory ++ - status.vmstats: ++ info: Virtual memory stats ++ - status.netdev: ++ info: Network device stats ++ - status.nproc: ++ info: Number of processing units available on this system ++ - status.procs: ++ info: Process data ++ ++general-health: ++ - ps.boot_time: ++ info: System Boot Time ++ - ps.swap_memory: ++ info: Swap Memory ++ output: txt ++ - ps.cpu_times: ++ info: CPU times ++ - ps.disk_io_counters: ++ info: Disk IO counters ++ - ps.disk_partition_usage: ++ info: Disk partition usage ++ output: table ++ - ps.disk_partitions: ++ info: Disk partitions ++ output: table ++ - ps.top: ++ info: Top CPU consuming processes ++ ++boot_log: ++ - filetree: ++ info: Collect boot logs ++ args: ++ - /var/log/boot.* ++ ++system.log: ++ # This works on any file system object. ++ - filetree: ++ info: Add system log ++ args: ++ - /var/log/syslog ++ - /var/log/messages ++ +diff --git a/salt/cli/support/profiles/jobs-active.yml b/salt/cli/support/profiles/jobs-active.yml +new file mode 100644 +index 0000000000..508c54ece7 +--- /dev/null ++++ b/salt/cli/support/profiles/jobs-active.yml +@@ -0,0 +1,3 @@ ++jobs-active: ++ - run:jobs.active: ++ info: List of all actively running jobs +diff --git a/salt/cli/support/profiles/jobs-last.yml b/salt/cli/support/profiles/jobs-last.yml +new file mode 100644 +index 0000000000..e3b719f552 +--- /dev/null ++++ b/salt/cli/support/profiles/jobs-last.yml +@@ -0,0 +1,3 @@ ++jobs-last: ++ - run:jobs.last_run: ++ info: List all detectable jobs and associated functions +diff --git a/salt/cli/support/profiles/jobs-trace.yml b/salt/cli/support/profiles/jobs-trace.yml +new file mode 100644 +index 0000000000..00b28e0502 +--- /dev/null ++++ b/salt/cli/support/profiles/jobs-trace.yml +@@ -0,0 +1,7 @@ ++jobs-details: ++ {% for job in runners('jobs.list_jobs') %} ++ - run:jobs.list_job: ++ info: Details on JID {{job}} ++ args: ++ - {{job}} ++ {% endfor %} +diff --git a/salt/cli/support/profiles/network.yml b/salt/cli/support/profiles/network.yml +new file mode 100644 +index 0000000000..268f02e61f +--- /dev/null ++++ b/salt/cli/support/profiles/network.yml +@@ -0,0 +1,27 @@ ++network: ++ - network.get_hostname: ++ info: Hostname ++ output: txt ++ - network.get_fqdn: ++ info: FQDN ++ output: txt ++ - network.default_route: ++ info: Default route ++ output: table ++ - network.interfaces: ++ info: All the available interfaces ++ output: table ++ - network.subnets: ++ info: List of IPv4 subnets ++ - network.subnets6: ++ info: List of IPv6 subnets ++ - network.routes: ++ info: Network configured routes from routing tables ++ output: table ++ - network.netstat: ++ info: Information on open ports and states ++ output: table ++ - network.active_tcp: ++ info: All running TCP connections ++ - network.arp: ++ info: ARP table +diff --git a/salt/cli/support/profiles/postgres.yml b/salt/cli/support/profiles/postgres.yml +new file mode 100644 +index 0000000000..2238752c7a +--- /dev/null ++++ b/salt/cli/support/profiles/postgres.yml +@@ -0,0 +1,11 @@ ++system.log: ++ - filetree: ++ info: Add system log ++ args: ++ - /var/log/syslog ++ ++etc/postgres: ++ - filetree: ++ info: Pick entire /etc/postgresql ++ args: ++ - /etc/postgresql +diff --git a/salt/cli/support/profiles/salt.yml b/salt/cli/support/profiles/salt.yml +new file mode 100644 +index 0000000000..4b18d98870 +--- /dev/null ++++ b/salt/cli/support/profiles/salt.yml +@@ -0,0 +1,9 @@ ++sysinfo: ++ - grains.items: ++ info: System grains ++ ++logfile: ++ - filetree: ++ info: Add current logfile ++ args: ++ - {{salt('config.get', 'log_file')}} +diff --git a/salt/cli/support/profiles/users.yml b/salt/cli/support/profiles/users.yml +new file mode 100644 +index 0000000000..391acdb606 +--- /dev/null ++++ b/salt/cli/support/profiles/users.yml +@@ -0,0 +1,22 @@ ++all-users: ++ {%for uname in salt('user.list_users') %} ++ - user.info: ++ info: Information about "{{uname}}" ++ args: ++ - {{uname}} ++ - user.list_groups: ++ info: List groups for user "{{uname}}" ++ args: ++ - {{uname}} ++ - shadow.info: ++ info: Shadow information about user "{{uname}}" ++ args: ++ - {{uname}} ++ - cron.raw_cron: ++ info: Cron for user "{{uname}}" ++ args: ++ - {{uname}} ++ {%endfor%} ++ - group.getent: ++ info: List of all available groups ++ output: table +diff --git a/salt/loader/lazy.py b/salt/loader/lazy.py +index d319fe54b4..5de995d446 100644 +--- a/salt/loader/lazy.py ++++ b/salt/loader/lazy.py +@@ -972,8 +972,10 @@ class LazyLoader(salt.utils.lazy.LazyDict): + mod_names = [module_name] + list(virtual_aliases) + + for attr in funcs_to_load: +- if attr.startswith("_"): +- # private functions are skipped ++ if attr.startswith("_") and attr != "__call__": ++ # private functions are skipped, ++ # except __call__ which is default entrance ++ # for multi-function batch-like state syntax + continue + func = getattr(mod, attr) + if not inspect.isfunction(func) and not isinstance(func, functools.partial): +diff --git a/salt/modules/saltsupport.py b/salt/modules/saltsupport.py +new file mode 100644 +index 0000000000..e800e3bf1f +--- /dev/null ++++ b/salt/modules/saltsupport.py +@@ -0,0 +1,405 @@ ++# ++# Author: Bo Maryniuk ++# ++# Copyright 2018 SUSE LLC ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++""" ++:codeauthor: :email:`Bo Maryniuk ` ++ ++Module to run salt-support within Salt. ++""" ++# pylint: disable=W0231,W0221 ++ ++ ++import datetime ++import logging ++import os ++import re ++import sys ++import tempfile ++import time ++ ++import salt.cli.support ++import salt.cli.support.intfunc ++import salt.defaults.exitcodes ++import salt.exceptions ++import salt.utils.decorators ++import salt.utils.dictupdate ++import salt.utils.odict ++import salt.utils.path ++import salt.utils.stringutils ++from salt.cli.support.collector import SaltSupport, SupportDataCollector ++ ++__virtualname__ = "support" ++log = logging.getLogger(__name__) ++ ++ ++class LogCollector: ++ """ ++ Output collector. ++ """ ++ ++ INFO = "info" ++ WARNING = "warning" ++ ERROR = "error" ++ ++ class MessagesList(list): ++ def append(self, obj): ++ list.append( ++ self, ++ "{} - {}".format( ++ datetime.datetime.utcnow().strftime("%T.%f")[:-3], obj ++ ), ++ ) ++ ++ __call__ = append ++ ++ def __init__(self): ++ self.messages = { ++ self.INFO: self.MessagesList(), ++ self.WARNING: self.MessagesList(), ++ self.ERROR: self.MessagesList(), ++ } ++ ++ def msg(self, message, *args, **kwargs): ++ title = kwargs.get("title") ++ if title: ++ message = "{}: {}".format(title, message) ++ self.messages[self.INFO](message) ++ ++ def info(self, message, *args, **kwargs): ++ self.msg(message) ++ ++ def warning(self, message, *args, **kwargs): ++ self.messages[self.WARNING](message) ++ ++ def error(self, message, *args, **kwargs): ++ self.messages[self.ERROR](message) ++ ++ def put(self, message, *args, **kwargs): ++ self.messages[self.INFO](message) ++ ++ def highlight(self, message, *values, **kwargs): ++ self.msg(message.format(*values)) ++ ++ ++class SaltSupportModule(SaltSupport): ++ """ ++ Salt Support module class. ++ """ ++ ++ def __init__(self): ++ """ ++ Constructor ++ """ ++ self.config = self.setup_config() ++ ++ def setup_config(self): ++ """ ++ Return current configuration ++ :return: ++ """ ++ return __opts__ ++ ++ def _get_archive_name(self, archname=None): ++ """ ++ Create default archive name. ++ ++ :return: ++ """ ++ archname = re.sub("[^a-z0-9]", "", (archname or "").lower()) or "support" ++ for grain in ["fqdn", "host", "localhost", "nodename"]: ++ host = __grains__.get(grain) ++ if host: ++ break ++ if not host: ++ host = "localhost" ++ ++ return os.path.join( ++ tempfile.gettempdir(), ++ "{hostname}-{archname}-{date}-{time}.bz2".format( ++ archname=archname, ++ hostname=host, ++ date=time.strftime("%Y%m%d"), ++ time=time.strftime("%H%M%S"), ++ ), ++ ) ++ ++ @salt.utils.decorators.external ++ def profiles(self): ++ """ ++ Get list of profiles. ++ ++ :return: ++ """ ++ return { ++ "standard": salt.cli.support.get_profiles(self.config), ++ "custom": [], ++ } ++ ++ @salt.utils.decorators.external ++ def archives(self): ++ """ ++ Get list of existing archives. ++ :return: ++ """ ++ arc_files = [] ++ tmpdir = tempfile.gettempdir() ++ for filename in os.listdir(tmpdir): ++ mtc = re.match(r"\w+-\w+-\d+-\d+\.bz2", filename) ++ if mtc and len(filename) == mtc.span()[-1]: ++ arc_files.append(os.path.join(tmpdir, filename)) ++ ++ return arc_files ++ ++ @salt.utils.decorators.external ++ def last_archive(self): ++ """ ++ Get the last available archive ++ :return: ++ """ ++ archives = {} ++ for archive in self.archives(): ++ archives[int(archive.split(".")[0].split("-")[-1])] = archive ++ ++ return archives and archives[max(archives)] or None ++ ++ @salt.utils.decorators.external ++ def delete_archives(self, *archives): ++ """ ++ Delete archives ++ :return: ++ """ ++ # Remove paths ++ _archives = [] ++ for archive in archives: ++ _archives.append(os.path.basename(archive)) ++ archives = _archives[:] ++ ++ ret = {"files": {}, "errors": {}} ++ for archive in self.archives(): ++ arc_dir = os.path.dirname(archive) ++ archive = os.path.basename(archive) ++ if archives and archive in archives or not archives: ++ archive = os.path.join(arc_dir, archive) ++ try: ++ os.unlink(archive) ++ ret["files"][archive] = "removed" ++ except Exception as err: ++ ret["errors"][archive] = str(err) ++ ret["files"][archive] = "left" ++ ++ return ret ++ ++ def format_sync_stats(self, cnt): ++ """ ++ Format stats of the sync output. ++ ++ :param cnt: ++ :return: ++ """ ++ stats = salt.utils.odict.OrderedDict() ++ if cnt.get("retcode") == salt.defaults.exitcodes.EX_OK: ++ for line in cnt.get("stdout", "").split(os.linesep): ++ line = line.split(": ") ++ if len(line) == 2: ++ stats[line[0].lower().replace(" ", "_")] = line[1] ++ cnt["transfer"] = stats ++ del cnt["stdout"] ++ ++ # Remove empty ++ empty_sections = [] ++ for section in cnt: ++ if not cnt[section] and section != "retcode": ++ empty_sections.append(section) ++ for section in empty_sections: ++ del cnt[section] ++ ++ return cnt ++ ++ @salt.utils.decorators.depends("rsync") ++ @salt.utils.decorators.external ++ def sync(self, group, name=None, host=None, location=None, move=False, all=False): ++ """ ++ Sync the latest archive to the host on given location. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' support.sync group=test ++ salt '*' support.sync group=test name=/tmp/myspecial-12345-67890.bz2 ++ salt '*' support.sync group=test name=/tmp/myspecial-12345-67890.bz2 host=allmystuff.lan ++ salt '*' support.sync group=test name=/tmp/myspecial-12345-67890.bz2 host=allmystuff.lan location=/opt/ ++ ++ :param group: name of the local directory to which sync is going to put the result files ++ :param name: name of the archive. Latest, if not specified. ++ :param host: name of the destination host for rsync. Default is master, if not specified. ++ :param location: local destination directory, default temporary if not specified ++ :param move: move archive file[s]. Default is False. ++ :param all: work with all available archives. Default is False (i.e. latest available) ++ ++ :return: ++ """ ++ tfh, tfn = tempfile.mkstemp() ++ processed_archives = [] ++ src_uri = uri = None ++ ++ last_arc = self.last_archive() ++ if name: ++ archives = [name] ++ elif all: ++ archives = self.archives() ++ elif last_arc: ++ archives = [last_arc] ++ else: ++ archives = [] ++ ++ for name in archives: ++ err = None ++ if not name: ++ err = "No support archive has been defined." ++ elif not os.path.exists(name): ++ err = 'Support archive "{}" was not found'.format(name) ++ if err is not None: ++ log.error(err) ++ raise salt.exceptions.SaltInvocationError(err) ++ ++ if not uri: ++ src_uri = os.path.dirname(name) ++ uri = "{host}:{loc}".format( ++ host=host or __opts__["master"], ++ loc=os.path.join(location or tempfile.gettempdir(), group), ++ ) ++ ++ os.write(tfh, salt.utils.stringutils.to_bytes(os.path.basename(name))) ++ os.write(tfh, salt.utils.stringutils.to_bytes(os.linesep)) ++ processed_archives.append(name) ++ log.debug("Syncing {filename} to {uri}".format(filename=name, uri=uri)) ++ os.close(tfh) ++ ++ if not processed_archives: ++ raise salt.exceptions.SaltInvocationError("No archives found to transfer.") ++ ++ ret = __salt__["rsync.rsync"]( ++ src=src_uri, ++ dst=uri, ++ additional_opts=["--stats", "--files-from={}".format(tfn)], ++ ) ++ ret["files"] = {} ++ for name in processed_archives: ++ if move: ++ salt.utils.dictupdate.update(ret, self.delete_archives(name)) ++ log.debug("Deleting {filename}".format(filename=name)) ++ ret["files"][name] = "moved" ++ else: ++ ret["files"][name] = "copied" ++ ++ try: ++ os.unlink(tfn) ++ except OSError as err: ++ log.error( ++ "Cannot remove temporary rsync file {fn}: {err}".format(fn=tfn, err=err) ++ ) ++ ++ return self.format_sync_stats(ret) ++ ++ @salt.utils.decorators.external ++ def run(self, profile="default", pillar=None, archive=None, output="nested"): ++ """ ++ Run Salt Support on the minion. ++ ++ profile ++ Set available profile name. Default is "default". ++ ++ pillar ++ Set available profile from the pillars. ++ ++ archive ++ Override archive name. Default is "support". This results to "hostname-support-YYYYMMDD-hhmmss.bz2". ++ ++ output ++ Change the default outputter. Default is "nested". ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' support.run ++ salt '*' support.run profile=network ++ salt '*' support.run pillar=something_special ++ """ ++ ++ class outputswitch: ++ """ ++ Output switcher on context ++ """ ++ ++ def __init__(self, output_device): ++ self._tmp_out = output_device ++ self._orig_out = None ++ ++ def __enter__(self): ++ self._orig_out = salt.cli.support.intfunc.out ++ salt.cli.support.intfunc.out = self._tmp_out ++ ++ def __exit__(self, *args): ++ salt.cli.support.intfunc.out = self._orig_out ++ ++ self.out = LogCollector() ++ with outputswitch(self.out): ++ self.collector = SupportDataCollector( ++ archive or self._get_archive_name(archname=archive), output ++ ) ++ self.collector.out = self.out ++ self.collector.open() ++ self.collect_local_data( ++ profile=profile, profile_source=__pillar__.get(pillar) ++ ) ++ self.collect_internal_data() ++ self.collector.close() ++ ++ return {"archive": self.collector.archive_path, "messages": self.out.messages} ++ ++ ++def __virtual__(): ++ """ ++ Set method references as module functions aliases ++ :return: ++ """ ++ support = SaltSupportModule() ++ ++ def _set_function(obj): ++ """ ++ Create a Salt function for the SaltSupport class. ++ """ ++ ++ def _cmd(*args, **kwargs): ++ """ ++ Call support method as a function from the Salt. ++ """ ++ _kwargs = {} ++ for kw in kwargs: ++ if not kw.startswith("__"): ++ _kwargs[kw] = kwargs[kw] ++ return obj(*args, **_kwargs) ++ ++ _cmd.__doc__ = obj.__doc__ ++ return _cmd ++ ++ for m_name in dir(support): ++ obj = getattr(support, m_name) ++ if getattr(obj, "external", False): ++ setattr(sys.modules[__name__], m_name, _set_function(obj)) ++ ++ return __virtualname__ +diff --git a/salt/scripts.py b/salt/scripts.py +index 07393373c9..16b032af2e 100644 +--- a/salt/scripts.py ++++ b/salt/scripts.py +@@ -622,3 +622,18 @@ def salt_pip(): + ] + _pip_args(sys.argv[1:], extras) + ret = subprocess.run(command, shell=False, check=False, env=env) + sys.exit(ret.returncode) ++ ++ ++def salt_support(): ++ """ ++ Run Salt Support that collects system data, logs etc for debug and support purposes. ++ :return: ++ """ ++ ++ import salt.cli.support.collector ++ ++ if "" in sys.path: ++ sys.path.remove("") ++ client = salt.cli.support.collector.SaltSupport() ++ _install_signal_handlers(client) ++ client.run() +diff --git a/salt/state.py b/salt/state.py +index 868be2749e..8352a8defc 100644 +--- a/salt/state.py ++++ b/salt/state.py +@@ -1671,7 +1671,9 @@ class State: + names = [] + if state.startswith("__"): + continue +- chunk = {"state": state, "name": name} ++ chunk = OrderedDict() ++ chunk["state"] = state ++ chunk["name"] = name + if orchestration_jid is not None: + chunk["__orchestration_jid__"] = orchestration_jid + if "__sls__" in body: +@@ -2382,9 +2384,16 @@ class State: + else: + self.format_slots(cdata) + with salt.utils.files.set_umask(low.get("__umask__")): +- ret = self.states[cdata["full"]]( +- *cdata["args"], **cdata["kwargs"] +- ) ++ if cdata["full"].split(".")[-1] == "__call__": ++ # __call__ requires OrderedDict to preserve state order ++ # kwargs are also invalid overall ++ ret = self.states[cdata["full"]]( ++ cdata["args"], module=None, state=cdata["kwargs"] ++ ) ++ else: ++ ret = self.states[cdata["full"]]( ++ *cdata["args"], **cdata["kwargs"] ++ ) + self.states.inject_globals = {} + if "check_cmd" in low: + state_check_cmd = "{0[state]}.mod_run_check_cmd".format(low) +@@ -3489,10 +3498,31 @@ class State: + running.update(errors) + return running + ++ def inject_default_call(self, high): ++ """ ++ Sets .call function to a state, if not there. ++ ++ :param high: ++ :return: ++ """ ++ for chunk in high: ++ state = high[chunk] ++ for state_ref in state: ++ needs_default = True ++ for argset in state[state_ref]: ++ if isinstance(argset, str): ++ needs_default = False ++ break ++ if needs_default: ++ order = state[state_ref].pop(-1) ++ state[state_ref].append("__call__") ++ state[state_ref].append(order) ++ + def call_high(self, high, orchestration_jid=None): + """ + Process a high data call and ensure the defined states. + """ ++ self.inject_default_call(high) + errors = [] + # If there is extension data reconcile it + high, ext_errors = self.reconcile_extend(high) +diff --git a/salt/states/saltsupport.py b/salt/states/saltsupport.py +new file mode 100644 +index 0000000000..fb0c9e0372 +--- /dev/null ++++ b/salt/states/saltsupport.py +@@ -0,0 +1,225 @@ ++# ++# Author: Bo Maryniuk ++# ++# Copyright 2018 SUSE LLC ++# Licensed under the Apache License, Version 2.0 (the "License"); ++# you may not use this file except in compliance with the License. ++# You may obtain a copy of the License at ++# ++# http://www.apache.org/licenses/LICENSE-2.0 ++# ++# Unless required by applicable law or agreed to in writing, software ++# distributed under the License is distributed on an "AS IS" BASIS, ++# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++# See the License for the specific language governing permissions and ++# limitations under the License. ++ ++r""" ++:codeauthor: :email:`Bo Maryniuk ` ++ ++Execution of Salt Support from within states ++============================================ ++ ++State to collect support data from the systems: ++ ++.. code-block:: yaml ++ ++ examine_my_systems: ++ support.taken: ++ - profile: default ++ ++ support.collected: ++ - group: somewhere ++ - move: true ++ ++""" ++import logging ++import os ++import tempfile ++ ++import salt.exceptions ++ ++# Import salt modules ++import salt.fileclient ++import salt.utils.decorators.path ++import salt.utils.odict ++ ++log = logging.getLogger(__name__) ++__virtualname__ = "support" ++ ++ ++class SaltSupportState: ++ """ ++ Salt-support. ++ """ ++ ++ EXPORTED = ["collected", "taken"] ++ ++ def get_kwargs(self, data): ++ kwargs = {} ++ for keyset in data: ++ kwargs.update(keyset) ++ ++ return kwargs ++ ++ def __call__(self, state): ++ """ ++ Call support. ++ ++ :param args: ++ :param kwargs: ++ :return: ++ """ ++ ret = { ++ "name": state.pop("name"), ++ "changes": {}, ++ "result": True, ++ "comment": "", ++ } ++ ++ out = {} ++ functions = ["Functions:"] ++ try: ++ for ref_func, ref_kwargs in state.items(): ++ if ref_func not in self.EXPORTED: ++ raise salt.exceptions.SaltInvocationError( ++ "Function {} is not found".format(ref_func) ++ ) ++ out[ref_func] = getattr(self, ref_func)(**self.get_kwargs(ref_kwargs)) ++ functions.append(" - {}".format(ref_func)) ++ ret["comment"] = "\n".join(functions) ++ except Exception as ex: ++ ret["comment"] = str(ex) ++ ret["result"] = False ++ ret["changes"] = out ++ ++ return ret ++ ++ def check_destination(self, location, group): ++ """ ++ Check destination for the archives. ++ :return: ++ """ ++ # Pre-create destination, since rsync will ++ # put one file named as group ++ try: ++ destination = os.path.join(location, group) ++ if os.path.exists(destination) and not os.path.isdir(destination): ++ raise salt.exceptions.SaltException( ++ 'Destination "{}" should be directory!'.format(destination) ++ ) ++ if not os.path.exists(destination): ++ os.makedirs(destination) ++ log.debug("Created destination directory for archives: %s", destination) ++ else: ++ log.debug( ++ "Archives destination directory %s already exists", destination ++ ) ++ except OSError as err: ++ log.error(err) ++ ++ def collected( ++ self, group, filename=None, host=None, location=None, move=True, all=True ++ ): ++ """ ++ Sync archives to a central place. ++ ++ :param name: ++ :param group: ++ :param filename: ++ :param host: ++ :param location: ++ :param move: ++ :param all: ++ :return: ++ """ ++ ret = { ++ "name": "support.collected", ++ "changes": {}, ++ "result": True, ++ "comment": "", ++ } ++ location = location or tempfile.gettempdir() ++ self.check_destination(location, group) ++ ret["changes"] = __salt__["support.sync"]( ++ group, name=filename, host=host, location=location, move=move, all=all ++ ) ++ ++ return ret ++ ++ def taken(self, profile="default", pillar=None, archive=None, output="nested"): ++ """ ++ Takes minion support config data. ++ ++ :param profile: ++ :param pillar: ++ :param archive: ++ :param output: ++ :return: ++ """ ++ ret = { ++ "name": "support.taken", ++ "changes": {}, ++ "result": True, ++ } ++ ++ result = __salt__["support.run"]( ++ profile=profile, pillar=pillar, archive=archive, output=output ++ ) ++ if result.get("archive"): ++ ret[ ++ "comment" ++ ] = "Information about this system has been saved to {} file.".format( ++ result["archive"] ++ ) ++ ret["changes"]["archive"] = result["archive"] ++ ret["changes"]["messages"] = {} ++ for key in ["info", "error", "warning"]: ++ if result.get("messages", {}).get(key): ++ ret["changes"]["messages"][key] = result["messages"][key] ++ else: ++ ret["comment"] = "" ++ ++ return ret ++ ++ ++_support_state = SaltSupportState() ++ ++ ++def __call__(*args, **kwargs): ++ """ ++ SLS single-ID syntax processing. ++ ++ module: ++ This module reference, equals to sys.modules[__name__] ++ ++ state: ++ Compiled state in preserved order. The function supposed to look ++ at first level array of functions. ++ ++ :param cdata: ++ :param kwargs: ++ :return: ++ """ ++ return _support_state(kwargs.get("state", {})) ++ ++ ++def taken(name, profile="default", pillar=None, archive=None, output="nested"): ++ return _support_state.taken( ++ profile=profile, pillar=pillar, archive=archive, output=output ++ ) ++ ++ ++def collected( ++ name, group, filename=None, host=None, location=None, move=True, all=True ++): ++ return _support_state.collected( ++ group=group, filename=filename, host=host, location=location, move=move, all=all ++ ) ++ ++ ++def __virtual__(): ++ """ ++ Salt Support state ++ """ ++ return __virtualname__ +diff --git a/salt/utils/args.py b/salt/utils/args.py +index 536aea3816..04a8a14054 100644 +--- a/salt/utils/args.py ++++ b/salt/utils/args.py +@@ -15,6 +15,7 @@ import salt.utils.jid + import salt.utils.versions + import salt.utils.yaml + from salt.exceptions import SaltInvocationError ++from salt.utils.odict import OrderedDict + + log = logging.getLogger(__name__) + +@@ -399,7 +400,7 @@ def format_call( + ret = initial_ret is not None and initial_ret or {} + + ret["args"] = [] +- ret["kwargs"] = {} ++ ret["kwargs"] = OrderedDict() + + aspec = get_function_argspec(fun, is_class_method=is_class_method) + +diff --git a/salt/utils/decorators/__init__.py b/salt/utils/decorators/__init__.py +index 1f62d5f3d6..1906cc2ecc 100644 +--- a/salt/utils/decorators/__init__.py ++++ b/salt/utils/decorators/__init__.py +@@ -866,3 +866,27 @@ def ensure_unicode_args(function): + return function(*args, **kwargs) + + return wrapped ++ ++ ++def external(func): ++ """ ++ Mark function as external. ++ ++ :param func: ++ :return: ++ """ ++ ++ def f(*args, **kwargs): ++ """ ++ Stub. ++ ++ :param args: ++ :param kwargs: ++ :return: ++ """ ++ return func(*args, **kwargs) ++ ++ f.external = True ++ f.__doc__ = func.__doc__ ++ ++ return f +diff --git a/salt/utils/parsers.py b/salt/utils/parsers.py +index 911b2cbb04..dc125de7d7 100644 +--- a/salt/utils/parsers.py ++++ b/salt/utils/parsers.py +@@ -17,6 +17,7 @@ import optparse + import os + import signal + import sys ++import tempfile + import traceback + import types + from functools import partial +@@ -31,6 +32,7 @@ import salt.utils.args + import salt.utils.data + import salt.utils.files + import salt.utils.jid ++import salt.utils.network + import salt.utils.platform + import salt.utils.process + import salt.utils.stringutils +@@ -2026,6 +2028,118 @@ class SyndicOptionParser( + return opts + + ++class SaltSupportOptionParser( ++ OptionParser, ++ ConfigDirMixIn, ++ MergeConfigMixIn, ++ LogLevelMixIn, ++ TimeoutMixIn, ++ metaclass=OptionParserMeta, ++): ++ default_timeout = 5 ++ description = "Salt Support is a program to collect all support data: logs, system configuration etc." ++ usage = "%prog [options] '' [arguments]" ++ # ConfigDirMixIn config filename attribute ++ _config_filename_ = "master" ++ ++ # LogLevelMixIn attributes ++ _default_logging_level_ = config.DEFAULT_MASTER_OPTS["log_level"] ++ _default_logging_logfile_ = config.DEFAULT_MASTER_OPTS["log_file"] ++ ++ def _mixin_setup(self): ++ self.add_option( ++ "-P", ++ "--show-profiles", ++ default=False, ++ action="store_true", ++ dest="support_profile_list", ++ help="Show available profiles", ++ ) ++ self.add_option( ++ "-p", ++ "--profile", ++ default="", ++ dest="support_profile", ++ help='Specify support profile or comma-separated profiles, e.g.: "salt,network"', ++ ) ++ support_archive = "{t}/{h}-support.tar.bz2".format( ++ t=tempfile.gettempdir(), h=salt.utils.network.get_fqhostname() ++ ) ++ self.add_option( ++ "-a", ++ "--archive", ++ default=support_archive, ++ dest="support_archive", ++ help=( ++ "Specify name of the resulting support archive. " ++ 'Default is "{f}".'.format(f=support_archive) ++ ), ++ ) ++ self.add_option( ++ "-u", ++ "--unit", ++ default="", ++ dest="support_unit", ++ help='Specify examined unit (default "master").', ++ ) ++ self.add_option( ++ "-U", ++ "--show-units", ++ default=False, ++ action="store_true", ++ dest="support_show_units", ++ help="Show available units", ++ ) ++ self.add_option( ++ "-f", ++ "--force", ++ default=False, ++ action="store_true", ++ dest="support_archive_force_overwrite", ++ help="Force overwrite existing archive, if exists", ++ ) ++ self.add_option( ++ "-o", ++ "--out", ++ default="null", ++ dest="support_output_format", ++ help=( ++ "Set the default output using the specified outputter, " ++ 'unless profile does not overrides this. Default: "yaml".' ++ ), ++ ) ++ ++ def find_existing_configs(self, default): ++ """ ++ Find configuration files on the system. ++ :return: ++ """ ++ configs = [] ++ for cfg in [default, self._config_filename_, "minion", "proxy", "cloud", "spm"]: ++ if not cfg: ++ continue ++ config_path = self.get_config_file_path(cfg) ++ if os.path.exists(config_path): ++ configs.append(cfg) ++ ++ if default and default not in configs: ++ raise SystemExit("Unknown configuration unit: {}".format(default)) ++ ++ return configs ++ ++ def setup_config(self, cfg=None): ++ """ ++ Open suitable config file. ++ :return: ++ """ ++ _opts, _args = optparse.OptionParser.parse_args(self) ++ configs = self.find_existing_configs(_opts.support_unit) ++ if configs and cfg not in configs: ++ cfg = configs[0] ++ ++ return config.master_config(self.get_config_file_path(cfg)) ++ ++ + class SaltCMDOptionParser( + OptionParser, + ConfigDirMixIn, +diff --git a/scripts/salt-support b/scripts/salt-support +new file mode 100755 +index 0000000000..4e0e79f3ea +--- /dev/null ++++ b/scripts/salt-support +@@ -0,0 +1,11 @@ ++#!/usr/bin/env python ++""" ++Salt support is to collect logs, ++debug data and system information ++for support purposes. ++""" ++ ++from salt.scripts import salt_support ++ ++if __name__ == "__main__": ++ salt_support() +diff --git a/setup.py b/setup.py +index 931ed40a51..e60f1b7085 100755 +--- a/setup.py ++++ b/setup.py +@@ -1061,6 +1061,7 @@ class SaltDistribution(distutils.dist.Distribution): + "scripts/salt-minion", + "scripts/salt-proxy", + "scripts/salt-run", ++ "scripts/salt-support", + "scripts/salt-ssh", + "scripts/salt-syndic", + "scripts/spm", +@@ -1109,6 +1110,7 @@ class SaltDistribution(distutils.dist.Distribution): + "salt-master = salt.scripts:salt_master", + "salt-minion = salt.scripts:salt_minion", + "salt-run = salt.scripts:salt_run", ++ "salt-support = salt.scripts:salt_support", + "salt-ssh = salt.scripts:salt_ssh", + "salt-syndic = salt.scripts:salt_syndic", + "spm = salt.scripts:salt_spm", +diff --git a/tests/pytests/unit/cli/test_support.py b/tests/pytests/unit/cli/test_support.py +new file mode 100644 +index 0000000000..dc0e99bb3d +--- /dev/null ++++ b/tests/pytests/unit/cli/test_support.py +@@ -0,0 +1,553 @@ ++""" ++ :codeauthor: Bo Maryniuk ++""" ++ ++ ++import os ++ ++import jinja2 ++import salt.cli.support.collector ++import salt.exceptions ++import salt.utils.files ++import yaml ++from salt.cli.support.collector import SaltSupport, SupportDataCollector ++from salt.cli.support.console import IndentOutput ++from salt.utils.color import get_colors ++from salt.utils.stringutils import to_bytes ++from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch ++from tests.support.unit import TestCase, skipIf ++ ++try: ++ import pytest ++except ImportError: ++ pytest = None ++ ++ ++@skipIf(not bool(pytest), "Pytest needs to be installed") ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class SaltSupportIndentOutputTestCase(TestCase): ++ """ ++ Unit Tests for the salt-support indent output. ++ """ ++ ++ def setUp(self): ++ """ ++ Setup test ++ :return: ++ """ ++ ++ self.message = "Stubborn processes on dumb terminal" ++ self.device = MagicMock() ++ self.iout = IndentOutput(device=self.device) ++ self.colors = get_colors() ++ ++ def tearDown(self): ++ """ ++ Remove instances after test run ++ :return: ++ """ ++ del self.message ++ del self.device ++ del self.iout ++ del self.colors ++ ++ def test_standard_output(self): ++ """ ++ Test console standard output. ++ """ ++ self.iout.put(self.message) ++ assert self.device.write.called ++ assert self.device.write.call_count == 5 ++ for idx, data in enumerate( ++ ["", str(self.colors["CYAN"]), self.message, str(self.colors["ENDC"]), "\n"] ++ ): ++ assert self.device.write.call_args_list[idx][0][0] == data ++ ++ def test_indent_output(self): ++ """ ++ Test indent distance. ++ :return: ++ """ ++ self.iout.put(self.message, indent=10) ++ for idx, data in enumerate( ++ [ ++ " " * 10, ++ str(self.colors["CYAN"]), ++ self.message, ++ str(self.colors["ENDC"]), ++ "\n", ++ ] ++ ): ++ assert self.device.write.call_args_list[idx][0][0] == data ++ ++ def test_color_config(self): ++ """ ++ Test color config changes on each ident. ++ :return: ++ """ ++ ++ conf = {0: "MAGENTA", 2: "RED", 4: "WHITE", 6: "YELLOW"} ++ self.iout = IndentOutput(conf=conf, device=self.device) ++ for indent in sorted(list(conf)): ++ self.iout.put(self.message, indent=indent) ++ ++ step = 1 ++ for ident_key in sorted(list(conf)): ++ assert str(self.device.write.call_args_list[step][0][0]) == str( ++ self.colors[conf[ident_key]] ++ ) ++ step += 5 ++ ++ ++@skipIf(not bool(pytest), "Pytest needs to be installed") ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class SaltSupportCollectorTestCase(TestCase): ++ """ ++ Collector tests. ++ """ ++ ++ def setUp(self): ++ """ ++ Setup the test case ++ :return: ++ """ ++ self.archive_path = "/highway/to/hell" ++ self.output_device = MagicMock() ++ self.collector = SupportDataCollector(self.archive_path, self.output_device) ++ ++ def tearDown(self): ++ """ ++ Tear down the test case elements ++ :return: ++ """ ++ del self.collector ++ del self.archive_path ++ del self.output_device ++ ++ @patch("salt.cli.support.collector.tarfile.TarFile", MagicMock()) ++ def test_archive_open(self): ++ """ ++ Test archive is opened. ++ ++ :return: ++ """ ++ self.collector.open() ++ assert self.collector.archive_path == self.archive_path ++ with pytest.raises(salt.exceptions.SaltException) as err: ++ self.collector.open() ++ assert "Archive already opened" in str(err) ++ ++ @patch("salt.cli.support.collector.tarfile.TarFile", MagicMock()) ++ def test_archive_close(self): ++ """ ++ Test archive is opened. ++ ++ :return: ++ """ ++ self.collector.open() ++ self.collector._flush_content = lambda: None ++ self.collector.close() ++ assert self.collector.archive_path == self.archive_path ++ with pytest.raises(salt.exceptions.SaltException) as err: ++ self.collector.close() ++ assert "Archive already closed" in str(err) ++ ++ def test_archive_addwrite(self): ++ """ ++ Test add to the archive a section and write to it. ++ ++ :return: ++ """ ++ archive = MagicMock() ++ with patch("salt.cli.support.collector.tarfile.TarFile", archive): ++ self.collector.open() ++ self.collector.add("foo") ++ self.collector.write(title="title", data="data", output="null") ++ self.collector._flush_content() ++ ++ assert archive.bz2open().addfile.call_args[1]["fileobj"].read() == to_bytes( ++ "title\n-----\n\nraw-content: data\n\n\n\n" ++ ) ++ ++ @patch("salt.utils.files.fopen", MagicMock(return_value="path=/dev/null")) ++ def test_archive_addlink(self): ++ """ ++ Test add to the archive a section and link an external file or directory to it. ++ ++ :return: ++ """ ++ archive = MagicMock() ++ with patch("salt.cli.support.collector.tarfile.TarFile", archive): ++ self.collector.open() ++ self.collector.add("foo") ++ self.collector.link(title="Backup Path", path="/path/to/backup.config") ++ self.collector._flush_content() ++ ++ assert archive.bz2open().addfile.call_count == 1 ++ assert archive.bz2open().addfile.call_args[1]["fileobj"].read() == to_bytes( ++ "Backup Path\n-----------\n\npath=/dev/null\n\n\n" ++ ) ++ ++ @patch("salt.utils.files.fopen", MagicMock(return_value="path=/dev/null")) ++ def test_archive_discard_section(self): ++ """ ++ Test discard a section from the archive. ++ ++ :return: ++ """ ++ archive = MagicMock() ++ with patch("salt.cli.support.collector.tarfile.TarFile", archive): ++ self.collector.open() ++ self.collector.add("solar-interference") ++ self.collector.link( ++ title="Thermal anomaly", path="/path/to/another/great.config" ++ ) ++ self.collector.add("foo") ++ self.collector.link(title="Backup Path", path="/path/to/backup.config") ++ self.collector._flush_content() ++ assert archive.bz2open().addfile.call_count == 2 ++ assert archive.bz2open().addfile.mock_calls[0][2][ ++ "fileobj" ++ ].read() == to_bytes( ++ "Thermal anomaly\n---------------\n\npath=/dev/null\n\n\n" ++ ) ++ self.collector.close() ++ ++ archive = MagicMock() ++ with patch("salt.cli.support.collector.tarfile.TarFile", archive): ++ self.collector.open() ++ self.collector.add("solar-interference") ++ self.collector.link( ++ title="Thermal anomaly", path="/path/to/another/great.config" ++ ) ++ self.collector.discard_current() ++ self.collector.add("foo") ++ self.collector.link(title="Backup Path", path="/path/to/backup.config") ++ self.collector._flush_content() ++ assert archive.bz2open().addfile.call_count == 2 ++ assert archive.bz2open().addfile.mock_calls[0][2][ ++ "fileobj" ++ ].read() == to_bytes("Backup Path\n-----------\n\npath=/dev/null\n\n\n") ++ self.collector.close() ++ ++ ++@skipIf(not bool(pytest), "Pytest needs to be installed") ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class SaltSupportRunnerTestCase(TestCase): ++ """ ++ Test runner class. ++ """ ++ ++ def setUp(self): ++ """ ++ Set up test suite. ++ :return: ++ """ ++ self.archive_path = "/dev/null" ++ self.output_device = MagicMock() ++ self.runner = SaltSupport() ++ self.runner.collector = SupportDataCollector( ++ self.archive_path, self.output_device ++ ) ++ ++ def tearDown(self): ++ """ ++ Tear down. ++ ++ :return: ++ """ ++ del self.archive_path ++ del self.output_device ++ del self.runner ++ ++ def test_function_config(self): ++ """ ++ Test function config formation. ++ ++ :return: ++ """ ++ self.runner.config = {} ++ msg = "Electromagnetic energy loss" ++ assert self.runner._setup_fun_config({"description": msg}) == { ++ "print_metadata": False, ++ "file_client": "local", ++ "fun": "", ++ "kwarg": {}, ++ "description": msg, ++ "cache_jobs": False, ++ "arg": [], ++ } ++ ++ def test_local_caller(self): ++ """ ++ Test local caller. ++ ++ :return: ++ """ ++ msg = "Because of network lag due to too many people playing deathmatch" ++ caller = MagicMock() ++ caller().call = MagicMock(return_value=msg) ++ ++ self.runner._get_caller = caller ++ self.runner.out = MagicMock() ++ assert self.runner._local_call({}) == msg ++ ++ caller().call = MagicMock(side_effect=SystemExit) ++ assert self.runner._local_call({}) == "Data is not available at this moment" ++ ++ err_msg = "The UPS doesn't have a battery backup." ++ caller().call = MagicMock(side_effect=Exception(err_msg)) ++ assert ( ++ self.runner._local_call({}) ++ == "Unhandled exception occurred: The UPS doesn't have a battery backup." ++ ) ++ ++ def test_local_runner(self): ++ """ ++ Test local runner. ++ ++ :return: ++ """ ++ msg = "Big to little endian conversion error" ++ runner = MagicMock() ++ runner().run = MagicMock(return_value=msg) ++ ++ self.runner._get_runner = runner ++ self.runner.out = MagicMock() ++ assert self.runner._local_run({}) == msg ++ ++ runner().run = MagicMock(side_effect=SystemExit) ++ assert self.runner._local_run({}) == "Runner is not available at this moment" ++ ++ err_msg = "Trojan horse ran out of hay" ++ runner().run = MagicMock(side_effect=Exception(err_msg)) ++ assert ( ++ self.runner._local_run({}) ++ == "Unhandled exception occurred: Trojan horse ran out of hay" ++ ) ++ ++ @patch("salt.cli.support.intfunc", MagicMock(spec=[])) ++ def test_internal_function_call_stub(self): ++ """ ++ Test missing internal function call is handled accordingly. ++ ++ :return: ++ """ ++ self.runner.out = MagicMock() ++ out = self.runner._internal_function_call( ++ {"fun": "everythingisawesome", "arg": [], "kwargs": {}} ++ ) ++ assert out == "Function everythingisawesome is not available" ++ ++ def test_internal_function_call(self): ++ """ ++ Test missing internal function call is handled accordingly. ++ ++ :return: ++ """ ++ msg = "Internet outage" ++ intfunc = MagicMock() ++ intfunc.everythingisawesome = MagicMock(return_value=msg) ++ self.runner.out = MagicMock() ++ with patch("salt.cli.support.intfunc", intfunc): ++ out = self.runner._internal_function_call( ++ {"fun": "everythingisawesome", "arg": [], "kwargs": {}} ++ ) ++ assert out == msg ++ ++ def test_get_action(self): ++ """ ++ Test action meta gets parsed. ++ ++ :return: ++ """ ++ action_meta = { ++ "run:jobs.list_jobs_filter": {"info": "List jobs filter", "args": [1]} ++ } ++ assert self.runner._get_action(action_meta) == ( ++ "List jobs filter", ++ None, ++ {"fun": "run:jobs.list_jobs_filter", "kwargs": {}, "arg": [1]}, ++ ) ++ action_meta = { ++ "user.info": {"info": 'Information about "usbmux"', "args": ["usbmux"]} ++ } ++ assert self.runner._get_action(action_meta) == ( ++ 'Information about "usbmux"', ++ None, ++ {"fun": "user.info", "kwargs": {}, "arg": ["usbmux"]}, ++ ) ++ ++ def test_extract_return(self): ++ """ ++ Test extract return from the output. ++ ++ :return: ++ """ ++ out = {"key": "value"} ++ assert self.runner._extract_return(out) == out ++ assert self.runner._extract_return({"return": out}) == out ++ ++ def test_get_action_type(self): ++ """ ++ Test action meta determines action type. ++ ++ :return: ++ """ ++ action_meta = { ++ "run:jobs.list_jobs_filter": {"info": "List jobs filter", "args": [1]} ++ } ++ assert self.runner._get_action_type(action_meta) == "run" ++ ++ action_meta = { ++ "user.info": {"info": 'Information about "usbmux"', "args": ["usbmux"]} ++ } ++ assert self.runner._get_action_type(action_meta) == "call" ++ ++ @patch("os.path.exists", MagicMock(return_value=True)) ++ def test_cleanup(self): ++ """ ++ Test cleanup routine. ++ ++ :return: ++ """ ++ arch = "/tmp/killme.zip" ++ unlink = MagicMock() ++ with patch("os.unlink", unlink): ++ self.runner.config = {"support_archive": arch} ++ self.runner.out = MagicMock() ++ self.runner._cleanup() ++ ++ assert ( ++ self.runner.out.warning.call_args[0][0] ++ == "Terminated earlier, cleaning up" ++ ) ++ unlink.assert_called_once_with(arch) ++ ++ @patch("os.path.exists", MagicMock(return_value=True)) ++ def test_check_existing_archive(self): ++ """ ++ Test check existing archive. ++ ++ :return: ++ """ ++ arch = "/tmp/endothermal-recalibration.zip" ++ unlink = MagicMock() ++ with patch("os.unlink", unlink), patch( ++ "os.path.exists", MagicMock(return_value=False) ++ ): ++ self.runner.config = { ++ "support_archive": "", ++ "support_archive_force_overwrite": True, ++ } ++ self.runner.out = MagicMock() ++ assert self.runner._check_existing_archive() ++ assert self.runner.out.warning.call_count == 0 ++ ++ with patch("os.unlink", unlink): ++ self.runner.config = { ++ "support_archive": arch, ++ "support_archive_force_overwrite": False, ++ } ++ self.runner.out = MagicMock() ++ assert not self.runner._check_existing_archive() ++ assert self.runner.out.warning.call_args[0][ ++ 0 ++ ] == "File {} already exists.".format(arch) ++ ++ with patch("os.unlink", unlink): ++ self.runner.config = { ++ "support_archive": arch, ++ "support_archive_force_overwrite": True, ++ } ++ self.runner.out = MagicMock() ++ assert self.runner._check_existing_archive() ++ assert self.runner.out.warning.call_args[0][ ++ 0 ++ ] == "Overwriting existing archive: {}".format(arch) ++ ++ ++@skipIf(not bool(pytest), "Pytest needs to be installed") ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class ProfileIntegrityTestCase(TestCase): ++ """ ++ Default profile integrity ++ """ ++ ++ def setUp(self): ++ """ ++ Set up test suite. ++ ++ :return: ++ """ ++ self.profiles = {} ++ profiles = os.path.join( ++ os.path.dirname(salt.cli.support.collector.__file__), "profiles" ++ ) ++ for profile in os.listdir(profiles): ++ self.profiles[profile.split(".")[0]] = os.path.join(profiles, profile) ++ ++ def tearDown(self): ++ """ ++ Tear down test suite. ++ ++ :return: ++ """ ++ del self.profiles ++ ++ def _render_template_to_yaml(self, name, *args, **kwargs): ++ """ ++ Get template referene for rendering. ++ :return: ++ """ ++ with salt.utils.files.fopen(self.profiles[name]) as t_fh: ++ template = t_fh.read() ++ return yaml.load( ++ jinja2.Environment().from_string(template).render(*args, **kwargs) ++ ) ++ ++ def test_non_template_profiles_parseable(self): ++ """ ++ Test shipped default profile is YAML parse-able. ++ ++ :return: ++ """ ++ for t_name in ["default", "jobs-active", "jobs-last", "network", "postgres"]: ++ with salt.utils.files.fopen(self.profiles[t_name]) as ref: ++ try: ++ yaml.load(ref) ++ parsed = True ++ except Exception: ++ parsed = False ++ assert parsed ++ ++ def test_users_template_profile(self): ++ """ ++ Test users template profile. ++ ++ :return: ++ """ ++ users_data = self._render_template_to_yaml( ++ "users", salt=MagicMock(return_value=["pokemon"]) ++ ) ++ assert len(users_data["all-users"]) == 5 ++ for user_data in users_data["all-users"]: ++ for tgt in ["user.list_groups", "shadow.info", "cron.raw_cron"]: ++ if tgt in user_data: ++ assert user_data[tgt]["args"] == ["pokemon"] ++ ++ def test_jobs_trace_template_profile(self): ++ """ ++ Test jobs-trace template profile. ++ ++ :return: ++ """ ++ jobs_trace = self._render_template_to_yaml( ++ "jobs-trace", runners=MagicMock(return_value=["0000"]) ++ ) ++ assert len(jobs_trace["jobs-details"]) == 1 ++ assert ( ++ jobs_trace["jobs-details"][0]["run:jobs.list_job"]["info"] ++ == "Details on JID 0000" ++ ) ++ assert jobs_trace["jobs-details"][0]["run:jobs.list_job"]["args"] == [0] +diff --git a/tests/unit/modules/test_saltsupport.py b/tests/unit/modules/test_saltsupport.py +new file mode 100644 +index 0000000000..f9ce7be29a +--- /dev/null ++++ b/tests/unit/modules/test_saltsupport.py +@@ -0,0 +1,496 @@ ++""" ++ :codeauthor: Bo Maryniuk ++""" ++ ++ ++import datetime ++ ++import salt.exceptions ++from salt.modules import saltsupport ++from tests.support.mixins import LoaderModuleMockMixin ++from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch ++from tests.support.unit import TestCase, skipIf ++ ++try: ++ import pytest ++except ImportError: ++ pytest = None ++ ++ ++@skipIf(not bool(pytest), "Pytest required") ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class SaltSupportModuleTestCase(TestCase, LoaderModuleMockMixin): ++ """ ++ Test cases for salt.modules.support::SaltSupportModule ++ """ ++ ++ def setup_loader_modules(self): ++ return {saltsupport: {}} ++ ++ @patch("tempfile.gettempdir", MagicMock(return_value="/mnt/storage")) ++ @patch("salt.modules.saltsupport.__grains__", {"fqdn": "c-3po"}) ++ @patch("time.strftime", MagicMock(return_value="000")) ++ def test_get_archive_name(self): ++ """ ++ Test archive name construction. ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ assert support._get_archive_name() == "/mnt/storage/c-3po-support-000-000.bz2" ++ ++ @patch("tempfile.gettempdir", MagicMock(return_value="/mnt/storage")) ++ @patch("salt.modules.saltsupport.__grains__", {"fqdn": "c-3po"}) ++ @patch("time.strftime", MagicMock(return_value="000")) ++ def test_get_custom_archive_name(self): ++ """ ++ Test get custom archive name. ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ temp_name = support._get_archive_name(archname="Darth Wader") ++ assert temp_name == "/mnt/storage/c-3po-darthwader-000-000.bz2" ++ temp_name = support._get_archive_name(archname="Яйця з сіллю") ++ assert temp_name == "/mnt/storage/c-3po-support-000-000.bz2" ++ temp_name = support._get_archive_name(archname="!@#$%^&*()Fillip J. Fry") ++ assert temp_name == "/mnt/storage/c-3po-fillipjfry-000-000.bz2" ++ ++ @patch( ++ "salt.cli.support.get_profiles", ++ MagicMock(return_value={"message": "Feature was not beta tested"}), ++ ) ++ def test_profiles_format(self): ++ """ ++ Test profiles format. ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ profiles = support.profiles() ++ assert "custom" in profiles ++ assert "standard" in profiles ++ assert "message" in profiles["standard"] ++ assert profiles["custom"] == [] ++ assert profiles["standard"]["message"] == "Feature was not beta tested" ++ ++ @patch("tempfile.gettempdir", MagicMock(return_value="/mnt/storage")) ++ @patch( ++ "os.listdir", ++ MagicMock( ++ return_value=[ ++ "one-support-000-000.bz2", ++ "two-support-111-111.bz2", ++ "trash.bz2", ++ "hostname-000-000.bz2", ++ "three-support-wrong222-222.bz2", ++ "000-support-000-000.bz2", ++ ] ++ ), ++ ) ++ def test_get_existing_archives(self): ++ """ ++ Get list of existing archives. ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ out = support.archives() ++ assert len(out) == 3 ++ for name in [ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/000-support-000-000.bz2", ++ ]: ++ assert name in out ++ ++ def test_last_archive(self): ++ """ ++ Get last archive name ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock( ++ return_value=[ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/three-support-222-222.bz2", ++ ] ++ ) ++ assert support.last_archive() == "/mnt/storage/three-support-222-222.bz2" ++ ++ @patch("os.unlink", MagicMock(return_value=True)) ++ def test_delete_all_archives_success(self): ++ """ ++ Test delete archives ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock( ++ return_value=[ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/three-support-222-222.bz2", ++ ] ++ ) ++ ret = support.delete_archives() ++ assert "files" in ret ++ assert "errors" in ret ++ assert not bool(ret["errors"]) ++ assert bool(ret["files"]) ++ assert isinstance(ret["errors"], dict) ++ assert isinstance(ret["files"], dict) ++ ++ for arc in support.archives(): ++ assert ret["files"][arc] == "removed" ++ ++ @patch( ++ "os.unlink", ++ MagicMock( ++ return_value=False, ++ side_effect=[ ++ OSError("Decreasing electron flux"), ++ OSError("Solar flares interference"), ++ None, ++ ], ++ ), ++ ) ++ def test_delete_all_archives_failure(self): ++ """ ++ Test delete archives failure ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock( ++ return_value=[ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/three-support-222-222.bz2", ++ ] ++ ) ++ ret = support.delete_archives() ++ assert "files" in ret ++ assert "errors" in ret ++ assert bool(ret["errors"]) ++ assert bool(ret["files"]) ++ assert isinstance(ret["errors"], dict) ++ assert isinstance(ret["files"], dict) ++ ++ assert ret["files"]["/mnt/storage/three-support-222-222.bz2"] == "removed" ++ assert ret["files"]["/mnt/storage/one-support-000-000.bz2"] == "left" ++ assert ret["files"]["/mnt/storage/two-support-111-111.bz2"] == "left" ++ ++ assert len(ret["errors"]) == 2 ++ assert ( ++ ret["errors"]["/mnt/storage/one-support-000-000.bz2"] ++ == "Decreasing electron flux" ++ ) ++ assert ( ++ ret["errors"]["/mnt/storage/two-support-111-111.bz2"] ++ == "Solar flares interference" ++ ) ++ ++ def test_format_sync_stats(self): ++ """ ++ Test format rsync stats for preserving ordering of the keys ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ stats = """ ++robot: Bender ++cute: Leela ++weird: Zoidberg ++professor: Farnsworth ++ """ ++ f_stats = support.format_sync_stats({"retcode": 0, "stdout": stats}) ++ assert list(f_stats["transfer"].keys()) == [ ++ "robot", ++ "cute", ++ "weird", ++ "professor", ++ ] ++ assert list(f_stats["transfer"].values()) == [ ++ "Bender", ++ "Leela", ++ "Zoidberg", ++ "Farnsworth", ++ ] ++ ++ @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) ++ @patch("os.close", MagicMock()) ++ def test_sync_no_archives_failure(self): ++ """ ++ Test sync failed when no archives specified. ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock(return_value=[]) ++ ++ with pytest.raises(salt.exceptions.SaltInvocationError) as err: ++ support.sync("group-name") ++ assert "No archives found to transfer" in str(err) ++ ++ @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) ++ @patch("os.path.exists", MagicMock(return_value=False)) ++ def test_sync_last_picked_archive_not_found_failure(self): ++ """ ++ Test sync failed when archive was not found (last picked) ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock( ++ return_value=[ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/three-support-222-222.bz2", ++ ] ++ ) ++ ++ 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) ++ ) ++ ++ @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) ++ @patch("os.path.exists", MagicMock(return_value=False)) ++ def test_sync_specified_archive_not_found_failure(self): ++ """ ++ Test sync failed when archive was not found (last picked) ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock( ++ return_value=[ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/three-support-222-222.bz2", ++ ] ++ ) ++ ++ 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) ++ ++ @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) ++ @patch("os.path.exists", MagicMock(return_value=False)) ++ @patch("os.close", MagicMock()) ++ def test_sync_no_archive_to_transfer_failure(self): ++ """ ++ Test sync failed when no archive was found to transfer ++ ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock(return_value=[]) ++ with pytest.raises(salt.exceptions.SaltInvocationError) as err: ++ support.sync("group-name", all=True) ++ assert "No archives found to transfer" in str(err) ++ ++ @patch("tempfile.mkstemp", MagicMock(return_value=(0, "dummy"))) ++ @patch("os.path.exists", MagicMock(return_value=True)) ++ @patch("os.close", MagicMock()) ++ @patch("os.write", MagicMock()) ++ @patch("os.unlink", MagicMock()) ++ @patch( ++ "salt.modules.saltsupport.__salt__", {"rsync.rsync": MagicMock(return_value={})} ++ ) ++ def test_sync_archives(self): ++ """ ++ Test sync archives ++ :return: ++ """ ++ support = saltsupport.SaltSupportModule() ++ support.archives = MagicMock( ++ return_value=[ ++ "/mnt/storage/one-support-000-000.bz2", ++ "/mnt/storage/two-support-111-111.bz2", ++ "/mnt/storage/three-support-222-222.bz2", ++ ] ++ ) ++ out = support.sync("group-name", host="buzz", all=True, move=False) ++ assert "files" in out ++ for arc_name in out["files"]: ++ assert out["files"][arc_name] == "copied" ++ assert saltsupport.os.unlink.call_count == 1 ++ assert saltsupport.os.unlink.call_args_list[0][0][0] == "dummy" ++ calls = [] ++ for call in saltsupport.os.write.call_args_list: ++ assert len(call) == 2 ++ calls.append(call[0]) ++ assert calls == [ ++ (0, b"one-support-000-000.bz2"), ++ (0, b"\n"), ++ (0, b"two-support-111-111.bz2"), ++ (0, b"\n"), ++ (0, b"three-support-222-222.bz2"), ++ (0, b"\n"), ++ ] ++ ++ @patch("salt.modules.saltsupport.__pillar__", {}) ++ @patch("salt.modules.saltsupport.SupportDataCollector", MagicMock()) ++ def test_run_support(self): ++ """ ++ Test run support ++ :return: ++ """ ++ saltsupport.SupportDataCollector(None, None).archive_path = "dummy" ++ support = saltsupport.SaltSupportModule() ++ support.collect_internal_data = MagicMock() ++ support.collect_local_data = MagicMock() ++ out = support.run() ++ ++ for section in ["messages", "archive"]: ++ assert section in out ++ assert out["archive"] == "dummy" ++ for section in ["warning", "error", "info"]: ++ assert section in out["messages"] ++ ld_call = support.collect_local_data.call_args_list[0][1] ++ assert "profile" in ld_call ++ assert ld_call["profile"] == "default" ++ assert "profile_source" in ld_call ++ assert ld_call["profile_source"] is None ++ assert support.collector.open.call_count == 1 ++ assert support.collector.close.call_count == 1 ++ assert support.collect_internal_data.call_count == 1 ++ ++ ++@skipIf(not bool(pytest), "Pytest required") ++@skipIf(NO_MOCK, NO_MOCK_REASON) ++class LogCollectorTestCase(TestCase, LoaderModuleMockMixin): ++ """ ++ Test cases for salt.modules.support::LogCollector ++ """ ++ ++ def setup_loader_modules(self): ++ return {saltsupport: {}} ++ ++ def test_msg(self): ++ """ ++ Test message to the log collector. ++ ++ :return: ++ """ ++ utcmock = MagicMock() ++ utcmock.utcnow = MagicMock(return_value=datetime.datetime.utcfromtimestamp(0)) ++ with patch("datetime.datetime", utcmock): ++ msg = "Upgrading /dev/null device" ++ out = saltsupport.LogCollector() ++ out.msg(msg, title="Here") ++ assert saltsupport.LogCollector.INFO in out.messages ++ assert ( ++ type(out.messages[saltsupport.LogCollector.INFO]) ++ == saltsupport.LogCollector.MessagesList ++ ) ++ assert out.messages[saltsupport.LogCollector.INFO] == [ ++ "00:00:00.000 - {}: {}".format("Here", msg) ++ ] ++ ++ def test_info_message(self): ++ """ ++ Test info message to the log collector. ++ ++ :return: ++ """ ++ utcmock = MagicMock() ++ utcmock.utcnow = MagicMock(return_value=datetime.datetime.utcfromtimestamp(0)) ++ with patch("datetime.datetime", utcmock): ++ msg = "SIMM crosstalk during tectonic stress" ++ out = saltsupport.LogCollector() ++ out.info(msg) ++ assert saltsupport.LogCollector.INFO in out.messages ++ assert ( ++ type(out.messages[saltsupport.LogCollector.INFO]) ++ == saltsupport.LogCollector.MessagesList ++ ) ++ assert out.messages[saltsupport.LogCollector.INFO] == [ ++ "00:00:00.000 - {}".format(msg) ++ ] ++ ++ def test_put_message(self): ++ """ ++ Test put message to the log collector. ++ ++ :return: ++ """ ++ utcmock = MagicMock() ++ utcmock.utcnow = MagicMock(return_value=datetime.datetime.utcfromtimestamp(0)) ++ with patch("datetime.datetime", utcmock): ++ msg = "Webmaster kidnapped by evil cult" ++ out = saltsupport.LogCollector() ++ out.put(msg) ++ assert saltsupport.LogCollector.INFO in out.messages ++ assert ( ++ type(out.messages[saltsupport.LogCollector.INFO]) ++ == saltsupport.LogCollector.MessagesList ++ ) ++ assert out.messages[saltsupport.LogCollector.INFO] == [ ++ "00:00:00.000 - {}".format(msg) ++ ] ++ ++ def test_warning_message(self): ++ """ ++ Test warning message to the log collector. ++ ++ :return: ++ """ ++ utcmock = MagicMock() ++ utcmock.utcnow = MagicMock(return_value=datetime.datetime.utcfromtimestamp(0)) ++ with patch("datetime.datetime", utcmock): ++ msg = "Your e-mail is now being delivered by USPS" ++ out = saltsupport.LogCollector() ++ out.warning(msg) ++ assert saltsupport.LogCollector.WARNING in out.messages ++ assert ( ++ type(out.messages[saltsupport.LogCollector.WARNING]) ++ == saltsupport.LogCollector.MessagesList ++ ) ++ assert out.messages[saltsupport.LogCollector.WARNING] == [ ++ "00:00:00.000 - {}".format(msg) ++ ] ++ ++ def test_error_message(self): ++ """ ++ Test error message to the log collector. ++ ++ :return: ++ """ ++ utcmock = MagicMock() ++ utcmock.utcnow = MagicMock(return_value=datetime.datetime.utcfromtimestamp(0)) ++ with patch("datetime.datetime", utcmock): ++ msg = "Learning curve appears to be fractal" ++ out = saltsupport.LogCollector() ++ out.error(msg) ++ assert saltsupport.LogCollector.ERROR in out.messages ++ assert ( ++ type(out.messages[saltsupport.LogCollector.ERROR]) ++ == saltsupport.LogCollector.MessagesList ++ ) ++ assert out.messages[saltsupport.LogCollector.ERROR] == [ ++ "00:00:00.000 - {}".format(msg) ++ ] ++ ++ def test_hl_message(self): ++ """ ++ Test highlighter message to the log collector. ++ ++ :return: ++ """ ++ utcmock = MagicMock() ++ utcmock.utcnow = MagicMock(return_value=datetime.datetime.utcfromtimestamp(0)) ++ with patch("datetime.datetime", utcmock): ++ out = saltsupport.LogCollector() ++ out.highlight("The {} TTYs became {} TTYs and vice versa", "real", "pseudo") ++ assert saltsupport.LogCollector.INFO in out.messages ++ assert ( ++ type(out.messages[saltsupport.LogCollector.INFO]) ++ == saltsupport.LogCollector.MessagesList ++ ) ++ assert out.messages[saltsupport.LogCollector.INFO] == [ ++ "00:00:00.000 - The real TTYs became " "pseudo TTYs and vice versa" ++ ] +-- +2.39.2 + + diff --git a/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch b/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch new file mode 100644 index 0000000..7c83c36 --- /dev/null +++ b/enable-passing-a-unix_socket-for-mysql-returners-bsc.patch @@ -0,0 +1,68 @@ +From e9d52cb97d619a76355c5aa1d03b733c125c0f22 Mon Sep 17 00:00:00 2001 +From: Maximilian Meister +Date: Thu, 3 May 2018 15:52:23 +0200 +Subject: [PATCH] enable passing a unix_socket for mysql returners + (bsc#1091371) + +quick fix for: + https://bugzilla.suse.com/show_bug.cgi?id=1091371 + +the upstream patch will go through some bigger refactoring of +the mysql drivers to be cleaner + +this patch should only be temporary and can be dropped again once +the refactor is done upstream + +Signed-off-by: Maximilian Meister +--- + salt/returners/mysql.py | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/salt/returners/mysql.py b/salt/returners/mysql.py +index 67b44004ac..a220f11465 100644 +--- a/salt/returners/mysql.py ++++ b/salt/returners/mysql.py +@@ -17,6 +17,7 @@ config. These are the defaults: + mysql.pass: 'salt' + mysql.db: 'salt' + mysql.port: 3306 ++ mysql.unix_socket: '/tmp/mysql.sock' + + SSL is optional. The defaults are set to None. If you do not want to use SSL, + either exclude these options or set them to None. +@@ -42,6 +43,7 @@ optional. The following ssl options are simply for illustration purposes: + alternative.mysql.ssl_ca: '/etc/pki/mysql/certs/localhost.pem' + alternative.mysql.ssl_cert: '/etc/pki/mysql/certs/localhost.crt' + alternative.mysql.ssl_key: '/etc/pki/mysql/certs/localhost.key' ++ alternative.mysql.unix_socket: '/tmp/mysql.sock' + + Should you wish the returner data to be cleaned out every so often, set + `keep_jobs_seconds` to the number of hours for the jobs to live in the +@@ -197,6 +199,7 @@ def _get_options(ret=None): + "ssl_ca": None, + "ssl_cert": None, + "ssl_key": None, ++ "unix_socket": "/tmp/mysql.sock", + } + + attrs = { +@@ -208,6 +211,7 @@ def _get_options(ret=None): + "ssl_ca": "ssl_ca", + "ssl_cert": "ssl_cert", + "ssl_key": "ssl_key", ++ "unix_socket": "unix_socket", + } + + _options = salt.returners.get_returner_options( +@@ -266,6 +270,7 @@ def _get_serv(ret=None, commit=False): + db=_options.get("db"), + port=_options.get("port"), + ssl=ssl_options, ++ unix_socket=_options.get("unix_socket"), + ) + + try: +-- +2.39.2 + + diff --git a/enhance-openscap-module-add-xccdf_eval-call-386.patch b/enhance-openscap-module-add-xccdf_eval-call-386.patch new file mode 100644 index 0000000..b7f7feb --- /dev/null +++ b/enhance-openscap-module-add-xccdf_eval-call-386.patch @@ -0,0 +1,425 @@ +From 17452801e950b3f49a9ec7ef444e3d57862cd9bf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 7 Jul 2021 15:41:48 +0100 +Subject: [PATCH] Enhance openscap module: add "xccdf_eval" call (#386) + +* Enhance openscap module: add xccdf_eval call + +* Allow 'tailoring_file' and 'tailoring_id' parameters + +* Fix wrong reference to subprocess.PIPE in openscap unit tests + +* Add changes suggested by pre-commit + +Co-authored-by: Michael Calmer + +Fix error handling in openscap module (bsc#1188647) (#409) +--- + changelog/59756.added | 1 + + salt/modules/openscap.py | 116 +++++++++++++- + tests/unit/modules/test_openscap.py | 234 ++++++++++++++++++++++++++++ + 3 files changed, 350 insertions(+), 1 deletion(-) + create mode 100644 changelog/59756.added + +diff --git a/changelog/59756.added b/changelog/59756.added +new file mode 100644 +index 0000000000..a59fb21eef +--- /dev/null ++++ b/changelog/59756.added +@@ -0,0 +1 @@ ++adding new call for openscap xccdf eval supporting new parameters +diff --git a/salt/modules/openscap.py b/salt/modules/openscap.py +index 770c8e7c04..216fd89eef 100644 +--- a/salt/modules/openscap.py ++++ b/salt/modules/openscap.py +@@ -4,6 +4,7 @@ Module for OpenSCAP Management + """ + + ++import os.path + import shlex + import shutil + import tempfile +@@ -55,6 +56,117 @@ _OSCAP_EXIT_CODES_MAP = { + } + + ++def xccdf_eval(xccdffile, ovalfiles=None, **kwargs): ++ """ ++ Run ``oscap xccdf eval`` commands on minions. ++ It uses cp.push_dir to upload the generated files to the salt master ++ in the master's minion files cachedir ++ (defaults to ``/var/cache/salt/master/minions/minion-id/files``) ++ ++ It needs ``file_recv`` set to ``True`` in the master configuration file. ++ ++ xccdffile ++ the path to the xccdf file to evaluate ++ ++ ovalfiles ++ additional oval definition files ++ ++ profile ++ the name of Profile to be evaluated ++ ++ rule ++ the name of a single rule to be evaluated ++ ++ oval_results ++ save OVAL results as well (True or False) ++ ++ results ++ write XCCDF Results into given file ++ ++ report ++ write HTML report into given file ++ ++ fetch_remote_resources ++ download remote content referenced by XCCDF (True or False) ++ ++ tailoring_file ++ use given XCCDF Tailoring file ++ ++ tailoring_id ++ use given DS component as XCCDF Tailoring file ++ ++ remediate ++ automatically execute XCCDF fix elements for failed rules. ++ Use of this option is always at your own risk. (True or False) ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' openscap.xccdf_eval /usr/share/openscap/scap-yast2sec-xccdf.xml profile=Default ++ ++ """ ++ success = True ++ error = None ++ upload_dir = None ++ returncode = None ++ if not ovalfiles: ++ ovalfiles = [] ++ ++ cmd_opts = ["oscap", "xccdf", "eval"] ++ if kwargs.get("oval_results"): ++ cmd_opts.append("--oval-results") ++ if "results" in kwargs: ++ cmd_opts.append("--results") ++ cmd_opts.append(kwargs["results"]) ++ if "report" in kwargs: ++ cmd_opts.append("--report") ++ cmd_opts.append(kwargs["report"]) ++ if "profile" in kwargs: ++ cmd_opts.append("--profile") ++ cmd_opts.append(kwargs["profile"]) ++ if "rule" in kwargs: ++ cmd_opts.append("--rule") ++ cmd_opts.append(kwargs["rule"]) ++ if "tailoring_file" in kwargs: ++ cmd_opts.append("--tailoring-file") ++ cmd_opts.append(kwargs["tailoring_file"]) ++ if "tailoring_id" in kwargs: ++ cmd_opts.append("--tailoring-id") ++ cmd_opts.append(kwargs["tailoring_id"]) ++ if kwargs.get("fetch_remote_resources"): ++ cmd_opts.append("--fetch-remote-resources") ++ if kwargs.get("remediate"): ++ cmd_opts.append("--remediate") ++ cmd_opts.append(xccdffile) ++ cmd_opts.extend(ovalfiles) ++ ++ if not os.path.exists(xccdffile): ++ success = False ++ error = "XCCDF File '{}' does not exist".format(xccdffile) ++ for ofile in ovalfiles: ++ if success and not os.path.exists(ofile): ++ success = False ++ error = "Oval File '{}' does not exist".format(ofile) ++ ++ if success: ++ tempdir = tempfile.mkdtemp() ++ proc = Popen(cmd_opts, stdout=PIPE, stderr=PIPE, cwd=tempdir) ++ (stdoutdata, error) = proc.communicate() ++ success = _OSCAP_EXIT_CODES_MAP.get(proc.returncode, False) ++ if proc.returncode < 0: ++ error += "\nKilled by signal {}\n".format(proc.returncode).encode('ascii') ++ returncode = proc.returncode ++ if success: ++ __salt__["cp.push_dir"](tempdir) ++ upload_dir = tempdir ++ shutil.rmtree(tempdir, ignore_errors=True) ++ ++ return dict( ++ success=success, upload_dir=upload_dir, error=error, returncode=returncode ++ ) ++ ++ + def xccdf(params): + """ + Run ``oscap xccdf`` commands on minions. +@@ -92,7 +204,9 @@ def xccdf(params): + tempdir = tempfile.mkdtemp() + proc = Popen(shlex.split(cmd), stdout=PIPE, stderr=PIPE, cwd=tempdir) + (stdoutdata, error) = proc.communicate() +- success = _OSCAP_EXIT_CODES_MAP[proc.returncode] ++ success = _OSCAP_EXIT_CODES_MAP.get(proc.returncode, False) ++ if proc.returncode < 0: ++ error += "\nKilled by signal {}\n".format(proc.returncode).encode('ascii') + returncode = proc.returncode + if success: + __salt__["cp.push_dir"](tempdir) +diff --git a/tests/unit/modules/test_openscap.py b/tests/unit/modules/test_openscap.py +index 045c37f7c9..301c1869ec 100644 +--- a/tests/unit/modules/test_openscap.py ++++ b/tests/unit/modules/test_openscap.py +@@ -21,6 +21,7 @@ class OpenscapTestCase(TestCase): + "salt.modules.openscap.tempfile.mkdtemp", + Mock(return_value=self.random_temp_dir), + ), ++ patch("salt.modules.openscap.os.path.exists", Mock(return_value=True)), + ] + for patcher in patchers: + self.apply_patch(patcher) +@@ -211,3 +212,236 @@ class OpenscapTestCase(TestCase): + "returncode": None, + }, + ) ++ ++ def test_new_openscap_xccdf_eval_success(self): ++ with patch( ++ "salt.modules.openscap.Popen", ++ MagicMock( ++ return_value=Mock( ++ **{"returncode": 0, "communicate.return_value": ("", "")} ++ ) ++ ), ++ ): ++ response = openscap.xccdf_eval( ++ self.policy_file, ++ profile="Default", ++ oval_results=True, ++ results="results.xml", ++ report="report.html", ++ ) ++ ++ self.assertEqual(openscap.tempfile.mkdtemp.call_count, 1) ++ expected_cmd = [ ++ "oscap", ++ "xccdf", ++ "eval", ++ "--oval-results", ++ "--results", ++ "results.xml", ++ "--report", ++ "report.html", ++ "--profile", ++ "Default", ++ self.policy_file, ++ ] ++ openscap.Popen.assert_called_once_with( ++ expected_cmd, ++ cwd=openscap.tempfile.mkdtemp.return_value, ++ stderr=subprocess.PIPE, ++ stdout=subprocess.PIPE, ++ ) ++ openscap.__salt__["cp.push_dir"].assert_called_once_with( ++ self.random_temp_dir ++ ) ++ self.assertEqual(openscap.shutil.rmtree.call_count, 1) ++ self.assertEqual( ++ response, ++ { ++ "upload_dir": self.random_temp_dir, ++ "error": "", ++ "success": True, ++ "returncode": 0, ++ }, ++ ) ++ ++ def test_new_openscap_xccdf_eval_success_with_extra_ovalfiles(self): ++ with patch( ++ "salt.modules.openscap.Popen", ++ MagicMock( ++ return_value=Mock( ++ **{"returncode": 0, "communicate.return_value": ("", "")} ++ ) ++ ), ++ ): ++ response = openscap.xccdf_eval( ++ self.policy_file, ++ ["/usr/share/xml/another-oval.xml", "/usr/share/xml/oval.xml"], ++ profile="Default", ++ oval_results=True, ++ results="results.xml", ++ report="report.html", ++ ) ++ ++ self.assertEqual(openscap.tempfile.mkdtemp.call_count, 1) ++ expected_cmd = [ ++ "oscap", ++ "xccdf", ++ "eval", ++ "--oval-results", ++ "--results", ++ "results.xml", ++ "--report", ++ "report.html", ++ "--profile", ++ "Default", ++ self.policy_file, ++ "/usr/share/xml/another-oval.xml", ++ "/usr/share/xml/oval.xml", ++ ] ++ openscap.Popen.assert_called_once_with( ++ expected_cmd, ++ cwd=openscap.tempfile.mkdtemp.return_value, ++ stderr=subprocess.PIPE, ++ stdout=subprocess.PIPE, ++ ) ++ openscap.__salt__["cp.push_dir"].assert_called_once_with( ++ self.random_temp_dir ++ ) ++ self.assertEqual(openscap.shutil.rmtree.call_count, 1) ++ self.assertEqual( ++ response, ++ { ++ "upload_dir": self.random_temp_dir, ++ "error": "", ++ "success": True, ++ "returncode": 0, ++ }, ++ ) ++ ++ def test_new_openscap_xccdf_eval_success_with_failing_rules(self): ++ with patch( ++ "salt.modules.openscap.Popen", ++ MagicMock( ++ return_value=Mock( ++ **{"returncode": 2, "communicate.return_value": ("", "some error")} ++ ) ++ ), ++ ): ++ response = openscap.xccdf_eval( ++ self.policy_file, ++ profile="Default", ++ oval_results=True, ++ results="results.xml", ++ report="report.html", ++ ) ++ ++ self.assertEqual(openscap.tempfile.mkdtemp.call_count, 1) ++ expected_cmd = [ ++ "oscap", ++ "xccdf", ++ "eval", ++ "--oval-results", ++ "--results", ++ "results.xml", ++ "--report", ++ "report.html", ++ "--profile", ++ "Default", ++ self.policy_file, ++ ] ++ openscap.Popen.assert_called_once_with( ++ expected_cmd, ++ cwd=openscap.tempfile.mkdtemp.return_value, ++ stderr=subprocess.PIPE, ++ stdout=subprocess.PIPE, ++ ) ++ openscap.__salt__["cp.push_dir"].assert_called_once_with( ++ self.random_temp_dir ++ ) ++ self.assertEqual(openscap.shutil.rmtree.call_count, 1) ++ self.assertEqual( ++ response, ++ { ++ "upload_dir": self.random_temp_dir, ++ "error": "some error", ++ "success": True, ++ "returncode": 2, ++ }, ++ ) ++ ++ def test_new_openscap_xccdf_eval_success_ignore_unknown_params(self): ++ with patch( ++ "salt.modules.openscap.Popen", ++ MagicMock( ++ return_value=Mock( ++ **{"returncode": 2, "communicate.return_value": ("", "some error")} ++ ) ++ ), ++ ): ++ response = openscap.xccdf_eval( ++ "/policy/file", ++ param="Default", ++ profile="Default", ++ oval_results=True, ++ results="results.xml", ++ report="report.html", ++ ) ++ ++ self.assertEqual( ++ response, ++ { ++ "upload_dir": self.random_temp_dir, ++ "error": "some error", ++ "success": True, ++ "returncode": 2, ++ }, ++ ) ++ expected_cmd = [ ++ "oscap", ++ "xccdf", ++ "eval", ++ "--oval-results", ++ "--results", ++ "results.xml", ++ "--report", ++ "report.html", ++ "--profile", ++ "Default", ++ "/policy/file", ++ ] ++ openscap.Popen.assert_called_once_with( ++ expected_cmd, ++ cwd=openscap.tempfile.mkdtemp.return_value, ++ stderr=subprocess.PIPE, ++ stdout=subprocess.PIPE, ++ ) ++ ++ def test_new_openscap_xccdf_eval_evaluation_error(self): ++ with patch( ++ "salt.modules.openscap.Popen", ++ MagicMock( ++ return_value=Mock( ++ **{ ++ "returncode": 1, ++ "communicate.return_value": ("", "evaluation error"), ++ } ++ ) ++ ), ++ ): ++ response = openscap.xccdf_eval( ++ self.policy_file, ++ profile="Default", ++ oval_results=True, ++ results="results.xml", ++ report="report.html", ++ ) ++ ++ self.assertEqual( ++ response, ++ { ++ "upload_dir": None, ++ "error": "evaluation error", ++ "success": False, ++ "returncode": 1, ++ }, ++ ) +-- +2.39.2 + + diff --git a/fix-bsc-1065792.patch b/fix-bsc-1065792.patch new file mode 100644 index 0000000..921455a --- /dev/null +++ b/fix-bsc-1065792.patch @@ -0,0 +1,25 @@ +From 42a5e5d1a898d7b8bdb56a94decf525204ebccb8 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Thu, 14 Dec 2017 16:21:40 +0100 +Subject: [PATCH] Fix bsc#1065792 + +--- + salt/states/service.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/salt/states/service.py b/salt/states/service.py +index 93c7c4fb07..0d8a4efa03 100644 +--- a/salt/states/service.py ++++ b/salt/states/service.py +@@ -78,6 +78,7 @@ def __virtual__(): + Only make these states available if a service provider has been detected or + assigned for this minion + """ ++ __salt__._load_all() + if "service.start" in __salt__: + return __virtualname__ + else: +-- +2.39.2 + + diff --git a/fix-for-suse-expanded-support-detection.patch b/fix-for-suse-expanded-support-detection.patch new file mode 100644 index 0000000..4b34956 --- /dev/null +++ b/fix-for-suse-expanded-support-detection.patch @@ -0,0 +1,39 @@ +From 7be26299bc7b6ec2065ab13857f088dc500ee882 Mon Sep 17 00:00:00 2001 +From: Jochen Breuer +Date: Thu, 6 Sep 2018 17:15:18 +0200 +Subject: [PATCH] Fix for SUSE Expanded Support detection + +A SUSE ES installation has both, the centos-release and redhat-release +file. Since os_data only used the centos-release file to detect a +CentOS installation, this lead to SUSE ES being detected as CentOS. + +This change also adds a check for redhat-release and then marks the +'lsb_distrib_id' as RedHat. +--- + salt/grains/core.py | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/salt/grains/core.py b/salt/grains/core.py +index 710c57f28f..1199ad274f 100644 +--- a/salt/grains/core.py ++++ b/salt/grains/core.py +@@ -2279,6 +2279,15 @@ def _legacy_linux_distribution_data(grains, os_release, lsb_has_error): + log.trace("Parsing distrib info from /etc/centos-release") + # CentOS Linux + grains["lsb_distrib_id"] = "CentOS" ++ # Maybe CentOS Linux; could also be SUSE Expanded Support. ++ # SUSE ES has both, centos-release and redhat-release. ++ if os.path.isfile("/etc/redhat-release"): ++ with salt.utils.files.fopen("/etc/redhat-release") as ifile: ++ for line in ifile: ++ if "red hat enterprise linux server" in line.lower(): ++ # This is a SUSE Expanded Support Rhel installation ++ grains["lsb_distrib_id"] = "RedHat" ++ break + with salt.utils.files.fopen("/etc/centos-release") as ifile: + for line in ifile: + # Need to pull out the version and codename +-- +2.39.2 + + diff --git a/fix-issue-2068-test.patch b/fix-issue-2068-test.patch new file mode 100644 index 0000000..4a2a20b --- /dev/null +++ b/fix-issue-2068-test.patch @@ -0,0 +1,52 @@ +From b0e713d6946526b894837406c0760c262e4312a1 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Wed, 9 Jan 2019 16:08:19 +0100 +Subject: [PATCH] Fix issue #2068 test + +Skip injecting `__call__` if chunk is not dict. + +This also fixes `integration/modules/test_state.py:StateModuleTest.test_exclude` that tests `include` and `exclude` state directives containing the only list of strings. + +Minor update: more correct is-dict check. +--- + salt/state.py | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/salt/state.py b/salt/state.py +index 8352a8defc..cb434a91e7 100644 +--- a/salt/state.py ++++ b/salt/state.py +@@ -12,6 +12,7 @@ The data sent to the state calls is as follows: + """ + + ++import collections + import copy + import datetime + import fnmatch +@@ -3507,16 +3508,18 @@ class State: + """ + for chunk in high: + state = high[chunk] ++ if not isinstance(state, collections.Mapping): ++ continue + for state_ref in state: + needs_default = True ++ if not isinstance(state[state_ref], list): ++ continue + for argset in state[state_ref]: + if isinstance(argset, str): + needs_default = False + break + if needs_default: +- order = state[state_ref].pop(-1) +- state[state_ref].append("__call__") +- state[state_ref].append(order) ++ state[state_ref].insert(-1, "__call__") + + def call_high(self, high, orchestration_jid=None): + """ +-- +2.39.2 + + diff --git a/fix-missing-minion-returns-in-batch-mode-360.patch b/fix-missing-minion-returns-in-batch-mode-360.patch new file mode 100644 index 0000000..615e333 --- /dev/null +++ b/fix-missing-minion-returns-in-batch-mode-360.patch @@ -0,0 +1,30 @@ +From 5158ebce305d961a2d2e3cb3f889b0cde593c4a0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Ond=C5=99ej=20Hole=C4=8Dek?= +Date: Mon, 10 May 2021 16:23:19 +0200 +Subject: [PATCH] Fix missing minion returns in batch mode (#360) + +Don't close pub if there are pending events, otherwise events will be lost +resulting in empty minion returns. + +Co-authored-by: Denis V. Meltsaykin +--- + salt/client/__init__.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/client/__init__.py b/salt/client/__init__.py +index bcda56c9b4..b2617e4554 100644 +--- a/salt/client/__init__.py ++++ b/salt/client/__init__.py +@@ -976,7 +976,7 @@ class LocalClient: + + self._clean_up_subscriptions(pub_data["jid"]) + finally: +- if not was_listening: ++ if not was_listening and not self.event.pending_events: + self.event.close_pub() + + def cmd_full_return( +-- +2.39.2 + + diff --git a/fix-ownership-of-salt-thin-directory-when-using-the-.patch b/fix-ownership-of-salt-thin-directory-when-using-the-.patch new file mode 100644 index 0000000..41118e9 --- /dev/null +++ b/fix-ownership-of-salt-thin-directory-when-using-the-.patch @@ -0,0 +1,50 @@ +From 5f6488ab9211927c421e3d87a4ee84fe659ceb8b Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Mon, 27 Jun 2022 18:03:49 +0300 +Subject: [PATCH] Fix ownership of salt thin directory when using the + Salt Bundle + +--- + salt/client/ssh/ssh_py_shim.py | 25 ++++++++++++++++++++++++- + 1 file changed, 24 insertions(+), 1 deletion(-) + +diff --git a/salt/client/ssh/ssh_py_shim.py b/salt/client/ssh/ssh_py_shim.py +index 293ea1b7fa..95171f7aea 100644 +--- a/salt/client/ssh/ssh_py_shim.py ++++ b/salt/client/ssh/ssh_py_shim.py +@@ -292,7 +292,30 @@ def main(argv): # pylint: disable=W0613 + os.makedirs(OPTIONS.saltdir) + cache_dir = os.path.join(OPTIONS.saltdir, "running_data", "var", "cache") + os.makedirs(os.path.join(cache_dir, "salt")) +- os.symlink("salt", os.path.relpath(os.path.join(cache_dir, "venv-salt-minion"))) ++ os.symlink( ++ "salt", os.path.relpath(os.path.join(cache_dir, "venv-salt-minion")) ++ ) ++ if os.path.exists(OPTIONS.saltdir) and ( ++ "SUDO_UID" in os.environ or "SUDO_GID" in os.environ ++ ): ++ try: ++ sudo_uid = int(os.environ.get("SUDO_UID", -1)) ++ except ValueError: ++ sudo_uid = -1 ++ try: ++ sudo_gid = int(os.environ.get("SUDO_GID", -1)) ++ except ValueError: ++ sudo_gid = -1 ++ dstat = os.stat(OPTIONS.saltdir) ++ if (sudo_uid != -1 and dstat.st_uid != sudo_uid) or ( ++ sudo_gid != -1 and dstat.st_gid != sudo_gid ++ ): ++ os.chown(OPTIONS.saltdir, sudo_uid, sudo_gid) ++ for dir_path, dir_names, file_names in os.walk(OPTIONS.saltdir): ++ for dir_name in dir_names: ++ os.lchown(os.path.join(dir_path, dir_name), sudo_uid, sudo_gid) ++ for file_name in file_names: ++ os.lchown(os.path.join(dir_path, file_name), sudo_uid, sudo_gid) + + if venv_salt_call is None: + # Use Salt thin only if Salt Bundle (venv-salt-minion) is not available +-- +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..68e34c9 --- /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-regression-with-depending-client.ssh-on-psutil-b.patch b/fix-regression-with-depending-client.ssh-on-psutil-b.patch new file mode 100644 index 0000000..bd1a7d7 --- /dev/null +++ b/fix-regression-with-depending-client.ssh-on-psutil-b.patch @@ -0,0 +1,53 @@ +From 42cfb51fa01e13fe043a62536ba37fd472bc2688 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Tue, 12 Apr 2022 10:08:17 +0300 +Subject: [PATCH] Fix regression with depending client.ssh on psutil + (bsc#1197533) + +--- + salt/client/ssh/__init__.py | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py +index d5a679821e..b120e0002e 100644 +--- a/salt/client/ssh/__init__.py ++++ b/salt/client/ssh/__init__.py +@@ -12,7 +12,6 @@ import hashlib + import logging + import multiprocessing + import os +-import psutil + import queue + import re + import shlex +@@ -420,6 +419,16 @@ class SSH(MultiprocessingStateMixin): + self.__parsed_rosters[self.ROSTER_UPDATE_FLAG] = False + return + ++ def _pid_exists(self, pid): ++ """ ++ Check if specified pid is alive ++ """ ++ try: ++ os.kill(pid, 0) ++ except OSError: ++ return False ++ return True ++ + def _update_roster(self, hostname=None, user=None): + """ + Update default flat roster with the passed in information. +@@ -639,7 +648,8 @@ class SSH(MultiprocessingStateMixin): + pid_running = ( + False + if cached_session["pid"] == 0 +- else cached_session.get("running", False) or psutil.pid_exists(cached_session["pid"]) ++ else cached_session.get("running", False) ++ or self._pid_exists(cached_session["pid"]) + ) + if ( + pid_running and prev_session_running < self.max_pid_wait +-- +2.39.2 + + diff --git a/fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch b/fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch new file mode 100644 index 0000000..2745350 --- /dev/null +++ b/fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch @@ -0,0 +1,128 @@ +From 4dbd5534a39fbfaebad32a00d0e6c512d840b0fd Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Thu, 31 Mar 2022 13:39:57 +0300 +Subject: [PATCH] Fix salt-ssh opts poisoning (bsc#1197637) - 3004 (#501) + +* Fix salt-ssh opts poisoning + +* Pass proper __opts__ to roster modules + +* Remove redundant copy.deepcopy for opts from handle_routine +--- + salt/client/ssh/__init__.py | 17 ++++++++++------- + salt/loader/__init__.py | 7 ++++++- + 2 files changed, 16 insertions(+), 8 deletions(-) + +diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py +index e6837df4e5..a527c03de6 100644 +--- a/salt/client/ssh/__init__.py ++++ b/salt/client/ssh/__init__.py +@@ -338,7 +338,7 @@ class SSH(MultiprocessingStateMixin): + self.session_flock_file = os.path.join( + self.opts["cachedir"], "salt-ssh.session.lock" + ) +- self.ssh_session_grace_time = int(self.opts.get("ssh_session_grace_time", 3)) ++ self.ssh_session_grace_time = int(self.opts.get("ssh_session_grace_time", 1)) + + # __setstate__ and __getstate__ are only used on spawning platforms. + def __setstate__(self, state): +@@ -571,7 +571,6 @@ class SSH(MultiprocessingStateMixin): + """ + LOG_LOCK.release() + salt.loader.LOAD_LOCK.release() +- opts = copy.deepcopy(opts) + single = Single( + opts, + opts["argv"], +@@ -608,6 +607,7 @@ class SSH(MultiprocessingStateMixin): + Spin up the needed threads or processes and execute the subsequent + routines + """ ++ opts = copy.deepcopy(self.opts) + que = multiprocessing.Queue() + running = {} + targets_queue = deque(self.targets.keys()) +@@ -618,7 +618,7 @@ class SSH(MultiprocessingStateMixin): + if not self.targets: + log.error("No matching targets found in roster.") + break +- if len(running) < self.opts.get("ssh_max_procs", 25) and not init: ++ if len(running) < opts.get("ssh_max_procs", 25) and not init: + if targets_queue: + host = targets_queue.popleft() + else: +@@ -636,7 +636,7 @@ class SSH(MultiprocessingStateMixin): + pid_running = ( + False + if cached_session["pid"] == 0 +- else psutil.pid_exists(cached_session["pid"]) ++ else cached_session.get("running", False) or psutil.pid_exists(cached_session["pid"]) + ) + if ( + pid_running and prev_session_running < self.max_pid_wait +@@ -651,9 +651,10 @@ class SSH(MultiprocessingStateMixin): + "salt-ssh/session", + host, + { +- "pid": 0, ++ "pid": os.getpid(), + "master_id": self.master_id, + "ts": time.time(), ++ "running": True, + }, + ) + for default in self.defaults: +@@ -681,7 +682,7 @@ class SSH(MultiprocessingStateMixin): + continue + args = ( + que, +- self.opts, ++ opts, + host, + self.targets[host], + mine, +@@ -717,6 +718,7 @@ class SSH(MultiprocessingStateMixin): + "pid": routine.pid, + "master_id": self.master_id, + "ts": time.time(), ++ "running": True, + }, + ) + continue +@@ -768,12 +770,13 @@ class SSH(MultiprocessingStateMixin): + "pid": 0, + "master_id": self.master_id, + "ts": time.time(), ++ "running": False, + }, + ) + if len(rets) >= len(self.targets): + break + # Sleep when limit or all threads started +- if len(running) >= self.opts.get("ssh_max_procs", 25) or len( ++ if len(running) >= opts.get("ssh_max_procs", 25) or len( + self.targets + ) >= len(running): + time.sleep(0.1) +diff --git a/salt/loader/__init__.py b/salt/loader/__init__.py +index 32f8a7702c..bbe4269839 100644 +--- a/salt/loader/__init__.py ++++ b/salt/loader/__init__.py +@@ -757,7 +757,12 @@ def roster(opts, runner=None, utils=None, whitelist=None, loaded_base_name=None, + opts, + tag="roster", + whitelist=whitelist, +- pack={"__runner__": runner, "__utils__": utils, "__context__": context}, ++ pack={ ++ "__runner__": runner, ++ "__utils__": utils, ++ "__context__": context, ++ "__opts__": opts, ++ }, + extra_module_dirs=utils.module_dirs if utils else None, + loaded_base_name=loaded_base_name, + ) +-- +2.39.2 + + diff --git a/fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch b/fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch new file mode 100644 index 0000000..431c0f9 --- /dev/null +++ b/fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch @@ -0,0 +1,141 @@ +From b4b2c59bfd479d59faeaf0e4d26d672828a519c8 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Wed, 25 Nov 2020 15:09:41 +0300 +Subject: [PATCH] Fix salt.utils.stringutils.to_str calls to make it + working with numeric uid/gid + +Fix upstream tests to work with 3006. +--- + salt/modules/file.py | 22 ++++++++++++------- + salt/states/file.py | 11 ++++++++-- + .../unit/modules/file/test_file_check.py | 10 ++++----- + 3 files changed, 28 insertions(+), 15 deletions(-) + +diff --git a/salt/modules/file.py b/salt/modules/file.py +index 4612d65511..55b236fe41 100644 +--- a/salt/modules/file.py ++++ b/salt/modules/file.py +@@ -5127,14 +5127,20 @@ def check_perms( + is_dir = os.path.isdir(name) + is_link = os.path.islink(name) + ++ def __safe_to_str(s): ++ try: ++ return salt.utils.stringutils.to_str(s) ++ except: ++ return salt.utils.stringutils.to_str(str(s)) ++ + # Check and make user/group/mode changes, then verify they were successful + if user: + if ( + salt.utils.platform.is_windows() and not user_to_uid(user) == cur["uid"] + ) or ( + not salt.utils.platform.is_windows() +- and not salt.utils.stringutils.to_str(user) == cur["user"] +- and not salt.utils.stringutils.to_str(user) == cur["uid"] ++ and not __safe_to_str(user) == cur["user"] ++ and not user == cur["uid"] + ): + perms["cuser"] = user + +@@ -5143,8 +5149,8 @@ def check_perms( + salt.utils.platform.is_windows() and not group_to_gid(group) == cur["gid"] + ) or ( + not salt.utils.platform.is_windows() +- and not salt.utils.stringutils.to_str(group) == cur["group"] +- and not salt.utils.stringutils.to_str(group) == cur["gid"] ++ and not __safe_to_str(group) == cur["group"] ++ and not group == cur["gid"] + ): + perms["cgroup"] = group + +@@ -5188,8 +5194,8 @@ def check_perms( + salt.utils.platform.is_windows() and not user_to_uid(user) == post["uid"] + ) or ( + not salt.utils.platform.is_windows() +- and not salt.utils.stringutils.to_str(user) == post["user"] +- and not salt.utils.stringutils.to_str(user) == post["uid"] ++ and not __safe_to_str(user) == post["user"] ++ and not user == post["uid"] + ): + if __opts__["test"] is True: + ret["changes"]["user"] = user +@@ -5204,8 +5210,8 @@ def check_perms( + salt.utils.platform.is_windows() and not group_to_gid(group) == post["gid"] + ) or ( + not salt.utils.platform.is_windows() +- and not salt.utils.stringutils.to_str(group) == post["group"] +- and not salt.utils.stringutils.to_str(group) == post["gid"] ++ and not __safe_to_str(group) == post["group"] ++ and not group == post["gid"] + ): + if __opts__["test"] is True: + ret["changes"]["group"] = group +diff --git a/salt/states/file.py b/salt/states/file.py +index 024e5e34ce..9630ff7096 100644 +--- a/salt/states/file.py ++++ b/salt/states/file.py +@@ -864,15 +864,22 @@ def _check_dir_meta(name, user, group, mode, follow_symlinks=False): + if not stats: + changes["directory"] = "new" + return changes ++ ++ def __safe_to_str(s): ++ try: ++ return salt.utils.stringutils.to_str(s) ++ except: ++ return salt.utils.stringutils.to_str(str(s)) ++ + if ( + user is not None +- and salt.utils.stringutils.to_str(user) != stats["user"] ++ and __safe_to_str(user) != stats["user"] + and user != stats.get("uid") + ): + changes["user"] = user + if ( + group is not None +- and salt.utils.stringutils.to_str(group) != stats["group"] ++ and __safe_to_str(group) != stats["group"] + and group != stats.get("gid") + ): + changes["group"] = group +diff --git a/tests/pytests/unit/modules/file/test_file_check.py b/tests/pytests/unit/modules/file/test_file_check.py +index ce86acd7fc..2294e6760b 100644 +--- a/tests/pytests/unit/modules/file/test_file_check.py ++++ b/tests/pytests/unit/modules/file/test_file_check.py +@@ -17,7 +17,7 @@ def configure_loader_modules(): + return { + filemod: { + "__context__": {}, +- "__opts__": {"test": False}, ++ "__opts__": {"test": True}, + } + } + +@@ -172,7 +172,7 @@ def test_check_managed_changes_follow_symlinks(a_link, tfile): + ), + # no user/group changes needed by id + ( +- {"user": 3001, "group": 4001}, ++ {"user": 2001, "group": 1001}, + {}, + ), + ], +@@ -184,9 +184,9 @@ def test_check_perms_user_group_name_and_id(input, expected): + stat_out = { + "user": "luser", + "group": "lgroup", +- "uid": 3001, +- "gid": 4001, +- "mode": "123", ++ "uid": 2001, ++ "gid": 1001, ++ "mode": "0123", + } + + patch_stats = patch( +-- +2.39.2 + + 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..79216f0 --- /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-tests-to-make-them-running-with-salt-testsuite.patch b/fix-tests-to-make-them-running-with-salt-testsuite.patch new file mode 100644 index 0000000..9b5523a --- /dev/null +++ b/fix-tests-to-make-them-running-with-salt-testsuite.patch @@ -0,0 +1,841 @@ +From 290d092c06dc378647dd1e49f000f012a7c07904 Mon Sep 17 00:00:00 2001 +From: vzhestkov +Date: Wed, 2 Aug 2023 16:13:49 +0200 +Subject: [PATCH] Fix tests to make them running with salt-testsuite + +--- + tests/pytests/unit/cli/test_batch_async.py | 718 +++++++++++---------- + tests/unit/cli/test_support.py | 6 +- + tests/unit/modules/test_saltsupport.py | 4 +- + 3 files changed, 364 insertions(+), 364 deletions(-) + +diff --git a/tests/pytests/unit/cli/test_batch_async.py b/tests/pytests/unit/cli/test_batch_async.py +index c0b708de76..e0774ffff3 100644 +--- a/tests/pytests/unit/cli/test_batch_async.py ++++ b/tests/pytests/unit/cli/test_batch_async.py +@@ -1,386 +1,392 @@ ++import pytest ++ + import salt.ext.tornado + from salt.cli.batch_async import BatchAsync +-from salt.ext.tornado.testing import AsyncTestCase + from tests.support.mock import MagicMock, patch +-from tests.support.unit import TestCase, skipIf +- +- +-class AsyncBatchTestCase(AsyncTestCase, TestCase): +- def setUp(self): +- self.io_loop = self.get_new_ioloop() +- opts = { +- "batch": "1", +- "conf_file": {}, +- "tgt": "*", +- "timeout": 5, +- "gather_job_timeout": 5, +- "batch_presence_ping_timeout": 1, +- "transport": None, +- "sock_dir": "", +- } +- +- with patch("salt.client.get_local_client", MagicMock(return_value=MagicMock())): +- with patch( +- "salt.cli.batch_async.batch_get_opts", MagicMock(return_value=opts) +- ): +- self.batch = BatchAsync( +- opts, +- MagicMock(side_effect=["1234", "1235", "1236"]), +- { +- "tgt": "", +- "fun": "", +- "kwargs": {"batch": "", "batch_presence_ping_timeout": 1}, +- }, +- ) +- +- def test_ping_jid(self): +- self.assertEqual(self.batch.ping_jid, "1234") +- +- def test_batch_jid(self): +- self.assertEqual(self.batch.batch_jid, "1235") +- +- def test_find_job_jid(self): +- self.assertEqual(self.batch.find_job_jid, "1236") +- +- def test_batch_size(self): +- """ +- Tests passing batch value as a number +- """ +- self.batch.opts = {"batch": "2", "timeout": 5} +- self.batch.minions = {"foo", "bar"} +- self.batch.start_batch() +- self.assertEqual(self.batch.batch_size, 2) +- +- @salt.ext.tornado.testing.gen_test +- def test_batch_start_on_batch_presence_ping_timeout(self): +- self.batch.event = MagicMock() +- future = salt.ext.tornado.gen.Future() +- future.set_result({"minions": ["foo", "bar"]}) +- self.batch.local.run_job_async.return_value = future +- ret = self.batch.start() +- # assert start_batch is called later with batch_presence_ping_timeout as param +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.start_batch,), +- ) +- # assert test.ping called +- self.assertEqual( +- self.batch.local.run_job_async.call_args[0], ("*", "test.ping", [], "glob") +- ) +- # assert targeted_minions == all minions matched by tgt +- self.assertEqual(self.batch.targeted_minions, {"foo", "bar"}) +- +- @salt.ext.tornado.testing.gen_test +- def test_batch_start_on_gather_job_timeout(self): +- self.batch.event = MagicMock() +- future = salt.ext.tornado.gen.Future() +- future.set_result({"minions": ["foo", "bar"]}) +- self.batch.local.run_job_async.return_value = future +- self.batch.batch_presence_ping_timeout = None +- ret = self.batch.start() +- # assert start_batch is called later with gather_job_timeout as param +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.start_batch,), +- ) + +- def test_batch_fire_start_event(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.opts = {"batch": "2", "timeout": 5} +- self.batch.event = MagicMock() +- self.batch.metadata = {"mykey": "myvalue"} +- self.batch.start_batch() +- self.assertEqual( +- self.batch.event.fire_event.call_args[0], +- ( ++ ++@pytest.fixture ++def batch(temp_salt_master): ++ opts = { ++ "batch": "1", ++ "conf_file": {}, ++ "tgt": "*", ++ "timeout": 5, ++ "gather_job_timeout": 5, ++ "batch_presence_ping_timeout": 1, ++ "transport": None, ++ "sock_dir": "", ++ } ++ ++ with patch("salt.client.get_local_client", MagicMock(return_value=MagicMock())): ++ with patch("salt.cli.batch_async.batch_get_opts", MagicMock(return_value=opts)): ++ batch = BatchAsync( ++ opts, ++ MagicMock(side_effect=["1234", "1235", "1236"]), + { +- "available_minions": {"foo", "bar"}, +- "down_minions": set(), +- "metadata": self.batch.metadata, ++ "tgt": "", ++ "fun": "", ++ "kwargs": {"batch": "", "batch_presence_ping_timeout": 1}, + }, +- "salt/batch/1235/start", +- ), +- ) ++ ) ++ yield batch + +- @salt.ext.tornado.testing.gen_test +- def test_start_batch_calls_next(self): +- self.batch.run_next = MagicMock(return_value=MagicMock()) +- self.batch.event = MagicMock() +- self.batch.start_batch() +- self.assertEqual(self.batch.initialized, True) +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], (self.batch.run_next,) +- ) + +- def test_batch_fire_done_event(self): +- self.batch.targeted_minions = {"foo", "baz", "bar"} +- self.batch.minions = {"foo", "bar"} +- self.batch.done_minions = {"foo"} +- self.batch.timedout_minions = {"bar"} +- self.batch.event = MagicMock() +- self.batch.metadata = {"mykey": "myvalue"} +- old_event = self.batch.event +- self.batch.end_batch() +- self.assertEqual( +- old_event.fire_event.call_args[0], +- ( +- { +- "available_minions": {"foo", "bar"}, +- "done_minions": self.batch.done_minions, +- "down_minions": {"baz"}, +- "timedout_minions": self.batch.timedout_minions, +- "metadata": self.batch.metadata, +- }, +- "salt/batch/1235/done", +- ), +- ) ++def test_ping_jid(batch): ++ assert batch.ping_jid == "1234" + +- def test_batch__del__(self): +- batch = BatchAsync(MagicMock(), MagicMock(), MagicMock()) +- event = MagicMock() +- batch.event = event +- batch.__del__() +- self.assertEqual(batch.local, None) +- self.assertEqual(batch.event, None) +- self.assertEqual(batch.ioloop, None) +- +- def test_batch_close_safe(self): +- batch = BatchAsync(MagicMock(), MagicMock(), MagicMock()) +- event = MagicMock() +- batch.event = event +- batch.patterns = { +- ("salt/job/1234/ret/*", "find_job_return"), +- ("salt/job/4321/ret/*", "find_job_return"), +- } +- batch.close_safe() +- self.assertEqual(batch.local, None) +- self.assertEqual(batch.event, None) +- self.assertEqual(batch.ioloop, None) +- self.assertEqual(len(event.unsubscribe.mock_calls), 2) +- self.assertEqual(len(event.remove_event_handler.mock_calls), 1) +- +- @salt.ext.tornado.testing.gen_test +- def test_batch_next(self): +- self.batch.event = MagicMock() +- self.batch.opts["fun"] = "my.fun" +- self.batch.opts["arg"] = [] +- self.batch._get_next = MagicMock(return_value={"foo", "bar"}) +- self.batch.batch_size = 2 +- future = salt.ext.tornado.gen.Future() +- future.set_result({"minions": ["foo", "bar"]}) +- self.batch.local.run_job_async.return_value = future +- self.batch.run_next() +- self.assertEqual( +- self.batch.local.run_job_async.call_args[0], +- ({"foo", "bar"}, "my.fun", [], "list"), +- ) +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.find_job, {"foo", "bar"}), +- ) +- self.assertEqual(self.batch.active, {"bar", "foo"}) +- +- def test_next_batch(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), {"foo", "bar"}) +- +- def test_next_batch_one_done(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.done_minions = {"bar"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), {"foo"}) +- +- def test_next_batch_one_done_one_active(self): +- self.batch.minions = {"foo", "bar", "baz"} +- self.batch.done_minions = {"bar"} +- self.batch.active = {"baz"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), {"foo"}) +- +- def test_next_batch_one_done_one_active_one_timedout(self): +- self.batch.minions = {"foo", "bar", "baz", "faz"} +- self.batch.done_minions = {"bar"} +- self.batch.active = {"baz"} +- self.batch.timedout_minions = {"faz"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), {"foo"}) +- +- def test_next_batch_bigger_size(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.batch_size = 3 +- self.assertEqual(self.batch._get_next(), {"foo", "bar"}) +- +- def test_next_batch_all_done(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.done_minions = {"foo", "bar"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), set()) +- +- def test_next_batch_all_active(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.active = {"foo", "bar"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), set()) +- +- def test_next_batch_all_timedout(self): +- self.batch.minions = {"foo", "bar"} +- self.batch.timedout_minions = {"foo", "bar"} +- self.batch.batch_size = 2 +- self.assertEqual(self.batch._get_next(), set()) +- +- def test_batch__event_handler_ping_return(self): +- self.batch.targeted_minions = {"foo"} +- self.batch.event = MagicMock( +- unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) +- ) +- self.batch.start() +- self.assertEqual(self.batch.minions, set()) +- self.batch._BatchAsync__event_handler(MagicMock()) +- self.assertEqual(self.batch.minions, {"foo"}) +- self.assertEqual(self.batch.done_minions, set()) +- +- def test_batch__event_handler_call_start_batch_when_all_pings_return(self): +- self.batch.targeted_minions = {"foo"} +- self.batch.event = MagicMock( +- unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) +- ) +- self.batch.start() +- self.batch._BatchAsync__event_handler(MagicMock()) +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.start_batch,), +- ) + +- def test_batch__event_handler_not_call_start_batch_when_not_all_pings_return(self): +- self.batch.targeted_minions = {"foo", "bar"} +- self.batch.event = MagicMock( +- unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) +- ) +- self.batch.start() +- self.batch._BatchAsync__event_handler(MagicMock()) +- self.assertEqual(len(self.batch.event.io_loop.spawn_callback.mock_calls), 0) ++def test_batch_jid(batch): ++ assert batch.batch_jid == "1235" ++ ++ ++def test_find_job_jid(batch): ++ assert batch.find_job_jid == "1236" ++ + +- def test_batch__event_handler_batch_run_return(self): +- self.batch.event = MagicMock( +- unpack=MagicMock(return_value=("salt/job/1235/ret/foo", {"id": "foo"})) ++def test_batch_size(batch): ++ """ ++ Tests passing batch value as a number ++ """ ++ batch.opts = {"batch": "2", "timeout": 5} ++ batch.minions = {"foo", "bar"} ++ batch.start_batch() ++ assert batch.batch_size == 2 ++ ++ ++def test_batch_start_on_batch_presence_ping_timeout(batch): ++ # batch_async = BatchAsyncMock(); ++ batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({"minions": ["foo", "bar"]}) ++ batch.local.run_job_async.return_value = future ++ with patch("salt.ext.tornado.gen.sleep", return_value=future): ++ # ret = batch_async.start(batch) ++ ret = batch.start() ++ # assert start_batch is called later with batch_presence_ping_timeout as param ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.start_batch,) ++ # assert test.ping called ++ assert batch.local.run_job_async.call_args[0] == ("*", "test.ping", [], "glob") ++ # assert targeted_minions == all minions matched by tgt ++ assert batch.targeted_minions == {"foo", "bar"} ++ ++ ++def test_batch_start_on_gather_job_timeout(batch): ++ # batch_async = BatchAsyncMock(); ++ batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({"minions": ["foo", "bar"]}) ++ batch.local.run_job_async.return_value = future ++ batch.batch_presence_ping_timeout = None ++ with patch("salt.ext.tornado.gen.sleep", return_value=future): ++ # ret = batch_async.start(batch) ++ ret = batch.start() ++ # assert start_batch is called later with gather_job_timeout as param ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.start_batch,) ++ ++ ++def test_batch_fire_start_event(batch): ++ batch.minions = {"foo", "bar"} ++ batch.opts = {"batch": "2", "timeout": 5} ++ batch.event = MagicMock() ++ batch.metadata = {"mykey": "myvalue"} ++ batch.start_batch() ++ assert batch.event.fire_event.call_args[0] == ( ++ { ++ "available_minions": {"foo", "bar"}, ++ "down_minions": set(), ++ "metadata": batch.metadata, ++ }, ++ "salt/batch/1235/start", ++ ) ++ ++ ++def test_start_batch_calls_next(batch): ++ batch.run_next = MagicMock(return_value=MagicMock()) ++ batch.event = MagicMock() ++ batch.start_batch() ++ assert batch.initialized ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.run_next,) ++ ++ ++def test_batch_fire_done_event(batch): ++ batch.targeted_minions = {"foo", "baz", "bar"} ++ batch.minions = {"foo", "bar"} ++ batch.done_minions = {"foo"} ++ batch.timedout_minions = {"bar"} ++ batch.event = MagicMock() ++ batch.metadata = {"mykey": "myvalue"} ++ old_event = batch.event ++ batch.end_batch() ++ assert old_event.fire_event.call_args[0] == ( ++ { ++ "available_minions": {"foo", "bar"}, ++ "done_minions": batch.done_minions, ++ "down_minions": {"baz"}, ++ "timedout_minions": batch.timedout_minions, ++ "metadata": batch.metadata, ++ }, ++ "salt/batch/1235/done", ++ ) ++ ++ ++def test_batch__del__(batch): ++ batch = BatchAsync(MagicMock(), MagicMock(), MagicMock()) ++ event = MagicMock() ++ batch.event = event ++ batch.__del__() ++ assert batch.local is None ++ assert batch.event is None ++ assert batch.ioloop is None ++ ++ ++def test_batch_close_safe(batch): ++ batch = BatchAsync(MagicMock(), MagicMock(), MagicMock()) ++ event = MagicMock() ++ batch.event = event ++ batch.patterns = { ++ ("salt/job/1234/ret/*", "find_job_return"), ++ ("salt/job/4321/ret/*", "find_job_return"), ++ } ++ batch.close_safe() ++ assert batch.local is None ++ assert batch.event is None ++ assert batch.ioloop is None ++ assert len(event.unsubscribe.mock_calls) == 2 ++ assert len(event.remove_event_handler.mock_calls) == 1 ++ ++ ++def test_batch_next(batch): ++ batch.event = MagicMock() ++ batch.opts["fun"] = "my.fun" ++ batch.opts["arg"] = [] ++ batch._get_next = MagicMock(return_value={"foo", "bar"}) ++ batch.batch_size = 2 ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({"minions": ["foo", "bar"]}) ++ batch.local.run_job_async.return_value = future ++ with patch("salt.ext.tornado.gen.sleep", return_value=future): ++ batch.run_next() ++ assert batch.local.run_job_async.call_args[0] == ( ++ {"foo", "bar"}, ++ "my.fun", ++ [], ++ "list", + ) +- self.batch.start() +- self.batch.active = {"foo"} +- self.batch._BatchAsync__event_handler(MagicMock()) +- self.assertEqual(self.batch.active, set()) +- self.assertEqual(self.batch.done_minions, {"foo"}) +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.schedule_next,), ++ assert batch.event.io_loop.spawn_callback.call_args[0] == ( ++ batch.find_job, ++ {"foo", "bar"}, + ) ++ assert batch.active == {"bar", "foo"} ++ + +- def test_batch__event_handler_find_job_return(self): +- self.batch.event = MagicMock( +- unpack=MagicMock( +- return_value=( +- "salt/job/1236/ret/foo", +- {"id": "foo", "return": "deadbeaf"}, +- ) ++def test_next_batch(batch): ++ batch.minions = {"foo", "bar"} ++ batch.batch_size = 2 ++ assert batch._get_next() == {"foo", "bar"} ++ ++ ++def test_next_batch_one_done(batch): ++ batch.minions = {"foo", "bar"} ++ batch.done_minions = {"bar"} ++ batch.batch_size = 2 ++ assert batch._get_next() == {"foo"} ++ ++ ++def test_next_batch_one_done_one_active(batch): ++ batch.minions = {"foo", "bar", "baz"} ++ batch.done_minions = {"bar"} ++ batch.active = {"baz"} ++ batch.batch_size = 2 ++ assert batch._get_next() == {"foo"} ++ ++ ++def test_next_batch_one_done_one_active_one_timedout(batch): ++ batch.minions = {"foo", "bar", "baz", "faz"} ++ batch.done_minions = {"bar"} ++ batch.active = {"baz"} ++ batch.timedout_minions = {"faz"} ++ batch.batch_size = 2 ++ assert batch._get_next() == {"foo"} ++ ++ ++def test_next_batch_bigger_size(batch): ++ batch.minions = {"foo", "bar"} ++ batch.batch_size = 3 ++ assert batch._get_next() == {"foo", "bar"} ++ ++ ++def test_next_batch_all_done(batch): ++ batch.minions = {"foo", "bar"} ++ batch.done_minions = {"foo", "bar"} ++ batch.batch_size = 2 ++ assert batch._get_next() == set() ++ ++ ++def test_next_batch_all_active(batch): ++ batch.minions = {"foo", "bar"} ++ batch.active = {"foo", "bar"} ++ batch.batch_size = 2 ++ assert batch._get_next() == set() ++ ++ ++def test_next_batch_all_timedout(batch): ++ batch.minions = {"foo", "bar"} ++ batch.timedout_minions = {"foo", "bar"} ++ batch.batch_size = 2 ++ assert batch._get_next() == set() ++ ++ ++def test_batch__event_handler_ping_return(batch): ++ batch.targeted_minions = {"foo"} ++ batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) ++ ) ++ batch.start() ++ assert batch.minions == set() ++ batch._BatchAsync__event_handler(MagicMock()) ++ assert batch.minions == {"foo"} ++ assert batch.done_minions == set() ++ ++ ++def test_batch__event_handler_call_start_batch_when_all_pings_return(batch): ++ batch.targeted_minions = {"foo"} ++ batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) ++ ) ++ batch.start() ++ batch._BatchAsync__event_handler(MagicMock()) ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.start_batch,) ++ ++ ++def test_batch__event_handler_not_call_start_batch_when_not_all_pings_return(batch): ++ batch.targeted_minions = {"foo", "bar"} ++ batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1234/ret/foo", {"id": "foo"})) ++ ) ++ batch.start() ++ batch._BatchAsync__event_handler(MagicMock()) ++ assert len(batch.event.io_loop.spawn_callback.mock_calls) == 0 ++ ++ ++def test_batch__event_handler_batch_run_return(batch): ++ batch.event = MagicMock( ++ unpack=MagicMock(return_value=("salt/job/1235/ret/foo", {"id": "foo"})) ++ ) ++ batch.start() ++ batch.active = {"foo"} ++ batch._BatchAsync__event_handler(MagicMock()) ++ assert batch.active == set() ++ assert batch.done_minions == {"foo"} ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.schedule_next,) ++ ++ ++def test_batch__event_handler_find_job_return(batch): ++ batch.event = MagicMock( ++ unpack=MagicMock( ++ return_value=( ++ "salt/job/1236/ret/foo", ++ {"id": "foo", "return": "deadbeaf"}, + ) + ) +- self.batch.start() +- self.batch.patterns.add(("salt/job/1236/ret/*", "find_job_return")) +- self.batch._BatchAsync__event_handler(MagicMock()) +- self.assertEqual(self.batch.find_job_returned, {"foo"}) +- +- @salt.ext.tornado.testing.gen_test +- def test_batch_run_next_end_batch_when_no_next(self): +- self.batch.end_batch = MagicMock() +- self.batch._get_next = MagicMock(return_value={}) +- self.batch.run_next() +- self.assertEqual(len(self.batch.end_batch.mock_calls), 1) +- +- @salt.ext.tornado.testing.gen_test +- def test_batch_find_job(self): +- self.batch.event = MagicMock() +- future = salt.ext.tornado.gen.Future() +- future.set_result({}) +- self.batch.local.run_job_async.return_value = future +- self.batch.minions = {"foo", "bar"} +- self.batch.jid_gen = MagicMock(return_value="1234") +- salt.ext.tornado.gen.sleep = MagicMock(return_value=future) +- self.batch.find_job({"foo", "bar"}) +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.check_find_job, {"foo", "bar"}, "1234"), ++ ) ++ batch.start() ++ batch.patterns.add(("salt/job/1236/ret/*", "find_job_return")) ++ batch._BatchAsync__event_handler(MagicMock()) ++ assert batch.find_job_returned == {"foo"} ++ ++ ++def test_batch_run_next_end_batch_when_no_next(batch): ++ batch.end_batch = MagicMock() ++ batch._get_next = MagicMock(return_value={}) ++ batch.run_next() ++ assert len(batch.end_batch.mock_calls) == 1 ++ ++ ++def test_batch_find_job(batch): ++ batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({}) ++ batch.local.run_job_async.return_value = future ++ batch.minions = {"foo", "bar"} ++ batch.jid_gen = MagicMock(return_value="1234") ++ with patch("salt.ext.tornado.gen.sleep", return_value=future): ++ batch.find_job({"foo", "bar"}) ++ assert batch.event.io_loop.spawn_callback.call_args[0] == ( ++ batch.check_find_job, ++ {"foo", "bar"}, ++ "1234", + ) + +- @salt.ext.tornado.testing.gen_test +- def test_batch_find_job_with_done_minions(self): +- self.batch.done_minions = {"bar"} +- self.batch.event = MagicMock() +- future = salt.ext.tornado.gen.Future() +- future.set_result({}) +- self.batch.local.run_job_async.return_value = future +- self.batch.minions = {"foo", "bar"} +- self.batch.jid_gen = MagicMock(return_value="1234") +- salt.ext.tornado.gen.sleep = MagicMock(return_value=future) +- self.batch.find_job({"foo", "bar"}) +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.check_find_job, {"foo"}, "1234"), +- ) + +- def test_batch_check_find_job_did_not_return(self): +- self.batch.event = MagicMock() +- self.batch.active = {"foo"} +- self.batch.find_job_returned = set() +- self.batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} +- self.batch.check_find_job({"foo"}, jid="1234") +- self.assertEqual(self.batch.find_job_returned, set()) +- self.assertEqual(self.batch.active, set()) +- self.assertEqual(len(self.batch.event.io_loop.add_callback.mock_calls), 0) +- +- def test_batch_check_find_job_did_return(self): +- self.batch.event = MagicMock() +- self.batch.find_job_returned = {"foo"} +- self.batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} +- self.batch.check_find_job({"foo"}, jid="1234") +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.find_job, {"foo"}), ++def test_batch_find_job_with_done_minions(batch): ++ batch.done_minions = {"bar"} ++ batch.event = MagicMock() ++ future = salt.ext.tornado.gen.Future() ++ future.set_result({}) ++ batch.local.run_job_async.return_value = future ++ batch.minions = {"foo", "bar"} ++ batch.jid_gen = MagicMock(return_value="1234") ++ with patch("salt.ext.tornado.gen.sleep", return_value=future): ++ batch.find_job({"foo", "bar"}) ++ assert batch.event.io_loop.spawn_callback.call_args[0] == ( ++ batch.check_find_job, ++ {"foo"}, ++ "1234", + ) + +- def test_batch_check_find_job_multiple_states(self): +- self.batch.event = MagicMock() +- # currently running minions +- self.batch.active = {"foo", "bar"} + +- # minion is running and find_job returns +- self.batch.find_job_returned = {"foo"} ++def test_batch_check_find_job_did_not_return(batch): ++ batch.event = MagicMock() ++ batch.active = {"foo"} ++ batch.find_job_returned = set() ++ batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} ++ batch.check_find_job({"foo"}, jid="1234") ++ assert batch.find_job_returned == set() ++ assert batch.active == set() ++ assert len(batch.event.io_loop.add_callback.mock_calls) == 0 + +- # minion started running but find_job did not return +- self.batch.timedout_minions = {"faz"} + +- # minion finished +- self.batch.done_minions = {"baz"} ++def test_batch_check_find_job_did_return(batch): ++ batch.event = MagicMock() ++ batch.find_job_returned = {"foo"} ++ batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} ++ batch.check_find_job({"foo"}, jid="1234") ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.find_job, {"foo"}) + +- # both not yet done but only 'foo' responded to find_job +- not_done = {"foo", "bar"} + +- self.batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} +- self.batch.check_find_job(not_done, jid="1234") ++def test_batch_check_find_job_multiple_states(batch): ++ batch.event = MagicMock() ++ # currently running minions ++ batch.active = {"foo", "bar"} + +- # assert 'bar' removed from active +- self.assertEqual(self.batch.active, {"foo"}) ++ # minion is running and find_job returns ++ batch.find_job_returned = {"foo"} + +- # assert 'bar' added to timedout_minions +- self.assertEqual(self.batch.timedout_minions, {"bar", "faz"}) ++ # minion started running but find_job did not return ++ batch.timedout_minions = {"faz"} ++ ++ # minion finished ++ batch.done_minions = {"baz"} ++ ++ # both not yet done but only 'foo' responded to find_job ++ not_done = {"foo", "bar"} ++ ++ batch.patterns = {("salt/job/1234/ret/*", "find_job_return")} ++ batch.check_find_job(not_done, jid="1234") ++ ++ # assert 'bar' removed from active ++ assert batch.active == {"foo"} ++ ++ # assert 'bar' added to timedout_minions ++ assert batch.timedout_minions == {"bar", "faz"} ++ ++ # assert 'find_job' schedueled again only for 'foo' ++ assert batch.event.io_loop.spawn_callback.call_args[0] == (batch.find_job, {"foo"}) + +- # assert 'find_job' schedueled again only for 'foo' +- self.assertEqual( +- self.batch.event.io_loop.spawn_callback.call_args[0], +- (self.batch.find_job, {"foo"}), +- ) + +- def test_only_on_run_next_is_scheduled(self): +- self.batch.event = MagicMock() +- self.batch.scheduled = True +- self.batch.schedule_next() +- self.assertEqual(len(self.batch.event.io_loop.spawn_callback.mock_calls), 0) ++def test_only_on_run_next_is_scheduled(batch): ++ batch.event = MagicMock() ++ batch.scheduled = True ++ batch.schedule_next() ++ assert len(batch.event.io_loop.spawn_callback.mock_calls) == 0 +diff --git a/tests/unit/cli/test_support.py b/tests/unit/cli/test_support.py +index dc0e99bb3d..971a0f122b 100644 +--- a/tests/unit/cli/test_support.py ++++ b/tests/unit/cli/test_support.py +@@ -14,7 +14,7 @@ from salt.cli.support.collector import SaltSupport, SupportDataCollector + from salt.cli.support.console import IndentOutput + from salt.utils.color import get_colors + from salt.utils.stringutils import to_bytes +-from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch ++from tests.support.mock import MagicMock, patch + from tests.support.unit import TestCase, skipIf + + try: +@@ -24,7 +24,6 @@ except ImportError: + + + @skipIf(not bool(pytest), "Pytest needs to be installed") +-@skipIf(NO_MOCK, NO_MOCK_REASON) + class SaltSupportIndentOutputTestCase(TestCase): + """ + Unit Tests for the salt-support indent output. +@@ -100,7 +99,6 @@ class SaltSupportIndentOutputTestCase(TestCase): + + + @skipIf(not bool(pytest), "Pytest needs to be installed") +-@skipIf(NO_MOCK, NO_MOCK_REASON) + class SaltSupportCollectorTestCase(TestCase): + """ + Collector tests. +@@ -232,7 +230,6 @@ class SaltSupportCollectorTestCase(TestCase): + + + @skipIf(not bool(pytest), "Pytest needs to be installed") +-@skipIf(NO_MOCK, NO_MOCK_REASON) + class SaltSupportRunnerTestCase(TestCase): + """ + Test runner class. +@@ -468,7 +465,6 @@ class SaltSupportRunnerTestCase(TestCase): + + + @skipIf(not bool(pytest), "Pytest needs to be installed") +-@skipIf(NO_MOCK, NO_MOCK_REASON) + class ProfileIntegrityTestCase(TestCase): + """ + Default profile integrity +diff --git a/tests/unit/modules/test_saltsupport.py b/tests/unit/modules/test_saltsupport.py +index 1715c68f4c..2afdd69b3e 100644 +--- a/tests/unit/modules/test_saltsupport.py ++++ b/tests/unit/modules/test_saltsupport.py +@@ -8,7 +8,7 @@ import datetime + import salt.exceptions + from salt.modules import saltsupport + from tests.support.mixins import LoaderModuleMockMixin +-from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch ++from tests.support.mock import MagicMock, patch + from tests.support.unit import TestCase, skipIf + + try: +@@ -18,7 +18,6 @@ except ImportError: + + + @skipIf(not bool(pytest), "Pytest required") +-@skipIf(NO_MOCK, NO_MOCK_REASON) + class SaltSupportModuleTestCase(TestCase, LoaderModuleMockMixin): + """ + Test cases for salt.modules.support::SaltSupportModule +@@ -361,7 +360,6 @@ professor: Farnsworth + + + @skipIf(not bool(pytest), "Pytest required") +-@skipIf(NO_MOCK, NO_MOCK_REASON) + class LogCollectorTestCase(TestCase, LoaderModuleMockMixin): + """ + Test cases for salt.modules.support::LogCollector +-- +2.41.0 + diff --git a/fix-the-regression-for-yumnotify-plugin-456.patch b/fix-the-regression-for-yumnotify-plugin-456.patch new file mode 100644 index 0000000..686ca94 --- /dev/null +++ b/fix-the-regression-for-yumnotify-plugin-456.patch @@ -0,0 +1,23 @@ +From b80c0d515e8715c160f94124dff8b5b90e773cd0 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Tue, 9 Nov 2021 16:19:56 +0300 +Subject: [PATCH] Fix the regression for yumnotify plugin (#456) + +--- + scripts/suse/yum/plugins/yumnotify.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/scripts/suse/yum/plugins/yumnotify.py b/scripts/suse/yum/plugins/yumnotify.py +index 0d117e8946..cec5256d20 100644 +--- a/scripts/suse/yum/plugins/yumnotify.py ++++ b/scripts/suse/yum/plugins/yumnotify.py +@@ -63,4 +63,4 @@ def posttrans_hook(conduit): + ) + ) + except OSError as e: +- print("Unable to save the cookie file: %s" % (e), file=sys.stderr) ++ sys.stderr.write("Unable to save the cookie file: %s\n" % (e)) +-- +2.39.2 + + 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..ed73858 --- /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-traceback.print_exc-calls-for-test_pip_state-432.patch b/fix-traceback.print_exc-calls-for-test_pip_state-432.patch new file mode 100644 index 0000000..65eb3a2 --- /dev/null +++ b/fix-traceback.print_exc-calls-for-test_pip_state-432.patch @@ -0,0 +1,26 @@ +From c37992e305978e95da1ac0a40a8142f578271320 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Mon, 8 Nov 2021 17:43:02 +0300 +Subject: [PATCH] Fix traceback.print_exc calls for test_pip_state (#432) + +--- + tests/unit/states/test_pip_state.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/unit/states/test_pip_state.py b/tests/unit/states/test_pip_state.py +index 5e4b6e0af1..981ad46a13 100644 +--- a/tests/unit/states/test_pip_state.py ++++ b/tests/unit/states/test_pip_state.py +@@ -442,7 +442,7 @@ class PipStateInstallationErrorTest(TestCase): + sys.stdout.flush() + sys.exit(2) + except Exception as exc: +- traceback.print_exc(exc, file=sys.stdout) ++ traceback.print_exc(file=sys.stdout) + sys.stdout.flush() + sys.exit(3) + sys.exit(0) +-- +2.39.2 + + 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..604baff --- /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/fix-version-detection-and-avoid-building-and-testing.patch b/fix-version-detection-and-avoid-building-and-testing.patch new file mode 100644 index 0000000..b1eb5f7 --- /dev/null +++ b/fix-version-detection-and-avoid-building-and-testing.patch @@ -0,0 +1,58 @@ +From c0fae09e5a4f6997a60007d970c7c6a5614d9102 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 19 Apr 2023 10:41:28 +0100 +Subject: [PATCH] Fix version detection and avoid building and testing + failures + +--- + salt/version.py | 20 ++------------------ + 1 file changed, 2 insertions(+), 18 deletions(-) + +diff --git a/salt/version.py b/salt/version.py +index 43cb5f86f7..67719bd020 100644 +--- a/salt/version.py ++++ b/salt/version.py +@@ -1,7 +1,6 @@ + """ + Set up the version of Salt + """ +-import argparse + import operator + import os + import platform +@@ -78,7 +77,7 @@ 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), 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)) +@@ -922,20 +921,5 @@ def versions_report(include_salt_cloud=False, include_extensions=True): + yield from info + + +-def _parser(): +- parser = argparse.ArgumentParser() +- parser.add_argument( +- "--next-release", help="Return the next release", action="store_true" +- ) +- # When pip installing we pass in other args to this script. +- # This allows us to catch those args but not use them +- parser.add_argument("unknown", nargs=argparse.REMAINDER) +- return parser.parse_args() +- +- + if __name__ == "__main__": +- args = _parser() +- if args.next_release: +- print(__saltstack_version__.next_release()) +- else: +- print(__version__) ++ print(__version__) +-- +2.39.2 + + diff --git a/fixed-gitfs-cachedir_basename-to-avoid-hash-collisio.patch b/fixed-gitfs-cachedir_basename-to-avoid-hash-collisio.patch new file mode 100644 index 0000000..8408745 --- /dev/null +++ b/fixed-gitfs-cachedir_basename-to-avoid-hash-collisio.patch @@ -0,0 +1,833 @@ +From 7051f86bb48dbd618a7422d469f3aae4c6f18008 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Thu, 31 Aug 2023 10:41:53 +0100 +Subject: [PATCH] Fixed gitfs cachedir_basename to avoid hash collisions + (#599) + +(bsc#1193948, bsc#1214797, CVE-2023-20898) + +Fix gitfs tests + +It's `gitfs` not `gtfs`, plus some code fixes and cleanup + +Signed-off-by: Pedro Algarvio + +fix doc + +wrap sha in base64 + +clean up cache name + +stop branch collision + +run pre + +Co-authored-by: cmcmarrow +--- + changelog/cve-2023-20898.security.md | 1 + + salt/utils/gitfs.py | 83 ++++++- + tests/pytests/unit/utils/test_gitfs.py | 255 +++++++++++++++++++++ + tests/unit/utils/test_gitfs.py | 305 ++++++------------------- + 4 files changed, 403 insertions(+), 241 deletions(-) + create mode 100644 changelog/cve-2023-20898.security.md + create mode 100644 tests/pytests/unit/utils/test_gitfs.py + +diff --git a/changelog/cve-2023-20898.security.md b/changelog/cve-2023-20898.security.md +new file mode 100644 +index 0000000000..44f1729192 +--- /dev/null ++++ b/changelog/cve-2023-20898.security.md +@@ -0,0 +1 @@ ++Fixed gitfs cachedir_basename to avoid hash collisions. Added MP Lock to gitfs. These changes should stop race conditions. +diff --git a/salt/utils/gitfs.py b/salt/utils/gitfs.py +index 38e84f38aa..af61aa0dda 100644 +--- a/salt/utils/gitfs.py ++++ b/salt/utils/gitfs.py +@@ -3,6 +3,7 @@ Classes which provide the shared base for GitFS, git_pillar, and winrepo + """ + + ++import base64 + import contextlib + import copy + import errno +@@ -11,10 +12,12 @@ import glob + import hashlib + import io + import logging ++import multiprocessing + import os + import shlex + import shutil + import stat ++import string + import subprocess + import time + import weakref +@@ -22,6 +25,7 @@ from datetime import datetime + + import salt.ext.tornado.ioloop + import salt.fileserver ++import salt.syspaths + import salt.utils.configparser + import salt.utils.data + import salt.utils.files +@@ -34,7 +38,6 @@ 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 +@@ -226,6 +229,10 @@ class GitProvider: + invoking the parent class' __init__. + """ + ++ # master lock should only be locked for very short periods of times "seconds" ++ # the master lock should be used when ever git provider reads or writes to one if it locks ++ _master_lock = multiprocessing.Lock() ++ + def __init__( + self, + opts, +@@ -452,13 +459,44 @@ class GitProvider: + failhard(self.role) + + hash_type = getattr(hashlib, self.opts.get("hash_type", "md5")) ++ # Generate full id. ++ # Full id helps decrease the chances of collections in the gitfs cache. ++ try: ++ target = str(self.get_checkout_target()) ++ except AttributeError: ++ target = "" ++ self._full_id = "-".join( ++ [ ++ getattr(self, "name", ""), ++ self.id, ++ getattr(self, "env", ""), ++ getattr(self, "_root", ""), ++ self.role, ++ getattr(self, "base", ""), ++ getattr(self, "branch", ""), ++ target, ++ ] ++ ) + # We loaded this data from yaml configuration files, so, its safe + # to use UTF-8 +- self.hash = hash_type(self.id.encode("utf-8")).hexdigest() +- self.cachedir_basename = getattr(self, "name", self.hash) ++ base64_hash = str( ++ base64.b64encode(hash_type(self._full_id.encode("utf-8")).digest()), ++ encoding="ascii", # base64 only outputs ascii ++ ).replace( ++ "/", "_" ++ ) # replace "/" with "_" to not cause trouble with file system ++ ++ # limit name length to 19, so we don't eat up all the path length for windows ++ # this is due to pygit2 limitations ++ # replace any unknown char with "_" to not cause trouble with file system ++ name_chars = string.ascii_letters + string.digits + "-" ++ cache_name = "".join( ++ c if c in name_chars else "_" for c in getattr(self, "name", "")[:19] ++ ) ++ ++ self.cachedir_basename = f"{cache_name}-{base64_hash}" + self.cachedir = salt.utils.path.join(cache_root, self.cachedir_basename) + self.linkdir = salt.utils.path.join(cache_root, "links", self.cachedir_basename) +- + if not os.path.isdir(self.cachedir): + os.makedirs(self.cachedir) + +@@ -473,6 +511,12 @@ class GitProvider: + log.critical(msg, exc_info=True) + failhard(self.role) + ++ def full_id(self): ++ return self._full_id ++ ++ def get_cachedir_basename(self): ++ return self.cachedir_basename ++ + def _get_envs_from_ref_paths(self, refs): + """ + Return the names of remote refs (stripped of the remote name) and tags +@@ -663,6 +707,19 @@ class GitProvider: + """ + Clear update.lk + """ ++ if self.__class__._master_lock.acquire(timeout=60) is False: ++ # if gitfs works right we should never see this timeout error. ++ log.error("gitfs master lock timeout!") ++ raise TimeoutError("gitfs master lock timeout!") ++ try: ++ return self._clear_lock(lock_type) ++ finally: ++ self.__class__._master_lock.release() ++ ++ def _clear_lock(self, lock_type="update"): ++ """ ++ Clear update.lk without MultiProcessing locks ++ """ + lock_file = self._get_lock_file(lock_type=lock_type) + + def _add_error(errlist, exc): +@@ -838,6 +895,20 @@ class GitProvider: + """ + Place a lock file if (and only if) it does not already exist. + """ ++ if self.__class__._master_lock.acquire(timeout=60) is False: ++ # if gitfs works right we should never see this timeout error. ++ log.error("gitfs master lock timeout!") ++ raise TimeoutError("gitfs master lock timeout!") ++ try: ++ return self.__lock(lock_type, failhard) ++ finally: ++ self.__class__._master_lock.release() ++ ++ def __lock(self, lock_type="update", failhard=False): ++ """ ++ Place a lock file if (and only if) it does not already exist. ++ Without MultiProcessing locks. ++ """ + try: + fh_ = os.open( + self._get_lock_file(lock_type), os.O_CREAT | os.O_EXCL | os.O_WRONLY +@@ -904,9 +975,9 @@ class GitProvider: + lock_type, + lock_file, + ) +- success, fail = self.clear_lock() ++ success, fail = self._clear_lock() + if success: +- return self._lock(lock_type="update", failhard=failhard) ++ return self.__lock(lock_type="update", failhard=failhard) + elif failhard: + raise + return +diff --git a/tests/pytests/unit/utils/test_gitfs.py b/tests/pytests/unit/utils/test_gitfs.py +new file mode 100644 +index 0000000000..e9915de412 +--- /dev/null ++++ b/tests/pytests/unit/utils/test_gitfs.py +@@ -0,0 +1,255 @@ ++import os ++import string ++import time ++ ++import pytest ++ ++import salt.fileserver.gitfs ++import salt.utils.gitfs ++from salt.exceptions import FileserverConfigError ++from tests.support.helpers import patched_environ ++from tests.support.mock import MagicMock, patch ++ ++try: ++ HAS_PYGIT2 = ( ++ salt.utils.gitfs.PYGIT2_VERSION ++ and salt.utils.gitfs.PYGIT2_VERSION >= salt.utils.gitfs.PYGIT2_MINVER ++ and salt.utils.gitfs.LIBGIT2_VERSION ++ and salt.utils.gitfs.LIBGIT2_VERSION >= salt.utils.gitfs.LIBGIT2_MINVER ++ ) ++except AttributeError: ++ HAS_PYGIT2 = False ++ ++ ++if HAS_PYGIT2: ++ import pygit2 ++ ++ ++@pytest.mark.parametrize( ++ "role_name,role_class", ++ ( ++ ("gitfs", salt.utils.gitfs.GitFS), ++ ("git_pillar", salt.utils.gitfs.GitPillar), ++ ("winrepo", salt.utils.gitfs.WinRepo), ++ ), ++) ++def test_provider_case_insensitive_gitfs_provider(minion_opts, role_name, role_class): ++ """ ++ Ensure that both lowercase and non-lowercase values are supported ++ """ ++ provider = "GitPython" ++ key = "{}_provider".format(role_name) ++ with patch.object(role_class, "verify_gitpython", MagicMock(return_value=True)): ++ with patch.object(role_class, "verify_pygit2", MagicMock(return_value=False)): ++ args = [minion_opts, {}] ++ kwargs = {"init_remotes": False} ++ if role_name == "winrepo": ++ kwargs["cache_root"] = "/tmp/winrepo-dir" ++ with patch.dict(minion_opts, {key: provider}): ++ # Try to create an instance with uppercase letters in ++ # provider name. If it fails then a ++ # FileserverConfigError will be raised, so no assert is ++ # necessary. ++ role_class(*args, **kwargs) ++ # Now try to instantiate an instance with all lowercase ++ # letters. Again, no need for an assert here. ++ role_class(*args, **kwargs) ++ ++ ++@pytest.mark.parametrize( ++ "role_name,role_class", ++ ( ++ ("gitfs", salt.utils.gitfs.GitFS), ++ ("git_pillar", salt.utils.gitfs.GitPillar), ++ ("winrepo", salt.utils.gitfs.WinRepo), ++ ), ++) ++def test_valid_provider_gitfs_provider(minion_opts, role_name, role_class): ++ """ ++ Ensure that an invalid provider is not accepted, raising a ++ FileserverConfigError. ++ """ ++ ++ def _get_mock(verify, provider): ++ """ ++ Return a MagicMock with the desired return value ++ """ ++ return MagicMock(return_value=verify.endswith(provider)) ++ ++ key = "{}_provider".format(role_name) ++ for provider in salt.utils.gitfs.GIT_PROVIDERS: ++ verify = "verify_gitpython" ++ mock1 = _get_mock(verify, provider) ++ with patch.object(role_class, verify, mock1): ++ verify = "verify_pygit2" ++ mock2 = _get_mock(verify, provider) ++ with patch.object(role_class, verify, mock2): ++ args = [minion_opts, {}] ++ kwargs = {"init_remotes": False} ++ if role_name == "winrepo": ++ kwargs["cache_root"] = "/tmp/winrepo-dir" ++ with patch.dict(minion_opts, {key: provider}): ++ role_class(*args, **kwargs) ++ with patch.dict(minion_opts, {key: "foo"}): ++ # Set the provider name to a known invalid provider ++ # and make sure it raises an exception. ++ with pytest.raises(FileserverConfigError): ++ role_class(*args, **kwargs) ++ ++ ++@pytest.fixture ++def _prepare_remote_repository_pygit2(tmp_path): ++ remote = os.path.join(tmp_path, "pygit2-repo") ++ filecontent = "This is an empty README file" ++ filename = "README" ++ signature = pygit2.Signature( ++ "Dummy Commiter", "dummy@dummy.com", int(time.time()), 0 ++ ) ++ repository = pygit2.init_repository(remote, False) ++ builder = repository.TreeBuilder() ++ tree = builder.write() ++ commit = repository.create_commit( ++ "HEAD", signature, signature, "Create master branch", tree, [] ++ ) ++ repository.create_reference("refs/tags/simple_tag", commit) ++ with salt.utils.files.fopen( ++ os.path.join(repository.workdir, filename), "w" ++ ) as file: ++ file.write(filecontent) ++ blob = repository.create_blob_fromworkdir(filename) ++ builder = repository.TreeBuilder() ++ builder.insert(filename, blob, pygit2.GIT_FILEMODE_BLOB) ++ tree = builder.write() ++ repository.index.read() ++ repository.index.add(filename) ++ repository.index.write() ++ commit = repository.create_commit( ++ "HEAD", ++ signature, ++ signature, ++ "Added a README", ++ tree, ++ [repository.head.target], ++ ) ++ repository.create_tag( ++ "annotated_tag", commit, pygit2.GIT_OBJ_COMMIT, signature, "some message" ++ ) ++ return remote ++ ++ ++@pytest.fixture ++def _prepare_provider(tmp_path, minion_opts, _prepare_remote_repository_pygit2): ++ cache = tmp_path / "pygit2-repo-cache" ++ minion_opts.update( ++ { ++ "cachedir": str(cache), ++ "gitfs_disable_saltenv_mapping": False, ++ "gitfs_base": "master", ++ "gitfs_insecure_auth": False, ++ "gitfs_mountpoint": "", ++ "gitfs_passphrase": "", ++ "gitfs_password": "", ++ "gitfs_privkey": "", ++ "gitfs_provider": "pygit2", ++ "gitfs_pubkey": "", ++ "gitfs_ref_types": ["branch", "tag", "sha"], ++ "gitfs_refspecs": [ ++ "+refs/heads/*:refs/remotes/origin/*", ++ "+refs/tags/*:refs/tags/*", ++ ], ++ "gitfs_root": "", ++ "gitfs_saltenv_blacklist": [], ++ "gitfs_saltenv_whitelist": [], ++ "gitfs_ssl_verify": True, ++ "gitfs_update_interval": 3, ++ "gitfs_user": "", ++ "verified_gitfs_provider": "pygit2", ++ } ++ ) ++ per_remote_defaults = { ++ "base": "master", ++ "disable_saltenv_mapping": False, ++ "insecure_auth": False, ++ "ref_types": ["branch", "tag", "sha"], ++ "passphrase": "", ++ "mountpoint": "", ++ "password": "", ++ "privkey": "", ++ "pubkey": "", ++ "refspecs": [ ++ "+refs/heads/*:refs/remotes/origin/*", ++ "+refs/tags/*:refs/tags/*", ++ ], ++ "root": "", ++ "saltenv_blacklist": [], ++ "saltenv_whitelist": [], ++ "ssl_verify": True, ++ "update_interval": 60, ++ "user": "", ++ } ++ per_remote_only = ("all_saltenvs", "name", "saltenv") ++ override_params = tuple(per_remote_defaults) ++ cache_root = cache / "gitfs" ++ role = "gitfs" ++ provider = salt.utils.gitfs.Pygit2( ++ minion_opts, ++ _prepare_remote_repository_pygit2, ++ per_remote_defaults, ++ per_remote_only, ++ override_params, ++ str(cache_root), ++ role, ++ ) ++ return provider ++ ++ ++@pytest.mark.skipif(not HAS_PYGIT2, reason="This host lacks proper pygit2 support") ++@pytest.mark.skip_on_windows( ++ reason="Skip Pygit2 on windows, due to pygit2 access error on windows" ++) ++def test_checkout_pygit2(_prepare_provider): ++ provider = _prepare_provider ++ provider.remotecallbacks = None ++ provider.credentials = None ++ provider.init_remote() ++ provider.fetch() ++ provider.branch = "master" ++ assert provider.cachedir in provider.checkout() ++ provider.branch = "simple_tag" ++ assert provider.cachedir in provider.checkout() ++ provider.branch = "annotated_tag" ++ assert provider.cachedir in provider.checkout() ++ provider.branch = "does_not_exist" ++ assert provider.checkout() is None ++ ++ ++@pytest.mark.skipif(not HAS_PYGIT2, reason="This host lacks proper pygit2 support") ++@pytest.mark.skip_on_windows( ++ reason="Skip Pygit2 on windows, due to pygit2 access error on windows" ++) ++def test_checkout_pygit2_with_home_env_unset(_prepare_provider): ++ provider = _prepare_provider ++ provider.remotecallbacks = None ++ provider.credentials = None ++ with patched_environ(__cleanup__=["HOME"]): ++ assert "HOME" not in os.environ ++ provider.init_remote() ++ provider.fetch() ++ assert "HOME" in os.environ ++ ++ ++def test_full_id_pygit2(_prepare_provider): ++ assert _prepare_provider.full_id().startswith("-") ++ assert _prepare_provider.full_id().endswith("/pygit2-repo---gitfs-master--") ++ ++ ++@pytest.mark.skipif(not HAS_PYGIT2, reason="This host lacks proper pygit2 support") ++@pytest.mark.skip_on_windows( ++ reason="Skip Pygit2 on windows, due to pygit2 access error on windows" ++) ++def test_get_cachedir_basename_pygit2(_prepare_provider): ++ basename = _prepare_provider.get_cachedir_basename() ++ assert len(basename) == 45 ++ assert basename[0] == "-" ++ # check that a valid base64 is given '/' -> '_' ++ assert all(c in string.ascii_letters + string.digits + "+_=" for c in basename[1:]) +diff --git a/tests/unit/utils/test_gitfs.py b/tests/unit/utils/test_gitfs.py +index 7c400b69af..6d8e97a239 100644 +--- a/tests/unit/utils/test_gitfs.py ++++ b/tests/unit/utils/test_gitfs.py +@@ -2,37 +2,20 @@ + These only test the provider selection and verification logic, they do not init + any remotes. + """ +-import os +-import shutil +-from time import time ++ ++import tempfile + + import pytest + ++import salt.ext.tornado.ioloop + import salt.fileserver.gitfs + import salt.utils.files + import salt.utils.gitfs ++import salt.utils.path + 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 + +-try: +- HAS_PYGIT2 = ( +- salt.utils.gitfs.PYGIT2_VERSION +- and salt.utils.gitfs.PYGIT2_VERSION >= salt.utils.gitfs.PYGIT2_MINVER +- and salt.utils.gitfs.LIBGIT2_VERSION +- and salt.utils.gitfs.LIBGIT2_VERSION >= salt.utils.gitfs.LIBGIT2_MINVER +- ) +-except AttributeError: +- HAS_PYGIT2 = False +- +- +-if HAS_PYGIT2: +- import pygit2 +- + + def _clear_instance_map(): + try: +@@ -45,6 +28,9 @@ def _clear_instance_map(): + + class TestGitBase(TestCase, AdaptedConfigurationTestCaseMixin): + def setUp(self): ++ self._tmp_dir = tempfile.TemporaryDirectory() ++ tmp_name = self._tmp_dir.name ++ + class MockedProvider( + salt.utils.gitfs.GitProvider + ): # pylint: disable=abstract-method +@@ -71,6 +57,7 @@ class TestGitBase(TestCase, AdaptedConfigurationTestCaseMixin): + ) + + def init_remote(self): ++ self.gitdir = salt.utils.path.join(tmp_name, ".git") + self.repo = True + new = False + return new +@@ -107,6 +94,7 @@ class TestGitBase(TestCase, AdaptedConfigurationTestCaseMixin): + for remote in self.main_class.remotes: + remote.fetched = False + del self.main_class ++ self._tmp_dir.cleanup() + + def test_update_all(self): + self.main_class.update() +@@ -126,226 +114,73 @@ class TestGitBase(TestCase, AdaptedConfigurationTestCaseMixin): + self.assertTrue(self.main_class.remotes[0].fetched) + self.assertFalse(self.main_class.remotes[1].fetched) + +- +-class TestGitFSProvider(TestCase): +- def setUp(self): +- self.opts = {"cachedir": "/tmp/gitfs-test-cache"} +- +- def tearDown(self): +- self.opts = None +- +- def test_provider_case_insensitive(self): +- """ +- Ensure that both lowercase and non-lowercase values are supported +- """ +- provider = "GitPython" +- for role_name, role_class in ( +- ("gitfs", salt.utils.gitfs.GitFS), +- ("git_pillar", salt.utils.gitfs.GitPillar), +- ("winrepo", salt.utils.gitfs.WinRepo), +- ): +- +- key = "{}_provider".format(role_name) +- with patch.object( +- role_class, "verify_gitpython", MagicMock(return_value=True) +- ): +- with patch.object( +- role_class, "verify_pygit2", MagicMock(return_value=False) +- ): +- args = [self.opts, {}] +- kwargs = {"init_remotes": False} +- if role_name == "winrepo": +- kwargs["cache_root"] = "/tmp/winrepo-dir" +- with patch.dict(self.opts, {key: provider}): +- # Try to create an instance with uppercase letters in +- # provider name. If it fails then a +- # FileserverConfigError will be raised, so no assert is +- # necessary. +- role_class(*args, **kwargs) +- # Now try to instantiate an instance with all lowercase +- # letters. Again, no need for an assert here. +- role_class(*args, **kwargs) +- +- def test_valid_provider(self): +- """ +- Ensure that an invalid provider is not accepted, raising a +- FileserverConfigError. +- """ +- +- def _get_mock(verify, provider): +- """ +- Return a MagicMock with the desired return value +- """ +- return MagicMock(return_value=verify.endswith(provider)) +- +- for role_name, role_class in ( +- ("gitfs", salt.utils.gitfs.GitFS), +- ("git_pillar", salt.utils.gitfs.GitPillar), +- ("winrepo", salt.utils.gitfs.WinRepo), +- ): +- key = "{}_provider".format(role_name) +- for provider in salt.utils.gitfs.GIT_PROVIDERS: +- verify = "verify_gitpython" +- mock1 = _get_mock(verify, provider) +- with patch.object(role_class, verify, mock1): +- verify = "verify_pygit2" +- mock2 = _get_mock(verify, provider) +- with patch.object(role_class, verify, mock2): +- args = [self.opts, {}] +- kwargs = {"init_remotes": False} +- if role_name == "winrepo": +- kwargs["cache_root"] = "/tmp/winrepo-dir" +- +- with patch.dict(self.opts, {key: provider}): +- role_class(*args, **kwargs) +- +- with patch.dict(self.opts, {key: "foo"}): +- # Set the provider name to a known invalid provider +- # and make sure it raises an exception. +- self.assertRaises( +- FileserverConfigError, role_class, *args, **kwargs +- ) +- +- +-@pytest.mark.skipif(not HAS_PYGIT2, reason="This host lacks proper pygit2 support") +-@pytest.mark.skip_on_windows( +- reason="Skip Pygit2 on windows, due to pygit2 access error on windows" +-) +-class TestPygit2(TestCase): +- def _prepare_remote_repository(self, path): +- shutil.rmtree(path, ignore_errors=True) +- +- filecontent = "This is an empty README file" +- filename = "README" +- +- signature = pygit2.Signature( +- "Dummy Commiter", "dummy@dummy.com", int(time()), 0 ++ def test_full_id(self): ++ self.assertEqual( ++ self.main_class.remotes[0].full_id(), "-file://repo1.git---gitfs-master--" + ) + +- repository = pygit2.init_repository(path, False) +- builder = repository.TreeBuilder() +- tree = builder.write() +- commit = repository.create_commit( +- "HEAD", signature, signature, "Create master branch", tree, [] ++ def test_full_id_with_name(self): ++ self.assertEqual( ++ self.main_class.remotes[1].full_id(), ++ "repo2-file://repo2.git---gitfs-master--", + ) +- repository.create_reference("refs/tags/simple_tag", commit) + +- with salt.utils.files.fopen( +- os.path.join(repository.workdir, filename), "w" +- ) as file: +- file.write(filecontent) +- +- blob = repository.create_blob_fromworkdir(filename) +- builder = repository.TreeBuilder() +- builder.insert(filename, blob, pygit2.GIT_FILEMODE_BLOB) +- tree = builder.write() +- +- repository.index.read() +- repository.index.add(filename) +- repository.index.write() +- +- commit = repository.create_commit( +- "HEAD", +- signature, +- signature, +- "Added a README", +- tree, +- [repository.head.target], +- ) +- repository.create_tag( +- "annotated_tag", commit, pygit2.GIT_OBJ_COMMIT, signature, "some message" ++ def test_get_cachedir_basename(self): ++ self.assertEqual( ++ self.main_class.remotes[0].get_cachedir_basename(), ++ "-jXhnbGDemchtZwTwaD2s6VOaVvs98a7w+AtiYlmOVb0=", + ) + +- def _prepare_cache_repository(self, remote, cache): +- opts = { +- "cachedir": cache, +- "__role": "minion", +- "gitfs_disable_saltenv_mapping": False, +- "gitfs_base": "master", +- "gitfs_insecure_auth": False, +- "gitfs_mountpoint": "", +- "gitfs_passphrase": "", +- "gitfs_password": "", +- "gitfs_privkey": "", +- "gitfs_provider": "pygit2", +- "gitfs_pubkey": "", +- "gitfs_ref_types": ["branch", "tag", "sha"], +- "gitfs_refspecs": [ +- "+refs/heads/*:refs/remotes/origin/*", +- "+refs/tags/*:refs/tags/*", +- ], +- "gitfs_root": "", +- "gitfs_saltenv_blacklist": [], +- "gitfs_saltenv_whitelist": [], +- "gitfs_ssl_verify": True, +- "gitfs_update_interval": 3, +- "gitfs_user": "", +- "verified_gitfs_provider": "pygit2", +- } +- per_remote_defaults = { +- "base": "master", +- "disable_saltenv_mapping": False, +- "insecure_auth": False, +- "ref_types": ["branch", "tag", "sha"], +- "passphrase": "", +- "mountpoint": "", +- "password": "", +- "privkey": "", +- "pubkey": "", +- "refspecs": [ +- "+refs/heads/*:refs/remotes/origin/*", +- "+refs/tags/*:refs/tags/*", +- ], +- "root": "", +- "saltenv_blacklist": [], +- "saltenv_whitelist": [], +- "ssl_verify": True, +- "update_interval": 60, +- "user": "", +- } +- per_remote_only = ("all_saltenvs", "name", "saltenv") +- override_params = tuple(per_remote_defaults.keys()) +- cache_root = os.path.join(cache, "gitfs") +- role = "gitfs" +- shutil.rmtree(cache_root, ignore_errors=True) +- provider = salt.utils.gitfs.Pygit2( +- opts, +- remote, +- per_remote_defaults, +- per_remote_only, +- override_params, +- cache_root, +- role, ++ def test_get_cachedir_base_with_name(self): ++ self.assertEqual( ++ self.main_class.remotes[1].get_cachedir_basename(), ++ "repo2-nuezpiDtjQRFC0ZJDByvi+F6Vb8ZhfoH41n_KFxTGsU=", + ) +- return provider + +- def test_checkout(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 +- provider.init_remote() +- provider.fetch() +- provider.branch = "master" +- self.assertIn(provider.cachedir, provider.checkout()) +- provider.branch = "simple_tag" +- self.assertIn(provider.cachedir, provider.checkout()) +- provider.branch = "annotated_tag" +- self.assertIn(provider.cachedir, provider.checkout()) +- provider.branch = "does_not_exist" +- self.assertIsNone(provider.checkout()) ++ def test_git_provider_mp_lock(self): ++ """ ++ Check that lock is released after provider.lock() ++ """ ++ provider = self.main_class.remotes[0] ++ provider.lock() ++ # check that lock has been released ++ self.assertTrue(provider._master_lock.acquire(timeout=5)) ++ provider._master_lock.release() + +- 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) ++ def test_git_provider_mp_clear_lock(self): ++ """ ++ Check that lock is released after provider.clear_lock() ++ """ ++ provider = self.main_class.remotes[0] ++ provider.clear_lock() ++ # check that lock has been released ++ self.assertTrue(provider._master_lock.acquire(timeout=5)) ++ provider._master_lock.release() ++ ++ @pytest.mark.slow_test ++ def test_git_provider_mp_lock_timeout(self): ++ """ ++ Check that lock will time out if master lock is locked. ++ """ ++ provider = self.main_class.remotes[0] ++ # Hijack the lock so git provider is fooled into thinking another instance is doing somthing. ++ self.assertTrue(provider._master_lock.acquire(timeout=5)) ++ try: ++ # git provider should raise timeout error to avoid lock race conditions ++ self.assertRaises(TimeoutError, provider.lock) ++ finally: ++ provider._master_lock.release() ++ ++ @pytest.mark.slow_test ++ def test_git_provider_mp_clear_lock_timeout(self): ++ """ ++ Check that clear lock will time out if master lock is locked. ++ """ ++ provider = self.main_class.remotes[0] ++ # Hijack the lock so git provider is fooled into thinking another instance is doing somthing. ++ self.assertTrue(provider._master_lock.acquire(timeout=5)) ++ try: ++ # git provider should raise timeout error to avoid lock race conditions ++ self.assertRaises(TimeoutError, provider.clear_lock) ++ finally: ++ provider._master_lock.release() +-- +2.41.0 + + diff --git a/fixes-for-python-3.10-502.patch b/fixes-for-python-3.10-502.patch new file mode 100644 index 0000000..81b055c --- /dev/null +++ b/fixes-for-python-3.10-502.patch @@ -0,0 +1,44 @@ +From 4996f423f14369fad14a9e6d2d3b8bd750c77fc7 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Tue, 5 Apr 2022 12:04:46 +0300 +Subject: [PATCH] Fixes for Python 3.10 (#502) + +* Use collections.abc.Mapping instead collections.Mapping in state +--- + salt/state.py | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/salt/state.py b/salt/state.py +index ab84cb8b4d..489424a083 100644 +--- a/salt/state.py ++++ b/salt/state.py +@@ -12,7 +12,6 @@ The data sent to the state calls is as follows: + """ + + +-import collections + import copy + import datetime + import fnmatch +@@ -27,6 +26,8 @@ import sys + import time + import traceback + ++from collections.abc import Mapping ++ + import salt.channel.client + import salt.fileclient + import salt.loader +@@ -3513,7 +3514,7 @@ class State: + """ + for chunk in high: + state = high[chunk] +- if not isinstance(state, collections.Mapping): ++ if not isinstance(state, Mapping): + continue + for state_ref in state: + needs_default = True +-- +2.39.2 + + diff --git a/html.tar.bz2 b/html.tar.bz2 new file mode 100644 index 0000000..9768c39 --- /dev/null +++ b/html.tar.bz2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6fba74e3093218b099e917e5ae52eb3ecdea2a7611b7b46b91dfca518e2a022 +size 10804131 diff --git a/include-aliases-in-the-fqdns-grains.patch b/include-aliases-in-the-fqdns-grains.patch new file mode 100644 index 0000000..ebb36a6 --- /dev/null +++ b/include-aliases-in-the-fqdns-grains.patch @@ -0,0 +1,138 @@ +From 4f459d670886a8f4a410fdbd1ec595477d45e4e2 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 17:10:37 +0100 +Subject: [PATCH] Include aliases in the fqdns grains + +Add UT for "is_fqdn" + +Add "is_fqdn" check to the network utils + +Bugfix: include FQDNs aliases + +Deprecate UnitTest assertion in favour of built-in assert keyword + +Add UT for fqdns aliases + +Leverage cached interfaces, if any. + +Implement network.fqdns module function (bsc#1134860) (#172) + +* Duplicate fqdns logic in module.network +* Move _get_interfaces to utils.network +* Reuse network.fqdns in grains.core.fqdns +* Return empty list when fqdns grains is disabled + +Co-authored-by: Eric Siebigteroth +--- + salt/modules/network.py | 5 +++- + salt/utils/network.py | 16 +++++++++++ + tests/pytests/unit/modules/test_network.py | 4 +-- + tests/unit/utils/test_network.py | 32 ++++++++++++++++++++++ + 4 files changed, 54 insertions(+), 3 deletions(-) + +diff --git a/salt/modules/network.py b/salt/modules/network.py +index 524b1b74fa..f959dbf97b 100644 +--- a/salt/modules/network.py ++++ b/salt/modules/network.py +@@ -2096,7 +2096,10 @@ def fqdns(): + # https://sourceware.org/bugzilla/show_bug.cgi?id=19329 + time.sleep(random.randint(5, 25) / 1000) + try: +- return [socket.getfqdn(socket.gethostbyaddr(ip)[0])] ++ name, aliaslist, addresslist = socket.gethostbyaddr(ip) ++ return [socket.getfqdn(name)] + [ ++ als for als in aliaslist if salt.utils.network.is_fqdn(als) ++ ] + except socket.herror as err: + if err.errno in (0, HOST_NOT_FOUND, NO_DATA): + # No FQDN for this IP address, so we don't need to know this all the time. +diff --git a/salt/utils/network.py b/salt/utils/network.py +index 2bea2cf129..6ec993a678 100644 +--- a/salt/utils/network.py ++++ b/salt/utils/network.py +@@ -2372,3 +2372,19 @@ def ip_bracket(addr, strip=False): + addr = addr.rstrip("]") + addr = ipaddress.ip_address(addr) + return ("[{}]" if addr.version == 6 and not strip else "{}").format(addr) ++ ++ ++def is_fqdn(hostname): ++ """ ++ Verify if hostname conforms to be a FQDN. ++ ++ :param hostname: text string with the name of the host ++ :return: bool, True if hostname is correct FQDN, False otherwise ++ """ ++ ++ compliant = re.compile(r"(?!-)[A-Z\d\-\_]{1,63}(? +Date: Tue, 25 Jan 2022 17:12:47 +0100 +Subject: [PATCH] info_installed works without status attr now + +If 'status' was excluded via attr, info_installed was no longer able to +detect if a package was installed or not. Now info_installed adds the +'status' for the 'lowpkg.info' request again. +--- + salt/modules/aptpkg.py | 9 +++++++++ + tests/pytests/unit/modules/test_aptpkg.py | 18 ++++++++++++++++++ + 2 files changed, 27 insertions(+) + +diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py +index 938e37cc9e..3289f6604d 100644 +--- a/salt/modules/aptpkg.py ++++ b/salt/modules/aptpkg.py +@@ -3461,6 +3461,15 @@ def info_installed(*names, **kwargs): + failhard = kwargs.pop("failhard", True) + kwargs.pop("errors", None) # Only for compatibility with RPM + attr = kwargs.pop("attr", None) # Package attributes to return ++ ++ # status is needed to see if a package is installed. So we have to add it, ++ # even if it's excluded via attr parameter. Otherwise all packages are ++ # returned. ++ if attr: ++ attr_list = set(attr.split(",")) ++ attr_list.add("status") ++ attr = ",".join(attr_list) ++ + all_versions = kwargs.pop( + "all_versions", False + ) # This is for backward compatible structure only +diff --git a/tests/pytests/unit/modules/test_aptpkg.py b/tests/pytests/unit/modules/test_aptpkg.py +index 4226957eeb..eb72447c3a 100644 +--- a/tests/pytests/unit/modules/test_aptpkg.py ++++ b/tests/pytests/unit/modules/test_aptpkg.py +@@ -385,6 +385,24 @@ def test_info_installed_attr(lowpkg_info_var): + assert ret["wget"] == expected_pkg + + ++def test_info_installed_attr_without_status(lowpkg_info_var): ++ """ ++ Test info_installed 'attr' for inclusion of 'status' attribute. ++ ++ Since info_installed should only return installed packages, we need to ++ call __salt__['lowpkg.info'] with the 'status' attribute even if the user ++ is not asking for it in 'attr'. Otherwise info_installed would not be able ++ to check if the package is installed and would return everything. ++ ++ :return: ++ """ ++ mock = MagicMock(return_value=lowpkg_info_var) ++ with patch.dict(aptpkg.__salt__, {"lowpkg.info": mock}): ++ aptpkg.info_installed("wget", attr="version") ++ assert "status" in mock.call_args.kwargs["attr"] ++ assert "version" in mock.call_args.kwargs["attr"] ++ ++ + def test_info_installed_all_versions(lowpkg_info_var): + """ + Test info_installed 'all_versions'. +-- +2.39.2 + + diff --git a/let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch b/let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch new file mode 100644 index 0000000..5cf8b4d --- /dev/null +++ b/let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch @@ -0,0 +1,32 @@ +From 1de8313e55317a62c36a1a6262e7b9463544d69c Mon Sep 17 00:00:00 2001 +From: Can Bulut Bayburt <1103552+cbbayburt@users.noreply.github.com> +Date: Wed, 4 Dec 2019 15:59:46 +0100 +Subject: [PATCH] Let salt-ssh use 'platform-python' binary in RHEL8 + (#191) + +RHEL/CentOS 8 has an internal Python interpreter called 'platform-python' +included in the base setup. + +Add this binary to the list of Python executables to look for when +creating the sh shim. +--- + salt/client/ssh/__init__.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py +index 88365a6099..049baff51a 100644 +--- a/salt/client/ssh/__init__.py ++++ b/salt/client/ssh/__init__.py +@@ -146,7 +146,7 @@ if [ "$SUDO" ] && [ "$SUDO_USER" ] + then SUDO="$SUDO -u $SUDO_USER" + fi + EX_PYTHON_INVALID={EX_THIN_PYTHON_INVALID} +-PYTHON_CMDS="python3 python27 python2.7 python26 python2.6 python2 python /usr/libexec/platform-python" ++PYTHON_CMDS="python3 /usr/libexec/platform-python python27 python2.7 python26 python2.6 python2 python" + for py_cmd in $PYTHON_CMDS + do + if command -v "$py_cmd" >/dev/null 2>&1 && "$py_cmd" -c "import sys; sys.exit(not (sys.version_info >= (2, 6)));" +-- +2.39.2 + + diff --git a/make-aptpkg.list_repos-compatible-on-enabled-disable.patch b/make-aptpkg.list_repos-compatible-on-enabled-disable.patch new file mode 100644 index 0000000..64c302c --- /dev/null +++ b/make-aptpkg.list_repos-compatible-on-enabled-disable.patch @@ -0,0 +1,28 @@ +From f9731227e7af0b1bf0a54993e0cac890225517f6 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Fri, 16 Nov 2018 10:54:12 +0100 +Subject: [PATCH] Make aptpkg.list_repos compatible on enabled/disabled + output + +--- + salt/modules/aptpkg.py | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/salt/modules/aptpkg.py b/salt/modules/aptpkg.py +index f68b1907e8..8e89744b5e 100644 +--- a/salt/modules/aptpkg.py ++++ b/salt/modules/aptpkg.py +@@ -1919,6 +1919,9 @@ def list_repos(**kwargs): + repo["file"] = source.file + repo["comps"] = getattr(source, "comps", []) + repo["disabled"] = source.disabled ++ repo["enabled"] = not repo[ ++ "disabled" ++ ] # This is for compatibility with the other modules + repo["dist"] = source.dist + repo["type"] = source.type + repo["uri"] = source.uri +-- +2.39.2 + + 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..8c3dfdf --- /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/make-setup.py-script-to-not-require-setuptools-9.1.patch b/make-setup.py-script-to-not-require-setuptools-9.1.patch new file mode 100644 index 0000000..89c706f --- /dev/null +++ b/make-setup.py-script-to-not-require-setuptools-9.1.patch @@ -0,0 +1,33 @@ +From d2b4c8170d7ff30bf33623fcbbb6ebb6d7af934e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 25 Mar 2020 13:09:52 +0000 +Subject: [PATCH] Make setup.py script to not require setuptools > 9.1 + +--- + setup.py | 8 -------- + 1 file changed, 8 deletions(-) + +diff --git a/setup.py b/setup.py +index e60f1b7085..8ca8a66d45 100755 +--- a/setup.py ++++ b/setup.py +@@ -632,14 +632,6 @@ class Install(install): + install.finalize_options(self) + + def run(self): +- if LooseVersion(setuptools.__version__) < LooseVersion("9.1"): +- sys.stderr.write( +- "\n\nInstalling Salt requires setuptools >= 9.1\n" +- "Available setuptools version is {}\n\n".format(setuptools.__version__) +- ) +- sys.stderr.flush() +- sys.exit(1) +- + # Let's set the running_salt_install attribute so we can add + # _version.txt in the build command + self.distribution.running_salt_install = True +-- +2.39.2 + + diff --git a/make-sure-configured-user-is-properly-set-by-salt-bs.patch b/make-sure-configured-user-is-properly-set-by-salt-bs.patch new file mode 100644 index 0000000..7b4e3ab --- /dev/null +++ b/make-sure-configured-user-is-properly-set-by-salt-bs.patch @@ -0,0 +1,204 @@ +From 5ea4add5c8e2bed50b9825edfff7565e5f6124f3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Tue, 22 Aug 2023 12:57:44 +0100 +Subject: [PATCH] Make sure configured user is properly set by Salt + (bsc#1210994) (#596) + +* Make sure Salt user and env is validated before daemon init + +* Ensure HOME is always present in env and set according to pwuser + +* Set User to salt in salt-master.service files + +* Return proper exitcode if user is not valid + +* Fix environment also for salt-ssh command + +* Increase start_timeout to avoid test to be flaky +--- + pkg/common/salt-master.service | 1 + + pkg/old/deb/salt-master.service | 1 + + pkg/old/suse/salt-master.service | 1 + + salt/cli/daemons.py | 27 +++++++++++++++++++ + salt/cli/ssh.py | 8 ++++++ + salt/utils/verify.py | 4 +-- + .../integration/cli/test_salt_minion.py | 4 +-- + 7 files changed, 42 insertions(+), 4 deletions(-) + +diff --git a/pkg/common/salt-master.service b/pkg/common/salt-master.service +index 377c87afeb..257ecc283f 100644 +--- a/pkg/common/salt-master.service ++++ b/pkg/common/salt-master.service +@@ -8,6 +8,7 @@ LimitNOFILE=100000 + Type=notify + NotifyAccess=all + ExecStart=/usr/bin/salt-master ++User=salt + + [Install] + WantedBy=multi-user.target +diff --git a/pkg/old/deb/salt-master.service b/pkg/old/deb/salt-master.service +index b5d0cdd22c..f9dca296b4 100644 +--- a/pkg/old/deb/salt-master.service ++++ b/pkg/old/deb/salt-master.service +@@ -7,6 +7,7 @@ LimitNOFILE=16384 + Type=notify + NotifyAccess=all + ExecStart=/usr/bin/salt-master ++User=salt + + [Install] + WantedBy=multi-user.target +diff --git a/pkg/old/suse/salt-master.service b/pkg/old/suse/salt-master.service +index 9e002d16ca..caabca511c 100644 +--- a/pkg/old/suse/salt-master.service ++++ b/pkg/old/suse/salt-master.service +@@ -8,6 +8,7 @@ LimitNOFILE=100000 + Type=simple + ExecStart=/usr/bin/salt-master + TasksMax=infinity ++User=salt + + [Install] + WantedBy=multi-user.target +diff --git a/salt/cli/daemons.py b/salt/cli/daemons.py +index ecc05c919e..c9ee9ced91 100644 +--- a/salt/cli/daemons.py ++++ b/salt/cli/daemons.py +@@ -7,6 +7,7 @@ import logging + import os + import warnings + ++import salt.defaults.exitcodes + import salt.utils.kinds as kinds + from salt.exceptions import SaltClientError, SaltSystemExit, get_error_message + from salt.utils import migrations +@@ -73,6 +74,16 @@ class DaemonsMixin: # pylint: disable=no-init + self.__class__.__name__, + ) + ++ def verify_user(self): ++ """ ++ Verify Salt configured user for Salt and shutdown daemon if not valid. ++ ++ :return: ++ """ ++ if not check_user(self.config["user"]): ++ self.action_log_info("Cannot switch to configured user for Salt. Exiting") ++ self.shutdown(salt.defaults.exitcodes.EX_NOUSER) ++ + def action_log_info(self, action): + """ + Say daemon starting. +@@ -178,6 +189,10 @@ class Master( + self.config["interface"] = ip_bracket(self.config["interface"]) + migrations.migrate_paths(self.config) + ++ # Ensure configured user is valid and environment is properly set ++ # before initializating rest of the stack. ++ self.verify_user() ++ + # Late import so logging works correctly + import salt.master + +@@ -290,6 +305,10 @@ class Minion( + + transport = self.config.get("transport").lower() + ++ # Ensure configured user is valid and environment is properly set ++ # before initializating rest of the stack. ++ self.verify_user() ++ + try: + # Late import so logging works correctly + import salt.minion +@@ -478,6 +497,10 @@ class ProxyMinion( + self.action_log_info("An instance is already running. Exiting") + self.shutdown(1) + ++ # Ensure configured user is valid and environment is properly set ++ # before initializating rest of the stack. ++ self.verify_user() ++ + # TODO: AIO core is separate from transport + # Late import so logging works correctly + import salt.minion +@@ -576,6 +599,10 @@ class Syndic( + + self.action_log_info('Setting up "{}"'.format(self.config["id"])) + ++ # Ensure configured user is valid and environment is properly set ++ # before initializating rest of the stack. ++ self.verify_user() ++ + # Late import so logging works correctly + import salt.minion + +diff --git a/salt/cli/ssh.py b/salt/cli/ssh.py +index 6048cb5f58..672f32b8c0 100644 +--- a/salt/cli/ssh.py ++++ b/salt/cli/ssh.py +@@ -1,7 +1,9 @@ + import sys + + import salt.client.ssh ++import salt.defaults.exitcodes + import salt.utils.parsers ++from salt.utils.verify import check_user + + + class SaltSSH(salt.utils.parsers.SaltSSHOptionParser): +@@ -15,5 +17,11 @@ class SaltSSH(salt.utils.parsers.SaltSSHOptionParser): + # that won't be used anyways with -H or --hosts + self.parse_args() + ++ if not check_user(self.config["user"]): ++ self.exit( ++ salt.defaults.exitcodes.EX_NOUSER, ++ "Cannot switch to configured user for Salt. Exiting", ++ ) ++ + ssh = salt.client.ssh.SSH(self.config) + ssh.run() +diff --git a/salt/utils/verify.py b/salt/utils/verify.py +index 879128f231..7899fbe538 100644 +--- a/salt/utils/verify.py ++++ b/salt/utils/verify.py +@@ -335,8 +335,8 @@ def check_user(user): + + # We could just reset the whole environment but let's just override + # the variables we can get from pwuser +- if "HOME" in os.environ: +- os.environ["HOME"] = pwuser.pw_dir ++ # We ensure HOME is always present and set according to pwuser ++ os.environ["HOME"] = pwuser.pw_dir + + if "SHELL" in os.environ: + os.environ["SHELL"] = pwuser.pw_shell +diff --git a/tests/pytests/integration/cli/test_salt_minion.py b/tests/pytests/integration/cli/test_salt_minion.py +index c0d6013474..bde2dd51d7 100644 +--- a/tests/pytests/integration/cli/test_salt_minion.py ++++ b/tests/pytests/integration/cli/test_salt_minion.py +@@ -41,7 +41,7 @@ def test_exit_status_unknown_user(salt_master, minion_id): + factory = salt_master.salt_minion_daemon( + minion_id, overrides={"user": "unknown-user"} + ) +- factory.start(start_timeout=10, max_start_attempts=1) ++ factory.start(start_timeout=30, max_start_attempts=1) + + assert exc.value.process_result.returncode == salt.defaults.exitcodes.EX_NOUSER + assert "The user is not available." in exc.value.process_result.stderr +@@ -53,7 +53,7 @@ def test_exit_status_unknown_argument(salt_master, minion_id): + """ + with pytest.raises(FactoryNotStarted) as exc: + factory = salt_master.salt_minion_daemon(minion_id) +- factory.start("--unknown-argument", start_timeout=10, max_start_attempts=1) ++ factory.start("--unknown-argument", start_timeout=30, max_start_attempts=1) + + assert exc.value.process_result.returncode == salt.defaults.exitcodes.EX_USAGE + assert "Usage" in exc.value.process_result.stderr +-- +2.41.0 + + diff --git a/make-sure-the-file-client-is-destroyed-upon-used.patch b/make-sure-the-file-client-is-destroyed-upon-used.patch new file mode 100644 index 0000000..60097db --- /dev/null +++ b/make-sure-the-file-client-is-destroyed-upon-used.patch @@ -0,0 +1,850 @@ +From a1fc5287d501a1ecdbd259e5bbdd4f7d5d06dd13 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Fri, 28 Apr 2023 09:41:28 +0200 +Subject: [PATCH] Make sure the file client is destroyed upon used + +Backport of https://github.com/saltstack/salt/pull/64113 +--- + salt/client/ssh/wrapper/saltcheck.py | 108 +++---- + salt/fileclient.py | 11 - + salt/modules/dockermod.py | 17 +- + salt/pillar/__init__.py | 6 +- + salt/states/ansiblegate.py | 11 +- + salt/utils/asynchronous.py | 2 +- + salt/utils/jinja.py | 53 ++- + salt/utils/mako.py | 7 + + salt/utils/templates.py | 303 +++++++++--------- + .../integration/states/test_include.py | 40 +++ + .../utils/jinja/test_salt_cache_loader.py | 47 ++- + 11 files changed, 330 insertions(+), 275 deletions(-) + create mode 100644 tests/pytests/integration/states/test_include.py + +diff --git a/salt/client/ssh/wrapper/saltcheck.py b/salt/client/ssh/wrapper/saltcheck.py +index d47b5cf6883..b0b94593809 100644 +--- a/salt/client/ssh/wrapper/saltcheck.py ++++ b/salt/client/ssh/wrapper/saltcheck.py +@@ -9,6 +9,7 @@ import tarfile + import tempfile + from contextlib import closing + ++import salt.fileclient + import salt.utils.files + import salt.utils.json + import salt.utils.url +@@ -28,65 +29,62 @@ def update_master_cache(states, saltenv="base"): + # Setup for copying states to gendir + gendir = tempfile.mkdtemp() + trans_tar = salt.utils.files.mkstemp() +- if "cp.fileclient_{}".format(id(__opts__)) not in __context__: +- __context__[ +- "cp.fileclient_{}".format(id(__opts__)) +- ] = salt.fileclient.get_file_client(__opts__) +- +- # generate cp.list_states output and save to gendir +- cp_output = salt.utils.json.dumps(__salt__["cp.list_states"]()) +- cp_output_file = os.path.join(gendir, "cp_output.txt") +- with salt.utils.files.fopen(cp_output_file, "w") as fp: +- fp.write(cp_output) +- +- # cp state directories to gendir +- already_processed = [] +- sls_list = salt.utils.args.split_input(states) +- for state_name in sls_list: +- # generate low data for each state and save to gendir +- state_low_file = os.path.join(gendir, state_name + ".low") +- state_low_output = salt.utils.json.dumps( +- __salt__["state.show_low_sls"](state_name) +- ) +- with salt.utils.files.fopen(state_low_file, "w") as fp: +- fp.write(state_low_output) +- +- state_name = state_name.replace(".", os.sep) +- if state_name in already_processed: +- log.debug("Already cached state for %s", state_name) +- else: +- file_copy_file = os.path.join(gendir, state_name + ".copy") +- log.debug("copying %s to %s", state_name, gendir) +- qualified_name = salt.utils.url.create(state_name, saltenv) +- # Duplicate cp.get_dir to gendir +- copy_result = __context__["cp.fileclient_{}".format(id(__opts__))].get_dir( +- qualified_name, gendir, saltenv ++ with salt.fileclient.get_file_client(__opts__) as cp_fileclient: ++ ++ # generate cp.list_states output and save to gendir ++ cp_output = salt.utils.json.dumps(__salt__["cp.list_states"]()) ++ cp_output_file = os.path.join(gendir, "cp_output.txt") ++ with salt.utils.files.fopen(cp_output_file, "w") as fp: ++ fp.write(cp_output) ++ ++ # cp state directories to gendir ++ already_processed = [] ++ sls_list = salt.utils.args.split_input(states) ++ for state_name in sls_list: ++ # generate low data for each state and save to gendir ++ state_low_file = os.path.join(gendir, state_name + ".low") ++ state_low_output = salt.utils.json.dumps( ++ __salt__["state.show_low_sls"](state_name) + ) +- if copy_result: +- copy_result = [dir.replace(gendir, state_cache) for dir in copy_result] +- copy_result_output = salt.utils.json.dumps(copy_result) +- with salt.utils.files.fopen(file_copy_file, "w") as fp: +- fp.write(copy_result_output) +- already_processed.append(state_name) ++ with salt.utils.files.fopen(state_low_file, "w") as fp: ++ fp.write(state_low_output) ++ ++ state_name = state_name.replace(".", os.sep) ++ if state_name in already_processed: ++ log.debug("Already cached state for %s", state_name) + else: +- # If files were not copied, assume state.file.sls was given and just copy state +- state_name = os.path.dirname(state_name) + file_copy_file = os.path.join(gendir, state_name + ".copy") +- if state_name in already_processed: +- log.debug("Already cached state for %s", state_name) ++ log.debug("copying %s to %s", state_name, gendir) ++ qualified_name = salt.utils.url.create(state_name, saltenv) ++ # Duplicate cp.get_dir to gendir ++ copy_result = cp_fileclient.get_dir(qualified_name, gendir, saltenv) ++ if copy_result: ++ copy_result = [ ++ dir.replace(gendir, state_cache) for dir in copy_result ++ ] ++ copy_result_output = salt.utils.json.dumps(copy_result) ++ with salt.utils.files.fopen(file_copy_file, "w") as fp: ++ fp.write(copy_result_output) ++ already_processed.append(state_name) + else: +- qualified_name = salt.utils.url.create(state_name, saltenv) +- copy_result = __context__[ +- "cp.fileclient_{}".format(id(__opts__)) +- ].get_dir(qualified_name, gendir, saltenv) +- if copy_result: +- copy_result = [ +- dir.replace(gendir, state_cache) for dir in copy_result +- ] +- copy_result_output = salt.utils.json.dumps(copy_result) +- with salt.utils.files.fopen(file_copy_file, "w") as fp: +- fp.write(copy_result_output) +- already_processed.append(state_name) ++ # If files were not copied, assume state.file.sls was given and just copy state ++ state_name = os.path.dirname(state_name) ++ file_copy_file = os.path.join(gendir, state_name + ".copy") ++ if state_name in already_processed: ++ log.debug("Already cached state for %s", state_name) ++ else: ++ qualified_name = salt.utils.url.create(state_name, saltenv) ++ copy_result = cp_fileclient.get_dir( ++ qualified_name, gendir, saltenv ++ ) ++ if copy_result: ++ copy_result = [ ++ dir.replace(gendir, state_cache) for dir in copy_result ++ ] ++ copy_result_output = salt.utils.json.dumps(copy_result) ++ with salt.utils.files.fopen(file_copy_file, "w") as fp: ++ fp.write(copy_result_output) ++ already_processed.append(state_name) + + # turn gendir into tarball and remove gendir + try: +diff --git a/salt/fileclient.py b/salt/fileclient.py +index fef5154a0be..f01a86dd0d4 100644 +--- a/salt/fileclient.py ++++ b/salt/fileclient.py +@@ -849,7 +849,6 @@ class Client: + kwargs.pop("env") + + kwargs["saltenv"] = saltenv +- url_data = urllib.parse.urlparse(url) + sfn = self.cache_file(url, saltenv, cachedir=cachedir) + if not sfn or not os.path.exists(sfn): + return "" +@@ -1165,13 +1164,8 @@ class RemoteClient(Client): + + if not salt.utils.platform.is_windows(): + hash_server, stat_server = self.hash_and_stat_file(path, saltenv) +- try: +- mode_server = stat_server[0] +- except (IndexError, TypeError): +- mode_server = None + else: + hash_server = self.hash_file(path, saltenv) +- mode_server = None + + # Check if file exists on server, before creating files and + # directories +@@ -1214,13 +1208,8 @@ class RemoteClient(Client): + if dest2check and os.path.isfile(dest2check): + if not salt.utils.platform.is_windows(): + hash_local, stat_local = self.hash_and_stat_file(dest2check, saltenv) +- try: +- mode_local = stat_local[0] +- except (IndexError, TypeError): +- mode_local = None + else: + hash_local = self.hash_file(dest2check, saltenv) +- mode_local = None + + if hash_local == hash_server: + return dest2check +diff --git a/salt/modules/dockermod.py b/salt/modules/dockermod.py +index f7344b66ac6..69b722f0c95 100644 +--- a/salt/modules/dockermod.py ++++ b/salt/modules/dockermod.py +@@ -6667,14 +6667,6 @@ def script_retcode( + )["retcode"] + + +-def _mk_fileclient(): +- """ +- Create a file client and add it to the context. +- """ +- if "cp.fileclient" not in __context__: +- __context__["cp.fileclient"] = salt.fileclient.get_file_client(__opts__) +- +- + def _generate_tmp_path(): + return os.path.join("/tmp", "salt.docker.{}".format(uuid.uuid4().hex[:6])) + +@@ -6688,11 +6680,10 @@ def _prepare_trans_tar(name, sls_opts, mods=None, pillar=None, extra_filerefs="" + # reuse it from salt.ssh, however this function should + # be somewhere else + refs = salt.client.ssh.state.lowstate_file_refs(chunks, extra_filerefs) +- _mk_fileclient() +- trans_tar = salt.client.ssh.state.prep_trans_tar( +- __context__["cp.fileclient"], chunks, refs, pillar, name +- ) +- return trans_tar ++ with salt.fileclient.get_file_client(__opts__) as fileclient: ++ return salt.client.ssh.state.prep_trans_tar( ++ fileclient, chunks, refs, pillar, name ++ ) + + + def _compile_state(sls_opts, mods=None): +diff --git a/salt/pillar/__init__.py b/salt/pillar/__init__.py +index 0dfab4cc579..26312b3bd53 100644 +--- a/salt/pillar/__init__.py ++++ b/salt/pillar/__init__.py +@@ -9,7 +9,6 @@ import logging + import os + import sys + import traceback +-import uuid + + import salt.channel.client + import salt.ext.tornado.gen +@@ -1351,6 +1350,11 @@ class Pillar: + if hasattr(self, "_closing") and self._closing: + return + self._closing = True ++ if self.client: ++ try: ++ self.client.destroy() ++ except AttributeError: ++ pass + + # pylint: disable=W1701 + def __del__(self): +diff --git a/salt/states/ansiblegate.py b/salt/states/ansiblegate.py +index 7fd4deb6c2a..9abd418c42c 100644 +--- a/salt/states/ansiblegate.py ++++ b/salt/states/ansiblegate.py +@@ -32,12 +32,10 @@ state: + - state: installed + + """ +- + import logging + import os + import sys + +-# Import salt modules + import salt.fileclient + import salt.utils.decorators.path + from salt.utils.decorators import depends +@@ -108,13 +106,6 @@ def __virtual__(): + return __virtualname__ + + +-def _client(): +- """ +- Get a fileclient +- """ +- return salt.fileclient.get_file_client(__opts__) +- +- + def _changes(plays): + """ + Find changes in ansible return data +@@ -171,7 +162,7 @@ def playbooks(name, rundir=None, git_repo=None, git_kwargs=None, ansible_kwargs= + } + if git_repo: + if not isinstance(rundir, str) or not os.path.isdir(rundir): +- with _client() as client: ++ with salt.fileclient.get_file_client(__opts__) as client: + rundir = client._extrn_path(git_repo, "base") + log.trace("rundir set to %s", rundir) + if not isinstance(git_kwargs, dict): +diff --git a/salt/utils/asynchronous.py b/salt/utils/asynchronous.py +index 2a858feee98..0c645bbc3bb 100644 +--- a/salt/utils/asynchronous.py ++++ b/salt/utils/asynchronous.py +@@ -131,7 +131,7 @@ class SyncWrapper: + result = io_loop.run_sync(lambda: getattr(self.obj, key)(*args, **kwargs)) + results.append(True) + results.append(result) +- except Exception as exc: # pylint: disable=broad-except ++ except Exception: # pylint: disable=broad-except + results.append(False) + results.append(sys.exc_info()) + +diff --git a/salt/utils/jinja.py b/salt/utils/jinja.py +index fcc5aec497e..a6a8a279605 100644 +--- a/salt/utils/jinja.py ++++ b/salt/utils/jinja.py +@@ -58,19 +58,6 @@ class SaltCacheLoader(BaseLoader): + and only loaded once per loader instance. + """ + +- _cached_pillar_client = None +- _cached_client = None +- +- @classmethod +- def shutdown(cls): +- for attr in ("_cached_client", "_cached_pillar_client"): +- client = getattr(cls, attr, None) +- if client is not None: +- # PillarClient and LocalClient objects do not have a destroy method +- if hasattr(client, "destroy"): +- client.destroy() +- setattr(cls, attr, None) +- + def __init__( + self, + opts, +@@ -93,8 +80,7 @@ class SaltCacheLoader(BaseLoader): + log.debug("Jinja search path: %s", self.searchpath) + self.cached = [] + self._file_client = _file_client +- # Instantiate the fileclient +- self.file_client() ++ self._close_file_client = _file_client is None + + def file_client(self): + """ +@@ -108,18 +94,10 @@ class SaltCacheLoader(BaseLoader): + or not hasattr(self._file_client, "opts") + or self._file_client.opts["file_roots"] != self.opts["file_roots"] + ): +- attr = "_cached_pillar_client" if self.pillar_rend else "_cached_client" +- cached_client = getattr(self, attr, None) +- if ( +- cached_client is None +- or not hasattr(cached_client, "opts") +- or cached_client.opts["file_roots"] != self.opts["file_roots"] +- ): +- cached_client = salt.fileclient.get_file_client( +- self.opts, self.pillar_rend +- ) +- setattr(SaltCacheLoader, attr, cached_client) +- self._file_client = cached_client ++ self._file_client = salt.fileclient.get_file_client( ++ self.opts, self.pillar_rend ++ ) ++ self._close_file_client = True + return self._file_client + + def cache_file(self, template): +@@ -221,6 +199,27 @@ class SaltCacheLoader(BaseLoader): + # there is no template file within searchpaths + raise TemplateNotFound(template) + ++ def destroy(self): ++ if self._close_file_client is False: ++ return ++ if self._file_client is None: ++ return ++ file_client = self._file_client ++ self._file_client = None ++ ++ try: ++ file_client.destroy() ++ except AttributeError: ++ # PillarClient and LocalClient objects do not have a destroy method ++ pass ++ ++ def __enter__(self): ++ self.file_client() ++ return self ++ ++ def __exit__(self, *args): ++ self.destroy() ++ + + class PrintableDict(OrderedDict): + """ +diff --git a/salt/utils/mako.py b/salt/utils/mako.py +index 69618de9837..037d5d86deb 100644 +--- a/salt/utils/mako.py ++++ b/salt/utils/mako.py +@@ -97,3 +97,10 @@ if HAS_MAKO: + self.cache[fpath] = self.file_client().get_file( + fpath, "", True, self.saltenv + ) ++ ++ def destroy(self): ++ if self.client: ++ try: ++ self.client.destroy() ++ except AttributeError: ++ pass +diff --git a/salt/utils/templates.py b/salt/utils/templates.py +index 4947b820a36..4a8adf2a14f 100644 +--- a/salt/utils/templates.py ++++ b/salt/utils/templates.py +@@ -362,163 +362,169 @@ def render_jinja_tmpl(tmplstr, context, tmplpath=None): + elif tmplstr.endswith("\n"): + newline = "\n" + +- if not saltenv: +- if tmplpath: +- loader = jinja2.FileSystemLoader(os.path.dirname(tmplpath)) +- else: +- loader = salt.utils.jinja.SaltCacheLoader( +- opts, +- saltenv, +- pillar_rend=context.get("_pillar_rend", False), +- _file_client=file_client, +- ) ++ try: ++ if not saltenv: ++ if tmplpath: ++ loader = jinja2.FileSystemLoader(os.path.dirname(tmplpath)) ++ else: ++ loader = salt.utils.jinja.SaltCacheLoader( ++ opts, ++ saltenv, ++ pillar_rend=context.get("_pillar_rend", False), ++ _file_client=file_client, ++ ) + +- env_args = {"extensions": [], "loader": loader} +- +- if hasattr(jinja2.ext, "with_"): +- env_args["extensions"].append("jinja2.ext.with_") +- if hasattr(jinja2.ext, "do"): +- env_args["extensions"].append("jinja2.ext.do") +- if hasattr(jinja2.ext, "loopcontrols"): +- env_args["extensions"].append("jinja2.ext.loopcontrols") +- env_args["extensions"].append(salt.utils.jinja.SerializerExtension) +- +- opt_jinja_env = opts.get("jinja_env", {}) +- opt_jinja_sls_env = opts.get("jinja_sls_env", {}) +- +- opt_jinja_env = opt_jinja_env if isinstance(opt_jinja_env, dict) else {} +- opt_jinja_sls_env = opt_jinja_sls_env if isinstance(opt_jinja_sls_env, dict) else {} +- +- # Pass through trim_blocks and lstrip_blocks Jinja parameters +- # trim_blocks removes newlines around Jinja blocks +- # lstrip_blocks strips tabs and spaces from the beginning of +- # line to the start of a block. +- if opts.get("jinja_trim_blocks", False): +- log.debug("Jinja2 trim_blocks is enabled") +- log.warning( +- "jinja_trim_blocks is deprecated and will be removed in a future release," +- " please use jinja_env and/or jinja_sls_env instead" +- ) +- opt_jinja_env["trim_blocks"] = True +- opt_jinja_sls_env["trim_blocks"] = True +- if opts.get("jinja_lstrip_blocks", False): +- log.debug("Jinja2 lstrip_blocks is enabled") +- log.warning( +- "jinja_lstrip_blocks is deprecated and will be removed in a future release," +- " please use jinja_env and/or jinja_sls_env instead" +- ) +- opt_jinja_env["lstrip_blocks"] = True +- opt_jinja_sls_env["lstrip_blocks"] = True +- +- def opt_jinja_env_helper(opts, optname): +- for k, v in opts.items(): +- k = k.lower() +- if hasattr(jinja2.defaults, k.upper()): +- log.debug("Jinja2 environment %s was set to %s by %s", k, v, optname) +- env_args[k] = v +- else: +- log.warning("Jinja2 environment %s is not recognized", k) ++ env_args = {"extensions": [], "loader": loader} + +- if "sls" in context and context["sls"] != "": +- opt_jinja_env_helper(opt_jinja_sls_env, "jinja_sls_env") +- else: +- opt_jinja_env_helper(opt_jinja_env, "jinja_env") ++ if hasattr(jinja2.ext, "with_"): ++ env_args["extensions"].append("jinja2.ext.with_") ++ if hasattr(jinja2.ext, "do"): ++ env_args["extensions"].append("jinja2.ext.do") ++ if hasattr(jinja2.ext, "loopcontrols"): ++ env_args["extensions"].append("jinja2.ext.loopcontrols") ++ env_args["extensions"].append(salt.utils.jinja.SerializerExtension) + +- if opts.get("allow_undefined", False): +- jinja_env = jinja2.sandbox.SandboxedEnvironment(**env_args) +- else: +- jinja_env = jinja2.sandbox.SandboxedEnvironment( +- undefined=jinja2.StrictUndefined, **env_args +- ) ++ opt_jinja_env = opts.get("jinja_env", {}) ++ opt_jinja_sls_env = opts.get("jinja_sls_env", {}) + +- indent_filter = jinja_env.filters.get("indent") +- jinja_env.tests.update(JinjaTest.salt_jinja_tests) +- jinja_env.filters.update(JinjaFilter.salt_jinja_filters) +- if salt.utils.jinja.JINJA_VERSION >= Version("2.11"): +- # Use the existing indent filter on Jinja versions where it's not broken +- jinja_env.filters["indent"] = indent_filter +- jinja_env.globals.update(JinjaGlobal.salt_jinja_globals) +- +- # globals +- jinja_env.globals["odict"] = OrderedDict +- jinja_env.globals["show_full_context"] = salt.utils.jinja.show_full_context +- +- jinja_env.tests["list"] = salt.utils.data.is_list +- +- decoded_context = {} +- for key, value in context.items(): +- if not isinstance(value, str): +- if isinstance(value, NamedLoaderContext): +- decoded_context[key] = value.value() +- else: +- decoded_context[key] = value +- continue ++ opt_jinja_env = opt_jinja_env if isinstance(opt_jinja_env, dict) else {} ++ opt_jinja_sls_env = ( ++ opt_jinja_sls_env if isinstance(opt_jinja_sls_env, dict) else {} ++ ) + +- try: +- decoded_context[key] = salt.utils.stringutils.to_unicode( +- value, encoding=SLS_ENCODING ++ # Pass through trim_blocks and lstrip_blocks Jinja parameters ++ # trim_blocks removes newlines around Jinja blocks ++ # lstrip_blocks strips tabs and spaces from the beginning of ++ # line to the start of a block. ++ if opts.get("jinja_trim_blocks", False): ++ log.debug("Jinja2 trim_blocks is enabled") ++ log.warning( ++ "jinja_trim_blocks is deprecated and will be removed in a future release," ++ " please use jinja_env and/or jinja_sls_env instead" + ) +- except UnicodeDecodeError as ex: +- log.debug( +- "Failed to decode using default encoding (%s), trying system encoding", +- SLS_ENCODING, ++ opt_jinja_env["trim_blocks"] = True ++ opt_jinja_sls_env["trim_blocks"] = True ++ if opts.get("jinja_lstrip_blocks", False): ++ log.debug("Jinja2 lstrip_blocks is enabled") ++ log.warning( ++ "jinja_lstrip_blocks is deprecated and will be removed in a future release," ++ " please use jinja_env and/or jinja_sls_env instead" + ) +- decoded_context[key] = salt.utils.data.decode(value) ++ opt_jinja_env["lstrip_blocks"] = True ++ opt_jinja_sls_env["lstrip_blocks"] = True ++ ++ def opt_jinja_env_helper(opts, optname): ++ for k, v in opts.items(): ++ k = k.lower() ++ if hasattr(jinja2.defaults, k.upper()): ++ log.debug( ++ "Jinja2 environment %s was set to %s by %s", k, v, optname ++ ) ++ env_args[k] = v ++ else: ++ log.warning("Jinja2 environment %s is not recognized", k) + +- jinja_env.globals.update(decoded_context) +- try: +- template = jinja_env.from_string(tmplstr) +- output = template.render(**decoded_context) +- except jinja2.exceptions.UndefinedError as exc: +- trace = traceback.extract_tb(sys.exc_info()[2]) +- line, out = _get_jinja_error(trace, context=decoded_context) +- if not line: +- tmplstr = "" +- raise SaltRenderError("Jinja variable {}{}".format(exc, out), line, tmplstr) +- except ( +- jinja2.exceptions.TemplateRuntimeError, +- jinja2.exceptions.TemplateSyntaxError, +- jinja2.exceptions.SecurityError, +- ) as exc: +- trace = traceback.extract_tb(sys.exc_info()[2]) +- line, out = _get_jinja_error(trace, context=decoded_context) +- if not line: +- tmplstr = "" +- raise SaltRenderError( +- "Jinja syntax error: {}{}".format(exc, out), line, tmplstr +- ) +- except (SaltInvocationError, CommandExecutionError) as exc: +- trace = traceback.extract_tb(sys.exc_info()[2]) +- line, out = _get_jinja_error(trace, context=decoded_context) +- if not line: +- tmplstr = "" +- raise SaltRenderError( +- "Problem running salt function in Jinja template: {}{}".format(exc, out), +- line, +- tmplstr, +- ) +- except Exception as exc: # pylint: disable=broad-except +- tracestr = traceback.format_exc() +- trace = traceback.extract_tb(sys.exc_info()[2]) +- line, out = _get_jinja_error(trace, context=decoded_context) +- if not line: +- tmplstr = "" ++ if "sls" in context and context["sls"] != "": ++ opt_jinja_env_helper(opt_jinja_sls_env, "jinja_sls_env") + else: +- tmplstr += "\n{}".format(tracestr) +- log.debug("Jinja Error") +- log.debug("Exception:", exc_info=True) +- log.debug("Out: %s", out) +- log.debug("Line: %s", line) +- log.debug("TmplStr: %s", tmplstr) +- log.debug("TraceStr: %s", tracestr) ++ opt_jinja_env_helper(opt_jinja_env, "jinja_env") + +- raise SaltRenderError( +- "Jinja error: {}{}".format(exc, out), line, tmplstr, trace=tracestr +- ) ++ if opts.get("allow_undefined", False): ++ jinja_env = jinja2.sandbox.SandboxedEnvironment(**env_args) ++ else: ++ jinja_env = jinja2.sandbox.SandboxedEnvironment( ++ undefined=jinja2.StrictUndefined, **env_args ++ ) ++ ++ indent_filter = jinja_env.filters.get("indent") ++ jinja_env.tests.update(JinjaTest.salt_jinja_tests) ++ jinja_env.filters.update(JinjaFilter.salt_jinja_filters) ++ if salt.utils.jinja.JINJA_VERSION >= Version("2.11"): ++ # Use the existing indent filter on Jinja versions where it's not broken ++ jinja_env.filters["indent"] = indent_filter ++ jinja_env.globals.update(JinjaGlobal.salt_jinja_globals) ++ ++ # globals ++ jinja_env.globals["odict"] = OrderedDict ++ jinja_env.globals["show_full_context"] = salt.utils.jinja.show_full_context ++ ++ jinja_env.tests["list"] = salt.utils.data.is_list ++ ++ decoded_context = {} ++ for key, value in context.items(): ++ if not isinstance(value, str): ++ if isinstance(value, NamedLoaderContext): ++ decoded_context[key] = value.value() ++ else: ++ decoded_context[key] = value ++ continue ++ ++ try: ++ decoded_context[key] = salt.utils.stringutils.to_unicode( ++ value, encoding=SLS_ENCODING ++ ) ++ except UnicodeDecodeError: ++ log.debug( ++ "Failed to decode using default encoding (%s), trying system encoding", ++ SLS_ENCODING, ++ ) ++ decoded_context[key] = salt.utils.data.decode(value) ++ ++ jinja_env.globals.update(decoded_context) ++ try: ++ template = jinja_env.from_string(tmplstr) ++ output = template.render(**decoded_context) ++ except jinja2.exceptions.UndefinedError as exc: ++ trace = traceback.extract_tb(sys.exc_info()[2]) ++ line, out = _get_jinja_error(trace, context=decoded_context) ++ if not line: ++ tmplstr = "" ++ raise SaltRenderError("Jinja variable {}{}".format(exc, out), line, tmplstr) ++ except ( ++ jinja2.exceptions.TemplateRuntimeError, ++ jinja2.exceptions.TemplateSyntaxError, ++ jinja2.exceptions.SecurityError, ++ ) as exc: ++ trace = traceback.extract_tb(sys.exc_info()[2]) ++ line, out = _get_jinja_error(trace, context=decoded_context) ++ if not line: ++ tmplstr = "" ++ raise SaltRenderError( ++ "Jinja syntax error: {}{}".format(exc, out), line, tmplstr ++ ) ++ except (SaltInvocationError, CommandExecutionError) as exc: ++ trace = traceback.extract_tb(sys.exc_info()[2]) ++ line, out = _get_jinja_error(trace, context=decoded_context) ++ if not line: ++ tmplstr = "" ++ raise SaltRenderError( ++ "Problem running salt function in Jinja template: {}{}".format( ++ exc, out ++ ), ++ line, ++ tmplstr, ++ ) ++ except Exception as exc: # pylint: disable=broad-except ++ tracestr = traceback.format_exc() ++ trace = traceback.extract_tb(sys.exc_info()[2]) ++ line, out = _get_jinja_error(trace, context=decoded_context) ++ if not line: ++ tmplstr = "" ++ else: ++ tmplstr += "\n{}".format(tracestr) ++ log.debug("Jinja Error") ++ log.debug("Exception:", exc_info=True) ++ log.debug("Out: %s", out) ++ log.debug("Line: %s", line) ++ log.debug("TmplStr: %s", tmplstr) ++ log.debug("TraceStr: %s", tracestr) ++ ++ raise SaltRenderError( ++ "Jinja error: {}{}".format(exc, out), line, tmplstr, trace=tracestr ++ ) + finally: +- if loader and hasattr(loader, "_file_client"): +- if hasattr(loader._file_client, "destroy"): +- loader._file_client.destroy() ++ if loader and isinstance(loader, salt.utils.jinja.SaltCacheLoader): ++ loader.destroy() + + # Workaround a bug in Jinja that removes the final newline + # (https://github.com/mitsuhiko/jinja2/issues/75) +@@ -569,9 +575,8 @@ def render_mako_tmpl(tmplstr, context, tmplpath=None): + except Exception: # pylint: disable=broad-except + raise SaltRenderError(mako.exceptions.text_error_template().render()) + finally: +- if lookup and hasattr(lookup, "_file_client"): +- if hasattr(lookup._file_client, "destroy"): +- lookup._file_client.destroy() ++ if lookup and isinstance(lookup, SaltMakoTemplateLookup): ++ lookup.destroy() + + + def render_wempy_tmpl(tmplstr, context, tmplpath=None): +diff --git a/tests/pytests/integration/states/test_include.py b/tests/pytests/integration/states/test_include.py +new file mode 100644 +index 00000000000..f814328c5e4 +--- /dev/null ++++ b/tests/pytests/integration/states/test_include.py +@@ -0,0 +1,40 @@ ++""" ++Integration tests for the jinja includes in states ++""" ++import logging ++ ++import pytest ++ ++log = logging.getLogger(__name__) ++ ++ ++@pytest.mark.slow_test ++def test_issue_64111(salt_master, salt_minion, salt_call_cli): ++ # This needs to be an integration test. A functional test does not trigger ++ # the issue fixed. ++ ++ macros_jinja = """ ++ {% macro a_jinja_macro(arg) -%} ++ {{ arg }} ++ {%- endmacro %} ++ """ ++ ++ init_sls = """ ++ include: ++ - common.file1 ++ """ ++ ++ file1_sls = """ ++ {% from 'common/macros.jinja' import a_jinja_macro with context %} ++ ++ a state id: ++ cmd.run: ++ - name: echo {{ a_jinja_macro("hello world") }} ++ """ ++ tf = salt_master.state_tree.base.temp_file ++ ++ with tf("common/macros.jinja", macros_jinja): ++ with tf("common/init.sls", init_sls): ++ with tf("common/file1.sls", file1_sls): ++ ret = salt_call_cli.run("state.apply", "common") ++ assert ret.returncode == 0 +diff --git a/tests/pytests/unit/utils/jinja/test_salt_cache_loader.py b/tests/pytests/unit/utils/jinja/test_salt_cache_loader.py +index 38c5ce5b724..e0f5fa158ff 100644 +--- a/tests/pytests/unit/utils/jinja/test_salt_cache_loader.py ++++ b/tests/pytests/unit/utils/jinja/test_salt_cache_loader.py +@@ -15,7 +15,7 @@ import salt.utils.json # pylint: disable=unused-import + import salt.utils.stringutils # pylint: disable=unused-import + import salt.utils.yaml # pylint: disable=unused-import + from salt.utils.jinja import SaltCacheLoader +-from tests.support.mock import Mock, patch ++from tests.support.mock import Mock, call, patch + + + @pytest.fixture +@@ -224,14 +224,45 @@ def test_file_client_kwarg(minion_opts, mock_file_client): + assert loader._file_client is mock_file_client + + +-def test_cache_loader_shutdown(minion_opts, mock_file_client): ++def test_cache_loader_passed_file_client(minion_opts, mock_file_client): + """ + The shudown method can be called without raising an exception when the + file_client does not have a destroy method + """ +- assert not hasattr(mock_file_client, "destroy") +- mock_file_client.opts = minion_opts +- loader = SaltCacheLoader(minion_opts, _file_client=mock_file_client) +- assert loader._file_client is mock_file_client +- # Shutdown method should not raise any exceptions +- loader.shutdown() ++ # Test SaltCacheLoader creating and destroying the file client created ++ file_client = Mock() ++ with patch("salt.fileclient.get_file_client", return_value=file_client): ++ loader = SaltCacheLoader(minion_opts) ++ assert loader._file_client is None ++ with loader: ++ assert loader._file_client is file_client ++ assert loader._file_client is None ++ assert file_client.mock_calls == [call.destroy()] ++ ++ # Test SaltCacheLoader reusing the file client passed ++ file_client = Mock() ++ file_client.opts = {"file_roots": minion_opts["file_roots"]} ++ with patch("salt.fileclient.get_file_client", return_value=Mock()): ++ loader = SaltCacheLoader(minion_opts, _file_client=file_client) ++ assert loader._file_client is file_client ++ with loader: ++ assert loader._file_client is file_client ++ assert loader._file_client is file_client ++ assert file_client.mock_calls == [] ++ ++ # Test SaltCacheLoader creating a client even though a file client was ++ # passed because the "file_roots" option is different, and, as such, ++ # the destroy method on the new file client is called, but not on the ++ # file client passed in. ++ file_client = Mock() ++ file_client.opts = {"file_roots": ""} ++ new_file_client = Mock() ++ with patch("salt.fileclient.get_file_client", return_value=new_file_client): ++ loader = SaltCacheLoader(minion_opts, _file_client=file_client) ++ assert loader._file_client is file_client ++ with loader: ++ assert loader._file_client is not file_client ++ assert loader._file_client is new_file_client ++ assert loader._file_client is None ++ assert file_client.mock_calls == [] ++ assert new_file_client.mock_calls == [call.destroy()] +-- +2.40.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..e52f765 --- /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/pass-the-context-to-pillar-ext-modules.patch b/pass-the-context-to-pillar-ext-modules.patch new file mode 100644 index 0000000..3fbe61e --- /dev/null +++ b/pass-the-context-to-pillar-ext-modules.patch @@ -0,0 +1,276 @@ +From bd671b53de8933732e2108624d7dfb6f9b183f38 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Fri, 28 Oct 2022 13:20:13 +0300 +Subject: [PATCH] Pass the context to pillar ext modules + +* Pass __context__ to ext pillar + +* Add test for passing the context to pillar ext module + +* Align the test and pillar to prevent failing test +--- + salt/master.py | 7 ++- + salt/pillar/__init__.py | 16 +++++- + tests/pytests/unit/test_master.py | 91 ++++++++++++++++++++++++++++++- + 3 files changed, 108 insertions(+), 6 deletions(-) + +diff --git a/salt/master.py b/salt/master.py +index a0552fa232..da1eb8cef5 100644 +--- a/salt/master.py ++++ b/salt/master.py +@@ -964,6 +964,7 @@ class MWorker(salt.utils.process.SignalHandlingProcess): + self.k_mtime = 0 + self.stats = collections.defaultdict(lambda: {"mean": 0, "runs": 0}) + self.stat_clock = time.time() ++ self.context = {} + + # We need __setstate__ and __getstate__ to also pickle 'SMaster.secrets'. + # Otherwise, 'SMaster.secrets' won't be copied over to the spawned process +@@ -1151,7 +1152,7 @@ class MWorker(salt.utils.process.SignalHandlingProcess): + self.key, + ) + self.clear_funcs.connect() +- self.aes_funcs = AESFuncs(self.opts) ++ self.aes_funcs = AESFuncs(self.opts, context=self.context) + salt.utils.crypt.reinit_crypto() + self.__bind() + +@@ -1214,7 +1215,7 @@ class AESFuncs(TransportMethods): + "_file_envs", + ) + +- def __init__(self, opts): ++ def __init__(self, opts, context=None): + """ + Create a new AESFuncs + +@@ -1224,6 +1225,7 @@ class AESFuncs(TransportMethods): + :returns: Instance for handling AES operations + """ + self.opts = opts ++ self.context = context + self.event = salt.utils.event.get_master_event( + self.opts, self.opts["sock_dir"], listen=False + ) +@@ -1611,6 +1613,7 @@ class AESFuncs(TransportMethods): + pillarenv=load.get("pillarenv"), + extra_minion_data=load.get("extra_minion_data"), + clean_cache=load.get("clean_cache"), ++ context=self.context, + ) + data = pillar.compile_pillar() + self.fs_.update_opts() +diff --git a/salt/pillar/__init__.py b/salt/pillar/__init__.py +index 5a3f5388b4..0dfab4cc57 100644 +--- a/salt/pillar/__init__.py ++++ b/salt/pillar/__init__.py +@@ -46,6 +46,7 @@ def get_pillar( + pillarenv=None, + extra_minion_data=None, + clean_cache=False, ++ context=None, + ): + """ + Return the correct pillar driver based on the file_client option +@@ -82,6 +83,7 @@ def get_pillar( + pillarenv=pillarenv, + clean_cache=clean_cache, + extra_minion_data=extra_minion_data, ++ context=context, + ) + return ptype( + opts, +@@ -93,6 +95,7 @@ def get_pillar( + pillar_override=pillar_override, + pillarenv=pillarenv, + extra_minion_data=extra_minion_data, ++ context=context, + ) + + +@@ -281,7 +284,7 @@ class AsyncRemotePillar(RemotePillarMixin): + raise salt.ext.tornado.gen.Return(ret_pillar) + + def destroy(self): +- if self._closing: ++ if hasattr(self, "_closing") and self._closing: + return + + self._closing = True +@@ -310,6 +313,7 @@ class RemotePillar(RemotePillarMixin): + pillar_override=None, + pillarenv=None, + extra_minion_data=None, ++ context=None, + ): + self.opts = opts + self.opts["saltenv"] = saltenv +@@ -334,6 +338,7 @@ class RemotePillar(RemotePillarMixin): + merge_lists=True, + ) + self._closing = False ++ self.context = context + + def compile_pillar(self): + """ +@@ -407,6 +412,7 @@ class PillarCache: + pillarenv=None, + extra_minion_data=None, + clean_cache=False, ++ context=None, + ): + # Yes, we need all of these because we need to route to the Pillar object + # if we have no cache. This is another refactor target. +@@ -434,6 +440,8 @@ class PillarCache: + minion_cache_path=self._minion_cache_path(minion_id), + ) + ++ self.context = context ++ + def _minion_cache_path(self, minion_id): + """ + Return the path to the cache file for the minion. +@@ -458,6 +466,7 @@ class PillarCache: + pillar_override=self.pillar_override, + pillarenv=self.pillarenv, + extra_minion_data=self.extra_minion_data, ++ context=self.context, + ) + return fresh_pillar.compile_pillar() + +@@ -533,6 +542,7 @@ class Pillar: + pillar_override=None, + pillarenv=None, + extra_minion_data=None, ++ context=None, + ): + self.minion_id = minion_id + self.ext = ext +@@ -571,7 +581,7 @@ class Pillar: + if opts.get("pillar_source_merging_strategy"): + self.merge_strategy = opts["pillar_source_merging_strategy"] + +- self.ext_pillars = salt.loader.pillars(ext_pillar_opts, self.functions) ++ self.ext_pillars = salt.loader.pillars(ext_pillar_opts, self.functions, context=context) + self.ignored_pillars = {} + self.pillar_override = pillar_override or {} + if not isinstance(self.pillar_override, dict): +@@ -1338,7 +1348,7 @@ class Pillar: + """ + This method exist in order to be API compatible with RemotePillar + """ +- if self._closing: ++ if hasattr(self, "_closing") and self._closing: + return + self._closing = True + +diff --git a/tests/pytests/unit/test_master.py b/tests/pytests/unit/test_master.py +index cd11d217c7..98c796912a 100644 +--- a/tests/pytests/unit/test_master.py ++++ b/tests/pytests/unit/test_master.py +@@ -4,7 +4,7 @@ import pytest + + import salt.master + import salt.utils.platform +-from tests.support.mock import patch ++from tests.support.mock import MagicMock, patch + + + @pytest.fixture +@@ -160,3 +160,92 @@ def test_when_syndic_return_processes_load_then_correct_values_should_be_returne + with patch.object(encrypted_requests, "_return", autospec=True) as fake_return: + encrypted_requests._syndic_return(payload) + fake_return.assert_called_with(expected_return) ++ ++ ++def test_mworker_pass_context(): ++ """ ++ Test of passing the __context__ to pillar ext module loader ++ """ ++ req_channel_mock = MagicMock() ++ local_client_mock = MagicMock() ++ ++ opts = { ++ "req_server_niceness": None, ++ "mworker_niceness": None, ++ "sock_dir": "/tmp", ++ "conf_file": "/tmp/fake_conf", ++ "transport": "zeromq", ++ "fileserver_backend": ["roots"], ++ "file_client": "local", ++ "pillar_cache": False, ++ "state_top": "top.sls", ++ "pillar_roots": {}, ++ } ++ ++ data = { ++ "id": "MINION_ID", ++ "grains": {}, ++ "saltenv": None, ++ "pillarenv": None, ++ "pillar_override": {}, ++ "extra_minion_data": {}, ++ "ver": "2", ++ "cmd": "_pillar", ++ } ++ ++ test_context = {"testing": 123} ++ ++ def mworker_bind_mock(): ++ mworker.aes_funcs.run_func(data["cmd"], data) ++ ++ with patch("salt.client.get_local_client", local_client_mock), patch( ++ "salt.master.ClearFuncs", MagicMock() ++ ), patch("salt.minion.MasterMinion", MagicMock()), patch( ++ "salt.utils.verify.valid_id", return_value=True ++ ), patch( ++ "salt.loader.matchers", MagicMock() ++ ), patch( ++ "salt.loader.render", MagicMock() ++ ), patch( ++ "salt.loader.utils", MagicMock() ++ ), patch( ++ "salt.loader.fileserver", MagicMock() ++ ), patch( ++ "salt.loader.minion_mods", MagicMock() ++ ), patch( ++ "salt.loader._module_dirs", MagicMock() ++ ), patch( ++ "salt.loader.LazyLoader", MagicMock() ++ ) as loadler_pillars_mock: ++ mworker = salt.master.MWorker(opts, {}, {}, [req_channel_mock]) ++ ++ with patch.object(mworker, "_MWorker__bind", mworker_bind_mock), patch.dict( ++ mworker.context, test_context ++ ): ++ mworker.run() ++ assert ( ++ loadler_pillars_mock.call_args_list[0][1].get("pack").get("__context__") ++ == test_context ++ ) ++ ++ loadler_pillars_mock.reset_mock() ++ ++ opts.update( ++ { ++ "pillar_cache": True, ++ "pillar_cache_backend": "file", ++ "pillar_cache_ttl": 1000, ++ "cachedir": "/tmp", ++ } ++ ) ++ ++ mworker = salt.master.MWorker(opts, {}, {}, [req_channel_mock]) ++ ++ with patch.object(mworker, "_MWorker__bind", mworker_bind_mock), patch.dict( ++ mworker.context, test_context ++ ), patch("salt.utils.cache.CacheFactory.factory", MagicMock()): ++ mworker.run() ++ assert ( ++ loadler_pillars_mock.call_args_list[0][1].get("pack").get("__context__") ++ == test_context ++ ) +-- +2.39.2 + + diff --git a/prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch b/prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch new file mode 100644 index 0000000..7d139ac --- /dev/null +++ b/prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch @@ -0,0 +1,240 @@ +From 90236c844cfce7da8beb7a570be19a8677c60820 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Tue, 12 Apr 2022 10:06:43 +0300 +Subject: [PATCH] Prevent affection of SSH.opts with LazyLoader + (bsc#1197637) + +* Prevent affection SSH.opts with LazyLoader + +* Restore parsed targets + +* Fix test_ssh unit tests + +Adjust unit tests +--- + salt/client/ssh/__init__.py | 19 +++++++++------- + .../pytests/unit/client/ssh/test_password.py | 4 +++- + .../unit/client/ssh/test_return_events.py | 2 +- + tests/pytests/unit/client/ssh/test_ssh.py | 22 +++++++++---------- + 4 files changed, 26 insertions(+), 21 deletions(-) + +diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py +index a527c03de6..d5a679821e 100644 +--- a/salt/client/ssh/__init__.py ++++ b/salt/client/ssh/__init__.py +@@ -224,15 +224,16 @@ class SSH(MultiprocessingStateMixin): + ROSTER_UPDATE_FLAG = "#__needs_update" + + def __init__(self, opts, context=None): ++ self.opts = copy.deepcopy(opts) ++ self.sopts = copy.deepcopy(self.opts) + self.__parsed_rosters = {SSH.ROSTER_UPDATE_FLAG: True} +- pull_sock = os.path.join(opts["sock_dir"], "master_event_pull.ipc") ++ pull_sock = os.path.join(self.opts["sock_dir"], "master_event_pull.ipc") + if os.path.exists(pull_sock) and zmq: + self.event = salt.utils.event.get_event( +- "master", opts["sock_dir"], opts=opts, listen=False ++ "master", self.opts["sock_dir"], opts=self.opts, listen=False + ) + else: + self.event = None +- self.opts = opts + if self.opts["regen_thin"]: + self.opts["ssh_wipe"] = True + if not salt.utils.path.which("ssh"): +@@ -243,7 +244,7 @@ class SSH(MultiprocessingStateMixin): + " to run. Exiting." + ), + ) +- self.opts["_ssh_version"] = ssh_version() ++ self.sopts["_ssh_version"] = ssh_version() + self.tgt_type = ( + self.opts["selected_target_option"] + if self.opts["selected_target_option"] +@@ -339,6 +340,9 @@ class SSH(MultiprocessingStateMixin): + self.opts["cachedir"], "salt-ssh.session.lock" + ) + self.ssh_session_grace_time = int(self.opts.get("ssh_session_grace_time", 1)) ++ self.sopts["tgt"] = copy.deepcopy(self.opts["tgt"]) ++ self.sopts["ssh_cli_tgt"] = copy.deepcopy(self.opts["ssh_cli_tgt"]) ++ self.opts = self.sopts + + # __setstate__ and __getstate__ are only used on spawning platforms. + def __setstate__(self, state): +@@ -607,7 +611,6 @@ class SSH(MultiprocessingStateMixin): + Spin up the needed threads or processes and execute the subsequent + routines + """ +- opts = copy.deepcopy(self.opts) + que = multiprocessing.Queue() + running = {} + targets_queue = deque(self.targets.keys()) +@@ -618,7 +621,7 @@ class SSH(MultiprocessingStateMixin): + if not self.targets: + log.error("No matching targets found in roster.") + break +- if len(running) < opts.get("ssh_max_procs", 25) and not init: ++ if len(running) < self.opts.get("ssh_max_procs", 25) and not init: + if targets_queue: + host = targets_queue.popleft() + else: +@@ -682,7 +685,7 @@ class SSH(MultiprocessingStateMixin): + continue + args = ( + que, +- opts, ++ self.opts, + host, + self.targets[host], + mine, +@@ -776,7 +779,7 @@ class SSH(MultiprocessingStateMixin): + if len(rets) >= len(self.targets): + break + # Sleep when limit or all threads started +- if len(running) >= opts.get("ssh_max_procs", 25) or len( ++ if len(running) >= self.opts.get("ssh_max_procs", 25) or len( + self.targets + ) >= len(running): + time.sleep(0.1) +diff --git a/tests/pytests/unit/client/ssh/test_password.py b/tests/pytests/unit/client/ssh/test_password.py +index 8a7794d2f4..0ca28d022e 100644 +--- a/tests/pytests/unit/client/ssh/test_password.py ++++ b/tests/pytests/unit/client/ssh/test_password.py +@@ -27,6 +27,8 @@ def test_password_failure(temp_salt_master, tmp_path): + opts["argv"] = ["test.ping"] + opts["selected_target_option"] = "glob" + opts["tgt"] = "localhost" ++ opts["ssh_cli_tgt"] = "localhost" ++ opts["_ssh_version"] = "foobar" + opts["arg"] = [] + roster = str(tmp_path / "roster") + handle_ssh_ret = [ +@@ -44,7 +46,7 @@ def test_password_failure(temp_salt_master, tmp_path): + "salt.client.ssh.SSH.handle_ssh", MagicMock(return_value=handle_ssh_ret) + ), patch("salt.client.ssh.SSH.key_deploy", MagicMock(return_value=expected)), patch( + "salt.output.display_output", display_output +- ): ++ ), patch("salt.client.ssh.ssh_version", MagicMock(return_value="foobar")): + client = ssh.SSH(opts) + ret = next(client.run_iter()) + with pytest.raises(SystemExit): +diff --git a/tests/pytests/unit/client/ssh/test_return_events.py b/tests/pytests/unit/client/ssh/test_return_events.py +index 1f0b0dbf33..18714741b9 100644 +--- a/tests/pytests/unit/client/ssh/test_return_events.py ++++ b/tests/pytests/unit/client/ssh/test_return_events.py +@@ -43,7 +43,7 @@ def test_not_missing_fun_calling_wfuncs(temp_salt_master, tmp_path): + assert "localhost" in ret + assert "fun" in ret["localhost"] + client.run() +- display_output.assert_called_once_with(expected, "nested", opts) ++ display_output.assert_called_once_with(expected, "nested", client.opts) + assert ret is handle_ssh_ret[0] + assert len(client.event.fire_event.call_args_list) == 2 + assert "fun" in client.event.fire_event.call_args_list[0][0][0] +diff --git a/tests/pytests/unit/client/ssh/test_ssh.py b/tests/pytests/unit/client/ssh/test_ssh.py +index 2be96ab195..377aad9998 100644 +--- a/tests/pytests/unit/client/ssh/test_ssh.py ++++ b/tests/pytests/unit/client/ssh/test_ssh.py +@@ -148,7 +148,7 @@ def test_expand_target_ip_address(opts, roster): + MagicMock(return_value=salt.utils.yaml.safe_load(roster)), + ): + client._expand_target() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + + + def test_expand_target_no_host(opts, tmp_path): +@@ -171,7 +171,7 @@ def test_expand_target_no_host(opts, tmp_path): + assert opts["tgt"] == user + host + with patch("salt.roster.get_roster_file", MagicMock(return_value=roster_file)): + client._expand_target() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + + + def test_expand_target_dns(opts, roster): +@@ -192,7 +192,7 @@ def test_expand_target_dns(opts, roster): + MagicMock(return_value=salt.utils.yaml.safe_load(roster)), + ): + client._expand_target() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + + + def test_expand_target_no_user(opts, roster): +@@ -204,7 +204,7 @@ def test_expand_target_no_user(opts, roster): + + with patch("salt.utils.network.is_reachable_host", MagicMock(return_value=False)): + client = ssh.SSH(opts) +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + + with patch( + "salt.roster.get_roster_file", MagicMock(return_value="/etc/salt/roster") +@@ -213,7 +213,7 @@ def test_expand_target_no_user(opts, roster): + MagicMock(return_value=salt.utils.yaml.safe_load(roster)), + ): + client._expand_target() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + + + def test_update_targets_ip_address(opts): +@@ -228,7 +228,7 @@ def test_update_targets_ip_address(opts): + client = ssh.SSH(opts) + assert opts["tgt"] == user + host + client._update_targets() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + assert client.targets[host]["user"] == user.split("@")[0] + + +@@ -244,7 +244,7 @@ def test_update_targets_dns(opts): + client = ssh.SSH(opts) + assert opts["tgt"] == user + host + client._update_targets() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + assert client.targets[host]["user"] == user.split("@")[0] + + +@@ -259,7 +259,7 @@ def test_update_targets_no_user(opts): + client = ssh.SSH(opts) + assert opts["tgt"] == host + client._update_targets() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + + + def test_update_expand_target_dns(opts, roster): +@@ -281,7 +281,7 @@ def test_update_expand_target_dns(opts, roster): + ): + client._expand_target() + client._update_targets() +- assert opts["tgt"] == host ++ assert client.opts["tgt"] == host + assert client.targets[host]["user"] == user.split("@")[0] + + +@@ -299,7 +299,7 @@ def test_parse_tgt(opts): + client = ssh.SSH(opts) + assert client.parse_tgt["hostname"] == host + assert client.parse_tgt["user"] == user.split("@")[0] +- assert opts.get("ssh_cli_tgt") == user + host ++ assert client.opts.get("ssh_cli_tgt") == user + host + + + def test_parse_tgt_no_user(opts): +@@ -316,7 +316,7 @@ def test_parse_tgt_no_user(opts): + client = ssh.SSH(opts) + assert client.parse_tgt["hostname"] == host + assert client.parse_tgt["user"] == opts["ssh_user"] +- assert opts.get("ssh_cli_tgt") == host ++ assert client.opts.get("ssh_cli_tgt") == host + + + def test_extra_filerefs(tmp_path, opts): +-- +2.39.2 + + diff --git a/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch b/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch new file mode 100644 index 0000000..e2d2871 --- /dev/null +++ b/prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch @@ -0,0 +1,102 @@ +From 4240f0d5ffbc46c557885c5a28d1f2fd0b4c5e48 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Mon, 8 Nov 2021 17:42:36 +0300 +Subject: [PATCH] Prevent pkg plugins errors on missing cookie path + (bsc#1186738) - 3002.2 (#415) + +* Prevent pkg plugins errors on missing cookie path (bsc#1186738) + +* Narrowing down exception handling + +* Modify for Python 3 only + +* Fix yumnotify +--- + scripts/suse/yum/plugins/README.md | 2 +- + scripts/suse/yum/plugins/yumnotify.py | 17 +++++++++++++---- + scripts/suse/zypper/plugins/commit/zyppnotify | 18 ++++++++++++------ + 3 files changed, 26 insertions(+), 11 deletions(-) + +diff --git a/scripts/suse/yum/plugins/README.md b/scripts/suse/yum/plugins/README.md +index cb3abd2260..3515845b31 100644 +--- a/scripts/suse/yum/plugins/README.md ++++ b/scripts/suse/yum/plugins/README.md +@@ -11,7 +11,7 @@ Configuration files are going to: + + Plugin itself goes to: + +- `/usr/share/yum-plugins/[name].conf` ++ `/usr/share/yum-plugins/[name].py` + + ## Permissions + +diff --git a/scripts/suse/yum/plugins/yumnotify.py b/scripts/suse/yum/plugins/yumnotify.py +index 4e137191a0..0d117e8946 100644 +--- a/scripts/suse/yum/plugins/yumnotify.py ++++ b/scripts/suse/yum/plugins/yumnotify.py +@@ -5,6 +5,7 @@ + + import hashlib + import os ++import sys + + from yum.plugins import TYPE_CORE + +@@ -51,7 +52,15 @@ def posttrans_hook(conduit): + """ + # Integrate Yum with Salt + if "SALT_RUNNING" not in os.environ: +- with open(CK_PATH, "w") as ck_fh: +- ck_fh.write( +- "{chksum} {mtime}\n".format(chksum=_get_checksum(), mtime=_get_mtime()) +- ) ++ try: ++ ck_dir = os.path.dirname(CK_PATH) ++ if not os.path.exists(ck_dir): ++ os.makedirs(ck_dir) ++ with open(CK_PATH, "w") as ck_fh: ++ ck_fh.write( ++ "{chksum} {mtime}\n".format( ++ chksum=_get_checksum(), mtime=_get_mtime() ++ ) ++ ) ++ except OSError as e: ++ print("Unable to save the cookie file: %s" % (e), file=sys.stderr) +diff --git a/scripts/suse/zypper/plugins/commit/zyppnotify b/scripts/suse/zypper/plugins/commit/zyppnotify +index bacbc8b97e..e3528e87a9 100755 +--- a/scripts/suse/zypper/plugins/commit/zyppnotify ++++ b/scripts/suse/zypper/plugins/commit/zyppnotify +@@ -1,4 +1,4 @@ +-#!/usr/bin/python ++#!/usr/bin/python3 + # + # Copyright (c) 2016 SUSE Linux LLC + # All Rights Reserved. +@@ -55,12 +55,18 @@ class DriftDetector(Plugin): + Hook when plugin closes Zypper's transaction. + """ + if "SALT_RUNNING" not in os.environ: +- with open(self.ck_path, "w") as ck_fh: +- ck_fh.write( +- "{chksum} {mtime}\n".format( +- chksum=self._get_checksum(), mtime=self._get_mtime() ++ try: ++ ck_dir = os.path.dirname(self.ck_path) ++ if not os.path.exists(ck_dir): ++ os.makedirs(ck_dir) ++ with open(self.ck_path, "w") as ck_fh: ++ ck_fh.write( ++ "{chksum} {mtime}\n".format( ++ chksum=self._get_checksum(), mtime=self._get_mtime() ++ ) + ) +- ) ++ except OSError as e: ++ print("Unable to save the cookie file: %s" % (e), file=sys.stderr) + + self.ack() + +-- +2.39.2 + + diff --git a/prevent-possible-exceptions-on-salt.utils.user.get_g.patch b/prevent-possible-exceptions-on-salt.utils.user.get_g.patch new file mode 100644 index 0000000..39ac71c --- /dev/null +++ b/prevent-possible-exceptions-on-salt.utils.user.get_g.patch @@ -0,0 +1,68 @@ +From 4ea91a61abbb6ef001f057685370454c85b72c3a Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Mon, 21 Aug 2023 13:04:32 +0200 +Subject: [PATCH] Prevent possible exceptions on + salt.utils.user.get_group_dict (bsc#1212794) + +* Prevent KeyError on calling grp.getgrnam in case of improper group + +* Add test of calling salt.utils.user.get_group_dict + +for the user having improper duplicate group + +* Update tests/pytests/functional/utils/user/test_get_group_dict.py + +Co-authored-by: Pedro Algarvio + +--------- + +Co-authored-by: Pedro Algarvio +--- + salt/utils/user.py | 6 +++++- + .../utils/user/test_get_group_dict.py | 17 +++++++++++++++++ + 2 files changed, 22 insertions(+), 1 deletion(-) + create mode 100644 tests/pytests/functional/utils/user/test_get_group_dict.py + +diff --git a/salt/utils/user.py b/salt/utils/user.py +index 9763667443..2f1ca65cf9 100644 +--- a/salt/utils/user.py ++++ b/salt/utils/user.py +@@ -352,7 +352,11 @@ def get_group_dict(user=None, include_default=True): + group_dict = {} + group_names = get_group_list(user, include_default=include_default) + for group in group_names: +- group_dict.update({group: grp.getgrnam(group).gr_gid}) ++ try: ++ group_dict.update({group: grp.getgrnam(group).gr_gid}) ++ except KeyError: ++ # In case if imporer duplicate group was returned by get_group_list ++ pass + return group_dict + + +diff --git a/tests/pytests/functional/utils/user/test_get_group_dict.py b/tests/pytests/functional/utils/user/test_get_group_dict.py +new file mode 100644 +index 0000000000..653d664607 +--- /dev/null ++++ b/tests/pytests/functional/utils/user/test_get_group_dict.py +@@ -0,0 +1,17 @@ ++import logging ++ ++import pytest ++ ++import salt.utils.platform ++import salt.utils.user ++from tests.support.mock import patch ++ ++log = logging.getLogger(__name__) ++ ++pytestmark = [ ++ pytest.mark.skip_unless_on_linux(reason="Should only run in platforms which have duplicate GID support"), ++] ++def test_get_group_dict_with_improper_duplicate_root_group(): ++ with patch("salt.utils.user.get_group_list", return_value=["+", "root"]): ++ group_list = salt.utils.user.get_group_dict("root") ++ assert group_list == {"root": 0} +-- +2.41.0 + diff --git a/prevent-shell-injection-via-pre_flight_script_args-4.patch b/prevent-shell-injection-via-pre_flight_script_args-4.patch new file mode 100644 index 0000000..de02448 --- /dev/null +++ b/prevent-shell-injection-via-pre_flight_script_args-4.patch @@ -0,0 +1,33 @@ +From 1b4e382856e1d5d8ef95890aec5a8e5e07254708 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Mon, 28 Feb 2022 14:25:43 +0000 +Subject: [PATCH] Prevent shell injection via pre_flight_script_args + (#497) + +Add tests around preflight script args + +Readjust logic to validate script args + +Use RLock to prevent issues in single threads +--- + salt/_logging/impl.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/_logging/impl.py b/salt/_logging/impl.py +index e050f43caf..2d1a276cb8 100644 +--- a/salt/_logging/impl.py ++++ b/salt/_logging/impl.py +@@ -107,7 +107,7 @@ DFLT_LOG_FMT_LOGFILE = "%(asctime)s,%(msecs)03d [%(name)-17s:%(lineno)-4d][%(lev + + # LOG_LOCK is used to prevent deadlocks on using logging + # in combination with multiprocessing with salt-api +-LOG_LOCK = threading.Lock() ++LOG_LOCK = threading.RLock() + + + class SaltLogRecord(logging.LogRecord): +-- +2.39.2 + + diff --git a/read-repo-info-without-using-interpolation-bsc-11356.patch b/read-repo-info-without-using-interpolation-bsc-11356.patch new file mode 100644 index 0000000..c06911a --- /dev/null +++ b/read-repo-info-without-using-interpolation-bsc-11356.patch @@ -0,0 +1,29 @@ +From ce0fedf25dea7eb63ccff8f9b90a9a35571a5f9d Mon Sep 17 00:00:00 2001 +From: Mihai Dinca +Date: Thu, 7 Nov 2019 15:11:49 +0100 +Subject: [PATCH] Read repo info without using interpolation + (bsc#1135656) + +--- + salt/modules/zypperpkg.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 6adf5f9aa3..d8220a1fdd 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -1155,7 +1155,9 @@ def _get_repo_info(alias, repos_cfg=None, root=None): + Get one repo meta-data. + """ + try: +- meta = dict((repos_cfg or _get_configured_repos(root=root)).items(alias)) ++ meta = dict( ++ (repos_cfg or _get_configured_repos(root=root)).items(alias, raw=True) ++ ) + meta["alias"] = alias + for key, val in meta.items(): + if val in ["0", "1"]: +-- +2.39.2 + + diff --git a/restore-default-behaviour-of-pkg-list-return.patch b/restore-default-behaviour-of-pkg-list-return.patch new file mode 100644 index 0000000..6e8489b --- /dev/null +++ b/restore-default-behaviour-of-pkg-list-return.patch @@ -0,0 +1,137 @@ +From a1a8b5a886705e5f005cb7ab067e42095066ef80 Mon Sep 17 00:00:00 2001 +From: Jochen Breuer +Date: Fri, 30 Aug 2019 14:20:06 +0200 +Subject: [PATCH] Restore default behaviour of pkg list return + +The default behaviour for pkg list return was to not include patches, +even when installing patches. Only the packages where returned. There +is now parameter to also return patches if that is needed. + +Co-authored-by: Mihai Dinca +--- + salt/modules/zypperpkg.py | 34 +++++++++++++++++++++++++--------- + 1 file changed, 25 insertions(+), 9 deletions(-) + +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 9d16fcb0b1..6adf5f9aa3 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -1456,8 +1456,10 @@ def refresh_db(force=None, root=None): + return ret + + +-def _find_types(pkgs): ++def _detect_includes(pkgs, inclusion_detection): + """Form a package names list, find prefixes of packages types.""" ++ if not inclusion_detection: ++ return None + return sorted({pkg.split(":", 1)[0] for pkg in pkgs if len(pkg.split(":", 1)) == 2}) + + +@@ -1473,6 +1475,7 @@ def install( + ignore_repo_failure=False, + no_recommends=False, + root=None, ++ inclusion_detection=False, + **kwargs + ): + """ +@@ -1588,6 +1591,9 @@ def install( + + .. versionadded:: 2018.3.0 + ++ inclusion_detection: ++ Detect ``includes`` based on ``sources`` ++ By default packages are always included + + Returns a dict containing the new package names and versions:: + +@@ -1663,7 +1669,8 @@ def install( + + diff_attr = kwargs.get("diff_attr") + +- includes = _find_types(targets) ++ includes = _detect_includes(targets, inclusion_detection) ++ + old = ( + list_pkgs(attr=diff_attr, root=root, includes=includes) + if not downloadonly +@@ -1964,7 +1971,7 @@ def upgrade( + return ret + + +-def _uninstall(name=None, pkgs=None, root=None): ++def _uninstall(inclusion_detection, name=None, pkgs=None, root=None): + """ + Remove and purge do identical things but with different Zypper commands, + this function performs the common logic. +@@ -1974,7 +1981,7 @@ def _uninstall(name=None, pkgs=None, root=None): + except MinionError as exc: + raise CommandExecutionError(exc) + +- includes = _find_types(pkg_params.keys()) ++ includes = _detect_includes(pkg_params.keys(), inclusion_detection) + old = list_pkgs(root=root, includes=includes) + targets = [] + for target in pkg_params: +@@ -2037,7 +2044,7 @@ def normalize_name(name): + + + def remove( +- name=None, pkgs=None, root=None, **kwargs ++ name=None, pkgs=None, root=None, inclusion_detection=False, **kwargs + ): # pylint: disable=unused-argument + """ + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 +@@ -2069,8 +2076,11 @@ def remove( + root + Operate on a different root directory. + +- .. versionadded:: 0.16.0 ++ inclusion_detection: ++ Detect ``includes`` based on ``pkgs`` ++ By default packages are always included + ++ .. versionadded:: 0.16.0 + + Returns a dict containing the changes. + +@@ -2082,10 +2092,12 @@ def remove( + salt '*' pkg.remove ,, + salt '*' pkg.remove pkgs='["foo", "bar"]' + """ +- return _uninstall(name=name, pkgs=pkgs, root=root) ++ return _uninstall(inclusion_detection, name=name, pkgs=pkgs, root=root) + + +-def purge(name=None, pkgs=None, root=None, **kwargs): # pylint: disable=unused-argument ++def purge( ++ name=None, pkgs=None, root=None, inclusion_detection=False, **kwargs ++): # pylint: disable=unused-argument + """ + .. versionchanged:: 2015.8.12,2016.3.3,2016.11.0 + On minions running systemd>=205, `systemd-run(1)`_ is now used to +@@ -2117,6 +2129,10 @@ def purge(name=None, pkgs=None, root=None, **kwargs): # pylint: disable=unused- + root + Operate on a different root directory. + ++ inclusion_detection: ++ Detect ``includes`` based on ``pkgs`` ++ By default packages are always included ++ + .. versionadded:: 0.16.0 + + +@@ -2130,7 +2146,7 @@ def purge(name=None, pkgs=None, root=None, **kwargs): # pylint: disable=unused- + salt '*' pkg.purge ,, + salt '*' pkg.purge pkgs='["foo", "bar"]' + """ +- return _uninstall(name=name, pkgs=pkgs, root=root) ++ return _uninstall(inclusion_detection, name=name, pkgs=pkgs, root=root) + + + def list_holds(pattern=None, full=True, root=None, **kwargs): +-- +2.39.2 + + diff --git a/return-the-expected-powerpc-os-arch-bsc-1117995.patch b/return-the-expected-powerpc-os-arch-bsc-1117995.patch new file mode 100644 index 0000000..12b9220 --- /dev/null +++ b/return-the-expected-powerpc-os-arch-bsc-1117995.patch @@ -0,0 +1,31 @@ +From ceaf42a67d21cb6fa723339559c85be969e67308 Mon Sep 17 00:00:00 2001 +From: Mihai Dinca +Date: Thu, 13 Dec 2018 12:17:35 +0100 +Subject: [PATCH] Return the expected powerpc os arch (bsc#1117995) + +--- + salt/utils/pkg/rpm.py | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/salt/utils/pkg/rpm.py b/salt/utils/pkg/rpm.py +index f9975f8dff..147447ba75 100644 +--- a/salt/utils/pkg/rpm.py ++++ b/salt/utils/pkg/rpm.py +@@ -69,9 +69,10 @@ def get_osarch(): + stderr=subprocess.PIPE, + ).communicate()[0] + else: +- ret = "".join([x for x in platform.uname()[-2:] if x][-1:]) +- +- return salt.utils.stringutils.to_str(ret).strip() or "unknown" ++ ret = "".join(list(filter(None, platform.uname()[-2:]))[-1:]) ++ ret = salt.utils.stringutils.to_str(ret).strip() or "unknown" ++ ARCH_FIXES_MAPPING = {"powerpc64le": "ppc64le"} ++ return ARCH_FIXES_MAPPING.get(ret, ret) + + + def check_32(arch, osarch=None): +-- +2.39.2 + + diff --git a/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch b/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch new file mode 100644 index 0000000..1c3a1e4 --- /dev/null +++ b/revert-fixing-a-use-case-when-multiple-inotify-beaco.patch @@ -0,0 +1,216 @@ +From 76f2b98a3a9b9a49903a4d3b47dca0f2311bd7af Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 19:07:34 +0100 +Subject: [PATCH] Revert "Fixing a use case when multiple inotify beacons + are defined but when notifications are fired the configuration fron the first + beacon are used." Revert "Adding a util function to remove hidden (options + starting with underscore) from the beacon configuration. This is used when + the beacons loop through the configuration, eg. status beacon, and expect + certain options." + +This reverts commit 68a891ab2fe53ebf329b9c83b875f3575e87e266. +This reverts commit 66c58dedf8c364eaeb35c5adce8bcc8fe5c1219a. +--- + salt/beacons/__init__.py | 1 - + salt/beacons/diskusage.py | 3 --- + salt/beacons/inotify.py | 24 ++++++++-------------- + salt/beacons/napalm_beacon.py | 6 ++---- + salt/beacons/status.py | 4 ---- + tests/pytests/unit/beacons/test_inotify.py | 5 +---- + tests/pytests/unit/test_beacons.py | 17 --------------- + 7 files changed, 11 insertions(+), 49 deletions(-) + +diff --git a/salt/beacons/__init__.py b/salt/beacons/__init__.py +index b346c2a648..90918cba5b 100644 +--- a/salt/beacons/__init__.py ++++ b/salt/beacons/__init__.py +@@ -94,7 +94,6 @@ class Beacon: + log.error("Configuration for beacon must be a list.") + continue + +- b_config[mod].append({"_beacon_name": mod}) + fun_str = "{}.beacon".format(beacon_name) + if fun_str in self.beacons: + runonce = self._determine_beacon_config( +diff --git a/salt/beacons/diskusage.py b/salt/beacons/diskusage.py +index 5be33ff975..0b8d7c53e1 100644 +--- a/salt/beacons/diskusage.py ++++ b/salt/beacons/diskusage.py +@@ -8,7 +8,6 @@ Beacon to monitor disk usage. + import logging + import re + +-import salt.utils.beacons + import salt.utils.platform + + try: +@@ -83,8 +82,6 @@ def beacon(config): + it will override the previously defined threshold. + + """ +- whitelist = [] +- config = salt.utils.beacons.remove_hidden_options(config, whitelist) + parts = psutil.disk_partitions(all=True) + ret = [] + for mounts in config: +diff --git a/salt/beacons/inotify.py b/salt/beacons/inotify.py +index 283b84fdc7..0dc60662a6 100644 +--- a/salt/beacons/inotify.py ++++ b/salt/beacons/inotify.py +@@ -67,19 +67,17 @@ def _get_notifier(config): + """ + Check the context for the notifier and construct it if not present + """ +- beacon_name = config.get("_beacon_name", "inotify") +- notifier = "{}.notifier".format(beacon_name) +- if notifier not in __context__: ++ if "inotify.notifier" not in __context__: + __context__["inotify.queue"] = collections.deque() + wm = pyinotify.WatchManager() +- __context__[notifier] = pyinotify.Notifier(wm, _enqueue) ++ __context__["inotify.notifier"] = pyinotify.Notifier(wm, _enqueue) + if ( + "coalesce" in config + and isinstance(config["coalesce"], bool) + and config["coalesce"] + ): +- __context__[notifier].coalesce_events() +- return __context__[notifier] ++ __context__["inotify.notifier"].coalesce_events() ++ return __context__["inotify.notifier"] + + + def validate(config): +@@ -239,9 +237,6 @@ def beacon(config): + being at the Notifier level in pyinotify. + """ + +- whitelist = ["_beacon_name"] +- config = salt.utils.beacons.remove_hidden_options(config, whitelist) +- + config = salt.utils.beacons.list_to_dict(config) + + ret = [] +@@ -264,7 +259,7 @@ def beacon(config): + break + path = os.path.dirname(path) + +- excludes = config["files"].get(path, {}).get("exclude", "") ++ excludes = config["files"][path].get("exclude", "") + + if excludes and isinstance(excludes, list): + for exclude in excludes: +@@ -351,9 +346,6 @@ def beacon(config): + + + def close(config): +- config = salt.utils.beacons.list_to_dict(config) +- beacon_name = config.get("_beacon_name", "inotify") +- notifier = "{}.notifier".format(beacon_name) +- if notifier in __context__: +- __context__[notifier].stop() +- del __context__[notifier] ++ if "inotify.notifier" in __context__: ++ __context__["inotify.notifier"].stop() ++ del __context__["inotify.notifier"] +diff --git a/salt/beacons/napalm_beacon.py b/salt/beacons/napalm_beacon.py +index 122d56edb7..692fbe07aa 100644 +--- a/salt/beacons/napalm_beacon.py ++++ b/salt/beacons/napalm_beacon.py +@@ -168,9 +168,10 @@ with a NTP server at a stratum level greater than 5. + """ + + import logging ++ ++# Import Python std lib + import re + +-import salt.utils.beacons + import salt.utils.napalm + + log = logging.getLogger(__name__) +@@ -306,9 +307,6 @@ def beacon(config): + """ + Watch napalm function and fire events. + """ +- whitelist = [] +- config = salt.utils.beacons.remove_hidden_options(config, whitelist) +- + log.debug("Executing napalm beacon with config:") + log.debug(config) + ret = [] +diff --git a/salt/beacons/status.py b/salt/beacons/status.py +index aa5aa13b47..e2c3177ea8 100644 +--- a/salt/beacons/status.py ++++ b/salt/beacons/status.py +@@ -91,7 +91,6 @@ import datetime + import logging + + import salt.exceptions +-import salt.utils.beacons + import salt.utils.platform + + log = logging.getLogger(__name__) +@@ -119,9 +118,6 @@ def beacon(config): + log.debug(config) + ctime = datetime.datetime.utcnow().isoformat() + +- whitelist = [] +- config = salt.utils.beacons.remove_hidden_options(config, whitelist) +- + if not config: + config = [ + { +diff --git a/tests/pytests/unit/beacons/test_inotify.py b/tests/pytests/unit/beacons/test_inotify.py +index 30a9a91db4..678a528529 100644 +--- a/tests/pytests/unit/beacons/test_inotify.py ++++ b/tests/pytests/unit/beacons/test_inotify.py +@@ -263,7 +263,6 @@ def test_multi_files_exclude(tmp_path): + + + # Check __get_notifier and ensure that the right bits are in __context__ +-# including a beacon_name specific notifier is found. + def test__get_notifier(): + config = { + "files": { +@@ -293,10 +292,8 @@ def test__get_notifier(): + }, + }, + "coalesce": True, +- "beacon_module": "inotify", +- "_beacon_name": "httpd.inotify", + } + + ret = inotify._get_notifier(config) + assert "inotify.queue" in inotify.__context__ +- assert "httpd.inotify.notifier" in inotify.__context__ ++ assert "inotify.notifier" in inotify.__context__ +diff --git a/tests/pytests/unit/test_beacons.py b/tests/pytests/unit/test_beacons.py +index 217cd5c6a4..855e271d7d 100644 +--- a/tests/pytests/unit/test_beacons.py ++++ b/tests/pytests/unit/test_beacons.py +@@ -104,20 +104,3 @@ def test_beacon_module(minion_opts): + } + ] + assert ret == _expected +- +- # Ensure that "beacon_name" is available in the call to the beacon function +- name = "ps.beacon" +- mocked = {name: MagicMock(return_value=_expected)} +- mocked[name].__globals__ = {} +- calls = [ +- call( +- [ +- {"processes": {"apache2": "stopped"}}, +- {"beacon_module": "ps"}, +- {"_beacon_name": "watch_apache"}, +- ] +- ) +- ] +- with patch.object(beacon, "beacons", mocked) as patched: +- beacon.process(minion_opts["beacons"], minion_opts["grains"]) +- patched[name].assert_has_calls(calls) +-- +2.39.2 + + diff --git a/revert-usage-of-long-running-req-channel-bsc-1213960.patch b/revert-usage-of-long-running-req-channel-bsc-1213960.patch new file mode 100644 index 0000000..4a0eeb7 --- /dev/null +++ b/revert-usage-of-long-running-req-channel-bsc-1213960.patch @@ -0,0 +1,418 @@ +From 3cc2aee2290bd9a4fba9e0cebe3b65370aa76af6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Fri, 1 Sep 2023 08:22:44 +0100 +Subject: [PATCH] Revert usage of long running REQ channel (bsc#1213960, + bsc#1213630, bsc#1213257) + +* Revert usage of long running REQ channel (bsc#1213960, bsc#1213630, bsc#1213257) + +This reverts commits: + https://github.com/saltstack/salt/commit/a99ffb557b8a1142225d4925aba4a5e493923d2f + https://github.com/saltstack/salt/commit/80ae5188807550e7592fa12d8661ee83c4313ec8 + https://github.com/saltstack/salt/commit/3c7e1ec1f08abd7cd1ba78ad7880acb6ba6fdce7 + https://github.com/saltstack/salt/commit/171926cc57618b51bf3fdc042b62212e681180fc + +From this PR: https://github.com/saltstack/salt/pull/61468 + +See: https://github.com/saltstack/salt/issues/62959#issuecomment-1658335432 + +* Revert "Fix linter" + +This reverts commit d09d2d3f31e06c554b4ed869b7dc4f8b8bce5dc9. + +* Revert "add a regression test" + +This reverts commit b2c32be0a80c92585a9063409c42895357bb0dbe. + +* Fix failing tests after reverting commits +--- + doc/topics/development/architecture.rst | 8 +- + salt/channel/client.py | 9 +-- + salt/minion.py | 47 +++-------- + salt/transport/ipc.py | 5 +- + salt/utils/asynchronous.py | 2 +- + .../transport/server/test_req_channel.py | 16 ++-- + tests/pytests/unit/test_minion.py | 79 ++++--------------- + 7 files changed, 39 insertions(+), 127 deletions(-) + +diff --git a/doc/topics/development/architecture.rst b/doc/topics/development/architecture.rst +index 17400db001..1c717092f8 100644 +--- a/doc/topics/development/architecture.rst ++++ b/doc/topics/development/architecture.rst +@@ -220,15 +220,11 @@ the received message. + 4) The new minion thread is created. The _thread_return() function starts up + and actually calls out to the requested function contained in the job. + 5) The requested function runs and returns a result. [Still in thread.] +-6) The result of the function that's run is published on the minion's local event bus with event +-tag "__master_req_channel_payload" [Still in thread.] ++6) The result of the function that's run is encrypted and returned to the ++master's ReqServer (TCP 4506 on master). [Still in thread.] + 7) Thread exits. Because the main thread was only blocked for the time that it + took to initialize the worker thread, many other requests could have been + received and processed during this time. +-8) Minion event handler gets the event with tag "__master_req_channel_payload" +-and sends the payload to master's ReqServer (TCP 4506 on master), via the long-running async request channel +-that was opened when minion first started up. +- + + + A Note on ClearFuncs vs. AESFuncs +diff --git a/salt/channel/client.py b/salt/channel/client.py +index e5b073ccdb..76d7a8e5b9 100644 +--- a/salt/channel/client.py ++++ b/salt/channel/client.py +@@ -98,7 +98,6 @@ class AsyncReqChannel: + "_crypted_transfer", + "_uncrypted_transfer", + "send", +- "connect", + ] + close_methods = [ + "close", +@@ -128,7 +127,7 @@ class AsyncReqChannel: + else: + auth = None + +- transport = salt.transport.request_client(opts, io_loop=io_loop) ++ transport = salt.transport.request_client(opts, io_loop) + return cls(opts, transport, auth) + + def __init__(self, opts, transport, auth, **kwargs): +@@ -271,10 +270,6 @@ class AsyncReqChannel: + + raise salt.ext.tornado.gen.Return(ret) + +- @salt.ext.tornado.gen.coroutine +- def connect(self): +- yield self.transport.connect() +- + @salt.ext.tornado.gen.coroutine + def send(self, load, tries=3, timeout=60, raw=False): + """ +@@ -295,7 +290,7 @@ class AsyncReqChannel: + ret = yield self._crypted_transfer(load, timeout=timeout, raw=raw) + break + except Exception as exc: # pylint: disable=broad-except +- log.trace("Failed to send msg %r", exc) ++ log.error("Failed to send msg %r", exc) + if _try >= tries: + raise + else: +diff --git a/salt/minion.py b/salt/minion.py +index c3b65f16c3..9597d6e63a 100644 +--- a/salt/minion.py ++++ b/salt/minion.py +@@ -1361,30 +1361,11 @@ class Minion(MinionBase): + """ + Return a future which will complete when you are connected to a master + """ +- # Consider refactoring so that eval_master does not have a subtle side-effect on the contents of the opts array + master, self.pub_channel = yield self.eval_master( + self.opts, self.timeout, self.safe, failed + ) +- +- # a long-running req channel +- self.req_channel = salt.transport.client.AsyncReqChannel.factory( +- self.opts, io_loop=self.io_loop +- ) +- +- if hasattr( +- self.req_channel, "connect" +- ): # TODO: consider generalizing this for all channels +- log.debug("Connecting minion's long-running req channel") +- yield self.req_channel.connect() +- + yield self._post_master_init(master) + +- @salt.ext.tornado.gen.coroutine +- def handle_payload(self, payload, reply_func): +- self.payloads.append(payload) +- yield reply_func(payload) +- self.payload_ack.notify() +- + # TODO: better name... + @salt.ext.tornado.gen.coroutine + def _post_master_init(self, master): +@@ -1599,6 +1580,7 @@ class Minion(MinionBase): + return functions, returners, errors, executors + + def _send_req_sync(self, load, timeout): ++ + if self.opts["minion_sign_messages"]: + log.trace("Signing event to be published onto the bus.") + minion_privkey_path = os.path.join(self.opts["pki_dir"], "minion.pem") +@@ -1607,11 +1589,9 @@ class Minion(MinionBase): + ) + load["sig"] = sig + +- with salt.utils.event.get_event( +- "minion", opts=self.opts, listen=False +- ) as event: +- return event.fire_event( +- load, "__master_req_channel_payload", timeout=timeout ++ with salt.channel.client.ReqChannel.factory(self.opts) as channel: ++ return channel.send( ++ load, timeout=timeout, tries=self.opts["return_retry_tries"] + ) + + @salt.ext.tornado.gen.coroutine +@@ -1624,11 +1604,9 @@ class Minion(MinionBase): + ) + load["sig"] = sig + +- with salt.utils.event.get_event( +- "minion", opts=self.opts, listen=False +- ) as event: +- ret = yield event.fire_event_async( +- load, "__master_req_channel_payload", timeout=timeout ++ with salt.channel.client.AsyncReqChannel.factory(self.opts) as channel: ++ ret = yield channel.send( ++ load, timeout=timeout, tries=self.opts["return_retry_tries"] + ) + raise salt.ext.tornado.gen.Return(ret) + +@@ -2055,7 +2033,7 @@ class Minion(MinionBase): + minion_instance._return_pub(ret) + + # Add default returners from minion config +- # Should have been converted to comma-delimited string already ++ # Should have been coverted to comma-delimited string already + if isinstance(opts.get("return"), str): + if data["ret"]: + data["ret"] = ",".join((data["ret"], opts["return"])) +@@ -2662,7 +2640,6 @@ class Minion(MinionBase): + """ + Send mine data to the master + """ +- # Consider using a long-running req channel to send mine data + with salt.channel.client.ReqChannel.factory(self.opts) as channel: + data["tok"] = self.tok + try: +@@ -2699,12 +2676,6 @@ class Minion(MinionBase): + force_refresh=data.get("force_refresh", False), + notify=data.get("notify", False), + ) +- elif tag.startswith("__master_req_channel_payload"): +- yield _minion.req_channel.send( +- data, +- timeout=_minion._return_retry_timer(), +- tries=_minion.opts["return_retry_tries"], +- ) + elif tag.startswith("pillar_refresh"): + yield _minion.pillar_refresh( + force_refresh=data.get("force_refresh", False), +@@ -3175,7 +3146,7 @@ class Minion(MinionBase): + if self._target_load(payload["load"]): + self._handle_decoded_payload(payload["load"]) + elif self.opts["zmq_filtering"]: +- # In the filtering enabled case, we'd like to know when minion sees something it shouldn't ++ # In the filtering enabled case, we'd like to know when minion sees something it shouldnt + log.trace( + "Broadcast message received not for this minion, Load: %s", + payload["load"], +diff --git a/salt/transport/ipc.py b/salt/transport/ipc.py +index 3a3f0c7a5f..cee100b086 100644 +--- a/salt/transport/ipc.py ++++ b/salt/transport/ipc.py +@@ -208,10 +208,7 @@ class IPCServer: + log.error("Exception occurred while handling stream: %s", exc) + + def handle_connection(self, connection, address): +- log.trace( +- "IPCServer: Handling connection to address: %s", +- address if address else connection, +- ) ++ log.trace("IPCServer: Handling connection to address: %s", address) + try: + with salt.utils.asynchronous.current_ioloop(self.io_loop): + stream = IOStream( +diff --git a/salt/utils/asynchronous.py b/salt/utils/asynchronous.py +index 0c645bbc3b..88596a4a20 100644 +--- a/salt/utils/asynchronous.py ++++ b/salt/utils/asynchronous.py +@@ -34,7 +34,7 @@ class SyncWrapper: + This is uses as a simple wrapper, for example: + + asynchronous = AsyncClass() +- # this method would regularly return a future ++ # this method would reguarly return a future + future = asynchronous.async_method() + + sync = SyncWrapper(async_factory_method, (arg1, arg2), {'kwarg1': 'val'}) +diff --git a/tests/pytests/functional/transport/server/test_req_channel.py b/tests/pytests/functional/transport/server/test_req_channel.py +index 4a74802a0d..555c040c1c 100644 +--- a/tests/pytests/functional/transport/server/test_req_channel.py ++++ b/tests/pytests/functional/transport/server/test_req_channel.py +@@ -124,7 +124,7 @@ def req_channel_crypt(request): + + + @pytest.fixture +-def push_channel(req_server_channel, salt_minion, req_channel_crypt): ++def req_channel(req_server_channel, salt_minion, req_channel_crypt): + with salt.channel.client.ReqChannel.factory( + salt_minion.config, crypt=req_channel_crypt + ) as _req_channel: +@@ -135,7 +135,7 @@ def push_channel(req_server_channel, salt_minion, req_channel_crypt): + _req_channel.obj._refcount = 0 + + +-def test_basic(push_channel): ++def test_basic(req_channel): + """ + Test a variety of messages, make sure we get the expected responses + """ +@@ -145,11 +145,11 @@ def test_basic(push_channel): + {"baz": "qux", "list": [1, 2, 3]}, + ] + for msg in msgs: +- ret = push_channel.send(dict(msg), timeout=5, tries=1) ++ ret = req_channel.send(dict(msg), timeout=5, tries=1) + assert ret["load"] == msg + + +-def test_normalization(push_channel): ++def test_normalization(req_channel): + """ + Since we use msgpack, we need to test that list types are converted to lists + """ +@@ -160,21 +160,21 @@ def test_normalization(push_channel): + {"list": tuple([1, 2, 3])}, + ] + for msg in msgs: +- ret = push_channel.send(msg, timeout=5, tries=1) ++ ret = req_channel.send(msg, timeout=5, tries=1) + for key, value in ret["load"].items(): + assert types[key] == type(value) + + +-def test_badload(push_channel, req_channel_crypt): ++def test_badload(req_channel, req_channel_crypt): + """ + Test a variety of bad requests, make sure that we get some sort of error + """ + msgs = ["", [], tuple()] + if req_channel_crypt == "clear": + for msg in msgs: +- ret = push_channel.send(msg, timeout=5, tries=1) ++ ret = req_channel.send(msg, timeout=5, tries=1) + assert ret == "payload and load must be a dict" + else: + for msg in msgs: + with pytest.raises(salt.exceptions.AuthenticationError): +- push_channel.send(msg, timeout=5, tries=1) ++ req_channel.send(msg, timeout=5, tries=1) +diff --git a/tests/pytests/unit/test_minion.py b/tests/pytests/unit/test_minion.py +index 1cee025a48..4508eaee95 100644 +--- a/tests/pytests/unit/test_minion.py ++++ b/tests/pytests/unit/test_minion.py +@@ -55,27 +55,26 @@ def test_minion_load_grains_default(): + + + @pytest.mark.parametrize( +- "event", ++ "req_channel", + [ + ( +- "fire_event", +- lambda data, tag, cb=None, timeout=60: True, ++ "salt.channel.client.AsyncReqChannel.factory", ++ lambda load, timeout, tries: salt.ext.tornado.gen.maybe_future(tries), + ), + ( +- "fire_event_async", +- lambda data, tag, cb=None, timeout=60: salt.ext.tornado.gen.maybe_future( +- True +- ), ++ "salt.channel.client.ReqChannel.factory", ++ lambda load, timeout, tries: tries, + ), + ], + ) +-def test_send_req_fires_completion_event(event, minion_opts): +- event_enter = MagicMock() +- event_enter.send.side_effect = event[1] +- event = MagicMock() +- event.__enter__.return_value = event_enter ++def test_send_req_tries(req_channel, minion_opts): ++ channel_enter = MagicMock() ++ channel_enter.send.side_effect = req_channel[1] ++ channel = MagicMock() ++ channel.__enter__.return_value = channel_enter + +- with patch("salt.utils.event.get_event", return_value=event): ++ with patch(req_channel[0], return_value=channel): ++ minion_opts = salt.config.DEFAULT_MINION_OPTS.copy() + minion_opts["random_startup_delay"] = 0 + minion_opts["return_retry_tries"] = 30 + minion_opts["grains"] = {} +@@ -85,62 +84,16 @@ def test_send_req_fires_completion_event(event, minion_opts): + load = {"load": "value"} + timeout = 60 + +- # XXX This is buggy because "async" in event[0] will never evaluate +- # to True and if it *did* evaluate to true the test would fail +- # because you Mock isn't a co-routine. +- if "async" in event[0]: ++ if "Async" in req_channel[0]: + rtn = minion._send_req_async(load, timeout).result() + else: + rtn = minion._send_req_sync(load, timeout) + +- # get the +- for idx, call in enumerate(event.mock_calls, 1): +- if "fire_event" in call[0]: +- condition_event_tag = ( +- len(call.args) > 1 +- and call.args[1] == "__master_req_channel_payload" +- ) +- condition_event_tag_error = "{} != {}; Call(number={}): {}".format( +- idx, call, call.args[1], "__master_req_channel_payload" +- ) +- condition_timeout = ( +- len(call.kwargs) == 1 and call.kwargs["timeout"] == timeout +- ) +- condition_timeout_error = "{} != {}; Call(number={}): {}".format( +- idx, call, call.kwargs["timeout"], timeout +- ) +- +- fire_event_called = True +- assert condition_event_tag, condition_event_tag_error +- assert condition_timeout, condition_timeout_error +- +- assert fire_event_called +- assert rtn +- +- +-async def test_send_req_async_regression_62453(minion_opts): +- event_enter = MagicMock() +- event_enter.send.side_effect = ( +- lambda data, tag, cb=None, timeout=60: salt.ext.tornado.gen.maybe_future(True) +- ) +- event = MagicMock() +- event.__enter__.return_value = event_enter +- +- minion_opts["random_startup_delay"] = 0 +- minion_opts["return_retry_tries"] = 30 +- minion_opts["grains"] = {} +- with patch("salt.loader.grains"): +- minion = salt.minion.Minion(minion_opts) +- +- load = {"load": "value"} +- timeout = 60 +- +- # We are just validating no exception is raised +- rtn = await minion._send_req_async(load, timeout) +- assert rtn is False ++ assert rtn == 30 + + +-def test_mine_send_tries(): ++@patch("salt.channel.client.ReqChannel.factory") ++def test_mine_send_tries(req_channel_factory): + channel_enter = MagicMock() + channel_enter.send.side_effect = lambda load, timeout, tries: tries + channel = MagicMock() +-- +2.41.0 + diff --git a/run-salt-api-as-user-salt-bsc-1064520.patch b/run-salt-api-as-user-salt-bsc-1064520.patch new file mode 100644 index 0000000..00c92fd --- /dev/null +++ b/run-salt-api-as-user-salt-bsc-1064520.patch @@ -0,0 +1,25 @@ +From a94cfd5dea05c2c4a9d6b8b243048a2ceeb3f208 Mon Sep 17 00:00:00 2001 +From: Christian Lanig +Date: Mon, 27 Nov 2017 13:10:26 +0100 +Subject: [PATCH] Run salt-api as user salt (bsc#1064520) + +--- + pkg/common/salt-api.service | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/pkg/common/salt-api.service b/pkg/common/salt-api.service +index d0b6d74120..9cdc9c582b 100644 +--- a/pkg/common/salt-api.service ++++ b/pkg/common/salt-api.service +@@ -6,6 +6,7 @@ After=network.target + [Service] + Type=notify + NotifyAccess=all ++User=salt + LimitNOFILE=8192 + ExecStart=/usr/bin/salt-api + TimeoutStopSec=3 +-- +2.39.2 + + diff --git a/run-salt-master-as-dedicated-salt-user.patch b/run-salt-master-as-dedicated-salt-user.patch new file mode 100644 index 0000000..5d71b0f --- /dev/null +++ b/run-salt-master-as-dedicated-salt-user.patch @@ -0,0 +1,47 @@ +From 6ffbf7fcc178f32c670b177b25ed64658c59f1bf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Klaus=20K=C3=A4mpf?= +Date: Wed, 20 Jan 2016 11:01:06 +0100 +Subject: [PATCH] Run salt master as dedicated salt user + +* Minion runs always as a root +--- + conf/master | 3 ++- + pkg/common/salt-common.logrotate | 2 ++ + 2 files changed, 4 insertions(+), 1 deletion(-) + +diff --git a/conf/master b/conf/master +index f542051d76..acff94abec 100644 +--- a/conf/master ++++ b/conf/master +@@ -25,7 +25,8 @@ + # permissions to allow the specified user to run the master. The exception is + # the job cache, which must be deleted if this user is changed. If the + # modified files cause conflicts, set verify_env to False. +-#user: root ++user: salt ++syndic_user: salt + + # Tell the master to also use salt-ssh when running commands against minions. + #enable_ssh_minions: False +diff --git a/pkg/common/salt-common.logrotate b/pkg/common/salt-common.logrotate +index a0306ff370..97d158db18 100644 +--- a/pkg/common/salt-common.logrotate ++++ b/pkg/common/salt-common.logrotate +@@ -1,4 +1,5 @@ + /var/log/salt/master { ++ su salt salt + weekly + missingok + rotate 7 +@@ -15,6 +16,7 @@ + } + + /var/log/salt/key { ++ su salt salt + weekly + missingok + rotate 7 +-- +2.39.2 + + diff --git a/salt-tmpfiles.d b/salt-tmpfiles.d new file mode 100644 index 0000000..d285540 --- /dev/null +++ b/salt-tmpfiles.d @@ -0,0 +1,5 @@ +# Type Path Mode UID GID Age Argument +d /run/salt 0750 root salt +d /run/salt/master 0750 salt salt +d /run/salt/minion 0750 root root + diff --git a/salt.changes b/salt.changes new file mode 100644 index 0000000..3b34567 --- /dev/null +++ b/salt.changes @@ -0,0 +1,6579 @@ +------------------------------------------------------------------- +Wed Sep 20 15:04:34 UTC 2023 - Pablo Suárez Hernández + +- Fix inconsistency in reported version by egg-info metadata (bsc#1215489) +- Revert usage of long running REQ channel (bsc#1213960, bsc#1213630, bsc#1213257) +- Fix gitfs cachedir basename to avoid hash collisions (bsc#1193948, bsc#1214797, CVE-2023-20898) +- Make sure configured user is properly set by Salt (bsc#1210994) +- Do not fail on bad message pack message (bsc#1213441, CVE-2023-20897) +- Fix broken tests to make them running in the testsuite +- Prevent possible exceptions on salt.utils.user.get_group_dict (bsc#1212794) +- 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-the-regression-of-user.present-state-when-group-.patch + * fixed-gitfs-cachedir_basename-to-avoid-hash-collisio.patch + * write-salt-version-before-building-when-using-with-s.patch + * make-master_tops-compatible-with-salt-3000-and-older.patch + * avoid-conflicts-with-dependencies-versions-bsc-12116.patch + * zypper-pkgrepo-alreadyconfigured-585.patch + * revert-usage-of-long-running-req-channel-bsc-1213960.patch + * do-not-fail-on-bad-message-pack-message-bsc-1213441-.patch + * fix-utf8-handling-in-pass-renderer-and-make-it-more-.patch + * prevent-possible-exceptions-on-salt.utils.user.get_g.patch + * tornado-fix-an-open-redirect-in-staticfilehandler-cv.patch + * fix-some-issues-detected-in-salt-support-cli-module-.patch + * fix-tests-to-make-them-running-with-salt-testsuite.patch + * 3006.0-prevent-_pygit2.giterror-error-loading-known_.patch + * define-__virtualname__-for-transactional_update-modu.patch + * make-sure-configured-user-is-properly-set-by-salt-bs.patch + * mark-salt-3006-as-released-586.patch + * fix-regression-multiple-values-for-keyword-argument-.patch + +------------------------------------------------------------------- +Fri May 5 08:29:26 UTC 2023 - Alexander Graul + +- Update to Salt release version 3006.0 (jsc#PED-3139) + * See release notes: https://docs.saltproject.io/en/latest/topics/releases/3006.0.html + +- Add python3-looseversion as new dependency for salt +- Add python3-packaging as new dependency for salt +- Drop conflictive patch dicarded from upstream +- Fix SLS rendering error when Jinja macros are used +- Fix version detection and avoid building and testing failures + +- Added: + * fix-version-detection-and-avoid-building-and-testing.patch + * make-sure-the-file-client-is-destroyed-upon-used.patch + +- Modified: + * 3005.1-implement-zypper-removeptf-573.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * add-custom-suse-capabilities-as-grains.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * add-migrated-state-and-gpg-key-management-functions-.patch + * add-publish_batch-to-clearfuncs-exposed-methods.patch + * add-salt-ssh-support-with-venv-salt-minion-3004-493.patch + * add-sleep-on-exception-handling-on-minion-connection.patch + * add-standalone-configuration-file-for-enabling-packa.patch + * add-support-for-gpgautoimport-539.patch + * allow-vendor-change-option-with-zypper.patch + * async-batch-implementation.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * bsc-1176024-fix-file-directory-user-and-group-owners.patch + * change-the-delimeters-to-prevent-possible-tracebacks.patch + * control-the-collection-of-lvm-grains-via-config.patch + * debian-info_installed-compatibility-50453.patch + * dnfnotify-pkgset-plugin-implementation-3002.2-450.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * don-t-use-shell-sbin-nologin-in-requisites.patch + * drop-serial-from-event.unpack-in-cli.batch_async.patch + * early-feature-support-config.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * enhance-openscap-module-add-xccdf_eval-call-386.patch + * fix-bsc-1065792.patch + * fix-for-suse-expanded-support-detection.patch + * fix-issue-2068-test.patch + * fix-missing-minion-returns-in-batch-mode-360.patch + * fix-ownership-of-salt-thin-directory-when-using-the-.patch + * fix-regression-with-depending-client.ssh-on-psutil-b.patch + * fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch + * fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch + * fix-the-regression-for-yumnotify-plugin-456.patch + * fix-traceback.print_exc-calls-for-test_pip_state-432.patch + * fixes-for-python-3.10-502.patch + * include-aliases-in-the-fqdns-grains.patch + * info_installed-works-without-status-attr-now.patch + * let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch + * make-aptpkg.list_repos-compatible-on-enabled-disable.patch + * make-setup.py-script-to-not-require-setuptools-9.1.patch + * pass-the-context-to-pillar-ext-modules.patch + * prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch + * prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch + * prevent-shell-injection-via-pre_flight_script_args-4.patch + * read-repo-info-without-using-interpolation-bsc-11356.patch + * restore-default-behaviour-of-pkg-list-return.patch + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + * revert-fixing-a-use-case-when-multiple-inotify-beaco.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * run-salt-master-as-dedicated-salt-user.patch + * save-log-to-logfile-with-docker.build.patch + * skip-package-names-without-colon-bsc-1208691-578.patch + * switch-firewalld-state-to-use-change_interface.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + * update-target-fix-for-salt-ssh-to-process-targets-li.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * use-rlock-to-avoid-deadlocks-in-salt-ssh.patch + * use-salt-bundle-in-dockermod.patch + * x509-fixes-111.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + +- Removed: + * add-amazon-ec2-detection-for-virtual-grains-bsc-1195.patch + * add-support-for-name-pkgs-and-diff_attr-parameters-t.patch + * align-amazon-ec2-nitro-grains-with-upstream-pr-bsc-1.patch + * allow-entrypoint-compatibility-for-importlib-metadat.patch + * clarify-pkg.installed-pkg_verify-documentation.patch + * detect-module.run-syntax.patch + * fix-salt.states.file.managed-for-follow_symlinks-tru.patch + * fix-state.apply-in-test-mode-with-file-state-module-.patch + * fix-test_ipc-unit-tests.patch + * fixes-pkg.version_cmp-on-openeuler-systems-and-a-few.patch + * fopen-workaround-bad-buffering-for-binary-mode-563.patch + * ignore-erros-on-reading-license-files-with-dpkg_lowp.patch + * ignore-extend-declarations-from-excluded-sls-files.patch + * ignore-non-utf8-characters-while-reading-files-with-.patch + * include-stdout-in-error-message-for-zypperpkg-559.patch + * make-pass-renderer-configurable-other-fixes-532.patch + * make-sure-saltcacheloader-use-correct-fileclient-519.patch + * normalize-package-names-once-with-pkg.installed-remo.patch + * retry-if-rpm-lock-is-temporarily-unavailable-547.patch + * set-default-target-for-pip-from-venv_pip_target-envi.patch + * state.apply-don-t-check-for-cached-pillar-errors.patch + * state.orchestrate_single-does-not-pass-pillar-none-4.patch + +------------------------------------------------------------------- +Fri Mar 17 12:05:55 UTC 2023 - Alexander Graul + +- Require python3-jmespath runtime dependency (bsc#1209233) +- Fix problem with detecting PTF packages (bsc#1208691) + +- Added: + * skip-package-names-without-colon-bsc-1208691-578.patch + +------------------------------------------------------------------- +Tue Jan 31 12:17:25 UTC 2023 - Pablo Suárez Hernández + +- Fixes pkg.version_cmp on openEuler systems and a few other OS flavors + +- Added: + * fixes-pkg.version_cmp-on-openeuler-systems-and-a-few.patch + +------------------------------------------------------------------- +Mon Jan 23 13:44:58 UTC 2023 - Pablo Suárez Hernández + +- Make pkg.remove function from zypperpkg module to handle also PTF packages + +- Added: + * 3005.1-implement-zypper-removeptf-573.patch + +------------------------------------------------------------------- +Tue Jan 17 16:32:12 UTC 2023 - Pablo Suárez Hernández + +- Control the collection of lvm grains via config (bsc#1204939) + +- Added: + * control-the-collection-of-lvm-grains-via-config.patch + +------------------------------------------------------------------- +Thu Jan 12 15:49:38 UTC 2023 - Pablo Suárez Hernández + +- Allow entrypoint compatibility for "importlib-metadata>=5.0.0" (bsc#1207071) + +- Added: + * allow-entrypoint-compatibility-for-importlib-metadat.patch + +------------------------------------------------------------------- +Mon Jan 9 12:44:28 UTC 2023 - Pablo Suárez Hernández + +- Add missing patch after rebase to fix collections Mapping issues + +- Added: + * fixes-for-python-3.10-502.patch + +------------------------------------------------------------------- +Wed Jan 4 13:29:57 UTC 2023 - Pablo Suárez Hernández + +- Prevent deadlocks in salt-ssh executions + +- Added: + * use-rlock-to-avoid-deadlocks-in-salt-ssh.patch + +------------------------------------------------------------------- +Mon Jan 2 15:51:45 UTC 2023 - Pablo Suárez Hernández + +- Create new salt-tests subpackage containing Salt tests + +------------------------------------------------------------------- +Thu Dec 29 13:35:08 UTC 2022 - Pablo Suárez Hernández + +- Update to Salt release version 3005.1 + * See release notes: https://docs.saltstack.com/en/latest/topics/releases/3005.1.html + +- Modified: + * activate-all-beacons-sources-config-pillar-grains.patch + * add-amazon-ec2-detection-for-virtual-grains-bsc-1195.patch + * add-custom-suse-capabilities-as-grains.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * add-migrated-state-and-gpg-key-management-functions-.patch + * add-publish_batch-to-clearfuncs-exposed-methods.patch + * add-salt-ssh-support-with-venv-salt-minion-3004-493.patch + * add-sleep-on-exception-handling-on-minion-connection.patch + * add-standalone-configuration-file-for-enabling-packa.patch + * add-support-for-gpgautoimport-539.patch + * add-support-for-name-pkgs-and-diff_attr-parameters-t.patch + * align-amazon-ec2-nitro-grains-with-upstream-pr-bsc-1.patch + * allow-vendor-change-option-with-zypper.patch + * async-batch-implementation.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * bsc-1176024-fix-file-directory-user-and-group-owners.patch + * change-the-delimeters-to-prevent-possible-tracebacks.patch + * clarify-pkg.installed-pkg_verify-documentation.patch + * debian-info_installed-compatibility-50453.patch + * detect-module.run-syntax.patch + * dnfnotify-pkgset-plugin-implementation-3002.2-450.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * don-t-use-shell-sbin-nologin-in-requisites.patch + * drop-serial-from-event.unpack-in-cli.batch_async.patch + * early-feature-support-config.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * enhance-openscap-module-add-xccdf_eval-call-386.patch + * fix-bsc-1065792.patch + * fix-for-suse-expanded-support-detection.patch + * fix-issue-2068-test.patch + * fix-missing-minion-returns-in-batch-mode-360.patch + * fix-ownership-of-salt-thin-directory-when-using-the-.patch + * fix-regression-with-depending-client.ssh-on-psutil-b.patch + * fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch + * fix-salt.states.file.managed-for-follow_symlinks-tru.patch + * fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch + * fix-state.apply-in-test-mode-with-file-state-module-.patch + * fix-test_ipc-unit-tests.patch + * fix-the-regression-for-yumnotify-plugin-456.patch + * fix-traceback.print_exc-calls-for-test_pip_state-432.patch + * fopen-workaround-bad-buffering-for-binary-mode-563.patch + * ignore-erros-on-reading-license-files-with-dpkg_lowp.patch + * ignore-extend-declarations-from-excluded-sls-files.patch + * ignore-non-utf8-characters-while-reading-files-with-.patch + * include-aliases-in-the-fqdns-grains.patch + * include-stdout-in-error-message-for-zypperpkg-559.patch + * info_installed-works-without-status-attr-now.patch + * let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch + * make-aptpkg.list_repos-compatible-on-enabled-disable.patch + * make-pass-renderer-configurable-other-fixes-532.patch + * make-setup.py-script-to-not-require-setuptools-9.1.patch + * make-sure-saltcacheloader-use-correct-fileclient-519.patch + * normalize-package-names-once-with-pkg.installed-remo.patch + * pass-the-context-to-pillar-ext-modules.patch + * prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch + * prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch + * prevent-shell-injection-via-pre_flight_script_args-4.patch + * read-repo-info-without-using-interpolation-bsc-11356.patch + * restore-default-behaviour-of-pkg-list-return.patch + * retry-if-rpm-lock-is-temporarily-unavailable-547.patch + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + * revert-fixing-a-use-case-when-multiple-inotify-beaco.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * run-salt-master-as-dedicated-salt-user.patch + * save-log-to-logfile-with-docker.build.patch + * set-default-target-for-pip-from-venv_pip_target-envi.patch + * state.apply-don-t-check-for-cached-pillar-errors.patch + * state.orchestrate_single-does-not-pass-pillar-none-4.patch + * switch-firewalld-state-to-use-change_interface.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + * update-target-fix-for-salt-ssh-to-process-targets-li.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * use-salt-bundle-in-dockermod.patch + * x509-fixes-111.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + +- Removed: + * 3003.3-do-not-consider-skipped-targets-as-failed-for.patch + * 3003.3-postgresql-json-support-in-pillar-423.patch + * add-missing-ansible-module-functions-to-whitelist-in.patch + * add-rpm_vercmp-python-library-for-version-comparison.patch + * adds-explicit-type-cast-for-port.patch + * backport-syndic-auth-fixes.patch + * batch.py-avoid-exception-when-minion-does-not-respon.patch + * check-if-dpkgnotify-is-executable-bsc-1186674-376.patch + * do-not-crash-when-unexpected-cmd-output-at-listing-p.patch + * enhance-logging-when-inotify-beacon-is-missing-pyino.patch + * fix-62092-catch-zmq.error.zmqerror-to-set-hwm-for-zm.patch + * fix-crash-when-calling-manage.not_alive-runners.patch + * fixes-56144-to-enable-hotadd-profile-support.patch + * fixes-for-python-3.10-502.patch + * fix-exception-in-yumpkg.remove-for-not-installed-pac.patch + * fix-for-cve-2022-22967-bsc-1200566.patch + * fix-inspector-module-export-function-bsc-1097531-481.patch + * fix-ip6_interface-grain-to-not-leak-secondary-ipv4-a.patch + * fix-issues-with-salt-ssh-s-extra-filerefs.patch + * fix-jinja2-contextfuntion-base-on-version-bsc-119874.patch + * fix-multiple-security-issues-bsc-1197417.patch + * fix-salt-call-event.send-call-with-grains-and-pillar.patch + * fix-the-regression-in-schedule-module-releasded-in-3.patch + * fix-wrong-test_mod_del_repo_multiline_values-test-af.patch + * force-zyppnotify-to-prefer-packages.db-than-packages.patch + * implementation-of-held-unheld-functions-for-state-pk.patch + * implementation-of-suse_ip-execution-module-bsc-10999.patch + * improvements-on-ansiblegate-module-354.patch + * mock-ip_addrs-in-utils-minions.py-unit-test-443.patch + * notify-beacon-for-debian-ubuntu-systems-347.patch + * refactor-and-improvements-for-transactional-updates-.patch + * support-transactional-systems-microos.patch + * wipe-notify_socket-from-env-in-cmdmod-bsc-1193357-30.patch + +------------------------------------------------------------------- +Fri Oct 28 14:43:03 UTC 2022 - Victor Zhestkov + +- Pass the context to pillar ext modules +- Align Amazon EC2 (Nitro) grains with upstream (bsc#1203685) +- Detect module run syntax version +- Implement automated patches alignment for the Salt Bundle + +- Added: + * detect-module.run-syntax.patch + * pass-the-context-to-pillar-ext-modules.patch + * align-amazon-ec2-nitro-grains-with-upstream-pr-bsc-1.patch + +------------------------------------------------------------------- +Fri Oct 21 13:30:08 UTC 2022 - Alexander Graul + +- Ignore extend declarations from excluded SLS files (bsc#1203886) +- Clarify pkg.installed pkg_verify documentation +- Enhance capture of error messages for Zypper calls in zypperpkg module + +- Added: + * ignore-extend-declarations-from-excluded-sls-files.patch + * include-stdout-in-error-message-for-zypperpkg-559.patch + * clarify-pkg.installed-pkg_verify-documentation.patch + +------------------------------------------------------------------- +Thu Oct 6 10:10:16 UTC 2022 - Pablo Suárez Hernández + +- Make pass renderer configurable and fix detected issues +- Workaround fopen line buffering for binary mode (bsc#1203834) +- Handle non-UTF-8 bytes in core grains generation (bsc#1202165) +- Fix Syndic authentication errors (bsc#1199562) + +- Added: + * make-pass-renderer-configurable-other-fixes-532.patch + * ignore-non-utf8-characters-while-reading-files-with-.patch + * fopen-workaround-bad-buffering-for-binary-mode-563.patch + * backport-syndic-auth-fixes.patch + +------------------------------------------------------------------- +Thu Sep 1 12:43:39 UTC 2022 - Victor Zhestkov + +- Add Amazon EC2 detection for virtual grains (bsc#1195624) +- Fix the regression in schedule module releasded in 3004 (bsc#1202631) +- Fix state.apply in test mode with file state module + on user/group checking (bsc#1202167) +- Change the delimeters to prevent possible tracebacks + on some packages with dpkg_lowpkg +- Make zypperpkg to retry if RPM lock is temporarily unavailable (bsc#1200596) + +- Added: + * fix-the-regression-in-schedule-module-releasded-in-3.patch + * retry-if-rpm-lock-is-temporarily-unavailable-547.patch + * change-the-delimeters-to-prevent-possible-tracebacks.patch + * add-amazon-ec2-detection-for-virtual-grains-bsc-1195.patch + * fix-state.apply-in-test-mode-with-file-state-module-.patch + +------------------------------------------------------------------- +Tue Jul 12 12:37:51 UTC 2022 - Alexander Graul + +- Fix test_ipc unit test + +- Added: + * fix-test_ipc-unit-tests.patch + +------------------------------------------------------------------- +Fri Jul 8 09:45:54 UTC 2022 - Pablo Suárez Hernández + +- Add support for gpgautoimport in zypperpkg module +- Update Salt to work with Jinja >= and <= 3.1.0 (bsc#1198744) +- Fix salt.states.file.managed() for follow_symlinks=True and test=True (bsc#1199372) +- Make Salt 3004 compatible with pyzmq >= 23.0.0 (bsc#1201082) + +- Added: + * fix-jinja2-contextfuntion-base-on-version-bsc-119874.patch + * add-support-for-gpgautoimport-539.patch + * fix-62092-catch-zmq.error.zmqerror-to-set-hwm-for-zm.patch + * fix-salt.states.file.managed-for-follow_symlinks-tru.patch + +------------------------------------------------------------------- +Thu Jul 7 14:58:25 UTC 2022 - Pablo Suárez Hernández + +- Add support for name, pkgs and diff_attr parameters to upgrade + function for zypper and yum (bsc#1198489) + +- Added: + * add-support-for-name-pkgs-and-diff_attr-parameters-t.patch + +------------------------------------------------------------------- +Tue Jun 28 07:40:48 UTC 2022 - Victor Zhestkov + +- Fix ownership of salt thin directory when using the Salt Bundle +- Set default target for pip from VENV_PIP_TARGET environment variable +- Normalize package names once with pkg.installed/removed using yum (bsc#1195895) +- Save log to logfile with docker.build +- Use Salt Bundle in dockermod +- Ignore erros on reading license files with dpkg_lowpkg (bsc#1197288) + +- Added: + * normalize-package-names-once-with-pkg.installed-remo.patch + * use-salt-bundle-in-dockermod.patch + * fix-ownership-of-salt-thin-directory-when-using-the-.patch + * ignore-erros-on-reading-license-files-with-dpkg_lowp.patch + * set-default-target-for-pip-from-venv_pip_target-envi.patch + * save-log-to-logfile-with-docker.build.patch + +------------------------------------------------------------------- +Thu Jun 16 09:52:06 UTC 2022 - Pablo Suárez Hernández + +- Fix PAM auth issue due missing check for PAM_ACCT_MGM return value (CVE-2022-22967) (bsc#1200566) + +- Added: + * fix-for-cve-2022-22967-bsc-1200566.patch + +------------------------------------------------------------------- +Thu May 19 11:00:15 UTC 2022 - Pablo Suárez Hernández + +- Make sure SaltCacheLoader use correct fileclient (bsc#1199149) + +- Added: + * make-sure-saltcacheloader-use-correct-fileclient-519.patch + +------------------------------------------------------------------- +Tue Apr 12 09:21:38 UTC 2022 - Victor Zhestkov + +- Prevent data pollution between actions proceesed + at the same time (bsc#1197637) +- Fix regression preventing bootstrapping new clients + caused by redundant dependency on psutil (bsc#1197533) + +- Added: + * fix-regression-with-depending-client.ssh-on-psutil-b.patch + * prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch + +------------------------------------------------------------------- +Tue Apr 5 09:29:14 UTC 2022 - Victor Zhestkov + +- Fixes for Python 3.10 + +- Added: + * fixes-for-python-3.10-502.patch + +------------------------------------------------------------------- +Thu Mar 31 11:16:01 UTC 2022 - Victor Zhestkov + +- Fix salt-ssh opts poisoning (bsc#1197637) + +- Added: + * fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch + +------------------------------------------------------------------- +Thu Mar 31 08:34:58 UTC 2022 - Pablo Suárez Hernández + +- Fix multiple security issues (bsc#1197417) + * Sign authentication replies to prevent MiTM (CVE-2022-22935) + * Sign pillar data to prevent MiTM attacks. (CVE-2022-22934) + * Prevent job and fileserver replays (CVE-2022-22936) + * Fixed targeting bug, especially visible when using syndic and user auth. (CVE-2022-22941) + +- Added: + * fix-multiple-security-issues-bsc-1197417.patch + +------------------------------------------------------------------- +Mon Feb 28 15:05:32 UTC 2022 - Pablo Suárez Hernández + +- Fix issues found around pre_flight_script_args + +- Added: + * prevent-shell-injection-via-pre_flight_script_args-4.patch + +------------------------------------------------------------------- +Thu Feb 24 14:06:44 UTC 2022 - Victor Zhestkov + +- Add salt-ssh with Salt Bundle support (venv-salt-minion) + (bsc#1182851, bsc#1196432) + +- Added: + * add-salt-ssh-support-with-venv-salt-minion-3004-493.patch + +------------------------------------------------------------------- +Thu Feb 17 15:27:00 UTC 2022 - Pablo Suárez Hernández + +- Restrict "state.orchestrate_single" to pass a pillar value if it exists (bsc#1194632) + +- Added: + * state.orchestrate_single-does-not-pass-pillar-none-4.patch + +------------------------------------------------------------------- +Tue Feb 8 13:53:36 UTC 2022 - Pablo Suárez Hernández + +- Update generated documentation to 3004 + +------------------------------------------------------------------- +Tue Feb 8 12:02:04 UTC 2022 - Pablo Suárez Hernández + +- Expose missing "ansible" module functions in Salt 3004 (bsc#1195625) + +- Added: + * add-missing-ansible-module-functions-to-whitelist-in.patch + +------------------------------------------------------------------- +Mon Feb 7 10:33:28 UTC 2022 - Alexander Graul + +- Fix salt-call event.send with pillar or grains + +- Added: + * fix-salt-call-event.send-call-with-grains-and-pillar.patch + +------------------------------------------------------------------- +Mon Jan 31 10:28:10 UTC 2022 - Alexander Graul + +- Fix exception in batch_async caused by a bad function call + +- Added: + * drop-serial-from-event.unpack-in-cli.batch_async.patch + +------------------------------------------------------------------- +Fri Jan 28 16:17:16 UTC 2022 - Victor Zhestkov + +- Fix inspector module export function (bsc#1097531) +- Wipe NOTIFY_SOCKET from env in cmdmod (bsc#1193357) + +- Added: + * fix-inspector-module-export-function-bsc-1097531-481.patch + * wipe-notify_socket-from-env-in-cmdmod-bsc-1193357-30.patch + +------------------------------------------------------------------- +Wed Jan 26 17:04:36 UTC 2022 - Alexander Graul + +- Update to version 3004, see release notes: https://docs.saltproject.io/en/master/topics/releases/3004.html +- Don't check for cached pillar errors on state.apply (bsc#1190781) + +- Added: + * state.apply-don-t-check-for-cached-pillar-errors.patch + +- Modified: + * add-migrated-state-and-gpg-key-management-functions-.patch + * switch-firewalld-state-to-use-change_interface.patch + * include-aliases-in-the-fqdns-grains.patch + * debian-info_installed-compatibility-50453.patch + * info_installed-works-without-status-attr-now.patch + * fix-traceback.print_exc-calls-for-test_pip_state-432.patch + * add-custom-suse-capabilities-as-grains.patch + * add-rpm_vercmp-python-library-for-version-comparison.patch + * 3003.3-do-not-consider-skipped-targets-as-failed-for.patch + * support-transactional-systems-microos.patch + * do-not-crash-when-unexpected-cmd-output-at-listing-p.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * update-target-fix-for-salt-ssh-to-process-targets-li.patch + * fix-exception-in-yumpkg.remove-for-not-installed-pac.patch + * enhance-openscap-module-add-xccdf_eval-call-386.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + * run-salt-master-as-dedicated-salt-user.patch + * 3003.3-postgresql-json-support-in-pillar-423.patch + * prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch + * early-feature-support-config.patch + * implementation-of-held-unheld-functions-for-state-pk.patch + * x509-fixes-111.patch + * fix-issues-with-salt-ssh-s-extra-filerefs.patch + * mock-ip_addrs-in-utils-minions.py-unit-test-443.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * refactor-and-improvements-for-transactional-updates-.patch + * improvements-on-ansiblegate-module-354.patch + * revert-fixing-a-use-case-when-multiple-inotify-beaco.patch + +- Removed: + * add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch + * prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch + * do-not-break-master_tops-for-minion-with-version-low.patch + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * do-not-monkey-patch-yaml-bsc-1177474.patch + * add-missing-aarch64-to-rpm-package-architectures-405.patch + * figure-out-python-interpreter-to-use-inside-containe.patch + * parsing-epoch-out-of-version-provided-during-pkg-rem.patch + * fix-a-test-and-some-variable-names-229.patch + * add-astra-linux-common-edition-to-the-os-family-list.patch + * better-handling-of-bad-public-keys-from-minions-bsc-.patch + * templates-move-the-globals-up-to-the-environment-jin.patch + * virt-enhancements.patch + * fix-aptpkg.normalize_name-when-package-arch-is-all.patch + * adding-preliminary-support-for-rocky.-59682-391.patch + * fix-save-for-iptables-state-module-bsc-1185131-372.patch + +------------------------------------------------------------------- +Mon Nov 15 15:14:54 UTC 2021 - Pablo Suárez Hernández + +- Simplify "transactional_update" module to not use SSH wrapper and allow more flexible execution +- Add "--no-return-event" option to salt-call to prevent sending return event back to master. +- Make "state.highstate" to acts on concurrent flag. +- Fix print regression for yumnotify plugin + +- Added: + * refactor-and-improvements-for-transactional-updates-.patch + * fix-the-regression-for-yumnotify-plugin-456.patch + +------------------------------------------------------------------- +Tue Nov 9 08:07:14 UTC 2021 - Victor Zhestkov + +- Use dnfnotify instead yumnotify for relevant distros +- dnfnotify pkgset plugin implementation +- Add rpm_vercmp python library support for version comparison +- Prevent pkg plugins errors on missing cookie path (bsc#1186738) + +- Added: + * add-rpm_vercmp-python-library-for-version-comparison.patch + * mock-ip_addrs-in-utils-minions.py-unit-test-443.patch + * dnfnotify-pkgset-plugin-implementation-3002.2-450.patch + * fix-traceback.print_exc-calls-for-test_pip_state-432.patch + * prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch + +------------------------------------------------------------------- +Fri Oct 15 15:06:25 UTC 2021 - Pablo Suárez Hernández + +- Fix ip6_interface grain to not leak secondary IPv4 aliases (bsc#1191412) +- Make "salt-api" package to require python3-cherrypy on RHEL systems +- Make "tar" as required for "salt-transactional-update" package + +- Added: + * fix-ip6_interface-grain-to-not-leak-secondary-ipv4-a.patch + +------------------------------------------------------------------- +Fri Oct 8 15:48:09 UTC 2021 - Pablo Suárez Hernández + +- Fix issues with salt-ssh's extra-filerefs + +- Added: + * fix-issues-with-salt-ssh-s-extra-filerefs.patch + +------------------------------------------------------------------- +Fri Oct 8 15:26:04 UTC 2021 - Pablo Suárez Hernández + +- Fix crash when calling manage.not_alive runners + +- Added: + * fix-crash-when-calling-manage.not_alive-runners.patch + +------------------------------------------------------------------- +Wed Oct 6 08:32:54 UTC 2021 - Pablo Suárez Hernández + +- Do not consider skipped targets as failed for ansible.playbooks state (bsc#1190446) + +- Added: + * 3003.3-do-not-consider-skipped-targets-as-failed-for.patch + +------------------------------------------------------------------- +Thu Sep 30 10:49:56 UTC 2021 - Pablo Suárez Hernández + +- Do not break master_tops for minion with version lower to 3003 + +- Added: + * do-not-break-master_tops-for-minion-with-version-low.patch + +------------------------------------------------------------------- +Fri Sep 24 15:07:50 UTC 2021 - Pablo Suárez Hernández + +- Support querying for JSON data in external sql pillar + +- Added: + * 3003.3-postgresql-json-support-in-pillar-423.patch + +------------------------------------------------------------------- +Mon Sep 20 13:03:39 UTC 2021 - Pablo Suárez Hernández + +- Update to Salt release version 3003.3 +- See release notes: https://docs.saltstack.com/en/latest/topics/releases/3003.3.html + +- Added: + * allow-vendor-change-option-with-zypper.patch + * support-transactional-systems-microos.patch + * virt-enhancements.patch + +- Modified: + * adds-explicit-type-cast-for-port.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * fixes-56144-to-enable-hotadd-profile-support.patch + * include-aliases-in-the-fqdns-grains.patch + * implementation-of-held-unheld-functions-for-state-pk.patch + * add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch + * debian-info_installed-compatibility-50453.patch + * fix-wrong-test_mod_del_repo_multiline_values-test-af.patch + * update-target-fix-for-salt-ssh-to-process-targets-li.patch + * x509-fixes-111.patch + * prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch + * restore-default-behaviour-of-pkg-list-return.patch + * adding-preliminary-support-for-rocky.-59682-391.patch + * add-astra-linux-common-edition-to-the-os-family-list.patch + * templates-move-the-globals-up-to-the-environment-jin.patch + * fix-bsc-1065792.patch + * add-migrated-state-and-gpg-key-management-functions-.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + * improvements-on-ansiblegate-module-354.patch + * add-custom-suse-capabilities-as-grains.patch + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + * revert-fixing-a-use-case-when-multiple-inotify-beaco.patch + * enhance-openscap-module-add-xccdf_eval-call-386.patch + * implementation-of-suse_ip-execution-module-bsc-10999.patch + * add-missing-aarch64-to-rpm-package-architectures-405.patch + * async-batch-implementation.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + * do-not-crash-when-unexpected-cmd-output-at-listing-p.patch + * figure-out-python-interpreter-to-use-inside-containe.patch + * better-handling-of-bad-public-keys-from-minions-bsc-.patch + * early-feature-support-config.patch + * do-not-monkey-patch-yaml-bsc-1177474.patch + +- Removed: + * fix-memory-leak-produced-by-batch-async-find_jobs-me.patch + * fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch + * fix-for-log-checking-in-x509-test.patch + * do-not-make-ansiblegate-to-crash-on-python3-minions.patch + * prevent-race-condition-on-sigterm-for-the-minion-bsc.patch + * remove-msgpack-1.0.0-requirement-in-the-installed-me.patch + * move-server_id-deprecation-warning-to-reduce-log-spa.patch + * re-adding-function-to-test-for-root.patch + * make-profiles-a-package.patch + * handle-master-tops-data-when-states-are-applied-by-t.patch + * fix-unit-tests-for-batch-async-after-refactor.patch + * prevent-test_mod_del_repo_multiline_values-to-fail.patch + * prevent-import-errors-when-running-test_btrfs-unit-t.patch + * fix-failing-unit-tests-for-batch-async.patch + * remove-unnecessary-yield-causing-badyielderror-bsc-1.patch + * virt-use-dev-kvm-to-detect-kvm-383.patch + * 3002.2-xen-spicevmc-dns-srv-records-backports-314.patch + * add-docker-logout-237.patch + * drop-wrong-mock-from-chroot-unit-test.patch + * fix-async-batch-multiple-done-events.patch + * fix-unit-test-for-grains-core.patch + * remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch + * pkgrepo-support-python-2.7-function-call-295.patch + * opensuse-3000-virt-defined-states-222.patch + * open-suse-3002.2-xen-grub-316.patch + * add-patch-support-for-allow-vendor-change-option-wit.patch + * fix-the-removed-six.itermitems-and-six.-_type-262.patch + * fix-aptpkg-systemd-call-bsc-1143301.patch + * add-almalinux-and-alibaba-cloud-linux-to-the-os-fami.patch + * fix-cve-2020-25592-and-add-tests-bsc-1178319.patch + * regression-fix-of-salt-ssh-on-processing-targets-353.patch + * do-not-break-repo-files-with-multiple-line-values-on.patch + * 3002-set-distro-requirement-to-oldest-supported-vers.patch + * integration-of-msi-authentication-with-azurearm-clou.patch + * zypperpkg-filter-patterns-that-start-with-dot-244.patch + * fix-for-temp-folder-definition-in-loader-unit-test.patch + * fix-novendorchange-option-284.patch + * backport-virt-patches-from-3001-256.patch + * allow-passing-kwargs-to-pkg.list_downloaded-bsc-1140.patch + * path-replace-functools.wraps-with-six.wraps-bsc-1177.patch + * virt-uefi-fix-backport-312.patch + * add-all_versions-parameter-to-include-all-installed-.patch + * add-pkg.services_need_restart-302.patch + * add-batch_presence_ping_timeout-and-batch_presence_p.patch + * allow-vendor-change-option-with-zypper-313.patch + * avoid-traceback-when-http.query-request-cannot-be-pe.patch + * changed-imports-to-vendored-tornado.patch + * fix-issue-parsing-errors-in-ansiblegate-state-module.patch + * sanitize-grains-loaded-from-roster_grains.json.patch + * handle-volumes-on-stopped-pools-in-virt.vm_info-373.patch + * add-multi-file-support-and-globbing-to-the-filetree-.patch + * loosen-azure-sdk-dependencies-in-azurearm-cloud-driv.patch + * backport-thread.is_alive-fix-390.patch + * get-os_arch-also-without-rpm-package-installed.patch + * python3.8-compatibility-pr-s-235.patch + * fixed-bug-lvm-has-no-parttion-type.-the-scipt-later-.patch + * ensure-virt.update-stop_on_reboot-is-updated-with-it.patch + * xfs-do-not-fails-if-type-is-not-present.patch + * grains-master-can-read-grains.patch + * invalidate-file-list-cache-when-cache-file-modified-.patch + * move-vendor-change-logic-to-zypper-class-355.patch + * implement-network.fqdns-module-function-bsc-1134860-.patch + * opensuse-3000.2-virt-backports-236-257.patch + * prevent-ansiblegate-unit-tests-to-fail-on-ubuntu.patch + * batch_async-avoid-using-fnmatch-to-match-event-217.patch + * provide-the-missing-features-required-for-yomi-yet-o.patch + * fix-__mount_device-wrapper-254.patch + * fix-ipv6-scope-bsc-1108557.patch + * fix-failing-unit-tests-for-systemd.patch + * use-current-ioloop-for-the-localclient-instance-of-b.patch + * revert-add-patch-support-for-allow-vendor-change-opt.patch + * remove-deprecated-warning-that-breaks-miniion-execut.patch + * prevent-systemd-run-description-issue-when-running-a.patch + * fix-grains.test_core-unit-test-277.patch + * prevent-command-injection-in-the-snapper-module-bsc-.patch + * backport-of-upstream-pr59492-to-3002.2-404.patch + * use-threadpool-from-multiprocessing.pool-to-avoid-le.patch + * reintroducing-reverted-changes.patch + * add-cpe_name-for-osversion-grain-parsing-u-49946.patch + * add-hold-unhold-functions.patch + * virt._get_domain-don-t-raise-an-exception-if-there-i.patch + * fix-error-handling-in-openscap-module-bsc-1188647-40.patch + * apply-patch-from-upstream-to-support-python-3.8.patch + * remove-deprecated-usage-of-no_mock-and-no_mock_reaso.patch + * add-supportconfig-module-for-remote-calls-and-saltss.patch + * allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch + * fall-back-to-pymysql.patch + * fixes-cve-2018-15750-cve-2018-15751.patch + * do-not-crash-when-there-are-ipv6-established-connect.patch + * improve-batch_async-to-release-consumed-memory-bsc-1.patch + * support-config-non-root-permission-issues-fixes-u-50.patch + * transactional_update-detect-recursion-in-the-executo.patch + * open-suse-3002.2-virt-network-311.patch + * option-to-en-disable-force-refresh-in-zypper-215.patch + * do-noop-for-services-states-when-running-systemd-in-.patch + * exclude-the-full-path-of-a-download-url-to-prevent-i.patch + * fix-a-wrong-rebase-in-test_core.py-180.patch + * add-new-custom-suse-capability-for-saltutil-state-mo.patch + * opensuse-3000-libvirt-engine-fixes-251.patch + * accumulated-changes-from-yomi-167.patch + * fix-async-batch-race-conditions.patch + * fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch + * loop-fix-variable-names-for-until_no_eval.patch + * batch-async-catch-exceptions-and-safety-unregister-a.patch + * grains.extra-support-old-non-intel-kernels-bsc-11806.patch + * backport-a-few-virt-prs-272.patch + * fix-git_pillar-merging-across-multiple-__env__-repos.patch + * drop-wrong-virt-capabilities-code-after-rebasing-pat.patch + * virt-adding-kernel-boot-parameters-to-libvirt-xml-55.patch + * async-batch-implementation-fix-320.patch + * support-for-btrfs-and-xfs-in-parted-and-mkfs.patch + * support-transactional-systems-microos-271.patch + * strip-trailing-from-repo.uri-when-comparing-repos-in.patch + * opensuse-3000.3-spacewalk-runner-parse-command-250.patch + * calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch + * add-virt.all_capabilities.patch + * ansiblegate-take-care-of-failed-skipped-and-unreacha.patch + * virt-pass-emulator-when-getting-domain-capabilities-.patch + * fixing-streamclosed-issue.patch + * fix-for-some-cves-bsc1181550.patch + * transactional_update-unify-with-chroot.call.patch + * do-not-raise-streamclosederror-traceback-but-only-lo.patch + * fix-batch_async-obsolete-test.patch + * fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch + * fix-zypper.list_pkgs-to-be-aligned-with-pkg-state.patch + * accumulated-changes-required-for-yomi-165.patch + * fix-virt.update-with-cpu-defined-263.patch + * remove-vendored-backports-abc-from-requirements.patch + * open-suse-3002.2-bigvm-310.patch + * xen-disk-fixes-264.patch + * virt.network_update-handle-missing-ipv4-netmask-attr.patch + * add-saltssh-multi-version-support-across-python-inte.patch + +------------------------------------------------------------------- +Wed Sep 15 11:18:58 UTC 2021 - Pablo Suárez Hernández + +- Exclude the full path of a download URL to prevent injection of + malicious code (bsc#1190265) (CVE-2021-21996) + +- Added: + * exclude-the-full-path-of-a-download-url-to-prevent-i.patch + +------------------------------------------------------------------- +Tue Aug 31 11:28:13 UTC 2021 - Victor Zhestkov + +- Fix wrong relative paths resolution with Jinja renderer when importing subdirectories + +- Added: + * templates-move-the-globals-up-to-the-environment-jin.patch + +------------------------------------------------------------------- +Thu Aug 19 14:41:12 UTC 2021 - Victor Zhestkov + +- Don't pass shell="/sbin/nologin" to onlyif/unless checks (bsc#1188259) +- Add missing aarch64 to rpm package architectures +- Backport of upstream PR#59492 + +- Added: + * backport-of-upstream-pr59492-to-3002.2-404.patch + * don-t-use-shell-sbin-nologin-in-requisites.patch + * add-missing-aarch64-to-rpm-package-architectures-405.patch + +------------------------------------------------------------------- +Wed Aug 11 12:22:24 UTC 2021 - Pablo Suárez Hernández + +- Fix failing unit test for systemd +- Fix error handling in openscap module (bsc#1188647) +- Better handling of bad public keys from minions (bsc#1189040) + +- Added: + * better-handling-of-bad-public-keys-from-minions-bsc-.patch + * fix-error-handling-in-openscap-module-bsc-1188647-40.patch + * fix-failing-unit-tests-for-systemd.patch + +------------------------------------------------------------------- +Tue Aug 10 12:59:25 UTC 2021 - Pablo Suárez Hernández + +- Define license macro as doc in spec file if not existing +- Add standalone formulas configuration for salt minion and remove salt-master requirement (bsc#1168327) + +------------------------------------------------------------------- +Fri Jul 16 15:35:10 UTC 2021 - Pablo Suárez Hernández + +- Do noop for services states when running systemd in offline mode (bsc#1187787) +- transactional_updates: do not execute states in parallel but use a queue (bsc#1188170) + +- Added: + * do-noop-for-services-states-when-running-systemd-in-.patch + +------------------------------------------------------------------- +Thu Jul 8 08:06:40 UTC 2021 - Pablo Suárez Hernández + +- Handle "master tops" data when states are applied by "transactional_update" (bsc#1187787) +- Enhance openscap module: add "xccdf_eval" call + +- Added: + * enhance-openscap-module-add-xccdf_eval-call-386.patch + * handle-master-tops-data-when-states-are-applied-by-t.patch + +------------------------------------------------------------------- +Tue Jul 6 08:00:23 UTC 2021 - Victor Zhestkov + +- virt: pass emulator when getting domain capabilities from libvirt +- Adding preliminary support for Rocky Linux +- Implementation of held/unheld functions for state pkg (bsc#1187813) + +- Added: + * implementation-of-held-unheld-functions-for-state-pk.patch + * adding-preliminary-support-for-rocky.-59682-391.patch + * virt-pass-emulator-when-getting-domain-capabilities-.patch + +------------------------------------------------------------------- +Fri Jun 25 11:54:13 UTC 2021 - Alexander Graul + +- Replace deprecated Thread.isAlive() with Thread.is_alive() + +- Added: + * backport-thread.is_alive-fix-390.patch + +------------------------------------------------------------------- +Thu Jun 24 12:41:03 UTC 2021 - Victor Zhestkov + +- Fix exception in yumpkg.remove for not installed package +- Fix save for iptables state module (bsc#1185131) + +- Added: + * fix-exception-in-yumpkg.remove-for-not-installed-pac.patch + * fix-save-for-iptables-state-module-bsc-1185131-372.patch + +------------------------------------------------------------------- +Thu Jun 24 09:44:36 UTC 2021 - Pablo Suárez Hernández + +- virt: use /dev/kvm to detect KVM + +- Added: + * virt-use-dev-kvm-to-detect-kvm-383.patch + +------------------------------------------------------------------- +Thu Jun 24 08:41:31 UTC 2021 - Pablo Suárez Hernández + +- zypperpkg: improve logic for handling vendorchange flags + +- Added: + * move-vendor-change-logic-to-zypper-class-355.patch + +------------------------------------------------------------------- +Mon Jun 21 14:57:02 UTC 2021 - Pablo Suárez Hernández + +- Add bundled provides for tornado to the spec file +- Enhance logging when inotify beacon is missing pyinotify (bsc#1186310) +- Add "python3-pyinotify" as a recommended package for Salt in SUSE/OpenSUSE distros + +- Added: + * enhance-logging-when-inotify-beacon-is-missing-pyino.patch + +------------------------------------------------------------------- +Fri Jun 4 09:00:07 UTC 2021 - Pablo Suárez Hernández + +- Fix tmpfiles.d configuration for salt to not use legacy paths (bsc#1173103) + +------------------------------------------------------------------- +Tue Jun 1 12:05:20 UTC 2021 - Pablo Suárez Hernández + +- Check if dpkgnotify is executable (bsc#1186674) + +- Added: + * check-if-dpkgnotify-is-executable-bsc-1186674-376.patch + +------------------------------------------------------------------- +Fri May 21 15:01:10 UTC 2021 - Pablo Suárez Hernández + +- Detect Python version to use inside container (bsc#1167586) (bsc#1164192) +- Handle volumes on stopped pools in virt.vm_info (bsc#1186287) +- Drop support for Python2. Obsoletes "python2-salt" package + +- Added: + * handle-volumes-on-stopped-pools-in-virt.vm_info-373.patch + * figure-out-python-interpreter-to-use-inside-containe.patch + +------------------------------------------------------------------- +Mon May 10 14:45:26 UTC 2021 - Pablo Suárez Hernández + +- grains.extra: support old non-intel kernels (bsc#1180650) +- Fix missing minion returns in batch mode (bsc#1184659) + +- Added: + * fix-missing-minion-returns-in-batch-mode-360.patch + * grains.extra-support-old-non-intel-kernels-bsc-11806.patch + +------------------------------------------------------------------- +Tue May 4 13:44:13 UTC 2021 - Jochen Breuer + +- Parsing Epoch out of version provided during pkg remove (bsc#1173692) + +- Added: + * parsing-epoch-out-of-version-provided-during-pkg-rem.patch + +------------------------------------------------------------------- +Tue Apr 27 15:02:30 UTC 2021 - Pablo Suárez Hernández + +- Fix issue parsing errors in ansiblegate state module + +- Added: + * fix-issue-parsing-errors-in-ansiblegate-state-module.patch + +------------------------------------------------------------------- +Tue Apr 27 12:27:17 UTC 2021 - Pablo Suárez Hernández + +- Prevent command injection in the snapper module (bsc#1185281) (CVE-2021-31607) +- transactional_update: detect recursion in the executor +- Add subpackage salt-transactional-update (jsc#SLE-18028) +- Remove duplicate directories from specfile + +- Added: + * transactional_update-detect-recursion-in-the-executo.patch + * prevent-command-injection-in-the-snapper-module-bsc-.patch + +------------------------------------------------------------------- +Tue Apr 20 12:18:06 UTC 2021 - Pablo Suárez Hernández + +- Improvements on "ansiblegate" module (bsc#1185092): + * New methods: ansible.targets / ansible.discover_playbooks + * General bugfixes + +- Added: + * improvements-on-ansiblegate-module-354.patch + +------------------------------------------------------------------- +Tue Apr 13 15:03:48 UTC 2021 - Pablo Suárez Hernández + +- Regression fix of salt-ssh on processing some targets + +- Added: + * regression-fix-of-salt-ssh-on-processing-targets-353.patch + +------------------------------------------------------------------- +Tue Apr 13 08:40:32 UTC 2021 - Pablo Suárez Hernández + +- Add support for Alibaba Cloud Linux 2 (Aliyun Linux) + +- Added: + * add-alibaba-cloud-linux-2-by-backporting-upstream-s-.patch + +------------------------------------------------------------------- +Fri Apr 9 14:39:50 UTC 2021 - Victor Zhestkov + +- Update target fix for salt-ssh to process targets list (bsc#1179831) + +- Added: + * update-target-fix-for-salt-ssh-to-process-targets-li.patch + +------------------------------------------------------------------- +Fri Apr 9 10:33:54 UTC 2021 - Alexander Graul + +- Add notify beacon for Debian/Ubuntu systems +- Add core grains support for AlmaLinux and Alibaba Could Linux + +- Added: + * add-almalinux-and-alibaba-cloud-linux-to-the-os-fami.patch + * notify-beacon-for-debian-ubuntu-systems-347.patch + +------------------------------------------------------------------- +Wed Mar 17 14:17:05 UTC 2021 - Jochen Breuer + +- Allow vendor change option with zypper + +- Added: + * allow-vendor-change-option-with-zypper-313.patch + +------------------------------------------------------------------- +Wed Mar 10 08:42:54 UTC 2021 - Pablo Suárez Hernández + +- virt.network_update: handle missing ipv4 netmask attribute + +- Added: + * virt.network_update-handle-missing-ipv4-netmask-attr.patch + +------------------------------------------------------------------- +Tue Mar 9 14:34:29 UTC 2021 - Alexander Graul + +- Set distro requirement to oldest supported version in requirements/base.txt + +- Added: + * 3002-set-distro-requirement-to-oldest-supported-vers.patch + +------------------------------------------------------------------- +Tue Mar 9 09:00:08 UTC 2021 - Pablo Suárez Hernández + +- Do not monkey patch yaml loaders: Prevent breaking Ansible filter modules (bsc#1177474) +- Don't require python3-certifi + +- Added: + * do-not-monkey-patch-yaml-bsc-1177474.patch + +------------------------------------------------------------------- +Wed Mar 3 09:32:53 UTC 2021 - Pablo Suárez Hernández + +- Fix race conditions for corner cases when handling SIGTERM by minion (bsc#1172110) + +- Added: + * prevent-race-condition-on-sigterm-for-the-minion-bsc.patch + +------------------------------------------------------------------- +Mon Mar 1 11:21:01 UTC 2021 - Alexander Graul + +- Allow extra_filerefs as sanitized kwargs for SSH client +- Fix regression on cmd.run when passing tuples as cmd (bsc#1182740) +- Fix for multiple for security issues + (CVE-2020-28243) (CVE-2020-28972) (CVE-2020-35662) (CVE-2021-3148) (CVE-2021-3144) + (CVE-2021-25281) (CVE-2021-25282) (CVE-2021-25283) (CVE-2021-25284) (CVE-2021-3197) + (bsc#1181550) (bsc#1181556) (bsc#1181557) (bsc#1181558) (bsc#1181559) (bsc#1181560) + (bsc#1181561) (bsc#1181562) (bsc#1181563) (bsc#1181564) (bsc#1181565) +- Implementation of suse_ip execution module to prevent issues with network.managed (bsc#1099976) +- Add sleep on exception handling on minion connection attempt to the master (bsc#1174855) +- Allows for the VMware provider to handle CPU and memory hot-add in newer versions of the software. (bsc#1181347) +- Always require python-certifi (used by salt.ext.tornado) +- Bring missing part of async batch implementation back (bsc#1182382) (CVE-2021-25315) + +- Added: + * implementation-of-suse_ip-execution-module-bsc-10999.patch + * fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch + * async-batch-implementation-fix-320.patch + * add-sleep-on-exception-handling-on-minion-connection.patch + * allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch + * fix-for-some-cves-bsc1181550.patch + * fixes-56144-to-enable-hotadd-profile-support.patch + +------------------------------------------------------------------- +Tue Feb 16 17:10:30 UTC 2021 - Alexander Graul + +- Always require python3-distro (bsc#1182293) + +------------------------------------------------------------------- +Thu Feb 11 16:02:59 UTC 2021 - Pablo Suárez Hernández + +- virt: search for grub.xen path +- Xen spicevmc, DNS SRV records backports: + Fix virtual network generated DNS XML for SRV records + Don't add spicevmc channel to xen VMs +- virt UEFI fix: virt.update when efi=True + +- Added: + * virt-uefi-fix-backport-312.patch + * 3002.2-xen-spicevmc-dns-srv-records-backports-314.patch + * open-suse-3002.2-xen-grub-316.patch + +------------------------------------------------------------------- +Mon Jan 25 13:52:50 UTC 2021 - Pablo Suárez Hernández + +- Do not crash when unexpected cmd output at listing patches (bsc#1181290) + +- Added: + * do-not-crash-when-unexpected-cmd-output-at-listing-p.patch + +------------------------------------------------------------------- +Fri Jan 22 16:28:51 UTC 2021 - Pablo Suárez Hernández + +- Fix behavior for "onlyif/unless" when multiple conditions (bsc#1180818) + +- Added: + * fix-onlyif-unless-when-multiple-conditions-bsc-11808.patch + +------------------------------------------------------------------- +Wed Jan 13 13:49:34 UTC 2021 - Pablo Suárez Hernández + +- Remove deprecated warning that breaks minion execution when "server_id_use_crc" opts is missing + +- Added: + * remove-deprecated-warning-that-breaks-miniion-execut.patch + +------------------------------------------------------------------- +Wed Jan 13 10:13:13 UTC 2021 - Pablo Suárez Hernández + +- Revert wrong zypper patch to support vendorchanges flags on pkg.install + +- Added: + * revert-add-patch-support-for-allow-vendor-change-opt.patch + +------------------------------------------------------------------- +Tue Jan 12 12:09:35 UTC 2021 - Pablo Suárez Hernández + +- Force zyppnotify to prefer Packages.db than Packages if it exists +- Allow vendor change option with zypper +- Add pkg.services_need_restart +- Fix for file.check_perms to work with numeric uid/gid + +- Added: + * force-zyppnotify-to-prefer-packages.db-than-packages.patch + * fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch + * add-patch-support-for-allow-vendor-change-option-wit.patch + * add-pkg.services_need_restart-302.patch + +------------------------------------------------------------------- +Tue Jan 12 10:31:02 UTC 2021 - Pablo Suárez Hernández + +- virt: more network support + Add more network and PCI/USB host devices passthrough support + to virt module and states + +- Added: + * open-suse-3002.2-virt-network-311.patch + +------------------------------------------------------------------- +Tue Jan 12 09:55:36 UTC 2021 - Pablo Suárez Hernández + +- Bigvm backports + virt consoles, CPU tuning and topology, and memory tuning. + +- Added: + * open-suse-3002.2-bigvm-310.patch + +------------------------------------------------------------------- +Mon Jan 11 16:11:22 UTC 2021 - Pablo Suárez Hernández + +- Fix pkg states when DEB package has "all" arch + +- Added: + * fix-aptpkg.normalize_name-when-package-arch-is-all.patch + +------------------------------------------------------------------- +Tue Jan 5 12:49:42 UTC 2021 - Pablo Suárez Hernández + +- Do not force beacons configuration to be a list. + Revert https://github.com/saltstack/salt/pull/58655 + +- Added: + * revert-fixing-a-use-case-when-multiple-inotify-beaco.patch + +------------------------------------------------------------------- +Tue Jan 5 10:15:08 UTC 2021 - Pablo Suárez Hernández + +- Drop wrong virt capabilities code after rebasing patches + +- Added: + * drop-wrong-virt-capabilities-code-after-rebasing-pat.patch + +------------------------------------------------------------------- +Fri Dec 18 12:13:49 UTC 2020 - Pablo Suárez Hernández + +- Update to Salt release version 3002.2 (jsc#ECO-3212) (jsc#SLE-18033) +- See release notes: https://docs.saltstack.com/en/latest/topics/releases/3002.2.html + +- Modified: + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch + * fix-__mount_device-wrapper-254.patch + * opensuse-3000.2-virt-backports-236-257.patch + * fixes-cve-2018-15750-cve-2018-15751.patch + * strip-trailing-from-repo.uri-when-comparing-repos-in.patch + * include-aliases-in-the-fqdns-grains.patch + * support-config-non-root-permission-issues-fixes-u-50.patch + * support-for-btrfs-and-xfs-in-parted-and-mkfs.patch + * fix-batch_async-obsolete-test.patch + * early-feature-support-config.patch + * changed-imports-to-vendored-tornado.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * add-hold-unhold-functions.patch + * do-not-crash-when-there-are-ipv6-established-connect.patch + * add-docker-logout-237.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * fix-a-test-and-some-variable-names-229.patch + * implement-network.fqdns-module-function-bsc-1134860-.patch + * debian-info_installed-compatibility-50453.patch + * fix-bsc-1065792.patch + * use-current-ioloop-for-the-localclient-instance-of-b.patch + * restore-default-behaviour-of-pkg-list-return.patch + * virt-adding-kernel-boot-parameters-to-libvirt-xml-55.patch + * use-threadpool-from-multiprocessing.pool-to-avoid-le.patch + * add-migrated-state-and-gpg-key-management-functions-.patch + * info_installed-works-without-status-attr-now.patch + * bsc-1176024-fix-file-directory-user-and-group-owners.patch + * opensuse-3000.3-spacewalk-runner-parse-command-250.patch + * fix-aptpkg-systemd-call-bsc-1143301.patch + * fix-memory-leak-produced-by-batch-async-find_jobs-me.patch + * ansiblegate-take-care-of-failed-skipped-and-unreacha.patch + * calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch + * add-cpe_name-for-osversion-grain-parsing-u-49946.patch + * python3.8-compatibility-pr-s-235.patch + * backport-virt-patches-from-3001-256.patch + * do-not-break-repo-files-with-multiple-line-values-on.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * accumulated-changes-required-for-yomi-165.patch + * support-transactional-systems-microos-271.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * remove-vendored-backports-abc-from-requirements.patch + * fall-back-to-pymysql.patch + * xen-disk-fixes-264.patch + * fix-for-temp-folder-definition-in-loader-unit-test.patch + * batch.py-avoid-exception-when-minion-does-not-respon.patch + * move-server_id-deprecation-warning-to-reduce-log-spa.patch + * avoid-traceback-when-http.query-request-cannot-be-pe.patch + * fixed-bug-lvm-has-no-parttion-type.-the-scipt-later-.patch + * fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch + * grains-master-can-read-grains.patch + * remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch + * fix-wrong-test_mod_del_repo_multiline_values-test-af.patch + * accumulated-changes-from-yomi-167.patch + * allow-passing-kwargs-to-pkg.list_downloaded-bsc-1140.patch + * loosen-azure-sdk-dependencies-in-azurearm-cloud-driv.patch + * add-astra-linux-common-edition-to-the-os-family-list.patch + * fix-async-batch-race-conditions.patch + * batch-async-catch-exceptions-and-safety-unregister-a.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * drop-wrong-mock-from-chroot-unit-test.patch + * fix-for-suse-expanded-support-detection.patch + * fix-novendorchange-option-284.patch + * fix-virt.update-with-cpu-defined-263.patch + * add-batch_presence_ping_timeout-and-batch_presence_p.patch + * fix-git_pillar-merging-across-multiple-__env__-repos.patch + * add-publish_batch-to-clearfuncs-exposed-methods.patch + * fix-unit-tests-for-batch-async-after-refactor.patch + * add-new-custom-suse-capability-for-saltutil-state-mo.patch + * prevent-test_mod_del_repo_multiline_values-to-fail.patch + * x509-fixes-111.patch + * adds-explicit-type-cast-for-port.patch + * run-salt-master-as-dedicated-salt-user.patch + * remove-msgpack-1.0.0-requirement-in-the-installed-me.patch + * switch-firewalld-state-to-use-change_interface.patch + * option-to-en-disable-force-refresh-in-zypper-215.patch + * fix-async-batch-multiple-done-events.patch + * make-setup.py-script-to-not-require-setuptools-9.1.patch + * add-custom-suse-capabilities-as-grains.patch + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * transactional_update-unify-with-chroot.call.patch + * fix-ipv6-scope-bsc-1108557.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + * opensuse-3000-libvirt-engine-fixes-251.patch + * fix-grains.test_core-unit-test-277.patch + * pkgrepo-support-python-2.7-function-call-295.patch + * prevent-import-errors-when-running-test_btrfs-unit-t.patch + * do-not-make-ansiblegate-to-crash-on-python3-minions.patch + * fix-issue-2068-test.patch + * ensure-virt.update-stop_on_reboot-is-updated-with-it.patch + * remove-deprecated-usage-of-no_mock-and-no_mock_reaso.patch + * read-repo-info-without-using-interpolation-bsc-11356.patch + * fix-zypper.list_pkgs-to-be-aligned-with-pkg-state.patch + * fixing-streamclosed-issue.patch + * virt._get_domain-don-t-raise-an-exception-if-there-i.patch + * loop-fix-variable-names-for-until_no_eval.patch + * improve-batch_async-to-release-consumed-memory-bsc-1.patch + * prevent-systemd-run-description-issue-when-running-a.patch + * integration-of-msi-authentication-with-azurearm-clou.patch + * add-all_versions-parameter-to-include-all-installed-.patch + * sanitize-grains-loaded-from-roster_grains.json.patch + * fix-failing-unit-tests-for-batch-async.patch + * reintroducing-reverted-changes.patch + * fix-for-log-checking-in-x509-test.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * opensuse-3000-virt-defined-states-222.patch + * add-virt.all_capabilities.patch + * prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch + * fix-cve-2020-25592-and-add-tests-bsc-1178319.patch + * fix-unit-test-for-grains-core.patch + * async-batch-implementation.patch + * apply-patch-from-upstream-to-support-python-3.8.patch + * remove-unnecessary-yield-causing-badyielderror-bsc-1.patch + * re-adding-function-to-test-for-root.patch + * zypperpkg-filter-patterns-that-start-with-dot-244.patch + * fix-a-wrong-rebase-in-test_core.py-180.patch + * add-multi-file-support-and-globbing-to-the-filetree-.patch + * fix-the-removed-six.itermitems-and-six.-_type-262.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + * add-standalone-configuration-file-for-enabling-packa.patch + * make-profiles-a-package.patch + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + * batch_async-avoid-using-fnmatch-to-match-event-217.patch + * do-not-raise-streamclosederror-traceback-but-only-lo.patch + * provide-the-missing-features-required-for-yomi-yet-o.patch + * make-aptpkg.list_repos-compatible-on-enabled-disable.patch + * backport-a-few-virt-prs-272.patch + * add-supportconfig-module-for-remote-calls-and-saltss.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * path-replace-functools.wraps-with-six.wraps-bsc-1177.patch + * get-os_arch-also-without-rpm-package-installed.patch + * invalidate-file-list-cache-when-cache-file-modified-.patch + * xfs-do-not-fails-if-type-is-not-present.patch + * prevent-ansiblegate-unit-tests-to-fail-on-ubuntu.patch + +- Removed: + * do-not-report-patches-as-installed-when-not-all-the-.patch + * add-pkg.services_need_restart-302.patch + * removes-unresolved-merge-conflict-in-yumpkg-module.patch + * add-missing-fun-for-returns-from-wfunc-executions.patch + * force-zyppnotify-to-prefer-packages.db-than-packages.patch + * decide-if-the-source-should-be-actually-skipped.patch + * make-lazyloader.__init__-call-to-_refresh_file_mappi.patch + * avoid-has_docker-true-if-import-messes-with-salt.uti.patch + * fix-for-bsc-1102248-psutil-is-broken-and-so-process-.patch + * set-passphrase-for-salt-ssh-keys-to-empty-string-293.patch + * fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch + * add-patch-support-for-allow-vendor-change-option-wit.patch + * opensuse-3000.3-bigvm-backports-303.patch + * msgpack-support-versions-1.0.0.patch + * fix-typo-on-msgpack-version-when-sanitizing-msgpack-.patch + * use-full-option-name-instead-of-undocumented-abbrevi.patch + * add-missing-_utils-at-loader-grains_func.patch + * loader-invalidate-the-import-cachefor-extra-modules.patch + * fix-for-return-value-ret-vs-return-in-batch-mode.patch + * make-salt.ext.tornado.gen-to-use-salt.ext.backports_.patch + +------------------------------------------------------------------- +Thu Dec 10 10:47:12 UTC 2020 - Jochen Breuer + +- Force zyppnotify to prefer Packages.db than Packages if it exists +- Allow vendor change option with zypper + +- Added: + * add-patch-support-for-allow-vendor-change-option-wit.patch + * force-zyppnotify-to-prefer-packages.db-than-packages.patch + +------------------------------------------------------------------- +Tue Dec 8 14:56:28 UTC 2020 - Jochen Breuer + +- Add pkg.services_need_restart +- Bigvm backports: + virt consoles, CPU tuning and topology, and memory tuning. +- Fix for file.check_perms to work with numeric uid/gid + +- Added: + * add-pkg.services_need_restart-302.patch + * fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch + * opensuse-3000.3-bigvm-backports-303.patch + +------------------------------------------------------------------- +Fri Nov 27 10:47:14 UTC 2020 - Victor Zhestkov + +- Change 'Requires(pre)' to 'Requires' for salt-minion package (bsc#1083110) + +------------------------------------------------------------------- +Mon Nov 16 09:48:45 UTC 2020 - Pablo Suárez Hernández + +- Fix syntax error on pkgrepo state with Python 2.7 +- transactional_update: unify with chroot.call + +- Added: + * pkgrepo-support-python-2.7-function-call-295.patch + * transactional_update-unify-with-chroot.call.patch + +------------------------------------------------------------------- +Tue Nov 10 15:43:09 UTC 2020 - Jochen Breuer + +- Add "migrated" state and GPG key management functions + +- Added: + * add-migrated-state-and-gpg-key-management-functions-.patch + +------------------------------------------------------------------- +Tue Nov 10 15:09:16 UTC 2020 - Jochen Breuer + +- Master can read grains (bsc#1179696) + +- Added: + * grains-master-can-read-grains.patch + +------------------------------------------------------------------- +Tue Nov 10 14:03:22 UTC 2020 - Jochen Breuer + +- Fix for broken psutil (bsc#1102248) + +- Added: + * fix-for-bsc-1102248-psutil-is-broken-and-so-process-.patch + +------------------------------------------------------------------- +Fri Nov 6 09:19:22 UTC 2020 - Pablo Suárez Hernández + +- Set passphrase for salt-ssh keys to empty string (bsc#1178485) + +- Added: + * set-passphrase-for-salt-ssh-keys-to-empty-string-293.patch + +------------------------------------------------------------------- +Wed Nov 4 10:54:32 UTC 2020 - Pablo Suárez Hernández + +- Properly validate eauth credentials and tokens on SSH calls made by Salt API + (bsc#1178319) (bsc#1178362) (bsc#1178361) + (CVE-2020-25592) (CVE-2020-17490) (CVE-2020-16846) + +- Added: + * fix-cve-2020-25592-and-add-tests-bsc-1178319.patch + +------------------------------------------------------------------- +Tue Oct 27 15:23:12 UTC 2020 - Pablo Suárez Hernández + +- Fix novendorchange handling in zypperpkg module + +- Added: + * fix-novendorchange-option-284.patch + +------------------------------------------------------------------- +Tue Oct 20 11:43:49 UTC 2020 - Pablo Suárez Hernández + +- Fix disk.blkid to avoid unexpected keyword argument '__pub_user' (bsc#1177867) + +- Added: + * path-replace-functools.wraps-with-six.wraps-bsc-1177.patch + +------------------------------------------------------------------- +Wed Oct 14 10:49:33 UTC 2020 - Pablo Suárez Hernández + +- Ensure virt.update stop_on_reboot is updated with its default value + +- Added: + * ensure-virt.update-stop_on_reboot-is-updated-with-it.patch + +------------------------------------------------------------------- +Tue Oct 13 15:26:05 UTC 2020 - Pablo Suárez Hernández + +- Do not break package building for systemd OSes + +------------------------------------------------------------------- +Tue Oct 13 11:10:06 UTC 2020 - Pablo Suárez Hernández + +- Drop wrong mock from chroot unit test + +- Added: + * drop-wrong-mock-from-chroot-unit-test.patch + +------------------------------------------------------------------- +Wed Oct 7 12:19:05 UTC 2020 - Jochen Breuer + +- Support systemd versions with dot (bsc#1176294) + +------------------------------------------------------------------- +Tue Oct 6 12:52:51 UTC 2020 - Jochen Breuer + +- Fix for grains.test_core unit test +- Fix file/directory user and group ownership containing UTF-8 + characters (bsc#1176024) +- Several changes to virtualization: +- - Fix virt update when cpu and memory are changed +- - Memory Tuning GSoC +- - Properly fix memory setting regression in virt.update +- - Expose libvirt on_reboot in virt states +- Support transactional systems (MicroOS) +- zypperpkg module ignores retcode 104 for search() (bsc#1159670) +- Xen disk fixes. No longer generates volumes for Xen disks, but the + corresponding file or block disk (bsc#1175987) + +- Added: + * fix-grains.test_core-unit-test-277.patch + * support-transactional-systems-microos-271.patch + * backport-a-few-virt-prs-272.patch + * xen-disk-fixes-264.patch + * zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + * bsc-1176024-fix-file-directory-user-and-group-owners.patch + +------------------------------------------------------------------- +Wed Sep 23 14:48:41 UTC 2020 - Pablo Suárez Hernández + +- Invalidate file list cache when cache file modified time is in the future (bsc#1176397) + +- Added: + * invalidate-file-list-cache-when-cache-file-modified-.patch + +------------------------------------------------------------------- +Wed Sep 16 11:52:33 UTC 2020 - Pablo Suárez Hernández + +- Prevent import errors when running test_btrfs unit tests + +- Added: + * prevent-import-errors-when-running-test_btrfs-unit-t.patch + +------------------------------------------------------------------- +Wed Sep 16 10:57:15 UTC 2020 - Pablo Suárez Hernández + +- Remove msgpack < 1.0.0 from base requirements (bsc#1176293) + +- Added: + * remove-msgpack-1.0.0-requirement-in-the-installed-me.patch + +------------------------------------------------------------------- +Tue Sep 15 14:22:12 UTC 2020 - Pablo Suárez Hernández + +- Fix virt.update with CPU defined + +- Added: + * fix-virt.update-with-cpu-defined-263.patch + +------------------------------------------------------------------- +Tue Sep 15 12:15:16 UTC 2020 - Pablo Suárez Hernández + +- Fix virt issues and invalid input errors from 'salt.utils.data' (bsc#1176480) + +- Added: + * fix-the-removed-six.itermitems-and-six.-_type-262.patch + +------------------------------------------------------------------- +Tue Sep 8 12:59:11 UTC 2020 - Jochen Breuer + +- Reintroduces the patches from + opensuse-3000.2-virt-backports-236.patch coming from Salt 3001 + +- Added: + * backport-virt-patches-from-3001-256.patch + +------------------------------------------------------------------- +Tue Sep 1 08:42:50 UTC 2020 - Pablo Suárez Hernández + +- Adding missing virt backports to 3000.3 + +- Added: + * opensuse-3000.2-virt-backports-236-257.patch + +------------------------------------------------------------------- +Thu Aug 27 14:30:10 UTC 2020 - Pablo Suárez Hernández + +- Do not raise StreamClosedError traceback but only log it (bsc#1175549) + +- Added: + * do-not-raise-streamclosederror-traceback-but-only-lo.patch + +------------------------------------------------------------------- +Mon Aug 17 11:28:26 UTC 2020 - Pablo Suárez Hernández + +- Take care of failed, skipped and unreachable tasks and propagate "retcode" (bsc#1173911) (bsc#1173909) + +- Added: + * ansiblegate-take-care-of-failed-skipped-and-unreacha.patch + +------------------------------------------------------------------- +Mon Aug 10 15:15:31 UTC 2020 - Alexander Graul + +- Require /usr/bin/python instead of /bin/python for RHEL-family (bsc#1173936) + +------------------------------------------------------------------- +Fri Jul 31 14:55:06 UTC 2020 - Alexander Graul + +- Don't install SuSEfirewall2 service files in Factory +- Fix __mount_device wrapper to accept separate args and kwargs + +- Added: + * fix-__mount_device-wrapper-254.patch + +------------------------------------------------------------------- +Fri Jul 3 13:19:02 UTC 2020 - Jochen Breuer + +- Fix the registration of libvirt pool and nodedev events +- Accept nested namespaces in spacewalk.api runner function. (bsc#1172211) +- info_installed works without status attr now (bsc#1171461) + +- Added: + * info_installed-works-without-status-attr-now.patch + * opensuse-3000.3-spacewalk-runner-parse-command-250.patch + * opensuse-3000-libvirt-engine-fixes-251.patch + +------------------------------------------------------------------- +Thu May 28 16:01:10 UTC 2020 - Pablo Suárez Hernández + +- Avoid traceback on debug logging for swarm module (bsc#1172075) + +- Added: + * avoid-has_docker-true-if-import-messes-with-salt.uti.patch + +------------------------------------------------------------------- +Thu May 28 08:51:19 UTC 2020 - Pablo Suárez Hernández + +- Add publish_batch to ClearFuncs exposed methods + +- Added: + * add-publish_batch-to-clearfuncs-exposed-methods.patch + +------------------------------------------------------------------- +Tue May 26 14:37:09 UTC 2020 - Pablo Suárez Hernández + +- Update to Salt release version 3000.3 + See release notes: https://docs.saltstack.com/en/latest/topics/releases/3000.3.html + +- Removed: + * fix-typo-in-minion_runner-for-aesfuncs-exposed-metho.patch + +------------------------------------------------------------------- +Thu May 21 08:35:30 UTC 2020 - Pablo Suárez Hernández + +- zypperpkg: filter patterns that start with dot (bsc#1171906) + +- Added: + * zypperpkg-filter-patterns-that-start-with-dot-244.patch + +------------------------------------------------------------------- +Wed May 20 13:27:23 UTC 2020 - Jochen Breuer + +- Batch mode now also correctly provides return value (bsc#1168340) + +- Added: + * fix-for-return-value-ret-vs-return-in-batch-mode.patch + +------------------------------------------------------------------- +Mon May 18 15:22:58 UTC 2020 - Alexander Graul + +- Add docker.logout to docker execution module (bsc#1165572) + +- Added: + * add-docker-logout-237.patch + +------------------------------------------------------------------- +Tue May 12 15:07:12 UTC 2020 - Jochen Breuer + +- Testsuite fix +- Add option to enable/disable force refresh for zypper + +- Added: + * option-to-en-disable-force-refresh-in-zypper-215.patch + * fix-a-test-and-some-variable-names-229.patch + +------------------------------------------------------------------- +Fri May 8 14:24:19 UTC 2020 - Jochen Breuer + +- Python 3.8 compatibility changes +- msgpack support for version >= 1.0.0 (bsc#1171257) + +- Added: + * python3.8-compatibility-pr-s-235.patch + * msgpack-support-versions-1.0.0.patch + +------------------------------------------------------------------- +Thu May 7 15:36:38 UTC 2020 - Pablo Suárez Hernández + +- Prevent sporious "salt-api" stuck processes when managing SSH minions + because of logging deadlock (bsc#1159284) +- Avoid segfault from "salt-api" under certain conditions of heavy load + managing SSH minions (bsc#1169604) + +- Added: + * prevent-logging-deadlock-on-salt-api-subprocesses-bs.patch + * make-lazyloader.__init__-call-to-_refresh_file_mappi.patch + +------------------------------------------------------------------- +Thu Apr 30 13:24:35 UTC 2020 - Pablo Suárez Hernández + +- Update to Salt release version 3000.2 + See release notes: https://docs.saltstack.com/en/latest/topics/releases/3000.2.html + +- Fix typo in 'minion_runner' for AESFuncs exposed methods + +- Added: + * fix-typo-in-minion_runner-for-aesfuncs-exposed-metho.patch + +- Removed: + * fix-cve-2020-11651-and-fix-cve-2020-11652.patch + +------------------------------------------------------------------- +Thu Apr 30 12:30:04 UTC 2020 - Pablo Suárez Hernández + +- Update to Salt release version 3000.1 + See release notes: https://docs.saltstack.com/en/latest/topics/releases/3000.1.html + +- Fix CVE-2020-11651 and CVE-2020-11652 (bsc#1170595) +- Do not require vendored backports-abc (bsc#1170288) +- Fix partition.mkpart to work without fstype (bsc#1169800) + +- Added: + * fixed-bug-lvm-has-no-parttion-type.-the-scipt-later-.patch + * remove-vendored-backports-abc-from-requirements.patch + * fix-cve-2020-11651-and-fix-cve-2020-11652.patch + +- Modified: + * fix-a-wrong-rebase-in-test_core.py-180.patch + * make-setup.py-script-to-not-require-setuptools-9.1.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * accumulated-changes-from-yomi-167.patch + +- Removed: + * fix-for-unless-requisite-when-pip-is-not-installed.patch + * fix-regression-in-service-states-with-reload-argumen.patch + +------------------------------------------------------------------- +Tue Apr 7 10:38:57 UTC 2020 - Pablo Suárez Hernández + +- Update to Salt version 3000 + See release notes: https://docs.saltstack.com/en/latest/topics/releases/3000.html + +- Do not make file.recurse state to fail when msgpack 0.5.4 (bsc#1167437) +- Fixes status attribute issue in aptpkg test +- Make setup.py script not to require setuptools greater than 9.1 + loop: fix variable names for until_no_eval +- Drop conflictive module.run state patch (bsc#1167437) +- Update patches after rebase with upstream v3000 tag (bsc#1167437) +- Fix some requirements issues depending on Python3 versions +- Removes obsolete patch +- Fix for low rpm_lowpkg unit test +- Add python-singledispatch as dependency for python2-salt +- Fix for temp folder definition in loader unit test +- Make "salt.ext.tornado.gen" to use "salt.ext.backports_abc" on Python 2 +- Fix regression in service states with reload argument +- Fix integration test failure for test_mod_del_repo_multiline_values +- Fix for unless requisite when pip is not installed +- Fix errors from unit tests due NO_MOCK and NO_MOCK_REASON deprecation +- Fix tornado imports and missing _utils after rebasing patches +- Removes unresolved merge conflict in yumpkg module + +- Added: + * make-setup.py-script-to-not-require-setuptools-9.1.patch + * opensuse-3000-virt-defined-states-222.patch + * fix-for-unless-requisite-when-pip-is-not-installed.patch + * fix-typo-on-msgpack-version-when-sanitizing-msgpack-.patch + * fix-regression-in-service-states-with-reload-argumen.patch + * batch_async-avoid-using-fnmatch-to-match-event-217.patch + * make-salt.ext.tornado.gen-to-use-salt.ext.backports_.patch + * virt._get_domain-don-t-raise-an-exception-if-there-i.patch + * loop-fix-variable-names-for-until_no_eval.patch + * removes-unresolved-merge-conflict-in-yumpkg-module.patch + * add-missing-_utils-at-loader-grains_func.patch + * changed-imports-to-vendored-tornado.patch + * sanitize-grains-loaded-from-roster_grains.json.patch + * fix-for-temp-folder-definition-in-loader-unit-test.patch + * remove-deprecated-usage-of-no_mock-and-no_mock_reaso.patch + * reintroducing-reverted-changes.patch + * adds-explicit-type-cast-for-port.patch + * fix-wrong-test_mod_del_repo_multiline_values-test-af.patch + * re-adding-function-to-test-for-root.patch + +- Modified: + * move-server_id-deprecation-warning-to-reduce-log-spa.patch + * let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch + * strip-trailing-from-repo.uri-when-comparing-repos-in.patch + * prevent-test_mod_del_repo_multiline_values-to-fail.patch + * prevent-ansiblegate-unit-tests-to-fail-on-ubuntu.patch + * remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch + * async-batch-implementation.patch + * add-hold-unhold-functions.patch + * add-all_versions-parameter-to-include-all-installed-.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * fix-for-log-checking-in-x509-test.patch + * fix-zypper.list_pkgs-to-be-aligned-with-pkg-state.patch + * add-multi-file-support-and-globbing-to-the-filetree-.patch + * remove-unnecessary-yield-causing-badyielderror-bsc-1.patch + * fix-bsc-1065792.patch + * use-threadpool-from-multiprocessing.pool-to-avoid-le.patch + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + * fixes-cve-2018-15750-cve-2018-15751.patch + * add-cpe_name-for-osversion-grain-parsing-u-49946.patch + * fix-failing-unit-tests-for-batch-async.patch + * decide-if-the-source-should-be-actually-skipped.patch + * allow-passing-kwargs-to-pkg.list_downloaded-bsc-1140.patch + * add-batch_presence_ping_timeout-and-batch_presence_p.patch + * run-salt-master-as-dedicated-salt-user.patch + * use-current-ioloop-for-the-localclient-instance-of-b.patch + * integration-of-msi-authentication-with-azurearm-clou.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + * improve-batch_async-to-release-consumed-memory-bsc-1.patch + * fix-unit-test-for-grains-core.patch + * add-supportconfig-module-for-remote-calls-and-saltss.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * debian-info_installed-compatibility-50453.patch + * include-aliases-in-the-fqdns-grains.patch + * implement-network.fqdns-module-function-bsc-1134860-.patch + * fix-async-batch-multiple-done-events.patch + * support-config-non-root-permission-issues-fixes-u-50.patch + * fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * avoid-traceback-when-http.query-request-cannot-be-pe.patch + * fix-aptpkg-systemd-call-bsc-1143301.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * do-not-break-repo-files-with-multiple-line-values-on.patch + * fix-batch_async-obsolete-test.patch + * provide-the-missing-features-required-for-yomi-yet-o.patch + * fall-back-to-pymysql.patch + * xfs-do-not-fails-if-type-is-not-present.patch + * restore-default-behaviour-of-pkg-list-return.patch + * add-missing-fun-for-returns-from-wfunc-executions.patch + * virt-adding-kernel-boot-parameters-to-libvirt-xml-55.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * loosen-azure-sdk-dependencies-in-azurearm-cloud-driv.patch + * support-for-btrfs-and-xfs-in-parted-and-mkfs.patch + * fixing-streamclosed-issue.patch + * do-not-crash-when-there-are-ipv6-established-connect.patch + * calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch + * fix-async-batch-race-conditions.patch + * fix-issue-2068-test.patch + * fix-a-wrong-rebase-in-test_core.py-180.patch + * fix-for-suse-expanded-support-detection.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * add-standalone-configuration-file-for-enabling-packa.patch + * switch-firewalld-state-to-use-change_interface.patch + * do-not-make-ansiblegate-to-crash-on-python3-minions.patch + * make-aptpkg.list_repos-compatible-on-enabled-disable.patch + * add-custom-suse-capabilities-as-grains.patch + * accumulated-changes-from-yomi-167.patch + * get-os_arch-also-without-rpm-package-installed.patch + * fix-git_pillar-merging-across-multiple-__env__-repos.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * early-feature-support-config.patch + * add-virt.all_capabilities.patch + * accumulated-changes-required-for-yomi-165.patch + * fix-memory-leak-produced-by-batch-async-find_jobs-me.patch + * fix-ipv6-scope-bsc-1108557.patch + * prevent-systemd-run-description-issue-when-running-a.patch + * make-profiles-a-package.patch + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * batch.py-avoid-exception-when-minion-does-not-respon.patch + * read-repo-info-without-using-interpolation-bsc-11356.patch + * x509-fixes-111.patch + * do-not-report-patches-as-installed-when-not-all-the-.patch + +- Removed: + * remove-virt.pool_delete-fast-parameter-178.patch + * adds-the-possibility-to-also-use-downloadonly-in-kwa.patch + * align-virt-full-info-fixes-with-upstream-192.patch + * take-checksums-arg-into-account-for-postgres.datadir.patch + * virt-1.volume_infos-fix-for-single-vm.patch + * virt.volume_infos-needs-to-ignore-inactive-pools-174.patch + * preserve-already-defined-destructive_tests-and-expen.patch + * list_downloaded-for-apt-module.patch + * fix-virt-states-to-not-fail-on-vms-already-stopped.-.patch + * virt.volume_infos-fix-for-single-vm.patch + * restrict-the-start_event_grains-only-to-the-start-ev.patch + * fix-virt.full_info-176.patch + * preserving-signature-in-module.run-state-u-50049.patch + * checking-for-jid-before-returning-data.patch + * virt.volume_infos-silence-libvirt-error-message-175.patch + * add-virt.volume_infos-and-virt.volume_delete.patch + * add-virt.network_get_xml-function.patch + * virt.network_define-allow-adding-ip-configuration.patch + * add-ppc64le-as-a-valid-rpm-package-architecture.patch + * bugfix-any-unicode-string-of-length-16-will-raise-ty.patch + * fix-for-older-mock-module.patch + * fix-virt.get_hypervisor-188.patch + * 2019.2.0-pr-54196-backport-173.patch + * enable-passing-grains-to-start-event-based-on-start_.patch + * fix-load-cached-grain-osrelease_info.patch + * open-suse-2019.2.3-virt-defined-states-219.patch + * backport-saltutil-state-module-to-2019.2-codebase.patch + +------------------------------------------------------------------- +Thu Apr 2 13:47:34 UTC 2020 - Pablo Suárez Hernández + +- Enable building and installation for Fedora +- Disable python2 build on Tumbleweed + We are removing the python2 interpreter from openSUSE (SLE16). + As such disable salt building for python2 there. + +------------------------------------------------------------------- +Thu Apr 2 10:54:53 UTC 2020 - Pablo Suárez Hernández + +- Sanitize grains loaded from roster_grains.json cache during "state.pkg" + +- Added: + * fix-load-cached-grain-osrelease_info.patch + +------------------------------------------------------------------- +Fri Mar 27 15:37:10 UTC 2020 - Jochen Breuer + +- Build: Buildequire pkgconfig(systemd) instead of systemd + +------------------------------------------------------------------- +Thu Mar 26 13:18:50 UTC 2020 - Pablo Suárez Hernández + +- Backport saltutil state module to 2019.2 codebase (bsc#1167556) +- Add new custom SUSE capability for saltutil state module + +- Added: + * backport-saltutil-state-module-to-2019.2-codebase.patch + * add-new-custom-suse-capability-for-saltutil-state-mo.patch + +------------------------------------------------------------------- +Tue Mar 17 10:35:25 UTC 2020 - Pablo Suárez Hernández + +- virt._get_domain: don't raise an exception if there is no VM + +- Added: + * virt._get_domain-don-t-raise-an-exception-if-there-i.patch + +------------------------------------------------------------------- +Mon Mar 16 13:40:30 UTC 2020 - Jochen Breuer + +- Adds test for zypper abbreviation fix +- Improved storage pool or network handling +- Better import cache handline + +- Added: + * loader-invalidate-the-import-cachefor-extra-modules.patch + * open-suse-2019.2.3-virt-defined-states-219.patch + +- Modified: + * use-full-option-name-instead-of-undocumented-abbrevi.patch + +------------------------------------------------------------------- +Thu Mar 5 12:12:35 UTC 2020 - Jochen Breuer + +- Use full option name instead of undocumented abbreviation for zypper +- Requiring python3-distro only for openSUSE/SLE >= 15 + +- Added: + * use-full-option-name-instead-of-undocumented-abbrevi.patch + +------------------------------------------------------------------- +Thu Mar 5 09:35:29 UTC 2020 - Pablo Suárez Hernández + +- python-distro is only needed for > Python 3.7. Removing it for Python 2 + +------------------------------------------------------------------- +Wed Mar 4 16:51:34 UTC 2020 - Pablo Suárez Hernández + +- Avoid possible user escalation upgrading salt-master (bsc#1157465) (CVE-2019-18897) + +------------------------------------------------------------------- +Wed Mar 4 10:29:13 UTC 2020 - Pablo Suárez Hernández + +- Fix unit tests failures in test_batch_async tests + +- Added: + * fix-unit-tests-for-batch-async-after-refactor.patch + +------------------------------------------------------------------- +Mon Mar 2 10:49:09 UTC 2020 - Pablo Suárez Hernández + +- Batch Async: Handle exceptions, properly unregister and close instances + after running async batching to avoid CPU starvation of the MWorkers (bsc#1162327) +- RHEL/CentOS 8 uses platform-python instead of python3 +- Enable build for Python 3.8 + +- Added: + * batch_async-avoid-using-fnmatch-to-match-event-217.patch + * apply-patch-from-upstream-to-support-python-3.8.patch + * batch-async-catch-exceptions-and-safety-unregister-a.patch + +------------------------------------------------------------------- +Wed Feb 12 09:16:12 UTC 2020 - Pablo Suárez Hernández + +- Fix 'os_family' grain for Astra Linux Common Edition + +- Added: + * add-astra-linux-common-edition-to-the-os-family-list.patch + +------------------------------------------------------------------- +Mon Feb 3 10:42:42 UTC 2020 - Jochen Breuer + +- Update to Salt version 2019.2.3 (CVE-2019-17361) (bsc#1163981) (bsc#1162504) + See release notes: https://docs.saltstack.com/en/latest/topics/releases/2019.2.3.html + +- Modified: + * use-adler32-algorithm-to-compute-string-checksums.patch + +------------------------------------------------------------------- +Wed Jan 29 15:38:36 UTC 2020 - Jochen Breuer + +- Enable passing grains to start event based on 'start_event_grains' configuration parameter + +- Added: + * restrict-the-start_event_grains-only-to-the-start-ev.patch + * enable-passing-grains-to-start-event-based-on-start_.patch + +------------------------------------------------------------------- +Mon Jan 13 16:09:36 UTC 2020 - Jochen Breuer + +- Support for Btrfs and XFS in parted and mkfs added + +- Added: + * support-for-btrfs-and-xfs-in-parted-and-mkfs.patch + +------------------------------------------------------------------- +Thu Jan 9 19:20:34 UTC 2020 - Jochen Breuer + +- Adds list_downloaded for apt Module to enable pre-downloading support +- Adds virt.(pool|network)_get_xml functions +- Various libvirt updates + * Add virt.pool_capabilities function + * virt.pool_running improvements + * Add virt.pool_deleted state + * virt.network_define allow adding IP configuration + +- Added: + * virt.network_define-allow-adding-ip-configuration.patch + * list_downloaded-for-apt-module.patch + * add-virt.network_get_xml-function.patch + +------------------------------------------------------------------- +Tue Jan 7 10:28:04 UTC 2020 - Pablo Suárez Hernández + +- virt: adding kernel boot parameters to libvirt xml + +- Added: + * virt-adding-kernel-boot-parameters-to-libvirt-xml-55.patch + +------------------------------------------------------------------- +Mon Dec 16 10:36:42 UTC 2019 - Pablo Suárez Hernández + +- Fix virt states to not fail on VMs already stopped + +- Added: + * fix-virt-states-to-not-fail-on-vms-already-stopped.-.patch + +------------------------------------------------------------------- +Thu Dec 12 10:21:15 UTC 2019 - Pablo Suárez Hernández + +- Add missing bugzilla references: + Properly handle colons in inline dicts with yamlloader (bsc#1095651) + Fix corrupt public key with m2crypto python3 (bsc#1099323) + Add missing dateutils import (bsc#1099945) + Fix UnicodeDecodeError using is_binary check (bsc#1100225) + Prevent payload crash on decoding binary data (bsc#1100697) + Fix file.blockreplace to avoid throwing IndexError (bsc#1101812) + Add API log rotation on SUSE package (bsc#1102218) + Fix wrong recurse behavior on for linux_acl.present (bsc#1106164) + Handle anycast IPv6 addresses on network.routes (bsc#1114474) + Crontab module fix: file attributes option missing (bsc#1114824) + Add metadata to accepted keyword arguments (bsc#1122680) + Bugfix: properly refresh pillars (bsc#1125015) + +------------------------------------------------------------------- +Wed Dec 11 14:27:24 UTC 2019 - Mihai Dincă + +- xfs: do not fail if type is not present (bsc#1153611) + +- Added: + * xfs-do-not-fails-if-type-is-not-present.patch + +------------------------------------------------------------------- +Tue Dec 10 12:56:45 UTC 2019 - Pablo Suárez Hernández + +- Don't use __python indirection macros on spec file + %__python is no longer defined in RPM 4.15 (python2 is going EOL in Jan 2020); + additionally, python/python3 are just binaries in the path. + +------------------------------------------------------------------- +Tue Dec 10 09:35:15 UTC 2019 - Pablo Suárez Hernández + +- Fix errors when running virt.get_hypervisor function + +- Added: + * fix-virt.get_hypervisor-188.patch + +------------------------------------------------------------------- +Mon Dec 9 16:37:04 UTC 2019 - Pablo Suárez Hernández + +- Align virt.full_info fixes with upstream Salt +- Let salt-ssh use platform-python on RHEL8 (bsc#1158441) + +- Added: + * align-virt-full-info-fixes-with-upstream-192.patch + * let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch + +------------------------------------------------------------------- +Tue Dec 3 12:22:55 UTC 2019 - Mihai Dincă + +- Fix StreamClosedError issue (bsc#1157479) + +- Added: + * fix-batch_async-obsolete-test.patch + * fixing-streamclosed-issue.patch + +------------------------------------------------------------------- +Thu Nov 28 15:27:27 UTC 2019 - Mihai Dincă + +- Prevent test_mod_del_repo_multiline_values to fail +- Read repo info without using interpolation (bsc#1135656) +- Requires vs BuildRequires +- Limiting M2Crypto to >= SLE15 +- Replacing pycrypto with M2Crypto (bsc#1165425) +- Fix for log checking in x509 test +- Update to 2019.2.2 release + +- Added: + * fix-for-log-checking-in-x509-test.patch + * prevent-test_mod_del_repo_multiline_values-to-fail.patch + * read-repo-info-without-using-interpolation-bsc-11356.patch + +- Modified: + * async-batch-implementation.patch + * add-hold-unhold-functions.patch + * adds-the-possibility-to-also-use-downloadonly-in-kwa.patch + * decide-if-the-source-should-be-actually-skipped.patch + * allow-passing-kwargs-to-pkg.list_downloaded-bsc-1140.patch + * add-batch_presence_ping_timeout-and-batch_presence_p.patch + * run-salt-master-as-dedicated-salt-user.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * fix-unit-test-for-grains-core.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * fix-async-batch-multiple-done-events.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * fix-for-older-mock-module.patch + * do-not-break-repo-files-with-multiple-line-values-on.patch + * fall-back-to-pymysql.patch + * add-missing-fun-for-returns-from-wfunc-executions.patch + * loosen-azure-sdk-dependencies-in-azurearm-cloud-driv.patch + * add-virt.volume_infos-and-virt.volume_delete.patch + * fix-issue-2068-test.patch + * switch-firewalld-state-to-use-change_interface.patch + * make-aptpkg.list_repos-compatible-on-enabled-disable.patch + * fix-ipv6-scope-bsc-1108557.patch + * 2019.2.0-pr-54196-backport-173.patch + * do-not-make-ansiblegate-to-crash-on-python3-minions.patch + * x509-fixes-111.patch + * prevent-ansiblegate-unit-tests-to-fail-on-ubuntu.patch + * fix-zypper.list_pkgs-to-be-aligned-with-pkg-state.patch + * add-cpe_name-for-osversion-grain-parsing-u-49946.patch + * fix-failing-unit-tests-for-batch-async.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + * improve-batch_async-to-release-consumed-memory-bsc-1.patch + * batch.py-avoid-exception-when-minion-does-not-respon.patch + * preserve-already-defined-destructive_tests-and-expen.patch + * virt.volume_infos-fix-for-single-vm.patch + * move-server_id-deprecation-warning-to-reduce-log-spa.patch + * include-aliases-in-the-fqdns-grains.patch + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * add-custom-suse-capabilities-as-grains.patch + * get-os_arch-also-without-rpm-package-installed.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * accumulated-changes-required-for-yomi-165.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch + * use-current-ioloop-for-the-localclient-instance-of-b.patch + * remove-virt.pool_delete-fast-parameter-178.patch + * add-multi-file-support-and-globbing-to-the-filetree-.patch + * use-threadpool-from-multiprocessing.pool-to-avoid-le.patch + * prevent-systemd-run-description-issue-when-running-a.patch + * integration-of-msi-authentication-with-azurearm-clou.patch + * virt.volume_infos-needs-to-ignore-inactive-pools-174.patch + * virt-1.volume_infos-fix-for-single-vm.patch + * add-supportconfig-module-for-remote-calls-and-saltss.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * strip-trailing-from-repo.uri-when-comparing-repos-in.patch + * preserving-signature-in-module.run-state-u-50049.patch + * fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch + * fix-aptpkg-systemd-call-bsc-1143301.patch + * calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch + * remove-unnecessary-yield-causing-badyielderror-bsc-1.patch + * debian-info_installed-compatibility-50453.patch + * add-standalone-configuration-file-for-enabling-packa.patch + * accumulated-changes-from-yomi-167.patch + * add-virt.all_capabilities.patch + * fix-memory-leak-produced-by-batch-async-find_jobs-me.patch + * do-not-report-patches-as-installed-when-not-all-the-.patch + * support-config-non-root-permission-issues-fixes-u-50.patch + * add-all_versions-parameter-to-include-all-installed-.patch + * fixes-cve-2018-15750-cve-2018-15751.patch + * fix-bsc-1065792.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * avoid-traceback-when-http.query-request-cannot-be-pe.patch + * restore-default-behaviour-of-pkg-list-return.patch + * take-checksums-arg-into-account-for-postgres.datadir.patch + * early-feature-support-config.patch + * provide-the-missing-features-required-for-yomi-yet-o.patch + * implement-network.fqdns-module-function-bsc-1134860-.patch + * fix-virt.full_info-176.patch + * checking-for-jid-before-returning-data.patch + * virt.volume_infos-silence-libvirt-error-message-175.patch + * do-not-crash-when-there-are-ipv6-established-connect.patch + * fix-for-suse-expanded-support-detection.patch + * fix-a-wrong-rebase-in-test_core.py-180.patch + * add-ppc64le-as-a-valid-rpm-package-architecture.patch + * make-profiles-a-package.patch + * bugfix-any-unicode-string-of-length-16-will-raise-ty.patch + * fix-git_pillar-merging-across-multiple-__env__-repos.patch + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + * fix-async-batch-race-conditions.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + +- Removed: + * fix-syndic-start-issue.patch + * prevent-already-reading-continuous-exception-message.patch + * virt.pool_running-fix-pool-start.patch + * azurefs-gracefully-handle-attributeerror.patch + * virt-handle-whitespaces-in-vm-names.patch + * mount-fix-extra-t-parameter.patch + * try-except-undefineflags-as-this-operation-is-not-su.patch + +------------------------------------------------------------------- +Wed Oct 30 11:23:05 UTC 2019 - Pablo Suárez Hernández + +- Remove virt.pool_delete fast parameter (U#54474) + +- Added: + * remove-virt.pool_delete-fast-parameter-178.patch + +------------------------------------------------------------------- +Wed Oct 30 10:22:18 UTC 2019 - Mihai Dincă + +- Remove unnecessary yield causing BadYieldError (bsc#1154620) + +- Added: + * remove-unnecessary-yield-causing-badyielderror-bsc-1.patch + +------------------------------------------------------------------- +Tue Oct 29 09:14:07 UTC 2019 - Pablo Suárez Hernández + +- Prevent 'Already reading' continuous exception message (bsc#1137642) + +- Added: + * prevent-already-reading-continuous-exception-message.patch + +------------------------------------------------------------------- +Fri Oct 25 14:39:23 UTC 2019 - Jochen Breuer + +- Fix for aptpkg test with older mock modules + +- Added: + * fix-for-older-mock-module.patch + +------------------------------------------------------------------- +Fri Oct 25 13:52:01 UTC 2019 - Pablo Suárez Hernández + +- Remove wrong tests for core grain and improve debug logging +- Use rich RPM deps to get a compatible version of tornado into the + buildroot. + +- Added: + * fix-a-wrong-rebase-in-test_core.py-180.patch + +------------------------------------------------------------------- +Tue Oct 22 09:29:19 UTC 2019 - Pablo Suárez Hernández + +- core.py: ignore wrong product_name files +- zypperpkg: understand product type + +- Added: + * accumulated-changes-from-yomi-167.patch + +------------------------------------------------------------------- +Mon Oct 21 15:10:37 UTC 2019 - Jochen Breuer + +- Enable usage of downloadonly parameter for apt module + +- Added: + * adds-the-possibility-to-also-use-downloadonly-in-kwa.patch + +------------------------------------------------------------------- +Wed Oct 9 12:40:33 UTC 2019 - Pablo Suárez Hernández + +- Add missing 'fun' on events coming from salt-ssh wfunc executions (bsc#1151947) + +- Added: + * add-missing-fun-for-returns-from-wfunc-executions.patch + +------------------------------------------------------------------- +Fri Oct 4 14:35:10 UTC 2019 - Pablo Suárez Hernández + +- Fix failing unit tests for batch async + +- Added: + * fix-failing-unit-tests-for-batch-async.patch + +------------------------------------------------------------------- +Thu Oct 3 14:50:41 UTC 2019 - Pablo Suárez Hernández + +- Fix memory consumption problem on BatchAsync (bsc#1137642) + +- Added: + * use-current-ioloop-for-the-localclient-instance-of-b.patch + +------------------------------------------------------------------- +Tue Oct 1 13:17:58 UTC 2019 - Pablo Suárez Hernández + +- Fix dependencies for RHEL 8 + +------------------------------------------------------------------- +Mon Sep 30 11:15:32 UTC 2019 - Pablo Suárez Hernández + +- Prevent systemd-run description issue when running aptpkg (bsc#1152366) + +- Added: + * prevent-systemd-run-description-issue-when-running-a.patch + +------------------------------------------------------------------- +Thu Sep 26 15:17:59 UTC 2019 - Pablo Suárez Hernández + +- Take checksums arg into account for postgres.datadir_init (bsc#1151650) + +- Added: + * take-checksums-arg-into-account-for-postgres.datadir.patch + +------------------------------------------------------------------- +Thu Sep 26 10:23:39 UTC 2019 - Pablo Suárez Hernández + +- Improve batch_async to release consumed memory (bsc#1140912) +- Fix memory leak produced by batch async find_jobs mechanism (bsc#1140912) +- Grant read and execute permission to others (bsc#1150447) + +- Added: + * improve-batch_async-to-release-consumed-memory-bsc-1.patch + * fix-memory-leak-produced-by-batch-async-find_jobs-me.patch + +------------------------------------------------------------------- +Thu Sep 5 17:45:50 UTC 2019 - Jochen Breuer + +- Require shadow instead of old pwdutils (bsc#1130588) + +------------------------------------------------------------------- +Wed Sep 4 18:45:56 UTC 2019 - Jochen Breuer + +- Conflict with tornado >= 5; for now we can only cope with Tornado 4.x (boo#1101780). + +------------------------------------------------------------------- +Tue Sep 3 15:16:15 UTC 2019 - Mihai Dincă + +- Fix virt.full_info (bsc#1146382) +- virt.volume_infos: silence libvirt error message +- virt.volume_infos needs to ignore inactive pools +- Fix for various bugs in virt network and pool states +- Implement network.fqdns module function (bsc#1134860) + +- Added: + * 2019.2.0-pr-54196-backport-173.patch + * virt.volume_infos-silence-libvirt-error-message-175.patch + * fix-virt.full_info-176.patch + * implement-network.fqdns-module-function-bsc-1134860-.patch + * virt.volume_infos-needs-to-ignore-inactive-pools-174.patch + +------------------------------------------------------------------- +Fri Aug 30 13:36:05 UTC 2019 - Jochen Breuer + +- Restore default behaviour of pkg list return (bsc#1148714) +- Strip trailing "/" from repo.uri when comparing repos in apktpkg.mod_repo (bsc#1146192) + +- Added: + * strip-trailing-from-repo.uri-when-comparing-repos-in.patch + * restore-default-behaviour-of-pkg-list-return.patch + +------------------------------------------------------------------- +Tue Aug 13 10:43:21 UTC 2019 - Pablo Suárez Hernández + +- Use python3 to build package Salt for RHEL8 +- Make python3 default for RHEL8 + +------------------------------------------------------------------- +Fri Aug 9 09:45:31 UTC 2019 - Mihai Dincă + +- Fix aptpkg systemd call (bsc#1143301) + +- Added: + * fix-aptpkg-systemd-call-bsc-1143301.patch + +------------------------------------------------------------------- +Tue Jul 30 14:56:02 UTC 2019 - Mihai Dincă + +- Move server_id deprecation warning to reduce log spamming (bsc#1135567) (bsc#1135732) + +- Added: + * move-server_id-deprecation-warning-to-reduce-log-spa.patch + +------------------------------------------------------------------- +Tue Jul 30 09:34:27 UTC 2019 - Pablo Suárez Hernández + +- Multiple fixes on cmdmod, chroot, freezer and zypperpkg needed for Yomi + cmdmod: fix runas and group in run_chroot + chroot: add missing sys directory + chroot: change variable name to root + chroot: fix bug in safe_kwargs iteration + freezer: do not fail in cache dir is present + freezer: clean freeze YAML profile on restore + zypperpkg: fix pkg.list_pkgs cache + +- Added: + * accumulated-changes-required-for-yomi-165.patch + +------------------------------------------------------------------- +Mon Jul 29 10:59:41 UTC 2019 - Pablo Suárez Hernández + +- Avoid traceback on http.query when there are errors with the requested URL (bsc#1128554) + +- Added: + * avoid-traceback-when-http.query-request-cannot-be-pe.patch + +------------------------------------------------------------------- +Thu Jul 25 10:26:16 UTC 2019 - Mihai Dincă + +- Salt python client get_full_returns seems return data from incorrect jid (bsc#1131114) + +- Added: + * checking-for-jid-before-returning-data.patch + +------------------------------------------------------------------- +Wed Jul 10 08:54:28 UTC 2019 - Pablo Suárez Hernández + +- virt.volume_infos: don't raise an error if there is no VM + +- Added: + * virt-1.volume_infos-fix-for-single-vm.patch + +------------------------------------------------------------------- +Mon Jul 8 14:22:58 UTC 2019 - Pablo Suárez Hernández + +- Prevent ansiblegate unit tests to fail on Ubuntu + +- Added: + * prevent-ansiblegate-unit-tests-to-fail-on-ubuntu.patch + +------------------------------------------------------------------- +Wed Jul 3 08:53:13 UTC 2019 - Pablo Suárez Hernández + +- Allow passing kwargs to pkg.list_downloaded for Zypper (bsc#1140193) + +- Added: + * allow-passing-kwargs-to-pkg.list_downloaded-bsc-1140.patch + +------------------------------------------------------------------- +Fri Jun 28 15:26:59 UTC 2019 - Pablo Suárez Hernández + +- Do not make "ansiblegate" module to crash on Python3 minions (bsc#1139761) + +- Added: + * do-not-make-ansiblegate-to-crash-on-python3-minions.patch + +------------------------------------------------------------------- +Thu Jun 20 12:32:04 UTC 2019 - Pablo Suárez Hernández + +- Provide the missing features required for Yomi (Yet one more installer) + +- Added: + * provide-the-missing-features-required-for-yomi-yet-o.patch + +------------------------------------------------------------------- +Fri Jun 14 14:09:29 UTC 2019 - Pablo Suárez Hernández + +- Fix zypper pkg.list_pkgs test expectation and dpkg mocking + +- Added: + * fix-zypper-pkg.list_pkgs-expectation-and-dpkg-mockin.patch + +------------------------------------------------------------------- +Fri Jun 14 12:15:43 UTC 2019 - Pablo Suárez Hernández + +- Set 'salt' group for files and directories created by + salt-standalone-formulas-configuration package +- Various fixes for virt module +- Fix virt.volume_infos raising an exception when there is only virtual machine on the minion. +- Fix virt.purge() on all non-KVM hypervisors. For instance on Xen, virt.purge would simply throw an exception about unsupported flag +- Building a libvirt pool starts it. When defining a new pool, we need to +let build start it or we will get libvirt errors. +- Fix handling of Virtual Machines with white space in their name. + +- Added: + * virt.pool_running-fix-pool-start.patch + * virt-handle-whitespaces-in-vm-names.patch + * virt.volume_infos-fix-for-single-vm.patch + * try-except-undefineflags-as-this-operation-is-not-su.patch + +------------------------------------------------------------------- +Wed Jun 5 14:26:29 UTC 2019 - Pablo Suárez Hernández + +- avoid batch.py exception when minion does not respond (bsc#1135507) + +- Added: + * batch.py-avoid-exception-when-minion-does-not-respon.patch + +------------------------------------------------------------------- +Mon Jun 3 11:01:57 UTC 2019 - psuarezhernandez@suse.com + +- Preserve already defined DESTRUCTIVE_TESTS and EXPENSIVE_TESTS + env variables + +- Added: + * preserve-already-defined-destructive_tests-and-expen.patch + +------------------------------------------------------------------- +Wed May 29 10:54:42 UTC 2019 - psuarezhernandez@suse.com + +- Do not break repo files with multiple line values on yumpkg (bsc#1135360) + +- Added: + * do-not-break-repo-files-with-multiple-line-values-on.patch + +------------------------------------------------------------------- +Fri May 24 16:03:09 UTC 2019 - psuarezhernandez@suse.com + +- Fix return status when installing or updating RPM packages + with "ppc64le" arch (bsc#1133647) + +- Added: + * add-ppc64le-as-a-valid-rpm-package-architecture.patch + +------------------------------------------------------------------- +Thu May 23 08:27:52 UTC 2019 - psuarezhernandez@suse.com + +- Add new "salt-standalone-formulas-configuration" package (fate#327791) + +- Added: + * add-standalone-configuration-file-for-enabling-packa.patch + +------------------------------------------------------------------- +Mon May 20 12:12:46 UTC 2019 - psuarezhernandez@suse.com + +- Switch firewalld state to use change_interface (bsc#1132076) + +- Added: + * switch-firewalld-state-to-use-change_interface.patch + +------------------------------------------------------------------- +Wed May 8 08:48:49 UTC 2019 - Mihai Dincă + +- Fix async-batch to fire a single done event + +- Added: + * fix-async-batch-multiple-done-events.patch + +------------------------------------------------------------------- +Tue May 7 15:37:39 UTC 2019 - psuarezhernandez@suse.com + +- Do not make Salt CLI to crash when there are IPv6 established + connections (bsc#1130784) + +- Added: + * do-not-crash-when-there-are-ipv6-established-connect.patch + +------------------------------------------------------------------- +Fri May 3 09:42:06 UTC 2019 - mdinca + +- Include aliases in FQDNS grain (bsc#1121439) + +------------------------------------------------------------------- +Thu May 2 16:18:45 UTC 2019 - mdinca + +- Fix issue preventing syndic to start +- Update year on spec copyright notice + +- Added: + * fix-syndic-start-issue.patch + +------------------------------------------------------------------- +Tue Apr 30 11:51:59 UTC 2019 - psuarezhernandez@suse.com + +- Use ThreadPool from multiprocessing.pool to avoid leakings + when calculating FQDNs +- Do not report patches as installed on RHEL systems when not all + the related packages are installed (bsc#1128061) + +- Added: + * use-threadpool-from-multiprocessing.pool-to-avoid-le.patch + * do-not-report-patches-as-installed-when-not-all-the-.patch + +------------------------------------------------------------------- +Fri Apr 26 10:00:01 UTC 2019 - mdinca@suse.de + +- Update to 2019.2.0 complete (FATE#327138, bsc#1133523) +- Fix batch/batch-async related issues +- Calculate FQDNs in parallel to avoid blockings (bsc#1129079) +- Incorporate virt.volume_info fixes (PR#131) +- Re-adds patch because of increased offset due to previous patch removal +- Removing patch to add root parameter to zypper module +- Fix for -t parameter in mount module + +- Added: + * mount-fix-extra-t-parameter.patch + * add-batch_presence_ping_timeout-and-batch_presence_p.patch + * fix-async-batch-race-conditions.patch + * calculate-fqdns-in-parallel-to-avoid-blockings-bsc-1.patch + +- Modified: + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * add-virt.volume_infos-and-virt.volume_delete.patch + +- Removed: + * zypper-add-root-configuration-parameter.patch + +------------------------------------------------------------------- +Thu Feb 28 16:18:38 UTC 2019 - Jochen Breuer + +- No longer limiting Python3 version to <3.7 + +------------------------------------------------------------------- +Thu Feb 28 08:24:16 UTC 2019 - Jochen Breuer + +- Async batch implementation + +- Added: + * async-batch-implementation.patch + +------------------------------------------------------------------- +Wed Feb 27 14:28:55 UTC 2019 - jbreuer@suse.de + +- Update to Salt 2019.2.0 release + For further information see: + https://docs.saltstack.com/en/latest/topics/releases/2019.2.0.html + +- Added: + * add-virt.all_capabilities.patch + * add-virt.volume_infos-and-virt.volume_delete.patch + * don-t-call-zypper-with-more-than-one-no-refresh.patch + * include-aliases-in-the-fqdns-grains.patch + * temporary-fix-extend-the-whitelist-of-allowed-comman.patch + +- Removed: + * accounting-for-when-files-in-an-archive-contain-non-.patch + * add-engine-relaying-libvirt-events.patch + * add-other-attribute-to-gecos-fields-to-avoid-inconsi.patch + * add-support-for-python-3.7.patch + * align-suse-salt-master.service-limitnofiles-limit-wi.patch + * avoid-incomprehensive-message-if-crashes.patch + * change-stringio-import-in-python2-to-import-the-clas.patch + * decode-file-contents-for-python2-bsc-1102013.patch + * do-not-override-jid-on-returners-only-sending-back-t.patch + * don-t-error-on-retcode-0-in-libcrypto.openssl_init_c.patch + * feat-add-grain-for-all-fqdns.patch + * fix-async-call-to-process-manager.patch + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + * fix-deprecation-warning-bsc-1095507.patch + * fix-diffing-binary-files-in-file.get_diff-bsc-109839.patch + * fix-for-ec2-rate-limit-failures.patch + * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch + * fix-for-sorting-of-multi-version-packages-bsc-109717.patch + * fix-index-error-when-running-on-python-3.patch + * fix-latin1-encoding-problems-on-file-module-bsc-1116.patch + * fix-mine.get-not-returning-data-workaround-for-48020.patch + * fix-unboundlocalerror-in-file.get_diff.patch + * fixed-usage-of-ipaddress.patch + * fixing-issue-when-a-valid-token-is-generated-even-wh.patch + * get-os_family-for-rpm-distros-from-the-rpm-macros.-u.patch + * improved-handling-of-ldap-group-id.patch + * only-do-reverse-dns-lookup-on-ips-for-salt-ssh.patch + * option-to-merge-current-pillar-with-opts-pillar-duri.patch + * prepend-current-directory-when-path-is-just-filename.patch + * prevent-zypper-from-parsing-repo-configuration-from-.patch + * remove-old-hack-when-reporting-multiversion-packages.patch + * retire-md5-checksum-for-pkg-mgmt-plugins.patch + * show-recommendations-for-salt-ssh-cross-version-pyth.patch + * strip-trailing-commas-on-linux-user-gecos-fields.patch + * support-use-of-gce-instance-credentials-109.patch + * update-error-list-for-zypper.patch + * x509-fixes-for-remote-signing-106.patch + +- Modified: + * add-all_versions-parameter-to-include-all-installed-.patch + * add-cpe_name-for-osversion-grain-parsing-u-49946.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * add-hold-unhold-functions.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * azurefs-gracefully-handle-attributeerror.patch + * bugfix-any-unicode-string-of-length-16-will-raise-ty.patch + * debian-info_installed-compatibility-50453.patch + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + * fall-back-to-pymysql.patch + * fix-for-suse-expanded-support-detection.patch + * fix-git_pillar-merging-across-multiple-__env__-repos.patch + * fix-ipv6-scope-bsc-1108557.patch + * fix-issue-2068-test.patch + * fix-zypper.list_pkgs-to-be-aligned-with-pkg-state.patch + * fixes-cve-2018-15750-cve-2018-15751.patch + * get-os_arch-also-without-rpm-package-installed.patch + * integration-of-msi-authentication-with-azurearm-clou.patch + * loosen-azure-sdk-dependencies-in-azurearm-cloud-driv.patch + * remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + * x509-fixes-111.patch + * zypper-add-root-configuration-parameter.patch + +------------------------------------------------------------------- +Wed Jan 23 15:25:29 UTC 2019 - bo@suse.de + +- Add root parameter to Zypper module + +- Added: + * zypper-add-root-configuration-parameter.patch + +------------------------------------------------------------------- +Wed Jan 16 16:28:09 UTC 2019 - psuarezhernandez@suse.com + +- Do not restrict the Python version to < 3.7 + +------------------------------------------------------------------- +Tue Jan 15 09:47:12 UTC 2019 - bo@suse.de + +- Fix integration tests in state compiler (U#2068) + +- Added: + * fix-issue-2068-test.patch + +------------------------------------------------------------------- +Fri Jan 11 13:23:13 UTC 2019 - psuarezhernandez@suse.com + +- Fix "pkg.list_pkgs" output when using "attr" to take the arch into account (bsc#1114029) + +- Added: + * remove-arch-from-name-when-pkg.list_pkgs-is-called-w.patch + +------------------------------------------------------------------- +Thu Jan 10 12:52:09 UTC 2019 - mdinca + +- Fix powerpc null server_id_arch (bsc#1117995) + +- Added: + * return-the-expected-powerpc-os-arch-bsc-1117995.patch + +------------------------------------------------------------------- +Thu Jan 10 09:53:33 UTC 2019 - bo@suse.de + +- Fix module 'azure.storage' has no attribute '__version__' + (bsc#1121091) + +- Added: + * azurefs-gracefully-handle-attributeerror.patch + +------------------------------------------------------------------- +Fri Jan 4 13:29:50 UTC 2019 - bo@suse.de + +- Add supportconfig module and states for minions and SaltSSH + +- Added: + * add-supportconfig-module-for-remote-calls-and-saltss.patch + +------------------------------------------------------------------- +Thu Jan 3 16:35:30 UTC 2019 - bo@suse.de + +- Fix FIPS enabled RES clients (bsc#1099887) + +- Added: + * retire-md5-checksum-for-pkg-mgmt-plugins.patch + +------------------------------------------------------------------- +Thu Jan 3 15:48:20 UTC 2019 - bo@suse.de + +- Add hold/unhold functions. Fix Debian repo "signed-by". + +- Added: + * decide-if-the-source-should-be-actually-skipped.patch + * add-hold-unhold-functions.patch + +------------------------------------------------------------------- +Tue Dec 4 16:28:21 UTC 2018 - psuarezhernandez@suse.com + +- Fix latin1 encoding problems on file module (bsc#1116837) + +- Added: + * fix-latin1-encoding-problems-on-file-module-bsc-1116.patch + +------------------------------------------------------------------- +Fri Nov 30 13:14:19 UTC 2018 - bo@suse.de + +- Don't error on retcode 0 in libcrypto.OPENSSL_init_crypto + +- Added: + * don-t-error-on-retcode-0-in-libcrypto.openssl_init_c.patch + +------------------------------------------------------------------- +Tue Nov 20 15:33:39 UTC 2018 - bo@suse.de + +- Debian info_installed compatibility (U#50453) + +- Added: + * debian-info_installed-compatibility-50453.patch + +------------------------------------------------------------------- +Fri Nov 16 14:17:45 UTC 2018 - bo@suse.de + +- Add compatibility with other package modules for "list_repos" function +- Bugfix: unable to detect os arch when RPM is not installed (bsc#1114197) + +- Added: + * make-aptpkg.list_repos-compatible-on-enabled-disable.patch + * get-os_arch-also-without-rpm-package-installed.patch + +------------------------------------------------------------------- +Thu Nov 8 09:32:49 UTC 2018 - psuarezhernandez@suse.com + +- Fix git_pillar merging across multiple __env__ repositories (bsc#1112874) + +- Added: + * fix-git_pillar-merging-across-multiple-__env__-repos.patch + +------------------------------------------------------------------- +Wed Oct 31 14:52:31 UTC 2018 - bo@suse.de + +- Fix LDAP authentication issue when a valid token is generated + by the salt-api even when invalid user credentials are passed. + (U#48901) + +- Added: + * fixing-issue-when-a-valid-token-is-generated-even-wh.patch + +------------------------------------------------------------------- +Tue Oct 30 10:48:23 UTC 2018 - Jochen Breuer + +- Improved handling of LDAP group id. gid is no longer treated as a + string, which could have lead to faulty group creations. (bsc#1113784) + +- Added: + * improved-handling-of-ldap-group-id.patch + +------------------------------------------------------------------- +Thu Oct 25 13:04:42 UTC 2018 - psuarezhernandez@suse.com + +- Fix remote command execution and incorrect access control + when using salt-api. (bsc#1113699) (CVE-2018-15751) +- Fix Directory traversal vulnerability when using salt-api. + Allows an attacker to determine what files exist on + a server when querying /run or /events. (bsc#1113698) (CVE-2018-15750) + +- Added: + * fixes-cve-2018-15750-cve-2018-15751.patch + +------------------------------------------------------------------- +Thu Oct 18 13:17:33 UTC 2018 - bo@suse.de + +- Add multi-file support and globbing to the filetree (U#50018) + +- Added: + * add-multi-file-support-and-globbing-to-the-filetree-.patch + +------------------------------------------------------------------- +Wed Oct 17 15:21:17 UTC 2018 - bo@suse.de + +- Bugfix: supportconfig non-root permission issues (U#50095) + +- Added: + * support-config-non-root-permission-issues-fixes-u-50.patch + +------------------------------------------------------------------- +Wed Oct 17 14:18:09 UTC 2018 - bo@suse.de + +- Open profiles permissions to everyone for read-only + +------------------------------------------------------------------- +Tue Oct 16 15:26:16 UTC 2018 - bo@suse.de + +- Preserving signature in "module.run" state (U#50049) + +- Added: + * preserving-signature-in-module.run-state-u-50049.patch + +------------------------------------------------------------------- +Fri Oct 12 11:48:40 UTC 2018 - bo@suse.de + +- Install default salt-support profiles + +------------------------------------------------------------------- +Thu Oct 11 15:04:30 UTC 2018 - bo@suse.de + +- Fix unit tests due to merger failure +- Add CPE_NAME for osversion* grain parsing +- Get os_family for RPM distros from the RPM macros +- Install support profiles + +- Added: + * get-os_family-for-rpm-distros-from-the-rpm-macros.-u.patch + * add-cpe_name-for-osversion-grain-parsing-u-49946.patch + * make-profiles-a-package.patch + * fix-unit-test-for-grains-core.patch + +------------------------------------------------------------------- +Tue Oct 9 14:50:25 UTC 2018 - psuarezhernandez@suse.com + +- Bugfix: any unicode string of length 16 will raise TypeError + +- Added: + * bugfix-any-unicode-string-of-length-16-will-raise-ty.patch + +------------------------------------------------------------------- +Mon Oct 8 08:52:23 UTC 2018 - psuarezhernandez@suse.com + +- Fix async call to process manager (bsc#1110938) +- Early feature: Salt support-config (salt-support) + +- Added: + * fix-async-call-to-process-manager.patch + * early-feature-support-config.patch + +------------------------------------------------------------------- +Mon Oct 1 16:03:27 UTC 2018 - bo@suse.de + +- Fix IPv6 scope (bsc#1108557) + +- Added: + * fix-ipv6-scope-bsc-1108557.patch + +------------------------------------------------------------------- +Fri Sep 28 12:37:02 UTC 2018 - bo@suse.de + +- Handle zypper ZYPPER_EXIT_NO_REPOS exit code (bsc#1108834, bsc#1109893) + +- Added: + * update-error-list-for-zypper.patch + +------------------------------------------------------------------- +Mon Sep 24 15:49:47 UTC 2018 - bo@suse.de + +- Bugfix for pkg_resources crash (bsc#1104491) + +- Added: + * do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + +------------------------------------------------------------------- +Fri Sep 21 15:39:49 UTC 2018 - psuarezhernandez@suse.com + +- Fix loosen azure sdk dependencies in azurearm cloud driver (bsc#1107333) + +- Added: + * loosen-azure-sdk-dependencies-in-azurearm-cloud-driv.patch + +------------------------------------------------------------------- +Thu Sep 20 11:25:57 UTC 2018 - psuarezhernandez@suse.com + +- Fix broken "resolve_capabilities" on Python 3 (bsc#1108995) + +- Added: + * fix-index-error-when-running-on-python-3.patch + +------------------------------------------------------------------- +Wed Sep 19 13:06:21 UTC 2018 - psuarezhernandez@suse.com + +- Allow empty service_account_private_key in GCE driver (bsc#1108969) + +- Added: + * support-use-of-gce-instance-credentials-109.patch + +------------------------------------------------------------------- +Tue Sep 18 14:28:13 UTC 2018 - mihai.dinca@suse.com + +- Decode file contents for python2 (bsc#1103530) +- Check dmidecoder executable on each "smbios" call to avoid race condition (bsc#1101880) +- Fix pkg.upgrade reports when dealing with multiversion packages (bsc#1102265) +- Accounting for when files in an archive contain non-ascii characters (bsc#1099460) +- Fix deprecation warning (bsc#1095507) +- Fix (bsc#1065792) +- Fix (bsc#108758) +- Handle packages with multiple version properly with zypper (bsc#1096514) +- Fix file.get_diff regression in 2018.3 (bsc#1098394) +- Provide python version mismatch solutions (bsc#1072599) +- Fix file.managed binary file utf8 error (bsc#1098394) +- Prevent zypper from parsing repo configuration from not .repo files (bsc#1094055) +- Add environment variable to know if yum is invoked from Salt (bsc#1057635) +- Prevent deprecation warning with salt-ssh (bsc#1095507) +- Align SUSE salt-master.service 'LimitNOFILES' limit with upstream Salt +- Add 'other' attribute to GECOS fields to avoid inconsistencies with chfn +- Collect all versions of installed packages on SUSE and RHEL systems (bsc#1089526) + +------------------------------------------------------------------- +Mon Sep 17 13:47:09 UTC 2018 - bo@suse.de + +- Prepend current directory when path is just filename (bsc#1095942) +- Integration of MSI authentication for azurearm +- Adds fix for SUSE Expanded Support os grain detection +- Fixes 509x remote signing +- Fix for StringIO import in Python2 +- Use Adler32 algorithm to compute string checksums (bsc#1102819) +- Only do reverse DNS lookup on IPs for salt-ssh (bsc#1104154) +- Add support for Python 3.7 +- Fix license macro to build on SLE12SP2 +- Decode file contents for python2 (bsc#1102013) +- Fix for sorting of multi-version packages (bsc#1097174 and bsc#1097413) +- Fix mine.get not returning data - workaround for #48020 (bsc#1100142) + +- Added: + * change-stringio-import-in-python2-to-import-the-clas.patch + * integration-of-msi-authentication-with-azurearm-clou.patch + * x509-fixes-for-remote-signing-106.patch + * fix-for-suse-expanded-support-detection.patch + * only-do-reverse-dns-lookup-on-ips-for-salt-ssh.patch + * prepend-current-directory-when-path-is-just-filename.patch + * add-support-for-python-3.7.patch + * decode-file-contents-for-python2-bsc-1102013.patch + * fix-mine.get-not-returning-data-workaround-for-48020.patch + * x509-fixes-111.patch + * use-adler32-algorithm-to-compute-string-checksums.patch + +- Modified: + * fix-for-sorting-of-multi-version-packages-bsc-109717.patch + +------------------------------------------------------------------- +Mon Jul 30 10:42:01 UTC 2018 - mihai.dinca@suse.com + +- Update to 2018.3.2 + See https://docs.saltstack.com/en/latest/topics/releases/2018.3.2.html + for full changelog + +- Added: + * accounting-for-when-files-in-an-archive-contain-non-.patch + * add-all_versions-parameter-to-include-all-installed-.patch + * add-custom-suse-capabilities-as-grains.patch + * add-engine-relaying-libvirt-events.patch + * add-environment-variable-to-know-if-yum-is-invoked-f.patch + * add-other-attribute-to-gecos-fields-to-avoid-inconsi.patch + * align-suse-salt-master.service-limitnofiles-limit-wi.patch + * avoid-incomprehensive-message-if-crashes.patch + * fix-deprecation-warning-bsc-1095507.patch + * fix-diffing-binary-files-in-file.get_diff-bsc-109839.patch + * fix-unboundlocalerror-in-file.get_diff.patch + * fix-zypper.list_pkgs-to-be-aligned-with-pkg-state.patch + * prevent-zypper-from-parsing-repo-configuration-from-.patch + * remove-old-hack-when-reporting-multiversion-packages.patch + * show-recommendations-for-salt-ssh-cross-version-pyth.patch + +- Modified: + * activate-all-beacons-sources-config-pillar-grains.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * do-not-override-jid-on-returners-only-sending-back-t.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + * fall-back-to-pymysql.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + * fix-for-ec2-rate-limit-failures.patch + * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch + * fixed-usage-of-ipaddress.patch + * option-to-merge-current-pillar-with-opts-pillar-duri.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * run-salt-master-as-dedicated-salt-user.patch + * strip-trailing-commas-on-linux-user-gecos-fields.patch + +- Deleted: + * explore-module.run-response-to-catch-the-result-in-d.patch + * extra-filerefs-include-files-even-if-no-refs-in-stat.patch + * fix-cp.push-empty-file.patch + * fix-for-sorting-of-multi-version-packages-bsc-109717.patch + * fix-openscap-push.patch + * initialize-__context__-retcode-for-functions-handled.patch + * make-it-possible-to-use-login-pull-and-push-from-mod.patch + * move-log_file-option-to-changeable-defaults.patch + * provide-kwargs-to-pkg_resource.parse_targets-require.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + + +------------------------------------------------------------------- +Thu May 17 15:14:01 UTC 2018 - jbreuer@suse.de + +- Documentation refresh to 2018.3.0 + + +------------------------------------------------------------------- +Wed May 16 10:57:17 UTC 2018 - jbreuer@suse.de + +- No more AWS EC2 rate limitations in salt-cloud (bsc#1088888) +- MySQL returner now also allows to use Unix sockets (bsc#1091371) + +- Added: + * fix-for-ec2-rate-limit-failures.patch + * enable-passing-a-unix_socket-for-mysql-returners-bsc.patch + +------------------------------------------------------------------- +Thu May 10 09:12:24 UTC 2018 - psuarezhernandez@suse.com + +- Do not override jid on returners, only sending back to master (bsc#1092373) +- Fixes for salt-ssh: + - Option --extra-filerefs doesn't add all files to the state + archive + - Pillar completely overwritten (not merged) when doing + module.run + state.apply with pillar in kwargs +- Remove minion/thin/version if exists to force thin regeneration (bsc#1092161) + +- Added: + * extra-filerefs-include-files-even-if-no-refs-in-stat.patch + * do-not-override-jid-on-returners-only-sending-back-t.patch + * option-to-merge-current-pillar-with-opts-pillar-duri.patch + +------------------------------------------------------------------- +Fri May 4 12:17:07 UTC 2018 - jbreuer@suse.de + +- Fixed Python 3 issue with CIDR addresses. + +- Added: + * fixed-usage-of-ipaddress.patch + +------------------------------------------------------------------- +Wed Apr 25 14:50:36 UTC 2018 - psuarezhernandez@suse.com + +- Fix minion scheduler to return a 'retcode' attribute (bsc#1089112) +- Fix for logging during network interface querying (bsc#1087581) +- Fix rhel packages requires both net-tools and iproute (bsc#1087055) + +- Added: + * initialize-__context__-retcode-for-functions-handled.patch + +- Modified: + * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch + +------------------------------------------------------------------- +Wed Apr 18 17:09:41 UTC 2018 - psuarezhernandez@suse.com + +- Fix patchinstall on yum module. Bad comparison (bsc#1087278) + +- Added: + * provide-kwargs-to-pkg_resource.parse_targets-require.patch + +------------------------------------------------------------------- +Wed Apr 18 16:55:28 UTC 2018 - psuarezhernandez@suse.com + +- Strip trailing commas on Linux user's GECOS fields (bsc#1089362) +- Fallback to PyMySQL (bsc#1087891) +- Improved test for fqdns +- Update SaltSSH patch +- Fix for [Errno 0] Resolver Error 0 (no error) (bsc#1087581) + * Lintfix: PEP8 ident + * Use proper levels of the error handling, use proper log formatting. + * Fix unit test for reversed fqdns return data + +- Added: + * strip-trailing-commas-on-linux-user-gecos-fields.patch + * fall-back-to-pymysql.patch + * fix-for-errno-0-resolver-error-0-no-error-bsc-108758.patch + +- Modified: + * add-saltssh-multi-version-support-across-python-inte.patch + +------------------------------------------------------------------- +Fri Apr 6 16:58:59 UTC 2018 - mdinca@suse.de + +- Update to 2018.3.0 + + +- Modified: + * explore-module.run-response-to-catch-the-result-in-d.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * fix-openscap-push.patch + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + * fix-cp.push-empty-file.patch + * make-it-possible-to-use-login-pull-and-push-from-mod.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * run-salt-master-as-dedicated-salt-user.patch + * move-log_file-option-to-changeable-defaults.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +------------------------------------------------------------------- +Thu Apr 5 15:58:22 UTC 2018 - mdinca@suse.de + +- Add python-2.6 support to salt-ssh + +- Modified: + * add-saltssh-multi-version-support-across-python-inte.patch + +------------------------------------------------------------------- +Wed Apr 4 16:32:10 UTC 2018 - mdinca@suse.de + +- Update salt-ssh multiversion patch + +- Modified: + * add-saltssh-multi-version-support-across-python-inte.patch + +- Removed: + * require-same-major-version-while-minor-is-allowed-to.patch + +------------------------------------------------------------------- +Wed Mar 28 12:18:08 UTC 2018 - mdinca@suse.de + +- Add iprout/net-tools dependency + + +------------------------------------------------------------------- +Wed Mar 28 11:57:30 UTC 2018 - mc@suse.de + +- salt-ssh: require same major version while minor is allowed to be + +- Added: + * require-same-major-version-while-minor-is-allowed-to.patch + +- Modified: + * explore-module.run-response-to-catch-the-result-in-d.patch + * add-saltssh-multi-version-support-across-python-inte.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * fix-openscap-push.patch + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + * fix-cp.push-empty-file.patch + * make-it-possible-to-use-login-pull-and-push-from-mod.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * run-salt-master-as-dedicated-salt-user.patch + * move-log_file-option-to-changeable-defaults.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +------------------------------------------------------------------- +Tue Mar 27 16:29:08 UTC 2018 - mdinca@suse.de + +- Add SaltSSH multi-version support across Python interpeters. + +- Added: + * add-saltssh-multi-version-support-across-python-inte.patch + +------------------------------------------------------------------- +Fri Mar 23 18:12:09 UTC 2018 - mdinca@suse.de + +- Fix zypper.info_installed 'ascii' issue + +- Modified: + * explore-module.run-response-to-catch-the-result-in-d.patch + * fix-openscap-push.patch + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + * fix-cp.push-empty-file.patch + * make-it-possible-to-use-login-pull-and-push-from-mod.patch + * move-log_file-option-to-changeable-defaults.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +------------------------------------------------------------------- +Fri Mar 23 16:19:42 UTC 2018 - mdinca@suse.de + +- Update openscap push patch to include the test fixes + +- Modified: + * explore-module.run-response-to-catch-the-result-in-d.patch + * fix-openscap-push.patch + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + * fix-cp.push-empty-file.patch + * make-it-possible-to-use-login-pull-and-push-from-mod.patch + * move-log_file-option-to-changeable-defaults.patch + +------------------------------------------------------------------- +Thu Mar 22 14:40:50 UTC 2018 - psuarezhernandez@suse.com + +- Explore 'module.run' state module output in depth to catch "result" properly + +- Added: + * explore-module.run-response-to-catch-the-result-in-d.patch + +------------------------------------------------------------------- +Thu Mar 22 09:10:33 UTC 2018 - mc@suse.de + +- make it possible to use docker login, pull and push from module.run and detect errors +- Added: + * make-it-possible-to-use-login-pull-and-push-from-mod.patch + +------------------------------------------------------------------- +Tue Mar 20 19:15:38 UTC 2018 - michele.bologna@suse.com + +- Fix logging with FQDNs + +- Added: + * fix-decrease-loglevel-when-unable-to-resolve-addr.patch + +------------------------------------------------------------------- +Wed Mar 14 09:37:07 UTC 2018 - mdinca@suse.de + +- Update patches + +- Modified: + * run-salt-master-as-dedicated-salt-user.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * fix-openscap-push.patch + * fix-cp.push-empty-file.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * move-log_file-option-to-changeable-defaults.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +- Removed: + * salt-ssh-fix-json-load-of-return-data-when-it-contai.patch + +------------------------------------------------------------------- +Tue Mar 13 18:28:03 UTC 2018 - mdinca@suse.de + +- Update cp.push patch +- Modified: + * fix-cp.push-empty-file.patch + * salt-ssh-fix-json-load-of-return-data-when-it-contai.patch + +------------------------------------------------------------------- +Mon Mar 12 17:47:34 UTC 2018 - mc@suse.de + +- force re-generate a new thin.tgz when an update gets installed + +------------------------------------------------------------------- +Sat Mar 10 12:00:17 UTC 2018 - mc@suse.de + +- fix salt-ssh with a different patch + +- remove: dumps-should-return-unicode-also-with-py2-to-prevent.patch +- added: salt-ssh-fix-json-load-of-return-data-when-it-contai.patch + +- Added: + * salt-ssh-fix-json-load-of-return-data-when-it-contai.patch + +- Removed: + * dumps-should-return-unicode-also-with-py2-to-prevent.patch + +------------------------------------------------------------------- +Fri Mar 9 15:56:00 UTC 2018 - mdinca@suse.de + +- Fix unicode decode error with salt-ssh + +- Added: + * dumps-should-return-unicode-also-with-py2-to-prevent.patch + +- Modified: + * run-salt-master-as-dedicated-salt-user.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * fix-openscap-push.patch + * fix-cp.push-empty-file.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * move-log_file-option-to-changeable-defaults.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +------------------------------------------------------------------- +Thu Mar 8 11:20:01 UTC 2018 - mdinca@suse.de + +- Fix cp.push empty file (bsc#1075950) +- salt-ssh - move log_file option to changeable defaults + +- Added: + * fix-cp.push-empty-file.patch + * move-log_file-option-to-changeable-defaults.patch + +- Modified: + * run-salt-master-as-dedicated-salt-user.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * fix-openscap-push.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * activate-all-beacons-sources-config-pillar-grains.patch + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +------------------------------------------------------------------- +Fri Mar 2 13:15:46 UTC 2018 - mdinca@suse.de + +- Daily update + +- Added: + * fix-openscap-push.patch + +- Removed: + * fix-grains-with-n.patch + +------------------------------------------------------------------- +Fri Mar 2 13:15:46 UTC 2018 - mdinca@suse.de + +- Fix grains containing trailing "\n" + +- Added: + * fix-grains-with-n.patch + +------------------------------------------------------------------- +Tue Feb 20 10:47:34 UTC 2018 - mdinca@suse.de + +- Remove salt-minion python2 requirement when python3 is default (bsc#1081592) + +------------------------------------------------------------------- +Tue Feb 13 15:17:11 UTC 2018 - mdinca@suse.de + +- Remove-obsolete-unicode-handling-in-pkg.info_installed + +- Added: + * remove-obsolete-unicode-handling-in-pkg.info_install.patch + +------------------------------------------------------------------- +Fri Feb 09 15:39:08 UTC 2018 - mdinca@suse.de + +- Update to salt-2018.1.99 + +- Modified: + * activate-all-beacons-sources-config-pillar-grains.patch + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * feat-add-grain-for-all-fqdns.patch + * fix-bsc-1065792.patch + * list_pkgs-add-parameter-for-returned-attribute-selec.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + * run-salt-master-as-dedicated-salt-user.patch + +- Deleted: + * python3-compatibility-fix-got-bytes-instead-of-strin.patch + * enable-with-salt-version-parameter-for-setup.py-scri.patch + * catching-error-when-pidfile-cannot-be-deleted.patch + * bugfix-always-return-a-string-list-on-unknown-job-ta.patch + * bugfix-the-logic-according-to-the-exact-described-pu.patch + * cherrypy-read-reads-bytes-from-the-wire-and-write-th.patch + * fix-for-delete_deployment-in-kubernetes-module.patch + * fix-salt-master-for-old-psutil.patch + * introduce-process_count_max-minion-configuration-par.patch + * multiprocessing-minion-option-documentation-fixes.patch + * older-logrotate-need-su-directive.patch + * return-error-when-gid_from_name-and-group-does-not-e.patch + * set-shell-environment-variable-64.patch + * split-only-strings-if-they-are-such.patch + * use-home-to-get-the-user-home-directory-instead-usin.patch + * yumpkg-don-t-use-diff_attr-when-determining-install-.patch + +------------------------------------------------------------------- +Fri Feb 09 15:39:08 UTC 2018 - jbreuer@suse.de + +- Fix-epoch-handling-for-Rhel-6-and-7 +- Modified: + * yumpkg-don-t-use-diff_attr-when-determining-install-.patch + +- Removed: + * fix-for-wrong-version-processing.patch + +------------------------------------------------------------------- +Fri Feb 09 15:39:08 UTC 2018 - jbreuer@suse.de + +- Restoring-installation-of-packages-for-Rhel-6-7 +Added: +* yumpkg-don-t-use-diff_attr-when-determining-install-.patch + +------------------------------------------------------------------- +Wed Feb 07 13:23:51 UTC 2018 - mdinca@suse.de + +- Prevent queryformat pattern from expanding (bsc#1079048) + +------------------------------------------------------------------- +Thu Feb 01 12:38:12 UTC 2018 - jbreuer@suse.de + +- Fix epoch handling for Rhel 6 and 7 (bsc#1068566) +- Reverting to current API for split_input + +- Added: + * yumpkg-don-t-use-diff_attr-when-determining-install-.patch + +- Removed: + * fix-for-wrong-version-processing.patch + +------------------------------------------------------------------- +Fri Jan 26 13:54:50 UTC 2018 - pablo.suarezhernandez@suse.com + +- Fix for wrong version processing during yum pkg install (bsc#1068566) +- Feat: add grain for all FQDNs (bsc#1063419) + +- Added: + * fix-for-wrong-version-processing.patch + * feat-add-grain-for-all-fqdns.patch + +------------------------------------------------------------------- +Thu Jan 25 13:04:24 UTC 2018 - pablo.suarezhernandez@suse.com + +- Fix the usage of custom macros on the spec file. + +------------------------------------------------------------------- +Thu Jan 25 10:49:53 UTC 2018 - bmaryniuk@suse.com + +- Fix RES7: different dependency names for python-PyYAML + and python-MarkupSafe + +------------------------------------------------------------------- +Wed Jan 24 15:04:42 UTC 2018 - bmaryniuk@suse.com + +- Build both python2 and python3 binaries together. + +------------------------------------------------------------------- +Wed Jan 17 11:26:54 UTC 2018 - pablo.suarezhernandez@suse.com + +- Bugfix: errors in external pillar causes crash instead of report + of them (bsc#1068446) +- Fix 'user.present' when 'gid_from_name' is set but group does + not exist. + +- Added: + * bugfix-the-logic-according-to-the-exact-described-pu.patch + * return-error-when-gid_from_name-and-group-does-not-e.patch + +------------------------------------------------------------------- +Thu Jan 11 15:57:11 UTC 2018 - jrenner@suse.com + +- Fix "No service execution module loaded" issue (bsc#1065792) +- Set SHELL environment variable + + Added: + * fix-bsc-1065792.patch + * set-shell-environment-variable-64.patch + +------------------------------------------------------------------- +Fri Jan 05 15:24:30 UTC 2018 - jbreuer@suse.de + +- Removed unnecessary logging on shutdown (bsc#1050003) +- Renamed patch that adds grain fqdns + + Changed: + * catching-error-when-pidfile-cannot-be-deleted + + Removed: + * fix-for-pidfile-removal-logging + + Renamed: + * add-fqdns-grains -> feat-add-grain-for-all-fqdns + +------------------------------------------------------------------- +Thu Dec 28 10:37:10 UTC 2017 - michele.bologna@suse.com + +- Add fqdns to grains (bsc#1063419) + Added: + * add-fqdns-grains.patch + +------------------------------------------------------------------- +Tue Dec 19 16:15:14 UTC 2017 - mc@suse.com + +- Fixing cherrypy websocket with python3 + Added: + * python3-compatibility-fix-got-bytes-instead-of-strin.patch + +------------------------------------------------------------------- +Mon Dec 18 15:28:26 UTC 2017 - mihai.dinca@suse.com + +- Various-bug-fixes +- Python3 bugfix for cherrypy read() +- Fix for logging on salt-master exit in rare cases (pid-file removal) +- Added: + * cherrypy-read-reads-bytes-from-the-wire-and-write-th.patch + * fix-for-pidfile-removal-logging.patch + * split-only-strings-if-they-are-such.patch + +------------------------------------------------------------------- +Thu Dec 14 09:18:02 UTC 2017 - mihai.dinca@suse.com + +- Fix salt-master for old psutil version +- Added: + * fix-salt-master-for-old-psutil.patch + +------------------------------------------------------------------- +Wed Dec 13 16:23:38 UTC 2017 - mihai.dinca@suse.com + +- Put back accidentally removed patches +- Added: + * avoid-excessive-syslogging-by-watchdog-cronjob-58.patch + * catching-error-when-pidfile-cannot-be-deleted.patch + +------------------------------------------------------------------- +Wed Dec 13 16:22:25 UTC 2017 - mihai.dinca@suse.com + +- Fix for delete_deployment in Kubernetes module (bsc#1059291) + +- Added: + * fix-for-delete_deployment-in-kubernetes-module.patch + +------------------------------------------------------------------- +Wed Dec 13 16:15:53 UTC 2017 - mihai.dinca@suse.com + +- Older logrotate need su directive (bsc#1071322) + * Added: + older-logrotate-need-su-directive.patch + +------------------------------------------------------------------- +Wed Dec 13 16:10:32 UTC 2017 - mihai.dinca@suse.com + +- Fix bsc#1041993 already included in 2017.7.2 +- Removed: + * removes-beacon-configuration-deprecation-warning-48.patch + +------------------------------------------------------------------- +Wed Dec 13 16:09:41 UTC 2017 - mihai.dinca@suse.com + +- Fixed beacons failure when pillar-based suppressing config-based. + (bsc#1060230) + +- Added: + * activate-all-beacons-sources-config-pillar-grains.patch + +------------------------------------------------------------------- +Thu Dec 7 11:19:12 UTC 2017 - dimstar@opensuse.org + +- Escape the usage of %{VERSION} when calling out to rpm. + RPM 4.14 has %{VERSION} defined as 'the main packages version'. + +------------------------------------------------------------------- +Fri Dec 1 15:33:01 UTC 2017 - mihai.dinca@suse.com + +- Fix wrong version reported by Salt (bsc#1061407) +- Fix CVE-2017-14696 (bsc#1062464) already included in 2017.7.2 + +------------------------------------------------------------------- +Mon Nov 27 17:13:03 UTC 2017 - mihai.dinca@suse.com + +- Run salt master as dedicated salt user +- Run salt-api as user salt (bsc#1064520) + +- Added: + * run-salt-master-as-dedicated-salt-user.patch + * run-salt-api-as-user-salt-bsc-1064520.patch + +------------------------------------------------------------------- +Fri Nov 9 10:22:08 UTC 2017 - mdinca@suse.de + +- Update to 2017.7.2 + See https://docs.saltstack.com/en/latest/topics/releases/2017.7.2.html + +- Added: + * enable-with-salt-version-parameter-for-setup.py-scri.patch + +- Removed: + * add-a-salt-minion-service-control-file.patch + * add-clean_id-function-to-salt.utils.verify.py.patch + * add-options-for-dockerng.patch + * add-ssh-option-to-salt-ssh.patch + * add-unit-test-for-skip-false-values-from-preferred_i.patch + * add-yum-plugin.patch + * add-zypp-notify-plugin.patch + * adding-salt-minion-watchdog-for-sysv-systems-rhel6-a.patch + * adding-support-for-installing-patches-in-yum-dnf-exe.patch + * avoid-failures-on-sles-12-sp2-because-of-new-systemd.patch + * bugfix-jobs-scheduled-to-run-at-a-future-time-stay-p.patch + * bugfix-unable-to-use-127-as-hostname.patch + * catching-error-when-pidfile-cannot-be-deleted.patch + * change-travis-configuration-file-to-use-salt-toaster.patch + * check-if-byte-strings-are-properly-encoded-in-utf-8.patch + * clean-up-change-attribute-from-interface-dict.patch + * do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch + * enables-salt-minion-watchdog-on-init.d-script-for-sy.patch + * escape-the-os.sep.patch + * fix-for-delete_deployment-in-kubernetes-module.patch + * fix-format-error-bsc-1043111.patch + * fix-grain-for-os_family-on-suse-series.patch + * fix-os_family-case-in-unittest.patch + * fix-regression-in-file.get_managed-add-unit-tests.patch + * fix-salt-summary-to-count-not-responding-minions-cor.patch + * fix-setting-language-on-suse-systems.patch + * fixed-issue-with-parsing-of-master-minion-returns-wh.patch + * fixing-beacons.list-integration-test-failure.patch + * introducing-the-kubernetes-module.patch + * notify-systemd-synchronously-bsc-1053376.patch + * rest_cherrypy-remove-sleep-call.patch + * revert-we-don-t-have-python-systemd-so-notify-can-t-.patch + * run-salt-api-as-user-salt-bsc-990029.patch + * run-salt-master-as-dedicated-salt-user.patch + * search-the-entire-cache_dir-because-storage-paths-ch.patch + * security-fixes-cve-2017-14695-and-cve-2017-14696.patch + * setting-up-os-grains-for-sles-expanded-support-suse-.patch + * special-salt-minion.service-file-for-rhel7.patch + * translate-variable-arguments-if-they-contain-hidden-.patch + * tserong-suse.com-we-don-t-have-python-systemd-so-not.patch + * use-correct-grain-constants-for-timezone.patch + +------------------------------------------------------------------- +Wed Oct 11 12:47:37 UTC 2017 - jbreuer@suse.de + +- Re-added previously removed unit-test for bsc#1050003 + +- Changed: + * catching-error-when-pidfile-cannot-be-deleted.patch + +------------------------------------------------------------------- +Wed Oct 11 10:02:25 UTC 2017 - bmaryniuk@suse.com + +- Fixes for CVE-2017-14695 and CVE-2017-14696 (bsc#1062462) +- Added: + * security-fixes-cve-2017-14695-and-cve-2017-14696.patch + +------------------------------------------------------------------- +Tue Oct 10 14:41:13 UTC 2017 - bmaryniuk@suse.com + +- Add missing follow-up for CVE-2017-12791 (bsc#1053955) +- Fixed salt target-type field returns "String" for existing jids + but an empty "Array" for non existing jids. (issue#1711) +- Added: + * bugfix-always-return-a-string-list-on-unknown-job-ta.patch + * escape-the-os.sep.patch + +------------------------------------------------------------------- +Mon Oct 9 14:08:46 UTC 2017 - bmaryniuk@suse.com + +- Fixed minion resource exhaustion when many functions are being + executed in parallel (bsc#1059758) + +- Changed: + * catching-error-when-pidfile-cannot-be-deleted.patch + +- Added: + * introduce-process_count_max-minion-configuration-par.patch + * multiprocessing-minion-option-documentation-fixes.patch + * revert-we-don-t-have-python-systemd-so-notify-can-t-.patch + +- Removed: + * revert-we-don-t-have-python-systemd-so-notify-can-t-work.patch + +------------------------------------------------------------------- +Thu Oct 5 15:27:55 UTC 2017 - pablo.suarezhernandez@suse.com + +- Remove 'TasksTask' attribute from salt-master.service in older + versions of systemd (bsc#985112) + +------------------------------------------------------------------- +Wed Sep 27 15:45:41 UTC 2017 - jbreuer@suse.de + +- Fix for delete_deployment in Kubernetes module (bsc#1059291) + +- Added: + * fix-for-delete_deployment-in-kubernetes-module.patch + +------------------------------------------------------------------- +Mon Sep 18 08:51:35 UTC 2017 - jbreuer@suse.de + +- Catching error when PIDfile cannot be deleted (bsc#1050003) + +- Added: + * catching-error-when-pidfile-cannot-be-deleted.patch + +------------------------------------------------------------------- +Tue Sep 12 08:46:47 UTC 2017 - pablo.suarezhernandez@suse.com + +- Use $HOME to get the user home directory instead using '~' char (bsc#1042749) + +- Added: + * use-home-to-get-the-user-home-directory-instead-usin.patch + +------------------------------------------------------------------- +Fri Sep 8 11:57:22 UTC 2017 - jbreuer@suse.de + +- Fixed patches for Kubernetes and YUM modules + +- Updated: + * list_pkgs-add-parameter-for-returned-attribute-selec.patch + * introducing-the-kubernetes-module.patch + +------------------------------------------------------------------- +Tue Sep 5 13:31:02 UTC 2017 - jbreuer@suse.de + +- Add patches to salt to support SUSE Manager scalability features + (bsc#1052264) + +- Added: + * list_pkgs-add-parameter-for-returned-attribute-selec.patch + +------------------------------------------------------------------- +Thu Aug 31 14:31:31 UTC 2017 - jbreuer@suse.de + +- Introducing the kubernetes module (bsc#1051948) + +- Added: + * introducing-the-kubernetes-module.patch + +------------------------------------------------------------------- +Wed Aug 30 15:46:20 UTC 2017 - jrenner@suse.com + +- Revert "We don't have python-systemd, so notify can't work" + +- Added: + * revert-we-don-t-have-python-systemd-so-notify-can-t-work.patch + +------------------------------------------------------------------- +Wed Aug 23 12:48:37 UTC 2017 - brejoc@gmail.com + +- Notify systemd synchronously via NOTIFY_SOCKET (bsc#1053376) + +- Added: + * notify-systemd-synchronously-bsc-1053376.patch + +------------------------------------------------------------------- +Wed Aug 16 09:54:15 UTC 2017 - pablo.suarezhernandez@suse.com + +- Add clean_id function to salt.utils.verify.py + (CVE-2017-12791, bsc#1053955) + +- Added: + * add-clean_id-function-to-salt.utils.verify.py.patch + +------------------------------------------------------------------- +Mon Jul 17 13:51:35 UTC 2017 - bmaryniuk@suse.com + +- Added bugfix when jobs scheduled to run at a future time stay + pending for Salt minions (bsc#1036125) + +- Added: + * bugfix-jobs-scheduled-to-run-at-a-future-time-stay-p.patch + +------------------------------------------------------------------- +Tue Jun 20 08:50:18 UTC 2017 - pablo.suarezhernandez@suse.com + +- Adding procps as dependency. This provides "ps" and "pgrep" utils + which are called from different Salt modules and also from new + salt-minion watchdog. + +------------------------------------------------------------------- +Thu Jun 8 15:27:06 UTC 2017 - pablo.suarezhernandez@suse.com + +- Adding a salt-minion watchdog for RHEL6 and SLES11 systems (sysV) + to restart salt-minion in case of crashes during upgrade. + +- Added: + * adding-salt-minion-watchdog-for-sysv-systems-rhel6-a.patch + * enables-salt-minion-watchdog-on-init.d-script-for-sy.patch + +------------------------------------------------------------------- +Wed Jun 7 11:38:39 UTC 2017 - mc@suse.com + +- fix format error (bsc#1043111) + +------------------------------------------------------------------- +Wed Jun 7 08:24:15 UTC 2017 - mc@suse.com + +- fix ownership for whole master cache directory (bsc#1035914) + +------------------------------------------------------------------- +Tue Jun 6 15:32:01 UTC 2017 - bmaryniuk@suse.com + +- Bugfix: clean up `change` attribute from interface dict (upstream) + Issue: https://github.com/saltstack/salt/issues/41461 + PR: 1. https://github.com/saltstack/salt/pull/41487 + 2. https://github.com/saltstack/salt/pull/41533 + +Added: + * clean-up-change-attribute-from-interface-dict.patch + +------------------------------------------------------------------- +Mon May 29 12:34:52 UTC 2017 - bmaryniuk@suse.com + +- Disable 3rd party runtime packages to be explicitly recommended. + (bsc#1040886) + +------------------------------------------------------------------- +Mon May 22 13:27:06 UTC 2017 - bmaryniuk@suse.com + +- Bugfix: orchestrate and batches returns false failed information + https://github.com/saltstack/salt/issues/40635 + +Added: + * fixed-issue-with-parsing-of-master-minion-returns-wh.patch + +------------------------------------------------------------------- +Sun May 21 09:06:45 UTC 2017 - mc@suse.com + +- speed-up cherrypy by removing sleep call + +------------------------------------------------------------------- +Fri May 19 12:09:26 UTC 2017 - mc@suse.com + +- wrong os_family grains on SUSE - fix unittests (bsc#1038855) + +------------------------------------------------------------------- +Thu May 18 17:51:48 UTC 2017 - mc@suse.com + +- fix setting the language on SUSE systems (bsc#1038855) + +------------------------------------------------------------------- +Wed May 17 13:29:34 UTC 2017 - bmaryniuk@suse.com + +- Documentation refresh to 2016.11.4 + +------------------------------------------------------------------- +Wed May 17 12:05:08 UTC 2017 - bmaryniuk@suse.com + +- Update to 2016.11.4 + See https://docs.saltstack.com/en/develop/topics/releases/2016.11.4.html + See https://docs.saltstack.com/en/develop/topics/releases/2016.11.3.html + See https://docs.saltstack.com/en/develop/topics/releases/2016.11.2.html + See https://docs.saltstack.com/en/develop/topics/releases/2016.11.1.html + for full changelog + +- Use SUSE specific salt-api.service (bsc#1039370) +- Bugfix: wrong os_family grains on SUSE (bsc#1038855) +- Bugfix: unable to use hostname for minion ID as '127' (upstream) +- Fix core grains constants for timezone (bsc#1032931) +- Add unit test for a skip false values from preferred IPs + upstream patch +- Adding "yum-plugin-security" as required for RHEL 6 +- Minor fixes on new pkg.list_downloaded +- Listing all type of advisory patches for Yum module +- Prevents zero length error on Python 2.6 +- Fixes zypper test error after backporting +- raet protocol is no longer supported (bsc#1020831) +- Fix: move SSH data to the new home (bsc#1027722) +- Fix: /var/log/salt/minion fails logrotate (bsc#1030009) +- Fix: Result of master_tops extension is mutually overwritten + (bsc#1030073) +- Allows to set 'timeout' and 'gather_job_timeout' via kwargs +- Allows to set custom timeouts for 'manage.up' and 'manage.status' +- Use salt's ordereddict for comparison (fixes failing tests) +- add special salt-minion.service file for RES7 +- fix scripts for salt-proxy +- define with systemd for fedora and rhel >= 7 (bsc#1027240) +- add openscap module +- file.get_managed regression fix (upstream issues #39762) +- fix translate variable arguments if they contain hidden keywords + (bsc#1025896) +- fix service handling for openSUSE +- added unit test for dockerng.sls_build dryrun +- added dryrun to dockerng.sls_build +- update dockerng minimal version requirements +- fix format error in error parsing +- keep fix for migrating salt home directory (bsc#1022562) +- Fix salt pkg.latest raises exception if package is + not available (bsc#1012999) +- Fix timezone: should be always in UTC (bsc#1017078) +- Fix timezone handling for rpm installtime (bsc#1017078) +- Increasing timeouts for running integrations tests +- Add buildargs option to dockerng.build module +- Disable custom rosters for Salt SSH via Salt API (bsc#1011800) + More: https://github.com/saltstack/salt/pull/38596 +- Fix error when missing ssh-option parameter +- readd yum notify plugin +- all kwargs to dockerng.create to provide all features to sls_build + as well +- rename patches to get rid of the prefix numbers + +- Added: + * bugfix-unable-to-use-127-as-hostname.patch + * fix-grain-for-os_family-on-suse-series.patch + * use-correct-grain-constants-for-timezone.patch + * search-the-entire-cache_dir-because-storage-paths-ch.patch + * add-unit-test-for-skip-false-values-from-preferred_i.patch + * add-a-salt-minion-service-control-file.patch + * add-options-for-dockerng.patch + * add-zypp-notify-plugin.patch + * fixing-beacons.list-integration-test-failure.patch + * fix-regression-in-file.get_managed-add-unit-tests.patch + * fix-salt-summary-to-count-not-responding-minions-cor.patch + * special-salt-minion.service-file-for-rhel7.patch + * translate-variable-arguments-if-they-contain-hidden-.patch + +- Renamed: + * 0001-tserong-suse.com-We-don-t-have-python-systemd-so-not.patch + => tserong-suse.com-we-don-t-have-python-systemd-so-not.patch + * 0002-Run-salt-master-as-dedicated-salt-user.patch + => run-salt-master-as-dedicated-salt-user.patch + * 0003-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch + => check-if-byte-strings-are-properly-encoded-in-utf-8.patch + * 0004-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch + => do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch + * 0009-Add-YUM-plugin.patch 0027-Add-YUM-plugin.patch + => add-yum-plugin.patch + * 0012-Run-salt-api-as-user-salt-bsc-990029.patch + => run-salt-api-as-user-salt-bsc-990029.patch + * 0020-Setting-up-OS-grains-for-SLES-Expanded-Support-SUSE-.patch + => setting-up-os-grains-for-sles-expanded-support-suse-.patch + * 0022-Change-travis-configuration-file-to-use-salt-toaster.patch + => change-travis-configuration-file-to-use-salt-toaster.patch + * 0036-Avoid-failures-on-SLES-12-SP2-because-of-new-systemd.patch + => avoid-failures-on-sles-12-sp2-because-of-new-systemd.patch + * 0042-Salt-ssh-ssh-option-param.patch + => add-ssh-option-to-salt-ssh.patch + * 0057-Adding-support-for-installing-patches-in-yum-dnf-exe.patch + => adding-support-for-installing-patches-in-yum-dnf-exe.patch + +- Removed: + * 0005-Use-SHA256-hash-type-by-default.patch + * 0006-Create-salt-proxy-instantiated-service-file.patch + * 0007-Add-SUSE-Manager-plugin.patch + * 0008-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch + * 0009-Rewrite-minion-ID-generator-bsc-967803.patch + * 0010-snapper-execution-module.patch + * 0011-Fix-module-import-being-Py3-and-P2.6-compatible.patch + * 0013-Bugfix-prevent-crash-if-python-dbus-module-is-instal.patch + * 0014-Fix-some-unittests.patch + * 0015-Check-for-single-quote-before-splitting-on-single-qu.patch + * 0016-Unit-test-fixes-for-2015.8.7.patch + * 0017-Fix-snapper_test-for-python26.patch + * 0018-Integration-tests-fixes-for-2015.8.7.patch + * 0019-Generate-Salt-Thin-with-configured-extra-modules.patch + * 0021-acl.delfacl-fix-position-of-X-option-to-setfacl.patch + * 0023-Adding-dist-upgrade-support-to-zypper-module.patch + * 0024-Fix-open-ssh-bsc-1004723-upstream-issue-36966.patch + * 0025-Including-resolver-params-for-Zypper-debug-solver.patch + * 0026-fix-status-handling-in-sysv-init-scripts.patch + * 0028-change-TIMEZONE-on-SUSE-systems-bsc-1008933.patch + * 0029-Do-not-include-gpg-pubkey-packages-filtering-by-thei.patch + * 0030-Extract-archive-into-existing-directory-add-overwrit.patch + * 0031-Port-rsync-state-from-2016.3.patch + * 0032-Support-remote-port-forwarding-with-salt-ssh.patch + * 0033-Add-master_tops-support-in-salt-ssh.patch + * 0034-Fix-pkg.latest_version-when-latest-already-installed.patch + * 0035-salt-api-service-must-be-from-type-simple.patch + * 0037-salt-minion-service-back-to-type-simple.patch + * 0038-Successfully-exit-of-salt-api-child-processes-when-S.patch + * 0039-Re-introducing-KillMode-process-for-salt-minion-syst.patch + * 0040-Adding-Restart-on-failure-for-salt-minion-systemd-se.patch + * 0041-add-try-restart-to-fix-autorestarting-on-SUSE-system.patch + * 0043-Fixes-wrong-enabled-opts-for-yumnotify-plugin.patch + * 0044-Add-general-sanitisers.patch + * 0045-Fix-timezone-handling-for-rpm-installtime-bsc-101707.patch + * 0046-Snapper-module-improvements.patch + * 0047-Fix-issue-with-cp.push-36136.patch + * 0048-Prevents-OSError-exception-in-case-path-doesn-t-exis.patch + * 0049-OpenSCAP-module.patch + * 0050-Fix-service-state-returning-stacktrace-bsc-1027044.patch + * 0051-Allows-to-set-timeout-and-gather_job_timeout-via-kwa.patch + * 0052-Don-t-send-passwords-after-shim-delimiter-is-found-3.patch + * 0053-fix-race-condition-on-cache-directory-creation.patch + * 0054-Merge-output-from-master_tops.patch + * 0055-Adding-downloadonly-support-to-yum-dnf-module.patch + * 0056-Makes-sure-gather_job_timeout-is-an-integer.patch + * 0058-Adds-custom-timeout-and-gather_job_timeout-to-local_.patch + * 0059-Add-SHELL-env-var-for-the-salt-api.service.patch + * 0060-Fix-logrotate-for-minion-bsc-1030009-21.patch + +------------------------------------------------------------------- +Thu May 11 09:06:25 UTC 2017 - bmaryniuk@suse.com + +- Bugfix: datetime should be returned always in UTC + +- Added: + * 0063-Bugfix-datetime-should-be-returned-always-in-UTC.patch +------------------------------------------------------------------- +Mon May 8 14:05:35 UTC 2017 - bmaryniuk@suse.com + +- Bugfix: scheduled state may cause crash while deserialising data + on infinite recursion. (bsc#1036125) + +- Added: + * 0062-Bugfix-deserialising-crashes-in-the-recursive-loop-b.patch + +------------------------------------------------------------------- +Tue May 2 11:37:19 UTC 2017 - pablo.suarezhernandez@suse.com + +- Enable yum to handle errata on RHEL 6: require yum-plugin-security + +------------------------------------------------------------------- +Wed Apr 26 13:59:06 UTC 2017 - pablo.suarezhernandez@suse.com + +- Minor fixes on new pkg.list_downloaded +- Listing all type of advisory patches for Yum module +- Prevents zero length error on Python 2.6 +- Fixes zypper test error after backporting + +- Added: + * 0061-Search-the-entire-CACHE_DIR-because-storage-paths-ch.patch + +- Modified: + * 0057-Adding-support-for-installing-patches-in-yum-dnf-exe.patch + +------------------------------------------------------------------- +Mon Apr 17 16:00:43 UTC 2017 - pablo.suarezhernandez@suse.com + +- Refactoring on Zypper and Yum execution and state modules to allow + installation of patches/errata. + +- Added: + * 0057-Adding-support-for-installing-patches-in-yum-dnf-exe.patch +- Removed: + * 0057-Allows-using-downloadonly-in-a-pkg.installed-state.patch + +------------------------------------------------------------------- +Wed Apr 12 14:05:52 UTC 2017 - bmaryniuk@suse.com + +- Fix log rotation permission issue (bsc#1030009) +- Use pkg/suse/salt-api.service by this package + +- Removed: + * 0059-Set-salt-api-shell-env.patch + +- Added: + * 0059-Add-SHELL-env-var-for-the-salt-api.service.patch + * 0060-Fix-logrotate-for-minion-bsc-1030009-21.patch + +------------------------------------------------------------------- +Mon Apr 11 11:50:51 UTC 2017 - malbu@suse.com + +- Patch to set SHELL env variable for the salt-api.service. + Needed for salt-ssh ProxyCommand to work properly. +- Added: + * 0059-Set-salt-api-shell-env.patch + +------------------------------------------------------------------- +Mon Apr 10 10:00:51 UTC 2017 - pablo.suarezhernandez@suse.com + +- Fixes 'timeout' and 'gather_job_timeout' kwargs parameters + for 'local_batch' client + +- Added: + * 0058-Adds-custom-timeout-and-gather_job_timeout-to-local_.patch + +------------------------------------------------------------------- +Fri Apr 7 11:15:35 UTC 2017 - bmaryniuk@suse.com + +- Add missing bootstrap script for Salt Cloud (bsc#1032452) + +------------------------------------------------------------------- +Tue Apr 4 14:05:40 UTC 2017 - bmaryniuk@suse.com + +- Fix: add missing /var/cache/salt/cloud directory (bsc#1032213) + +------------------------------------------------------------------- +Fri Mar 31 11:14:39 UTC 2017 - bmaryniuk@suse.com + +- Added test case for race conditions on cache directory creation +- Modified: + * 0053-fix-race-condition-on-cache-directory-creation.patch + +------------------------------------------------------------------- +Thu Mar 30 15:06:28 UTC 2017 - pablo.suarezhernandez@suse.com + +- Adding "pkg.install downloadonly=True" support to yum/dnf + execution module +- Makes sure "gather_job_timeout" is an Integer +- Adding "pkg.downloaded" state and support for installing + patches/erratas + +- Added: + * 0055-Adding-downloadonly-support-to-yum-dnf-module.patch + * 0056-Makes-sure-gather_job_timeout-is-an-integer.patch + * 0057-Allows-using-downloadonly-in-a-pkg.installed-state.patch + +------------------------------------------------------------------- +Wed Mar 29 13:45:52 UTC 2017 - bmaryniuk@suse.com + +- Fix: merge master_tops output + +------------------------------------------------------------------- +Wed Mar 29 06:54:31 UTC 2017 - moio@suse.com + +- Fix: race condition on cache directory creation + +- Added: + * 0053-fix-race-condition-on-cache-directory-creation.patch + +------------------------------------------------------------------- +Fri Mar 24 09:32:03 UTC 2017 - bmaryniuk@suse.com + +- Cleanup salt user environment preparation (bsc#1027722) + +------------------------------------------------------------------- +Wed Mar 22 17:04:01 UTC 2017 - pkazmierczak@suse.com + +- Don't send passwords after shim delimiter is found (bsc#1019386) +- Add: + * 0052-Don-t-send-passwords-after-shim-delimiter-is-found-3.patch + +------------------------------------------------------------------- +Thu Mar 16 17:33:12 UTC 2017 - pablo.suarezhernandez@suse.com + +- Allows to set 'timeout' and 'gather_job_timeout' via kwargs +- Allows to set custom timeouts for 'manage.up' and 'manage.status' +- Add: + * 0051-Allows-to-set-timeout-and-gather_job_timeout-via-kwa.patch + +------------------------------------------------------------------- +Tue Mar 7 16:23:52 UTC 2017 - mihai.dinca@suse.com + +- Update systemd module unit tests (Update patch 0050) + +------------------------------------------------------------------- +Sun Mar 5 13:48:56 UTC 2017 - mc@suse.com + +- define with system for fedora and rhel 7 (bsc#1027240) + +------------------------------------------------------------------- +Fri Mar 3 15:30:33 UTC 2017 - mihai.dinca@suse.com + +- Fix service state returning stacktrace (bsc#1027044) +- Add: + * 0050-Fix-service-state-returning-stacktrace-bsc-1027044.patch + +------------------------------------------------------------------- +Tue Feb 21 08:17:41 UTC 2017 - mihai.dinca@suse.com + +- Update OpenSCAP Module patch + +------------------------------------------------------------------- +Fri Feb 17 13:05:13 UTC 2017 - mihai.dinca@suse.com + +- OpenSCAP Module +- Added: + * 0049-OpenSCAP-module.patch + +------------------------------------------------------------------- +Wed Feb 15 10:50:37 UTC 2017 - pablo.suarezhernandez@suse.com + +- Prevents 'OSError' exception in case certain job cache path + doesn't exist (bsc#1023535) +- Added: + * 0048-Prevents-OSError-exception-in-case-path-doesn-t-exis.patch + +------------------------------------------------------------------- +Mon Feb 13 16:24:05 UTC 2017 - mihai.dinca@suse.com + +- Backport: Fix issue with cp.push (#36136) +- Add: + * 0047-Fix-issue-with-cp.push-36136.patch + +------------------------------------------------------------------- +Tue Feb 7 14:38:10 UTC 2017 - bmaryniuk@suse.com + +- Fix salt-minion update on RHEL (bsc#1022841) + +------------------------------------------------------------------- +Mon Feb 6 12:08:17 UTC 2017 - pablo.suarezhernandez@suse.com + +- Adding new functions to Snapper execution module. +- Added: + * snapper-module-improvements.patch + +------------------------------------------------------------------- +Thu Jan 26 13:41:03 UTC 2017 - bmaryniuk@suse.com + +- Fix invalid chars allowed for data IDs (bsc#1011304) + Fix timezone: should be always in UTC (bsc#1017078) + Add: + * 0044-Add-general-sanitisers.patch + * 0045-Fix-timezone-handling-for-rpm-installtime-bsc-101707.patch + +------------------------------------------------------------------- +Wed Jan 25 11:07:30 UTC 2017 - pablo.suarezhernandez@suse.com + +- Fixes wrong "enabled" opts for yumnotify plugin + Add: + * 0043-Fixes-wrong-enabled-opts-for-yumnotify-plugin.patch + +------------------------------------------------------------------- +Mon Jan 2 16:47:37 UTC 2017 - malbu@suse.com + +- ssh-option parameter for salt-ssh command. + Added: + * 0042-Salt-ssh-ssh-option-param.patch + +------------------------------------------------------------------- +Tue Dec 20 17:48:48 CET 2016 - mc@suse.de + +- minion should pre-require salt +- do not restart salt-minion in the salt package +- add try-restart to sys-v init scripts + Add: + * 0041-add-try-restart-to-fix-autorestarting-on-SUSE-system.patch + +------------------------------------------------------------------- +Tue Dec 20 09:41:27 UTC 2016 - pablo.suarezhernandez@suse.com + +- Adding "Restart=on-failure" for salt-minion systemd service + Add: + * 0040-Adding-Restart-on-failure-for-salt-minion-systemd-se.patch + +------------------------------------------------------------------- +Mon Dec 19 15:25:17 UTC 2016 - pablo.suarezhernandez@suse.com + +- Re-introducing "KillMode=process" for salt-minion systemd service + Add: + * 0039-Re-introducing-KillMode-process-for-salt-minion-syst.patch + +------------------------------------------------------------------- +Wed Dec 14 16:16:55 UTC 2016 - mihai.dinca@suse.com + +- Successfully exit of salt-api child processes when SIGTERM is received + Add: + * 0038-Successfully-exit-of-salt-api-child-processes-when-S.patch + +------------------------------------------------------------------- +Wed Dec 14 13:31:09 UTC 2016 - mihai.dinca@suse.com + +- Add new patches: + * 0034-Fix-pkg.latest_version-when-latest-already-installed.patch + * 0035-salt-api-service-must-be-from-type-simple.patch + * 0036-Avoid-failures-on-SLES-12-SP2-because-of-new-systemd.patch + * 0037-salt-minion-service-back-to-type-simple.patch + +------------------------------------------------------------------- +Fri Dec 9 15:25:53 UTC 2016 - mihai.dinca@suse.com + +- Update to 2015.8.12 +- Fix possible information leak due to revoked keys still being used. + (bsc#1012398, CVE-2016-9639) + +- inherited patches + 0001-tserong-suse.com-We-don-t-have-python-systemd-so-not.patch + 0002-Run-salt-master-as-dedicated-salt-user.patch + 0003-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch + +- renamed patches + 0026-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch + -> 0004-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch + 0036-Use-SHA256-hash-type-by-default.patch + -> 0005-Use-SHA256-hash-type-by-default.patch + 0046-Add-SUSE-Manager-plugin.patch + -> 0007-Add-SUSE-Manager-plugin.patch + 0048-Create-salt-proxy-instantiated-service-file.patch + -> 0006-Create-salt-proxy-instantiated-service-file.patch + 0053-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch + -> 0008-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch + 0059-Rewrite-minion-ID-generator-bsc-967803.patch + -> 0009-Rewrite-minion-ID-generator-bsc-967803.patch + 0061-snapper-execution-module.patch + -> 0010-snapper-execution-module.patch + 0063-Fix-module-import-being-Py3-and-P2.6-compatible.patch + -> 0011-Fix-module-import-being-Py3-and-P2.6-compatible.patch + 0064-Run-salt-api-as-user-salt-bsc-990029.patch + -> 0012-Run-salt-api-as-user-salt-bsc-990029.patch + 0067-Bugfix-prevent-crash-if-python-dbus-module-is-instal.patch + -> 0013-Bugfix-prevent-crash-if-python-dbus-module-is-instal.patch + 0070-Fix-some-unittests.patch + -> 0014-Fix-some-unittests.patch + 0071-Check-for-single-quote-before-splitting-on-single-qu.patch + -> 0015-Check-for-single-quote-before-splitting-on-single-qu.patch + 0072-Unit-test-fixes-for-2015.8.7.patch + -> 0016-Unit-test-fixes-for-2015.8.7.patch + 0073-Fix-snapper_test-for-python26.patch + -> 0017-Fix-snapper_test-for-python26.patch + 0074-Integration-tests-fixes-for-2015.8.7.patch + -> 0018-Integration-tests-fixes-for-2015.8.7.patch + 0075-Generate-Salt-Thin-with-configured-extra-modules.patch + -> 0019-Generate-Salt-Thin-with-configured-extra-modules.patch + 0076-Setting-up-OS-grains-for-SLES-Expanded-Support-SUSE-.patch + -> 0020-Setting-up-OS-grains-for-SLES-Expanded-Support-SUSE-.patch + 0077-acl.delfacl-fix-position-of-X-option-to-setfacl.patch + -> 0021-acl.delfacl-fix-position-of-X-option-to-setfacl.patch + 0078-Change-travis-configuration-file-to-use-salt-toaster.patch + -> 0022-Change-travis-configuration-file-to-use-salt-toaster.patch + 0079-Adding-dist-upgrade-support-to-zypper-module.patch + -> 0023-Adding-dist-upgrade-support-to-zypper-module.patch + 0080-Fix-open-ssh-bsc-1004723-upstream-issue-36966.patch + -> 0024-Fix-open-ssh-bsc-1004723-upstream-issue-36966.patch + 0081-Including-resolver-params-for-Zypper-debug-solver.patch + -> 0025-Including-resolver-params-for-Zypper-debug-solver.patch + 0082-fix-status-handling-in-sysv-init-scripts.patch + -> 0026-fix-status-handling-in-sysv-init-scripts.patch + +- new patches from upstream + 0027-Add-YUM-plugin.patch + 0028-change-TIMEZONE-on-SUSE-systems-bsc-1008933.patch (bsc#1008933) + 0029-Do-not-include-gpg-pubkey-packages-filtering-by-thei.patch + 0030-Extract-archive-into-existing-directory-add-overwrit.patch + 0031-Port-rsync-state-from-2016.3.patch + 0032-Support-remote-port-forwarding-with-salt-ssh.patch + 0033-Add-master_tops-support-in-salt-ssh.patch + +- upstreamed patches + 0004-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch + 0005-Fix-package-status-filtering-on-latest-version-and-i.patch + 0006-add_key-reject_key-do-not-crash-w-Permission-denied-.patch + 0007-Force-kill-websocket-s-child-processes-faster-than-d.patch + 0008-Fix-types-in-the-output-data-and-return-just-a-list-.patch + 0009-The-functions-in-the-state-module-that-return-a-retc.patch + 0010-add-handling-for-OEM-products.patch + 0011-improve-doc-for-list_pkgs.patch + 0012-implement-version_cmp-for-zypper.patch + 0013-pylint-changes.patch + 0014-Check-if-rpm-python-can-be-imported.patch + 0015-call-zypper-with-option-non-interactive-everywhere.patch + 0016-write-a-zypper-command-builder-function.patch + 0017-Fix-crash-with-scheduler-and-runners-31106.patch + 0018-unify-behavior-of-refresh.patch + 0019-add-refresh-option-to-more-functions.patch + 0020-simplify-checking-the-refresh-paramater.patch + 0021-do-not-change-kwargs-in-refresh-while-checking-a-val.patch + 0022-fix-argument-handling-for-pkg.download.patch + 0023-Initial-Zypper-Unit-Tests-and-bugfixes.patch + 0024-proper-checking-if-zypper-exit-codes-and-handling-of.patch + 0025-adapt-tests-to-new-zypper_check_result-output.patch + 0027-make-suse-check-consistent-with-rh_service.patch + 0028-fix-numerical-check-of-osrelease.patch + 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch + 0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch + 0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch + 0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch + 0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch + 0034-Fix-git_pillar-race-condition.patch + 0035-Fix-the-always-false-behavior-on-checking-state.patch + 0037-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch + 0038-fix-sorting-by-latest-version-when-called-with-an-at.patch + 0039-Prevent-metadata-download-when-getting-installed-pro.patch + 0040-Check-if-EOL-is-available-in-a-particular-product-bs.patch + 0041-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch + 0042-align-OS-grains-from-older-SLES-with-current-one-bsc.patch + 0043-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch + 0044-Unblock-Zypper.-Modify-environment.patch + 0045-Bugfix-Restore-boolean-values-from-the-repo-configur.patch + 0047-Old-style-proxymodules-need-to-be-setup-earlier-in-m.patch + 0049-Prevent-several-minion-processes-on-the-same-machine.patch + 0050-checksum-validation-when-zypper-pkg.download.patch + 0051-unit-tests-for-rpm.checksum-and-zypper.download.patch + 0052-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch + 0054-fix-groupadd-module-for-sles11-systems.patch + 0055-Backport-31164-and-31364-32474.patch + 0056-Move-log-message-from-INFO-to-DEBUG.patch + 0057-fix-salt-summary-to-count-not-responding-minions-cor.patch + 0058-Getting-the-os-grain-from-CPE_NAME-inside-etc-os-rel.patch + 0060-Bugfix-return-boolean-only-for-isbase-and-installed-.patch + 0062-Add-realpath-to-lvm.pvdisplay-and-use-it-in-vg_prese.patch + 0065-fix-beacon-list-to-include-all-beacons-being-process.patch + 0066-Fix-continuous-minion-restart-if-a-dependency-wasn-t.patch + 0068-Add-ignore_repo_failure-option-to-suppress-zypper-s-.patch + 0069-Remove-zypper-s-raise-exception-if-mod_repo-has-no-a.patch + +------------------------------------------------------------------- +Mon Nov 28 14:44:21 UTC 2016 - kkaempf@suse.com + +- Splitted non-Linux and other external platform modules + to 'salt-other' sub-package. + +------------------------------------------------------------------- +Mon Nov 28 14:24:24 UTC 2016 - kkaempf@suse.com + +- Switch package group from System/Monitoring to System/Management + +------------------------------------------------------------------- +Sun Nov 6 17:39:54 UTC 2016 - mc@suse.com + +- fix exist codes of sysv init script (bsc#999852) + Add: + * 0082-fix-status-handling-in-sysv-init-scripts.patch + +------------------------------------------------------------------- +Mon Oct 31 16:53:51 UTC 2016 - pablo.suarezhernandez@suse.com + +- Including resolution parameters in the Zypper debug-solver call + during a dry-run dist-upgrade. + Add: + * 0081-Including-resolver-params-for-Zypper-debug-solver.patch + +------------------------------------------------------------------- +Tue Oct 25 07:19:39 UTC 2016 - bmaryniuk@suse.com + +- Fix Salt API crash via salt-ssh on empty roster (bsc#1004723) + Add: + * 0080-Fix-open-ssh-bsc-1004723-upstream-issue-36966.patch + +------------------------------------------------------------------- +Wed Oct 19 14:24:10 UTC 2016 - pablo.suarezhernandez@suse.com + +- Adding 'dist-upgrade' support to zypper module (FATE#320559) + Add: + * 0079-Adding-dist-upgrade-support-to-zypper-module.patch + +------------------------------------------------------------------- +Mon Oct 17 14:07:17 UTC 2016 - mihai.dinca@suse.com + +- Copy .travis.yml from git commit ea63e793567ba777e47dc766a4f88edfb037a02f + Add: + * travis.yml +- Change travis configuration file to use salt-toaster + Add: + * travis.yml + * 0078-Change-travis-configuration-file-to-use-salt-toaster.patch + +------------------------------------------------------------------- +Thu Oct 13 15:41:47 UTC 2016 - mihai.dinca@suse.com + +- acl.delfacl: fix position of -X option to setfacl (bsc#1004260) + Add: + * 0077-acl.delfacl-fix-position-of-X-option-to-setfacl.patch + +------------------------------------------------------------------- +Tue Oct 11 11:52:04 UTC 2016 - mc@suse.com + +- fix generated shebang in scripts on SLES-ES 7 (bsc#1004047) + +------------------------------------------------------------------- +Thu Oct 6 17:49:01 CEST 2016 - mc@suse.de + +- add update-documentation.sh to specfile + +------------------------------------------------------------------- +Wed Oct 5 13:33:16 UTC 2016 - mihai.dinca@suse.com + +- Setting up OS grains for SLES-ES (SLES Expanded Support platform) + Add: + * 0076-Setting-up-OS-grains-for-SLES-Expanded-Support-SUSE-.patch + +------------------------------------------------------------------- +Tue Oct 4 13:24:12 UTC 2016 - Michele.Bologna@suse.com + +- Move salt home directory to /var/lib/salt (bsc#1002529) +- Adjust permissions on home directory +- Adjust pre-install script to correctly move existing salt users' home directory + +salt user cannot write in his own home directory (/srv/salt) because +it is owned by user `root`. +This prevents salt from correctly save ssh known hosts in ~/.ssh/ +and breaks salt-ssh bootstrapping. + +------------------------------------------------------------------- +Tue Sep 27 09:59:38 UTC 2016 - bmaryniuk@suse.com + +- Updated html.tar.bz2 documentation tarball. +- Generate Salt Thin with configured extra modules (bsc#990439) + Add: + * 0075-Generate-Salt-Thin-with-configured-extra-modules.patch + +------------------------------------------------------------------- +Tue Sep 13 14:43:45 UTC 2016 - pablo.suarezhernandez@suse.com + +- Unit and integration tests fixes for 2015.8.7 + Add: + * 0072-Unit-test-fixes-for-2015.8.7.patch + * 0073-Fix-snapper_test-for-python26.patch + * 0074-Integration-tests-fixes-for-2015.8.7.patch + +------------------------------------------------------------------- +Fri Sep 2 10:05:01 UTC 2016 - pablo.suarezhernandez@suse.com + +- Prevent pkg.install failure for expired keys (bsc#996455) + Add: + * 0071-Check-for-single-quote-before-splitting-on-single-qu.patch + +------------------------------------------------------------------- +Mon Aug 29 15:06:56 UTC 2016 - bmaryniuk@suse.com + +- Required D-Bus and generating machine ID + +------------------------------------------------------------------- +Fri Aug 26 15:08:32 CEST 2016 - mc@suse.de + +- add a macro to check if the docs should be build or the static + tarball should be used + +------------------------------------------------------------------- +Wed Aug 24 09:03:21 UTC 2016 - mihai.dinca@suse.com + +- Fix a couple of failing unittests + * 0070-Fix-some-unittests.patch + +------------------------------------------------------------------- +Tue Aug 23 14:15:58 UTC 2016 - bmaryniuk@suse.com + +- Helper script for updating documentation tarball. + Added: + * update-documentation.sh + +------------------------------------------------------------------- +Tue Aug 16 14:54:49 UTC 2016 - mihai.dinca@suse.com + +- Fix python-jinja2 requirements in rhel + +------------------------------------------------------------------- +Tue Aug 16 11:48:27 UTC 2016 - bmaryniuk@suse.com + +- Fix pkg.installed refresh repo failure (bsc#993549) + Fix salt.states.pkgrepo.management no change failure (bsc#990440) + Add: + * 0068-Add-ignore_repo_failure-option-to-suppress-zypper-s-.patch + * 0069-Remove-zypper-s-raise-exception-if-mod_repo-has-no-a.patch + +------------------------------------------------------------------- +Thu Aug 11 14:35:06 UTC 2016 - bmaryniuk@suse.com + +- Prevent snapper module crash on load if no DBus is + available in the system (bsc#993039) + Add: + * 0067-Bugfix-prevent-crash-if-python-dbus-module-is-instal.patch + +------------------------------------------------------------------- +Thu Aug 11 12:34:34 UTC 2016 - bmaryniuk@suse.com + +- Prevent continuous restart, if a dependency wasn't installed + (bsc#991048) + Add: + * 0066-Fix-continuous-minion-restart-if-a-dependency-wasn-t.patch + +------------------------------------------------------------------- +Wed Aug 3 14:40:34 UTC 2016 - pablo.suarezhernandez@suse.com + +- Fix beacon list to include all beacons being process + Add: + * 0065-fix-beacon-list-to-include-all-beacons-being-process.patch + +------------------------------------------------------------------- +Fri Jul 29 10:59:09 CEST 2016 - mc@suse.de + +- Run salt-api as user salt like the master (bsc#990029) + Add: + * 0064-Run-salt-api-as-user-salt-bsc-990029.patch + +------------------------------------------------------------------- +Fri Jul 22 10:08:59 UTC 2016 - dmacvicar@suse.de + +- Revert patch Minion ID generation (bsc#967803) + Removes: + * 0059-Rewrite-minion-ID-generator-bsc-967803.patch + +------------------------------------------------------------------- +Wed Jul 20 15:16:55 UTC 2016 - bmaryniuk@suse.com + +- Fix broken inspector due to accidentally + missed commit (bsc#989798) + Add: + * 0063-Fix-module-import-being-Py3-and-P2.6-compatible.patch + +------------------------------------------------------------------- +Wed Jul 20 09:33:36 UTC 2016 - bmaryniuk@suse.com + +- Set always build salt-doc package. + +------------------------------------------------------------------- +Tue Jul 19 14:18:35 UTC 2016 - pablo.suarezhernandez@suse.com + +- Bugfix: lvm.vg_present does not recognize PV with certain LVM + filter settings (bsc#988506) + Add: + * 0062-Add-realpath-to-lvm.pvdisplay-and-use-it-in-vg_prese.patch + +------------------------------------------------------------------- +Mon Jul 18 10:20:05 UTC 2016 - pablo.suarezhernandez@suse.com + +- Backport: Snapper module for Salt. + Add: + * 0061-snapper-execution-module.patch + +------------------------------------------------------------------- +Fri Jul 15 15:37:18 UTC 2016 - bmaryniuk@suse.com + +- Bugfix: pkg.list_products on "registerrelease" and "productline" + returns boolean.False if empty (bsc#989193, bsc#986019) + Add: + * 0060-Bugfix-return-boolean-only-for-isbase-and-installed-.patch + +------------------------------------------------------------------- +Fri Jun 24 10:33:29 UTC 2016 - bmaryniuk@suse.com + +- Rewrite Minion ID generation (bsc#967803) + Add: + * 0059-Rewrite-minion-ID-generator-bsc-967803.patch + +------------------------------------------------------------------- +Wed Jun 22 08:16:04 UTC 2016 - pablo.suarezhernandez@suse.com + +- Bugfix: Fixed behavior for SUSE OS grains (bsc#970669) + Bugfix: Salt os_family does not detect SLES for SAP (bsc#983017) + Add: + * 0058-Getting-the-os-grain-from-CPE_NAME-inside-etc-os-rel.patch + +------------------------------------------------------------------- +Tue Jun 21 12:25:13 CEST 2016 - mc@suse.de + +- Move log message from INFO to DEBUG (bsc#985661) + Add: + 0056-Move-log-message-from-INFO-to-DEBUG.patch +- fix salt --summary to count not responding minions correctly + (bsc#972311) + Add: + * 0057-fix-salt-summary-to-count-not-responding-minions-cor.patch + +------------------------------------------------------------------- +Tue Jun 14 09:52:40 UTC 2016 - tserong@suse.com + +- Fix memory leak on custom execution module sheduled jobs (bsc#983512) + Add: + * 0055-Backport-31164-and-31364-32474.patch + +------------------------------------------------------------------- +Thu Jun 9 10:04:24 UTC 2016 - pablo.suarezhernandez@suse.com + +- fix groupadd module for sles11 systems (bsc#978150) + Add: + * 0054-fix-groupadd-module-for-sles11-systems.patch + +------------------------------------------------------------------- +Wed Jun 1 09:33:20 UTC 2016 - mihai.dinca@suse.com + +- Fix pkgrepo.managed gpgkey argument doesn't work (bsc#979448) + Add: + * 0053-Fix-pkgrepo.managed-gpgkey-argument-bsc-979448.patch + +------------------------------------------------------------------- +Fri May 27 09:20:03 UTC 2016 - pablo.suarezhernandez@suse.com + +- Package checksum validation for zypper pkg.download + Add: + * 0050-checksum-validation-when-zypper-pkg.download.patch + * 0051-unit-tests-for-rpm.checksum-and-zypper.download.patch +- Check if a job has executed and returned successfully + Add: + * 0052-jobs.exit_success-allow-to-check-if-a-job-has-execut.patch + +------------------------------------------------------------------- +Mon May 23 15:54:12 UTC 2016 - bmaryniuk@suse.com + +- Remove option -f from startproc (bsc#975733) + Add: + * 0049-Prevent-several-minion-processes-on-the-same-machine.patch + +------------------------------------------------------------------- +Mon May 23 13:47:49 UTC 2016 - bmaryniuk@suse.com + +- Changed Zypper's plugin. Added Unit test and related to that + data (bsc#980313). + Update: + * 0046-Add-SUSE-Manager-plugin.patch + + Delete (not needed anymore): + * 0049-Alter-the-event-name.patch + +------------------------------------------------------------------- +Tue May 17 09:57:57 UTC 2016 - bmaryniuk@suse.com + +- Zypper plugin: alter the generated event name on package set + change. + Add: + * 0049-Alter-the-event-name.patch + +------------------------------------------------------------------- +Thu May 12 00:42:27 UTC 2016 - tserong@suse.com + +- Fix file ownership on master keys and cache directories during upgrade + (handles upgrading from salt 2014, where the daemon ran as root, to 2015 + where it runs as the salt user, bsc#979676). + +------------------------------------------------------------------- +Wed May 11 11:01:44 UTC 2016 - pablo.suarezhernandez@suse.com + +- salt-proxy .service file created (bsc#975306) + Add: + * 0048-Create-salt-proxy-instantiated-service-file.patch + +------------------------------------------------------------------- +Wed May 11 08:51:44 UTC 2016 - pablo.suarezhernandez@suse.com + +- Prevent salt-proxy test.ping crash (bsc#975303) + Add: + * 0047-Old-style-proxymodules-need-to-be-setup-earlier-in-m.patch + +------------------------------------------------------------------- +Wed May 11 07:18:54 UTC 2016 - bmaryniuk@suse.com + +- Fix shared directories ownership issues. + +------------------------------------------------------------------- +Mon May 9 08:55:32 UTC 2016 - bmaryniuk@suse.com + +- Add Zypper plugin to generate an event, + once Zypper is used outside the Salt infrastructure + demand (bsc#971372). + Add: + * 0046-Add-SUSE-Manager-plugin.patch + +------------------------------------------------------------------- +Fri May 6 14:53:03 UTC 2016 - bmaryniuk@suse.com + +- Restore boolean values from the repo configuration + Fix priority attribute (bsc#978833) + Add: + * 0045-Bugfix-Restore-boolean-values-from-the-repo-configur.patch + +------------------------------------------------------------------- +Wed May 4 12:39:32 UTC 2016 - bmaryniuk@suse.com + +- Unblock-Zypper. (bsc#976148) + Modify-environment. (bsc#971372) + Add: + * 0044-Unblock-Zypper.-Modify-environment.patch + +------------------------------------------------------------------- +Wed Apr 20 08:59:06 UTC 2016 - bmaryniuk@suse.com + +- Prevent crash if pygit2 package is requesting re-compilation. + Add: + * 0043-Prevent-crash-if-pygit2-package-is-requesting-re-com.patch + +------------------------------------------------------------------- +Mon Apr 18 10:24:56 CEST 2016 - mc@suse.de + +- align OS grains from older SLES with current one (bsc#975757) + Add: + * 0042-align-OS-grains-from-older-SLES-with-current-one-bsc.patch + +------------------------------------------------------------------- +Thu Apr 14 07:23:47 UTC 2016 - bmaryniuk@suse.com + +- Bugfix: salt-key crashes if tries to generate keys + to the directory w/o write access (bsc#969320) + Add: + * 0041-Bugfix-salt-key-crashes-if-tries-to-generate-keys-to.patch + +------------------------------------------------------------------- +Tue Apr 12 12:34:40 UTC 2016 - bmaryniuk@suse.com + +- Check if EOL is available in a particular product (bsc#975093) + Add: + * 0040-Check-if-EOL-is-available-in-a-particular-product-bs.patch + +------------------------------------------------------------------- +Tue Apr 5 13:05:49 UTC 2016 - mc@suse.com + +- fix building with docs on SLE11 + +------------------------------------------------------------------- +Tue Apr 5 11:19:02 UTC 2016 - mc@suse.com + +- Prevent metadata download when getting installed products + Add: + * 0039-Prevent-metadata-download-when-getting-installed-pro.patch + +------------------------------------------------------------------- +Tue Apr 5 08:51:38 UTC 2016 - kkaempf@suse.com + +- Add statically built docs. + +------------------------------------------------------------------- +Mon Apr 4 07:51:27 UTC 2016 - mc@suse.com + +- fix sorting by latest package + Add: + * 0038-fix-sorting-by-latest-version-when-called-with-an-at.patch + +------------------------------------------------------------------- +Sat Apr 2 12:55:57 UTC 2016 - mc@suse.com + +- ensure pkg.info_installed report latest package version + (bsc#972490) + Add: + * 0037-Force-sort-the-RPM-output-to-ensure-latest-version-o.patch + +------------------------------------------------------------------- +Thu Mar 17 12:09:14 UTC 2016 - bmaryniuk@suse.com + +- Use SHA256 by default in master, minion and proxy (bsc#955373) + Add: + * 0036-Use-SHA256-hash-type-by-default.patch + +------------------------------------------------------------------- +Wed Mar 16 15:34:26 UTC 2016 - bmaryniuk@suse.com + +- Fix state structure compilation + Add: + * 0035-Fix-the-always-false-behavior-on-checking-state.patch +- Fix git_pillar race condition + Add: + * 0034-Fix-git_pillar-race-condition.patch + +------------------------------------------------------------------- +Sat Mar 12 17:08:03 UTC 2016 - mc@suse.com + +- fix detection of base products in SLE11 + * 0030-Bugfix-on-SLE11-series-base-product-reported-as-addi.patch +- fix rpm info for SLE11 + * 0031-Only-use-LONGSIZE-in-rpm.info-if-available.-Otherwis.patch + * 0032-Add-error-check-when-retcode-is-0-but-stderr-is-pres.patch +- fix init system detection for SLE11 + * 0033-fixing-init-system-dectection-on-sles-11-refs-31617.patch + +------------------------------------------------------------------- +Fri Mar 11 14:42:52 UTC 2016 - bmaryniuk@suse.com + +- Re-add corrected patch: + 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch + +------------------------------------------------------------------- +Wed Mar 9 09:39:09 UTC 2016 - kkaempf@suse.com + +- Make checksum configurable (upstream still wants md5, we + suggest sha256). bsc#955373 + Add: + 0029-Make-use-of-checksum-configurable-defaults-to-MD5-SH.patch + +------------------------------------------------------------------- +Fri Mar 4 10:41:52 UTC 2016 - tampakrap@opensuse.org + +- Fix the service state / module on SLE11. + Add: + * 0027-make-suse-check-consistent-with-rh_service.patch + * 0028-fix-numerical-check-of-osrelease.patch + +------------------------------------------------------------------- +Fri Mar 4 09:54:00 CET 2016 - mc@suse.de + +- Prevent rebuilds in OBS by not generating a date as a comment in + a source file + Add: 0026-do-not-generate-a-date-in-a-comment-to-prevent-rebui.patch + +------------------------------------------------------------------- +Fri Feb 26 14:55:14 CET 2016 - mc@suse.de + +- Add better checking for zypper exit codes and simplify evaluation + of the zypper error messages. + Add: 0024-proper-checking-if-zypper-exit-codes-and-handling-of.patch +- Adapt unit tests + Add: 0025-adapt-tests-to-new-zypper_check_result-output.patch + +------------------------------------------------------------------- +Fri Feb 26 10:42:17 UTC 2016 - bmaryniuk@suse.com + +- Add initial pack of Zypper's Unit tests. + Use XML output in list_upgrades. + Bugfix: upgrade_available crashes when only one package specified + Purge is not using "-u" anymore + Add: + * 0023-Initial-Zypper-Unit-Tests-and-bugfixes.patch + +------------------------------------------------------------------- +Tue Feb 23 11:58:00 CET 2016 - mc@suse.de + +- fix argument handling of pkg.download + Add: 0022-fix-argument-handling-for-pkg.download.patch + +------------------------------------------------------------------- +Mon Feb 22 16:12:43 CET 2016 - mc@suse.de + +- unify behavior of zypper refresh in salt + Add: 0018-unify-behavior-of-refresh.patch + 0019-add-refresh-option-to-more-functions.patch + 0020-simplify-checking-the-refresh-paramater.patch + 0021-do-not-change-kwargs-in-refresh-while-checking-a-val.patch + +------------------------------------------------------------------- +Sat Feb 20 11:41:45 CET 2016 - mc@suse.de + +- Fix crash with scheduler and runners + Add: 0017-Fix-crash-with-scheduler-and-runners-31106.patch + +------------------------------------------------------------------- +Fri Feb 19 15:01:38 CET 2016 - mc@suse.de + +- Call zypper always with --non-interactive + Add: + * 0015-call-zypper-with-option-non-interactive-everywhere.patch + * 0016-write-a-zypper-command-builder-function.patch + +------------------------------------------------------------------- +Fri Feb 19 13:00:52 CET 2016 - mc@suse.de + +- require rpm-python on SUSE for zypper support + +------------------------------------------------------------------- +Thu Feb 18 11:01:21 CET 2016 - mc@suse.de + +- fix state return code + Add: 0009-The-functions-in-the-state-module-that-return-a-retc.patch +- add handling of OEM products to pkg.list_products + Add: 0010-add-handling-for-OEM-products.patch +- improve doc for list_pkgs + Add: 0011-improve-doc-for-list_pkgs.patch +- implement pkg.version_cmp in zypper.py + Add: + * 0012-implement-version_cmp-for-zypper.patch + * 0013-pylint-changes.patch + * 0014-Check-if-rpm-python-can-be-imported.patch + +------------------------------------------------------------------- +Wed Feb 17 17:57:57 UTC 2016 - aboe76@gmail.com + +- Update to 2015.8.7 + this is a small update to fix some regressions + see https://docs.saltstack.com/en/latest/topics/releases/2015.8.7.html + +------------------------------------------------------------------- +Thu Feb 11 17:42:38 UTC 2016 - bmaryniuk@suse.com + +- Booleans should not be strings from XML, add Unix ticks time and + format result in a list of maps. + Add: + * 0008-Fix-types-in-the-output-data-and-return-just-a-list-.patch + +------------------------------------------------------------------- +Thu Feb 11 16:27:33 UTC 2016 - bmaryniuk@suse.com + +- Stop salt-api daemon faster (bsc#963322) + Add: + * 0007-Force-kill-websocket-s-child-processes-faster-than-d.patch + +------------------------------------------------------------------- +Wed Feb 10 08:30:45 UTC 2016 - dmacvicar@suse.de + +- Do not crash on salt-key reject/delete consecutive calls. + Add: + * 0006-add_key-reject_key-do-not-crash-w-Permission-denied-.patch + +------------------------------------------------------------------- +Mon Feb 8 16:15:56 UTC 2016 - kkaempf@suse.com + +- Update to 2015.8.5 + Security fixes: + * CVE-2016-1866: Improper handling of clear messages on the + minion remote code execution (boo#965403) + See https://docs.saltstack.com/en/latest/topics/releases/2015.8.5.html + Dropped patches (all upstream): + * 0003-List-products-consistently-across-all-SLES-systems.patch + * 0004-Add-missing-return-data-to-scheduled-jobs.patch + * 0005-Fix-RPM-issues-with-the-date-time-and-add-package-at.patch + * 0006-Bugfix-info_available-does-not-work-correctly-on-SLE.patch + Renamed patches: + * 0007-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch + -> 0003-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch + * 0008-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch + -> 0004-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch + * 0009-Fix-package-status-filtering-on-latest-version-and-i.patch + -> 0005-Fix-package-status-filtering-on-latest-version-and-i.patch + +- Update to 2015.8.4 + See https://docs.saltstack.com/en/latest/topics/releases/2015.8.4.html + +------------------------------------------------------------------- +Wed Jan 27 13:09:53 UTC 2016 - bmaryniuk@suse.com + +- Fix latest version available comparison and implement epoch + support in Zypper module. + Add: + * 0009-Fix-package-status-filtering-on-latest-version-and-i.patch + +------------------------------------------------------------------- +Wed Jan 27 09:25:15 UTC 2016 - bmaryniuk@suse.com + +- Update patch from opensuse to upstream version. + Update: + * 0008-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch + +------------------------------------------------------------------- +Tue Jan 26 09:42:57 UTC 2016 - bmaryniuk@suse.com + +- Fix dependencies to Salt subpackages requiring release along the + version. + +------------------------------------------------------------------- +Mon Jan 25 16:24:55 UTC 2016 - bmaryniuk@suse.com + +- Fix pkg.latest crash. +- Fix pkg.latest SLS ID bug, when pkgs empty list is passed, + but SLS ID still treated as a package name. + + Add: + * 0008-Fix-pkg.latest-prevent-crash-on-multiple-package-ins.patch + +------------------------------------------------------------------- +Wed Jan 20 15:10:04 UTC 2016 - bmaryniuk@suse.com + +- Drop: + * -0004-zypper-check-package-header-content-for-valid-utf-8.patch +- Rename: + * -0004-zypper-check-package-header-content-for-valid-utf-8.patch + +0004-Add-missing-return-data-to-scheduled-jobs.patch + * -0005-Add-missing-return-data-to-scheduled-jobs.patch + +0004-Add-missing-return-data-to-scheduled-jobs.patch + * -0006-Fix-RPM-issues-with-the-date-time-and-add-package-at.patch + +0005-Fix-RPM-issues-with-the-date-time-and-add-package-at.patch + * -0007-Bugfix-info_available-does-not-work-correctly-on-SLE.patch + +0006-Bugfix-info_available-does-not-work-correctly-on-SLE.patch +- Add: + * 0007-Check-if-byte-strings-are-properly-encoded-in-UTF-8.patch + +------------------------------------------------------------------- +Wed Jan 20 10:19:07 UTC 2016 - kkaempf@suse.com + +- Rename use-forking-daemon.patch to + 0001-tserong-suse.com-We-don-t-have-python-systemd-so-not.patch +- Rename use-salt-user-for-master.patch to + 0002-Run-salt-master-as-dedicated-salt-user.patch +- Rename 1efe484309a5c776974e723f3da0f5181f4bdb86.patch to + 0003-List-products-consistently-across-all-SLES-systems.patch +- Rename zypper-utf-8.patch to + 0004-zypper-check-package-header-content-for-valid-utf-8.patch +- Rename salt-2015.8-schedule-ret.patch to + 0005-Add-missing-return-data-to-scheduled-jobs.patch +- Rename salt-2015.8-pkg-zypper-attr-filtering.patch to + 0006-Fix-RPM-issues-with-the-date-time-and-add-package-at.patch +- Rename salt-2015.8-zypper-info.patch to + 0007-Bugfix-info_available-does-not-work-correctly-on-SLE.patch + +------------------------------------------------------------------- +Fri Jan 15 13:16:46 UTC 2016 - dmacvicar@suse.de + +- Fix zypper module info_available on SLE-11 + * add salt-2015.8-zypper-info.patch + * https://github.com/saltstack/salt/pull/30384 +- zypper/pkg: add package attributes filtering + * add salt-2015.8-pkg-zypper-attr-filtering.patch + * https://github.com/saltstack/salt/pull/30267 +- Remove obsoleted patches and fixes: + * 0001-Add-rpm.minimal_info-fix-rpm.info.patch + * 0002-Reduce-information-returned-from-pkg.info_installed.patch + * Remove require on glibc-locale (bsc#959572) + +------------------------------------------------------------------- +Wed Jan 13 12:03:06 UTC 2016 - dmacvicar@suse.de + +- Add missing return data to scheduled jobs + * add salt-2015.8-schedule-ret.patch for + * https://github.com/saltstack/salt/pull/30246 + +------------------------------------------------------------------- +Mon Dec 21 14:06:27 UTC 2015 - kkaempf@suse.com + +- Update zypper-utf-8.patch for Python 2.6 + +------------------------------------------------------------------- +Thu Dec 17 15:53:47 UTC 2015 - kkaempf@suse.com + +- require glibc-locale (bsc#959572) + +------------------------------------------------------------------- +Tue Dec 15 13:50:17 UTC 2015 - kkaempf@suse.com + +- Report epoch and architecture of installed packages + 0001-Add-rpm.minimal_info-fix-rpm.info.patch + +- pkg.info_installed exceeds the maximum event size, reduce the + information to what's actually needed + 0002-Reduce-information-returned-from-pkg.info_installed.patch + +------------------------------------------------------------------- +Wed Dec 9 08:28:21 UTC 2015 - kkaempf@suse.com + +- Filter out bad UTF-8 strings in package data (bsc#958350) + zypper-utf-8.patch + +------------------------------------------------------------------- +Tue Dec 1 22:04:14 UTC 2015 - aboe76@gmail.com + +- Updated to salt 2015.8.3 bugfix release +- remove the following patches because upstream merged them: + - 4b9302d79455d6a586b7cad1d7990cb22e7bc62e.patch + - os_grain.patch + - zypper_pkgrepo.patch +- more details at: https://docs.saltstack.com/en/latest/topics/releases/2015.8.3.html + +------------------------------------------------------------------- +Tue Dec 1 14:40:25 UTC 2015 - bmaryniuk@suse.com + +- added 1efe484309a5c776974e723f3da0f5181f4bdb86.patch: + reimplements pkg.list_products that potentially may be broken in + a future releases of SLES. + +------------------------------------------------------------------- +Mon Nov 30 17:19:06 UTC 2015 - mrueckert@suse.de + +- added 4b9302d79455d6a586b7cad1d7990cb22e7bc62e.patch: + fixes a regression introduced in 2015.8.2, which was actually + holding back the release. Downgrade is not an option as we need + the leap fixes. + +------------------------------------------------------------------- +Thu Nov 26 13:46:55 UTC 2015 - mrueckert@suse.de + +- it shouldnt be >= 1110 but just > 1110 + +------------------------------------------------------------------- +Wed Nov 25 13:43:16 UTC 2015 - mrueckert@suse.de + +- require pmtools on sle11 to get dmidecode + +------------------------------------------------------------------- +Fri Nov 20 23:52:14 UTC 2015 - mrueckert@suse.de + +- update use-salt-user-for-master.patch: + First step to make the syndic also run as salt user. + +------------------------------------------------------------------- +Fri Nov 13 21:56:35 UTC 2015 - aboe76@gmail.com + +- Updated to bugfix release 2015.8.2 +- os_grain.patch fix the "os" grain on SLES11SP4 +- zypper_pkgrepo.patch fix the priority and humanname pkgrepo args for the + zypper backend + + for more details: + https://docs.saltstack.com/en/2015.8/topics/releases/2015.8.2.html + +------------------------------------------------------------------- +Thu Oct 15 09:43:16 UTC 2015 - mrueckert@suse.de + +- update to 2015.8.1 + - Add support for ``spm.d/*.conf`` configuration of SPM + (:issue:`27010`) + - Fix ``proxy`` grains breakage for non-proxy minions + (:issue:`27039`) + - Fix global key management for git state + - Fix passing http auth to ``util.http`` from ``state.file`` + (:issue:`21917`) + - Fix ``multiprocessing: True`` in windows (on by default`) + - Add ``pkg.info`` to pkg modules + - Fix name of ``serial`` grain (this was accidentally renamed in + 2015.8.0`) + - Merge config values from ``master.d``/``minion.d`` conf files + (rather than flat update`) + - Clean grains cache on grains sync (:issue:`19853`) + - Remove streamed response for fileclient to avoid HTTP + redirection problems (:issue:`27093`) + - Fixed incorrect warning about ``osrelease`` grain + (:issue:`27065`) + - Fix authentication via Salt-API with tokens (:issue:`27270`) + - Fix winrepo downloads from https locations (:issue:`27081`) + - Fix potential error with salt-call as non-root user + (:issue:`26889`) + - Fix global minion provider overrides (:issue:`27209`) + - Fix backward compatibility issues for pecl modules + - Fix Windows uninstaller to only remove ``./bin``, ``salt*``, + ``nssm.exe``, ``uninst.exe`` (:issue:`27383`) + - Fix misc issues with mongo returner. + - Add sudo option to cloud config files (:issue:`27398`) + - Fix regression in RunnerClient argument handling + (:issue:`25107`) + - Fix ``dockerng.running`` replacing creation hostconfig with + runtime hostconfig (:issue:`27265`) + - Fix dockerng.running replacing creation hostconfig with runtime + hostconfig (:issue:`27265`) + - Increased performance on boto asg/elb states due to + ``__states__`` integration + - Windows minion no longer requires powershell to restart + (:issue:`26629`) + - Fix x509 module to support recent versions of OpenSSL + (:issue:`27326`) + - Some issues with proxy minions were corrected. +- drop salt-2015.8-backports-susemanager.diff: included in update +- guard raet buildrequires with bcond_with raet and comment out the + recommends for salt-raet. + +------------------------------------------------------------------- +Mon Oct 12 10:11:33 UTC 2015 - tampakrap@opensuse.org + +- remove pygit2 global recommends, it is only needed in the master +- remove git-core, pygit2 should pull it as a dependency +- add a (currently disabled) %check + +------------------------------------------------------------------- +Mon Oct 12 10:08:57 UTC 2015 - toddrme2178@gmail.com + +- Add salt-2015.8-backports-susemanager.diff + Returns detailed information about a package + +------------------------------------------------------------------- +Mon Oct 12 08:48:25 UTC 2015 - dmacvicar@suse.de + +- ifdef Recommends to build on RHEL based distros +- use _initddir instead of _sysconfdir/init.d as + it works on both platforms. + +------------------------------------------------------------------- +Mon Oct 12 08:19:45 UTC 2015 - dmacvicar@suse.de + +- allow to disable docs in preparation for building + on other platforms without all dependencies. + +------------------------------------------------------------------- +Mon Oct 12 08:07:01 UTC 2015 - dmacvicar@suse.de + +- python-libnacl, python-ioflo are _not_ required to build the + package. They are anyways requires of python-raet, which is + also not required to build the package. + +------------------------------------------------------------------- +Sat Oct 10 00:00:39 UTC 2015 - mrueckert@suse.de + +- merge (build)requires/recommends with requirements/*txt and + setup.py + +------------------------------------------------------------------- +Fri Oct 9 23:35:05 UTC 2015 - mrueckert@suse.de + +- add raet subpackage which will pull all requires for it and + provides config snippets to enable it for the minion and master. + +------------------------------------------------------------------- +Fri Oct 9 15:34:16 UTC 2015 - mrueckert@suse.de + +- add tmpfiles.d file + +------------------------------------------------------------------- +Fri Oct 9 14:12:39 UTC 2015 - dmacvicar@suse.de + +- Remove requires on python-ioflo and python-libnacl + they will be pulled by python-raet, + which is optional. + +------------------------------------------------------------------- +Fri Oct 9 12:12:48 UTC 2015 - dmacvicar@suse.de + +- python-raet is optional, so make it a Recommends + +------------------------------------------------------------------- +Fri Oct 9 12:04:03 UTC 2015 - dmacvicar@suse.de + +- update backports patch from 2015.8 branch + +------------------------------------------------------------------- +Wed Oct 7 20:06:19 UTC 2015 - mrueckert@suse.de + +- update use-forking-daemon.patch: + the original intention was to get rid of the python systemd + dependency. for this we do not have daemonize the whole process. + just switching to simple mode is enough. + +------------------------------------------------------------------- +Wed Oct 7 19:15:54 UTC 2015 - mrueckert@suse.de + +- drop fdupes: + 1. it broke python byte code handling + 2. the only part of the package which would really benefit from + it would be the doc package. but given we only install the + files via %doc, we can not use it for that either. +- reenable completions on distros newer than sle11 +- do not use _datarootdir, use _datadir instead. + +------------------------------------------------------------------- +Wed Oct 7 14:34:18 UTC 2015 - mrueckert@suse.de + +- package all directories in /var/cache/salt and /etc/salt and + have permissions set for non root salt master +- update use-salt-user-for-master.patch: + - also patch the logrotate file to include the su option + +------------------------------------------------------------------- +Tue Oct 6 12:27:49 UTC 2015 - mrueckert@suse.de + +- remove duplicated recommends +- never require pygit2 and git. the master can run fine without. + always use recommends + +------------------------------------------------------------------- +Tue Oct 6 11:36:13 UTC 2015 - tampakrap@opensuse.org + +- cleanup dependencies: + - remove a lot of unneeded buildrequires + - fdupes not present on SLE10 + - python-certifi needed on SLE11 + - python-zypp not needed any more + - python-pygit2 is not a global requirement + - convert python-pysqlite to recommends as it is not available on python <=2.7 +- sles_version -> suse_version +- %exclude the cloud/deploy/*.sh scripts to fix build issue on SLE11 + +------------------------------------------------------------------- +Mon Oct 5 12:44:40 UTC 2015 - tampakrap@opensuse.org + +- Remove python-PyYAML from the dependencies list, as python-yaml is the same +- Build the -completion subpackages in SLE11 as well +- Add salt-proxy (by dmacvicar@suse.de) +- Create salt user/group only in the -master subpkg + +------------------------------------------------------------------- +Sat Oct 3 17:37:20 UTC 2015 - infroma@gmail.com + +- Fix typo in use-forking-daemon.patch, that prevented daemon loading + +------------------------------------------------------------------- +Thu Oct 1 08:13:58 UTC 2015 - toddrme2178@gmail.com + +- Fix typo in Requires + +------------------------------------------------------------------- +Tue Sep 29 09:11:38 UTC 2015 - toddrme2178@gmail.com + +- Cleanup requirements + +------------------------------------------------------------------- +Wed Sep 23 18:06:52 UTC 2015 - aboe76@gmail.com + +- New Major release 2015.8.0 + + for more details: + http://docs.saltstack.com/en/latest/topics/releases/2015.8.0.html + +- Cleaned the spec file with spec-cleaner +- Added the use-salt-user-for-master.patch see README.SUSE +- Updated the files ownership with salt user +- removed m2crypto depency + +------------------------------------------------------------------- +Tue Sep 22 11:51:50 UTC 2015 - infroma@gmail.com + +- Removed fish dependency for fish completions. + +------------------------------------------------------------------- +Tue Sep 22 11:12:47 UTC 2015 - infroma@gmail.com + +- Added fish completions. + +------------------------------------------------------------------- +Mon Sep 21 08:58:31 UTC 2015 - tampakrap@opensuse.org + +- Support SLE11SP{3,4}, where the M2Crypto package is named python-m2crypto + +------------------------------------------------------------------- +Tue Aug 18 06:58:18 UTC 2015 - aboe76@gmail.com + +- Updated to Bugfix release 2015.5 + + for more details: + https://github.com/saltstack/salt/blob/develop/doc/topics/releases/2015.5.5.rst + +- Add prereq, for user creation. +- Add creation of salt user in preparation of running the salt-master daemon + as non-root user salt. + https://bugzilla.opensuse.org/show_bug.cgi?id=939831 +- Add README.SUSE with explanation and how to. + +------------------------------------------------------------------- +Mon Jul 20 12:22:26 UTC 2015 - bwiedemann@suse.com + +- only require git-core to not pull in git-web and gitk + +------------------------------------------------------------------- +Wed Jul 8 08:47:32 UTC 2015 - aboe76@gmail.com + +- New Bugfix release 2015.5.3 + + for more details: + http://docs.saltstack.com/en/latest/topics/releases/2015.5.3.html + +------------------------------------------------------------------- +Thu Jun 4 19:46:19 UTC 2015 - aboe76@gmail.com + +- New Bugfix release 2015.5.2 + + for more details: + http://docs.saltstack.com/en/latest/topics/releases/2015.5.2.html + +------------------------------------------------------------------- +Sat May 23 18:31:44 UTC 2015 - aboe76@gmail.com + +- New Bugfix release 2015.5.1 + salt.runners.cloud.action() has changed the fun keyword argument to func. + Please update any calls to this function in the cloud runner. + + for more details: + http://docs.saltstack.com/en/latest/topics/releases/2015.5.1.html + +------------------------------------------------------------------- +Fri May 15 21:04:44 UTC 2015 - aboe76@gmail.com + +- Removed python-pssh depency not needed anymore. + +------------------------------------------------------------------- +Wed May 6 20:33:53 UTC 2015 - aboe76@gmail.com + +- Major release 2015.5.0 Lithium +- update to 2015.5.0 + The 2015.5.0 feature release of Salt is focused on hardening Salt + and mostly on improving existing systems. A few major additions + are present, primarily the new Beacon system. Most enhancements + have been focused around improving existing features and + interfaces. + + As usual the release notes are not exhaustive and primarily + include the most notable additions and improvements. Hundreds of + bugs have been fixed and many modules have been substantially + updated and added. + + + See especially the warning right on the top regarding + python_shell=False. + + For all details see + http://docs.saltstack.com/en/latest/topics/releases/2015.5.0.html +- RPM Package changes: +- add some versions to the buildrequires to match the 2 + requirements files from the tarball +- Moved the depencencies to main salt package + except where they are specific for the package +- Changed python-request dependency,only needed on salt-cloud +- Added python-tornado dependency for http.py +- Fixed zsh_completion in tarball. +- Fixed salt-api requirements to require python-cherrypy +- Fixed salt-cloud requiments to require salt-master + +------------------------------------------------------------------- +Sun Apr 19 17:48:05 UTC 2015 - aboe76@gmail.com + +- New Bugfix release 2014.7.5 +Changes: ++ Fixed a key error bug in salt-cloud ++ Updated man pages to better match documentation ++ Fixed bug concerning high CPU usage with salt-ssh ++ Fixed bugs with remounting cvfs and fuse filesystems ++ Fixed bug with alowing requisite tracking of entire sls files ++ Fixed bug with aptpkg.mod_repo returning OK even if apt-add-repository fails ++ Increased frequency of ssh terminal output checking ++ Fixed malformed locale string in localmod module ++ Fixed checking of available version of package when accept_keywords were changed ++ Fixed bug to make git.latest work with empty repositories ++ Added **kwargs to service.mod_watch which removes warnings about enable and __reqs__ not being supported by the function ++ Improved state comments to not grow so quickly on failed requisites ++ Added force argument to service to trigger force_reload ++ Fixed bug to andle pkgrepo keyids that have been converted to int ++ Fixed module.portage_config bug with appending accept_keywords ++ Fixed bug to correctly report disk usage on windows minion ++ Added the ability to specify key prefix for S3 ext_pillar ++ Fixed issues with batch mode operating on the incorrect number of minions ++ Fixed a bug with the proxmox cloud provider stacktracing on disk definition ++ Fixed a bug with the changes dictionary in the file state ++ Fixed the TCP keep alive settings to work better with SREQ caching ++ Fixed many bugs within the iptables state and module ++ Fixed bug with states by adding fun, state, and unless to the state runtime internal keywords listing ++ Added ability to eAuth against Active Directory ++ Fixed some salt-ssh issues when running on Fedora 21 ++ Fixed grains.get_or_set_hash to work with multiple entries under same key ++ Added better explanations and more examples of how the Reactor calls functions to docs ++ Fixed bug to not pass ex_config_drive to libcloud unless it's explicitly enabled ++ Fixed bug with pip.install on windows ++ Fixed bug where puppet.run always returns a 0 retcode ++ Fixed race condition bug with minion scheduling via pillar ++ Made efficiency improvements and bug fixes to the windows installer ++ Updated environment variables to fix bug with pygit2 when running salt as non-root user ++ Fixed cas behavior on data module -- data.cas was not saving changes ++ Fixed GPG rendering error ++ Fixed strace error in virt.query ++ Fixed stacktrace when running chef-solo command ++ Fixed possible bug wherein uncaught exceptions seem to make zmq3 tip over when threading is involved ++ Fixed argument passing to the reactor ++ Fixed glibc caching to prevent bug where salt-minion getaddrinfo in dns_check() never got updated nameservers +Known Issues: ++ In multimaster mode, a minion may become temporarily unresponsive if modules or pillars are refreshed at the +same time that one or more masters are down. This can be worked around by setting 'auth_timeout' and 'auth_tries' +down to shorter periods. + +------------------------------------------------------------------- +Mon Mar 30 21:41:22 UTC 2015 - aboe76@gmail.com + +- New Bugfix Release 2014.7.4 +- Updated patch use-forking-daemon.patch +- fix salt-zsh-completion conflicts ++ Multi-master minions mode no longer route fileclient operations asymetrically. + This fixes the source of many multi-master bugs where the minion would + become unrepsonsive from one or more masters. ++ Fix bug wherein network.iface could produce stack traces. ++ net.arp will no longer be made available unless arp is installed on the + system. ++ Major performance improvements to Saltnado ++ Allow KVM module to operate under KVM itself or VMWare Fusion ++ Various fixes to the Windows installation scripts ++ Fix issue where the syndic would not correctly propogate loads to the master + job cache. ++ Improve error handling on invalid /etc/network/interfaces file in salt + networking modules ++ Fix bug where a reponse status was not checked for in fileclient.get_url ++ Enable eauth when running salt in batch mode ++ Increase timeout in Boto Route53 module ++ Fix bugs with Salt's 'tar' module option parsing ++ Fix parsing of NTP servers on Windows ++ Fix issue with blockdev tuning not reporting changes correctly ++ Update to the latest Salt bootstrap script ++ Update Linode salt-cloud driver to use either linode-python or + apache-libcloud ++ Fix for s3.query function to return correct headers ++ Fix for s3.head returning None for files that exist ++ Fix the disable function in win_service module so that the service is + disabled correctly ++ Fix race condition between master and minion when making a directory when + both daemons are on the same host ++ Fix an issue where file.recurse would fail at the root of an svn repo + when the repo has a mountpoint ++ Fix an issue where file.recurse would fail at the root of an hgfs repo + when the repo has a mountpoint ++ Fix an issue where file.recurse would fail at the root of an gitfs repo + when the repo has a mountpoint ++ Add status.master capability for Windows. ++ Various fixes to ssh_known_hosts ++ Various fixes to states.network bonding for Debian ++ The debian_ip.get_interfaces module no longer removes nameservers. ++ Better integration between grains.virtual and systemd-detect-virt and + virt-what ++ Fix traceback in sysctl.present state output ++ Fix for issue where mount.mounted would fail when superopts were not a part + of mount.active (extended=True). Also mount.mounted various fixes for Solaris + and FreeBSD. ++ Fix error where datetimes were not correctly safeguarded before being passed + into msgpack. ++ Fix file.replace regressions. If the pattern is not found, and if dry run is False, + and if `backup` is False, and if a pre-existing file exists with extension `.bak`, + then that backup file will be overwritten. This backup behavior is a result of how `fileinput` + works. Fixing it requires either passing through the file twice (the + first time only to search for content and set a flag), or rewriting + `file.replace` so it doesn't use `fileinput` ++ VCS filreserver fixes/optimizations ++ Catch fileserver configuration errors on master start ++ Raise errors on invalid gitfs configurations ++ set_locale when locale file does not exist (Redhat family) ++ Fix to correctly count active devices when created mdadm array with spares ++ Fix to correctly target minions in batch mode ++ Support ssh:// urls using the gitfs dulwhich backend ++ New fileserver runner ++ Fix various bugs with argument parsing to the publish module. ++ Fix disk.usage for Synology OS ++ Fix issue with tags occurring twice with docker.pulled ++ Fix incorrect key error in SMTP returner ++ Fix condition which would remount loopback filesystems on every state run ++ Remove requsites from listens after they are called in the state system ++ Make system implementation of service.running aware of legacy service calls ++ Fix issue where publish.publish would not handle duplicate responses gracefully. ++ Accept Kali Linux for aptpkg salt execution module ++ Fix bug where cmd.which could not handle a dirname as an argument ++ Fix issue in ps.pgrep where exceptions were thrown on Windows. + +- Known Issues: ++ In multimaster mode, a minion may become temporarily unresponsive + if modules or pillars are refreshed at the same time that one + or more masters are down. This can be worked around by setting + 'auth_timeout' and 'auth_tries' down to shorter periods. +------------------------------------------------------------------- +Thu Feb 12 19:35:34 UTC 2015 - aboe76@gmail.com + +- New Bugfix release 2014.7.2: +- fix package bug with fdupes. +- keep sle 11 sp3 support. ++ Fix erroneous warnings for systemd service enabled check (issue 19606) ++ Fix FreeBSD kernel module loading, listing, and persistence kmod (issue 197151, issue 19682) ++ Allow case-sensitive npm package names in the npm state. This may break behavior + for people expecting the state to lowercase their npm package names for them. + The npm module was never affected by mandatory lowercasing. (issue 20329) ++ Deprecate the activate parameter for pip.install for both the module and the state. + If bin_env is given and points to a virtualenv, there is no need to activate that virtualenv + in a shell for pip to install to the virtualenv. ++ Fix a file-locking bug in gitfs (issue 18839) + +------------------------------------------------------------------- +Thu Jan 15 17:50:52 UTC 2015 - aboe76@gmail.com + +- New Bugfix release 2014.7.1: ++ Fixed gitfs serving symlinks in file.recurse states (issue 17700) ++ Fixed holding of multiple packages (YUM) when combined with version pinning (issue 18468) ++ Fixed use of Jinja templates in masterless mode with non-roots fileserver backend (issue 17963) ++ Re-enabled pillar and compound matching for mine and publish calls. Note that pillar globbing is still disabled for those modes, for security reasons. (issue 17194) ++ Fix for tty: True in salt-ssh (issue 16847) +- Needed to provide zsh completion because of the tarball missing the zsh completion script. +- Removed man salt.1.gz file from salt-master because upstream removed it. +- Added man salt.7.gz to salt-master package + +------------------------------------------------------------------- +Mon Nov 3 21:35:31 UTC 2014 - aboe76@gmail.com + +- Updated to Major Release 2014.7.0 +- added python-zipp as depency +- added recommend python-pygit2, this is the preferred gitfs backend of saltstack +- added zsh-completion package +- Removed Patch fix-service-py-version-parsing-sles.patch already fixed in this package +- Removed Patch pass-all-systemd-list-units.patch already fixed in this package +- Removed Patch disable-service-py-for-suse-family.patch already fixed in this package +- Removed Patch allow-systemd-units-no-unit-files.patch already fixed in this package +- Removed Patch allow-systemd-parameterized-services.patch already fixed in this package +- More information at: http://docs.saltstack.com/en/latest/topics/releases/2014.7.0.html +- SALT SSH ENHANCEMENTS: + + Support for Fileserver Backends + + Support for Saltfile + + Ext Pillar + + No more sshpass needed + + Pure Python Shim + + Custom Module Delivery + + CP module Support + + More Thin Directory Options + - Salt State System enhancements: + + New Imperative State Keyword "Listen" + + New Mod Aggregate Runtime Manipulator + + New Requisites: onchanges and onfail + + New Global onlyif and unless + + Use names to expand and override values + - Salt Major Features: + + Improved Scheduler Additions + + Red Hat 7 Support + + Fileserver Backends in Salt-call + + Amazon Execution Modules in salt-cloud + + LXC Runner Enhancements + + Next Gen Docker Management + + Peer System Performance Improvements + + SDB Encryption at rest for configs + + GPG Renderer encrypted pillar at rest + + OpenStack Expansions + + Queues System external queue systems into Salt events + + Multi Master Failover Additions + + Chef Execution Module + - salt-api Project Merge + + Synchronous and Asynchronous Execution of Runner and Wheel Modules + + rest_cherrypy Additions + + Web Hooks + - Fileserver Backend Enhancements: + + New gitfs Features + + Pygit2 and Dulwich support + + Mountpoints support + + New hgfs Features + + mountpoints support + + New svnfs Features: + + mountpoints + + minionfs Featuressupport + + mountpoints + - New Salt Modules: + + Oracle + + Random + + Redis + + Amazon Simple Queue Service + + Block Device Management + + CoreOS etcd + + Genesis + + InfluxDB + + Server Density + + Twilio Notifications + + Varnish + + ZNC IRC Bouncer + + SMTP + - NEW RUNNERS: + + Map/Reduce Style + + Queue + - NEW EXTERNAL PILLARS: + + CoreOS etcd + - NEW SALT-CLOUD PROVIDERS: + + Aliyun ECS Cloud + + LXC Containers + + Proxmox (OpenVZ containers & KVM) +- DEPRECATIONS: + + Salt.modules.virtualenv_mod +------------------------------------------------------------------- +Thu Oct 16 19:26:57 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.13 a bugfix release on 2014.1.12 + + fix module run exit code (issue 16420) + + salt cloud Check the exit status code of scp before assuming it has failed. (issue 16599) + + +------------------------------------------------------------------- +Fri Oct 10 18:47:07 UTC 2014 - aboe76@gmail.com +ff +- Updated to 2014.1.12 a bugfix release on 2014.1.11 + + Fix scp_file always failing (which broke salt-cloud) (issue 16437) + + Fix regression in pillar in masterless (issue 16210, issue 16416, issue 16428) + +------------------------------------------------------------------- +Wed Sep 10 18:10:50 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.11 is another bugfix release for 2014.1.0. Changes include: + + Fix for minion_id with byte-order mark (BOM) (issue 12296) + + Fix runas deprecation in at module + + Fix trailing slash befhavior for file.makedirs_ (issue 14019) + + Fix chocolatey path (issue 13870) + + Fix git_pillar infinite loop issues (issue 14671) + + Fix json outputter null case + + Fix for minion error if one of multiple masters are down (issue 14099) + + Updated the use-forking-daemon.patch with the right version + +------------------------------------------------------------------- +Mon Aug 18 13:06:07 UTC 2014 - tserong@suse.com + +- Fix service.py version parsing for SLE 11 + + Added fix-service-py-version-parsing-sles.patch + +------------------------------------------------------------------- +Tue Aug 12 09:44:43 UTC 2014 - tserong@suse.com + +- Remove salt-master's hard requirement for git and python-GitPython on SLE 12 + +------------------------------------------------------------------- +Wed Aug 6 06:36:02 UTC 2014 - tserong@suse.com + +- Ensure salt uses systemd for services on SLES + + Added disable-service-py-for-suse-family.patch + +------------------------------------------------------------------- +Mon Aug 4 16:12:14 UTC 2014 - aboe76@gmail.com + +- RPM spec update + + added service_add_pre function + +------------------------------------------------------------------- +Fri Aug 1 19:41:12 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.10: + + Version 2014.1.9 contained a regression which caused inaccurate Salt version + detection, and thus was never packaged for general release. This version + contains the version detection fix, but is otherwise identical to 2014.1.9. + + Version 2014.1.8 contained a regression which caused inaccurate Salt version + detection, and thus was never packaged for general release. This version + contains the version detection fix, but is otherwise identical to 2014.1.8. + +------------------------------------------------------------------- +Wed Jul 30 20:22:09 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.8: + + Ensure salt-ssh will not continue if permissions on a temporary directory are not correct. + + Use the bootstrap script distributed with Salt instead of relying on an external resource + + Remove unused testing code + + Ensure salt states are placed into the .salt directory in salt-ssh + + Use a randomized path for temporary files in a salt-cloud deployment + + Clean any stale directories to ensure a fresh copy of salt-ssh during a deployment + +------------------------------------------------------------------- +Thu Jul 24 13:11:03 UTC 2014 - tserong@suse.com + +- Allow salt to correctly detect services provided by init scripts + + Added allow-systemd-units-no-unit-files.patch + + Added allow-systemd-parameterized-services.patch + + Added pass-all-systemd-list-units.patch +- Move systemd service file fix to patch, add PIDFile parameter (this + fix is applicable for all SUSE versions, not just 12.3) + + Added use-forking-daemon.patch + +------------------------------------------------------------------- +Wed Jul 23 06:24:00 UTC 2014 - aboe76@gmail.com + +- Improve systemd service file fix for 12.3 + Use forking instead of Simple and daemonize salt-master process + +------------------------------------------------------------------- +Sat Jul 19 07:58:18 UTC 2014 - aboe76@gmail.com + +- Fixed bug in opensuse 12.3 systemd file + systemd 198 doesn't have python-systemd binding. +- Disabled testing on SLES + +------------------------------------------------------------------- +Thu Jul 10 18:25:05 UTC 2014 - aboe76@gmail.com + +- Update to 2014.7 + This release was a hotfix release for the regression listed above which was present in the 2014.1.6 +- Fix batch mode regression (issue 14046) + +------------------------------------------------------------------- +Wed Jul 9 06:42:05 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.6 +- Fix extra iptables --help output (Sorry!) (issue 13648, issue 13507, issue 13527, issue 13607) +- Fix mount.active for Solaris +- Fix support for allow-hotplug statement in debian_ip network module +- Add sqlite3 to esky builds +- Fix jobs.active output (issue 9526) +- Fix the virtual grain for Xen (issue 13534) +- Fix eauth for batch mode (issue 9605) +- Fix force-related issues with tomcat support (issue 12889) +- Fix KeyError when cloud mapping +- Fix salt-minion restart loop in Windows (issue 12086) +- Fix detection of service virtual module on Fedora minions +- Fix traceback with missing ipv4 grain (issue 13838) +- Fix issue in roots backend with invalid data in mtime_map (issue 13836) +- Fix traceback in jobs.active (issue 11151) + +------------------------------------------------------------------- +Wed Jun 11 18:53:38 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.5 +- Add function for finding cached job on the minion +- Fix for minion caching jobs when master is down +- Bump default `syndic_wait` to 5 to fix syndic-related problems + (issue 12262) +- Fix false positive error in logs for `makeconf` state (issue 9762) +- Fix for extra blank lines in `file.blockreplace` (issue 12422) +- Use system locale for ports package installations +- Fix for `cmd_iter`/`cmd_iter_no_block` blocking issues (issue 12617) +- Fix traceback when syncing custom types (issue 12883) +- Fix cleaning directory symlinks in `file.directory` +- Add performance optimizations for `saltutil.sync_all` and + `state.highstate` +- Fix possible error in `saltutil.running` +- Fix for kmod modules with dashes (issue 13239) +- Fix possible race condition for Windows minions in state module reloading + (issue 12370) +- Fix bug with roster for `passwd`s that are loaded as non-string objects + (issue 13249) +- Keep duplicate version numbers from showing up in `pkg.list_pkgs` output +- Fixes for Jinja renderer, timezone mod`module + `/mod`state ` (issue 12724) +- Fix timedatectl parsing for systemd>=210 (issue 12728) +- Removed the deprecated external nodes classifier (originally accessible by + setting a value for external_nodes in the master configuration file). Note + that this functionality has been marked deprecated for some time and was + replaced by the more general doc`master tops ` system. +- More robust escaping of ldap filter strings. +- Fix trailing slash in conf_master`gitfs_root` causing files not to be + available (issue 13185) + +------------------------------------------------------------------- +Tue Jun 10 21:10:44 UTC 2014 - aboe76@gmail.com + +- added bash completion package + +------------------------------------------------------------------- +Mon May 5 18:19:29 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.4 + - Fix setup.py dependency issue (issue 12031) + - Fix handling for IOErrors under certain circumstances (issue 11783 and issue 11853) + - Fix fatal exception when `/proc/1/cgroup` is not readable (issue 11619) + - Fix os grains for OpenSolaris (issue 11907) + - Fix `lvs.zero` module argument pass-through (issue 9001) + - Fix bug in `debian_ip` interaction with `network.system` state (issue 11164) + - Remove bad binary package verification code (issue 12177) + - Fix traceback in solaris package installation (issue 12237) + - Fix `file.directory` state symlink handling (issue 12209) + - Remove `external_ip` grain + - Fix `file.managed` makedirs issues (issue 10446) + - Fix hang on non-existent Windows drive letter for `file` module (issue 9880) + - Fix salt minion caching all users on the server (issue 9743) + +------------------------------------------------------------------- +Thu Apr 17 18:06:56 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.3 + - Fix username detection when su'ed to root on FreeBSD (issue 11628) + - Fix minionfs backend for file.recurse states + - Fix 32-bit packages of different arches than the CPU arch, on 32-bit RHEL/CentOS (issue 11822) + - Fix bug with specifying alternate home dir on user creation (FreeBSD) (issue 11790) + - Don’t reload site module on module refresh for MacOS + - Fix regression with running execution functions in Pillar SLS (issue 11453) + - Fix some modules missing from Windows installer + - Don’t log an error for yum commands that return nonzero exit status on non-failure (issue 11645) + - Fix bug in rabbitmq state (issue 8703) + - Fix missing ssh config options (issue 10604) + - Fix top.sls ordering (issue 10810 and issue 11691) + - Fix salt-key --list all (issue 10982) + - Fix win_servermanager install/remove function (issue 11038) + - Fix interaction with tokens when running commands as root (issue 11223) + - Fix overstate bug with find_job and **kwargs (issue 10503) + - Fix saltenv for aptpkg.mod_repo from pkgrepo state + - Fix environment issue causing file caching problems (issue 11189) + - Fix bug in __parse_key in registry state (issue 11408) + - Add minion auth retry on rejection (issue 10763) + - Fix publish_session updating the encryption key (issue 11493) + - Fix for bad AssertionError raised by GitPython (issue 11473) + - Fix debian_ip to allow disabling and enabling networking on Ubuntu (issue 11164) + - Fix potential memory leak caused by saved (and unused) events (issue 11582) + - Fix exception handling in the MySQL module (issue 11616) + - Fix environment-related error (issue 11534) + - Include psutil on Windows + - Add file.replace and file.search to Windows (issue 11471) + - Add additional file module helpers to Windows (issue 11235) + - Add pid to netstat output on Windows (issue 10782) + - Fix Windows not caching new versions of installers in winrepo (issue 10597) + - Fix hardcoded md5 hashing + - Fix kwargs in salt-ssh (issue 11609) + - Fix file backup timestamps (issue 11745) + - Fix stacktrace on sys.doc with invalid eauth (issue 11293) + - Fix git.latest with test=True (issue 11595) + - Fix file.check_perms hardcoded follow_symlinks (issue 11387) + - Fix certain pkg states for RHEL5/Cent5 machines (issue 11719) +- Packaging: + - python-psutil depencies (more functional modules out of the box) + - python-yaml depencies (more functional modules out of the box) + - python-requests depencies (salt-cloud) + + +------------------------------------------------------------------- +Wed Mar 19 19:29:13 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.1 Bug Fix release +- temporarily disabled integration check after consult with Upstream +------------------------------------------------------------------- +Thu Feb 20 19:10:20 UTC 2014 - aboe76@gmail.com + +- Updated to 2014.1.0 Major Release +- features: + - 2014.1.0 is the first release to follow the new date-based release naming system. + - Salt Cloud Merged into Salt + - Google Compute Engine support is added to salt-cloud. + - Salt Virt released + - Docker Integration + - IPv6 Support for iptables State/Module + - GitFS Improvements + - MinionFS + - saltenv + - Grains Caching + - Improved Command Logging Control + - PagerDuty Support + - Virtual Terminal + - Proxy Minions +- bugfixes: + - Fix mount.mounted leaving conflicting entries in fstab (:issue:`7079`) + - Fix mysql returner serialization to use json (:issue:`9590`) + - Fix ZMQError: Operation cannot be accomplished in current state errors (:issue:`6306`) + - Rbenv and ruby improvements + - Fix quoting issues with mysql port (:issue:`9568`) + - Update mount module/state to support multiple swap partitions (:issue:`9520`) + - Fix archive state to work with bsdtar + - Clarify logs for minion ID caching + - Add numeric revision support to git state (:issue:`9718`) + - Update master_uri with master_ip (:issue:`9694`) + - Add comment to Debian mod_repo (:issue:`9923`) + - Fix potential undefined loop variable in rabbitmq state (:issue:`8703`) + - Fix for salt-virt runner to delete key on VM deletion + - Fix for salt-run -d to limit results to specific runner or function (:issue:`9975`) + - Add tracebacks to jinja renderer when applicable (:issue:`10010`) + - Fix parsing in monit module (:issue:`10041`) + - Fix highstate output from syndic minions (:issue:`9732`) + - Quiet logging when dealing with passwords/hashes (:issue:`10000`) + - Fix for multiple remotes in git_pillar (:issue:`9932`) + - Fix npm installed command (:issue:`10109`) + - Add safeguards for utf8 errors in zcbuildout module + - Fix compound commands (:issue:`9746`) + - Add systemd notification when master is started + - Many doc improvements +- packaging: + - source tarball includes all packaging files in pkg folder. + - fixed rpmlint errors about duplicates. + - fixed rpmlint errors about non executables scripts. +------------------------------------------------------------------- +Sat Jan 25 20:21:12 UTC 2014 - aboe76@gmail.com + +- Updated to 0.17.5 a bugfix release for 0.17.0: + + +------------------------------------------------------------------- +Thu Dec 12 12:57:51 UTC 2013 - aboe76@gmail.com + +- Updated to 0.17.4 which is another bugfix release for 0.17.0: + - Fix some jinja render errors (issue 8418) + - Fix file.replace state changing file ownership (issue 8399) + - Fix state ordering with the PyDSL renderer (issue 8446) + - Fix for new npm version (issue 8517) + - Fix for pip state requiring name even with requirements file (issue 8519) + - Add sane maxrunning defaults for scheduler (issue 8563) + - Fix states duplicate key detection (issue 8053) + - Fix SUSE patch level reporting (issue 8428) + - Fix managed file creation umask (issue 8590) + - Fix logstash exception (issue 8635) + - Improve argument exception handling for salt command (issue 8016) + - Fix pecl success reporting (issue 8750) + - Fix launchctl module exceptions (issue 8759) + - Fix argument order in pw_user module + - Add warnings for failing grains (issue 8690) + - Fix hgfs problems caused by connections left open (issue 8811 and issue 8810) + - Fix installation of packages with dots in pkg name (issue 8614) + - Fix noarch package installation on CentOS 6 (issue 8945) + - Fix portage_config.enforce_nice_config (issue 8252) + - Fix salt.util.copyfile umask usage (issue 8590) + - Fix rescheduling of failed jobs (issue 8941) + - Fix conflicting options in postgres module (issue 8717) + - Fix ps modules for psutil >= 0.3.0 (issue 7432) + - Fix postgres module to return False on failure (issue 8778) + - Fix argument passing for args with pound signs (issue 8585) + - Fix pid of salt CLi command showing in status.pid output (issue 8720) + - Fix rvm to run gem as the correct user (issue 8951) + - Fix namespace issue in win_file module (issue 9060) + - Fix masterless state paths on windows (issue 9021) + - Fix timeout option in master config (issue 9040) + +------------------------------------------------------------------- +Thu Nov 21 15:33:06 UTC 2013 - speilicke@suse.com + +- Add bugzilla for solved issues + +------------------------------------------------------------------- +Fri Nov 15 18:57:46 UTC 2013 - aboe76@gmail.com + +- dropped python-urllib3 depency not in factory yet. + only needed with saltstack helium and higher + +------------------------------------------------------------------- +Thu Nov 14 22:05:06 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.17.2 Bugfix Release: + - Add ability to delete key with grains.delval (issue 7872) + - Fix possible state compiler stack trace (issue 5767) + - Fix grains targeting for new grains (issue 5737) + - Fix bug with merging in git_pillar (issue 6992) + - Fix print_jobs duplicate results + - Fix possible KeyError from ext_job_cache missing option + - Fix auto_order for - names states (issue 7649) + - Fix regression in new gitfs installs (directory not found error) + - Fix fileclient in case of master restart (issue 7987) + - Try to output warning if CLI command malformed (issue 6538) + - Fix --out=quiet to actually be quiet (issue 8000) + - Fix for state.sls in salt-ssh (issue 7991) + - Fix for MySQL grants ordering issue (issue 5817) + - Fix traceback for certain missing CLI args (issue 8016) + - Add ability to disable lspci queries on master (issue 4906) + - Fail if sls defined in topfile does not exist (issue 5998) + - Add ability to downgrade MySQL grants (issue 6606) + - Fix ssh_auth.absent traceback (issue 8043) + - Fix ID-related issues (issue 8052, issue 8050, and others) + - Fix for jinja rendering issues (issue 8066 and issue 8079) + - Fix argument parsing in salt-ssh (issue 7928) + - Fix some GPU detection instances (issue 6945) + - Fix bug preventing includes from other environments in SLS files + - Fix for kwargs with dashes (issue 8102) + - Fix apache.adduser without apachectl (issue 8123) + - Fix issue with evaluating test kwarg in states (issue 7788) + - Fix regression in salt.client.Caller() (issue 8078) + - Fix bug where cmd.script would try to run even if caching failed (issue 7601) + - Fix for mine data not being updated (issue 8144) + - Fix a Xen detection edge case (issue 7839) + - Fix version generation for when it's part of another git repo (issue 8090) + - Fix _handle_iorder stacktrace so that the real syntax error is shown (issue 8114 and issue 7905) + - Fix git.latest state when a commit SHA is used (issue 8163) + - Fix for specifying identify file in git.latest (issue 8094) + - Fix for --output-file CLI arg (issue 8205) + - Add ability to specify shutdown time for system.shutdown (issue 7833) + - Fix for salt version using non-salt git repo info (issue 8266) + - Add additional hints at impact of pkgrepo states when test=True (issue 8247) + - Fix for salt-ssh files not being owned by root (issue 8216) + - Fix retry logic and error handling in fileserver (related to issue 7755) + - Fix file.replace with test=True (issue 8279) + - Add flag for limiting file traversal in fileserver (issue 6928) + - Fix for extra mine processes (issue 5729) + - Fix for unloading custom modules (issue 7691) + - Fix for salt-ssh opts (issue 8005 and issue 8271) + - Fix compound matcher for grains (issue 7944) + - Add dir_mode to file.managed (issue 7860) + - Improve traceroute support for FreeBSD and OS X (issue 4927) + - Fix for matching minions under syndics (issue 7671) + - Improve exception handling for missing ID (issue 8259) + - Add configuration option for minion_id_caching + - Fix open mode auth errors (issue 8402) + +------------------------------------------------------------------- +Sun Nov 10 07:52:54 UTC 2013 - aboe76@gmail.com + +- In preparation of salt Helium all requirements of salt-cloud + absorbed in salt + +------------------------------------------------------------------- +Fri Nov 1 07:22:04 UTC 2013 - aboe76@gmail.com + +- Added salt-doc package with html documentation of salt + +------------------------------------------------------------------- +Thu Oct 31 21:25:24 UTC 2013 - aboe76@gmail.com + +- Disabled salt unit test, new test assert value not in 0.17.1 + +------------------------------------------------------------------- +Mon Oct 21 06:00:31 UTC 2013 - aboe76@gmail.com + +- Updated requirements python-markupsafe required for salt-ssh + +------------------------------------------------------------------- +Fri Oct 18 11:24:28 UTC 2013 - p.drouand@gmail.com + +- Don't support sysvinit and systemd for the same system; add conditionnal + macros to use systemd only on systems which support it and sysvinit + on other systems + +------------------------------------------------------------------- +Thu Oct 17 18:27:23 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.17.1 bugfix release (bnc#849205, bnc#849204, bnc#849184): + - Fix symbolic links in thin.tgz (:issue:`7482`) + - Pass env through to file.patch state (:issue:`7452`) + - Service provider fixes and reporting improvements (:issue:`7361`) + - Add --priv option for specifying salt-ssh private key + - Fix salt-thin's salt-call on setuptools installations (:issue:`7516`) + - Fix salt-ssh to support passwords with spaces (:issue:`7480`) + - Fix regression in wildcard includes (:issue:`7455`) + - Fix salt-call outputter regression (:issue:`7456`) + - Fix custom returner support for startup states (:issue:`7540`) + - Fix value handling in augeas (:issue:`7605`) + - Fix regression in apt (:issue:`7624`) + - Fix minion ID guessing to use socket.getfqdn() first (:issue:`7558`) + - Add minion ID caching (:issue:`7558`) + - Fix salt-key race condition (:issue:`7304`) + - Add --include-all flag to salt-key (:issue:`7399`) + - Fix custom grains in pillar (part of :issue:`5716`, :issue:`6083`) + - Fix race condition in salt-key (:issue:`7304`) + - Fix regression in minion ID guessing, prioritize socket.getfqdn() (:issue:`7558`) + - Cache minion ID on first guess (:issue:`7558`) + - Allow trailing slash in file.directory state + - Fix reporting of file_roots in pillar return (:issue:`5449` and :issue:`5951`) + - Remove pillar matching for mine.get (:issue:`7197`) + - Sanitize args for multiple execution modules + - Fix yumpkag mod_repo functions to filter hidden args (:issue:`7656`) + - Fix conflicting IDs in state includes (:issue:`7526`) + - Fix mysql_grants.absent string formatting issue (:issue:`7827`) + - Fix postgres.version so it won't return None (:issue:`7695`) + - Fix for trailing slashes in mount.mounted state + - Fix rogue AttributErrors in the outputter system (:issue:`7845`) + - Fix for incorrect ssh key encodings resulting in incorrect key added (:issue:`7718`) + - Fix for pillar/grains naming regression in python renderer (:issue:`7693`) + - Fix args/kwargs handling in the scheduler (:issue:`7422`) + - Fix logfile handling for file://, tcp:// and udp:// (:issue:`7754`) + - Fix error handling in config file parsing (:issue:`6714`) + - Fix RVM using sudo when running as non-root user (:issue:`2193`) + - Fix client ACL and underlying logging bugs (:issue:`7706`) + - Fix scheduler bug with returner (:issue:`7367`) + - Fix user management bug related to default groups (:issue:`7690`) + - Fix various salt-ssh bugs (:issue:`7528`) + - Many various documentation fixes + +------------------------------------------------------------------- +Thu Oct 3 06:01:23 UTC 2013 - aboe76@gmail.com + +- Updated init files to be inline with fedora/rhel packaging upstream + +------------------------------------------------------------------- +Mon Sep 30 18:56:27 UTC 2013 - aboe76@gmail.com + +- Cleaned up spec file: +- Unit testing can be done on all distributions + +------------------------------------------------------------------- +Sat Sep 28 19:11:10 UTC 2013 - aboe76@gmail.com + +- Updated package following salt package guidelins: + https://github.com/saltstack/salt/blob/develop/doc/topics/conventions/packaging.rst +- activated salt-testing for unit testing salt before releasing rpm +- updated docs +- added python-xml as dependency + +------------------------------------------------------------------- +Thu Sep 19 17:18:06 UTC 2013 - aboe76@gmail.com + +- Updated 0.17.0 Feature Release + Major features: + - halite (web Gui) + - salt ssh (remote execution/states over ssh) with its own package + - Rosters (list system targets not know to master) + - State Auto Order (state evaluation and execute in order of define) + - state.sls Runner (system orchestration from within states via master) + - Mercurial Fileserver Backend + - External Logging Handlers (sentry and logstash support) + - Jenkins Testing + - Salt Testing Project (testing libraries for salt) + - StormPath External Authentication support + - LXC Support (lxc support for salt-virt) + - Package dependencies reordering: + * salt-master requires python-pyzmq, and recommends python-halite + * salt-minion requires python-pyzmq + * salt-ssh requires sshpass + * salt-syndic requires salt-master + Minor features: + - 0.17.0 release wil be last release for 0.XX.X numbering system + Next release will be .. + +------------------------------------------------------------------- +Sat Sep 7 22:44:41 UTC 2013 - aboe76@gmail.com + +- Update 0.16.4 bugfix release: + - Multiple documentation improvements/additions + - Added the osfinger and osarch grains + - Fix bug in :mod:`hg.latest ` state + that would erroneously delete directories (:issue:`6661`) + - Fix bug related to pid not existing for + :mod:`ps.top ` (:issue:`6679`) + - Fix regression in :mod:`MySQL returner ` + (:issue:`6695`) + - Fix IP addresses grains (ipv4 and ipv6) to include all addresses + (:issue:`6656`) + - Fix regression preventing authenticated FTP (:issue:`6733`) + - Fix :mod:`file.contains ` on values + YAML parses as non-string (:issue:`6817`) + - Fix :mod:`file.get_gid `, + :mod:`file.get_uid `, and + :mod:`file.chown ` for broken symlinks + (:issue:`6826`) + - Fix comment for service reloads in service state (:issue:`6851`) + +------------------------------------------------------------------- +Fri Aug 9 18:08:12 UTC 2013 - aboe76@gmail.com + +- Update 0.16.3 bugfix release: + - Fixed scheduler config in pillar + - Fixed default value for file_recv master config option + - Fixed missing master configuration file parameters + - Fixed regression in binary package installation on 64-bit systems + - Fixed stackgrace when commenting a section in top.sls + - Fixed state declarations not formed as a list message. + - Fixed infinite loop on minion + - Fixed stacktrace in watch when state is 'prereq' + - Feature: function filter_by to grains module + - Feature: add new "osfinger" grain + +------------------------------------------------------------------- +Sat Aug 3 06:01:32 UTC 2013 - aboe76@gmail.com + +- Fixed regression bug in salt 0.16.2 + - Newly installed salt-minion doesn't create + /var/cache/salt/minion/proc + - fix let package create this directory + next version of Salt doesn't need this. + +------------------------------------------------------------------- +Fri Aug 2 05:36:08 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.16.2 + - gracefully handle lsb_release data when it is enclosed in quotes + - fixed pillar load from master config + - pillar function pillar.item and pillar.items instead of pillar.data + - fixed traceback when pillar sls is malformed + - gracefully handle quoted publish commands + - publich function publish.item and publish.items instead of publish.data + - salt-key usage in minionswarm script fixed + - minion random reauth_delay added to stagger re-auth attempts. + - improved user and group management + - improved file management + - improved package management + - service management custom initscripts support + - module networking hwaddr renamed to be in line with other modules + - fixed traceback in bridge.show + - fixed ssh know_hosts and auth.present output. + for more information: http://docs.saltstack.com/topics/releases/0.16.2.html + +------------------------------------------------------------------- +Mon Jul 29 20:11:14 UTC 2013 - aboe76@gmail.com + +- removed not needed requirements: + Requires(pre): /usr/sbin/groupadd + Requires(pre): /usr/sbin/useradd + Requires(pre): /usr/sbin/userdel +------------------------------------------------------------------- +Mon Jul 29 18:06:03 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.16.1 + - Bugfix release + - postgresql module Fixes #6352. + - returner fixes Fixes issue #5518 + - http authentication issues fixed #6356 + - warning of deprecation runas in favor of user +- more information at https://github.com/saltstack/salt/commits/v0.16.1 + +------------------------------------------------------------------- +Fri Jul 5 21:24:21 UTC 2013 - aboe76@gmail.com + +- Updated init files, rc_status instead of rc status. + +------------------------------------------------------------------- +Tue Jul 2 04:55:21 UTC 2013 - aboe76@gmail.com + +- Update to salt 0.16.0 final + - Multi-Master capability + - Prereq, the new requisite + - Peer system improvement + - Relative Includes + - More state Output Options + - Improved Windows Support + - Multi Targets for pkg.removed, pgk.purged States + - Random Times in cron states + - Confirmation Prompt on Key acceptance on master +- full changelog details: http://docs.saltstack.com/topics/releases/0.16.0.html +------------------------------------------------------------------- +Sat Jun 22 05:31:10 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.16.0RC +- New Features in 0.16.0: + - Multi-Master capability + - Prereq, the new requisite + - Peer system improvement + - Relative Includes + - More state Output Options + - Improved Windows Support + - Multi Targets for pkg.removed, pgk.purged States + - Random Times in cron states + - Confirmation Prompt on Key acceptance on master +- full changelog details: http://docs.saltstack.com/topics/releases/0.16.0.html + +------------------------------------------------------------------- +Wed Jun 12 20:48:36 UTC 2013 - aboe76@gmail.com + +- Updated init files from upstream, so init files are the same for + fedora/redhat/centos/debian/suse +- Removed salt user and daemon.conf file, so package is in line + with upstream packages fedora/centos/debian. + +------------------------------------------------------------------- +Sun Jun 2 07:39:03 UTC 2013 - aboe76@gmail.com + +- minor permission fix on salt config files to fix external auth + +------------------------------------------------------------------- +Sat Jun 1 21:51:07 UTC 2013 - aboe76@gmail.com + +- Service release 0.15.3 + showstoppers from 0.15.2: + - mine fix cross validity. + - redhat package issue + - pillar refresh fix + +------------------------------------------------------------------- +Wed May 29 16:10:42 UTC 2013 - aboe76@gmail.com + +- Service release 0.15.2 + xinetd service name not appended + virt-module uses qemu-img + publish.publish returns same info as salt-master + updated gitfs module + +------------------------------------------------------------------- +Mon May 27 20:42:06 UTC 2013 - aboe76@gmail.com + +- Fixed salt-master config file not readable by user 'salt' + +------------------------------------------------------------------- +Mon May 27 20:04:14 UTC 2013 - aboe76@gmail.com + +- Updated package spec: security enhancement. + added system user salt to run salt-master under privileged user 'salt' + added config dirs, master.d/minion.d/syndic.d to add config files. + added salt-daemon.conf were salt user is specified under salt-master. + +------------------------------------------------------------------- +Sun May 12 20:18:24 UTC 2013 - aboe76@gmail.com + +- Updated package spec, for systemd unit files + according to how systemd files needs to be packaged +- added logrotate on salt log files +- fixed rpmlint complain about reload function in init files + +------------------------------------------------------------------- +Wed May 8 21:44:49 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.15.1 +- bugfix release. +- fixes suse service check + +------------------------------------------------------------------- +Sat May 4 08:16:27 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.15.0 + Major update: + - salt mine function + - ipv6 support + - copy files from minions to master + - better template debugging + - state event firing + - major syndic updates + - peer system updates + - minion key revokation + - function return codes + - functions in overstate + - Pillar error reporting + - Cached State Data + - Monitoring states +- Read http://docs.saltstack.com/topics/releases/0.15.0.html for more information +- improved init files overwrite with /etc/default/salt + +------------------------------------------------------------------- +Tue Apr 23 19:18:29 UTC 2013 - aboe76@gmail.com + +- Updated init files: +- removed probe/reload/force reload + this isn't supported + +------------------------------------------------------------------- +Sun Apr 14 14:46:00 UTC 2013 - aboe76@gmail.com + +- Updated init files + +------------------------------------------------------------------- +Sun Apr 14 07:00:51 UTC 2013 - aboe76@gmail.com + +- Updated to 0.14.1 bugfix release: +- some major fixes for the syndic system, +- fixes to file.recurse and external auth and +- fixes for windows + +------------------------------------------------------------------- +Thu Apr 11 05:37:29 UTC 2013 - aboe76@gmail.com + +- Updated salt init files with option -d to really daemonize it + +------------------------------------------------------------------- +Sat Mar 23 23:51:53 UTC 2013 - aboe76@gmail.com + +- Updated to 0.14.0 + MAJOR FEATURES: + - Salt - As a Cloud Controller + - Libvirt State + - New get Functions + +------------------------------------------------------------------- +Tue Mar 19 06:46:36 UTC 2013 - aboe76@gmail.com + +- Updated to 0.13.3 + Last Bugfixes release before 0.14.0 + +------------------------------------------------------------------- +Wed Mar 13 22:04:43 UTC 2013 - aboe76@gmail.com + +- Updated 0.13.2 + Bugfixes release (not specified) + +------------------------------------------------------------------- +Mon Feb 25 17:52:59 UTC 2013 - aboe76@gmail.com + +- Updated spec file, postun removal of init.d files + +------------------------------------------------------------------- +Sat Feb 16 09:25:30 UTC 2013 - aboe76@gmail.com + +- Updated to Salt 0.13.1 bugfixes: +- Fix #3693 (variable ref'ed before assignment) +- Fix stack trace introduced with +- Updated limit to be escaped like before and after. +- Import install command from setuptools if we use them. +- Fix user info not displayed correctly when group doesn't map cleanly +- fix bug: Client.cache_dir() +- Fix #3717 +- Fix #3716 +- Fix cmdmod.py daemon error +- Updated test to properly determine homebrew user +- Fixed whitespace issue + +------------------------------------------------------------------- +Thu Feb 14 06:43:08 UTC 2013 - aboe76@gmail.com + +- Updated to salt 0.13.0 + +------------------------------------------------------------------- +Wed Jan 30 20:57:57 UTC 2013 - aboe76@gmail.com + +- Updated Suse Copyright in Spec-file + +------------------------------------------------------------------- +Mon Jan 28 15:23:08 UTC 2013 - toddrme2178@gmail.com + +- Cleanup spec file + +------------------------------------------------------------------- +Sat Jan 26 09:29:39 UTC 2013 - aboe76@gmail.com + +- split syndic from master in separate package + +------------------------------------------------------------------- +Tue Jan 22 17:53:39 UTC 2013 - aboe76@gmail.com + +- updated to salt 0.12.1 bugfix release + +------------------------------------------------------------------- +Wed Jan 16 06:38:40 UTC 2013 - aboe76@gmail.com + +- uploaded to salt 1.12.0 diff --git a/salt.spec b/salt.spec new file mode 100644 index 0000000..808901d --- /dev/null +++ b/salt.spec @@ -0,0 +1,1448 @@ +# +# spec file for package salt +# +# Copyright (c) 2021 SUSE LLC +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# +%global debug_package %{nil} + +%if 0%{?suse_version} > 1210 || 0%{?rhel} >= 7 || 0%{?fedora} >=28 +%bcond_without systemd +%else +%bcond_with systemd +%endif +%{!?python3_sitelib: %global python3_sitelib %(python3 -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} +%if 0%{?suse_version} > 1110 +%bcond_without bash_completion +%bcond_without fish_completion +%bcond_without zsh_completion +%else +%bcond_with bash_completion +%bcond_with fish_completion +%bcond_with zsh_completion +%endif +%bcond_with test +%bcond_without docs +%bcond_with builddocs + +Name: salt +Version: 3006.0 +Release: 0 +Summary: A parallel remote execution system +License: Apache-2.0 +Group: System/Management +Url: https://saltproject.io/ +Source: v%{version}.tar.gz +Source1: README.SUSE +Source2: salt-tmpfiles.d +Source3: html.tar.bz2 +Source4: update-documentation.sh +Source5: travis.yml +Source6: transactional_update.conf + +### SALT PATCHES LIST BEGIN +### IMPORTANT: The line above is used as a snippet marker. Do not touch it. + +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/88f40fff3b81edaa55f37949f56c67112ca2dcad +Patch1: run-salt-master-as-dedicated-salt-user.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/cdecbbdf5db3f1cb6b603916fecd80738f5fae9a +Patch2: run-salt-api-as-user-salt-bsc-1064520.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/c44b897eb1305c6b9c341fc16f729d2293ab24e4 +Patch3: activate-all-beacons-sources-config-pillar-grains.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/3c83bab3da101223c99af1f9ee2f3bf5e97be3f8 +Patch4: avoid-excessive-syslogging-by-watchdog-cronjob-58.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/1b9a160f578cf446f5ae622a450d23022e7e3ca5 +Patch5: fix-bsc-1065792.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/fec7f65b4debede8cf0eef335182fce2206e200d +Patch6: enable-passing-a-unix_socket-for-mysql-returners-bsc.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/90 +Patch7: add-environment-variable-to-know-if-yum-is-invoked-f.patch + +#### SUSE CAPABILITIES - unified #### +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/713ccfdc5c6733495d3ce7f26a8cfeddb8e9e9c4 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/b713d0b3031faadc17cd9cf09977ccc19e50bef7 +Patch8: add-custom-suse-capabilities-as-grains.patch +########### + +#### SUSE SLES-ES SUPPORT #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57166 +Patch9: fix-for-suse-expanded-support-detection.patch +############ + +#### ADLER - unified #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/48812 +# (closed upstream in favor of different solution - might affect server_id) +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/159 +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/73e357d7eee19a73cade22becb30d9689cae27ba +Patch10: use-adler32-algorithm-to-compute-string-checksums.patch +########### + +#### X509 - unified #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/56819 +Patch11: x509-fixes-111.patch +########### + +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58054 +Patch12: do-not-load-pip-state-if-there-is-no-3rd-party-depen.patch + +#### SALT SUPPORT - unified #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57054 +Patch13: early-feature-support-config.patch +########### + +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57071 +Patch14: make-aptpkg.list_repos-compatible-on-enabled-disable.patch + +### DEBIAN INFO_INSTALLED - unified ### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/50453 +# (master PR not yet created - codejam) +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/50453 +# https://github.com/saltstack/salt/commit/e20362f6f053eaa4144583604e6aac3d62838419 +# Can be dropped one pull/50453 is in released version. +Patch15: debian-info_installed-compatibility-50453.patch +########### + +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/116 (missing upstream PR to master) +Patch16: return-the-expected-powerpc-os-arch-bsc-1117995.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51119 (master PR not yet created) +Patch17: fix-issue-2068-test.patch +# PATCH_FIX_OPENSUSE Temporary fix allowing "id_" and "force" params while upstrem figures it out +Patch18: temporary-fix-extend-the-whitelist-of-allowed-comman.patch + +### FQDNS #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51384 (master PR not yet created) +Patch19: include-aliases-in-the-fqdns-grains.patch +########### + +#### BATCH ASYNC - unified ##### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60269 +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/50546 +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/51863 +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/139 +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/141 +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/144 +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52855 +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/6af07030a502c427781991fc9a2b994fa04ef32e +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/002543df392f65d95dbc127dc058ac897f2035ed +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/55d8a777d6a9b19c959e14a4060e5579e92cd106 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/8378bb24a5a53973e8dba7658b8b3465d967329f +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/182 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/190 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/217 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/8a23030d347b7487328c0395f5e30ef29daf1455 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/a38adfa2efe40c2b1508b685af0b5d28a6bbcfc8 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/b4c401cfe6031b61e27f7795bfa1aca6e8341e52 +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/320 +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/commit/25b4e3ea983b2606b2fb3d3c0e42f9840208bf84 (cleanup local code) +Patch20: async-batch-implementation.patch +########### + +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/52743 +Patch21: switch-firewalld-state-to-use-change_interface.patch + +### STANDALONE FORMULA CONFIGURATION ### +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/8ad65d6fa39edc7fc1967e2df1f3db0aa7df4d11 +Patch22: add-standalone-configuration-file-for-enabling-packa.patch +############# + +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/177 +# (deviation from upstream - we should probably port this) +Patch23: restore-default-behaviour-of-pkg-list-return.patch +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/186 (missing upstream PR to master) +Patch24: read-repo-info-without-using-interpolation-bsc-11356.patch +# PATCH_FIX_OPENSUSE https://github.com/openSUSE/salt/pull/191 (missing upstream PR to master) +Patch25: let-salt-ssh-use-platform-python-binary-in-rhel8-191.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/a18ac47b75550bd55f4ca91dc221ed408881984c +Patch26: make-setup.py-script-to-not-require-setuptools-9.1.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/228 (missing upstream PR) +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/da936daeebd701e147707ad814c07bfc259d4be (not yet upstream PR) +Patch27: add-publish_batch-to-clearfuncs-exposed-methods.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/57779 +Patch28: info_installed-works-without-status-attr-now.patch +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58552 +Patch29: zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch + +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/275 (missing upstream PR) +Patch30: bsc-1176024-fix-file-directory-user-and-group-owners.patch + +#### NO VENDOR CHANGE #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/60421 +Patch31: allow-vendor-change-option-with-zypper.patch +########### + +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58784 +Patch32: add-migrated-state-and-gpg-key-management-functions-.patch + +### BEACON CONFIG ### +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/commit/5ea2f10b15684dd417bad858642faafc92cd382 +# (revert https://github.com/saltstack/salt/pull/58655) +Patch33: revert-fixing-a-use-case-when-multiple-inotify-beaco.patch +########### + +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/298 (missing upstream PR) +Patch34: fix-salt.utils.stringutils.to_str-calls-to-make-it-w.patch + +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/307 (missing upstream PR) +Patch35: add-sleep-on-exception-handling-on-minion-connection.patch + +### SALT-SSH PROCESSING TARGETS ### +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/336 (missing upstream PR) +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/353 (missing upstream PR) +Patch36: update-target-fix-for-salt-ssh-to-process-targets-li.patch +############ + +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/58503 +Patch37: fix-missing-minion-returns-in-batch-mode-360.patch + +#### OPENSCAP ENHANCE #### +# PATCH-FIX_UPSTREAM https://github.com/saltstack/salt/pull/59756 +Patch38: enhance-openscap-module-add-xccdf_eval-call-386.patch +############### + +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/413 (missing upstream PR) +Patch39: don-t-use-shell-sbin-nologin-in-requisites.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/432 (missing upstream PR) +Patch40: fix-traceback.print_exc-calls-for-test_pip_state-432.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/415 (missing upstream PR) +Patch41: prevent-pkg-plugins-errors-on-missing-cookie-path-bs.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61180 +Patch42: dnfnotify-pkgset-plugin-implementation-3002.2-450.patch +# PATCH-FIX_OPENSUSE https://github.com/openSUSE/salt/pull/456 (missing upstream PR) +Patch43: fix-the-regression-for-yumnotify-plugin-456.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/482 +Patch44: drop-serial-from-event.unpack-in-cli.batch_async.patch + +### SALT-SSH WITH SALT BUNDLE ### +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61715 (ssh_pre_flight_args) +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/493 +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/497 +Patch45: add-salt-ssh-support-with-venv-salt-minion-3004-493.patch +Patch46: prevent-shell-injection-via-pre_flight_script_args-4.patch +############### + +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/501 +Patch47: fix-salt-ssh-opts-poisoning-bsc-1197637-3004-501.patch + +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/505 +Patch48: prevent-affection-of-ssh.opts-with-lazyloader-bsc-11.patch + +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/506 +Patch49: fix-regression-with-depending-client.ssh-on-psutil-b.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62109 +Patch50: use-salt-bundle-in-dockermod.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61984 +Patch51: save-log-to-logfile-with-docker.build.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/534 +Patch52: fix-ownership-of-salt-thin-directory-when-using-the-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62209 +Patch53: add-support-for-gpgautoimport-539.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62519 +Patch54: change-the-delimeters-to-prevent-possible-tracebacks.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62898 +Patch55: pass-the-context-to-pillar-ext-modules.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/commit/c6be36eeea49ee0d0641da272087305f79c32c99 (not yet upstream) +# Fix problem caused by: https://github.com/openSUSE/salt/pull/493 (Patch47) affecting only 3005.1. +Patch56: use-rlock-to-avoid-deadlocks-in-salt-ssh.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61064 +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/commit/5e3ff4d662321c237ddd5b2c5c83f35a84af594c (not PR to master yet) +Patch57: fixes-for-python-3.10-502.patch +# PATCH-FIX-OPENSUSE: https://github.com/openSUSE/salt/pull/571 +Patch58: control-the-collection-of-lvm-grains-via-config.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/63460 +Patch59: 3005.1-implement-zypper-removeptf-573.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/63460 +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/578 +Patch60: skip-package-names-without-colon-bsc-1208691-578.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/commit/c0fae09e5a4f6997a60007d970c7c6a5614d9102 +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 +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64599 +Patch74: prevent-possible-exceptions-on-salt.utils.user.get_g.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/592 +Patch75: fix-tests-to-make-them-running-with-salt-testsuite.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/f82860b8ad3ee786762fa02fa1a6eaf6e24dc8d4 +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/65020 +Patch76: do-not-fail-on-bad-message-pack-message-bsc-1213441-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64510 +Patch77: make-sure-configured-user-is-properly-set-by-salt-bs.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/64959 +Patch78: fixed-gitfs-cachedir_basename-to-avoid-hash-collisio.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/600 +Patch79: revert-usage-of-long-running-req-channel-bsc-1213960.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/65238 +Patch80: write-salt-version-before-building-when-using-with-s.patch + +### IMPORTANT: The line below is used as a snippet marker. Do not touch it. +### SALT PATCHES LIST END + +BuildRoot: %{_tmppath}/%{name}-%{version}-build +BuildRequires: logrotate +%if 0%{?suse_version} > 1020 +BuildRequires: fdupes +%endif + +Requires: python3-%{name} = %{version}-%{release} +Obsoletes: python2-%{name} + +Requires(pre): %{_sbindir}/groupadd +Requires(pre): %{_sbindir}/useradd + +%if 0%{?suse_version} +Requires(pre): %fillup_prereq +Requires(pre): shadow +%endif + +%if 0%{?suse_version} +Requires(pre): dbus-1 +%else +Requires(pre): dbus +%endif + +Requires: logrotate +Requires: procps + +%if 0%{?suse_version} >= 1500 +Requires: iproute2 +%else +%if 0%{?suse_version} +Requires: net-tools +%else +Requires: iproute +%endif +%endif + +%if %{with systemd} +BuildRequires: pkgconfig(systemd) +%{?systemd_ordering} +%else +%if 0%{?suse_version} +Requires(pre): %insserv_prereq +%endif +%endif + +%if %{with fish_completion} +%define fish_dir %{_datadir}/fish/ +%define fish_completions_dir %{_datadir}/fish/completions/ +%endif + +%if %{with bash_completion} +%if 0%{?suse_version} >= 1140 +BuildRequires: bash-completion +%else +BuildRequires: bash +%endif +%endif + +%if %{with zsh_completion} +BuildRequires: zsh +%endif + +%if 0%{?rhel} || 0%{?fedora} +BuildRequires: yum +%endif + +%description +Salt is a distributed remote execution system used to execute commands and +query data. It was developed in order to bring the best solutions found in +the world of remote execution together and make them better, faster and more +malleable. Salt accomplishes this via its ability to handle larger loads of +information, and not just dozens, but hundreds or even thousands of individual +servers, handle them quickly and through a simple and manageable interface. + +%package -n python3-salt +Summary: python3 library for salt +Group: System/Management +Requires: %{name} = %{version}-%{release} +BuildRequires: python-rpm-macros +%if 0%{?rhel} == 8 +BuildRequires: platform-python +%else +BuildRequires: python3 +%endif +BuildRequires: python3-devel +BuildRequires: python3-setuptools +# requirements/base.txt +%if 0%{?rhel} || 0%{?fedora} +BuildRequires: python3-jinja2 +BuildRequires: python3-markupsafe +BuildRequires: python3-msgpack > 0.3 +BuildRequires: python3-zmq >= 2.2.0 +BuildRequires: python3-m2crypto +%else +BuildRequires: python3-Jinja2 +BuildRequires: python3-MarkupSafe +BuildRequires: python3-msgpack-python > 0.3 +BuildRequires: python3-pyzmq >= 2.2.0 +%if 0%{?suse_version} >= 1500 +BuildRequires: python3-M2Crypto +%else +BuildRequires: python3-pycrypto >= 2.6.1 +%endif +%endif +BuildRequires: python3-PyYAML +BuildRequires: python3-psutil +BuildRequires: python3-requests >= 1.0.0 +BuildRequires: python3-distro +BuildRequires: python3-looseversion +BuildRequires: python3-packaging + +# requirements/zeromq.txt +%if %{with test} +BuildRequires: python3-boto >= 2.32.1 +BuildRequires: python3-mock +BuildRequires: python3-moto >= 0.3.6 +BuildRequires: python3-pip +BuildRequires: python3-salt-testing >= 2015.2.16 +BuildRequires: python3-unittest2 +BuildRequires: python3-xml +%endif +%if %{with builddocs} +BuildRequires: python3-sphinx +%endif +%if 0%{?rhel} == 8 +Requires: platform-python +%else +Requires: python3 +%endif +# requirements/base.txt +%if 0%{?rhel} || 0%{?fedora} +Requires: python3-jinja2 +Requires: yum +Requires: python3-markupsafe +Requires: python3-msgpack > 0.3 +Requires: python3-m2crypto +Requires: python3-zmq >= 2.2.0 + +%if 0%{?rhel} == 8 || 0%{?fedora} >= 30 +Requires: dnf +%endif +%if 0%{?rhel} == 6 +Requires: yum-plugin-security +%endif +%else # SUSE +Requires: python3-Jinja2 +Requires: python3-MarkupSafe +Requires: python3-msgpack-python > 0.3 +%if 0%{?suse_version} >= 1500 +Requires: python3-M2Crypto +%else +Requires: python3-pycrypto >= 2.6.1 +%endif +Requires: python3-pyzmq >= 2.2.0 +%endif # end of RHEL / SUSE specific section +Requires: python3-jmespath +Requires: python3-PyYAML +Requires: python3-psutil +Requires: python3-requests >= 1.0.0 +Requires: python3-distro +Requires: python3-looseversion +Requires: python3-packaging +Requires: python3-contextvars +%if 0%{?suse_version} +# required for zypper.py +Requires: python3-rpm +Requires(pre): libzypp(plugin:system) >= 0 +Requires: python3-zypp-plugin +# requirements/opt.txt (not all) +# Suggests: python-MySQL-python ## Disabled for now, originally Recommended +Suggests: python3-timelib +Suggests: python3-gnupg +# requirements/zeromq.txt +%endif +# +%if 0%{?suse_version} +# python-xml is part of python-base in all rhel versions +Requires: python3-xml +Suggests: python3-Mako +Recommends: python3-netaddr +Recommends: python3-pyinotify +%endif + +Provides: bundled(python3-tornado) = 4.5.3 + +%description -n python3-salt +Python3 specific files for salt + +%package api +Summary: The api for Salt a parallel remote execution system +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: %{name}-master = %{version}-%{release} +%if 0%{?suse_version} +Requires: python3-CherryPy >= 3.2.2 +%else +Requires: python3-cherrypy >= 3.2.2 +%endif + +%description api +salt-api is a modular interface on top of Salt that can provide a variety of entry points into a running Salt system. + +%package cloud +Summary: Generic cloud provisioning tool for Saltstack +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: %{name}-master = %{version}-%{release} +Requires: python3-apache-libcloud +%if 0%{?suse_version} +Recommends: python3-botocore +Recommends: python3-netaddr +%endif + +%description cloud +public cloud VM management system +provision virtual machines on various public clouds via a cleanly +controlled profile and mapping system. + +%if %{with docs} +%package doc +Summary: Documentation for salt, a parallel remote execution system +Group: Documentation/HTML +Requires: %{name} = %{version} + +%description doc +This contains the documentation of salt, it is an offline version of http://docs.saltstack.com. +%endif + +%package master +Summary: The management component of Saltstack with zmq protocol supported +Group: System/Management +Requires: %{name} = %{version}-%{release} +%if 0%{?suse_version} +Recommends: python3-pygit2 >= 0.20.3 +%endif +%ifarch %{ix86} x86_64 +%if 0%{?suse_version} +%if 0%{?suse_version} > 1110 +Requires: dmidecode +%else +Requires: pmtools +%endif +%endif +%endif +%if %{with systemd} +%{?systemd_requires} +BuildRequires: systemd +%else +%if 0%{?suse_version} +Requires(pre): %insserv_prereq +%endif +%endif +%if 0%{?suse_version} +Requires(pre): %fillup_prereq +%endif + +%description master +The Salt master is the central server to which all minions connect. +Enabled commands to remote systems to be called in parallel rather +than serially. + +%package minion +Summary: The client component for Saltstack +Group: System/Management +Requires: %{name} = %{version}-%{release} +%if 0%{?suse_version} > 1500 || 0%{?sle_version} > 150000 +Requires: (%{name}-transactional-update = %{version}-%{release} if read-only-root-fs) +%endif + +%if %{with systemd} +%{?systemd_requires} +%else +%if 0%{?suse_version} +Requires(pre): %insserv_prereq +%endif +%endif +%if 0%{?suse_version} +Requires(pre): %fillup_prereq +%endif + +%description minion +Salt minion is queried and controlled from the master. +Listens to the salt master and execute the commands. + +%package proxy +Summary: Component for salt that enables controlling arbitrary devices +Group: System/Management +Requires: %{name} = %{version}-%{release} +%if %{with systemd} +%{?systemd_requires} +%else +%if 0%{?suse_version} +Requires(pre): %insserv_prereq +%endif +%endif +%if 0%{?suse_version} +Requires(pre): %fillup_prereq +%endif + +%description proxy +Proxy minions are a developing Salt feature that enables controlling devices that, +for whatever reason, cannot run a standard salt-minion. +Examples include network gear that has an API but runs a proprietary OS, +devices with limited CPU or memory, or devices that could run a minion, but for +security reasons, will not. + + +%package syndic +Summary: The syndic component for saltstack +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: %{name}-master = %{version}-%{release} +%if %{with systemd} +%{?systemd_requires} +%else +%if 0%{?suse_version} +Requires(pre): %insserv_prereq +%endif +%endif +%if 0%{?suse_version} +Requires(pre): %fillup_prereq +%endif + +%description syndic +Salt syndic is the master-of-masters for salt +The master of masters for salt-- it enables +the management of multiple masters at a time.. + +%package ssh +Summary: Management component for Saltstack with ssh protocol +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: %{name}-master = %{version}-%{release} +%if 0%{?suse_version} +Recommends: sshpass +%endif +%if %{with systemd} +%{?systemd_requires} +%else +%if 0%{?suse_version} +Requires(pre): %insserv_prereq +%endif +%endif +%if 0%{?suse_version} +Requires(pre): %fillup_prereq +%endif + +%description ssh +Salt ssh is a master running without zmq. +it enables the management of minions over a ssh connection. + +%package tests +Summary: Unit and integration tests for Salt +Requires: %{name} = %{version}-%{release} + +%description tests +Collections of unit and integration tests for Salt + +%if %{with bash_completion} +%package bash-completion +Summary: Bash Completion for %{name} +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: bash-completion +%if 0%{?suse_version} > 1110 +BuildArch: noarch +%endif + +%description bash-completion +Bash command line completion support for %{name}. + +%endif + +%if %{with fish_completion} +%package fish-completion +Summary: Fish Completion for %{name} +Group: System/Management +Requires: %{name} = %{version}-%{release} + +%if 0%{?suse_version} > 1110 +BuildArch: noarch +%endif + +%description fish-completion +Fish command line completion support for %{name}. +%endif + +%if %{with zsh_completion} +%package zsh-completion +Summary: Zsh Completion for %{name} +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: zsh +%if 0%{?suse_version} > 1110 +BuildArch: noarch +%endif + +%description zsh-completion +Zsh command line completion support for %{name}. + +%endif + +%package standalone-formulas-configuration +Summary: Standalone Salt configuration to make the packaged formulas available for the Salt master +Group: System/Management +Requires: %{name} +Provides: salt-formulas-configuration +Conflicts: otherproviders(salt-formulas-configuration) + +%description standalone-formulas-configuration +This package adds the standalone configuration for the Salt master in order to make the packaged Salt formulas available on the Salt master + +%package transactional-update +Summary: Transactional update executor configuration +Group: System/Management +Requires: %{name} = %{version}-%{release} +Requires: %{name}-minion = %{version}-%{release} +Requires: tar + +%description transactional-update +For transactional systems, like MicroOS, Salt can operate +transparently if the executor "transactional-update" is registered in +list of active executors. This package add the configuration file. + + +%prep +%setup -q -n salt-%{version}-suse +cp %{S:1} . +cp %{S:5} ./.travis.yml +cp %{S:6} . +%autopatch -p1 + +%build +# Putting /usr/bin at the front of $PATH is needed for RHEL/RES 7. Without this +# change, the RPM will require /bin/python, which is not provided by any package +# on RHEL/RES 7. +%if 0%{?fedora} || 0%{?rhel} +export PATH=/usr/bin:$PATH +%endif +python3 setup.py --with-salt-version=%{version} --salt-transport=both build +mv build _build.python3 + +%if %{with docs} && %{without builddocs} +# extract docs from the tarball +mkdir -p doc/_build +pushd doc/_build/ +tar xfv %{S:3} +popd +%endif + +%if %{with docs} && %{with builddocs} +## documentation +cd doc && make html && rm _build/html/.buildinfo && rm _build/html/_images/proxy_minions.png && cd _build/html && chmod -R -x+X * +%endif + +%install +mv _build.python3 build +python3 setup.py --salt-transport=both install --prefix=%{_prefix} --root=%{buildroot} +mv build _build.python3 + +DEF_PYPATH=_build.python3/scripts-*/ + +rm -f %{buildroot}%{_bindir}/* +for script in $DEF_PYPATH/*; do + install -m 0755 $script %{buildroot}%{_bindir} +done + +## create missing directories +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/cloud +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master/jobs +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master/proc +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master/queues +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master/roots +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master/syndics +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/master/tokens +install -Dd -m 0750 %{buildroot}%{_localstatedir}/cache/salt/minion/extmod +install -Dd -m 0750 %{buildroot}%{_localstatedir}/log/salt +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/ +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/cloud.maps.d +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/cloud.profiles.d +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/cloud.providers.d +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/master.d +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/minion.d +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/master +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/master/minions +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/master/minions_autosign +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/master/minions_denied +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/master/minions_pre +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/master/minions_rejected +install -Dd -m 0750 %{buildroot}%{_sysconfdir}/salt/pki/minion +install -Dd -m 0750 %{buildroot}/srv/pillar +install -Dd -m 0750 %{buildroot}/srv/salt +install -Dd -m 0750 %{buildroot}/srv/spm +install -Dd -m 0750 %{buildroot}/var/lib/salt +install -Dd -m 0755 %{buildroot}%{_docdir}/salt +install -Dd -m 0755 %{buildroot}%{_sbindir} +install -Dd -m 0755 %{buildroot}%{_sysconfdir}/logrotate.d/ + +# Install salt-support profiles +install -Dpm 0644 salt/cli/support/profiles/* %{buildroot}%{python3_sitelib}/salt/cli/support/profiles + +# Install Salt tests +install -Dd -m 0750 %{buildroot}%{_datadir}/salt +install -Dd -m 0750 %{buildroot}%{_datadir}/salt/tests +cp -a tests/* %{buildroot}%{_datadir}/salt/tests/ +sed -i '1s=^#!/usr/bin/\(python\|env python\)[0-9.]*=#!/usr/bin/python3=' %{buildroot}%{_datadir}/salt/tests/runtests.py + +## Install Zypper plugins only on SUSE machines +%if 0%{?suse_version} +install -Dd -m 0750 %{buildroot}%{_prefix}/lib/zypp/plugins/commit +%{__install} scripts/suse/zypper/plugins/commit/zyppnotify %{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify +sed -i '1s=^#!/usr/bin/\(python\|env python\)[0-9.]*=#!/usr/bin/python3=' %{buildroot}%{_prefix}/lib/zypp/plugins/commit/zyppnotify +%endif + +# Install Yum plugins only on RH machines +%if 0%{?fedora} || 0%{?rhel} +%if 0%{?fedora} >= 22 || 0%{?rhel} >= 8 +install -Dd %{buildroot}%{python3_sitelib}/dnf-plugins +install -Dd %{buildroot}%{python3_sitelib}/dnf-plugins/__pycache__ +install -Dd %{buildroot}%{_sysconfdir}/dnf/plugins +%{__install} scripts/suse/dnf/plugins/dnfnotify.py %{buildroot}%{python3_sitelib}/dnf-plugins +%{__install} scripts/suse/dnf/plugins/dnfnotify.conf %{buildroot}%{_sysconfdir}/dnf/plugins +%{__python3} -m compileall -d %{python3_sitelib}/dnf-plugins %{buildroot}%{python3_sitelib}/dnf-plugins/dnfnotify.py +%{__python3} -O -m compileall -d %{python3_sitelib}/dnf-plugins %{buildroot}%{python3_sitelib}/dnf-plugins/dnfnotify.py +%else +install -Dd %{buildroot}%{_prefix}/share/yum-plugins +install -Dd %{buildroot}%{_sysconfdir}/yum/pluginconf.d +%{__install} scripts/suse/yum/plugins/yumnotify.py %{buildroot}%{_prefix}/share/yum-plugins +%{__install} scripts/suse/yum/plugins/yumnotify.conf %{buildroot}%{_sysconfdir}/yum/pluginconf.d +%{__python} -m compileall -d %{_prefix}/share/yum-plugins %{buildroot}%{_prefix}/share/yum-plugins/yumnotify.py +%{__python} -O -m compileall -d %{_prefix}/share/yum-plugins %{buildroot}%{_prefix}/share/yum-plugins/yumnotify.py +%endif +%endif + +## install init and systemd scripts +%if %{with systemd} +install -Dpm 0644 pkg/old/suse/salt-master.service %{buildroot}%{_unitdir}/salt-master.service +%if 0%{?suse_version} +install -Dpm 0644 pkg/old/suse/salt-minion.service %{buildroot}%{_unitdir}/salt-minion.service +%else +install -Dpm 0644 pkg/old/suse/salt-minion.service.rhel7 %{buildroot}%{_unitdir}/salt-minion.service +%endif +install -Dpm 0644 pkg/common/salt-syndic.service %{buildroot}%{_unitdir}/salt-syndic.service +install -Dpm 0644 pkg/old/suse/salt-api.service %{buildroot}%{_unitdir}/salt-api.service +install -Dpm 0644 pkg/common/salt-proxy@.service %{buildroot}%{_unitdir}/salt-proxy@.service +ln -s service %{buildroot}%{_sbindir}/rcsalt-master +ln -s service %{buildroot}%{_sbindir}/rcsalt-syndic +ln -s service %{buildroot}%{_sbindir}/rcsalt-minion +ln -s service %{buildroot}%{_sbindir}/rcsalt-api +install -Dpm 644 %{S:2} %{buildroot}/usr/lib/tmpfiles.d/salt.conf +%else +mkdir -p %{buildroot}%{_initddir} +## install init scripts +install -Dpm 0755 pkg/old/suse/salt-master %{buildroot}%{_initddir}/salt-master +install -Dpm 0755 pkg/old/suse/salt-syndic %{buildroot}%{_initddir}/salt-syndic +install -Dpm 0755 pkg/old/suse/salt-minion %{buildroot}%{_initddir}/salt-minion +install -Dpm 0755 pkg/old/suse/salt-api %{buildroot}%{_initddir}/salt-api +ln -sf %{_initddir}/salt-master %{buildroot}%{_sbindir}/rcsalt-master +ln -sf %{_initddir}/salt-syndic %{buildroot}%{_sbindir}/rcsalt-syndic +ln -sf %{_initddir}/salt-minion %{buildroot}%{_sbindir}/rcsalt-minion +ln -sf %{_initddir}/salt-api %{buildroot}%{_sbindir}/rcsalt-api +%endif + +## Install sysV salt-minion watchdog for SLES11 and RHEL6 +%if 0%{?rhel} == 6 || 0%{?suse_version} == 1110 +install -Dpm 0755 scripts/suse/watchdog/salt-daemon-watcher %{buildroot}%{_bindir}/salt-daemon-watcher +%endif + +# +## install config files +install -Dpm 0640 conf/minion %{buildroot}%{_sysconfdir}/salt/minion +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 +install -Dpm 0640 conf/cloud.profiles %{buildroot}%{_sysconfdir}/salt/cloud.profiles +install -Dpm 0640 conf/cloud.providers %{buildroot}%{_sysconfdir}/salt/cloud.providers +install -Dpm 0640 transactional_update.conf %{buildroot}%{_sysconfdir}/salt/minion.d/transactional_update.conf +# +## install logrotate file (for RHEL6 we use without sudo) +%if 0%{?rhel} > 6 || 0%{?suse_version} +install -Dpm 0644 pkg/old/suse/salt-common.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/salt +%else +install -Dpm 0644 pkg/common/salt-common.logrotate %{buildroot}%{_sysconfdir}/logrotate.d/salt +%endif +# +%if 0%{?suse_version} <= 1500 +## install SuSEfirewall2 rules +install -Dpm 0644 pkg/old/suse/salt.SuSEfirewall2 %{buildroot}%{_sysconfdir}/sysconfig/SuSEfirewall2.d/services/salt +%endif +# +## install completion scripts +%if %{with bash_completion} +install -Dpm 0644 pkg/common/salt.bash %{buildroot}%{_sysconfdir}/bash_completion.d/salt +%endif +%if %{with zsh_completion} +install -Dpm 0644 pkg/common/salt.zsh %{buildroot}%{_sysconfdir}/zsh_completion.d/salt +%endif + +%if %{with fish_completion} +mkdir -p %{buildroot}%{fish_completions_dir} +install -Dpm 0644 pkg/common/fish-completions/* %{buildroot}%{fish_completions_dir} +%endif + +# Standalone Salt formulas configuration +install -Dd -m 0750 %{buildroot}%{_prefix}/share/salt-formulas +install -Dd -m 0750 %{buildroot}%{_prefix}/share/salt-formulas/states +install -Dd -m 0750 %{buildroot}%{_prefix}/share/salt-formulas/metadata +install -Dpm 0640 conf/suse/standalone-formulas-configuration.conf %{buildroot}%{_sysconfdir}/salt/master.d +install -Dpm 0640 conf/suse/standalone-formulas-configuration.conf %{buildroot}%{_sysconfdir}/salt/minion.d + +%if 0%{?suse_version} > 1020 +%fdupes %{buildroot}%{_docdir} +%fdupes %{buildroot}%{python3_sitelib} +%endif + +%check +%if %{with test} +python3 setup.py test --runtests-opts=-u +%endif + +%pre +S_HOME="/var/lib/salt" +S_PHOME="/srv/salt" +getent passwd salt | grep $S_PHOME >/dev/null && usermod -d $S_HOME salt +getent group salt >/dev/null || %{_sbindir}/groupadd -r salt +getent passwd salt >/dev/null || %{_sbindir}/useradd -r -g salt -d $S_HOME -s /bin/false -c "salt-master daemon" salt +if [[ -d "$S_PHOME/.ssh" ]]; then + mv $S_PHOME/.ssh $S_HOME +fi + +%post +%if %{with systemd} +systemd-tmpfiles --create /usr/lib/tmpfiles.d/salt.conf || true +%else +dbus-uuidgen --ensure +%endif + +%preun proxy +%if %{with systemd} +%if 0%{?suse_version} +%service_del_preun salt-proxy@.service +%else +%systemd_preun salt-proxy@.service +%endif +%else +%if 0%{?suse_version} +%stop_on_removal salt-proxy +%endif +%endif + +%pre proxy +%if %{with systemd} +%if 0%{?suse_version} +%service_add_pre salt-proxy@.service +%endif +%endif + +%post proxy +%if %{with systemd} +%if 0%{?suse_version} +%service_add_post salt-proxy@.service +%fillup_only +%else +%systemd_post salt-proxy@.service +%endif +%else +%if 0%{?suse_version} +%fillup_and_insserv +%endif +%endif + +%postun proxy +%if %{with systemd} +%if 0%{?suse_version} +%service_del_postun salt-proxy@.service +%else +%systemd_postun_with_restart salt-proxy@.service +%endif +%else +%if 0%{?suse_version} +%insserv_cleanup +%restart_on_update salt-proxy +%endif +%endif + +%preun syndic +%if %{with systemd} +%if 0%{?suse_version} +%service_del_preun salt-syndic.service +%else +%systemd_preun salt-syndic.service +%endif +%else +%if 0%{?suse_version} +%stop_on_removal salt-syndic +%else + if [ $1 -eq 0 ] ; then + /sbin/service salt-syndic stop >/dev/null 2>&1 + /sbin/chkconfig --del salt-syndic + fi +%endif +%endif + +%pre syndic +%if %{with systemd} +%if 0%{?suse_version} +%service_add_pre salt-syndic.service +%endif +%endif + +%post syndic +%if %{with systemd} +%if 0%{?suse_version} +%service_add_post salt-syndic.service +%fillup_only +%else +%systemd_post salt-syndic.service +%endif +%else +%if 0%{?suse_version} +%fillup_and_insserv +%endif +%endif + +%postun syndic +%if %{with systemd} +%if 0%{?suse_version} +%service_del_postun salt-syndic.service +%else +%systemd_postun_with_restart salt-syndic.service +%endif +%else +%if 0%{?suse_version} +%insserv_cleanup +%restart_on_update salt-syndic +%endif +%endif + +%preun master +%if %{with systemd} +%if 0%{?suse_version} +%service_del_preun salt-master.service +%else +%systemd_preun salt-master.service +%endif +%else +%if 0%{?suse_version} +%stop_on_removal salt-master +%else + if [ $1 -eq 0 ] ; then + /sbin/service salt-master stop >/dev/null 2>&1 + /sbin/chkconfig --del salt-master + fi +%endif +%endif + +%pre master +%if %{with systemd} +%if 0%{?suse_version} +%service_add_pre salt-master.service +%endif +%endif + +%post master +if [ $1 -eq 2 ] ; then + # Upgrading from an earlier version. If this is from 2014, where daemons + # ran as root, we need to chown some stuff to salt in order for the new + # version to actually work. It seems a manual restart of salt-master may + # still be required, but at least this will actually work given the file + # ownership is correct. + # Symlinks are excluded to avoid possible user escalation (bsc#1157465) (CVE-2019-18897). + for file in master.{pem,pub} ; do + [ -f /etc/salt/pki/master/$file ] && [ ! -L /etc/salt/pki/master/$file ] && chown --no-dereference salt /etc/salt/pki/master/$file + done + MASTER_CACHE_DIR="/var/cache/salt/master" + [ -d $MASTER_CACHE_DIR ] && find $MASTER_CACHE_DIR -type d | xargs -r chown --no-dereference salt:salt + [ -d $MASTER_CACHE_DIR ] && find $MASTER_CACHE_DIR -type f | xargs -r chown --no-dereference salt:salt + [ -f $MASTER_CACHE_DIR/.root_key ] && chown --no-dereference root:root $MASTER_CACHE_DIR/.root_key + true +fi +%if %{with systemd} +systemd_ver=$(rpm -q systemd --queryformat="%%{VERSION}") +if [ "${systemd_ver%%.*}" -lt 228 ]; then + # On systemd < 228 the 'TasksTask' attribute is not available. + # Removing TasksMax from salt-master.service on SLE12SP1 LTSS (bsc#985112) + sed -i '/TasksMax=infinity/d' %{_unitdir}/salt-master.service +fi +%if 0%{?suse_version} +%service_add_post salt-master.service +%fillup_only +%else +%systemd_post salt-master.service +%endif +%else +%if 0%{?suse_version} +%fillup_and_insserv +%else + /sbin/chkconfig --add salt-master +%endif +%endif + +%postun master +%if %{with systemd} +%if 0%{?suse_version} +%service_del_postun salt-master.service +%else +%systemd_postun_with_restart salt-master.service +%endif +%else +%if 0%{?suse_version} +%restart_on_update salt-master +%insserv_cleanup +%else + if [ "$1" -ge "1" ] ; then + /sbin/service salt-master condrestart >/dev/null 2>&1 || : + fi +%endif +%endif + +%preun minion +%if %{with systemd} +%if 0%{?suse_version} +%service_del_preun salt-minion.service +%else +%systemd_preun salt-minion.service +%endif +%else +%if 0%{?suse_version} +%stop_on_removal salt-minion +%else + if [ $1 -eq 0 ] ; then + /sbin/service salt-minion stop >/dev/null 2>&1 + /sbin/chkconfig --del salt-minion + fi +%endif +%endif + +%pre minion +%if %{with systemd} +%if 0%{?suse_version} +%service_add_pre salt-minion.service +%endif +%endif + +%post minion +%if %{with systemd} +%if 0%{?suse_version} +%service_add_post salt-minion.service +%fillup_only +%else +%systemd_post salt-minion.service +%endif +%else +%if 0%{?suse_version} +%fillup_and_insserv +%else + /sbin/chkconfig --add salt-minion +%endif +%endif + +%postun minion +%if %{with systemd} +%if 0%{?suse_version} +%service_del_postun salt-minion.service +%else +%systemd_postun_with_restart salt-minion.service +%endif +%else +%if 0%{?suse_version} +%insserv_cleanup +%restart_on_update salt-minion +%else + if [ "$1" -ge "1" ] ; then + /sbin/service salt-minion condrestart >/dev/null 2>&1 || : + fi +%endif +%endif + +%preun api +%if %{with systemd} +%if 0%{?suse_version} +%service_del_preun salt-api.service +%else +%systemd_preun salt-api.service +%endif +%else +%stop_on_removal +%endif + +%pre api +%if %{with systemd} +%if 0%{?suse_version} +%service_add_pre salt-api.service +%endif +%endif + +%post api +%if %{with systemd} +%if 0%{?suse_version} +%service_add_post salt-api.service +%else +%systemd_post salt-api.service +%endif +%else +%if 0%{?suse_version} +%fillup_and_insserv +%endif +%endif + +%postun api +%if %{with systemd} +%if 0%{?suse_version} +%service_del_postun salt-api.service +%else +%systemd_postun_with_restart salt-api.service +%endif +%else +%if 0%{?suse_version} +%insserv_cleanup +%restart_on_update +%endif +%endif + +%posttrans -n python3-salt +# force re-generate a new thin.tgz +rm -f %{_localstatedir}/cache/salt/master/thin/version +rm -f %{_localstatedir}/cache/salt/minion/thin/version + +%files api +%defattr(-,root,root) +%{_bindir}/salt-api +%{_sbindir}/rcsalt-api +%if %{with systemd} +%{_unitdir}/salt-api.service +%else +%{_initddir}/salt-api +%endif +%{_mandir}/man1/salt-api.1.* + +%files cloud +%defattr(-,root,root) +%{_bindir}/salt-cloud +%dir %attr(0750, root, salt) %{_sysconfdir}/salt/cloud.maps.d +%dir %attr(0750, root, salt) %{_sysconfdir}/salt/cloud.profiles.d +%dir %attr(0750, root, salt) %{_sysconfdir}/salt/cloud.providers.d +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/cloud +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/cloud.profiles +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/cloud.providers +%dir %attr(0750, root, salt) %{_localstatedir}/cache/salt/cloud +%attr(755,root,root)%{python3_sitelib}/salt/cloud/deploy/bootstrap-salt.sh +%{_mandir}/man1/salt-cloud.1.* + +%files ssh +%defattr(-,root,root) +%{_bindir}/salt-ssh +%{_mandir}/man1/salt-ssh.1.gz + +%files syndic +%defattr(-,root,root) +%{_bindir}/salt-syndic +%{_mandir}/man1/salt-syndic.1.gz +%{_sbindir}/rcsalt-syndic +%if %{with systemd} +%{_unitdir}/salt-syndic.service +%else +%{_initddir}/salt-syndic +%endif + +%files minion +%defattr(-,root,root) +%{_bindir}/salt-minion +%{_mandir}/man1/salt-minion.1.gz +%config(noreplace) %attr(0640, root, root) %{_sysconfdir}/salt/minion +%config(noreplace) %attr(0640, root, root) %ghost %{_sysconfdir}/salt/minion_id +%dir %attr(0750, root, root) %{_sysconfdir}/salt/minion.d/ +%dir %attr(0750, root, root) %{_sysconfdir}/salt/pki/minion/ +%dir %attr(0750, root, root) %{_localstatedir}/cache/salt/minion/ +%{_sbindir}/rcsalt-minion + +# Install plugin only on SUSE machines +%if 0%{?suse_version} +%{_prefix}/lib/zypp/plugins/commit/zyppnotify +%endif + +# Install Yum plugins only on RH machines +%if 0%{?fedora} || 0%{?rhel} +%if 0%{?fedora} >= 22 || 0%{?rhel} >= 8 +%{python3_sitelib}/dnf-plugins/dnfnotify.py +%{python3_sitelib}/dnf-plugins/__pycache__/dnfnotify.* +%{_sysconfdir}/dnf/plugins/dnfnotify.conf +%else +%{_prefix}/share/yum-plugins/yumnotify.* +%{_sysconfdir}/yum/pluginconf.d/yumnotify.conf +%endif +%endif + +%if %{with systemd} +%{_unitdir}/salt-minion.service +%else +%config(noreplace) %{_initddir}/salt-minion +%endif + +## Install sysV salt-minion watchdog for SLES11 and RHEL6 +%if 0%{?rhel} == 6 || 0%{?suse_version} == 1110 +%{_bindir}/salt-daemon-watcher +%endif + +%files proxy +%defattr(-,root,root) +%{_bindir}/salt-proxy +%{_mandir}/man1/salt-proxy.1.gz +%if %{with systemd} +%{_unitdir}/salt-proxy@.service +%endif + +%files master +%defattr(-,root,root) +%{_bindir}/salt +%{_bindir}/salt-master +%{_bindir}/salt-cp +%{_bindir}/salt-key +%{_bindir}/salt-run +%{_mandir}/man1/salt-master.1.gz +%{_mandir}/man1/salt-cp.1.gz +%{_mandir}/man1/salt-key.1.gz +%{_mandir}/man1/salt-run.1.gz +%{_mandir}/man7/salt.7.gz +%if 0%{?suse_version} <= 1500 +%config(noreplace) %{_sysconfdir}/sysconfig/SuSEfirewall2.d/services/salt +%endif +%{_sbindir}/rcsalt-master +%if %{with systemd} +%{_unitdir}/salt-master.service +%else +%config(noreplace) %{_initddir}/salt-master +%endif +# +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/master +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/roster +%dir %attr(0755, root, salt) %{_sysconfdir}/salt/master.d/ +%dir %attr(0750, salt, salt) %{_sysconfdir}/salt/pki/master/ +%dir %attr(0750, salt, salt) %{_sysconfdir}/salt/pki/master/minions/ +%dir %attr(0750, salt, salt) %{_sysconfdir}/salt/pki/master/minions_autosign/ +%dir %attr(0750, salt, salt) %{_sysconfdir}/salt/pki/master/minions_denied/ +%dir %attr(0750, salt, salt) %{_sysconfdir}/salt/pki/master/minions_pre/ +%dir %attr(0750, salt, salt) %{_sysconfdir}/salt/pki/master/minions_rejected/ +%dir %attr(0755, salt, salt) /var/lib/salt +%dir %attr(0755, root, salt) /srv/salt +%dir %attr(0755, root, salt) /srv/pillar +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/ +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/jobs/ +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/proc/ +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/queues/ +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/roots/ +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/syndics/ +%dir %attr(0750, salt, salt) %{_localstatedir}/cache/salt/master/tokens/ + +%files +%defattr(-,root,root,-) +%{_bindir}/spm +%{_bindir}/salt-call +%{_bindir}/salt-support +%{_mandir}/man1/salt-call.1.gz +%{_mandir}/man1/spm.1.gz +%config(noreplace) %{_sysconfdir}/logrotate.d/salt +%{!?_licensedir:%global license %doc} +%license LICENSE +%doc AUTHORS README.rst README.SUSE +# +%dir %attr(0750, root, salt) %{_sysconfdir}/salt +%dir %attr(0750, root, salt) %{_sysconfdir}/salt/pki +%dir %attr(0750, salt, salt) %{_localstatedir}/log/salt +%dir %attr(0750, root, salt) %{_localstatedir}/cache/salt +%dir %attr(0750, root, salt) /srv/spm +%if %{with systemd} +/usr/lib/tmpfiles.d/salt.conf +%endif +%{_mandir}/man1/salt.1.* + +%files -n python3-salt +%defattr(-,root,root,-) +%{python3_sitelib}/* +%exclude %{python3_sitelib}/salt/cloud/deploy/*.sh + +%if %{with docs} +%files doc +%defattr(-,root,root) +%doc doc/_build/html +%endif + +%files tests +%dir %{_datadir}/salt/ +%dir %{_datadir}/salt/tests/ +%{_datadir}/salt/tests/* + +%if %{with bash_completion} +%files bash-completion +%defattr(-,root,root) +%dir %{_sysconfdir}/bash_completion.d/ +%config %{_sysconfdir}/bash_completion.d/%{name} +%endif + +%if %{with zsh_completion} +%files zsh-completion +%defattr(-,root,root) +%dir %{_sysconfdir}/zsh_completion.d/ +%config %{_sysconfdir}/zsh_completion.d/%{name} +%endif + +%if %{with fish_completion} +%files fish-completion +%defattr(-,root,root) +%{fish_completions_dir}/salt* +%dir %{fish_completions_dir} +%dir %{fish_dir} +%endif + +%files standalone-formulas-configuration +%defattr(-,root,root) +%dir %attr(0755, root, salt) %{_sysconfdir}/salt/master.d/ +%config(noreplace) %attr(0640, root, salt) %{_sysconfdir}/salt/master.d/standalone-formulas-configuration.conf +%dir %attr(0750, root, root) %{_sysconfdir}/salt/minion.d/ +%config(noreplace) %attr(0640, root, root) %{_sysconfdir}/salt/minion.d/standalone-formulas-configuration.conf +%dir %attr(0755, root, salt) %{_prefix}/share/salt-formulas/ +%dir %attr(0755, root, salt) %{_prefix}/share/salt-formulas/states/ +%dir %attr(0755, root, salt) %{_prefix}/share/salt-formulas/metadata/ + +%files transactional-update +%defattr(-,root,root) +%config(noreplace) %attr(0640, root, root) %{_sysconfdir}/salt/minion.d/transactional_update.conf + + +%changelog + + diff --git a/save-log-to-logfile-with-docker.build.patch b/save-log-to-logfile-with-docker.build.patch new file mode 100644 index 0000000..a92aa87 --- /dev/null +++ b/save-log-to-logfile-with-docker.build.patch @@ -0,0 +1,56 @@ +From 88adb2f59137213119f1da2b6dbf6fce859fc12f Mon Sep 17 00:00:00 2001 +From: Vladimir Nadvornik +Date: Mon, 27 Jun 2022 17:00:58 +0200 +Subject: [PATCH] Save log to logfile with docker.build + +--- + salt/modules/dockermod.py | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +diff --git a/salt/modules/dockermod.py b/salt/modules/dockermod.py +index 8b6ab8058e..f7344b66ac 100644 +--- a/salt/modules/dockermod.py ++++ b/salt/modules/dockermod.py +@@ -4006,6 +4006,7 @@ def build( + fileobj=None, + dockerfile=None, + buildargs=None, ++ logfile=None, + ): + """ + .. versionchanged:: 2018.3.0 +@@ -4059,6 +4060,9 @@ def build( + buildargs + A dictionary of build arguments provided to the docker build process. + ++ logfile ++ Path to log file. Output from build is written to this file if not None. ++ + + **RETURN DATA** + +@@ -4133,6 +4137,20 @@ def build( + stream_data = [] + for line in response: + stream_data.extend(salt.utils.json.loads(line, cls=DockerJSONDecoder)) ++ ++ if logfile: ++ try: ++ with salt.utils.files.fopen(logfile, "a") as f: ++ for item in stream_data: ++ try: ++ item_type = next(iter(item)) ++ except StopIteration: ++ continue ++ if item_type == "stream": ++ f.write(item[item_type]) ++ except OSError: ++ log.error("Unable to write logfile '%s'", logfile) ++ + errors = [] + # Iterate through API response and collect information + for item in stream_data: +-- +2.39.2 + + diff --git a/skip-package-names-without-colon-bsc-1208691-578.patch b/skip-package-names-without-colon-bsc-1208691-578.patch new file mode 100644 index 0000000..503c666 --- /dev/null +++ b/skip-package-names-without-colon-bsc-1208691-578.patch @@ -0,0 +1,27 @@ +From c61da0bef8d4d8394592db2f9995cdf4820c02af Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Mon, 27 Feb 2023 11:35:41 +0100 +Subject: [PATCH] Skip package names without colon (bsc#1208691) (#578) + +Fixes a problem in `_find_ptf_packages()` when passing multiple packages to `zypperpkg.remove` / `zypperpkg.purge`. The problem occurs when a passed package is not installed, in that case the output of the `rpm` subprocess is not parsed correctly. +--- + salt/modules/zypperpkg.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index 44f2cdbd3a..cdec397d69 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -2688,6 +2688,8 @@ def _find_ptf_packages(pkgs, root=None): + for line in output.splitlines(): + if not line.strip(): + continue ++ if ":" not in line: ++ continue + pkg, provides = line.split(":", 1) + if "ptf()" in provides: + ptfs.append(pkg) +-- +2.39.2 + + diff --git a/switch-firewalld-state-to-use-change_interface.patch b/switch-firewalld-state-to-use-change_interface.patch new file mode 100644 index 0000000..13812f7 --- /dev/null +++ b/switch-firewalld-state-to-use-change_interface.patch @@ -0,0 +1,72 @@ +From 57626d8eb77d2c559365d1df974100e474671fef Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 17:12:04 +0100 +Subject: [PATCH] Switch firewalld state to use change_interface + +firewalld.present state allows to bind interface to given zone. +However if the interface is already bound to some other zone, call- +ing `add_interface` will not change rebind the interface but report +error. +Option `change_interface` however can rebind the interface from one +zone to another. + +This PR adds `firewalld.change_interface` call to firewalld module +and updates `firewalld.present` state to use this call. +--- + salt/modules/firewalld.py | 23 +++++++++++++++++++++++ + salt/states/firewalld.py | 4 +++- + 2 files changed, 26 insertions(+), 1 deletion(-) + +diff --git a/salt/modules/firewalld.py b/salt/modules/firewalld.py +index 135713d851..70bc738240 100644 +--- a/salt/modules/firewalld.py ++++ b/salt/modules/firewalld.py +@@ -918,6 +918,29 @@ def remove_interface(zone, interface, permanent=True): + return __firewall_cmd(cmd) + + ++def change_interface(zone, interface, permanent=True): ++ """ ++ Change zone the interface bound to ++ ++ .. versionadded:: 2019.?.? ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' firewalld.change_interface zone eth0 ++ """ ++ if interface in get_interfaces(zone, permanent): ++ log.info("Interface is already bound to zone.") ++ ++ cmd = "--zone={} --change-interface={}".format(zone, interface) ++ ++ if permanent: ++ cmd += " --permanent" ++ ++ return __firewall_cmd(cmd) ++ ++ + def get_sources(zone, permanent=True): + """ + List sources bound to a zone +diff --git a/salt/states/firewalld.py b/salt/states/firewalld.py +index cc6eaba5c3..534b9dd62d 100644 +--- a/salt/states/firewalld.py ++++ b/salt/states/firewalld.py +@@ -691,7 +691,9 @@ def _present( + for interface in new_interfaces: + if not __opts__["test"]: + try: +- __salt__["firewalld.add_interface"](name, interface, permanent=True) ++ __salt__["firewalld.change_interface"]( ++ name, interface, permanent=True ++ ) + except CommandExecutionError as err: + ret["comment"] = "Error: {}".format(err) + return ret +-- +2.39.2 + + diff --git a/temporary-fix-extend-the-whitelist-of-allowed-comman.patch b/temporary-fix-extend-the-whitelist-of-allowed-comman.patch new file mode 100644 index 0000000..9c8ab2f --- /dev/null +++ b/temporary-fix-extend-the-whitelist-of-allowed-comman.patch @@ -0,0 +1,34 @@ +From 2575e64ee21f774a1efb6960972e9d476a8d5927 Mon Sep 17 00:00:00 2001 +From: Bo Maryniuk +Date: Thu, 24 Jan 2019 18:12:35 +0100 +Subject: [PATCH] temporary fix: extend the whitelist of allowed commands + +--- + salt/auth/__init__.py | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/salt/auth/__init__.py b/salt/auth/__init__.py +index b87e2aff0d..331baab211 100644 +--- a/salt/auth/__init__.py ++++ b/salt/auth/__init__.py +@@ -12,6 +12,7 @@ so that any external authentication system can be used inside of Salt + # 5. Cache auth token with relative data opts['token_dir'] + # 6. Interface to verify tokens + ++ + import getpass + import logging + import random +@@ -42,6 +43,8 @@ AUTH_INTERNAL_KEYWORDS = frozenset( + "gather_job_timeout", + "kwarg", + "match", ++ "id_", ++ "force", + "metadata", + "print_event", + "raw", +-- +2.39.2 + + 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..0757250 --- /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/transactional_update.conf b/transactional_update.conf new file mode 100644 index 0000000..b2c4954 --- /dev/null +++ b/transactional_update.conf @@ -0,0 +1,4 @@ +# Enable the transactional_update executor +module_executors: + - transactional_update + - direct_call diff --git a/travis.yml b/travis.yml new file mode 100644 index 0000000..7b4c8ce --- /dev/null +++ b/travis.yml @@ -0,0 +1,35 @@ +language: python + +python: + - '2.6' + - '2.7' + +before_install: + - sudo apt-get update + - sudo apt-get install --fix-broken --ignore-missing -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" swig rabbitmq-server ruby python-apt mysql-server libmysqlclient-dev + - (git describe && git fetch --tags) || (git remote add upstream git://github.com/saltstack/salt.git && git fetch --tags upstream) + - pip install mock + - pip install --allow-external http://dl.dropbox.com/u/174789/m2crypto-0.20.1.tar.gz + - pip install --upgrade pep8 'pylint<=1.2.0' + - pip install --upgrade coveralls + - "if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install unittest2 ordereddict; fi" + - pip install git+https://github.com/saltstack/salt-testing.git#egg=SaltTesting + +install: + - pip install -r requirements/zeromq.txt -r requirements/cloud.txt + - pip install --allow-all-external -r requirements/opt.txt + +before_script: + - "/home/travis/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pylint --rcfile=.testing.pylintrc salt/ && echo 'Finished Pylint Check Cleanly' || echo 'Finished Pylint Check With Errors'" + - "/home/travis/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pep8 --ignore=E501,E12 salt/ && echo 'Finished PEP-8 Check Cleanly' || echo 'Finished PEP-8 Check With Errors'" + +script: "sudo -E /home/travis/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/python setup.py test --runtests-opts='--run-destructive --sysinfo -v --coverage'" + +after_success: + - coveralls + +notifications: + irc: + channels: "irc.freenode.org#salt-devel" + on_success: change + on_failure: change diff --git a/update-documentation.sh b/update-documentation.sh new file mode 100644 index 0000000..49457a9 --- /dev/null +++ b/update-documentation.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# +# Update html.tar.bz2 documentation tarball +# Author: Bo Maryniuk +# + +NO_SPHINX_PARAM="--without-sphinx" + +function build_virtenv() { + virtualenv --system-site-packages $1 + source $1/bin/activate + pip install --upgrade pip + if [ -z "$2" ]; then + pip install -I Sphinx + fi +} + +function check_env() { + if [[ -z "$1" || "$1" != "$NO_SPHINX_PARAM" ]] && [ ! -z "$(which sphinx-build 2>/dev/null)" ]; then + cat </dev/null)" ]; then + echo "Error: '$cmd' is still missing. Install it, please." + exit 1; + fi + done +} + +function quilt_setup() { + quilt setup -v salt.spec + cd $1 + quilt push -a +} + +function build_docs() { + cd $1 + make html + rm _build/html/.buildinfo + cd _build/html + chmod -R -x+X * + cd .. + tar cvf - html | bzip2 > $2/html.tar.bz2 +} + +function write_changelog() { + mv salt.changes salt.changes.previous + TIME=$(date -u +'%a %b %d %T %Z %Y') + MAIL=$1 + SEP="-------------------------------------------------------------------" + cat < salt.changes +$SEP +$TIME - $MAIL + +- Updated html.tar.bz2 documentation tarball. + +EOF + cat salt.changes.previous >> salt.changes + rm salt.changes.previous +} + +if [ -z "$1" ]; then + echo "Usage: $0 [--without-sphinx]" + exit 1; +fi + +check_env $2; + +START=$(pwd) +V_ENV="sphinx_doc_gen" +V_TMP=$(mktemp -d) + +for f in "salt.spec" "v*tar.gz" "*"; do + cp -v $f $V_TMP +done + +cd $V_TMP; +build_virtenv $V_ENV $2; + +SRC_DIR="salt-$(cat salt.spec | grep ^Version: | cut -d: -f2 | sed -e 's/[[:blank:]]//g')-suse"; +quilt_setup $SRC_DIR +build_docs doc $V_TMP + +cd $START +mv $V_TMP/html.tar.bz2 $START +rm -rf $V_TMP + +echo "Done" +echo "---------------" diff --git a/update-target-fix-for-salt-ssh-to-process-targets-li.patch b/update-target-fix-for-salt-ssh-to-process-targets-li.patch new file mode 100644 index 0000000..a407037 --- /dev/null +++ b/update-target-fix-for-salt-ssh-to-process-targets-li.patch @@ -0,0 +1,98 @@ +From b6bf7e1cb3efedbb651b7d6c5f36b73d88cfa1c0 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Fri, 9 Apr 2021 16:01:32 +0300 +Subject: [PATCH] Update target fix for salt-ssh to process targets list + (bsc#1179831) (#336) + +* Update target fix for salt-ssh to process targets list (bsc#1179831) + +* Improvement for fixing (bsc#1179831) + +Regression fix of salt-ssh on processing targets (#353) +--- + salt/client/ssh/__init__.py | 46 +++++++++++++++++++++++-------------- + 1 file changed, 29 insertions(+), 17 deletions(-) + +diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py +index 049baff51a..19089ce8ad 100644 +--- a/salt/client/ssh/__init__.py ++++ b/salt/client/ssh/__init__.py +@@ -338,7 +338,7 @@ class SSH(MultiprocessingStateMixin): + if not self.opts.get("ssh_cli_tgt"): + self.opts["ssh_cli_tgt"] = self.opts.get("tgt", "") + hostname = self.opts.get("ssh_cli_tgt", "") +- if "@" in hostname: ++ if isinstance(hostname, str) and "@" in hostname: + user, hostname = hostname.split("@", 1) + else: + user = self.opts.get("ssh_user") +@@ -393,7 +393,7 @@ class SSH(MultiprocessingStateMixin): + self.__parsed_rosters[self.ROSTER_UPDATE_FLAG] = False + return + +- def _update_roster(self): ++ def _update_roster(self, hostname=None, user=None): + """ + Update default flat roster with the passed in information. + :return: +@@ -407,8 +407,8 @@ class SSH(MultiprocessingStateMixin): + " host: {hostname}\n user: {user}\n passwd: {passwd}\n".format( + s_user=getpass.getuser(), + s_time=datetime.datetime.utcnow().isoformat(), +- hostname=self.opts.get("tgt", ""), +- user=self.opts.get("ssh_user", ""), ++ hostname=hostname if hostname else self.opts.get("tgt", ""), ++ user=user if user else self.opts.get("ssh_user", ""), + passwd=self.opts.get("ssh_passwd", ""), + ) + ) +@@ -425,20 +425,32 @@ class SSH(MultiprocessingStateMixin): + Uptade targets in case hostname was directly passed without the roster. + :return: + """ +- hostname = self.parse_tgt["hostname"] ++ hosts = self.parse_tgt["hostname"] + user = self.parse_tgt["user"] +- if hostname == "*": +- hostname = "" +- +- if salt.utils.network.is_reachable_host(hostname): +- self.opts["tgt"] = hostname +- self.targets[hostname] = { +- "passwd": self.opts.get("ssh_passwd", ""), +- "host": hostname, +- "user": user, +- } +- if self.opts.get("ssh_update_roster"): +- self._update_roster() ++ ++ if not isinstance(hosts, (list, tuple)): ++ hosts = list([hosts]) ++ _hosts = list() ++ for hostname in hosts: ++ _user = user ++ if "@" in hostname: ++ _user, hostname = hostname.split("@", 1) ++ if hostname == "*": ++ continue ++ if salt.utils.network.is_reachable_host(hostname): ++ _hosts.append(hostname) ++ self.targets[hostname] = { ++ "passwd": self.opts.get("ssh_passwd", ""), ++ "host": hostname, ++ "user": _user, ++ } ++ if self.opts.get("ssh_update_roster"): ++ self._update_roster(hostname=hostname, user=_user) ++ ++ if self.tgt_type == "list": ++ self.opts["tgt"] = _hosts ++ elif _hosts: ++ self.opts["tgt"] = _hosts[0] + + def get_pubkey(self): + """ +-- +2.39.2 + + diff --git a/use-adler32-algorithm-to-compute-string-checksums.patch b/use-adler32-algorithm-to-compute-string-checksums.patch new file mode 100644 index 0000000..ed4be6c --- /dev/null +++ b/use-adler32-algorithm-to-compute-string-checksums.patch @@ -0,0 +1,124 @@ +From ef6da7d43fcf51a7d705422624c1e7a94b1297f2 Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 16:36:57 +0100 +Subject: [PATCH] Use Adler32 algorithm to compute string checksums + +Generate the same numeric value across all Python versions and platforms + +Re-add getting hash by Python shell-out method + +Add an option to choose between default hashing, Adler32 or CRC32 algorithms + +Set default config option for server_id hashing to False on minion + +Choose CRC method, default to faster but less reliable "adler32", if crc is in use + +Add warning for Sodium. + +Move server_id deprecation warning to reduce log spamming (bsc#1135567) (bsc#1135732) + +Remove deprecated warning that breaks miniion execution when "server_id_use_crc" opts are missing +--- + salt/config/__init__.py | 4 ++++ + salt/grains/core.py | 48 +++++++++++++++++++++++++++++++++++++---- + 2 files changed, 48 insertions(+), 4 deletions(-) + +diff --git a/salt/config/__init__.py b/salt/config/__init__.py +index 1632663474..43182f3f92 100644 +--- a/salt/config/__init__.py ++++ b/salt/config/__init__.py +@@ -991,6 +991,9 @@ VALID_OPTS = immutabletypes.freeze( + "maintenance_interval": int, + # Fileserver process restart interval + "fileserver_interval": int, ++ # Use Adler32 hashing algorithm for server_id (default False until Sodium, "adler32" after) ++ # Possible values are: False, adler32, crc32 ++ "server_id_use_crc": (bool, str), + } + ) + +@@ -1296,6 +1299,7 @@ DEFAULT_MINION_OPTS = immutabletypes.freeze( + "global_state_conditions": None, + "reactor_niceness": None, + "fips_mode": False, ++ "server_id_use_crc": False, + } + ) + +diff --git a/salt/grains/core.py b/salt/grains/core.py +index 1199ad274f..5c12556346 100644 +--- a/salt/grains/core.py ++++ b/salt/grains/core.py +@@ -21,6 +21,7 @@ import subprocess + import sys + import time + import uuid ++import zlib + from errno import EACCES, EPERM + + import salt.exceptions +@@ -3382,6 +3383,36 @@ def _hw_data(osdata): + return grains + + ++def _get_hash_by_shell(): ++ """ ++ Shell-out Python 3 for compute reliable hash ++ :return: ++ """ ++ id_ = __opts__.get("id", "") ++ id_hash = None ++ py_ver = sys.version_info[:2] ++ if py_ver >= (3, 3): ++ # Python 3.3 enabled hash randomization, so we need to shell out to get ++ # a reliable hash. ++ id_hash = __salt__["cmd.run"]( ++ [sys.executable, "-c", 'print(hash("{}"))'.format(id_)], ++ env={"PYTHONHASHSEED": "0"}, ++ ) ++ try: ++ id_hash = int(id_hash) ++ except (TypeError, ValueError): ++ log.debug( ++ "Failed to hash the ID to get the server_id grain. Result of hash command: %s", ++ id_hash, ++ ) ++ id_hash = None ++ if id_hash is None: ++ # Python < 3.3 or error encountered above ++ id_hash = hash(id_) ++ ++ return abs(id_hash % (2 ** 31)) ++ ++ + def get_server_id(): + """ + Provides an integer based on the FQDN of a machine. +@@ -3392,10 +3423,19 @@ def get_server_id(): + # server_id + + if salt.utils.platform.is_proxy(): +- return {} +- id_ = __opts__.get("id", "") +- hash_ = int(hashlib.sha256(id_.encode()).hexdigest(), 16) +- return {"server_id": abs(hash_ % (2**31))} ++ server_id = {} ++ else: ++ use_crc = __opts__.get("server_id_use_crc") ++ if bool(use_crc): ++ id_hash = ( ++ getattr(zlib, use_crc, zlib.adler32)(__opts__.get("id", "").encode()) ++ & 0xFFFFFFFF ++ ) ++ else: ++ id_hash = _get_hash_by_shell() ++ server_id = {"server_id": id_hash} ++ ++ return server_id + + + def get_master(): +-- +2.39.2 + + diff --git a/use-rlock-to-avoid-deadlocks-in-salt-ssh.patch b/use-rlock-to-avoid-deadlocks-in-salt-ssh.patch new file mode 100644 index 0000000..f8d91dc --- /dev/null +++ b/use-rlock-to-avoid-deadlocks-in-salt-ssh.patch @@ -0,0 +1,27 @@ +From 578932e56be4b4151aa33bd25997c916b0e00a04 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 4 Jan 2023 13:11:50 +0000 +Subject: [PATCH] Use RLock to avoid deadlocks in salt-ssh + +--- + salt/loader/__init__.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/loader/__init__.py b/salt/loader/__init__.py +index bbe4269839..b41cc64b8e 100644 +--- a/salt/loader/__init__.py ++++ b/salt/loader/__init__.py +@@ -82,7 +82,7 @@ SALT_INTERNAL_LOADERS_PATHS = ( + str(SALT_BASE_PATH / "wheel"), + ) + +-LOAD_LOCK = threading.Lock() ++LOAD_LOCK = threading.RLock() + + + def LazyLoader(*args, **kwargs): +-- +2.39.2 + + diff --git a/use-salt-bundle-in-dockermod.patch b/use-salt-bundle-in-dockermod.patch new file mode 100644 index 0000000..9a07049 --- /dev/null +++ b/use-salt-bundle-in-dockermod.patch @@ -0,0 +1,375 @@ +From b0891f83afa354c4b1f803af8a679ecf5a7fb63c Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov +Date: Mon, 27 Jun 2022 17:59:24 +0300 +Subject: [PATCH] Use Salt Bundle in dockermod + +* Use Salt Bundle for salt calls in dockermod + +* Add test of performing a call with the Salt Bundle +--- + salt/modules/dockermod.py | 197 +++++++++++++++--- + .../unit/modules/dockermod/test_module.py | 78 ++++++- + 2 files changed, 241 insertions(+), 34 deletions(-) + +diff --git a/salt/modules/dockermod.py b/salt/modules/dockermod.py +index 6870c26b0e..8b6ab8058e 100644 +--- a/salt/modules/dockermod.py ++++ b/salt/modules/dockermod.py +@@ -201,14 +201,19 @@ import copy + import fnmatch + import functools + import gzip ++import hashlib + import json + import logging + import os ++import pathlib + import pipes + import re + import shutil + import string + import subprocess ++import sys ++import tarfile ++import tempfile + import time + import uuid + +@@ -6698,6 +6703,111 @@ def _compile_state(sls_opts, mods=None): + return st_.state.compile_high_data(high_data) + + ++def gen_venv_tar(cachedir, venv_dest_dir, venv_name): ++ """ ++ Generate tarball with the Salt Bundle if required and return the path to it ++ """ ++ exec_path = pathlib.Path(sys.executable).parts ++ venv_dir_name = "venv-salt-minion" ++ if venv_dir_name not in exec_path: ++ return None ++ ++ venv_tar = os.path.join(cachedir, "venv-salt.tgz") ++ venv_hash = os.path.join(cachedir, "venv-salt.hash") ++ venv_lock = os.path.join(cachedir, ".venv-salt.lock") ++ ++ venv_path = os.path.join(*exec_path[0 : exec_path.index(venv_dir_name)]) ++ ++ with __utils__["files.flopen"](venv_lock, "w"): ++ start_dir = os.getcwd() ++ venv_hash_file = os.path.join(venv_path, venv_dir_name, "venv-hash.txt") ++ try: ++ with __utils__["files.fopen"](venv_hash_file, "r") as fh: ++ venv_hash_src = fh.readline().strip() ++ except Exception: # pylint: disable=broad-except ++ # It makes no sense what caused the exception ++ # Just calculate the hash different way ++ for cmd in ("rpm -qi venv-salt-minion", "dpkg -s venv-salt-minion"): ++ ret = __salt__["cmd.run_all"]( ++ cmd, ++ python_shell=True, ++ clean_env=True, ++ env={"LANG": "C", "LANGUAGE": "C", "LC_ALL": "C"}, ++ ) ++ if ret.get("retcode") == 0 and ret.get("stdout"): ++ venv_hash_src = hashlib.sha256( ++ "{}\n".format(ret.get("stdout")).encode() ++ ).hexdigest() ++ break ++ try: ++ with __utils__["files.fopen"](venv_hash, "r") as fh: ++ venv_hash_dest = fh.readline().strip() ++ except Exception: # pylint: disable=broad-except ++ # It makes no sense what caused the exception ++ # Set the hash to impossible value to force new tarball creation ++ venv_hash_dest = "UNKNOWN" ++ if venv_hash_src == venv_hash_dest and os.path.isfile(venv_tar): ++ return venv_tar ++ try: ++ tfd, tmp_venv_tar = tempfile.mkstemp( ++ dir=cachedir, ++ prefix=".venv-", ++ suffix=os.path.splitext(venv_tar)[1], ++ ) ++ os.close(tfd) ++ ++ os.chdir(venv_path) ++ tfp = tarfile.open(tmp_venv_tar, "w:gz") ++ ++ for root, dirs, files in salt.utils.path.os_walk( ++ venv_dir_name, followlinks=True ++ ): ++ for name in files: ++ if name == "python" and pathlib.Path(root).parts == ( ++ venv_dir_name, ++ "bin", ++ ): ++ tfd, tmp_python_file = tempfile.mkstemp( ++ dir=cachedir, ++ prefix=".python-", ++ ) ++ os.close(tfd) ++ try: ++ with __utils__["files.fopen"]( ++ os.path.join(root, name), "r" ++ ) as fh_in: ++ with __utils__["files.fopen"]( ++ tmp_python_file, "w" ++ ) as fh_out: ++ rd_lines = fh_in.readlines() ++ rd_lines = [ ++ 'export VIRTUAL_ENV="{}"\n'.format( ++ os.path.join(venv_dest_dir, venv_name) ++ ) ++ if line.startswith("export VIRTUAL_ENV=") ++ else line ++ for line in rd_lines ++ ] ++ fh_out.write("".join(rd_lines)) ++ os.chmod(tmp_python_file, 0o755) ++ tfp.add(tmp_python_file, arcname=os.path.join(root, name)) ++ continue ++ finally: ++ if os.path.isfile(tmp_python_file): ++ os.remove(tmp_python_file) ++ if not name.endswith((".pyc", ".pyo")): ++ tfp.add(os.path.join(root, name)) ++ ++ tfp.close() ++ shutil.move(tmp_venv_tar, venv_tar) ++ with __utils__["files.fopen"](venv_hash, "w") as fh: ++ fh.write("{}\n".format(venv_hash_src)) ++ finally: ++ os.chdir(start_dir) ++ ++ return venv_tar ++ ++ + def call(name, function, *args, **kwargs): + """ + Executes a Salt function inside a running container +@@ -6733,47 +6843,68 @@ def call(name, function, *args, **kwargs): + if function is None: + raise CommandExecutionError("Missing function parameter") + +- # move salt into the container +- thin_path = __utils__["thin.gen_thin"]( +- __opts__["cachedir"], +- extra_mods=__salt__["config.option"]("thin_extra_mods", ""), +- so_mods=__salt__["config.option"]("thin_so_mods", ""), +- ) +- ret = copy_to( +- name, thin_path, os.path.join(thin_dest_path, os.path.basename(thin_path)) +- ) ++ venv_dest_path = "/var/tmp" ++ venv_name = "venv-salt-minion" ++ venv_tar = gen_venv_tar(__opts__["cachedir"], venv_dest_path, venv_name) + +- # figure out available python interpreter inside the container (only Python3) +- pycmds = ("python3", "/usr/libexec/platform-python") +- container_python_bin = None +- for py_cmd in pycmds: +- cmd = [py_cmd] + ["--version"] +- ret = run_all(name, subprocess.list2cmdline(cmd)) +- if ret["retcode"] == 0: +- container_python_bin = py_cmd +- break +- if not container_python_bin: +- raise CommandExecutionError( +- "Python interpreter cannot be found inside the container. Make sure Python is installed in the container" ++ if venv_tar is not None: ++ venv_python_bin = os.path.join(venv_dest_path, venv_name, "bin", "python") ++ dest_venv_tar = os.path.join(venv_dest_path, os.path.basename(venv_tar)) ++ copy_to(name, venv_tar, dest_venv_tar, overwrite=True, makedirs=True) ++ run_all( ++ name, ++ subprocess.list2cmdline( ++ ["tar", "zxf", dest_venv_tar, "-C", venv_dest_path] ++ ), ++ ) ++ run_all(name, subprocess.list2cmdline(["rm", "-f", dest_venv_tar])) ++ container_python_bin = venv_python_bin ++ thin_dest_path = os.path.join(venv_dest_path, venv_name) ++ thin_salt_call = os.path.join(thin_dest_path, "bin", "salt-call") ++ else: ++ # move salt into the container ++ thin_path = __utils__["thin.gen_thin"]( ++ __opts__["cachedir"], ++ extra_mods=__salt__["config.option"]("thin_extra_mods", ""), ++ so_mods=__salt__["config.option"]("thin_so_mods", ""), + ) + +- # untar archive +- untar_cmd = [ +- container_python_bin, +- "-c", +- 'import tarfile; tarfile.open("{0}/{1}").extractall(path="{0}")'.format( +- thin_dest_path, os.path.basename(thin_path) +- ), +- ] +- ret = run_all(name, subprocess.list2cmdline(untar_cmd)) +- if ret["retcode"] != 0: +- return {"result": False, "comment": ret["stderr"]} ++ ret = copy_to( ++ name, thin_path, os.path.join(thin_dest_path, os.path.basename(thin_path)) ++ ) ++ ++ # figure out available python interpreter inside the container (only Python3) ++ pycmds = ("python3", "/usr/libexec/platform-python") ++ container_python_bin = None ++ for py_cmd in pycmds: ++ cmd = [py_cmd] + ["--version"] ++ ret = run_all(name, subprocess.list2cmdline(cmd)) ++ if ret["retcode"] == 0: ++ container_python_bin = py_cmd ++ break ++ if not container_python_bin: ++ raise CommandExecutionError( ++ "Python interpreter cannot be found inside the container. Make sure Python is installed in the container" ++ ) ++ ++ # untar archive ++ untar_cmd = [ ++ container_python_bin, ++ "-c", ++ 'import tarfile; tarfile.open("{0}/{1}").extractall(path="{0}")'.format( ++ thin_dest_path, os.path.basename(thin_path) ++ ), ++ ] ++ ret = run_all(name, subprocess.list2cmdline(untar_cmd)) ++ if ret["retcode"] != 0: ++ return {"result": False, "comment": ret["stderr"]} ++ thin_salt_call = os.path.join(thin_dest_path, "salt-call") + + try: + salt_argv = ( + [ + container_python_bin, +- os.path.join(thin_dest_path, "salt-call"), ++ thin_salt_call, + "--metadata", + "--local", + "--log-file", +diff --git a/tests/pytests/unit/modules/dockermod/test_module.py b/tests/pytests/unit/modules/dockermod/test_module.py +index 8fb7806497..1ac7dff52a 100644 +--- a/tests/pytests/unit/modules/dockermod/test_module.py ++++ b/tests/pytests/unit/modules/dockermod/test_module.py +@@ -3,6 +3,7 @@ Unit tests for the docker module + """ + + import logging ++import sys + + import pytest + +@@ -26,6 +27,7 @@ def configure_loader_modules(minion_opts): + whitelist=[ + "args", + "docker", ++ "files", + "json", + "state", + "thin", +@@ -880,13 +882,16 @@ def test_call_success(): + client = Mock() + client.put_archive = Mock() + get_client_mock = MagicMock(return_value=client) ++ gen_venv_tar_mock = MagicMock(return_value=None) + + context = {"docker.exec_driver": "docker-exec"} + salt_dunder = {"config.option": docker_config_mock} + + with patch.object(docker_mod, "run_all", docker_run_all_mock), patch.object( + docker_mod, "copy_to", docker_copy_to_mock +- ), patch.object(docker_mod, "_get_client", get_client_mock), patch.dict( ++ ), patch.object(docker_mod, "_get_client", get_client_mock), patch.object( ++ docker_mod, "gen_venv_tar", gen_venv_tar_mock ++ ), patch.dict( + docker_mod.__opts__, {"cachedir": "/tmp"} + ), patch.dict( + docker_mod.__salt__, salt_dunder +@@ -931,6 +936,11 @@ def test_call_success(): + != docker_run_all_mock.mock_calls[9][1][1] + ) + ++ # check the parameters of gen_venv_tar call ++ assert gen_venv_tar_mock.mock_calls[0][1][0] == "/tmp" ++ assert gen_venv_tar_mock.mock_calls[0][1][1] == "/var/tmp" ++ assert gen_venv_tar_mock.mock_calls[0][1][2] == "venv-salt-minion" ++ + assert {"retcode": 0, "comment": "container cmd"} == ret + + +@@ -1352,3 +1362,69 @@ def test_port(): + "bar": {"6666/tcp": ports["bar"]["6666/tcp"]}, + "baz": {}, + } ++ ++ ++@pytest.mark.slow_test ++def test_call_with_gen_venv_tar(): ++ """ ++ test module calling inside containers with the Salt Bundle ++ """ ++ ret = None ++ docker_run_all_mock = MagicMock( ++ return_value={ ++ "retcode": 0, ++ "stdout": '{"retcode": 0, "comment": "container cmd"}', ++ "stderr": "err", ++ } ++ ) ++ docker_copy_to_mock = MagicMock(return_value={"retcode": 0}) ++ docker_config_mock = MagicMock(return_value="") ++ docker_cmd_run_mock = MagicMock( ++ return_value={ ++ "retcode": 0, ++ "stdout": "test", ++ } ++ ) ++ client = Mock() ++ client.put_archive = Mock() ++ get_client_mock = MagicMock(return_value=client) ++ ++ context = {"docker.exec_driver": "docker-exec"} ++ salt_dunder = { ++ "config.option": docker_config_mock, ++ "cmd.run_all": docker_cmd_run_mock, ++ } ++ ++ with patch.object(docker_mod, "run_all", docker_run_all_mock), patch.object( ++ docker_mod, "copy_to", docker_copy_to_mock ++ ), patch.object(docker_mod, "_get_client", get_client_mock), patch.object( ++ sys, "executable", "/tmp/venv-salt-minion/bin/python" ++ ), patch.dict( ++ docker_mod.__opts__, {"cachedir": "/tmp"} ++ ), patch.dict( ++ docker_mod.__salt__, salt_dunder ++ ), patch.dict( ++ docker_mod.__context__, context ++ ): ++ ret = docker_mod.call("ID", "test.arg", 1, 2, arg1="val1") ++ ++ # Check that the directory is different each time ++ # [ call(name, [args]), ... ++ assert "mkdir" in docker_run_all_mock.mock_calls[0][1][1] ++ ++ assert ( ++ "tar zxf /var/tmp/venv-salt.tgz -C /var/tmp" ++ == docker_run_all_mock.mock_calls[1][1][1] ++ ) ++ ++ assert docker_run_all_mock.mock_calls[3][1][1].startswith( ++ "/var/tmp/venv-salt-minion/bin/python /var/tmp/venv-salt-minion/bin/salt-call " ++ ) ++ ++ # check remove the salt bundle tarball ++ assert docker_run_all_mock.mock_calls[2][1][1] == "rm -f /var/tmp/venv-salt.tgz" ++ ++ # check directory cleanup ++ assert docker_run_all_mock.mock_calls[4][1][1] == "rm -rf /var/tmp/venv-salt-minion" ++ ++ assert {"retcode": 0, "comment": "container cmd"} == ret +-- +2.39.2 + + diff --git a/v3006.0.tar.gz b/v3006.0.tar.gz new file mode 100644 index 0000000..036b058 --- /dev/null +++ b/v3006.0.tar.gz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09703cff7142ae5e86c958ccff342e7800df7465031f95accdbfc4274059e4d1 +size 20816042 diff --git a/write-salt-version-before-building-when-using-with-s.patch b/write-salt-version-before-building-when-using-with-s.patch new file mode 100644 index 0000000..e97befd --- /dev/null +++ b/write-salt-version-before-building-when-using-with-s.patch @@ -0,0 +1,30 @@ +From cc161359ef7432960ef2f0b8f816986fa6798403 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Wed, 20 Sep 2023 13:07:29 +0100 +Subject: [PATCH] Write salt version before building when using + --with-salt-version (bsc#1215489) (#604) + +--- + setup.py | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/setup.py b/setup.py +index 8ca8a66d45..cf7e54f930 100755 +--- a/setup.py ++++ b/setup.py +@@ -591,6 +591,10 @@ HOME_DIR = {home_dir!r} + + class Build(build): + def run(self): ++ if getattr(self.distribution, "with_salt_version", False): ++ self.distribution.salt_version_hardcoded_path = SALT_VERSION_HARDCODED ++ self.run_command("write_salt_version") ++ + # Run build.run function + build.run(self) + salt_build_ver_file = os.path.join(self.build_lib, "salt", "_version.txt") +-- +2.41.0 + + diff --git a/x509-fixes-111.patch b/x509-fixes-111.patch new file mode 100644 index 0000000..13af746 --- /dev/null +++ b/x509-fixes-111.patch @@ -0,0 +1,431 @@ +From 094b34760a85c3ee27bf64783624b17bd3bbca0a Mon Sep 17 00:00:00 2001 +From: Alexander Graul +Date: Tue, 18 Jan 2022 16:38:17 +0100 +Subject: [PATCH] X509 fixes (#111) + +* Return proper content type for the x509 certificate + +* Remove parenthesis + +* Remove extra-variables during the import + +* Comment fix + +* Remove double returns + +* Change log level from trace to debug + +* Remove 'pass' and add logging instead + +* Remove unnecessary wrapping + +Remove wrapping + +* PEP 8: line too long + +PEP8: line too long + +* PEP8: Redefine RSAError variable in except clause + +* Do not return None if name was not found + +* Do not return None if no matched minions found + +* Fix unit tests + +Fix for log checking in x509 test + +We are logging in debug and not in trace mode here. +--- + salt/modules/publish.py | 2 + + salt/modules/x509.py | 93 ++++++++++++++++----------------- + salt/states/x509.py | 74 ++++++++++++++++++++++++-- + tests/unit/modules/test_x509.py | 6 +-- + 4 files changed, 120 insertions(+), 55 deletions(-) + +diff --git a/salt/modules/publish.py b/salt/modules/publish.py +index cc424cc383..a82cb3ac98 100644 +--- a/salt/modules/publish.py ++++ b/salt/modules/publish.py +@@ -199,6 +199,8 @@ def _publish( + else: + return ret + ++ return {} ++ + + def publish( + tgt, fun, arg=None, tgt_type="glob", returner="", timeout=5, via_master=None +diff --git a/salt/modules/x509.py b/salt/modules/x509.py +index 57c381ea38..6699a5d363 100644 +--- a/salt/modules/x509.py ++++ b/salt/modules/x509.py +@@ -42,16 +42,13 @@ from salt.utils.odict import OrderedDict + + try: + import M2Crypto +- +- HAS_M2 = True + except ImportError: +- HAS_M2 = False ++ M2Crypto = None ++ + try: + import OpenSSL +- +- HAS_OPENSSL = True + except ImportError: +- HAS_OPENSSL = False ++ OpenSSL = None + + __virtualname__ = "x509" + +@@ -94,15 +91,10 @@ def __virtual__(): + # salt.features appears to not be setup when invoked via peer publishing + if __opts__.get("features", {}).get("x509_v2"): + return (False, "Superseded, using x509_v2") +- if HAS_M2: +- salt.utils.versions.warn_until( +- "Potassium", +- "The x509 modules are deprecated. Please migrate to the replacement " +- "modules (x509_v2). They are the default from Salt 3008 (Argon) onwards.", +- ) +- return __virtualname__ +- else: +- return (False, "Could not load x509 module, m2crypto unavailable") ++ return ( ++ __virtualname__ if M2Crypto is not None else False, ++ "Could not load x509 module, m2crypto unavailable", ++ ) + + + class _Ctx(ctypes.Structure): +@@ -160,8 +152,8 @@ def _new_extension(name, value, critical=0, issuer=None, _pyfree=1): + x509_ext_ptr = M2Crypto.m2.x509v3_ext_conf(None, ctx, name, value) + lhash = None + except AttributeError: +- lhash = M2Crypto.m2.x509v3_lhash() +- ctx = M2Crypto.m2.x509v3_set_conf_lhash(lhash) ++ lhash = M2Crypto.m2.x509v3_lhash() # pylint: disable=no-member ++ ctx = M2Crypto.m2.x509v3_set_conf_lhash(lhash) # pylint: disable=no-member + # ctx not zeroed + _fix_ctx(ctx, issuer) + x509_ext_ptr = M2Crypto.m2.x509v3_ext_conf(lhash, ctx, name, value) +@@ -300,7 +292,7 @@ def _get_signing_policy(name): + signing_policy = policies.get(name) + if signing_policy: + return signing_policy +- return __salt__["config.get"]("x509_signing_policies", {}).get(name) ++ return __salt__["config.get"]("x509_signing_policies", {}).get(name) or {} + + + def _pretty_hex(hex_str): +@@ -338,9 +330,11 @@ def _text_or_file(input_): + """ + if _isfile(input_): + with salt.utils.files.fopen(input_) as fp_: +- return salt.utils.stringutils.to_str(fp_.read()) ++ out = salt.utils.stringutils.to_str(fp_.read()) + else: +- return salt.utils.stringutils.to_str(input_) ++ out = salt.utils.stringutils.to_str(input_) ++ ++ return out + + + def _parse_subject(subject): +@@ -359,7 +353,7 @@ def _parse_subject(subject): + ret_list.append((nid_num, nid_name, val)) + nids.append(nid_num) + except TypeError as err: +- log.trace("Missing attribute '%s'. Error: %s", nid_name, err) ++ log.debug("Missing attribute '%s'. Error: %s", nid_name, err) + for nid_num, nid_name, val in sorted(ret_list): + ret[nid_name] = val + return ret +@@ -557,8 +551,8 @@ def get_pem_entries(glob_path): + if os.path.isfile(path): + try: + ret[path] = get_pem_entry(text=path) +- except ValueError: +- pass ++ except ValueError as err: ++ log.debug("Unable to get PEM entries from %s: %s", path, err) + + return ret + +@@ -636,8 +630,8 @@ def read_certificates(glob_path): + if os.path.isfile(path): + try: + ret[path] = read_certificate(certificate=path) +- except ValueError: +- pass ++ except ValueError as err: ++ log.debug("Unable to read certificate %s: %s", path, err) + + return ret + +@@ -667,10 +661,9 @@ def read_csr(csr): + "Subject": _parse_subject(csr.get_subject()), + "Subject Hash": _dec2hex(csr.get_subject().as_hash()), + "Public Key Hash": hashlib.sha1(csr.get_pubkey().get_modulus()).hexdigest(), ++ "X509v3 Extensions": _get_csr_extensions(csr), + } + +- ret["X509v3 Extensions"] = _get_csr_extensions(csr) +- + return ret + + +@@ -980,7 +973,7 @@ def create_crl( + # pyOpenSSL Note due to current limitations in pyOpenSSL it is impossible + # to specify a digest For signing the CRL. This will hopefully be fixed + # soon: https://github.com/pyca/pyopenssl/pull/161 +- if not HAS_OPENSSL: ++ if OpenSSL is None: + raise salt.exceptions.SaltInvocationError( + "Could not load OpenSSL module, OpenSSL unavailable" + ) +@@ -1131,6 +1124,7 @@ def get_signing_policy(signing_policy_name): + signing_policy = _get_signing_policy(signing_policy_name) + if not signing_policy: + return "Signing policy {} does not exist.".format(signing_policy_name) ++ + if isinstance(signing_policy, list): + dict_ = {} + for item in signing_policy: +@@ -1147,7 +1141,7 @@ def get_signing_policy(signing_policy_name): + signing_policy["signing_cert"], "CERTIFICATE" + ) + except KeyError: +- pass ++ log.debug('Unable to get "certificate" PEM entry') + + return signing_policy + +@@ -1782,7 +1776,8 @@ def create_csr(path=None, text=False, **kwargs): + ) + ) + +- for entry in sorted(subject.nid): ++ # pylint: disable=unused-variable ++ for entry, num in subject.nid.items(): + if entry in kwargs: + setattr(subject, entry, kwargs[entry]) + +@@ -1818,7 +1813,6 @@ def create_csr(path=None, text=False, **kwargs): + extstack.push(ext) + + csr.add_extensions(extstack) +- + csr.sign( + _get_private_key_obj( + kwargs["private_key"], passphrase=kwargs["private_key_passphrase"] +@@ -1826,10 +1820,11 @@ def create_csr(path=None, text=False, **kwargs): + kwargs["algorithm"], + ) + +- if path: +- return write_pem(text=csr.as_pem(), path=path, pem_type="CERTIFICATE REQUEST") +- else: +- return csr.as_pem() ++ return ( ++ write_pem(text=csr.as_pem(), path=path, pem_type="CERTIFICATE REQUEST") ++ if path ++ else csr.as_pem() ++ ) + + + def verify_private_key(private_key, public_key, passphrase=None): +@@ -1854,7 +1849,7 @@ def verify_private_key(private_key, public_key, passphrase=None): + salt '*' x509.verify_private_key private_key=/etc/pki/myca.key \\ + public_key=/etc/pki/myca.crt + """ +- return bool(get_public_key(private_key, passphrase) == get_public_key(public_key)) ++ return get_public_key(private_key, passphrase) == get_public_key(public_key) + + + def verify_signature( +@@ -1910,7 +1905,10 @@ def verify_crl(crl, cert): + salt '*' x509.verify_crl crl=/etc/pki/myca.crl cert=/etc/pki/myca.crt + """ + if not salt.utils.path.which("openssl"): +- raise salt.exceptions.SaltInvocationError("openssl binary not found in path") ++ raise salt.exceptions.SaltInvocationError( ++ 'External command "openssl" not found' ++ ) ++ + crltext = _text_or_file(crl) + crltext = get_pem_entry(crltext, pem_type="X509 CRL") + crltempfile = tempfile.NamedTemporaryFile(delete=True) +@@ -1970,8 +1968,9 @@ def expired(certificate): + ret["expired"] = True + else: + ret["expired"] = False +- except ValueError: +- pass ++ except ValueError as err: ++ log.debug("Failed to get data of expired certificate: %s", err) ++ log.trace(err, exc_info=True) + + return ret + +@@ -1994,6 +1993,7 @@ def will_expire(certificate, days): + + salt '*' x509.will_expire "/etc/pki/mycert.crt" days=30 + """ ++ ts_pt = "%Y-%m-%d %H:%M:%S" + ret = {} + + if os.path.isfile(certificate): +@@ -2007,14 +2007,11 @@ def will_expire(certificate, days): + _expiration_date = cert.get_not_after().get_datetime() + + ret["cn"] = _parse_subject(cert.get_subject())["CN"] +- +- if _expiration_date.strftime("%Y-%m-%d %H:%M:%S") <= _check_time.strftime( +- "%Y-%m-%d %H:%M:%S" +- ): +- ret["will_expire"] = True +- else: +- ret["will_expire"] = False +- except ValueError: +- pass ++ ret["will_expire"] = _expiration_date.strftime( ++ ts_pt ++ ) <= _check_time.strftime(ts_pt) ++ except ValueError as err: ++ log.debug("Unable to return details of a sertificate expiration: %s", err) ++ log.trace(err, exc_info=True) + + return ret +diff --git a/salt/states/x509.py b/salt/states/x509.py +index aebbc4cc82..f9cbec87f9 100644 +--- a/salt/states/x509.py ++++ b/salt/states/x509.py +@@ -192,11 +192,12 @@ import re + import salt.exceptions + import salt.utils.versions + from salt.features import features ++import salt.utils.stringutils + + try: + from M2Crypto.RSA import RSAError + except ImportError: +- pass ++ RSAError = Exception("RSA Error") + + log = logging.getLogger(__name__) + +@@ -215,7 +216,7 @@ def __virtual__(): + ) + return "x509" + else: +- return (False, "Could not load x509 state: m2crypto unavailable") ++ return False, "Could not load x509 state: the x509 is not available" + + + def _revoked_to_list(revs): +@@ -704,7 +705,70 @@ def certificate_managed(name, days_remaining=90, append_certs=None, **kwargs): + "Old": invalid_reason, + "New": "Certificate will be valid and up to date", + } +- return ret ++ private_key_args.update(managed_private_key) ++ kwargs["public_key_passphrase"] = private_key_args["passphrase"] ++ ++ if private_key_args["new"]: ++ rotate_private_key = True ++ private_key_args["new"] = False ++ ++ if _check_private_key( ++ private_key_args["name"], ++ bits=private_key_args["bits"], ++ passphrase=private_key_args["passphrase"], ++ new=private_key_args["new"], ++ overwrite=private_key_args["overwrite"], ++ ): ++ private_key = __salt__["x509.get_pem_entry"]( ++ private_key_args["name"], pem_type="RSA PRIVATE KEY" ++ ) ++ else: ++ new_private_key = True ++ private_key = __salt__["x509.create_private_key"]( ++ text=True, ++ bits=private_key_args["bits"], ++ passphrase=private_key_args["passphrase"], ++ cipher=private_key_args["cipher"], ++ verbose=private_key_args["verbose"], ++ ) ++ ++ kwargs["public_key"] = private_key ++ ++ current_days_remaining = 0 ++ current_comp = {} ++ ++ if os.path.isfile(name): ++ try: ++ current = __salt__["x509.read_certificate"](certificate=name) ++ current_comp = copy.deepcopy(current) ++ if "serial_number" not in kwargs: ++ current_comp.pop("Serial Number") ++ if "signing_cert" not in kwargs: ++ try: ++ current_comp["X509v3 Extensions"][ ++ "authorityKeyIdentifier" ++ ] = re.sub( ++ r"serial:([0-9A-F]{2}:)*[0-9A-F]{2}", ++ "serial:--", ++ current_comp["X509v3 Extensions"]["authorityKeyIdentifier"], ++ ) ++ except KeyError: ++ pass ++ current_comp.pop("Not Before") ++ current_comp.pop("MD5 Finger Print") ++ current_comp.pop("SHA1 Finger Print") ++ current_comp.pop("SHA-256 Finger Print") ++ current_notafter = current_comp.pop("Not After") ++ current_days_remaining = ( ++ datetime.datetime.strptime(current_notafter, "%Y-%m-%d %H:%M:%S") ++ - datetime.datetime.now() ++ ).days ++ if days_remaining == 0: ++ days_remaining = current_days_remaining - 1 ++ except salt.exceptions.SaltInvocationError: ++ current = "{} is not a valid Certificate.".format(name) ++ else: ++ current = "{} does not exist.".format(name) + + contents = __salt__["x509.create_certificate"](text=True, **kwargs) + # Check the module actually returned a cert and not an error message as a string +@@ -900,6 +964,8 @@ def pem_managed(name, text, backup=False, **kwargs): + Any arguments supported by :py:func:`file.managed ` are supported. + """ + file_args, kwargs = _get_file_args(name, **kwargs) +- file_args["contents"] = __salt__["x509.get_pem_entry"](text=text) ++ file_args["contents"] = salt.utils.stringutils.to_str( ++ __salt__["x509.get_pem_entry"](text=text) ++ ) + + return __states__["file.managed"](**file_args) +diff --git a/tests/unit/modules/test_x509.py b/tests/unit/modules/test_x509.py +index f1ca5bb45a..a5c44f0ed2 100644 +--- a/tests/unit/modules/test_x509.py ++++ b/tests/unit/modules/test_x509.py +@@ -119,9 +119,9 @@ class X509TestCase(TestCase, LoaderModuleMockMixin): + + subj = FakeSubject() + x509._parse_subject(subj) +- assert x509.log.trace.call_args[0][0] == "Missing attribute '%s'. Error: %s" +- assert x509.log.trace.call_args[0][1] == list(subj.nid.keys())[0] +- assert isinstance(x509.log.trace.call_args[0][2], TypeError) ++ assert x509.log.debug.call_args[0][0] == "Missing attribute '%s'. Error: %s" ++ assert x509.log.debug.call_args[0][1] == list(subj.nid.keys())[0] ++ assert isinstance(x509.log.debug.call_args[0][2], TypeError) + + @pytest.mark.skipif( + not HAS_M2CRYPTO, reason="Skipping, reason=M2Crypto is unavailable" +-- +2.39.2 + + diff --git a/zypper-pkgrepo-alreadyconfigured-585.patch b/zypper-pkgrepo-alreadyconfigured-585.patch new file mode 100644 index 0000000..fda221b --- /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 + + diff --git a/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch b/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch new file mode 100644 index 0000000..5798416 --- /dev/null +++ b/zypperpkg-ignore-retcode-104-for-search-bsc-1176697-.patch @@ -0,0 +1,275 @@ +From deaee93b2f83f1524ec136afc1a5198b33d293d2 Mon Sep 17 00:00:00 2001 +From: Alberto Planas +Date: Mon, 5 Oct 2020 16:24:16 +0200 +Subject: [PATCH] zypperpkg: ignore retcode 104 for search() + (bsc#1176697) (#270) + +--- + salt/modules/zypperpkg.py | 28 ++++++--- + tests/unit/modules/test_zypperpkg.py | 87 ++++++++++++++++++++++------ + 2 files changed, 89 insertions(+), 26 deletions(-) + +diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py +index d8220a1fdd..4bb10f445a 100644 +--- a/salt/modules/zypperpkg.py ++++ b/salt/modules/zypperpkg.py +@@ -103,6 +103,8 @@ class _Zypper: + } + + LOCK_EXIT_CODE = 7 ++ NOT_FOUND_EXIT_CODE = 104 ++ + XML_DIRECTIVES = ["-x", "--xmlout"] + # ZYPPER_LOCK is not affected by --root + ZYPPER_LOCK = "/var/run/zypp.pid" +@@ -134,6 +136,7 @@ class _Zypper: + self.__no_raise = False + self.__refresh = False + self.__ignore_repo_failure = False ++ self.__ignore_not_found = False + self.__systemd_scope = False + self.__root = None + +@@ -153,6 +156,9 @@ class _Zypper: + # Ignore exit code for 106 (repo is not available) + if "no_repo_failure" in kwargs: + self.__ignore_repo_failure = kwargs["no_repo_failure"] ++ # Ignore exit code for 104 (package not found) ++ if "ignore_not_found" in kwargs: ++ self.__ignore_not_found = kwargs["ignore_not_found"] + if "systemd_scope" in kwargs: + self.__systemd_scope = kwargs["systemd_scope"] + if "root" in kwargs: +@@ -333,6 +339,10 @@ class _Zypper: + if self.__root: + self.__cmd.extend(["--root", self.__root]) + ++ # Do not consider 104 as a retcode error ++ if self.__ignore_not_found: ++ kwargs["success_retcodes"] = [_Zypper.NOT_FOUND_EXIT_CODE] ++ + self.__cmd.extend(args) + kwargs["output_loglevel"] = "trace" + kwargs["python_shell"] = False +@@ -479,9 +489,11 @@ class Wildcard: + Get available versions of the package. + :return: + """ +- solvables = self.zypper.nolock.xml.call( +- "se", "-xv", self.name +- ).getElementsByTagName("solvable") ++ solvables = ( ++ self.zypper(ignore_not_found=True) ++ .nolock.xml.call("se", "-v", self.name) ++ .getElementsByTagName("solvable") ++ ) + if not solvables: + raise CommandExecutionError( + "No packages found matching '{}'".format(self.name) +@@ -1086,7 +1098,7 @@ def list_repo_pkgs(*args, **kwargs): + + root = kwargs.get("root") or None + for node in ( +- __zypper__(root=root) ++ __zypper__(root=root, ignore_not_found=True) + .xml.call("se", "-s", *targets) + .getElementsByTagName("solvable") + ): +@@ -2556,7 +2568,9 @@ def owner(*paths, **kwargs): + def _get_visible_patterns(root=None): + """Get all available patterns in the repo that are visible.""" + patterns = {} +- search_patterns = __zypper__(root=root).nolock.xml.call("se", "-t", "pattern") ++ search_patterns = __zypper__(root=root, ignore_not_found=True).nolock.xml.call( ++ "se", "-t", "pattern" ++ ) + for element in search_patterns.getElementsByTagName("solvable"): + installed = element.getAttribute("status") == "installed" + patterns[element.getAttribute("name")] = { +@@ -2753,7 +2767,7 @@ def search(criteria, refresh=False, **kwargs): + + cmd.append(criteria) + solvables = ( +- __zypper__(root=root) ++ __zypper__(root=root, ignore_not_found=True) + .nolock.noraise.xml.call(*cmd) + .getElementsByTagName("solvable") + ) +@@ -3005,7 +3019,7 @@ def _get_patches(installed_only=False, root=None): + """ + patches = {} + for element in ( +- __zypper__(root=root) ++ __zypper__(root=root, ignore_not_found=True) + .nolock.xml.call("se", "-t", "patch") + .getElementsByTagName("solvable") + ): +diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py +index 22137a2544..5e4c967520 100644 +--- a/tests/unit/modules/test_zypperpkg.py ++++ b/tests/unit/modules/test_zypperpkg.py +@@ -28,7 +28,10 @@ class ZyppCallMock: + + def __call__(self, *args, **kwargs): + # If the call is for a configuration modifier, we return self +- if any(i in kwargs for i in ("no_repo_failure", "systemd_scope", "root")): ++ if any( ++ i in kwargs ++ for i in ("no_repo_failure", "ignore_not_found", "systemd_scope", "root") ++ ): + return self + return MagicMock(return_value=self.__return_value)() + +@@ -1662,8 +1665,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + + """ +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + wcard = zypper.Wildcard(_zpr) + wcard.name, wcard.version = "libzypp", "*" + assert wcard._get_scope_versions(wcard._get_available_versions()) == [ +@@ -1685,8 +1689,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + """ + +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + wcard = zypper.Wildcard(_zpr) + wcard.name, wcard.version = "libzypp", "16.2.*-2*" + assert wcard._get_scope_versions(wcard._get_available_versions()) == [ +@@ -1707,8 +1712,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + """ + +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + wcard = zypper.Wildcard(_zpr) + wcard.name, wcard.version = "libzypp", "16.2.5*" + assert wcard._get_scope_versions(wcard._get_available_versions()) == [ +@@ -1728,8 +1734,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + """ + +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + wcard = zypper.Wildcard(_zpr) + wcard.name, wcard.version = "libzypp", "*.1" + assert wcard._get_scope_versions(wcard._get_available_versions()) == [ +@@ -1750,8 +1757,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + + """ +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + assert zypper.Wildcard(_zpr)("libzypp", "16.2.4*") == "16.2.4-19.5" + assert zypper.Wildcard(_zpr)("libzypp", "16.2*") == "16.2.5-25.1" + assert zypper.Wildcard(_zpr)("libzypp", "*6-*") == "17.2.6-27.9.1" +@@ -1770,8 +1778,10 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + + """ +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) ++ + assert zypper.Wildcard(_zpr)("libzypp", None) is None + + def test_wildcard_to_query_typecheck(self): +@@ -1787,8 +1797,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + + """ +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + assert isinstance(zypper.Wildcard(_zpr)("libzypp", "*.1"), str) + + def test_wildcard_to_query_condition_preservation(self): +@@ -1804,8 +1815,9 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + + """ +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) + + for op in zypper.Wildcard.Z_OP: + assert zypper.Wildcard(_zpr)( +@@ -1831,8 +1843,10 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): + + + """ +- _zpr = MagicMock() +- _zpr.nolock.xml.call = MagicMock(return_value=minidom.parseString(xmldoc)) ++ __zpr = MagicMock() ++ __zpr.nolock.xml.call.return_value = minidom.parseString(xmldoc) ++ _zpr = MagicMock(return_value=__zpr) ++ + with self.assertRaises(CommandExecutionError): + for op in [">>", "==", "<<", "+"]: + zypper.Wildcard(_zpr)("libzypp", "{}*.1".format(op)) +@@ -1958,3 +1972,38 @@ pattern() = package-c""" + self.assertFalse(zypper.__zypper__._is_rpm_lock()) + self.assertEqual(lockf_mock.call_count, 2) + zypper.__zypper__._reset() ++ ++ def test_search(self): ++ """Test zypperpkg.search()""" ++ xml_mock = MagicMock(return_value=[]) ++ zypp_mock = MagicMock(return_value=xml_mock) ++ ZyppCallMock(return_value=xml_mock) ++ with patch("salt.modules.zypperpkg.__zypper__", zypp_mock): ++ zypper.search("emacs") ++ zypp_mock.assert_called_with(root=None, ignore_not_found=True) ++ xml_mock.nolock.noraise.xml.call.assert_called_with("search", "emacs") ++ ++ def test_search_not_found(self): ++ """Test zypperpkg.search()""" ++ ret = { ++ "stdout": "", ++ "stderr": None, ++ "retcode": 104, ++ } ++ run_all_mock = MagicMock(return_value=ret) ++ with patch.dict(zypper.__salt__, {"cmd.run_all": run_all_mock}): ++ self.assertRaises(CommandExecutionError, zypper.search, "vim") ++ run_all_mock.assert_called_with( ++ [ ++ "zypper", ++ "--non-interactive", ++ "--xmlout", ++ "--no-refresh", ++ "search", ++ "vim", ++ ], ++ success_retcodes=[104], ++ output_loglevel="trace", ++ python_shell=False, ++ env={"ZYPP_READONLY_HACK": "1"}, ++ ) +-- +2.39.2 + +