Do not allow python2 to be added to salt-ssh tar

This commit is contained in:
ch3ll 2020-09-03 10:02:13 -04:00 committed by Daniel Wozniak
parent bcde0376b1
commit af7000f9dd
8 changed files with 209 additions and 308 deletions

1
changelog/57647.fixed Normal file
View file

@ -0,0 +1 @@
Do not allow python2 to be added to salt-ssh tar since Salt deprecated Python 2.

View file

@ -102,10 +102,10 @@ Alternatively ssh agent forwarding can be used by setting the priv to agent-forw
Calling Salt SSH
================
.. note:: ``salt-ssh`` on RHEL/CentOS 5
.. note:: ``salt-ssh`` on target hosts without Python 3
The ``salt-ssh`` command requires at least python 2.6, which is not
installed by default on RHEL/CentOS 5. An easy workaround in this
The ``salt-ssh`` command requires at least python 3, which is not
installed by default on some target hosts. An easy workaround in this
situation is to use the ``-r`` option to run a raw shell command that
installs python26:
@ -322,4 +322,5 @@ is being dropped we have provided multiple ways to work around this with Salt-SS
can use the following options:
* :ref:`ssh_pre_flight <ssh_pre_flight>`
* :ref:`SSH ext alternatives <ssh-ext-alternatives>`
* Using the Salt-SSH raw shell calls to install Python3.
* Use an older version of Salt on the target host that still supports Python 2 using the feature :ref:`SSH ext alternatives <ssh-ext-alternatives>`

View file

@ -5,10 +5,10 @@ SSH Ext Alternatives
====================
In the 2019.2.0 release the ``ssh_ext_alternatives`` feature was added.
This allows salt-ssh to work across different python versions. You will
This allows salt-ssh to work across different supported python versions. You will
need to ensure you have the following:
- Salt is installed, with all required dependnecies for both Python2 and Python3
- Salt is installed, with all required dependnecies for the Python version.
- Everything needs to be importable from the respective Python environment.
To enable using this feature you will need to edit the master configuration similar
@ -31,6 +31,14 @@ to below:
markupsafe: /opt/markupsafe
backports_abc: /opt/backports_abc.py
.. warning::
When using Salt versions >= 3001 and Python 2 is your ``py-version``
you need to use an older version of Salt that supports Python 2.
For example, if using Salt-SSH version 3001 and you do not want
to install Python 3 on your target host you can use ``ssh_ext_alternatives``'s
``path`` option. This option needs to point to a 2019.2.3 Salt installation directory
on your Salt-SSH host, which still supports Python 2.
auto_detect
-----------

View file

