Repository: saltstack/salt
Pull request: Do not allow python2 to be added to salt-ssh tar by default
### What does this PR do?
After dropping python 2 support from Salt we should not be adding python 2 modules to the salt-ssh binary by default. This PR addresses this by removing the code that automatically looked for python 2 modules and added them to the tar. This also ensures that `ssh_ext_alternatives` can still work with older python 2 supported salt versions. For example, when using `ssh_ext_alternatives` you can use the 2019.2.3 version of salt that is copied over in the tar. This version still supports Python 2 so we should still allow a user to use this version when using the `ssh_ext_alternatives` feature.

### What issues does this PR fix or reference?
Fixes: https://github.com/saltstack/salt/issues/57647

### Previous Behavior
Misleading error when python 3 is not installed on the target host:

```
        ERROR: Python version error. Recommendation(s) follow:
        - Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
          master to interact with Python 2.6/2.7 targets
        - Install Python 3 on the target machine(s)
```

and python 2 modules added to the tar even though they will not be used.

### New Behavior
New Error to inform user they need to install Python 3 on target host.

```
        ERROR: Python version error. Recommendation(s) follow:
        - Install Python 3 on the target machine(s)
        - You can use ssh_pre_flight or raw shell (-r) to install Python 3
```

### Merge requirements satisfied?
- [x] Docs
- [x] Changelog - https://docs.saltstack.com/en/master/topics/development/changelog.html
- [x] Tests written/updated

### Commits signed with GPG?
Yes

Problem statement:
[BUG] 06.11.2020 -- salt-ssh -- salt doesn't properly recognize appropriate python versions
**Description**

I have a Centos 7 master and an Centos 7 client. salt-ssh exits with the following error:

```
ERROR: Python version error. Recommendation(s) follow:
- Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
  master to interact with Python 2.6/2.7 targets
- Install Python 3 on the target machine(s)
```

Centos 7 Client has python versions: 2.6 & 2.7.5. 

Centos 7 Host, where I run salt-ssh from, has python 2.6, 2.7.5, and python 3.6.8. 

I believe this may be a bug as I recently ran this from a manjaro box, identical setup, and there were 0 issues. I had to rekick the manjaro box to centos over the weekend, arrived at this issue, and have been unable to find a solution.

**Setup**

Here is the roster file:

```
<REDACTED>:
  host: <REDACTED>
  user: <REDACTED>
  passwd: <REDACTED>
  sudo_user: <REDACTED>
  thin_dir: /home/<REDACTED>/.salt_auto
```

**Steps to Reproduce the behavior**
Here is what I ran, followed by the trace log output. It is confusing to me, as it looks like it searches and eventually finds python2.7, and successfully runs, then returns that it couldn't find it. Uncertain if it is relevant, but running with `-r` for raw ssh commands will succeed without issues. The error mentioned occurs regardless of whether I try to apply states, or run other execution modules.

