Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package salt for openSUSE:Factory checked in at 2022-07-12 11:12:13 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/salt (Old) and /work/SRC/openSUSE:Factory/.salt.new.1523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "salt" Tue Jul 12 11:12:13 2022 rev:130 rq:988366 version:3004 Changes: -------- --- /work/SRC/openSUSE:Factory/salt/salt.changes 2022-06-24 08:45:13.963133946 +0200 +++ /work/SRC/openSUSE:Factory/.salt.new.1523/salt.changes 2022-07-12 11:12:22.719693673 +0200 @@ -1,0 +2,41 @@ +Fri Jul 8 09:45:54 UTC 2022 - Pablo Su??rez Hern??ndez <[email protected]> + +- 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 <[email protected]> + +- 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 <[email protected]> + +- 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 + +------------------------------------------------------------------- New: ---- add-support-for-gpgautoimport-539.patch add-support-for-name-pkgs-and-diff_attr-parameters-t.patch fix-62092-catch-zmq.error.zmqerror-to-set-hwm-for-zm.patch fix-jinja2-contextfuntion-base-on-version-bsc-119874.patch fix-ownership-of-salt-thin-directory-when-using-the-.patch fix-salt.states.file.managed-for-follow_symlinks-tru.patch ignore-erros-on-reading-license-files-with-dpkg_lowp.patch normalize-package-names-once-with-pkg.installed-remo.patch save-log-to-logfile-with-docker.build.patch set-default-target-for-pip-from-venv_pip_target-envi.patch use-salt-bundle-in-dockermod.patch ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ salt.spec ++++++ --- /var/tmp/diff_new_pack.v5BJIy/_old 2022-07-12 11:12:24.179695584 +0200 +++ /var/tmp/diff_new_pack.v5BJIy/_new 2022-07-12 11:12:24.183695589 +0200 @@ -308,7 +308,28 @@ Patch80: make-sure-saltcacheloader-use-correct-fileclient-519.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/commit/e068a34ccb2e17ae7224f8016a24b727f726d4c8 Patch81: fix-for-cve-2022-22967-bsc-1200566.patch - +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61827 +Patch82: ignore-erros-on-reading-license-files-with-dpkg_lowp.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62109 +Patch83: use-salt-bundle-in-dockermod.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61984 +Patch84: save-log-to-logfile-with-docker.build.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62029 +Patch85: normalize-package-names-once-with-pkg.installed-remo.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62089 +Patch86: set-default-target-for-pip-from-venv_pip_target-envi.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/534 +Patch87: fix-ownership-of-salt-thin-directory-when-using-the-.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62033 +Patch88: add-support-for-name-pkgs-and-diff_attr-parameters-t.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62119 +Patch89: fix-62092-catch-zmq.error.zmqerror-to-set-hwm-for-zm.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62067 +Patch90: fix-salt.states.file.managed-for-follow_symlinks-tru.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/61856 +Patch91: fix-jinja2-contextfuntion-base-on-version-bsc-119874.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/62209 +Patch92: add-support-for-gpgautoimport-539.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate ++++++ _lastrevision ++++++ --- /var/tmp/diff_new_pack.v5BJIy/_old 2022-07-12 11:12:24.239695663 +0200 +++ /var/tmp/diff_new_pack.v5BJIy/_new 2022-07-12 11:12:24.239695663 +0200 @@ -1,3 +1,3 @@ -f20138622e17e52fd49e531edd607b46d08a146c +e07459bfeea39239f6b446f40f6502e72dea488f (No newline at EOF) ++++++ add-support-for-gpgautoimport-539.patch ++++++ >From fbd5163bd0d5409a1823e9fb8e0cb623c22d6036 Mon Sep 17 00:00:00 2001 From: Michael Calmer <[email protected]> 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 39d26f0e93..b622105e15 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py @@ -591,7 +591,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"] @@ -705,7 +705,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[:] @@ -1395,7 +1395,6 @@ def mod_repo(repo, **kwargs): cmd_opt.append("--name='{}'".format(kwargs.get("humanname"))) if kwargs.get("gpgautoimport") is True: - global_cmd_opt.append("--gpg-auto-import-keys") call_refresh = True if cmd_opt: @@ -1407,8 +1406,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" @@ -1419,7 +1418,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 @@ -1430,6 +1429,17 @@ def refresh_db(force=None, root=None): {'<database name>': 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. @@ -1450,11 +1460,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: @@ -1639,7 +1660,7 @@ def install( 'arch': '<new-arch>'}}} """ if refresh: - refresh_db(root) + refresh_db(root, **kwargs) try: pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"]( @@ -1934,7 +1955,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") @@ -2844,7 +2865,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": @@ -2995,7 +3016,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 ( @@ -3147,7 +3168,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) @@ -3241,7 +3262,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 fea6eeb004..3f1560a385 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py @@ -358,7 +358,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() @@ -376,6 +381,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): """ @@ -1555,18 +1627,23 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): 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): """ @@ -1585,15 +1662,17 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): "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): """ @@ -1606,10 +1685,10 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): 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} ) @@ -1617,11 +1696,17 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): 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): @@ -1641,16 +1726,19 @@ class ZypperTestCase(TestCase, LoaderModuleMockMixin): "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.36.1 ++++++ add-support-for-name-pkgs-and-diff_attr-parameters-t.patch ++++++ ++++ 1058 lines (skipped) ++++++ fix-62092-catch-zmq.error.zmqerror-to-set-hwm-for-zm.patch ++++++ >From df474d3cc0a5f02591fea093f9efc324c6feef46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= <[email protected]> Date: Thu, 7 Jul 2022 11:38:09 +0100 Subject: [PATCH] Fix #62092: Catch zmq.error.ZMQError to set HWM for zmq >= 3 (#543) It looks like before release 23.0.0, when trying to access zmq.HWM it was raising ``AttributeError``, which is now wrapped under pyzmq's own ``zmq.error.ZMQError``. Simply caching that, should then set the HWM correctly for zmq >= 3 and therefore fix #62092. Co-authored-by: Mircea Ulinic <[email protected]> --- salt/transport/zeromq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/salt/transport/zeromq.py b/salt/transport/zeromq.py index 9e61b23255..aa06298ee1 100644 --- a/salt/transport/zeromq.py +++ b/salt/transport/zeromq.py @@ -898,7 +898,7 @@ class ZeroMQPubServerChannel(salt.transport.server.PubServerChannel): try: pub_sock.setsockopt(zmq.HWM, self.opts.get("pub_hwm", 1000)) # in zmq >= 3.0, there are separate send and receive HWM settings - except AttributeError: + except (AttributeError, zmq.error.ZMQError): # Set the High Water Marks. For more information on HWM, see: # http://api.zeromq.org/4-1:zmq-setsockopt pub_sock.setsockopt(zmq.SNDHWM, self.opts.get("pub_hwm", 1000)) -- 2.36.1 ++++++ fix-jinja2-contextfuntion-base-on-version-bsc-119874.patch ++++++ >From 65494338f5a9bdaa0be27afab3da3a03a92d8cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= <[email protected]> Date: Fri, 8 Jul 2022 13:35:50 +0100 Subject: [PATCH] fix: jinja2 contextfuntion base on version (bsc#1198744) (#520) --- salt/utils/jinja.py | 16 ++++++++++++++-- tests/unit/utils/test_jinja.py | 8 +++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/salt/utils/jinja.py b/salt/utils/jinja.py index 0cb70bf64a..6b5b0d4e81 100644 --- a/salt/utils/jinja.py +++ b/salt/utils/jinja.py @@ -25,7 +25,7 @@ import salt.utils.json import salt.utils.stringutils import salt.utils.url import salt.utils.yaml -from jinja2 import BaseLoader, Markup, TemplateNotFound, nodes +from jinja2 import BaseLoader, TemplateNotFound, nodes from jinja2.environment import TemplateModule from jinja2.exceptions import TemplateRuntimeError from jinja2.ext import Extension @@ -34,6 +34,12 @@ from salt.utils.decorators.jinja import jinja_filter, jinja_global, jinja_test from salt.utils.odict import OrderedDict from salt.utils.versions import LooseVersion +try: + from markupsafe import Markup +except ImportError: + # jinja < 3.1 + from jinja2 import Markup + log = logging.getLogger(__name__) __all__ = ["SaltCacheLoader", "SerializerExtension"] @@ -706,7 +712,13 @@ def method_call(obj, f_name, *f_args, **f_kwargs): return getattr(obj, f_name, lambda *args, **kwargs: None)(*f_args, **f_kwargs) [email protected] +try: + contextfunction = jinja2.contextfunction +except AttributeError: + contextfunction = jinja2.pass_context + + +@contextfunction def show_full_context(ctx): return salt.utils.data.simple_types_filter( {key: value for key, value in ctx.items()} diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index 6502831aff..6bbcf9ef6f 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -22,7 +22,7 @@ import salt.utils.files import salt.utils.json import salt.utils.stringutils import salt.utils.yaml -from jinja2 import DictLoader, Environment, Markup, exceptions +from jinja2 import DictLoader, Environment, exceptions from salt.exceptions import SaltRenderError from salt.utils.decorators.jinja import JinjaFilter from salt.utils.jinja import ( @@ -46,6 +46,12 @@ try: except ImportError: HAS_TIMELIB = False +try: + from markupsafe import Markup +except ImportError: + # jinja < 3.1 + from jinja2 import Markup + BLINESEP = salt.utils.stringutils.to_bytes(os.linesep) -- 2.36.1 ++++++ fix-ownership-of-salt-thin-directory-when-using-the-.patch ++++++ >From 34a81d88db3862bcc03cdda4974e576723af7643 Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <[email protected]> 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.36.1 ++++++ fix-salt.states.file.managed-for-follow_symlinks-tru.patch ++++++ >From 10705d922a11e5f2654d26e83e9f302862fafb18 Mon Sep 17 00:00:00 2001 From: Petr Pavlu <[email protected]> Date: Fri, 8 Jul 2022 10:11:52 +0200 Subject: [PATCH] Fix salt.states.file.managed() for follow_symlinks=True and test=True (bsc#1199372) (#535) When managing file /etc/test as follows: > file /etc/test: > file.managed: > - name: /etc/test > - source: salt://config/test > - mode: 644 > - follow_symlinks: True and with /etc/test being a symlink to a different file, an invocation of "salt-call '*' state.apply test=True" can report that the file should be updated even when a subsequent run of the same command without the test parameter makes no changes. The problem is that the test code path doesn't take correctly into account the follow_symlinks=True setting and ends up comparing permissions of the symlink instead of its target file. The patch addresses the problem by extending functions salt.modules.file.check_managed(), check_managed_changes() and check_file_meta() to have the follow_symlinks parameter which gets propagated to the salt.modules.file.stats() call and by updating salt.states.file.managed() to forward the same parameter to salt.modules.file.check_managed_changes(). Fixes #62066. [Cherry-picked from upstream commit 95bfbe31a2dc54723af3f1783d40de152760fe1a.] --- changelog/62066.fixed | 1 + salt/modules/file.py | 27 +++- salt/states/file.py | 1 + .../unit/modules/file/test_file_check.py | 144 ++++++++++++++++++ 4 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 changelog/62066.fixed create mode 100644 tests/pytests/unit/modules/file/test_file_check.py diff --git a/changelog/62066.fixed b/changelog/62066.fixed new file mode 100644 index 0000000000..68216a03c1 --- /dev/null +++ b/changelog/62066.fixed @@ -0,0 +1 @@ +Fixed salt.states.file.managed() for follow_symlinks=True and test=True diff --git a/salt/modules/file.py b/salt/modules/file.py index 73619064ef..40c07455e3 100644 --- a/salt/modules/file.py +++ b/salt/modules/file.py @@ -5281,11 +5281,18 @@ def check_managed( serole=None, setype=None, serange=None, + follow_symlinks=False, **kwargs ): """ Check to see what changes need to be made for a file + follow_symlinks + If the desired path is a symlink, follow it and check the permissions + of the file to which the symlink points. + + .. versionadded:: 3005 + CLI Example: .. code-block:: bash @@ -5336,6 +5343,7 @@ def check_managed( serole=serole, setype=setype, serange=serange, + follow_symlinks=follow_symlinks, ) # Ignore permission for files written temporary directories # Files in any path will still be set correctly using get_managed() @@ -5372,6 +5380,7 @@ def check_managed_changes( setype=None, serange=None, verify_ssl=True, + follow_symlinks=False, **kwargs ): """ @@ -5387,6 +5396,12 @@ def check_managed_changes( .. versionadded:: 3002 + follow_symlinks + If the desired path is a symlink, follow it and check the permissions + of the file to which the symlink points. + + .. versionadded:: 3005 + CLI Example: .. code-block:: bash @@ -5456,6 +5471,7 @@ def check_managed_changes( serole=serole, setype=setype, serange=serange, + follow_symlinks=follow_symlinks, ) __clean_tmp(sfn) return changes @@ -5477,6 +5493,7 @@ def check_file_meta( setype=None, serange=None, verify_ssl=True, + follow_symlinks=False, ): """ Check for the changes in the file metadata. @@ -5553,6 +5570,12 @@ def check_file_meta( will not attempt to validate the servers certificate. Default is True. .. versionadded:: 3002 + + follow_symlinks + If the desired path is a symlink, follow it and check the permissions + of the file to which the symlink points. + + .. versionadded:: 3005 """ changes = {} if not source_sum: @@ -5560,7 +5583,9 @@ def check_file_meta( try: lstats = stats( - name, hash_type=source_sum.get("hash_type", None), follow_symlinks=False + name, + hash_type=source_sum.get("hash_type", None), + follow_symlinks=follow_symlinks, ) except CommandExecutionError: lstats = {} diff --git a/salt/states/file.py b/salt/states/file.py index 54e7decf86..a6288025e5 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -3038,6 +3038,7 @@ def managed( setype=setype, serange=serange, verify_ssl=verify_ssl, + follow_symlinks=follow_symlinks, **kwargs ) diff --git a/tests/pytests/unit/modules/file/test_file_check.py b/tests/pytests/unit/modules/file/test_file_check.py new file mode 100644 index 0000000000..bd0379ddae --- /dev/null +++ b/tests/pytests/unit/modules/file/test_file_check.py @@ -0,0 +1,144 @@ +import getpass +import logging +import os + +import pytest +import salt.modules.file as filemod +import salt.utils.files +import salt.utils.platform + +log = logging.getLogger(__name__) + + [email protected] +def configure_loader_modules(): + return {filemod: {"__context__": {}}} + + [email protected] +def tfile(tmp_path): + filename = str(tmp_path / "file-check-test-file") + + with salt.utils.files.fopen(filename, "w") as fp: + fp.write("Hi hello! I am a file.") + os.chmod(filename, 0o644) + + yield filename + + os.remove(filename) + + [email protected] +def a_link(tmp_path, tfile): + linkname = str(tmp_path / "a_link") + os.symlink(tfile, linkname) + + yield linkname + + os.remove(linkname) + + +def get_link_perms(): + if salt.utils.platform.is_linux(): + return "0777" + return "0755" + + [email protected]_on_windows(reason="os.symlink is not available on Windows") +def test_check_file_meta_follow_symlinks(a_link, tfile): + user = getpass.getuser() + lperms = get_link_perms() + + # follow_symlinks=False (default) + ret = filemod.check_file_meta( + a_link, tfile, None, None, user, None, lperms, None, None + ) + assert ret == {} + + ret = filemod.check_file_meta( + a_link, tfile, None, None, user, None, "0644", None, None + ) + assert ret == {"mode": "0644"} + + # follow_symlinks=True + ret = filemod.check_file_meta( + a_link, tfile, None, None, user, None, "0644", None, None, follow_symlinks=True + ) + assert ret == {} + + [email protected]_on_windows(reason="os.symlink is not available on Windows") +def test_check_managed_follow_symlinks(a_link, tfile): + user = getpass.getuser() + lperms = get_link_perms() + + # Function check_managed() ignores mode changes for files in the temp directory. + # Trick it to not recognize a_link as such. + a_link = "/" + a_link + + # follow_symlinks=False (default) + ret, comments = filemod.check_managed( + a_link, tfile, None, None, user, None, lperms, None, None, None, None, None + ) + assert ret is True + assert comments == "The file {} is in the correct state".format(a_link) + + ret, comments = filemod.check_managed( + a_link, tfile, None, None, user, None, "0644", None, None, None, None, None + ) + assert ret is None + assert comments == "The following values are set to be changed:\nmode: 0644\n" + + # follow_symlinks=True + ret, comments = filemod.check_managed( + a_link, + tfile, + None, + None, + user, + None, + "0644", + None, + None, + None, + None, + None, + follow_symlinks=True, + ) + assert ret is True + assert comments == "The file {} is in the correct state".format(a_link) + + [email protected]_on_windows(reason="os.symlink is not available on Windows") +def test_check_managed_changes_follow_symlinks(a_link, tfile): + user = getpass.getuser() + lperms = get_link_perms() + + # follow_symlinks=False (default) + ret = filemod.check_managed_changes( + a_link, tfile, None, None, user, None, lperms, None, None, None, None, None + ) + assert ret == {} + + ret = filemod.check_managed_changes( + a_link, tfile, None, None, user, None, "0644", None, None, None, None, None + ) + assert ret == {"mode": "0644"} + + # follow_symlinks=True + ret = filemod.check_managed_changes( + a_link, + tfile, + None, + None, + user, + None, + "0644", + None, + None, + None, + None, + None, + follow_symlinks=True, + ) + assert ret == {} -- 2.36.1 ++++++ ignore-erros-on-reading-license-files-with-dpkg_lowp.patch ++++++ >From 90d0e3ce40e46a9bff3e477b61e02cf3e87e8b9f Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <[email protected]> Date: Mon, 27 Jun 2022 17:55:49 +0300 Subject: [PATCH] Ignore erros on reading license files with dpkg_lowpkg (bsc#1197288) * Ignore erros on reading license files with dpkg_lowpkg (bsc#1197288) * Add test for license reading with dpkg_lowpkg --- salt/modules/dpkg_lowpkg.py | 2 +- tests/pytests/unit/modules/test_dpkg_lowpkg.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/pytests/unit/modules/test_dpkg_lowpkg.py diff --git a/salt/modules/dpkg_lowpkg.py b/salt/modules/dpkg_lowpkg.py index afbd619490..2c25b1fb2a 100644 --- a/salt/modules/dpkg_lowpkg.py +++ b/salt/modules/dpkg_lowpkg.py @@ -361,7 +361,7 @@ def _get_pkg_license(pkg): licenses = set() cpr = "/usr/share/doc/{}/copyright".format(pkg) if os.path.exists(cpr): - with salt.utils.files.fopen(cpr) as fp_: + with salt.utils.files.fopen(cpr, errors="ignore") as fp_: for line in salt.utils.stringutils.to_unicode(fp_.read()).split(os.linesep): if line.startswith("License:"): licenses.add(line.split(":", 1)[1].strip()) diff --git a/tests/pytests/unit/modules/test_dpkg_lowpkg.py b/tests/pytests/unit/modules/test_dpkg_lowpkg.py new file mode 100644 index 0000000000..1a89660c02 --- /dev/null +++ b/tests/pytests/unit/modules/test_dpkg_lowpkg.py @@ -0,0 +1,18 @@ +import os + +import salt.modules.dpkg_lowpkg as dpkg +from tests.support.mock import MagicMock, mock_open, patch + + +def test_get_pkg_license(): + """ + Test _get_pkg_license for ignore errors on reading license from copyright files + """ + license_read_mock = mock_open(read_data="") + with patch.object(os.path, "exists", MagicMock(return_value=True)), patch( + "salt.utils.files.fopen", license_read_mock + ): + dpkg._get_pkg_license("bash") + + assert license_read_mock.calls[0].args[0] == "/usr/share/doc/bash/copyright" + assert license_read_mock.calls[0].kwargs["errors"] == "ignore" -- 2.36.1 ++++++ normalize-package-names-once-with-pkg.installed-remo.patch ++++++ >From 09afcd0d04788ec4351c1c0b73a0c6eb3b0fd8c9 Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <[email protected]> Date: Mon, 27 Jun 2022 18:01:21 +0300 Subject: [PATCH] Normalize package names once with pkg.installed/removed using yum (bsc#1195895) * Normalize the package names only once on install/remove * Add test for checking pkg.installed/removed with only normalisation * Fix split_arch conditions * Fix test_pkg --- salt/modules/yumpkg.py | 18 ++- salt/states/pkg.py | 3 +- tests/pytests/unit/states/test_pkg.py | 177 +++++++++++++++++++++++++- 3 files changed, 192 insertions(+), 6 deletions(-) diff --git a/salt/modules/yumpkg.py b/salt/modules/yumpkg.py index 9f8f548e5f..3138ac2e59 100644 --- a/salt/modules/yumpkg.py +++ b/salt/modules/yumpkg.py @@ -1449,7 +1449,12 @@ def install( try: pkg_params, pkg_type = __salt__["pkg_resource.parse_targets"]( - name, pkgs, sources, saltenv=saltenv, normalize=normalize, **kwargs + name, + pkgs, + sources, + saltenv=saltenv, + normalize=normalize and kwargs.get("split_arch", True), + **kwargs ) except MinionError as exc: raise CommandExecutionError(exc) @@ -1603,7 +1608,10 @@ def install( except ValueError: pass else: - if archpart in salt.utils.pkg.rpm.ARCHES: + if archpart in salt.utils.pkg.rpm.ARCHES and ( + archpart != __grains__["osarch"] + or kwargs.get("split_arch", True) + ): arch = "." + archpart pkgname = namepart @@ -2134,11 +2142,13 @@ def remove(name=None, pkgs=None, **kwargs): # pylint: disable=W0613 arch = "" pkgname = target try: - namepart, archpart = target.rsplit(".", 1) + namepart, archpart = pkgname.rsplit(".", 1) except ValueError: pass else: - if archpart in salt.utils.pkg.rpm.ARCHES: + if archpart in salt.utils.pkg.rpm.ARCHES and ( + archpart != __grains__["osarch"] or kwargs.get("split_arch", True) + ): arch = "." + archpart pkgname = namepart # Since we don't always have the arch info, epoch information has to parsed out. But diff --git a/salt/states/pkg.py b/salt/states/pkg.py index 0d601e1aaf..71298e6c7a 100644 --- a/salt/states/pkg.py +++ b/salt/states/pkg.py @@ -1901,6 +1901,7 @@ def installed( normalize=normalize, update_holds=update_holds, ignore_epoch=ignore_epoch, + split_arch=False, **kwargs ) except CommandExecutionError as exc: @@ -2973,7 +2974,7 @@ def _uninstall( } changes = __salt__["pkg.{}".format(action)]( - name, pkgs=pkgs, version=version, **kwargs + name, pkgs=pkgs, version=version, split_arch=False, **kwargs ) new = __salt__["pkg.list_pkgs"](versions_as_list=True, **kwargs) failed = [] diff --git a/tests/pytests/unit/states/test_pkg.py b/tests/pytests/unit/states/test_pkg.py index 17b91bcb39..10acae9f88 100644 --- a/tests/pytests/unit/states/test_pkg.py +++ b/tests/pytests/unit/states/test_pkg.py @@ -2,6 +2,8 @@ import logging import pytest import salt.modules.beacons as beaconmod +import salt.modules.pkg_resource as pkg_resource +import salt.modules.yumpkg as yumpkg import salt.states.beacon as beaconstate import salt.states.pkg as pkg import salt.utils.state as state_utils @@ -17,7 +19,7 @@ def configure_loader_modules(): pkg: { "__env__": "base", "__salt__": {}, - "__grains__": {"os": "CentOS"}, + "__grains__": {"os": "CentOS", "os_family": "RedHat"}, "__opts__": {"test": False, "cachedir": ""}, "__instance_id__": "", "__low__": {}, @@ -25,6 +27,15 @@ def configure_loader_modules(): }, beaconstate: {"__salt__": {}, "__opts__": {}}, beaconmod: {"__salt__": {}, "__opts__": {}}, + pkg_resource: { + "__salt__": {}, + "__grains__": {"os": "CentOS", "os_family": "RedHat"}, + }, + yumpkg: { + "__salt__": {}, + "__grains__": {"osarch": "x86_64", "osmajorrelease": 7}, + "__opts__": {}, + }, } @@ -715,3 +726,167 @@ def test_held_unheld(package_manager): hold_mock.assert_not_called() unhold_mock.assert_any_call(name="held-test", pkgs=["baz"]) unhold_mock.assert_any_call(name="held-test", pkgs=["bar"]) + + +def test_installed_with_single_normalize(): + """ + Test pkg.installed with preventing multiple package name normalisation + """ + + list_no_weird_installed = { + "pkga": "1.0.1", + "pkgb": "1.0.2", + "pkgc": "1.0.3", + } + list_no_weird_installed_ver_list = { + "pkga": ["1.0.1"], + "pkgb": ["1.0.2"], + "pkgc": ["1.0.3"], + } + list_with_weird_installed = { + "pkga": "1.0.1", + "pkgb": "1.0.2", + "pkgc": "1.0.3", + "weird-name-1.2.3-1234.5.6.test7tst.x86_64": "20220214-2.1", + } + list_with_weird_installed_ver_list = { + "pkga": ["1.0.1"], + "pkgb": ["1.0.2"], + "pkgc": ["1.0.3"], + "weird-name-1.2.3-1234.5.6.test7tst.x86_64": ["20220214-2.1"], + } + list_pkgs = MagicMock( + side_effect=[ + # For the package with version specified + list_no_weird_installed_ver_list, + {}, + list_no_weird_installed, + list_no_weird_installed_ver_list, + list_with_weird_installed, + list_with_weird_installed_ver_list, + # For the package with no version specified + list_no_weird_installed_ver_list, + {}, + list_no_weird_installed, + list_no_weird_installed_ver_list, + list_with_weird_installed, + list_with_weird_installed_ver_list, + ] + ) + + salt_dict = { + "pkg.install": yumpkg.install, + "pkg.list_pkgs": list_pkgs, + "pkg.normalize_name": yumpkg.normalize_name, + "pkg_resource.version_clean": pkg_resource.version_clean, + "pkg_resource.parse_targets": pkg_resource.parse_targets, + } + + with patch("salt.modules.yumpkg.list_pkgs", list_pkgs), patch( + "salt.modules.yumpkg.version_cmp", MagicMock(return_value=0) + ), patch( + "salt.modules.yumpkg._call_yum", MagicMock(return_value={"retcode": 0}) + ) as call_yum_mock, patch.dict( + pkg.__salt__, salt_dict + ), patch.dict( + pkg_resource.__salt__, salt_dict + ), patch.dict( + yumpkg.__salt__, salt_dict + ), patch.dict( + yumpkg.__grains__, {"os": "CentOS", "osarch": "x86_64", "osmajorrelease": 7} + ), patch.object( + yumpkg, "list_holds", MagicMock() + ): + + expected = { + "weird-name-1.2.3-1234.5.6.test7tst.x86_64": { + "old": "", + "new": "20220214-2.1", + } + } + ret = pkg.installed( + "test_install", + pkgs=[{"weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch": "20220214-2.1"}], + ) + call_yum_mock.assert_called_once() + assert ( + call_yum_mock.mock_calls[0].args[0][2] + == "weird-name-1.2.3-1234.5.6.test7tst.x86_64-20220214-2.1" + ) + assert ret["result"] + assert ret["changes"] == expected + + +def test_removed_with_single_normalize(): + """ + Test pkg.removed with preventing multiple package name normalisation + """ + + list_no_weird_installed = { + "pkga": "1.0.1", + "pkgb": "1.0.2", + "pkgc": "1.0.3", + } + list_no_weird_installed_ver_list = { + "pkga": ["1.0.1"], + "pkgb": ["1.0.2"], + "pkgc": ["1.0.3"], + } + list_with_weird_installed = { + "pkga": "1.0.1", + "pkgb": "1.0.2", + "pkgc": "1.0.3", + "weird-name-1.2.3-1234.5.6.test7tst.x86_64": "20220214-2.1", + } + list_with_weird_installed_ver_list = { + "pkga": ["1.0.1"], + "pkgb": ["1.0.2"], + "pkgc": ["1.0.3"], + "weird-name-1.2.3-1234.5.6.test7tst.x86_64": ["20220214-2.1"], + } + list_pkgs = MagicMock( + side_effect=[ + list_with_weird_installed_ver_list, + list_with_weird_installed, + list_no_weird_installed, + list_no_weird_installed_ver_list, + ] + ) + + salt_dict = { + "pkg.remove": yumpkg.remove, + "pkg.list_pkgs": list_pkgs, + "pkg.normalize_name": yumpkg.normalize_name, + "pkg_resource.parse_targets": pkg_resource.parse_targets, + "pkg_resource.version_clean": pkg_resource.version_clean, + } + + with patch("salt.modules.yumpkg.list_pkgs", list_pkgs), patch( + "salt.modules.yumpkg.version_cmp", MagicMock(return_value=0) + ), patch( + "salt.modules.yumpkg._call_yum", MagicMock(return_value={"retcode": 0}) + ) as call_yum_mock, patch.dict( + pkg.__salt__, salt_dict + ), patch.dict( + pkg_resource.__salt__, salt_dict + ), patch.dict( + yumpkg.__salt__, salt_dict + ): + + expected = { + "weird-name-1.2.3-1234.5.6.test7tst.x86_64": { + "old": "20220214-2.1", + "new": "", + } + } + ret = pkg.removed( + "test_remove", + pkgs=[{"weird-name-1.2.3-1234.5.6.test7tst.x86_64.noarch": "20220214-2.1"}], + ) + call_yum_mock.assert_called_once() + assert ( + call_yum_mock.mock_calls[0].args[0][2] + == "weird-name-1.2.3-1234.5.6.test7tst.x86_64-20220214-2.1" + ) + assert ret["result"] + assert ret["changes"] == expected -- 2.36.1 ++++++ save-log-to-logfile-with-docker.build.patch ++++++ >From c70db2e50599339118c9bf00c69f5cd38ef220bb Mon Sep 17 00:00:00 2001 From: Vladimir Nadvornik <[email protected]> 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 e6b81e7f09..1f871b40cf 100644 --- a/salt/modules/dockermod.py +++ b/salt/modules/dockermod.py @@ -3990,6 +3990,7 @@ def build( fileobj=None, dockerfile=None, buildargs=None, + logfile=None, ): """ .. versionchanged:: 2018.3.0 @@ -4043,6 +4044,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** @@ -4117,6 +4121,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.36.1 ++++++ set-default-target-for-pip-from-venv_pip_target-envi.patch ++++++ ++++ 1862 lines (skipped) ++++++ use-salt-bundle-in-dockermod.patch ++++++ >From ed53e3cbd62352b8d2af4d4b36c03e40981263bb Mon Sep 17 00:00:00 2001 From: Victor Zhestkov <[email protected]> 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 fdded88dbb..e6b81e7f09 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 @@ -6682,6 +6687,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 @@ -6717,47 +6827,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 47fe5d55e6..19c7f450d7 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 import salt.config @@ -26,6 +27,7 @@ def configure_loader_modules(): 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": {}, } + + [email protected]_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.36.1
