Repository: saltstack/salt
Pull request: netmiko poxy import fixes
### What does this PR do?
Due to changes in the Netmiko library for the exception paths, need to check the version of Netmiko python library and then import the exceptions from different locations depending on the result.

### What issues does this PR fix or reference?
Fixes: #62405 

### Merge requirements satisfied?
**[NOTICE] Bug fixes or features added to Salt require tests.**
<!-- Please review the [test documentation](https://docs.saltproject.io/en/master/topics/tutorials/writing_tests.html) for details on how to implement tests into Salt's test suite. -->
- [ ] Docs
- [ ] Changelog - https://docs.saltproject.io/en/master/topics/development/changelog.html
- [ ] Tests written/updated

### Commits signed with GPG?
Yes

Please review [Salt's Contributing Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html) for best practices.

See GitHub's [page on GPG signing](https://help.github.com/articles/signing-commits-using-gpg/) for more information about signing commits with GPG.


Problem statement:
[BUG] netmiko proxy exception depending on version installed
**Description**
If a user attempts to use the Netmiko proxy minion with the version of Netmiko Python library higher than 3.4.0 then an exception is caused and the proxy minion is unable to load.

```
[DEBUG   ] Failed to import proxy netmiko_px:
Traceback (most recent call last):
  File "salt/loader/lazy.py", line 768, in _load_module
    mod = self.run(spec.loader.load_module)
  File "salt/loader/lazy.py", line 1228, in run
    return self._last_context.run(self._run_as, _func_or_method, *args, **kwargs)
  File "salt/loader/lazy.py", line 1243, in _run_as
    return _func_or_method(*args, **kwargs)
  File "<frozen importlib._bootstrap_external>", line 529, in _check_name_wrapper
  File "<frozen importlib._bootstrap_external>", line 1029, in load_module
  File "<frozen importlib._bootstrap_external>", line 854, in load_module
  File "<frozen importlib._bootstrap>", line 274, in _load_module_shim
  File "<frozen importlib._bootstrap>", line 711, in _load
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 850, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/opt/saltstack/salt/run/salt/proxy/netmiko_px.py", line 195, in <module>
    from netmiko.ssh_exception import NetMikoTimeoutException
ModuleNotFoundError: No module named 'netmiko.ssh_exception'
```

**Setup**
1/ Install Netmiko version higher than 3.4.0
2. Setup the pillar files for a simple Netmiko proxy minion:
```
proxy:
  proxytype: netmiko
  device_type: linux
  host: 127.0.0.1
  username: saltdev
  password: 'saltdev'
  always_alive: False
```
3. Start the Salt proxy minion using the above mentioned minion id.

Hints from issue discussion:

Commit message: Adding changelog.

Patch to review:
diff --git a/changelog/62405.fixed b/changelog/62405.fixed
new file mode 100644
index 000000000000..a8077992fe1e
--- /dev/null
+++ b/changelog/62405.fixed
@@ -0,0 +1 @@
+Due to changes in the Netmiko library for the exception paths, need to check the version of Netmiko python library and then import the exceptions from different locations depending on the result.
diff --git a/salt/proxy/netmiko_px.py b/salt/proxy/netmiko_px.py
index b9afe596e761..673c707c8016 100644
--- a/salt/proxy/netmiko_px.py
+++ b/salt/proxy/netmiko_px.py
@@ -187,13 +187,19 @@
 import time
 
 import salt.utils.args
+from salt.utils.versions import LooseVersion as _LooseVersion
 
 try:
     from netmiko import ConnectHandler
-    from netmiko.ssh_exception import (
-        NetMikoAuthenticationException,
-        NetMikoTimeoutException,
-    )
+    from netmiko import __version__ as found_netmiko_version
+
+    if _LooseVersion(found_netmiko_version) <= _LooseVersion("3.4.0"):
+        from netmiko.ssh_exception import (
+            NetMikoAuthenticationException,
+            NetMikoTimeoutException,
+        )
+    else:
+        from netmiko import NetMikoAuthenticationException, NetMikoTimeoutException
 
     HAS_NETMIKO = True
 except ImportError:


Code review comments:
diff_hunk: @@ -187,13 +187,19 @@
 import time
 
 import salt.utils.args
+from salt.utils.versions import LooseVersion as _LooseVersion
 
 try:
     from netmiko import ConnectHandler
-    from netmiko.ssh_exception import (
-        NetMikoAuthenticationException,
-        NetMikoTimeoutException,
-    )
+    from netmiko import __version__ as found_netmiko_version
+
+    if _LooseVersion(found_netmiko_version) <= _LooseVersion("3.4.0"):
path: salt/proxy/netmiko_px.py
text: @user1:
I'd do a simple `try/catch ImportError`, it's faster and doesn't require version comparisons.
Try to import from the newer module, fallback to the old.