```[~]$ salt-ssh -c ~/.salt '*' 'uptime'
[...]
ERROR: Python version error. Recommendation(s) follow:
- Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
  master to interact with Python 2.6/2.7 targets
- Install Python 3 on the target machine(s)

--------------------------------------------------------------------------------
echo "ERROR: Unable to locate appropriate python command" >&2
exit $EX_PYTHON_INVALID
EOF
[TRACE   ] <REDACTED>'s password:
[TRACE   ] + SUDO=
+ '[' -n '' ']'
+ SUDO_USER=<REDACTED>
+ '[' '' ']'
+ '[' '' ']'
+ EX_PYTHON_INVALID=10
+ PYTHON_CMDS='python3 python27 python2.7 python26 python2.6 python2 python'
[TRACE   ] + for py_cmd in '$PYTHON_CMDS'
+ command -v python3
+ continue
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python27
+ continue
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python2.7
+ python2.7 -c 'import sys; sys.exit(not (sys.version_info >= (2, 6)));'
[TRACE   ] ++ python2.7 -c 'from __future__ import print_function;import sys; print(sys.executable);'
[TRACE   ] + py_cmd_path=/usr/bin/python2.7
++ command -v python2.7
[TRACE   ] + cmdpath=/usr/bin/python2.7
+ grep 'shell script'
+ file /usr/bin/python2.7
[TRACE   ] + exec /usr/bin/python2.7 -c 'import base64;
[...]
[TRACE   ] "").decode("utf-8"))'
[DEBUG   ] End of file(EOL). Brain-dead platform.
[TRACE   ] STDOUT <REDACTED>
<REDACTED>'s password: 
/usr/bin/scp

[TRACE   ] STDERR <REDACTED>
+ SUDO=
+ '[' -n '' ']'
+ SUDO_USER=<REDACTED>
+ '[' '' ']'
+ '[' '' ']'
+ EX_PYTHON_INVALID=10
+ PYTHON_CMDS='python3 python27 python2.7 python26 python2.6 python2 python'
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python3
+ continue
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python27
+ continue
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python2.7
+ python2.7 -c 'import sys; sys.exit(not (sys.version_info >= (2, 6)));'
++ python2.7 -c 'from __future__ import print_function;import sys; print(sys.executable);'
+ py_cmd_path=/usr/bin/python2.7
++ command -v python2.7
+ cmdpath=/usr/bin/python2.7
+ grep 'shell script'
+ file /usr/bin/python2.7
+ exec /usr/bin/python2.7 -c 'import base64;
[...]
[DEBUG   ] RETCODE <REDACTED>: 10
[DEBUG   ] LazyLoaded nested.output
[TRACE   ] data = {'<REDACTED>': {'stdout': 'ERROR: Python version error. Recommendation(s) follow:\n- Install Python 2.6/2.7 Sa
lt dependencies on the...
```
If I remove the thin directory on the client, and run without using raw ssh commands, it returns there was an issue dpeloying the thin, however on the client, it looks like it deployed successfully.

Master:
```
[~]$ time salt-ssh -c ~/.salt '*' 'uptime'
[ERROR   ] ERROR: Failure deploying thin, retrying:
STDOUT:
<REACTED>'s password: 

STDERR:

RETCODE: 10
<REACTED>:
    ----------
    retcode:
        10
    stderr:
    stdout:
        ERROR: Python version error. Recommendation(s) follow:
        - Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
          master to interact with Python 2.6/2.7 targets
        - Install Python 3 on the target machine(s)
```

Client during and after install of thin:

```
[~]# ls /home/<REACTED>/.salt_auto/
salt-thin.tgz
[~]# ls /home/<REACTED>/.salt_auto/
code-checksum  minion  py3  pyall  salt-call  supported-versions  version
```

**Expected behavior**
salt-ssh runs execution modules, or anything really that isn't raw ssh, without an error. 

**Versions Report**

```
[~]$ salt --versions-report

Salt Version:
           Salt: 3000.3
 
Dependency Versions:
           cffi: 1.14.0
       cherrypy: Not Installed
       dateutil: Not Installed
      docker-py: Not Installed
          gitdb: Not Installed
      gitpython: Not Installed
         Jinja2: 2.11.2
        libgit2: Not Installed
       M2Crypto: 0.35.2
           Mako: Not Installed
   msgpack-pure: Not Installed
 msgpack-python: 0.6.2
   mysql-python: Not Installed
      pycparser: 2.20
       pycrypto: 2.6.1
   pycryptodome: 3.9.7
         pygit2: Not Installed
         Python: 3.6.8 (default, Apr  2 2020, 13:34:55)
   python-gnupg: Not Installed
         PyYAML: 5.3.1
          PyZMQ: 15.3.0
          smmap: Not Installed
        timelib: Not Installed
        Tornado: 4.5.3
            ZMQ: 4.1.4
 
System Versions:
           dist: centos 7.8.2003 Core
         locale: UTF-8
        machine: x86_64
        release: 3.10.0-1127.10.1.el7.x86_64
         system: Linux
        version: CentOS Linux 7.8.2003 Core
```
From the thin client:

```
[ ~]# cat /home/<REACTED>/.salt_auto/version 
3000.3
```

