New Upstream Release - sshtunnel
Ready changes
Summary
Merged new upstream version: 0.4.0 (was: 0.1.4).
Resulting package
Built on 2023-01-06T15:41 (took 6m18s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python3-sshtunnel
Lintian Result
Diff
diff --git a/LICENSE b/LICENSE
index 3953212..58a509f 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2014-2016 Pahaz Blinov
+Copyright (c) 2014-2019 Pahaz White
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
index 3eba37e..6b7ecac 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,5 @@
# Include the data files recursive-include data *
-# If using Python 2.6 or less, then have to include package data, even though
-# it's already declared in setup.py
-# include sample/*.dat
-
include LICENSE
include *.rst
include docs/conf.py
@@ -11,3 +7,10 @@ include docs/Makefile
include docs/*.rst
include docs/*.txt
include tests/*
+include e2e_tests/*
+include e2e_tests/ssh-server-config/*
+exclude .github/*
+exclude .circleci/*
+exclude *.pyc
+exclude __pycache__
+exclude Pipfile*
diff --git a/PKG-INFO b/PKG-INFO
index 6b2e89d..7f02d5c 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,22 +1,21 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
Name: sshtunnel
-Version: 0.1.4
+Version: 0.4.0
Summary: Pure python SSH tunnels
Home-page: https://github.com/pahaz/sshtunnel
-Author: Pahaz Blinov
-Author-email: pahaz.blinov@gmail.com
+Author: Pahaz White
+Author-email: pahaz.white@gmail.com
License: MIT
-Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.1.4.zip
-Description-Content-Type: UNKNOWN
+Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.zip
Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
|pyversions| |license|
- **Author**: `Pahaz Blinov`_
+ **Author**: `Pahaz`_
**Repo**: https://github.com/pahaz/sshtunnel/
- Inspired by https://github.com/jmagnusson/bgtunnel, but it doesn't work on
+ Inspired by https://github.com/jmagnusson/bgtunnel, which doesn't work on
Windows.
See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
@@ -112,12 +111,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
port.
- .. code-block:: py
+ .. code-block:: python
from sshtunnel import SSHTunnelForwarder
server = SSHTunnelForwarder(
- 'pahaz.urfuclub.ru',
+ 'alfa.8iq.dev',
ssh_username="pahaz",
ssh_password="secret",
remote_bind_address=('127.0.0.1', 8080)
@@ -137,12 +136,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
assuming password protected pkey authentication, remote server's SSH service is
listening on port 443 and that port is open in the firewall (**Fig2**):
- .. code-block:: py
+ .. code-block:: python
import paramiko
- from sshtunnel import SSHTunnelForwarder
+ import sshtunnel
- with SSHTunnelForwarder(
+ with sshtunnel.open_tunnel(
(REMOTE_SERVER_IP, 443),
ssh_username="",
ssh_pkey="/var/ssh/rsa_key",
@@ -164,12 +163,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
Example of a port forwarding for the Vagrant MySQL local port:
- .. code-block:: py
+ .. code-block:: python
- from sshtunnel import SSHTunnelForwarder
+ from sshtunnel import open_tunnel
from time import sleep
- with SSHTunnelForwarder(
+ with open_tunnel(
('localhost', 2222),
ssh_username="vagrant",
ssh_password="vagrant",
@@ -189,6 +188,40 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
(bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
+ Example 4
+ ---------
+
+ Opening an SSH session jumping over two tunnels. SSH transport and tunnels
+ will be daemonised, which will not wait for the connections to stop at close
+ time.
+
+ .. code-block:: python
+
+ import sshtunnel
+ from paramiko import SSHClient
+
+
+ with sshtunnel.open_tunnel(
+ ssh_address_or_host=('GW1_ip', 20022),
+ remote_bind_address=('GW2_ip', 22),
+ ) as tunnel1:
+ print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
+ with sshtunnel.open_tunnel(
+ ssh_address_or_host=('localhost', tunnel1.local_bind_port),
+ remote_bind_address=('target_ip', 22),
+ ssh_username='GW2_user',
+ ssh_password='GW2_pwd',
+ ) as tunnel2:
+ print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
+ with SSHClient() as ssh:
+ ssh.connect('localhost',
+ port=tunnel2.local_bind_port,
+ username='target_user',
+ password='target_pwd',
+ )
+ ssh.exec_command(...)
+
+
CLI usage
=========
@@ -198,11 +231,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
[-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
- [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER [FOLDER ...]]]
+ [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
+ [-d [FOLDER [FOLDER ...]]]
ssh_address
Pure python ssh tunnel utils
- Version 0.1.4
+ Version 0.4.0
positional arguments:
ssh_address SSH server IP address (GW for SSH tunnels)
@@ -246,7 +280,7 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
-d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
List of directories where SSH pkeys (in the format `id_*`) may be found
- .. _Pahaz Blinov: https://github.com/pahaz
+ .. _Pahaz: https://github.com/pahaz
.. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
.. _paramiko: http://www.paramiko.org/
.. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
@@ -285,10 +319,45 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
- `Dan Harbin`_
- `Ignacio Peluffo`_
- `Niels Zeilemaker`_
+ - `Georgy Rylov`_
+ - `Eddie Chiang`_
+ - `kkrasovskii`_
CHANGELOG
=========
+ - v.0.4.0 (`Pahaz`_)
+ + Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (`#219`_)
+ + Add docker based end to end functinal tests for Mongo/Postgres/MySQL (`#219`_)
+ + Add docker based end to end hangs tests (`#219`_)
+
+ - v.0.3.2 (`Pahaz`_, `JM Fernández`_)
+ + Fix host key directory detection
+ + Unify default ssh config folder to `~/.ssh`
+
+ - v.0.3.1 (`Pahaz`_)
+ + Increase open connection timeout to 10 secods
+
+ - v.0.3.0 (`Pahaz`_)
+ + Change default with context behavior to use `.stop(force=True)` on exit (is not fully backward compatible)
+ + Remove useless `daemon_forward_servers = True` hack for hangs prevention (is not fully backward compatible)
+ + Set transport keepalive to 5 second by default (disabled for version < 0.3.0)
+ + Set default transport timeout to 0.1
+ + Deprecate and remove `block_on_close` option
+ + Fix "deadlocks" / "tunneling hangs" (`#173`_, `#201`_, `#162`_, `#211`_)
+
+ - v.0.2.2 (`Pahaz`_)
+ + Add `.stop(force=True)` for force close active connections (`#201`_)
+
+ - v.0.2.1 (`Pahaz`_, `Eddie Chiang`_ and `kkrasovskii`_)
+ + Fixes bug with orphan thread for a tunnel that is DOWN (`#170`_)
+
+ - v.0.2.0 (`Georgy Rylov`_)
+ + Support IPv6 without proxy command. Use built-in paramiko create socket logic. The logic tries to use ipv6 socket family first, then ipv4 socket family.
+
+ - v.0.1.5 (`JM Fernández`_)
+ + Introduce `block_on_close` attribute
+
- v.0.1.4 (`Niels Zeilemaker`_)
+ Allow loading pkeys from `~/.ssh`
@@ -305,7 +374,7 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
- v.0.1.0 (`JM Fernández`_)
+ Add `tunnel_bindings` property
+ Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
- (`Pahaz Blinov`_, `JM Fernández`_)
+ (`Pahaz`_, `JM Fernández`_)
+ Add TRACE logging level (`JM Fernández`_)
+ Code and tests refactoring (`JM Fernández`_)
+ Drop python3.2 support
@@ -331,55 +400,56 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
+ Add coverage (`JM Fernández`_)
+ Refactoring (`JM Fernández`_)
- - v.0.0.6 (`Pahaz Blinov`_)
- + add ``-S`` CLI options for ssh private key password support (`Pahaz Blinov`_)
+ - v.0.0.6 (`Pahaz`_)
+ + add ``-S`` CLI options for ssh private key password support (`Pahaz`_)
- - v.0.0.5 (`Pahaz Blinov`_)
+ - v.0.0.5 (`Pahaz`_)
+ add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
+ add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
+ ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
+ fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
- + add tests (`Pahaz Blinov`_)
- + add CI integration (`Pahaz Blinov`_)
- + normal packaging (`Pahaz Blinov`_)
- + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz Blinov`_) [changed default behavior]
- + use daemon mode = False in all threads by default; detail_ (`Pahaz Blinov`_) [changed default behavior]
+ + add tests (`Pahaz`_)
+ + add CI integration (`Pahaz`_)
+ + normal packaging (`Pahaz`_)
+ + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz`_) [changed default behavior]
+ + use daemon mode = False in all threads by default; detail_ (`Pahaz`_) [changed default behavior]
- - v.0.0.4.4 (`Pahaz Blinov`_)
- + fix issue `#24`_ - hide ssh password in logs (`Pahaz Blinov`_)
+ - v.0.0.4.4 (`Pahaz`_)
+ + fix issue `#24`_ - hide ssh password in logs (`Pahaz`_)
- - v.0.0.4.3 (`Pahaz Blinov`_)
- + fix default port issue `#19`_ (`Pahaz Blinov`_)
+ - v.0.0.4.3 (`Pahaz`_)
+ + fix default port issue `#19`_ (`Pahaz`_)
- - v.0.0.4.2 (`Pahaz Blinov`_)
+ - v.0.0.4.2 (`Pahaz`_)
+ fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
- - v.0.0.4.1 (`Pahaz Blinov`_)
- + fix CLI issues `#13`_ (`Pahaz Blinov`_)
+ - v.0.0.4.1 (`Pahaz`_)
+ + fix CLI issues `#13`_ (`Pahaz`_)
- - v.0.0.4 (`Pahaz Blinov`_)
- + daemon mode by default for all threads (`JM Fernández`_, `Pahaz Blinov`_) - *incompatible*
- + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
- + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
+ - v.0.0.4 (`Pahaz`_)
+ + daemon mode by default for all threads (`JM Fernández`_, `Pahaz`_) - *incompatible*
+ + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
+ + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
+ rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
+ add CLI interface (`JM Fernández`_)
+ support opening several tunnels at once (`JM Fernández`_)
- + improve stability and readability (`JM Fernández`_, `Pahaz Blinov`_)
- + improve logging (`JM Fernández`_, `Pahaz Blinov`_)
- + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz Blinov`_)
+ + improve stability and readability (`JM Fernández`_, `Pahaz`_)
+ + improve logging (`JM Fernández`_, `Pahaz`_)
+ + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz`_)
+ add ``ssh_config_file`` argument support (`JM Fernández`_)
- + add Python 3 support (`JM Fernández`_, `Pahaz Blinov`_)
+ + add Python 3 support (`JM Fernández`_, `Pahaz`_)
- - v.0.0.3 (`Pahaz Blinov`_)
+ - v.0.0.3 (`Pahaz`_)
+ add ``threaded`` option (`Cameron Maske`_)
+ fix exception error message, correctly printing destination address (`Gustavo Machado`_)
- + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz Blinov`_)
+ + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz`_)
- - v.0.0.1 (`Pahaz Blinov`_)
- + ``SSHTunnelForwarder`` class (`Pahaz Blinov`_)
- + ``open`` function (`Pahaz Blinov`_)
+ - v.0.0.1 (`Pahaz`_)
+ + ``SSHTunnelForwarder`` class (`Pahaz`_)
+ + ``open`` function (`Pahaz`_)
+ .. _Pahaz: https://github.com/pahaz
.. _Cameron Maske: https://github.com/cameronmaske
.. _Gustavo Machado: https://github.com/gdmachado
.. _Colin Jermain: https://github.com/cjermain
@@ -391,6 +461,9 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
.. _Dan Harbin: https://github.com/RasterBurn
.. _Ignacio Peluffo: https://github.com/ipeluffo
.. _Niels Zeilemaker: https://github.com/NielsZeilemaker
+ .. _Georgy Rylov: https://github.com/g0djan
+ .. _Eddie Chiang: https://github.com/eddie-chiang
+ .. _kkrasovskii: https://github.com/kkrasovskii
.. _#13: https://github.com/pahaz/sshtunnel/issues/13
.. _#16: https://github.com/pahaz/sshtunnel/issues/16
.. _#19: https://github.com/pahaz/sshtunnel/issues/19
@@ -404,6 +477,13 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
.. _#41: https://github.com/pahaz/sshtunnel/issues/41
.. _#43: https://github.com/pahaz/sshtunnel/issues/43
.. _#46: https://github.com/pahaz/sshtunnel/issues/46
+ .. _#170: https://github.com/pahaz/sshtunnel/issues/170
+ .. _#201: https://github.com/pahaz/sshtunnel/issues/201
+ .. _#162: https://github.com/pahaz/sshtunnel/issues/162
+ .. _#173: https://github.com/pahaz/sshtunnel/issues/173
+ .. _#201: https://github.com/pahaz/sshtunnel/issues/201
+ .. _#211: https://github.com/pahaz/sshtunnel/issues/211
+ .. _#219: https://github.com/pahaz/sshtunnel/issues/219
.. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
Keywords: ssh tunnel paramiko proxy tcp-forward
@@ -420,3 +500,9 @@ Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Description-Content-Type: text/x-rst
+Provides-Extra: dev
+Provides-Extra: test
+Provides-Extra: build_sphinx
diff --git a/README.rst b/README.rst
index 419cbae..65b04bd 100644
--- a/README.rst
+++ b/README.rst
@@ -2,11 +2,11 @@
|pyversions| |license|
-**Author**: `Pahaz Blinov`_
+**Author**: `Pahaz`_
**Repo**: https://github.com/pahaz/sshtunnel/
-Inspired by https://github.com/jmagnusson/bgtunnel, but it doesn't work on
+Inspired by https://github.com/jmagnusson/bgtunnel, which doesn't work on
Windows.
See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
@@ -102,12 +102,12 @@ Code corresponding to **Fig1** above follows, given remote server's address is
``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
port.
-.. code-block:: py
+.. code-block:: python
from sshtunnel import SSHTunnelForwarder
server = SSHTunnelForwarder(
- 'pahaz.urfuclub.ru',
+ 'alfa.8iq.dev',
ssh_username="pahaz",
ssh_password="secret",
remote_bind_address=('127.0.0.1', 8080)
@@ -127,12 +127,12 @@ Example of a port forwarding to a private server not directly reachable,
assuming password protected pkey authentication, remote server's SSH service is
listening on port 443 and that port is open in the firewall (**Fig2**):
-.. code-block:: py
+.. code-block:: python
import paramiko
- from sshtunnel import SSHTunnelForwarder
+ import sshtunnel
- with SSHTunnelForwarder(
+ with sshtunnel.open_tunnel(
(REMOTE_SERVER_IP, 443),
ssh_username="",
ssh_pkey="/var/ssh/rsa_key",
@@ -154,12 +154,12 @@ Example 3
Example of a port forwarding for the Vagrant MySQL local port:
-.. code-block:: py
+.. code-block:: python
- from sshtunnel import SSHTunnelForwarder
+ from sshtunnel import open_tunnel
from time import sleep
- with SSHTunnelForwarder(
+ with open_tunnel(
('localhost', 2222),
ssh_username="vagrant",
ssh_password="vagrant",
@@ -179,6 +179,40 @@ Or simply using the CLI:
(bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
+Example 4
+---------
+
+Opening an SSH session jumping over two tunnels. SSH transport and tunnels
+will be daemonised, which will not wait for the connections to stop at close
+time.
+
+.. code-block:: python
+
+ import sshtunnel
+ from paramiko import SSHClient
+
+
+ with sshtunnel.open_tunnel(
+ ssh_address_or_host=('GW1_ip', 20022),
+ remote_bind_address=('GW2_ip', 22),
+ ) as tunnel1:
+ print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
+ with sshtunnel.open_tunnel(
+ ssh_address_or_host=('localhost', tunnel1.local_bind_port),
+ remote_bind_address=('target_ip', 22),
+ ssh_username='GW2_user',
+ ssh_password='GW2_pwd',
+ ) as tunnel2:
+ print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
+ with SSHClient() as ssh:
+ ssh.connect('localhost',
+ port=tunnel2.local_bind_port,
+ username='target_user',
+ password='target_pwd',
+ )
+ ssh.exec_command(...)
+
+
CLI usage
=========
@@ -188,11 +222,12 @@ CLI usage
usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
[-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
- [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER [FOLDER ...]]]
+ [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
+ [-d [FOLDER [FOLDER ...]]]
ssh_address
Pure python ssh tunnel utils
- Version 0.1.4
+ Version 0.4.0
positional arguments:
ssh_address SSH server IP address (GW for SSH tunnels)
@@ -236,7 +271,7 @@ CLI usage
-d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
List of directories where SSH pkeys (in the format `id_*`) may be found
-.. _Pahaz Blinov: https://github.com/pahaz
+.. _Pahaz: https://github.com/pahaz
.. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
.. _paramiko: http://www.paramiko.org/
.. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
diff --git a/changelog.rst b/changelog.rst
index 6d877bc..4ace59f 100644
--- a/changelog.rst
+++ b/changelog.rst
@@ -12,10 +12,45 @@ CONTRIBUTORS
- `Dan Harbin`_
- `Ignacio Peluffo`_
- `Niels Zeilemaker`_
+- `Georgy Rylov`_
+- `Eddie Chiang`_
+- `kkrasovskii`_
CHANGELOG
=========
+- v.0.4.0 (`Pahaz`_)
+ + Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (`#219`_)
+ + Add docker based end to end functinal tests for Mongo/Postgres/MySQL (`#219`_)
+ + Add docker based end to end hangs tests (`#219`_)
+
+- v.0.3.2 (`Pahaz`_, `JM Fernández`_)
+ + Fix host key directory detection
+ + Unify default ssh config folder to `~/.ssh`
+
+- v.0.3.1 (`Pahaz`_)
+ + Increase open connection timeout to 10 secods
+
+- v.0.3.0 (`Pahaz`_)
+ + Change default with context behavior to use `.stop(force=True)` on exit (is not fully backward compatible)
+ + Remove useless `daemon_forward_servers = True` hack for hangs prevention (is not fully backward compatible)
+ + Set transport keepalive to 5 second by default (disabled for version < 0.3.0)
+ + Set default transport timeout to 0.1
+ + Deprecate and remove `block_on_close` option
+ + Fix "deadlocks" / "tunneling hangs" (`#173`_, `#201`_, `#162`_, `#211`_)
+
+- v.0.2.2 (`Pahaz`_)
+ + Add `.stop(force=True)` for force close active connections (`#201`_)
+
+- v.0.2.1 (`Pahaz`_, `Eddie Chiang`_ and `kkrasovskii`_)
+ + Fixes bug with orphan thread for a tunnel that is DOWN (`#170`_)
+
+- v.0.2.0 (`Georgy Rylov`_)
+ + Support IPv6 without proxy command. Use built-in paramiko create socket logic. The logic tries to use ipv6 socket family first, then ipv4 socket family.
+
+- v.0.1.5 (`JM Fernández`_)
+ + Introduce `block_on_close` attribute
+
- v.0.1.4 (`Niels Zeilemaker`_)
+ Allow loading pkeys from `~/.ssh`
@@ -32,7 +67,7 @@ CHANGELOG
- v.0.1.0 (`JM Fernández`_)
+ Add `tunnel_bindings` property
+ Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
- (`Pahaz Blinov`_, `JM Fernández`_)
+ (`Pahaz`_, `JM Fernández`_)
+ Add TRACE logging level (`JM Fernández`_)
+ Code and tests refactoring (`JM Fernández`_)
+ Drop python3.2 support
@@ -58,55 +93,56 @@ CHANGELOG
+ Add coverage (`JM Fernández`_)
+ Refactoring (`JM Fernández`_)
-- v.0.0.6 (`Pahaz Blinov`_)
- + add ``-S`` CLI options for ssh private key password support (`Pahaz Blinov`_)
+- v.0.0.6 (`Pahaz`_)
+ + add ``-S`` CLI options for ssh private key password support (`Pahaz`_)
-- v.0.0.5 (`Pahaz Blinov`_)
+- v.0.0.5 (`Pahaz`_)
+ add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
+ add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
+ ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
+ fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
- + add tests (`Pahaz Blinov`_)
- + add CI integration (`Pahaz Blinov`_)
- + normal packaging (`Pahaz Blinov`_)
- + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz Blinov`_) [changed default behavior]
- + use daemon mode = False in all threads by default; detail_ (`Pahaz Blinov`_) [changed default behavior]
+ + add tests (`Pahaz`_)
+ + add CI integration (`Pahaz`_)
+ + normal packaging (`Pahaz`_)
+ + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz`_) [changed default behavior]
+ + use daemon mode = False in all threads by default; detail_ (`Pahaz`_) [changed default behavior]
-- v.0.0.4.4 (`Pahaz Blinov`_)
- + fix issue `#24`_ - hide ssh password in logs (`Pahaz Blinov`_)
+- v.0.0.4.4 (`Pahaz`_)
+ + fix issue `#24`_ - hide ssh password in logs (`Pahaz`_)
-- v.0.0.4.3 (`Pahaz Blinov`_)
- + fix default port issue `#19`_ (`Pahaz Blinov`_)
+- v.0.0.4.3 (`Pahaz`_)
+ + fix default port issue `#19`_ (`Pahaz`_)
-- v.0.0.4.2 (`Pahaz Blinov`_)
+- v.0.0.4.2 (`Pahaz`_)
+ fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
-- v.0.0.4.1 (`Pahaz Blinov`_)
- + fix CLI issues `#13`_ (`Pahaz Blinov`_)
+- v.0.0.4.1 (`Pahaz`_)
+ + fix CLI issues `#13`_ (`Pahaz`_)
-- v.0.0.4 (`Pahaz Blinov`_)
- + daemon mode by default for all threads (`JM Fernández`_, `Pahaz Blinov`_) - *incompatible*
- + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
- + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
+- v.0.0.4 (`Pahaz`_)
+ + daemon mode by default for all threads (`JM Fernández`_, `Pahaz`_) - *incompatible*
+ + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
+ + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
+ rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
+ add CLI interface (`JM Fernández`_)
+ support opening several tunnels at once (`JM Fernández`_)
- + improve stability and readability (`JM Fernández`_, `Pahaz Blinov`_)
- + improve logging (`JM Fernández`_, `Pahaz Blinov`_)
- + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz Blinov`_)
+ + improve stability and readability (`JM Fernández`_, `Pahaz`_)
+ + improve logging (`JM Fernández`_, `Pahaz`_)
+ + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz`_)
+ add ``ssh_config_file`` argument support (`JM Fernández`_)
- + add Python 3 support (`JM Fernández`_, `Pahaz Blinov`_)
+ + add Python 3 support (`JM Fernández`_, `Pahaz`_)
-- v.0.0.3 (`Pahaz Blinov`_)
+- v.0.0.3 (`Pahaz`_)
+ add ``threaded`` option (`Cameron Maske`_)
+ fix exception error message, correctly printing destination address (`Gustavo Machado`_)
- + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz Blinov`_)
+ + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz`_)
-- v.0.0.1 (`Pahaz Blinov`_)
- + ``SSHTunnelForwarder`` class (`Pahaz Blinov`_)
- + ``open`` function (`Pahaz Blinov`_)
+- v.0.0.1 (`Pahaz`_)
+ + ``SSHTunnelForwarder`` class (`Pahaz`_)
+ + ``open`` function (`Pahaz`_)
+.. _Pahaz: https://github.com/pahaz
.. _Cameron Maske: https://github.com/cameronmaske
.. _Gustavo Machado: https://github.com/gdmachado
.. _Colin Jermain: https://github.com/cjermain
@@ -118,6 +154,9 @@ CHANGELOG
.. _Dan Harbin: https://github.com/RasterBurn
.. _Ignacio Peluffo: https://github.com/ipeluffo
.. _Niels Zeilemaker: https://github.com/NielsZeilemaker
+.. _Georgy Rylov: https://github.com/g0djan
+.. _Eddie Chiang: https://github.com/eddie-chiang
+.. _kkrasovskii: https://github.com/kkrasovskii
.. _#13: https://github.com/pahaz/sshtunnel/issues/13
.. _#16: https://github.com/pahaz/sshtunnel/issues/16
.. _#19: https://github.com/pahaz/sshtunnel/issues/19
@@ -131,4 +170,11 @@ CHANGELOG
.. _#41: https://github.com/pahaz/sshtunnel/issues/41
.. _#43: https://github.com/pahaz/sshtunnel/issues/43
.. _#46: https://github.com/pahaz/sshtunnel/issues/46
+.. _#170: https://github.com/pahaz/sshtunnel/issues/170
+.. _#201: https://github.com/pahaz/sshtunnel/issues/201
+.. _#162: https://github.com/pahaz/sshtunnel/issues/162
+.. _#173: https://github.com/pahaz/sshtunnel/issues/173
+.. _#201: https://github.com/pahaz/sshtunnel/issues/201
+.. _#211: https://github.com/pahaz/sshtunnel/issues/211
+.. _#219: https://github.com/pahaz/sshtunnel/issues/219
.. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
diff --git a/debian/changelog b/debian/changelog
index 69b7d3e..5f8db51 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,9 +1,10 @@
-sshtunnel (0.1.4-4) UNRELEASED; urgency=medium
+sshtunnel (0.4.0-1) UNRELEASED; urgency=medium
* Bump debhelper from old 12 to 13.
* Update standards version to 4.1.5, no changes needed.
+ * New upstream release.
- -- Debian Janitor <janitor@jelmer.uk> Fri, 06 Jan 2023 09:34:08 -0000
+ -- Debian Janitor <janitor@jelmer.uk> Fri, 06 Jan 2023 15:35:41 -0000
sshtunnel (0.1.4-3) unstable; urgency=medium
diff --git a/docs/conf.py b/docs/conf.py
index e3bd5b3..7be950a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,6 +16,8 @@
import sys
import os
+import sshtunnel
+
# Patch to disable warning on non-local image
import sphinx.environment
from docutils.utils import get_source_line
@@ -61,17 +63,17 @@ master_doc = 'index'
# General information about the project.
project = 'sshtunnel'
-copyright = '2014-2016, Pahaz Blinov and contributors'
-author = 'Pahaz Blinov'
+copyright = '2014-2020, Pahaz White and contributors'
+author = 'Pahaz White'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '0.0.8'
+version = sshtunnel.__version__
# The full version, including alpha/beta/rc tags.
-release = '0.0.8'
+release = sshtunnel.__version__
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/index.rst b/docs/index.rst
index 4210823..2e38cda 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -8,9 +8,6 @@ Welcome to sshtunnel's documentation!
API
===
-.. toctree::
- :maxdepth: 3
-
.. automodule:: sshtunnel
:members:
:member-order: bysource
@@ -21,4 +18,4 @@ API
License
=======
-.. include:: ../LICENSE
\ No newline at end of file
+.. include:: ../LICENSE
diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt
deleted file mode 100644
index 1d3c833..0000000
--- a/docs/requirements-docs.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-docutils==0.12
-sphinx==1.3.5
-sphinxcontrib-napoleon==0.5.0
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..76356ef
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,3 @@
+docutils
+sphinx
+sphinxcontrib-napoleon
diff --git a/e2e_tests/docker-compose.yaml b/e2e_tests/docker-compose.yaml
new file mode 100644
index 0000000..5f559c8
--- /dev/null
+++ b/e2e_tests/docker-compose.yaml
@@ -0,0 +1,60 @@
+---
+version: "2.1"
+services:
+ ssh:
+ image: ghcr.io/linuxserver/openssh-server
+ container_name: openssh-server
+ hostname: openssh-server
+ environment:
+ - PUID=1000
+ - PGID=1000
+ - TZ=Europe/London
+ - PUBLIC_KEY_FILE=/config/ssh_host_keys/ssh_host_rsa_key.pub
+ - SUDO_ACCESS=false
+ - PASSWORD_ACCESS=false
+ - USER_NAME=linuxserver
+ volumes:
+ - ./ssh-server-config:/config/ssh_host_keys:ro
+ ports:
+ - "127.0.0.1:2223:2222"
+ networks:
+ - inner
+
+ postgresdb:
+ image: postgres:13.0
+ environment:
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+ POSTGRES_DB: main
+ networks:
+ inner:
+ ipv4_address: 10.5.0.5
+
+ mysqldb:
+ image: mysql:8
+ environment:
+ MYSQL_DATABASE: main
+ MYSQL_USER: mysql
+ MYSQL_PASSWORD: mysql
+ MYSQL_ROOT_PASSWORD: mysqlroot
+ networks:
+ inner:
+ ipv4_address: 10.5.0.6
+
+ mongodb:
+ image: mongo:3.6
+ environment:
+ MONGO_INITDB_ROOT_USERNAME: mongo
+ MONGO_INITDB_ROOT_PASSWORD: mongo
+ MONGO_INITDB_DATABASE: main
+ networks:
+ inner:
+ ipv4_address: 10.5.0.7
+
+networks:
+ inner:
+ driver: bridge
+ ipam:
+ config:
+ - subnet: 10.5.0.0/16
+ gateway: 10.5.0.1
diff --git a/e2e_tests/run_docker_e2e_db_tests.py b/e2e_tests/run_docker_e2e_db_tests.py
new file mode 100644
index 0000000..e14edd3
--- /dev/null
+++ b/e2e_tests/run_docker_e2e_db_tests.py
@@ -0,0 +1,241 @@
+import select
+import traceback
+import sys
+import os
+import time
+from sshtunnel import SSHTunnelForwarder
+import sshtunnel
+import logging
+import threading
+import paramiko
+
+sshtunnel.DEFAULT_LOGLEVEL = 1
+logging.basicConfig(
+ format='%(asctime)s| %(levelname)-4.3s|%(threadName)10.9s/%(lineno)04d@%(module)-10.9s| %(message)s', level=1)
+
+SSH_SERVER_ADDRESS = ('127.0.0.1', 2223)
+SSH_SERVER_USERNAME = 'linuxserver'
+SSH_PKEY = os.path.join(os.path.dirname(__file__), 'ssh-server-config', 'ssh_host_rsa_key')
+SSH_SERVER_REMOTE_SIDE_ADDRESS_PG = ('10.5.0.5', 5432)
+SSH_SERVER_REMOTE_SIDE_ADDRESS_MYSQL = ('10.5.0.6', 3306)
+SSH_SERVER_REMOTE_SIDE_ADDRESS_MONGO = ('10.5.0.7', 27017)
+
+PG_DATABASE_NAME = 'main'
+PG_USERNAME = 'postgres'
+PG_PASSWORD = 'postgres'
+PG_QUERY = 'select version()'
+PG_EXPECT = eval(
+ """('PostgreSQL 13.0 (Debian 13.0-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit',)""")
+
+MYSQL_DATABASE_NAME = 'main'
+MYSQL_USERNAME = 'mysql'
+MYSQL_PASSWORD = 'mysql'
+MYSQL_QUERY = 'select version()'
+MYSQL_EXPECT = (('8.0.22',),)
+
+MONGO_DATABASE_NAME = 'main'
+MONGO_USERNAME = 'mongo'
+MONGO_PASSWORD = 'mongo'
+MONGO_QUERY = lambda client, db: client.server_info()
+MONGO_EXPECT = eval(
+ """{'version': '3.6.21', 'gitVersion': '1cd2db51dce4b16f4bc97a75056269df0dc0bddb', 'modules': [], 'allocator': 'tcmalloc', 'javascriptEngine': 'mozjs', 'sysInfo': 'deprecated', 'versionArray': [3, 6, 21, 0], 'openssl': {'running': 'OpenSSL 1.0.2g 1 Mar 2016', 'compiled': 'OpenSSL 1.0.2g 1 Mar 2016'}, 'buildEnvironment': {'distmod': 'ubuntu1604', 'distarch': 'x86_64', 'cc': '/opt/mongodbtoolchain/v2/bin/gcc: gcc (GCC) 5.4.0', 'ccflags': '-fno-omit-frame-pointer -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -fno-builtin-memcmp', 'cxx': '/opt/mongodbtoolchain/v2/bin/g++: g++ (GCC) 5.4.0', 'cxxflags': '-Woverloaded-virtual -Wno-maybe-uninitialized -std=c++14', 'linkflags': '-pthread -Wl,-z,now -rdynamic -Wl,--fatal-warnings -fstack-protector-strong -fuse-ld=gold -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro', 'target_arch': 'x86_64', 'target_os': 'linux'}, 'bits': 64, 'debug': False, 'maxBsonObjectSize': 16777216, 'storageEngines': ['devnull', 'ephemeralForTest', 'mmapv1', 'wiredTiger'], 'ok': 1.0}""")
+
+
+def run_postgres_query(port, query=PG_QUERY):
+ import psycopg2
+
+ ASYNC_OK = 1
+ ASYNC_READ_TIMEOUT = 2
+ ASYNC_WRITE_TIMEOUT = 3
+ ASYNC_TIMEOUT = 0.2
+
+ def wait(conn):
+ while 1:
+ state = conn.poll()
+ if state == psycopg2.extensions.POLL_OK:
+ break
+ elif state == psycopg2.extensions.POLL_WRITE:
+ select.select([], [conn.fileno()], [])
+ elif state == psycopg2.extensions.POLL_READ:
+ select.select([conn.fileno()], [], [])
+ else:
+ raise psycopg2.OperationalError(
+ "poll() returned %s from _wait function" % state)
+
+ def wait_timeout(conn):
+ while 1:
+ state = conn.poll()
+ if state == psycopg2.extensions.POLL_OK:
+ return ASYNC_OK
+ elif state == psycopg2.extensions.POLL_WRITE:
+ # Wait for the given time and then check the return status
+ # If three empty lists are returned then the time-out is
+ # reached.
+ timeout_status = select.select(
+ [], [conn.fileno()], [], ASYNC_TIMEOUT
+ )
+ if timeout_status == ([], [], []):
+ return ASYNC_WRITE_TIMEOUT
+ elif state == psycopg2.extensions.POLL_READ:
+ # Wait for the given time and then check the return status
+ # If three empty lists are returned then the time-out is
+ # reached.
+ timeout_status = select.select(
+ [conn.fileno()], [], [], ASYNC_TIMEOUT
+ )
+ if timeout_status == ([], [], []):
+ return ASYNC_READ_TIMEOUT
+ else:
+ raise psycopg2.OperationalError(
+ "poll() returned %s from _wait_timeout function" % state
+ )
+
+ pg_conn = psycopg2.connect(
+ host='127.0.0.1',
+ hostaddr='127.0.0.1',
+ port=port,
+ database=PG_DATABASE_NAME,
+ user=PG_USERNAME,
+ password=PG_PASSWORD,
+ sslmode='disable',
+ async_=1
+ )
+ wait(pg_conn)
+ cur = pg_conn.cursor()
+ cur.execute(query)
+ res = wait_timeout(cur.connection)
+ while res != ASYNC_OK:
+ res = wait_timeout(cur.connection)
+ return cur.fetchone()
+
+
+def run_mysql_query(port, query=MYSQL_QUERY):
+ import pymysql
+ conn = pymysql.connect(
+ host='127.0.0.1',
+ port=port,
+ user=MYSQL_USERNAME,
+ password=MYSQL_PASSWORD,
+ database=MYSQL_DATABASE_NAME,
+ connect_timeout=5,
+ read_timeout=5)
+ cursor = conn.cursor()
+ cursor.execute(query)
+ return cursor.fetchall()
+
+
+def run_mongo_query(port, query=MONGO_QUERY):
+ import pymongo
+ client = pymongo.MongoClient('127.0.0.1', port)
+ db = client[MONGO_DATABASE_NAME]
+ return query(client, db)
+
+
+def create_tunnel():
+ logging.info('Creating SSHTunnelForwarder... (sshtunnel v%s, paramiko v%s)',
+ sshtunnel.__version__, paramiko.__version__)
+ tunnel = SSHTunnelForwarder(
+ SSH_SERVER_ADDRESS,
+ ssh_username=SSH_SERVER_USERNAME,
+ ssh_pkey=SSH_PKEY,
+ remote_bind_addresses=[
+ SSH_SERVER_REMOTE_SIDE_ADDRESS_PG, SSH_SERVER_REMOTE_SIDE_ADDRESS_MYSQL,
+ SSH_SERVER_REMOTE_SIDE_ADDRESS_MONGO,
+ ],
+ )
+ return tunnel
+
+
+def start(tunnel):
+ try:
+ logging.info('Trying to start ssh tunnel...')
+ tunnel.start()
+ except Exception as e:
+ logging.exception('Tunnel start exception: %r', e)
+ raise
+
+
+def run_db_queries(tunnel):
+ result1, result2, result3 = None, None, None
+
+ try:
+ logging.info('Trying to run PG query...')
+ result1 = run_postgres_query(tunnel.local_bind_ports[0])
+ logging.info('PG query: %r', result1)
+ except Exception as e:
+ logging.exception('PG query exception: %r', e)
+ raise
+
+ try:
+ logging.info('Trying to run MYSQL query...')
+ result2 = run_mysql_query(tunnel.local_bind_ports[1])
+ logging.info('MYSQL query: %r', result2)
+ except Exception as e:
+ logging.exception('MYSQL query exception: %r', e)
+ raise
+
+ try:
+ logging.info('Trying to run MONGO query...')
+ result3 = run_mongo_query(tunnel.local_bind_ports[2])
+ logging.info('MONGO query: %r', result3)
+ except Exception as e:
+ logging.exception('MONGO query exception: %r', e)
+ raise
+
+ return result1, result2, result3
+
+
+def wait_and_check_or_restart_if_required(tunnel, i=1):
+ logging.warning('Sleeping for %s second...', i)
+ while i:
+ time.sleep(1)
+ if i % 10 == 0:
+ logging.info('Running tunnel.check_tunnels... (i=%s)', i)
+ tunnel.check_tunnels()
+ logging.info('Check result: %r (i=%s)', tunnel.tunnel_is_up, i)
+ if not tunnel.is_active:
+ logging.warning('Tunnel is DOWN! restarting ...')
+ tunnel.restart()
+ i -= 1
+
+
+def stop(tunnel, force=True):
+ try:
+ logging.info('Trying to stop resources...')
+ tunnel.stop(force=force)
+ except Exception as e:
+ logging.exception('Tunnel stop exception: %r', e)
+ raise
+
+
+def show_threading_state_if_required():
+ current_threads = list(threading.enumerate())
+ if len(current_threads) > 1:
+ logging.warning('[1] THREAD INFO')
+ logging.info('Threads: %r', current_threads)
+ logging.info('Threads.daemon: %r', [x.daemon for x in current_threads])
+
+ if len(current_threads) > 1:
+ logging.warning('[2] STACK INFO')
+ code = ["\n\n*** STACKTRACE - START ***\n"]
+ for threadId, stack in sys._current_frames().items():
+ code.append("\n# ThreadID: %s" % threadId)
+ for filename, lineno, name, line in traceback.extract_stack(stack):
+ code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
+ if line:
+ code.append(" %s" % (line.strip()))
+ code.append("\n*** STACKTRACE - END ***\n\n")
+ logging.info('\n'.join(code))
+
+
+if __name__ == '__main__':
+ logging.warning('RUN')
+ tunnel = create_tunnel()
+ start(tunnel)
+ res = run_db_queries(tunnel)
+ stop(tunnel)
+ wait_and_check_or_restart_if_required(tunnel)
+ show_threading_state_if_required()
+ logging.warning('EOF')
+
+ assert res == (PG_EXPECT, MYSQL_EXPECT, MONGO_EXPECT)
diff --git a/e2e_tests/run_docker_e2e_hangs_tests.py b/e2e_tests/run_docker_e2e_hangs_tests.py
new file mode 100644
index 0000000..0ec7449
--- /dev/null
+++ b/e2e_tests/run_docker_e2e_hangs_tests.py
@@ -0,0 +1,13 @@
+import logging
+import sshtunnel
+import os
+
+
+if __name__ == '__main__':
+ path = os.path.join(os.path.dirname(__file__), 'run_docker_e2e_db_tests.py')
+ with open(path) as f:
+ exec(f.read())
+ logging.warning('RUN')
+ tunnel = create_tunnel()
+ start(tunnel)
+ logging.warning('EOF')
diff --git a/e2e_tests/ssh-server-config/ssh_host_dsa_key b/e2e_tests/ssh-server-config/ssh_host_dsa_key
new file mode 100644
index 0000000..3c7e9af
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_dsa_key
@@ -0,0 +1,21 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
+NzAAAAgQDSZjQVKBCj57wXTZTFusc/Amp5wet2ugo/Mh+86+v2WDbluFztNZXTA3EtX8p6
+zZtoLZJ/+VCtLqZD7MjJIt4/bPhOjyXOlbtIwL7w80drTxMFBOvuBQkD+TqIzaONwzsN5b
+GcQNACpyz4C2eSUP4KOmOrKXovFI6pMQ22lbqrrQAAABUAv4qw6qJkET1T4J8o0RgzoxNI
+TFkAAACAQQ5w7+2rPlC/GP9ScUCQZTicgzAYlTNOCvIcO4pRj7E1NwNMuafl6xNRjrIYBp
+OqMhDLIBx15Yob0J/6PpE65oeQ8Lq8QboZxO8bio0FGt4qE6mXB4vJq2oOwQkWHzH64x9l
+fmFQNe8KRpd0G/daXBgeF+FEqV2vVsjsjKXxwncAAACAFRvMwvnkzX/c2MaWx78+HJEjjf
+ATYt2acoLAH2YRwnhavQyEScNQDiZnBbIr2J21ccvGvFyZT2dtcz83pwFDa9o7Y41EWQG7
+ifRPYrj9aHd3TyxeiSGSZlna9ekcfXbIF7+aRHSyEie/YIYUGm73jCW+TDcXK1nQHu7tGL
+1KkBQAAAHox++oGsfvqBoAAAAHc3NoLWRzcwAAAIEA0mY0FSgQo+e8F02UxbrHPwJqecHr
+droKPzIfvOvr9lg25bhc7TWV0wNxLV/Kes2baC2Sf/lQrS6mQ+zIySLeP2z4To8lzpW7SM
+C+8PNHa08TBQTr7gUJA/k6iM2jjcM7DeWxnEDQAqcs+AtnklD+Cjpjqyl6LxSOqTENtpW6
+q60AAAAVAL+KsOqiZBE9U+CfKNEYM6MTSExZAAAAgEEOcO/tqz5Qvxj/UnFAkGU4nIMwGJ
+UzTgryHDuKUY+xNTcDTLmn5esTUY6yGAaTqjIQyyAcdeWKG9Cf+j6ROuaHkPC6vEG6GcTv
+G4qNBRreKhOplweLyatqDsEJFh8x+uMfZX5hUDXvCkaXdBv3WlwYHhfhRKldr1bI7Iyl8c
+J3AAAAgBUbzML55M1/3NjGlse/PhyRI43wE2LdmnKCwB9mEcJ4Wr0MhEnDUA4mZwWyK9id
+tXHLxrxcmU9nbXM/N6cBQ2vaO2ONRFkBu4n0T2K4/Wh3d08sXokhkmZZ2vXpHH12yBe/mk
+R0shInv2CGFBpu94wlvkw3FytZ0B7u7Ri9SpAUAAAAFAZscEj14jPPE+Znbk4FflEe6t2r
+AAAAE3Jvb3RAb3BlbnNzaC1zZXJ2ZXI=
+-----END OPENSSH PRIVATE KEY-----
diff --git a/e2e_tests/ssh-server-config/ssh_host_dsa_key.pub b/e2e_tests/ssh-server-config/ssh_host_dsa_key.pub
new file mode 100644
index 0000000..7fea8e5
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_dsa_key.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBANJmNBUoEKPnvBdNlMW6xz8CannB63a6Cj8yH7zr6/ZYNuW4XO01ldMDcS1fynrNm2gtkn/5UK0upkPsyMki3j9s+E6PJc6Vu0jAvvDzR2tPEwUE6+4FCQP5OojNo43DOw3lsZxA0AKnLPgLZ5JQ/go6Y6spei8UjqkxDbaVuqutAAAAFQC/irDqomQRPVPgnyjRGDOjE0hMWQAAAIBBDnDv7as+UL8Y/1JxQJBlOJyDMBiVM04K8hw7ilGPsTU3A0y5p+XrE1GOshgGk6oyEMsgHHXlihvQn/o+kTrmh5DwurxBuhnE7xuKjQUa3ioTqZcHi8mrag7BCRYfMfrjH2V+YVA17wpGl3Qb91pcGB4X4USpXa9WyOyMpfHCdwAAAIAVG8zC+eTNf9zYxpbHvz4ckSON8BNi3ZpygsAfZhHCeFq9DIRJw1AOJmcFsivYnbVxy8a8XJlPZ21zPzenAUNr2jtjjURZAbuJ9E9iuP1od3dPLF6JIZJmWdr16Rx9dsgXv5pEdLISJ79ghhQabveMJb5MNxcrWdAe7u0YvUqQFA== root@openssh-server
diff --git a/e2e_tests/ssh-server-config/ssh_host_ecdsa_key b/e2e_tests/ssh-server-config/ssh_host_ecdsa_key
new file mode 100644
index 0000000..b94696b
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_ecdsa_key
@@ -0,0 +1,9 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
+1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9T8ajwjHV1Xl705ShFqry77rS7wrh
+lCN0a4Hf33yapuCBKCRDr+/Y7gISoiER3rez56TQCvIFuKUEgCUsTMSWAAAAsFXWKa1V1i
+mtAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1PxqPCMdXVeXvT
+lKEWqvLvutLvCuGUI3Rrgd/ffJqm4IEoJEOv79juAhKiIRHet7PnpNAK8gW4pQSAJSxMxJ
+YAAAAhAOe2L6cmoGh4gZ++o+GiqMQ2WQ3RUfle/gc0G1nhLPhWAAAAE3Jvb3RAb3BlbnNz
+aC1zZXJ2ZXIBAgME
+-----END OPENSSH PRIVATE KEY-----
diff --git a/e2e_tests/ssh-server-config/ssh_host_ecdsa_key.pub b/e2e_tests/ssh-server-config/ssh_host_ecdsa_key.pub
new file mode 100644
index 0000000..bb68789
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_ecdsa_key.pub
@@ -0,0 +1 @@
+ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1PxqPCMdXVeXvTlKEWqvLvutLvCuGUI3Rrgd/ffJqm4IEoJEOv79juAhKiIRHet7PnpNAK8gW4pQSAJSxMxJY= root@openssh-server
diff --git a/e2e_tests/ssh-server-config/ssh_host_ed25519_key b/e2e_tests/ssh-server-config/ssh_host_ed25519_key
new file mode 100644
index 0000000..a99b456
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_ed25519_key
@@ -0,0 +1,7 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACCjD/qc+T3Cc/k2pbjOUFW0OobeLnoWUAoaBhTQchI26wAAAJgismUyIrJl
+MgAAAAtzc2gtZWQyNTUxOQAAACCjD/qc+T3Cc/k2pbjOUFW0OobeLnoWUAoaBhTQchI26w
+AAAECV2axXFduGtP3RS1f97smocwVLphmETzpWdwi89jWrJaMP+pz5PcJz+TaluM5QVbQ6
+ht4uehZQChoGFNByEjbrAAAAE3Jvb3RAb3BlbnNzaC1zZXJ2ZXIBAg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/e2e_tests/ssh-server-config/ssh_host_ed25519_key.pub b/e2e_tests/ssh-server-config/ssh_host_ed25519_key.pub
new file mode 100644
index 0000000..b07edee
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_ed25519_key.pub
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKMP+pz5PcJz+TaluM5QVbQ6ht4uehZQChoGFNByEjbr root@openssh-server
diff --git a/e2e_tests/ssh-server-config/ssh_host_rsa_key b/e2e_tests/ssh-server-config/ssh_host_rsa_key
new file mode 100644
index 0000000..01f74b4
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_rsa_key
@@ -0,0 +1,38 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
+NhAAAAAwEAAQAAAYEAvIGU0pRpThhIcaSPrg2+v7cXl+QcG0icb45hfD44yrCoXkpJp7nh
+Hv0ObZL2Y1cG7eeayYF4AqD3kwQ7W89GN6YO9b/mkJgawk0/YLUyojTS9dbcTbdkfPzyUa
+vTMDjly+PIjfiWOEnUgPf1y3xONLkJU0ILyTmgTzSIMNdKngtdCGfytBCuNiPKU8hEdEVt
+82ebqgtLoSYn9cUcVVz6LewzUh8+YtoPb8Z/BIVEzU37HiE9MOYIBXjo1AEJSnOCkjwlVl
+PzLhcXKTPht0iwv/KnZNNg0LDmnU/z0n+nPq/EMflum8jRYbgp0C5hksPdc8e0eEKd9gak
+t7B0ta3Mjt5b8HPQdBGZI/QFufEnSOxfJmoK4Bvjy/oUwE0hGU6po5g+4T2j6Bqqm2I+yV
+EbkP/UiuD/kEiT0C3yCV547gIDjN2ME9tGJDkd023BFvqn3stFVVZ5WsisRKGc+lvTfqeA
+JyKFaVt5a23y68ztjEMVrMLksRuEF8gG5kV7EGyjAAAFiCzGBRksxgUZAAAAB3NzaC1yc2
+EAAAGBALyBlNKUaU4YSHGkj64Nvr+3F5fkHBtInG+OYXw+OMqwqF5KSae54R79Dm2S9mNX
+Bu3nmsmBeAKg95MEO1vPRjemDvW/5pCYGsJNP2C1MqI00vXW3E23ZHz88lGr0zA45cvjyI
+34ljhJ1ID39ct8TjS5CVNCC8k5oE80iDDXSp4LXQhn8rQQrjYjylPIRHRFbfNnm6oLS6Em
+J/XFHFVc+i3sM1IfPmLaD2/GfwSFRM1N+x4hPTDmCAV46NQBCUpzgpI8JVZT8y4XFykz4b
+dIsL/yp2TTYNCw5p1P89J/pz6vxDH5bpvI0WG4KdAuYZLD3XPHtHhCnfYGpLewdLWtzI7e
+W/Bz0HQRmSP0BbnxJ0jsXyZqCuAb48v6FMBNIRlOqaOYPuE9o+gaqptiPslRG5D/1Irg/5
+BIk9At8gleeO4CA4zdjBPbRiQ5HdNtwRb6p97LRVVWeVrIrEShnPpb036ngCcihWlbeWtt
+8uvM7YxDFazC5LEbhBfIBuZFexBsowAAAAMBAAEAAAGAflHjdb2oV4HkQetBsSRa18QM1m
+cxAoOE+SiTYRudGQ6KtSzY8MGZ/xca7QiXfXhbF1+llTTiQ/i0Dtu+H0blyfLIgZwIGIsl
+G2GCf/7MoG//kmhaFuY3O56Rj3MyQVVPgHLy+VhE6hFniske+C4jhicc/aL7nOu15n3Qad
+JLmV8KB9EIjevDoloXgk9ot/WyuXKLmMaa9rFIA+UDmJyGtfFbbsOrHbj8sS11/oSD14RT
+LBygEb2EUI52j2LmY/LEvUL+59oCuJ6Y/h+pMdFeuHJzGjrVb573KnGwejzY24HHzzebrC
+Q+9NyVCTyizPHNu9w52/GPEZQFQBi7o9cDMd3ITZEPIaIvDHsUwPXaHUBHy/XHQTs8pDqk
+zCMcAs5zdzao2I0LQ+ZFYyvl1rue82ITjDISX1WK6nFYLBVXugi0rLGEdH6P+Psfl3uCIf
+aW7c12/BpZz2Pql5AuO1wsu4rmz2th68vaC/0IDqWekIbW9qihFbqnhfAxRsIURjpBAAAA
+wDhIQPsj9T9Vud3Z/TZjiAKCPbg3zi082u1GMMxXnNQtKO3J35wU7VUcAxAzosWr+emMqS
+U0qW+a5RXr3sqUOqH85b5+Xw0yv2sTr2pL0ALFW7Tq1mesCc3K0So3Yo30pWRIOxYM9ihm
+E4ci/3mN5kcKWwvLLomFPRU9u0XtIGKnF/cNByTuz9fceR6Pi6mQXZawv+OOMiBeu0gbyp
+F1uVe8PCshzCrWTE3UjRpQxy9gizvSbGZyGQi1Lm42JXKG3wAAAMEA4r4CLM1xsyxBBMld
+rxiTqy6bfrZjKkT5MPjBjp+57i5kW9NVqGCnIy/m98pLTuKjTCDmUuWQXS+oqhHw5vq/wj
+RvQYqkJDz1UGmC1lD2qyqERjOiWa8/iy4dXSLeHCT70+/xR2dBb0z8cT++yZEqLdEZSnHG
+yRaZMHot1OohVDqJS8nEbxOzgPGdopRMiX6ws/p5/k9YAGkHx0hszA8cn/Tk2/mdS5lugw
+Y7mdXzfcKvxkgoFrG7XowqRVrozcvDAAAAwQDU1ITasquNLaQhKNqiHx/N7bvKVO33icAx
+NdShqJEWx/g9idvQ25sA1Ubc1a+Ot5Lgfrs2OBKe+LgSmPAZOjv4ShqBHtsSh3am8/K1xR
+gQKgojLL4FhtgxtwoZrVvovZHGV3g2A28BRGbKIGVGPsOszJALU7jlLlcTHlB7SCQBI8FQ
+vTi2UEsfTmA22NnuVPITeqbmAQQXkSZcZbpbvdc0vQzp/3iOb/OCrIMET3HqVEMyQVsVs6
+xa9026AMTGLaEAAAATcm9vdEBvcGVuc3NoLXNlcnZlcg==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/e2e_tests/ssh-server-config/ssh_host_rsa_key.pub b/e2e_tests/ssh-server-config/ssh_host_rsa_key.pub
new file mode 100644
index 0000000..3288d9e
--- /dev/null
+++ b/e2e_tests/ssh-server-config/ssh_host_rsa_key.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8gZTSlGlOGEhxpI+uDb6/txeX5BwbSJxvjmF8PjjKsKheSkmnueEe/Q5tkvZjVwbt55rJgXgCoPeTBDtbz0Y3pg71v+aQmBrCTT9gtTKiNNL11txNt2R8/PJRq9MwOOXL48iN+JY4SdSA9/XLfE40uQlTQgvJOaBPNIgw10qeC10IZ/K0EK42I8pTyER0RW3zZ5uqC0uhJif1xRxVXPot7DNSHz5i2g9vxn8EhUTNTfseIT0w5ggFeOjUAQlKc4KSPCVWU/MuFxcpM+G3SLC/8qdk02DQsOadT/PSf6c+r8Qx+W6byNFhuCnQLmGSw91zx7R4Qp32BqS3sHS1rcyO3lvwc9B0EZkj9AW58SdI7F8magrgG+PL+hTATSEZTqmjmD7hPaPoGqqbYj7JURuQ/9SK4P+QSJPQLfIJXnjuAgOM3YwT20YkOR3TbcEW+qfey0VVVnlayKxEoZz6W9N+p4AnIoVpW3lrbfLrzO2MQxWswuSxG4QXyAbmRXsQbKM= root@openssh-server
diff --git a/e2e_tests/ssh-server-config/sshd_config b/e2e_tests/ssh-server-config/sshd_config
new file mode 100644
index 0000000..2112156
--- /dev/null
+++ b/e2e_tests/ssh-server-config/sshd_config
@@ -0,0 +1,117 @@
+# $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
+
+# This is the sshd server system-wide configuration file. See
+# sshd_config(5) for more information.
+
+# This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin
+
+# The strategy used for options in the default sshd_config shipped with
+# OpenSSH is to specify options with their default value where
+# possible, but leave them commented. Uncommented options override the
+# default value.
+
+#Port 22
+#AddressFamily any
+#ListenAddress 0.0.0.0
+#ListenAddress ::
+
+#HostKey /etc/ssh/ssh_host_rsa_key
+#HostKey /etc/ssh/ssh_host_ecdsa_key
+#HostKey /etc/ssh/ssh_host_ed25519_key
+
+# Ciphers and keying
+#RekeyLimit default none
+
+# Logging
+#SyslogFacility AUTH
+#LogLevel INFO
+
+# Authentication:
+
+#LoginGraceTime 2m
+#PermitRootLogin prohibit-password
+#StrictModes yes
+#MaxAuthTries 6
+#MaxSessions 10
+
+#PubkeyAuthentication yes
+
+# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
+# but this is overridden so installations will only check .ssh/authorized_keys
+AuthorizedKeysFile .ssh/authorized_keys
+
+#AuthorizedPrincipalsFile none
+
+#AuthorizedKeysCommand none
+#AuthorizedKeysCommandUser nobody
+
+# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
+#HostbasedAuthentication no
+# Change to yes if you don't trust ~/.ssh/known_hosts for
+# HostbasedAuthentication
+#IgnoreUserKnownHosts no
+# Don't read the user's ~/.rhosts and ~/.shosts files
+#IgnoreRhosts yes
+
+# To disable tunneled clear text passwords, change to no here!
+PasswordAuthentication no
+#PermitEmptyPasswords no
+
+# Change to no to disable s/key passwords
+#ChallengeResponseAuthentication yes
+
+# Kerberos options
+#KerberosAuthentication no
+#KerberosOrLocalPasswd yes
+#KerberosTicketCleanup yes
+#KerberosGetAFSToken no
+
+# GSSAPI options
+#GSSAPIAuthentication no
+#GSSAPICleanupCredentials yes
+
+# Set this to 'yes' to enable PAM authentication, account processing,
+# and session processing. If this is enabled, PAM authentication will
+# be allowed through the ChallengeResponseAuthentication and
+# PasswordAuthentication. Depending on your PAM configuration,
+# PAM authentication via ChallengeResponseAuthentication may bypass
+# the setting of "PermitRootLogin without-password".
+# If you just want the PAM account and session checks to run without
+# PAM authentication, then enable this but set PasswordAuthentication
+# and ChallengeResponseAuthentication to 'no'.
+#UsePAM no
+
+#AllowAgentForwarding yes
+# Feel free to re-enable these if your use case requires them.
+AllowTcpForwarding yes
+GatewayPorts no
+X11Forwarding no
+#X11DisplayOffset 10
+#X11UseLocalhost yes
+#PermitTTY yes
+#PrintMotd yes
+#PrintLastLog yes
+#TCPKeepAlive yes
+#PermitUserEnvironment no
+#Compression delayed
+#ClientAliveInterval 0
+#ClientAliveCountMax 3
+#UseDNS no
+PidFile /config/sshd.pid
+#MaxStartups 10:30:100
+#PermitTunnel no
+#ChrootDirectory none
+#VersionAddendum none
+
+# no default banner path
+#Banner none
+
+# override default of no subsystems
+Subsystem sftp /usr/lib/ssh/sftp-server -u 022
+
+# Example of overriding settings on a per-user basis
+#Match User anoncvs
+# X11Forwarding no
+# AllowTcpForwarding no
+# PermitTTY no
+# ForceCommand cvs server
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..b0471b7
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta:__legacy__"
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 1440405..ccaaab8 100644
--- a/setup.py
+++ b/setup.py
@@ -6,12 +6,9 @@ https://github.com/pypa/sampleproject
"""
import re
-import sys
from os import path
from codecs import open # To use a consistent encoding
-
from setuptools import setup # Always prefer setuptools over distutils
-from setuptools.command.test import test as TestCommand
here = path.abspath(path.dirname(__file__))
name = 'sshtunnel'
@@ -32,21 +29,6 @@ with open(path.join(here, name + '.py'), encoding='utf-8') as f:
version = eval(re.search("__version__[ ]*=[ ]*([^\r\n]+)", data).group(1))
-class Tox(TestCommand):
- """ Integration with tox """
-
- def finalize_options(self):
- TestCommand.finalize_options(self)
- self.test_args = ['--recreate', '-v']
- self.test_suite = True
-
- def run_tests(self):
- # import here, otherwise eggs aren't loaded
- import tox
- errcode = tox.cmdline(self.test_args)
- sys.exit(errcode)
-
-
setup(
name=name,
@@ -57,14 +39,15 @@ setup(
description=description,
long_description='\n'.join((long_description, documentation, changelog)),
+ long_description_content_type='text/x-rst',
# The project's main homepage.
url=url,
download_url=ppa + version + '.zip', # noqa
# Author details
- author='Pahaz Blinov',
- author_email='pahaz.blinov@gmail.com',
+ author='Pahaz White',
+ author_email='pahaz.white@gmail.com',
# Choose your license
license='MIT',
@@ -92,6 +75,8 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ 'Programming Language :: Python :: 3.8',
],
platforms=['unix', 'macos', 'windows'],
@@ -112,13 +97,16 @@ setup(
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=[
- 'paramiko>=1.15.2',
+ 'paramiko>=2.7.2',
],
# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
# for example:
# $ pip install -e .[dev,test]
+ tests_require=[
+ 'tox>=1.8.1',
+ ],
extras_require={
'dev': ['check-manifest'],
'test': [
@@ -146,6 +134,4 @@ setup(
]
},
- # Integrate tox with setuptools
- cmdclass={'test': Tox},
)
diff --git a/sshtunnel.egg-info/PKG-INFO b/sshtunnel.egg-info/PKG-INFO
index 6b2e89d..7f02d5c 100644
--- a/sshtunnel.egg-info/PKG-INFO
+++ b/sshtunnel.egg-info/PKG-INFO
@@ -1,22 +1,21 @@
-Metadata-Version: 1.1
+Metadata-Version: 2.1
Name: sshtunnel
-Version: 0.1.4
+Version: 0.4.0
Summary: Pure python SSH tunnels
Home-page: https://github.com/pahaz/sshtunnel
-Author: Pahaz Blinov
-Author-email: pahaz.blinov@gmail.com
+Author: Pahaz White
+Author-email: pahaz.white@gmail.com
License: MIT
-Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.1.4.zip
-Description-Content-Type: UNKNOWN
+Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.zip
Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
|pyversions| |license|
- **Author**: `Pahaz Blinov`_
+ **Author**: `Pahaz`_
**Repo**: https://github.com/pahaz/sshtunnel/
- Inspired by https://github.com/jmagnusson/bgtunnel, but it doesn't work on
+ Inspired by https://github.com/jmagnusson/bgtunnel, which doesn't work on
Windows.
See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
@@ -112,12 +111,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
port.
- .. code-block:: py
+ .. code-block:: python
from sshtunnel import SSHTunnelForwarder
server = SSHTunnelForwarder(
- 'pahaz.urfuclub.ru',
+ 'alfa.8iq.dev',
ssh_username="pahaz",
ssh_password="secret",
remote_bind_address=('127.0.0.1', 8080)
@@ -137,12 +136,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
assuming password protected pkey authentication, remote server's SSH service is
listening on port 443 and that port is open in the firewall (**Fig2**):
- .. code-block:: py
+ .. code-block:: python
import paramiko
- from sshtunnel import SSHTunnelForwarder
+ import sshtunnel
- with SSHTunnelForwarder(
+ with sshtunnel.open_tunnel(
(REMOTE_SERVER_IP, 443),
ssh_username="",
ssh_pkey="/var/ssh/rsa_key",
@@ -164,12 +163,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
Example of a port forwarding for the Vagrant MySQL local port:
- .. code-block:: py
+ .. code-block:: python
- from sshtunnel import SSHTunnelForwarder
+ from sshtunnel import open_tunnel
from time import sleep
- with SSHTunnelForwarder(
+ with open_tunnel(
('localhost', 2222),
ssh_username="vagrant",
ssh_password="vagrant",
@@ -189,6 +188,40 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
(bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
+ Example 4
+ ---------
+
+ Opening an SSH session jumping over two tunnels. SSH transport and tunnels
+ will be daemonised, which will not wait for the connections to stop at close
+ time.
+
+ .. code-block:: python
+
+ import sshtunnel
+ from paramiko import SSHClient
+
+
+ with sshtunnel.open_tunnel(
+ ssh_address_or_host=('GW1_ip', 20022),
+ remote_bind_address=('GW2_ip', 22),
+ ) as tunnel1:
+ print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
+ with sshtunnel.open_tunnel(
+ ssh_address_or_host=('localhost', tunnel1.local_bind_port),
+ remote_bind_address=('target_ip', 22),
+ ssh_username='GW2_user',
+ ssh_password='GW2_pwd',
+ ) as tunnel2:
+ print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
+ with SSHClient() as ssh:
+ ssh.connect('localhost',
+ port=tunnel2.local_bind_port,
+ username='target_user',
+ password='target_pwd',
+ )
+ ssh.exec_command(...)
+
+
CLI usage
=========
@@ -198,11 +231,12 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
[-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
- [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER [FOLDER ...]]]
+ [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
+ [-d [FOLDER [FOLDER ...]]]
ssh_address
Pure python ssh tunnel utils
- Version 0.1.4
+ Version 0.4.0
positional arguments:
ssh_address SSH server IP address (GW for SSH tunnels)
@@ -246,7 +280,7 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
-d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
List of directories where SSH pkeys (in the format `id_*`) may be found
- .. _Pahaz Blinov: https://github.com/pahaz
+ .. _Pahaz: https://github.com/pahaz
.. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
.. _paramiko: http://www.paramiko.org/
.. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
@@ -285,10 +319,45 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
- `Dan Harbin`_
- `Ignacio Peluffo`_
- `Niels Zeilemaker`_
+ - `Georgy Rylov`_
+ - `Eddie Chiang`_
+ - `kkrasovskii`_
CHANGELOG
=========
+ - v.0.4.0 (`Pahaz`_)
+ + Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (`#219`_)
+ + Add docker based end to end functinal tests for Mongo/Postgres/MySQL (`#219`_)
+ + Add docker based end to end hangs tests (`#219`_)
+
+ - v.0.3.2 (`Pahaz`_, `JM Fernández`_)
+ + Fix host key directory detection
+ + Unify default ssh config folder to `~/.ssh`
+
+ - v.0.3.1 (`Pahaz`_)
+ + Increase open connection timeout to 10 secods
+
+ - v.0.3.0 (`Pahaz`_)
+ + Change default with context behavior to use `.stop(force=True)` on exit (is not fully backward compatible)
+ + Remove useless `daemon_forward_servers = True` hack for hangs prevention (is not fully backward compatible)
+ + Set transport keepalive to 5 second by default (disabled for version < 0.3.0)
+ + Set default transport timeout to 0.1
+ + Deprecate and remove `block_on_close` option
+ + Fix "deadlocks" / "tunneling hangs" (`#173`_, `#201`_, `#162`_, `#211`_)
+
+ - v.0.2.2 (`Pahaz`_)
+ + Add `.stop(force=True)` for force close active connections (`#201`_)
+
+ - v.0.2.1 (`Pahaz`_, `Eddie Chiang`_ and `kkrasovskii`_)
+ + Fixes bug with orphan thread for a tunnel that is DOWN (`#170`_)
+
+ - v.0.2.0 (`Georgy Rylov`_)
+ + Support IPv6 without proxy command. Use built-in paramiko create socket logic. The logic tries to use ipv6 socket family first, then ipv4 socket family.
+
+ - v.0.1.5 (`JM Fernández`_)
+ + Introduce `block_on_close` attribute
+
- v.0.1.4 (`Niels Zeilemaker`_)
+ Allow loading pkeys from `~/.ssh`
@@ -305,7 +374,7 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
- v.0.1.0 (`JM Fernández`_)
+ Add `tunnel_bindings` property
+ Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
- (`Pahaz Blinov`_, `JM Fernández`_)
+ (`Pahaz`_, `JM Fernández`_)
+ Add TRACE logging level (`JM Fernández`_)
+ Code and tests refactoring (`JM Fernández`_)
+ Drop python3.2 support
@@ -331,55 +400,56 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
+ Add coverage (`JM Fernández`_)
+ Refactoring (`JM Fernández`_)
- - v.0.0.6 (`Pahaz Blinov`_)
- + add ``-S`` CLI options for ssh private key password support (`Pahaz Blinov`_)
+ - v.0.0.6 (`Pahaz`_)
+ + add ``-S`` CLI options for ssh private key password support (`Pahaz`_)
- - v.0.0.5 (`Pahaz Blinov`_)
+ - v.0.0.5 (`Pahaz`_)
+ add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
+ add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
+ ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
+ fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
- + add tests (`Pahaz Blinov`_)
- + add CI integration (`Pahaz Blinov`_)
- + normal packaging (`Pahaz Blinov`_)
- + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz Blinov`_) [changed default behavior]
- + use daemon mode = False in all threads by default; detail_ (`Pahaz Blinov`_) [changed default behavior]
+ + add tests (`Pahaz`_)
+ + add CI integration (`Pahaz`_)
+ + normal packaging (`Pahaz`_)
+ + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz`_) [changed default behavior]
+ + use daemon mode = False in all threads by default; detail_ (`Pahaz`_) [changed default behavior]
- - v.0.0.4.4 (`Pahaz Blinov`_)
- + fix issue `#24`_ - hide ssh password in logs (`Pahaz Blinov`_)
+ - v.0.0.4.4 (`Pahaz`_)
+ + fix issue `#24`_ - hide ssh password in logs (`Pahaz`_)
- - v.0.0.4.3 (`Pahaz Blinov`_)
- + fix default port issue `#19`_ (`Pahaz Blinov`_)
+ - v.0.0.4.3 (`Pahaz`_)
+ + fix default port issue `#19`_ (`Pahaz`_)
- - v.0.0.4.2 (`Pahaz Blinov`_)
+ - v.0.0.4.2 (`Pahaz`_)
+ fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
- - v.0.0.4.1 (`Pahaz Blinov`_)
- + fix CLI issues `#13`_ (`Pahaz Blinov`_)
+ - v.0.0.4.1 (`Pahaz`_)
+ + fix CLI issues `#13`_ (`Pahaz`_)
- - v.0.0.4 (`Pahaz Blinov`_)
- + daemon mode by default for all threads (`JM Fernández`_, `Pahaz Blinov`_) - *incompatible*
- + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
- + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
+ - v.0.0.4 (`Pahaz`_)
+ + daemon mode by default for all threads (`JM Fernández`_, `Pahaz`_) - *incompatible*
+ + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
+ + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
+ rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
+ add CLI interface (`JM Fernández`_)
+ support opening several tunnels at once (`JM Fernández`_)
- + improve stability and readability (`JM Fernández`_, `Pahaz Blinov`_)
- + improve logging (`JM Fernández`_, `Pahaz Blinov`_)
- + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz Blinov`_)
+ + improve stability and readability (`JM Fernández`_, `Pahaz`_)
+ + improve logging (`JM Fernández`_, `Pahaz`_)
+ + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz`_)
+ add ``ssh_config_file`` argument support (`JM Fernández`_)
- + add Python 3 support (`JM Fernández`_, `Pahaz Blinov`_)
+ + add Python 3 support (`JM Fernández`_, `Pahaz`_)
- - v.0.0.3 (`Pahaz Blinov`_)
+ - v.0.0.3 (`Pahaz`_)
+ add ``threaded`` option (`Cameron Maske`_)
+ fix exception error message, correctly printing destination address (`Gustavo Machado`_)
- + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz Blinov`_)
+ + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz`_)
- - v.0.0.1 (`Pahaz Blinov`_)
- + ``SSHTunnelForwarder`` class (`Pahaz Blinov`_)
- + ``open`` function (`Pahaz Blinov`_)
+ - v.0.0.1 (`Pahaz`_)
+ + ``SSHTunnelForwarder`` class (`Pahaz`_)
+ + ``open`` function (`Pahaz`_)
+ .. _Pahaz: https://github.com/pahaz
.. _Cameron Maske: https://github.com/cameronmaske
.. _Gustavo Machado: https://github.com/gdmachado
.. _Colin Jermain: https://github.com/cjermain
@@ -391,6 +461,9 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
.. _Dan Harbin: https://github.com/RasterBurn
.. _Ignacio Peluffo: https://github.com/ipeluffo
.. _Niels Zeilemaker: https://github.com/NielsZeilemaker
+ .. _Georgy Rylov: https://github.com/g0djan
+ .. _Eddie Chiang: https://github.com/eddie-chiang
+ .. _kkrasovskii: https://github.com/kkrasovskii
.. _#13: https://github.com/pahaz/sshtunnel/issues/13
.. _#16: https://github.com/pahaz/sshtunnel/issues/16
.. _#19: https://github.com/pahaz/sshtunnel/issues/19
@@ -404,6 +477,13 @@ Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
.. _#41: https://github.com/pahaz/sshtunnel/issues/41
.. _#43: https://github.com/pahaz/sshtunnel/issues/43
.. _#46: https://github.com/pahaz/sshtunnel/issues/46
+ .. _#170: https://github.com/pahaz/sshtunnel/issues/170
+ .. _#201: https://github.com/pahaz/sshtunnel/issues/201
+ .. _#162: https://github.com/pahaz/sshtunnel/issues/162
+ .. _#173: https://github.com/pahaz/sshtunnel/issues/173
+ .. _#201: https://github.com/pahaz/sshtunnel/issues/201
+ .. _#211: https://github.com/pahaz/sshtunnel/issues/211
+ .. _#219: https://github.com/pahaz/sshtunnel/issues/219
.. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
Keywords: ssh tunnel paramiko proxy tcp-forward
@@ -420,3 +500,9 @@ Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Description-Content-Type: text/x-rst
+Provides-Extra: dev
+Provides-Extra: test
+Provides-Extra: build_sphinx
diff --git a/sshtunnel.egg-info/SOURCES.txt b/sshtunnel.egg-info/SOURCES.txt
index d96700f..267c5d4 100644
--- a/sshtunnel.egg-info/SOURCES.txt
+++ b/sshtunnel.egg-info/SOURCES.txt
@@ -4,13 +4,26 @@ README.rst
Troubleshoot.rst
changelog.rst
docs.rst
+pyproject.toml
setup.cfg
setup.py
sshtunnel.py
docs/Makefile
docs/conf.py
docs/index.rst
-docs/requirements-docs.txt
+docs/requirements.txt
+e2e_tests/docker-compose.yaml
+e2e_tests/run_docker_e2e_db_tests.py
+e2e_tests/run_docker_e2e_hangs_tests.py
+e2e_tests/ssh-server-config/ssh_host_dsa_key
+e2e_tests/ssh-server-config/ssh_host_dsa_key.pub
+e2e_tests/ssh-server-config/ssh_host_ecdsa_key
+e2e_tests/ssh-server-config/ssh_host_ecdsa_key.pub
+e2e_tests/ssh-server-config/ssh_host_ed25519_key
+e2e_tests/ssh-server-config/ssh_host_ed25519_key.pub
+e2e_tests/ssh-server-config/ssh_host_rsa_key
+e2e_tests/ssh-server-config/ssh_host_rsa_key.pub
+e2e_tests/ssh-server-config/sshd_config
sshtunnel.egg-info/PKG-INFO
sshtunnel.egg-info/SOURCES.txt
sshtunnel.egg-info/dependency_links.txt
@@ -18,7 +31,8 @@ sshtunnel.egg-info/entry_points.txt
sshtunnel.egg-info/requires.txt
sshtunnel.egg-info/top_level.txt
tests/__init__.py
-tests/__init__.pyc
+tests/requirements-syntax.txt
+tests/requirements.txt
tests/test_forwarder.py
tests/testconfig
tests/testrsa.key
diff --git a/sshtunnel.egg-info/requires.txt b/sshtunnel.egg-info/requires.txt
index 948948c..5146a0d 100644
--- a/sshtunnel.egg-info/requires.txt
+++ b/sshtunnel.egg-info/requires.txt
@@ -1,4 +1,4 @@
-paramiko>=1.15.2
+paramiko>=2.7.2
[build_sphinx]
sphinx
diff --git a/sshtunnel.py b/sshtunnel.py
index 9774698..52a23b3 100644
--- a/sshtunnel.py
+++ b/sshtunnel.py
@@ -29,42 +29,43 @@ if sys.version_info[0] < 3: # pragma: no cover
import SocketServer as socketserver
string_types = basestring, # noqa
input_ = raw_input # noqa
-else:
+else: # pragma: no cover
import queue
import socketserver
string_types = str
input_ = input
-__version__ = '0.1.4'
+__version__ = '0.4.0'
__author__ = 'pahaz'
-DEFAULT_LOGLEVEL = logging.ERROR #: default level if no logger passed (ERROR)
-TUNNEL_TIMEOUT = 1.0 #: Timeout (seconds) for tunnel connection
-DAEMON = False
-TRACE_LEVEL = 1
+#: Timeout (seconds) for transport socket (``socket.settimeout``)
+SSH_TIMEOUT = 0.1 # ``None`` may cause a block of transport thread
+#: Timeout (seconds) for tunnel connection (open_channel timeout)
+TUNNEL_TIMEOUT = 10.0
+
+_DAEMON = True #: Use daemon threads in connections
_CONNECTION_COUNTER = 1
_LOCK = threading.Lock()
-#: Timeout (seconds) for the connection to the SSH gateway, ``None`` to disable
-SSH_TIMEOUT = None
-DEPRECATIONS = {
+_DEPRECATIONS = {
'ssh_address': 'ssh_address_or_host',
'ssh_host': 'ssh_address_or_host',
'ssh_private_key': 'ssh_pkey',
'raise_exception_if_any_forwarder_have_a_problem': 'mute_exceptions'
}
+# logging
+DEFAULT_LOGLEVEL = logging.ERROR #: default level if no logger passed (ERROR)
+TRACE_LEVEL = 1
logging.addLevelName(TRACE_LEVEL, 'TRACE')
+DEFAULT_SSH_DIRECTORY = '~/.ssh'
-if os.name == 'posix':
- DEFAULT_SSH_DIRECTORY = '~/.ssh'
- UnixStreamServer = socketserver.UnixStreamServer
-else:
- DEFAULT_SSH_DIRECTORY = '~/ssh'
- UnixStreamServer = socketserver.TCPServer
+_StreamServer = socketserver.UnixStreamServer if os.name == 'posix' \
+ else socketserver.TCPServer
#: Path of optional ssh configuration file
+DEFAULT_SSH_DIRECTORY = '~/.ssh'
SSH_CONFIG_FILE = os.path.join(DEFAULT_SSH_DIRECTORY, 'config')
########################
@@ -190,7 +191,7 @@ def create_logger(logger=None,
:class:`logging.Logger`
"""
logger = logger or logging.getLogger(
- '{0}.SSHTunnelForwarder'.format(__name__)
+ 'sshtunnel.SSHTunnelForwarder'
)
if not any(isinstance(x, logging.Handler) for x in logger.handlers):
logger.setLevel(loglevel or DEFAULT_LOGLEVEL)
@@ -307,23 +308,33 @@ class _ForwardHandler(socketserver.BaseRequestHandler):
if self.request in rqst:
data = self.request.recv(1024)
if not data:
+ self.logger.log(
+ TRACE_LEVEL,
+ '>>> OUT {0} recv empty data >>>'.format(self.info)
+ )
break
- self.logger.log(TRACE_LEVEL,
- '>>> OUT {0} send to {1}: {2} >>>'.format(
- self.info,
- self.remote_address,
- hexlify(data)
- ))
- chan.send(data)
+ self.logger.log(
+ TRACE_LEVEL,
+ '>>> OUT {0} send to {1}: {2} >>>'.format(
+ self.info,
+ self.remote_address,
+ hexlify(data)
+ )
+ )
+ chan.sendall(data)
if chan in rqst: # else
if not chan.recv_ready():
+ self.logger.log(
+ TRACE_LEVEL,
+ '<<< IN {0} recv is not ready <<<'.format(self.info)
+ )
break
data = chan.recv(1024)
self.logger.log(
TRACE_LEVEL,
'<<< IN {0} recv: {1} <<<'.format(self.info, hexlify(data))
)
- self.request.send(data)
+ self.request.sendall(data)
def handle(self):
uid = get_connection_id()
@@ -339,15 +350,12 @@ class _ForwardHandler(socketserver.BaseRequestHandler):
src_addr=src_address,
timeout=TUNNEL_TIMEOUT
)
- except paramiko.SSHException:
- chan = None
- if chan is None:
- msg = '{0} to {1} was rejected by the SSH server'.format(
- self.info,
- self.remote_address
- )
- self.logger.log(TRACE_LEVEL, msg)
- raise HandlerSSHTunnelForwarderError(msg)
+ except Exception as e: # pragma: no cover
+ msg_tupe = 'ssh ' if isinstance(e, paramiko.SSHException) else ''
+ exc_msg = 'open new channel {0}error: {1}'.format(msg_tupe, e)
+ log_msg = '{0} {1}'.format(self.info, exc_msg)
+ self.logger.log(TRACE_LEVEL, log_msg)
+ raise HandlerSSHTunnelForwarderError(exc_msg)
self.logger.log(TRACE_LEVEL, '{0} connected'.format(self.info))
try:
@@ -376,14 +384,23 @@ class _ForwardServer(socketserver.TCPServer): # Not Threading
def __init__(self, *args, **kwargs):
self.logger = create_logger(kwargs.pop('logger', None))
- self.tunnel_ok = queue.Queue()
+ self.tunnel_ok = queue.Queue(1)
socketserver.TCPServer.__init__(self, *args, **kwargs)
def handle_error(self, request, client_address):
(exc_class, exc, tb) = sys.exc_info()
- self.logger.error('Could not establish connection from {0} to remote '
- 'side of the tunnel'.format(request.getsockname()))
- self.tunnel_ok.put(False)
+ local_side = request.getsockname()
+ remote_side = self.remote_address
+ self.logger.error('Could not establish connection from local {0} '
+ 'to remote {1} side of the tunnel: {2}'
+ .format(local_side, remote_side, exc))
+ try:
+ self.tunnel_ok.put(False, block=False, timeout=0.1)
+ except queue.Full:
+ # wait untill tunnel_ok.get is called
+ pass
+ except exc:
+ self.logger.error('unexpected internal error: {0}'.format(exc))
@property
def local_address(self):
@@ -415,18 +432,19 @@ class _ThreadingForwardServer(socketserver.ThreadingMixIn, _ForwardServer):
Allow concurrent connections to each tunnel
"""
# If True, cleanly stop threads created by ThreadingMixIn when quitting
- daemon_threads = DAEMON
+ # This value is overrides by SSHTunnelForwarder.daemon_forward_servers
+ daemon_threads = _DAEMON
-class _UnixStreamForwardServer(UnixStreamServer):
+class _StreamForwardServer(_StreamServer):
"""
- Serve over UNIX domain sockets (does not work on Windows)
+ Serve over domain sockets (does not work on Windows)
"""
def __init__(self, *args, **kwargs):
self.logger = create_logger(kwargs.pop('logger', None))
- self.tunnel_ok = queue.Queue()
- UnixStreamServer.__init__(self, *args, **kwargs)
+ self.tunnel_ok = queue.Queue(1)
+ _StreamServer.__init__(self, *args, **kwargs)
@property
def local_address(self):
@@ -453,13 +471,14 @@ class _UnixStreamForwardServer(UnixStreamServer):
return self.RequestHandlerClass.remote_address[1]
-class _ThreadingUnixStreamForwardServer(socketserver.ThreadingMixIn,
- _UnixStreamForwardServer):
+class _ThreadingStreamForwardServer(socketserver.ThreadingMixIn,
+ _StreamForwardServer):
"""
Allow concurrent connections to each tunnel
"""
# If True, cleanly stop threads created by ThreadingMixIn when quitting
- daemon_threads = DAEMON
+ # This value is overrides by SSHTunnelForwarder.daemon_forward_servers
+ daemon_threads = _DAEMON
class SSHTunnelForwarder(object):
@@ -614,9 +633,8 @@ class SSHTunnelForwarder(object):
host_pkey_directories (list):
Look for pkeys in folders on this list, for example ['~/.ssh'].
- An empty list disables this feature
- Default: ``None``
+ Default: ``None`` (disabled)
.. versionadded:: 0.1.4
@@ -649,9 +667,10 @@ class SSHTunnelForwarder(object):
Interval in seconds defining the period in which, if no data
was sent over the connection, a *'keepalive'* packet will be
sent (and ignored by the remote host). This can be useful to
- keep connections alive over a NAT
+ keep connections alive over a NAT. You can set to 0.0 for
+ disable keepalive.
- Default: 0.0 (no keepalive packets are sent)
+ Default: 5.0 (no keepalive packets are sent)
.. versionadded:: 0.0.7
@@ -720,8 +739,10 @@ class SSHTunnelForwarder(object):
"""
skip_tunnel_checkup = True
- daemon_forward_servers = DAEMON #: flag tunnel threads in daemon mode
- daemon_transport = DAEMON #: flag SSH transport thread in daemon mode
+ # This option affects the `ForwardServer` and all his threads
+ daemon_forward_servers = _DAEMON #: flag tunnel threads in daemon mode
+ # This option affect only `Transport` thread
+ daemon_transport = _DAEMON #: flag SSH transport thread in daemon mode
def local_is_up(self, target):
"""
@@ -747,12 +768,59 @@ class SSHTunnelForwarder(object):
'target can be a valid UNIX domain socket.')
return False
- if self.skip_tunnel_checkup: # force tunnel check at this point
- self.skip_tunnel_checkup = False
- self.check_tunnels()
- self.skip_tunnel_checkup = True # roll it back
+ self.check_tunnels()
return self.tunnel_is_up.get(target, True)
+ def check_tunnels(self):
+ """
+ Check that if all tunnels are established and populates
+ :attr:`.tunnel_is_up`
+ """
+ skip_tunnel_checkup = self.skip_tunnel_checkup
+ try:
+ # force tunnel check at this point
+ self.skip_tunnel_checkup = False
+ for _srv in self._server_list:
+ self._check_tunnel(_srv)
+ finally:
+ self.skip_tunnel_checkup = skip_tunnel_checkup # roll it back
+
+ def _check_tunnel(self, _srv):
+ """ Check if tunnel is already established """
+ if self.skip_tunnel_checkup:
+ self.tunnel_is_up[_srv.local_address] = True
+ return
+ self.logger.info('Checking tunnel to: {0}'.format(_srv.remote_address))
+ if isinstance(_srv.local_address, string_types): # UNIX stream
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ else:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(TUNNEL_TIMEOUT)
+ try:
+ # Windows raises WinError 10049 if trying to connect to 0.0.0.0
+ connect_to = ('127.0.0.1', _srv.local_port) \
+ if _srv.local_host == '0.0.0.0' else _srv.local_address
+ s.connect(connect_to)
+ self.tunnel_is_up[_srv.local_address] = _srv.tunnel_ok.get(
+ timeout=TUNNEL_TIMEOUT * 1.1
+ )
+ self.logger.debug(
+ 'Tunnel to {0} is DOWN'.format(_srv.remote_address)
+ )
+ except socket.error:
+ self.logger.debug(
+ 'Tunnel to {0} is DOWN'.format(_srv.remote_address)
+ )
+ self.tunnel_is_up[_srv.local_address] = False
+
+ except queue.Empty:
+ self.logger.debug(
+ 'Tunnel to {0} is UP'.format(_srv.remote_address)
+ )
+ self.tunnel_is_up[_srv.local_address] = True
+ finally:
+ s.close()
+
def _make_ssh_forward_handler_class(self, remote_address_):
"""
Make SSH Handler class
@@ -766,9 +834,9 @@ class SSHTunnelForwarder(object):
def _make_ssh_forward_server_class(self, remote_address_):
return _ThreadingForwardServer if self._threaded else _ForwardServer
- def _make_unix_ssh_forward_server_class(self, remote_address_):
- return _ThreadingUnixStreamForwardServer if \
- self._threaded else _UnixStreamForwardServer
+ def _make_stream_ssh_forward_server_class(self, remote_address_):
+ return _ThreadingStreamForwardServer if self._threaded \
+ else _StreamForwardServer
def _make_ssh_forward_server(self, remote_address, local_bind_address):
"""
@@ -776,10 +844,9 @@ class SSHTunnelForwarder(object):
"""
_Handler = self._make_ssh_forward_handler_class(remote_address)
try:
- if isinstance(local_bind_address, string_types):
- forward_maker_class = self._make_unix_ssh_forward_server_class
- else:
- forward_maker_class = self._make_ssh_forward_server_class
+ forward_maker_class = self._make_stream_ssh_forward_server_class \
+ if isinstance(local_bind_address, string_types) \
+ else self._make_ssh_forward_server_class
_Server = forward_maker_class(remote_address)
ssh_forward_server = _Server(
local_bind_address,
@@ -826,7 +893,7 @@ class SSHTunnelForwarder(object):
mute_exceptions=False,
remote_bind_address=None,
remote_bind_addresses=None,
- set_keepalive=0.0,
+ set_keepalive=5.0,
threaded=True, # old version False
compression=None,
allow_agent=True, # look for keys from an SSH agent
@@ -1019,16 +1086,21 @@ class SSHTunnelForwarder(object):
keys = SSHTunnelForwarder.get_agent_keys(logger=logger) \
if allow_agent else []
- if host_pkey_directories is not None:
- paramiko_key_types = {'rsa': paramiko.RSAKey,
- 'dsa': paramiko.DSSKey,
- 'ecdsa': paramiko.ECDSAKey,
- 'ed25519': paramiko.Ed25519Key}
- for directory in host_pkey_directories or [DEFAULT_SSH_DIRECTORY]:
- for keytype in paramiko_key_types.keys():
- ssh_pkey_expanded = os.path.expanduser(
- os.path.join(directory, 'id_{}'.format(keytype))
- )
+ if host_pkey_directories is None:
+ host_pkey_directories = [DEFAULT_SSH_DIRECTORY]
+
+ paramiko_key_types = {'rsa': paramiko.RSAKey,
+ 'dsa': paramiko.DSSKey,
+ 'ecdsa': paramiko.ECDSAKey}
+ if hasattr(paramiko, 'Ed25519Key'):
+ # NOQA: new in paramiko>=2.2: http://docs.paramiko.org/en/stable/api/keys.html#module-paramiko.ed25519key
+ paramiko_key_types['ed25519'] = paramiko.Ed25519Key
+ for directory in host_pkey_directories:
+ for keytype in paramiko_key_types.keys():
+ ssh_pkey_expanded = os.path.expanduser(
+ os.path.join(directory, 'id_{}'.format(keytype))
+ )
+ try:
if os.path.isfile(ssh_pkey_expanded):
ssh_pkey = SSHTunnelForwarder.read_private_key_file(
pkey_file=ssh_pkey_expanded,
@@ -1037,11 +1109,12 @@ class SSHTunnelForwarder(object):
)
if ssh_pkey:
keys.append(ssh_pkey)
+ except OSError as exc:
+ if logger:
+ logger.warning('Private key file {0} check error: {1}'
+ .format(ssh_pkey_expanded, exc))
if logger:
- logger.info('{0} keys loaded from host directory'.format(
- len(keys))
- )
-
+ logger.info('{0} key(s) loaded'.format(len(keys)))
return keys
@staticmethod
@@ -1090,7 +1163,7 @@ class SSHTunnelForwarder(object):
logger.warning('Private key file not found: {0}'
.format(ssh_pkey))
if isinstance(ssh_pkey, paramiko.pkey.PKey):
- ssh_loaded_pkeys.append(ssh_pkey)
+ ssh_loaded_pkeys.insert(0, ssh_pkey)
if not ssh_password and not ssh_loaded_pkeys:
raise ValueError('No password or public key available!')
@@ -1112,15 +1185,24 @@ class SSHTunnelForwarder(object):
self.logger.debug('Connecting via proxy: {0}'.format(proxy_repr))
_socket = self.ssh_proxy
else:
- _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ _socket = (self.ssh_host, self.ssh_port)
if isinstance(_socket, socket.socket):
_socket.settimeout(SSH_TIMEOUT)
_socket.connect((self.ssh_host, self.ssh_port))
transport = paramiko.Transport(_socket)
+ sock = transport.sock
+ if isinstance(sock, socket.socket):
+ sock.settimeout(SSH_TIMEOUT)
transport.set_keepalive(self.set_keepalive)
transport.use_compression(compress=self.compression)
transport.daemon = self.daemon_transport
-
+ # try to solve https://github.com/paramiko/paramiko/issues/1181
+ # transport.banner_timeout = 200
+ if isinstance(sock, socket.socket):
+ sock_timeout = sock.gettimeout()
+ sock_info = repr((sock.family, sock.type, sock.proto))
+ self.logger.debug('Transport socket info: {0}, timeout={1}'
+ .format(sock_info, sock_timeout))
return transport
def _create_tunnels(self):
@@ -1177,19 +1259,19 @@ class SSHTunnelForwarder(object):
"""
Processes optional deprecate arguments
"""
- if deprecated_attrib not in DEPRECATIONS:
+ if deprecated_attrib not in _DEPRECATIONS:
raise ValueError('{0} not included in deprecations list'
.format(deprecated_attrib))
if deprecated_attrib in kwargs:
warnings.warn("'{0}' is DEPRECATED use '{1}' instead"
.format(deprecated_attrib,
- DEPRECATIONS[deprecated_attrib]),
+ _DEPRECATIONS[deprecated_attrib]),
DeprecationWarning)
if attrib:
raise ValueError("You can't use both '{0}' and '{1}'. "
"Please only use one of them"
.format(deprecated_attrib,
- DEPRECATIONS[deprecated_attrib]))
+ _DEPRECATIONS[deprecated_attrib]))
else:
return kwargs.pop(deprecated_attrib)
return attrib
@@ -1213,12 +1295,11 @@ class SSHTunnelForwarder(object):
paramiko.Pkey
"""
ssh_pkey = None
- for pkey_class in (key_type,) if key_type else (
- paramiko.RSAKey,
- paramiko.DSSKey,
- paramiko.ECDSAKey,
- paramiko.Ed25519Key
- ):
+ key_types = (paramiko.RSAKey, paramiko.DSSKey, paramiko.ECDSAKey)
+ if hasattr(paramiko, 'Ed25519Key'):
+ # NOQA: new in paramiko>=2.2: http://docs.paramiko.org/en/stable/api/keys.html#module-paramiko.ed25519key
+ key_types += (paramiko.Ed25519Key, )
+ for pkey_class in (key_type,) if key_type else key_types:
try:
ssh_pkey = pkey_class.from_private_key_file(
pkey_file,
@@ -1240,50 +1321,6 @@ class SSHTunnelForwarder(object):
.format(pkey_file, pkey_class))
return ssh_pkey
- def _check_tunnel(self, _srv):
- """ Check if tunnel is already established """
- if self.skip_tunnel_checkup:
- self.tunnel_is_up[_srv.local_address] = True
- return
- self.logger.info('Checking tunnel to: {0}'.format(_srv.remote_address))
- if isinstance(_srv.local_address, string_types): # UNIX stream
- s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
- else:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.settimeout(TUNNEL_TIMEOUT)
- try:
- # Windows raises WinError 10049 if trying to connect to 0.0.0.0
- connect_to = ('127.0.0.1', _srv.local_port) \
- if _srv.local_host == '0.0.0.0' else _srv.local_address
- s.connect(connect_to)
- self.tunnel_is_up[_srv.local_address] = _srv.tunnel_ok.get(
- timeout=TUNNEL_TIMEOUT * 1.1
- )
- self.logger.debug(
- 'Tunnel to {0} is DOWN'.format(_srv.remote_address)
- )
- except socket.error:
- self.logger.debug(
- 'Tunnel to {0} is DOWN'.format(_srv.remote_address)
- )
- self.tunnel_is_up[_srv.local_address] = False
-
- except queue.Empty:
- self.logger.debug(
- 'Tunnel to {0} is UP'.format(_srv.remote_address)
- )
- self.tunnel_is_up[_srv.local_address] = True
- finally:
- s.close()
-
- def check_tunnels(self):
- """
- Check that if all tunnels are established and populates
- :attr:`.tunnel_is_up`
- """
- for _srv in self._server_list:
- self._check_tunnel(_srv)
-
def start(self):
""" Start the SSH tunnels """
if self.is_alive:
@@ -1307,9 +1344,18 @@ class SSHTunnelForwarder(object):
self._raise(HandlerSSHTunnelForwarderError,
'An error occurred while opening tunnels.')
- def stop(self):
+ def stop(self, force=False):
"""
- Shut the tunnel down.
+ Shut the tunnel down. By default we are always waiting until closing
+ all connections. You can use `force=True` to force close connections
+
+ Keyword Arguments:
+ force (bool):
+ Force close current connections
+
+ Default: False
+
+ .. versionadded:: 0.2.2
.. note:: This **had** to be handled with care before ``0.1.0``:
@@ -1330,7 +1376,7 @@ class SSHTunnelForwarder(object):
(address_to_str(k.local_address) for k in self._server_list)
) or 'None'
self.logger.debug('Listening tunnels: ' + opened_address_text)
- self._stop_transport()
+ self._stop_transport(force=force)
self._server_list = [] # reset server list
self.tunnel_is_up = {} # reset tunnel status
@@ -1395,28 +1441,37 @@ class SSHTunnelForwarder(object):
address_to_str(_srv.remote_address))
)
- def _stop_transport(self):
+ def _stop_transport(self, force=False):
""" Close the underlying transport when nothing more is needed """
try:
self._check_is_started()
except (BaseSSHTunnelForwarderError,
HandlerSSHTunnelForwarderError) as e:
self.logger.warning(e)
+ if force and self.is_active:
+ # don't wait connections
+ self.logger.info('Closing ssh transport')
+ self._transport.close()
+ self._transport.stop_thread()
for _srv in self._server_list:
- tunnel = _srv.local_address
- if self.tunnel_is_up[tunnel]:
- self.logger.info('Shutting down tunnel {0}'.format(tunnel))
- _srv.shutdown()
+ status = 'up' if self.tunnel_is_up[_srv.local_address] else 'down'
+ self.logger.info('Shutting down tunnel: {0} <> {1} ({2})'.format(
+ address_to_str(_srv.local_address),
+ address_to_str(_srv.remote_address),
+ status
+ ))
+ _srv.shutdown()
_srv.server_close()
# clean up the UNIX domain socket if we're using one
- if isinstance(_srv, _UnixStreamForwardServer):
+ if isinstance(_srv, _StreamForwardServer):
try:
os.unlink(_srv.local_address)
except Exception as e:
self.logger.error('Unable to unlink socket {0}: {1}'
- .format(self.local_address, repr(e)))
+ .format(_srv.local_address, repr(e)))
self.is_alive = False
if self.is_active:
+ self.logger.info('Closing ssh transport')
self._transport.close()
self._transport.stop_thread()
self.logger.debug('Transport is closed')
@@ -1533,7 +1588,7 @@ class SSHTunnelForwarder(object):
self.ssh_proxy.cmd[1] if self.ssh_proxy else 'no',
self.ssh_username,
credentials,
- self.ssh_host_key if self.ssh_host_key else'not checked',
+ self.ssh_host_key if self.ssh_host_key else 'not checked',
'' if self.is_alive else 'not ',
'disabled' if not self.set_keepalive else
'every {0} sec'.format(self.set_keepalive),
@@ -1556,7 +1611,15 @@ class SSHTunnelForwarder(object):
self.__exit__()
def __exit__(self, *args):
- self._stop_transport()
+ self.stop(force=True)
+
+ def __del__(self):
+ if self.is_active or self.is_alive:
+ self.logger.warning(
+ "It looks like you didn't call the .stop() before "
+ "the SSHTunnelForwarder obj was collected by "
+ "the garbage collector! Running .stop(force=True)")
+ self.stop(force=True)
def open_tunnel(*args, **kwargs):
@@ -1615,8 +1678,15 @@ def open_tunnel(*args, **kwargs):
kwargs
)
- ssh_port = kwargs.pop('ssh_port', None)
+ ssh_port = kwargs.pop('ssh_port', 22)
skip_tunnel_checkup = kwargs.pop('skip_tunnel_checkup', True)
+ block_on_close = kwargs.pop('block_on_close', None)
+ if block_on_close:
+ warnings.warn("'block_on_close' is DEPRECATED. You should use either"
+ " .stop() or .stop(force=True), depends on what you do"
+ " with the active connections. This option has no"
+ " affect since 0.3.0",
+ DeprecationWarning)
if not args:
if isinstance(ssh_address_or_host, tuple):
args = (ssh_address_or_host, )
@@ -1808,7 +1878,7 @@ def _parse_arguments(args=None):
return vars(parser.parse_args(args))
-def _cli_main(args=None):
+def _cli_main(args=None, **extras):
""" Pass input arguments to open_tunnel
Mandatory: ssh_address, -R (remote bind address list)
@@ -1840,6 +1910,9 @@ def _cli_main(args=None):
logging.DEBUG,
TRACE_LEVEL]
arguments.setdefault('debug_level', levels[verbosity])
+ # do this while supporting py27/py34 instead of merging dicts
+ for (extra, value) in extras.items():
+ arguments.setdefault(extra, value)
with open_tunnel(**arguments) as tunnel:
if tunnel.is_alive:
input_('''
diff --git a/tests/__init__.pyc b/tests/__init__.pyc
deleted file mode 100644
index 4d58f5e..0000000
Binary files a/tests/__init__.pyc and /dev/null differ
diff --git a/tests/requirements-syntax.txt b/tests/requirements-syntax.txt
new file mode 100644
index 0000000..8e66e79
--- /dev/null
+++ b/tests/requirements-syntax.txt
@@ -0,0 +1,8 @@
+bashtest
+check-manifest
+docutils
+flake8
+mccabe
+pygments
+readme
+twine
diff --git a/tests/requirements.txt b/tests/requirements.txt
new file mode 100644
index 0000000..f9eba80
--- /dev/null
+++ b/tests/requirements.txt
@@ -0,0 +1,11 @@
+coveralls
+mock
+pytest
+pytest-cov
+pytest-xdist
+twine
+# readme-renderer (required by twine) 25.0 has removed support for Python 3.4
+readme-renderer>21.0,<25.0; python_version == '3.4'
+# try to solve CI problem
+importlib-metadata==1.7.0; python_version == '3.5'
+importlib-metadata==1.1.3; python_version == '3.4'
diff --git a/tests/test_forwarder.py b/tests/test_forwarder.py
index 30cf285..40662d0 100644
--- a/tests/test_forwarder.py
+++ b/tests/test_forwarder.py
@@ -74,6 +74,7 @@ open_tunnel = partial(
ssh_config_file=None,
allow_agent=False,
skip_tunnel_checkup=True,
+ host_pkey_directories=[],
)
# CONSTANTS
@@ -169,23 +170,19 @@ class NullServer(paramiko.ServerInterface):
return paramiko.AUTH_SUCCESSFUL if _ok else paramiko.AUTH_FAILED
def check_channel_request(self, kind, chanid):
- self.log.debug('NullServer.check_channel_request()'
- .format(kind, chanid))
+ self.log.debug('NullServer.check_channel_request()')
return paramiko.OPEN_SUCCEEDED
def check_channel_exec_request(self, channel, command):
- self.log.debug('NullServer.check_channel_exec_request()'
- .format(channel, command))
+ self.log.debug('NullServer.check_channel_exec_request()')
return True
def check_port_forward_request(self, address, port):
- self.log.debug('NullServer.check_port_forward_request()'
- .format(address, port))
+ self.log.debug('NullServer.check_port_forward_request()')
return True
def check_global_request(self, kind, msg):
- self.log.debug('NullServer.check_port_forward_request()'
- .format(kind, msg))
+ self.log.debug('NullServer.check_port_forward_request()')
return True
def check_channel_direct_tcpip_request(self, chanid, origin, destination):
@@ -413,9 +410,9 @@ class SSHClientTest(unittest.TestCase):
self.log.info('<<< forward-server received STOP signal')
except socket.error:
self.log.critical('{0} sending RST'.format(info))
- except Exception as e:
- # we reach this point usually when schan is None (paramiko bug?)
- self.log.critical(repr(e))
+ # except Exception as e:
+ # # we reach this point usually when schan is None (paramiko bug?)
+ # self.log.critical(repr(e))
finally:
if schan:
self.log.debug('{0} closing connection...'.format(info))
@@ -424,7 +421,7 @@ class SSHClientTest(unittest.TestCase):
self.log.debug('{0} connection closed.'.format(info))
def randomize_eport(self):
- return self.eport + random.randint(1, 999)
+ return random.randint(49152, 65535)
def test_echo_server(self):
with self._test_server(
@@ -491,7 +488,8 @@ class SSHClientTest(unittest.TestCase):
remote_bind_address=(self.eaddr, self.eport),
logger=self.log,
ssh_config_file=None,
- allow_agent=False
+ allow_agent=False,
+ host_pkey_directories=[],
)
self.assertEqual(server.ssh_host, self.saddr)
self.assertEqual(server.ssh_port, self.sport)
@@ -693,7 +691,7 @@ class SSHClientTest(unittest.TestCase):
open_tunnel(**_kwargs)
logged_message = "'{0}' is DEPRECATED use '{1}' instead"\
.format(deprecated_arg,
- sshtunnel.DEPRECATIONS[deprecated_arg])
+ sshtunnel._DEPRECATIONS[deprecated_arg])
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertEqual(logged_message, str(w[-1].message))
@@ -713,7 +711,7 @@ class SSHClientTest(unittest.TestCase):
open_tunnel(**_kwargs)
logged_message = "'{0}' is DEPRECATED use '{1}' instead"\
.format(deprecated_arg,
- sshtunnel.DEPRECATIONS[deprecated_arg])
+ sshtunnel._DEPRECATIONS[deprecated_arg])
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertEqual(logged_message, str(w[-1].message))
@@ -961,8 +959,8 @@ class SSHClientTest(unittest.TestCase):
) as server:
self.assertIsInstance(server.local_bind_addresses, list)
self.assertListEqual(server.local_bind_addresses,
- [l for l in zip([self.saddr] * 2,
- server.local_bind_ports)])
+ list(zip([self.saddr] * 2,
+ server.local_bind_ports)))
with self.assertRaises(sshtunnel.BaseSSHTunnelForwarderError):
self.log.info(server.local_bind_address)
@@ -1009,7 +1007,8 @@ class SSHClientTest(unittest.TestCase):
'-R', '{0}:{1}'.format(self.eaddr,
self.eport),
'-c', '',
- '-n'])
+ '-n'],
+ host_pkey_directories=[])
self.stop_echo_and_ssh_server()
@unittest.skipIf(sys.version_info < (2, 7),
@@ -1084,7 +1083,7 @@ class SSHClientTest(unittest.TestCase):
s.close
log = 'send to {0}'.format((self.eaddr, self.eport))
- self.assertTrue(any(log in l for l in
+ self.assertTrue(any(log in msg for msg in
self.sshtunnel_log_messages['trace']))
# set loglevel back to the original value
logger = sshtunnel.create_logger(logger=self.log,
@@ -1125,7 +1124,8 @@ class SSHClientTest(unittest.TestCase):
remote_bind_address=(self.eaddr, self.eport),
logger=self.log,
ssh_config_file=None,
- allow_agent=False
+ allow_agent=False,
+ host_pkey_directories=[],
)
try:
tunnel.daemon_forward_servers = case
@@ -1157,11 +1157,10 @@ class SSHClientTest(unittest.TestCase):
local_bind_address=('', self.randomize_eport()),
logger=self.log
) as server:
-
- keys = server.get_keys(allow_agent=True)
+ keys = server.get_keys(logger=self.log)
self.assertIsInstance(keys, list)
- self.assertTrue(any('keys loaded from agent' in l) for l in
- self.sshtunnel_log_messages['info'])
+ self.assertFalse(any('keys loaded from agent' in msg for msg in
+ self.sshtunnel_log_messages['info']))
with self._test_server(
(self.saddr, self.sport),
@@ -1171,10 +1170,10 @@ class SSHClientTest(unittest.TestCase):
local_bind_address=('', self.randomize_eport()),
logger=self.log
) as server:
- keys = server.get_keys()
+ keys = server.get_keys(logger=self.log, allow_agent=True)
self.assertIsInstance(keys, list)
- self.assertFalse(any('keys loaded from agent' in l for l in
- self.sshtunnel_log_messages['info']))
+ self.assertTrue(any('keys loaded from agent' in msg for msg in
+ self.sshtunnel_log_messages['info']))
tmp_dir = tempfile.mkdtemp()
shutil.copy(get_test_data_path(PKEY_FILE),
@@ -1186,8 +1185,8 @@ class SSHClientTest(unittest.TestCase):
)
self.assertIsInstance(keys, list)
self.assertTrue(
- any('1 keys loaded from host directory' in l
- for l in self.sshtunnel_log_messages['info'])
+ any('1 key(s) loaded' in msg
+ for msg in self.sshtunnel_log_messages['info'])
)
shutil.rmtree(tmp_dir)
@@ -1387,16 +1386,16 @@ class AuxiliaryTest(unittest.TestCase):
'item',
kwargs.copy())
- def check_address(self):
+ def test_check_address(self):
""" Test that an exception is raised with incorrect bind addresses """
address_list = [('10.0.0.1', 10000),
('10.0.0.1', 10001)]
if os.name == 'posix': # UNIX sockets supported by the platform
address_list.append('/tmp/unix-socket')
+ # UNIX sockets not supported on remote addresses
+ with self.assertRaises(AssertionError):
+ sshtunnel.check_addresses(address_list, is_remote=True)
self.assertIsNone(sshtunnel.check_addresses(address_list))
- # UNIX sockets not supported on remote addresses
- with self.assertRaises(AssertionError):
- sshtunnel.check_addresses(address_list, is_remote=True)
with self.assertRaises(ValueError):
sshtunnel.check_address('this is not valid')
with self.assertRaises(ValueError):
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.4.0.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.4.0.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.4.0.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.4.0.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.4.0.egg-info/top_level.txt
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.1.4.egg-info/PKG-INFO -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.1.4.egg-info/dependency_links.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.1.4.egg-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.1.4.egg-info/requires.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/sshtunnel-0.1.4.egg-info/top_level.txt
No differences were encountered in the control files