@ -1527,30 +1527,6 @@ ARGS = {arguments}\n'''.format(
"Permissions problem, target user may need " "to be root or use sudo:\n {0}"
)
def _version_mismatch_error():
messages = {
2: {
6: "Install Python 2.7 / Python 3 Salt dependencies on the Salt SSH master \n"
"to interact with Python 2.7 / Python 3 targets",
7: "Install Python 2.6 / Python 3 Salt dependencies on the Salt SSH master \n"
"to interact with Python 2.6 / Python 3 targets",
},
3: {
"default": "- Install Python 2.6/2.7 Salt dependencies on the Salt SSH \n"
" master to interact with Python 2.6/2.7 targets\n"
"- Install Python 3 on the target machine(s)",
},
"default": "Matching major/minor Python release (>=2.6) needed both on the Salt SSH \n"
"master and target machine",
}
major, minor = sys.version_info[:2]
help_msg = (
messages.get(major, {}).get(minor)
or messages.get(major, {}).get("default")
or messages["default"]
)
return "Python version error. Recommendation(s) follow:\n" + help_msg
errors = [
(
(),
@ -1560,7 +1536,9 @@ ARGS = {arguments}\n'''.format(
(
(salt.defaults.exitcodes.EX_THIN_PYTHON_INVALID,),
"Python interpreter is too old",
_version_mismatch_error(),
"Python version error. Recommendation(s) follow:\n"
"- Install Python 3 on the target machine(s)\n"
"- You can use ssh_pre_flight or raw shell (-r) to install Python 3",
),
(
(salt.defaults.exitcodes.EX_THIN_CHECKSUM,),

View file

@ -1,8 +1,6 @@
# -*- coding: utf-8 -*-
"""
Generate the salt thin tarball from the installed python files
"""
from __future__ import absolute_import, print_function, unicode_literals
import copy
import logging
@ -14,7 +12,6 @@ import tarfile
import tempfile
import zipfile
import distro
import jinja2
import msgpack
import salt
@ -72,11 +69,13 @@ except ImportError:
except ImportError:
ssl_match_hostname = None
# pylint: enable=import-error,no-name-in-module
if _six.PY2:
import concurrent
distro = None
else:
import distro
concurrent = None
@ -177,7 +176,7 @@ def gte():
return salt.utils.json.dumps(tops, ensure_ascii=False)
def get_tops_python(py_ver, exclude=None):
def get_tops_python(py_ver, exclude=None, ext_py_ver=None):
"""
Get top directories for the ssh_ext_alternatives dependencies
automatically for the given python version. This allows
@ -188,10 +187,12 @@ def get_tops_python(py_ver, exclude=None):
:param exclude:
list of modules not to auto detect
:param ext_py_ver:
the py-version from the ssh_ext_alternatives config
"""
files = {}
for mod in [
"distro",
mods = [
"jinja2",
"yaml",
"tornado",
@ -203,7 +204,11 @@ def get_tops_python(py_ver, exclude=None):
"ssl_match_hostname",
"markupsafe",
"backports_abc",
]:
]
if ext_py_ver and tuple(ext_py_ver) >= (3, 0):
mods.append("distro")
for mod in mods:
if exclude and mod in exclude:
continue
@ -242,7 +247,7 @@ def get_ext_tops(config):
"""
config = copy.deepcopy(config)
alternatives = {}
required = ["jinja2", "yaml", "tornado", "msgpack", "distro"]
required = ["jinja2", "yaml", "tornado", "msgpack"]
tops = []
for ns, cfg in salt.ext.six.iteritems(config or {}):
alternatives[ns] = cfg
@ -258,6 +263,9 @@ def get_ext_tops(config):
if err_msg:
raise salt.exceptions.SaltSystemExit(err_msg)
if tuple(locked_py_version) >= (3, 0) and "distro" not in required:
required.append("distro")
if cfg.get("dependencies") == "inherit":
# TODO: implement inheritance of the modules from _here_
raise NotImplementedError("This feature is not yet implemented")
@ -293,7 +301,7 @@ def get_ext_tops(config):
" in the external configuration: {}".format(required)
)
log.error(msg)
raise salt.exceptions.SaltSystemExit(msg)
raise salt.exceptions.SaltSystemExit(msg=msg)
alternatives[ns]["dependencies"] = tops
return alternatives
@ -381,7 +389,7 @@ def _get_supported_py_config(tops, extended_cfg):
for the supported Python interpreter versions. This is then written into the thin.tgz
archive and then verified by salt.client.ssh.ssh_py_shim.get_executable()
Note: Minimum default of 2.x versions is 2.7 and 3.x is 3.0, unless specified in namespaces.
Note: Minimum default of 3.x is 3.0, unless specified in namespaces.
:return:
"""
@ -437,7 +445,9 @@ def _pack_alternative(extended_cfg, digest_collector, tfp):
config[ns]["dependencies"] = {}
# get auto deps
auto_deps = get_tops_python(py_ver, exclude=exclude)
auto_deps = get_tops_python(
py_ver, exclude=exclude, ext_py_ver=cfg["py-version"]
)
for dep in auto_deps:
config[ns]["dependencies"][dep] = auto_deps[dep]
@ -450,7 +460,7 @@ def _pack_alternative(extended_cfg, digest_collector, tfp):
base, top_dirname = os.path.basename(top), os.path.dirname(top)
os.chdir(top_dirname)
site_pkg_dir = (
_is_shareable(base) and "pyall" or "py{0}".format(py_ver_major)
_is_shareable(base) and "pyall" or "py{}".format(py_ver_major)
)
log.debug(
'Packing alternative "%s" to "%s/%s" destination',
@ -510,9 +520,14 @@ def gen_thin(
salt-run thin.generate mako,wempy 1
salt-run thin.generate overwrite=1
"""
if sys.version_info < (2, 6):
if python2_bin != "python2" or python3_bin != "python3":
salt.utils.versions.warn_until(
"Silicon",
"python2_bin and python3_bin are no longer used, please update your call to gen_thin",
)
if sys.version_info < (3,):
raise salt.exceptions.SaltSystemExit(
'The minimum required python version to run salt-ssh is "2.6".'
'The minimum required python version to run salt-ssh is "3".'
)
if compress not in ["gzip", "zip"]:
log.warning(
@ -562,93 +577,12 @@ def gen_thin(
)
else:
return thintar
if _six.PY3:
# Let's check for the minimum python 2 version requirement, 2.6
if not salt.utils.path.which(python2_bin):
log.debug(
"%s binary does not exist. Will not detect Python 2 version",
python2_bin,
)
else:
py_shell_cmd = "{} -c 'import sys;sys.stdout.write(\"%s.%s\\n\" % sys.version_info[:2]);'".format(
python2_bin
)
cmd = subprocess.Popen(py_shell_cmd, stdout=subprocess.PIPE, shell=True)
stdout, _ = cmd.communicate()
if cmd.returncode == 0:
py2_version = tuple(
int(n) for n in stdout.decode("utf-8").strip().split(".")
)
if py2_version < (2, 6):
raise salt.exceptions.SaltSystemExit(
'The minimum required python version to run salt-ssh is "2.6".'
'The version reported by "{0}" is "{1}". Please try "salt-ssh '
'--python2-bin=<path-to-python-2.6-binary-or-higher>".'.format(
python2_bin, stdout.strip()
)
)
else:
log.debug("Unable to detect %s version", python2_bin)
log.debug(stdout)
tops_failure_msg = "Failed %s tops for Python binary %s."
python_check_msg = (
"%s binary does not exist. Will not attempt to generate tops for Python %s"
)
tops_py_version_mapping = {}
tops = get_tops(extra_mods=extra_mods, so_mods=so_mods)
tops_py_version_mapping[sys.version_info.major] = tops
# Collect tops, alternative to 2.x version
if _six.PY2 and sys.version_info.major == 2:
# Get python 3 tops
if not salt.utils.path.which(python3_bin):
log.debug(python_check_msg, python3_bin, "3")
else:
py_shell_cmd = "{0} -c 'import salt.utils.thin as t;print(t.gte())' '{1}'".format(
python3_bin,
salt.utils.json.dumps({"extra_mods": extra_mods, "so_mods": so_mods}),
)
cmd = subprocess.Popen(
py_shell_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
)
stdout, stderr = cmd.communicate()
if cmd.returncode == 0:
try:
tops = salt.utils.json.loads(stdout)
tops_py_version_mapping["3"] = tops
except ValueError as err:
log.error(tops_failure_msg, "parsing", python3_bin)
log.exception(err)
else:
log.debug(tops_failure_msg, "collecting", python3_bin)
log.debug(stderr)
# Collect tops, alternative to 3.x version
if _six.PY3 and sys.version_info.major == 3:
# Get python 2 tops
if not salt.utils.path.which(python2_bin):
log.debug(python_check_msg, python2_bin, "2")
else:
py_shell_cmd = "{0} -c 'import salt.utils.thin as t;print(t.gte())' '{1}'".format(
python2_bin,
salt.utils.json.dumps({"extra_mods": extra_mods, "so_mods": so_mods}),
)
cmd = subprocess.Popen(
py_shell_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
)
stdout, stderr = cmd.communicate()
if cmd.returncode == 0:
try:
tops = salt.utils.json.loads(stdout.decode("utf-8"))
tops_py_version_mapping["2"] = tops
except ValueError as err:
log.error(tops_failure_msg, "parsing", python2_bin)
log.exception(err)
else:
log.debug(tops_failure_msg, "collecting", python2_bin)
log.debug(stderr)
with salt.utils.files.fopen(pymap_cfg, "wb") as fp_:
fp_.write(
_get_supported_py_config(
@ -760,7 +694,7 @@ def thin_sum(cachedir, form="sha1"):
code_checksum_path = os.path.join(cachedir, "thin", "code-checksum")
if os.path.isfile(code_checksum_path):
with salt.utils.files.fopen(code_checksum_path, "r") as fh:
code_checksum = "'{0}'".format(fh.read().strip())
code_checksum = "'{}'".format(fh.read().strip())
else:
code_checksum = "'0'"
@ -789,6 +723,11 @@ def gen_min(
salt-run min.generate mako,wempy 1
salt-run min.generate overwrite=1
"""
if python2_bin != "python2" or python3_bin != "python3":
salt.utils.versions.warn_until(
"Silicon",
"python2_bin and python3_bin are no longer used, please update your call to gen_min",
)
mindir = os.path.join(cachedir, "min")
if not os.path.isdir(mindir):
os.makedirs(mindir)
@ -818,81 +757,10 @@ def gen_min(
pass
else:
return mintar
if _six.PY3:
# Let's check for the minimum python 2 version requirement, 2.6
py_shell_cmd = (
python2_bin + " -c 'from __future__ import print_function; import sys; "
'print("{0}.{1}".format(*(sys.version_info[:2])));\''
)
cmd = subprocess.Popen(py_shell_cmd, stdout=subprocess.PIPE, shell=True)
stdout, _ = cmd.communicate()
if cmd.returncode == 0:
py2_version = tuple(
int(n) for n in stdout.decode("utf-8").strip().split(".")
)
if py2_version < (2, 6):
# Bail!
raise salt.exceptions.SaltSystemExit(
'The minimum required python version to run salt-ssh is "2.6".'
'The version reported by "{0}" is "{1}". Please try "salt-ssh '
'--python2-bin=<path-to-python-2.6-binary-or-higher>".'.format(
python2_bin, stdout.strip()
)
)
elif sys.version_info < (2, 6):
# Bail! Though, how did we reached this far in the first place.
raise salt.exceptions.SaltSystemExit(
'The minimum required python version to run salt-ssh is "2.6".'
)
tops_py_version_mapping = {}
tops = get_tops(extra_mods=extra_mods, so_mods=so_mods)
if _six.PY2:
tops_py_version_mapping["2"] = tops
else:
tops_py_version_mapping["3"] = tops
# TODO: Consider putting known py2 and py3 compatible libs in its own sharable directory.
# This would reduce the min size.
if _six.PY2 and sys.version_info[0] == 2:
# Get python 3 tops
py_shell_cmd = (
python3_bin + " -c 'import sys; import json; import salt.utils.thin; "
"print(json.dumps(salt.utils.thin.get_tops(**(json.loads(sys.argv[1]))), ensure_ascii=False)); exit(0);' "
"'{0}'".format(
salt.utils.json.dumps({"extra_mods": extra_mods, "so_mods": so_mods})
)
)
cmd = subprocess.Popen(
py_shell_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
)
stdout, stderr = cmd.communicate()
if cmd.returncode == 0:
try:
tops = salt.utils.json.loads(stdout)
tops_py_version_mapping["3"] = tops
except ValueError:
pass
if _six.PY3 and sys.version_info[0] == 3:
# Get python 2 tops
py_shell_cmd = (
python2_bin + " -c 'from __future__ import print_function; "
"import sys; import json; import salt.utils.thin; "
"print(json.dumps(salt.utils.thin.get_tops(**(json.loads(sys.argv[1]))), ensure_ascii=False)); exit(0);' "
"'{0}'".format(
salt.utils.json.dumps({"extra_mods": extra_mods, "so_mods": so_mods})
)
)
cmd = subprocess.Popen(
py_shell_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
)
stdout, stderr = cmd.communicate()
if cmd.returncode == 0:
try:
tops = salt.utils.json.loads(stdout.decode("utf-8"))
tops_py_version_mapping["2"] = tops
except ValueError:
pass
tops_py_version_mapping["3"] = tops
tfp = tarfile.open(mintar, "w:gz", dereference=True)
try: # cwd may not exist if it was removed but salt was run from it
@ -1038,7 +906,7 @@ def gen_min(
os.chdir(tempdir)
if not os.path.isdir(top):
# top is a single file module
tfp.add(base, arcname=os.path.join("py{0}".format(py_ver), base))
tfp.add(base, arcname=os.path.join("py{}".format(py_ver), base))
continue
for root, dirs, files in salt.utils.path.os_walk(base, followlinks=True):
for name in files:
@ -1051,7 +919,7 @@ def gen_min(
continue
tfp.add(
os.path.join(root, name),
arcname=os.path.join("py{0}".format(py_ver), root, name),
arcname=os.path.join("py{}".format(py_ver), root, name),
)
if tempdir is not None:
shutil.rmtree(tempdir)

View file

@ -0,0 +1,58 @@
import pytest
import salt.utils.msgpack
from salt.client import ssh
from tests.support.mock import MagicMock, patch
@pytest.fixture()
def ssh_target(tmpdir):
argv = [
"ssh.set_auth_key",
"root",
"hobn+amNAXSBTiOXEqlBjGB...rsa root@master",
]
opts = {
"argv": argv,
"__role": "master",
"cachedir": tmpdir.strpath,
"extension_modules": tmpdir.join("extmods"),
}
target = {
"passwd": "abc123",
"ssh_options": None,
"sudo": False,
"identities_only": False,
"host": "login1",
"user": "root",
"timeout": 65,
"remote_port_forwards": None,
"sudo_user": "",
"port": "22",
"priv": "/etc/salt/pki/master/ssh/salt-ssh.rsa",
}
return opts, target
def test_cmd_block_python_version_error(ssh_target):
opts = ssh_target[0]
target = ssh_target[1]
single = ssh.Single(
opts,
opts["argv"],
"localhost",
mods={},
fsclient=None,
thin=salt.utils.thin.thin_path(opts["cachedir"]),
mine=False,
winrm=False,
**target
)
mock_shim = MagicMock(
return_value=(("", "ERROR: Unable to locate appropriate python command\n", 10))
)
patch_shim = patch("salt.client.ssh.Single.shim_cmd", mock_shim)
with patch_shim:
ret = single.cmd_block()
assert "ERROR: Python version error. Recommendation(s) follow:" in ret[0]

View file

@ -0,0 +1,77 @@
import pytest
import salt.exceptions
import salt.utils.stringutils
import salt.utils.thin
from tests.support.mock import MagicMock, patch
def _mock_popen(return_value=None, side_effect=None, returncode=0):
proc = MagicMock()
proc.communicate = MagicMock(return_value=return_value, side_effect=side_effect)
proc.returncode = returncode
popen = MagicMock(return_value=proc)
return popen
@pytest.mark.parametrize("version", [[2, 7], [3, 0], [3, 7]])
def test_get_tops_python(version):
"""
Tests 'distro' is only included when targeting
python 3 in get_tops_python
"""
python3 = False
if tuple(version) >= (3, 0):
python3 = True
mods = ["jinja2"]
if python3:
mods.append("distro")
popen_ret = tuple(salt.utils.stringutils.to_bytes(x) for x in ("", ""))
mock_popen = _mock_popen(return_value=popen_ret)
patch_proc = patch("salt.utils.thin.subprocess.Popen", mock_popen)
with patch_proc:
salt.utils.thin.get_tops_python("python2", ext_py_ver=version)
cmds = [x[0][0] for x in mock_popen.call_args_list]
assert [x for x in cmds if "jinja2" in x]
if python3:
assert [x for x in cmds if "distro" in x]
else:
assert not [x for x in cmds if "distro" in x]
@pytest.mark.parametrize("version", [[2, 7], [3, 0], [3, 7]])
def test_get_ext_tops(version):
"""
Tests 'distro' is only included when targeting
python 3 in get_ext_tops
"""
python3 = False
if tuple(version) >= (3, 0):
python3 = True
cfg = {
"namespace": {
"path": "/foo",
"py-version": version,
"dependencies": {
"jinja2": "/jinja/foo.py",
"yaml": "/yaml/",
"tornado": "/tornado/tornado.py",
"msgpack": "msgpack.py",
},
}
}
with patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=True)):
if python3:
with pytest.raises(salt.exceptions.SaltSystemExit) as err:
salt.utils.thin.get_ext_tops(cfg)
else:
ret = salt.utils.thin.get_ext_tops(cfg)
if python3:
assert "distro" in err.value.code
else:
assert not [x for x in ret["namespace"]["dependencies"] if "distro" in x]
assert [x for x in ret["namespace"]["dependencies"] if "msgpack" in x]

View file

@ -137,7 +137,6 @@ class SSHThinTestCase(TestCase):
return tf
@patch("salt.exceptions.SaltSystemExit", Exception)
@patch("salt.utils.thin.log", MagicMock())
@patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
def test_get_ext_tops_cfg_missing_dependencies(self):
@ -148,7 +147,7 @@ class SSHThinTestCase(TestCase):
"""
cfg = {"namespace": {"py-version": [0, 0], "path": "/foo", "dependencies": []}}
with pytest.raises(Exception) as err:
with pytest.raises(salt.exceptions.SaltSystemExit) as err:
thin.get_ext_tops(cfg)
self.assertIn("Missing dependencies", str(err.value))
self.assertTrue(thin.log.error.called)
@ -223,7 +222,6 @@ class SSHThinTestCase(TestCase):
== thin.log.warning.mock_calls[0][1][0] % "test"
)
@patch("salt.exceptions.SaltSystemExit", Exception)
@patch("salt.utils.thin.log", MagicMock())
@patch("salt.utils.thin.os.path.isfile", MagicMock(return_value=False))
def test_get_ext_tops_dependency_config_check(self):
@ -664,72 +662,10 @@ class SSHThinTestCase(TestCase):
thin.sys.exc_clear = lambda: None
thin.gen_thin("")
self.assertIn(
"The minimum required python version to run salt-ssh is " '"2.6"',
"The minimum required python version to run salt-ssh is " '"3"',
str(err.value),
)
@skipIf(
salt.utils.platform.is_windows() and thin._six.PY2, "Dies on Python2 on Windows"
)
@patch("salt.exceptions.SaltSystemExit", Exception)
@patch("salt.utils.thin.os.makedirs", MagicMock())
@patch("salt.utils.files.fopen", MagicMock())
@patch("salt.utils.thin._get_salt_call", MagicMock())
@patch("salt.utils.thin._get_ext_namespaces", MagicMock())
@patch("salt.utils.thin.get_tops", MagicMock(return_value=["/foo3", "/bar3"]))
@patch("salt.utils.thin.get_ext_tops", MagicMock(return_value={}))
@patch("salt.utils.thin.os.path.isfile", MagicMock())
@patch("salt.utils.thin.os.path.isdir", MagicMock(return_value=True))
@patch("salt.utils.thin.os.remove", MagicMock())
@patch("salt.utils.thin.os.path.exists", MagicMock())
@patch("salt.utils.path.os_walk", MagicMock(return_value=[]))
@patch(
"salt.utils.thin.subprocess.Popen",
_popen(
None,
side_effect=[(bts("2.7"), bts("")), (bts('["/foo27", "/bar27"]'), bts(""))],
),
)
@patch("salt.utils.thin.tarfile", MagicMock())
@patch("salt.utils.thin.zipfile", MagicMock())
@patch("salt.utils.thin.os.getcwd", MagicMock())
@patch("salt.utils.thin.os.access", MagicMock(return_value=True))
@patch("salt.utils.thin.os.chdir", MagicMock())
@patch("salt.utils.thin.tempfile.mkdtemp", MagicMock())
@patch(
"salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
)
@patch("salt.utils.thin.shutil", MagicMock())
@patch("salt.utils.path.which", MagicMock(return_value=""))
@patch("salt.utils.thin._get_thintar_prefix", MagicMock())
def test_gen_thin_python_exist_or_not(self):
"""
Test thin.gen_thin function if the opposite python
binary does not exist
"""
with TstSuiteLoggingHandler() as handler:
thin.gen_thin("")
salt.utils.thin.subprocess.Popen.assert_not_called()
if salt.ext.six.PY2:
self.assertIn(
"DEBUG:python3 binary does not exist. Will not attempt to generate "
"tops for Python 3",
handler.messages,
)
if salt.ext.six.PY3:
self.assertIn(
"DEBUG:python2 binary does not exist. Will not "
"detect Python 2 version",
handler.messages,
)
self.assertIn(
"DEBUG:python2 binary does not exist. Will not attempt to generate "
"tops for Python 2",
handler.messages,
)
@skipIf(
salt.utils.platform.is_windows() and thin._six.PY2, "Dies on Python2 on Windows"
)
@ -765,8 +701,6 @@ class SSHThinTestCase(TestCase):
"salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
)
@patch("salt.utils.thin.shutil", MagicMock())
@patch("salt.utils.thin._six.PY3", True)
@patch("salt.utils.thin._six.PY2", False)
@patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
@patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
def test_gen_thin_compression_fallback_py3(self):
@ -818,15 +752,11 @@ class SSHThinTestCase(TestCase):
"salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
)
@patch("salt.utils.thin.shutil", MagicMock())
@patch("salt.utils.thin._six.PY3", True)
@patch("salt.utils.thin._six.PY2", False)
@patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
@patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
def test_gen_thin_control_files_written_py3(self):
"""
Test thin.gen_thin function if control files are written (version, salt-call etc).
NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
:return:
"""
thin.gen_thin("")
@ -836,7 +766,7 @@ class SSHThinTestCase(TestCase):
for idx, fname in enumerate(
["version", ".thin-gen-py-version", "salt-call", "supported-versions"]
):
name = thin.tarfile.open().method_calls[idx + 4][1][0]
name = thin.tarfile.open().method_calls[idx + 2][1][0]
self.assertEqual(name, fname)
thin.tarfile.open().close.assert_called()
@ -862,13 +792,6 @@ class SSHThinTestCase(TestCase):
)
),
)
@patch(
"salt.utils.thin.subprocess.Popen",
_popen(
None,
side_effect=[(bts("2.7"), bts("")), (bts('["/foo27", "/bar27"]'), bts(""))],
),
)
@patch("salt.utils.thin.tarfile", _tarfile(None))
@patch("salt.utils.thin.zipfile", MagicMock())
@patch(
@ -882,8 +805,6 @@ class SSHThinTestCase(TestCase):
"salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
)
@patch("salt.utils.thin.shutil", MagicMock())
@patch("salt.utils.thin._six.PY3", True)
@patch("salt.utils.thin._six.PY2", False)
@patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
@patch("salt.utils.hashutils.DigestCollector", MagicMock())
@patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
@ -896,7 +817,7 @@ class SSHThinTestCase(TestCase):
"""
thin.gen_thin("")
files = []
for py in ("py2", "py2", "py3", "pyall"):
for py in ("py3", "pyall"):
for i in range(1, 4):
files.append(os.path.join(py, "root", "r{}".format(i)))
for i in range(4, 7):
@ -919,7 +840,7 @@ class SSHThinTestCase(TestCase):
MagicMock(
return_value={
"namespace": {
"py-version": [2, 7],
"py-version": [3, 0],
"path": "/opt/2015.8/salt",
"dependencies": ["/opt/certifi", "/opt/whatever"],
}
@ -940,13 +861,6 @@ class SSHThinTestCase(TestCase):
)
),
)
@patch(
"salt.utils.thin.subprocess.Popen",
_popen(
None,
side_effect=[(bts("2.7"), bts("")), (bts('["/foo27", "/bar27"]'), bts(""))],
),
)
@patch("salt.utils.thin.tarfile", _tarfile(None))
@patch("salt.utils.thin.zipfile", MagicMock())
@patch(
@ -960,21 +874,17 @@ class SSHThinTestCase(TestCase):
"salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
)
@patch("salt.utils.thin.shutil", MagicMock())
@patch("salt.utils.thin._six.PY3", True)
@patch("salt.utils.thin._six.PY2", False)
@patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
@patch("salt.utils.hashutils.DigestCollector", MagicMock())
@patch("salt.utils.path.which", MagicMock(return_value="/usr/bin/python"))
def test_gen_thin_ext_alternative_content_files_written_py3(self):
"""
Test thin.gen_thin function if external alternative content files are written.
NOTE: Py2 version of this test is not required, as code shares the same spot across the versions.
:return:
"""
ext_conf = {
"namespace": {
"py-version": [2, 7],
"py-version": [3, 0],
"path": "/opt/2015.8/salt",
"dependencies": {
"certifi": "/opt/certifi",
@ -985,13 +895,13 @@ class SSHThinTestCase(TestCase):
thin.gen_thin("", extended_cfg=ext_conf)
files = []
for py in ("pyall", "pyall", "py2"):
for py in ("pyall", "pyall", "py3"):
for i in range(1, 4):
files.append(os.path.join("namespace", py, "root", "r{}".format(i)))
for i in range(4, 7):
files.append(os.path.join("namespace", py, "root2", "r{}".format(i)))
for idx, cl in enumerate(thin.tarfile.open().method_calls[12:-6]):
for idx, cl in enumerate(thin.tarfile.open().method_calls[:-6]):
arcname = cl[2].get("arcname")
self.assertIn(arcname, files)
files.pop(files.index(arcname))
@ -1077,8 +987,6 @@ class SSHThinTestCase(TestCase):
"salt.utils.thin.tempfile.mkstemp", MagicMock(return_value=(3, ".temporary"))
)
@patch("salt.utils.thin.shutil", MagicMock())
@patch("salt.utils.thin._six.PY3", True)
@patch("salt.utils.thin._six.PY2", False)
@patch("salt.utils.thin.sys.version_info", _version_info(None, 3, 6))
def test_gen_thin_control_files_written_access_denied_cwd(self):
"""
@ -1111,7 +1019,6 @@ class SSHThinTestCase(TestCase):
self._popen(
None,
side_effect=[
(bts("distro.py"), bts("")),
(bts("jinja2/__init__.py"), bts("")),
(bts("yaml/__init__.py"), bts("")),
(bts("tornado/__init__.py"), bts("")),
@ -1123,6 +1030,7 @@ class SSHThinTestCase(TestCase):
(bts(""), bts("")),
(bts(""), bts("")),
(bts(""), bts("")),
(bts("distro.py"), bts("")),
],
),
)
@ -1132,7 +1040,7 @@ class SSHThinTestCase(TestCase):
with patch_proc, patch_os, patch_which:
with TstSuiteLoggingHandler() as log_handler:
exp_ret = copy.deepcopy(self.exp_ret)
ret = thin.get_tops_python("python2.7")
ret = thin.get_tops_python("python3.7", ext_py_ver=[3, 7])
if salt.utils.platform.is_windows():
for key, value in ret.items():
ret[key] = str(pathlib.Path(value).resolve(strict=False))
@ -1140,7 +1048,7 @@ class SSHThinTestCase(TestCase):
exp_ret[key] = str(pathlib.Path(value).resolve(strict=False))
assert ret == exp_ret
assert (
"ERROR:Could not auto detect file location for module concurrent for python version python2.7"
"ERROR:Could not auto detect file location for module concurrent for python version python3.7"
in log_handler.messages
)
@ -1153,7 +1061,6 @@ class SSHThinTestCase(TestCase):
self._popen(
None,
side_effect=[
(bts("distro.py"), bts("")),
(bts("tornado/__init__.py"), bts("")),
(bts("msgpack/__init__.py"), bts("")),
(bts("certifi/__init__.py"), bts("")),
@ -1163,6 +1070,7 @@ class SSHThinTestCase(TestCase):
(bts(""), bts("")),
(bts(""), bts("")),
(bts(""), bts("")),
(bts("distro.py"), bts("")),
],
),
)
@ -1173,7 +1081,9 @@ class SSHThinTestCase(TestCase):
patch_os = patch("os.path.exists", return_value=True)
patch_which = patch("salt.utils.path.which", return_value=True)
with patch_proc, patch_os, patch_which:
ret = thin.get_tops_python("python2.7", exclude=self.exc_libs)
ret = thin.get_tops_python(
"python3.7", exclude=self.exc_libs, ext_py_ver=[3, 7]
)
if salt.utils.platform.is_windows():
for key, value in ret.items():
ret[key] = str(pathlib.Path(value).resolve(strict=False))