**Additional context**

I am brand new to salt! Started learning a few weeks ago, apologies if I missed something simple, or haven't got all the lingo down yet.

Hints from issue discussion:
Reading back, I just realized it references dependencies specifically for python2.6/2.7... so I installed those on the master just to be certain with:

```
yum install python2-libs python2-pip python2-setuptools python2-pyyaml python2-crypto python2-idna python2-jinja2 m2crypto python2-msgpack python2-psutil python2-pysocks python2-requests python2-six python2-urllib3 python2-zmq
```

The other 3 were already satisfied:

```
$ pip install chardet markupsafe pycurl
Requirement already satisfied (use --upgrade to upgrade): chardet in /usr/lib/python2.7/site-packages
Requirement already satisfied (use --upgrade to upgrade): markupsafe in /usr/lib64/python2.7/site-packages
Requirement already satisfied (use --upgrade to upgrade): pycurl in /usr/lib64/python2.7/site-packages
```

Here is the version report again

```
[~]$ salt --versions-report 
Salt Version:
           Salt: 3000.3
 
Dependency Versions:
           cffi: 1.14.0
       cherrypy: Not Installed
       dateutil: Not Installed
      docker-py: Not Installed
          gitdb: Not Installed
      gitpython: Not Installed
         Jinja2: 2.11.2
        libgit2: Not Installed
       M2Crypto: 0.35.2
           Mako: Not Installed
   msgpack-pure: Not Installed
 msgpack-python: 0.6.2
   mysql-python: Not Installed
      pycparser: 2.20
       pycrypto: 2.6.1
   pycryptodome: 3.9.7
         pygit2: Not Installed
         Python: 3.6.8 (default, Apr  2 2020, 13:34:55)
   python-gnupg: Not Installed
         PyYAML: 5.3.1
          PyZMQ: 15.3.0
          smmap: Not Installed
        timelib: Not Installed
        Tornado: 4.5.3
            ZMQ: 4.1.4
 
System Versions:
           dist: centos 7.8.2003 Core
         locale: UTF-8
        machine: x86_64
        release: 3.10.0-1127.10.1.el7.x86_64
         system: Linux
        version: CentOS Linux 7.8.2003 
```

Unfortunately the same error is returned as before:

```
<REDACTED>:
    ----------
    retcode:
        10
    stderr:
    stdout:
        ERROR: Python version error. Recommendation(s) follow:
        - Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
          master to interact with Python 2.6/2.7 targets
        - Install Python 3 on the target machine(s)
```
Thanks for the report @paralllax someone on @saltstack/team-ssh ; any insight on this one? 😄 
Thank you @xeacott 

As a note, I updated to the latest version today, and the same issue persists

```
$ time salt-ssh -c ~/.salt '*' 'uptime'
[ERROR   ] ERROR: Failure deploying thin, retrying:
STDOUT:
<REDACTED>@<REDACTED>'s password: 

STDERR:

RETCODE: 10
<REDACTED>:
    ----------
    retcode:
        10
    stderr:
    stdout:
        ERROR: Python version error. Recommendation(s) follow:
        - Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
          master to interact with Python 2.6/2.7 targets
        - Install Python 3 on the target machine(s)
```

Note that similar to before, it still deploys the thin client despite the error:

```
# ls /home/<REDACTED>/.salt_auto/
code-checksum  minion  py3  pyall  salt-call  supported-versions  version
```

I hadn't noticed, it seems to deploy the py3 thin client instead of py2 . I don't know python super well, but it seems like the error happens somewhere around here in salt/utils/thin.py.

On the thin client:

```
[ .salt_auto]# cat supported-versions 
py3:3:0
[ .salt_auto]# ls py3
backports_abc.py  markupsafe             tornado
distro.py         ssl_match_hostname.py  yaml
```

From the most recent clone of the salt repo under salt/utils/thin.py:

```
    for py_ver, tops in _six.iteritems(copy.deepcopy(tops)):
        py_ver = int(py_ver)
        if py_ver == 2:
            pymap.append("py2:2:7")
        elif py_ver == 3:
            pymap.append("py3:3:0")
```

```
$ salt --versions-report
Salt Version:
           Salt: 3001
 
Dependency Versions:
           cffi: 1.14.0
       cherrypy: Not Installed
       dateutil: Not Installed
      docker-py: Not Installed
          gitdb: Not Installed
      gitpython: Not Installed
         Jinja2: 2.11.2
        libgit2: Not Installed
       M2Crypto: 0.35.2
           Mako: Not Installed
   msgpack-pure: Not Installed
 msgpack-python: 0.6.2
   mysql-python: Not Installed
      pycparser: 2.20
       pycrypto: 2.6.1
   pycryptodome: 3.9.7
         pygit2: Not Installed
         Python: 3.6.8 (default, Apr  2 2020, 13:34:55)
   python-gnupg: Not Installed
         PyYAML: 5.3.1
          PyZMQ: 17.0.0
          smmap: Not Installed
        timelib: Not Installed
        Tornado: 4.5.3
            ZMQ: 4.1.4
 
System Versions:
           dist: centos 7 Core
         locale: UTF-8
        machine: x86_64
        release: 3.10.0-1127.10.1.el7.x86_64
         system: Linux
        version: CentOS Linux 7 Core
```
I don't get it. I fixed that myself two years ago. Broken again?? 🤦
Sadly it seems so :/ Though it was quite a while back, would you happen to know the pull request? I don't know python all too well, but I could try and check it out.
I just tested on 3000.3 and this is working. Can you make sure to remove your thin from /var/cache/salt/master/thin/ and run the command again. 

And if you could look at the debug output and look for where it says `Adding module to the tops: "salt"` for example and `Packing "salt" to "pyall" destination`. Want to make sure you arent running into any errors, for example if it was not able to add a certain module or it cant find python

now what i did discover is this is actually broken on the head of master. We dropped python2 support and do not allow any python2 code to run, i'll look into how we can work around this.
okay I don't think we can still allow python2 modules to be put into the tar, because we do not allow python2 code to run throughout salt, so even if python2 is included in the tar ball a user could not run salt commands on the target host anyways.I think the best thing to do here is remove the python2 code here and update the documentation/error reporting. Then a user will have two options- to use the `ssh_pre_flight` option (https://docs.saltstack.com/en/latest/topics/ssh/roster.html#ssh-pre-flight) to install python3 on the target host before salt-call runs or they will be able to use this feature https://github.com/saltstack/salt/issues/56944 which will be added in Magnesium.

To make things clear for your use case @paralllax tho it should still be working on versions <3001


Thank you so much! That makes sense, much appreciated. 

I moved back to version 3000.3 and I purged the thin client from the cache and on the minion as well. I tested once more. Looking at the section requested in the debug logs, it looks like there is an error there. 

```
[DEBUG   ] Adding module to the tops: "salt.ext.backports_abc"                                                                              [544/1883]
[DEBUG   ] Failed collecting tops for Python binary python2.
[DEBUG   ] b'Traceback (most recent call last):\n  File "<string>", line 1, in <module>\nImportError: No module named salt.utils.thin\n'
[DEBUG   ] Packing default libraries based on current Salt version
```

I am uncertain why is can't find that module, but am looking to get more information. If you have any ideas, let me know! Thank you again.
`salt.utils.thin` is a part of salt. If you try to import that with your python 2 binary what happens? `python2 -c "import salt.utils.thin"`
It seems it works with python3 but not with python2. python2 is the default version which I suppose is why it errors out. 

```
$ python2 -c 'import salt.utils.thin'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ImportError: No module named salt.utils.thin
```

```
$ python3 -c 'import salt.utils.thin'
$
```

I have salt-3000.3 installed

```
$ rpm -qa salt*
salt-syndic-3000.3-1.el7.noarch
salt-master-3000.3-1.el7.noarch
salt-cloud-3000.3-1.el7.noarch
salt-3000.3-1.el7.noarch
salt-ssh-3000.3-1.el7.noarch
salt-api-3000.3-1.el7.noarch
salt-minion-3000.3-1.el7.noarch
```

it looks like salt isn't installed for python2

```
$ ls /usr/lib/python3.6/site-packages/salt/
acl      client      daemons        ext            key.py     matchers   output      __pycache__  runner.py    spm          templates  utils
auth     cloud       defaults       fileclient.py  loader.py  metaproxy  payload.py  queues       runners      state.py     thorium    _version.py
beacons  _compat.py  engines        fileserver     log        minion.py  pillar      renderers    scripts.py   states       tokens     version.py
cache    config      exceptions.py  grains         _logging   modules    platform    returners    sdb          syspaths.py  tops       wheel
cli      crypt.py    executors      __init__.py    master.py  netapi     proxy       roster       serializers  template.py  transport
$ ls /usr/lib/python2.7/site-packages/*salt*
ls: cannot access /usr/lib/python2.7/site-packages/*salt*: No such file or directory
```

I installed it, and it looks like it can import the module now

```
$ pip2 install salt==3000.3 salt-ssh=3000.3
[...]
$ python2 -c 'import salt.utils.thin'
$
```

However it seems to still fail with the same error from previously when attempting to run salt-ssh

```
    stdout:
        ERROR: Python version error. Recommendation(s) follow:
        - Install Python 2.6/2.7 Salt dependencies on the Salt SSH 
          master to interact with Python 2.6/2.7 targets
        - Install Python 3 on the target machine(s)
```

I checked the trace level log, and it returns the same errors when checking each python version. It seems like it works with python2.7, but still fails regardless. 

```
echo "ERROR: Unable to locate appropriate python command" >&2
[...]
echo "ERROR: Unable to locate appropriate python command" >&2
[...]
+ PYTHON_CMDS='python3 python27 python2.7 python26 python2.6 python2 python'
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python3
+ continue
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python27
+ continue
+ for py_cmd in '$PYTHON_CMDS'
+ command -v python2.7
+ python2.7 -c 'import sys; sys.exit(not (sys.version_info >= (2, 6)));'
++ python2.7 -c 'from __future__ import print_function;import sys; print(sys.executable);'
+ py_cmd_path=/usr/bin/python2.7
++ command -v python2.7
+ cmdpath=/usr/bin/python2.7
+ grep 'shell script'
+ file /usr/bin/python2.7
+ exec /usr/bin/python2.7 -c 'import base64;
[...]
        ERROR: Python version error. Recommendation(s) follow:...
```

Commit message: fix pytest ssh tests

Patch to review:
diff --git a/changelog/57647.fixed b/changelog/57647.fixed
new file mode 100644
index 000000000000..f5d84cfa575e
--- /dev/null
+++ b/changelog/57647.fixed
@@ -0,0 +1 @@
+Do not allow python2 to be added to salt-ssh tar since Salt deprecated Python 2.
diff --git a/doc/topics/ssh/index.rst b/doc/topics/ssh/index.rst
index 270aae8668b6..c012d373e0c7 100644
--- a/doc/topics/ssh/index.rst
+++ b/doc/topics/ssh/index.rst
@@ -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>`
diff --git a/doc/topics/ssh/ssh_ext_alternatives.rst b/doc/topics/ssh/ssh_ext_alternatives.rst
index bef63f432f0b..11c0342da68c 100644
--- a/doc/topics/ssh/ssh_ext_alternatives.rst
+++ b/doc/topics/ssh/ssh_ext_alternatives.rst
@@ -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
 -----------
 
diff --git a/salt/client/ssh/__init__.py b/salt/client/ssh/__init__.py
index 278fd5887ab9..71a180d3e739 100644
--- a/salt/client/ssh/__init__.py
+++ b/salt/client/ssh/__init__.py
@@ -1,7 +1,6 @@
 """
 Create ssh executor system
 """
-# Import python libs
 
 import base64
 import binascii
@@ -28,8 +27,6 @@
 import salt.loader
 import salt.log
 import salt.minion
-
-# Import salt libs
 import salt.output
 import salt.roster
 import salt.serializers.yaml
@@ -46,8 +43,6 @@
 import salt.utils.thin
 import salt.utils.url
 import salt.utils.verify
-
-# Import 3rd-party libs
 from salt.ext import six
 from salt.ext.six.moves import input  # pylint: disable=import-error,redefined-builtin
 from salt.template import compile_template
@@ -1527,30 +1522,6 @@ def categorize_shim_errors(self, stdout_bytes, stderr_bytes, retcode):
             "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 +1531,9 @@ def _version_mismatch_error():
             (
                 (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,),
diff --git a/salt/utils/thin.py b/salt/utils/thin.py
index b4ba262ca9e0..3ba71616fe36 100644
--- a/salt/utils/thin.py
+++ b/salt/utils/thin.py
@@ -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 tempfile
 import zipfile
 
-import distro
 import jinja2
 import msgpack
 import salt
@@ -72,11 +69,13 @@
     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)
diff --git a/tests/pytests/unit/client/test_ssh.py b/tests/pytests/unit/client/test_ssh.py
new file mode 100644
index 000000000000..b73618692f92
--- /dev/null
+++ b/tests/pytests/unit/client/test_ssh.py
@@ -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").strpath,
+    }
+    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]
diff --git a/tests/pytests/unit/utils/test_thin.py b/tests/pytests/unit/utils/test_thin.py
new file mode 100644
index 000000000000..3d19effa3010
--- /dev/null
+++ b/tests/pytests/unit/utils/test_thin.py
@@ -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]
diff --git a/tests/unit/utils/test_thin.py b/tests/unit/utils/test_thin.py
index 56702e6ffff6..ecf398b06849 100644
--- a/tests/unit/utils/test_thin.py
+++ b/tests/unit/utils/test_thin.py
@@ -137,7 +137,6 @@ def _tarfile(self, getinfo=False):
 
         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 @@ def test_get_ext_tops_cfg_missing_dependencies(self):
         """
         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 @@ def test_get_ext_tops_cfg_interpreter(self):
             == 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 @@ def test_gen_thin_fails_ancient_python_version(self):
             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 @@ def test_gen_thin_python_exist_or_not(self):
         "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 @@ def test_gen_thin_compression_fallback_py3(self):
         "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 @@ def test_gen_thin_control_files_written_py3(self):
         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 @@ def test_gen_thin_control_files_written_py3(self):
             )
         ),
     )
-    @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 @@ def test_gen_thin_control_files_written_py3(self):
         "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 @@ def test_gen_thin_main_content_files_written_py3(self):
         """
         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 @@ def test_gen_thin_main_content_files_written_py3(self):
         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 @@ def test_gen_thin_main_content_files_written_py3(self):
             )
         ),
     )
-    @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 @@ def test_gen_thin_main_content_files_written_py3(self):
         "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 @@ def test_gen_thin_ext_alternative_content_files_written_py3(self):
 
         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 @@ def test_get_supported_py_config_ext_tops(self):
         "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 @@ def test_get_tops_python(self):
             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 @@ def test_get_tops_python(self):
                     (bts(""), bts("")),
                     (bts(""), bts("")),
                     (bts(""), bts("")),
+                    (bts("distro.py"), bts("")),
                 ],
             ),
         )
@@ -1132,7 +1040,7 @@ def test_get_tops_python(self):
         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 @@ def test_get_tops_python(self):
                         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 @@ def test_get_tops_python_exclude(self):
             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 @@ def test_get_tops_python_exclude(self):
                     (bts(""), bts("")),
                     (bts(""), bts("")),
                     (bts(""), bts("")),
+                    (bts("distro.py"), bts("")),
                 ],
             ),
         )
@@ -1173,7 +1081,9 @@ def test_get_tops_python_exclude(self):
         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))


Code review comments:
diff_hunk: @@ -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.
path: salt/utils/thin.py
text: @user1:
Are we sure on 3.0, salt is only meant to work with 3.5, right?
Or for this piece of code it doesn't really matter?

@author:
I will re-word this. You can even actually use 2.7 if using an older version of salt.