Skip to content

Commit 8283765

Browse files
author
Pan
committed
Added CI scripts for building system packages and binary wheels.
Migrated ssh2 client line parsing code to Cython. Updated readme. Updated test imports. Added monkey patching to paramiko single host client as well as parallel client
1 parent bf7d1fa commit 8283765

File tree

25 files changed

+4417
-1356
lines changed

25 files changed

+4417
-1356
lines changed

.travis.yml

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,108 @@
11
language: python
22
cache: pip
3-
sudo: false
3+
sudo: required
44
notifications:
55
email: false
6+
services:
7+
- docker
68
python:
7-
- 2.6
89
- 2.7
910
- 3.4
1011
- 3.5
1112
- 3.6
12-
matrix:
13-
allow_failures:
14-
- python: 2.6
13+
addons:
14+
apt:
15+
packages:
16+
- libssh2-1-dev
17+
- openssh-server
18+
- rpm
19+
- dpkg
1520
before_install:
1621
- pip install -U pip setuptools
1722
install:
1823
- pip install -r requirements_dev.txt
1924
script:
2025
- nosetests --with-coverage --cover-package=pssh
21-
# - flake8 pssh
22-
- cd doc; make html
23-
before_deploy:
24-
- cd $TRAVIS_BUILD_DIR
26+
- flake8 pssh
27+
- cd doc; make html; cd ..
2528
after_success:
2629
- codecov
27-
deploy:
28-
provider: pypi
29-
user: pkittenis
30-
password:
31-
secure: ZQJ41Nguc7Y5XHvtN8lITIiW1S1jvy0p50rssMUJpa9wVZIh0HcW0K/Xv2v17fDNuOvQlVbsF0sY/BmcRfH7c7nzwt7fRXGOjXbZk5djqwusKXL6zlVN7OKjAY6j2EByOjD9UpDDkB5tDqb4lRBCX87wknii/t+7/8P0ddoBojM=
32-
on:
33-
tags: true
34-
repo: ParallelSSH/parallel-ssh
35-
distributions: sdist bdist_wheel
36-
skip_upload_docs: true
30+
jobs:
31+
include:
32+
- os: osx
33+
# if: tag IS present OR branch = master
34+
# tag =~ ^\d+\.\d+(\.\d+)?(-\S*)?$
35+
before_install: skip
36+
install:
37+
- brew install libssh2
38+
- pip install -U delocate twine wheel pip setuptools
39+
- pip install -r requirements.txt
40+
- python setup.py bdist_wheel
41+
script:
42+
- delocate-listdeps --all dist/*.whl
43+
- delocate-wheel -v dist/*.whl
44+
- delocate-listdeps --all dist/*.whl
45+
- ls -l dist/
46+
- brew uninstall libssh2
47+
- pip install -v dist/*.whl
48+
- pwd; mkdir temp; cd temp; pwd
49+
- python -c "import pssh.native.ssh2"
50+
- cd ..; pwd
51+
after_success:
52+
- if [[ ! -z "$TRAVIS_TAG" ]]; then
53+
twine upload -u $PYPI_U -p $PYPI_P dist/*.whl;
54+
fi
55+
language: generic
56+
python: skip
57+
- stage: build_packages
58+
os: linux
59+
python: 3.6
60+
before_install: skip
61+
install: skip
62+
script: skip
63+
after_success: skip
64+
before_deploy:
65+
- docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD"
66+
- ./ci/docker/build-packages.sh
67+
deploy:
68+
- provider: releases
69+
skip_cleanup: true
70+
api_key:
71+
secure: hKf+D9ZWRCJWNQtlOWeFh7z1a+VSz+GK5qOY0e1+iV/PrM0f41wy2yej0bxG1zS6CQAnJBK6/gmq5uXXhQhGNQeIQs7zElyKlrijQAn5UstPPJTRIk2oywRr2b+q0k3V42tto6WbhjqPRpOQl/pNTjKJCc/UPgd6kOVZEhCfAec=
72+
file_glob: true
73+
file: '*.{deb,rpm}'
74+
on:
75+
repo: ParallelSSH/parallel-ssh
76+
tags: true
77+
- stage: deploy_pypi
78+
os: linux
79+
python: 3.6
80+
before_install: skip
81+
install: skip
82+
script: skip
83+
after_success: skip
84+
deploy:
85+
- provider: pypi
86+
user: pkittenis
87+
password:
88+
secure: ZQJ41Nguc7Y5XHvtN8lITIiW1S1jvy0p50rssMUJpa9wVZIh0HcW0K/Xv2v17fDNuOvQlVbsF0sY/BmcRfH7c7nzwt7fRXGOjXbZk5djqwusKXL6zlVN7OKjAY6j2EByOjD9UpDDkB5tDqb4lRBCX87wknii/t+7/8P0ddoBojM=
89+
on:
90+
repo: ParallelSSH/parallel-ssh
91+
tags: true
92+
distributions: sdist
93+
skip_upload_docs: true
94+
skip_cleanup: true
95+
- stage: build wheels
96+
os: linux
97+
python: 3.6
98+
before_install: skip
99+
install:
100+
- pip install twine
101+
script:
102+
- if [[ ! -z "$TRAVIS_TAG" ]]; then
103+
echo "Building wheels for tag ${TRAVIS_TAG}" &&
104+
docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" &&
105+
./ci/travis/build-manylinux.sh;
106+
fi
107+
after_success:
108+
- twine upload -u $PYPI_U -p $PYPI_P wheelhouse/*.whl

README.rst

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
parallel-ssh
33
============
44

5-
Asynchronous parallel SSH client library.
5+
Non-blocking, asynchronous parallel SSH client library.
66

77
Run SSH commands over many - hundreds/hundreds of thousands - number of servers asynchronously and with minimal system load on the client host.
88

9+
Native code based client with extremely high performance - based on ``libssh2`` C library.
10+
911
.. image:: https://img.shields.io/badge/License-LGPL%20v2-blue.svg
1012
:target: https://pypi.python.org/pypi/parallel-ssh
1113
:alt: License
@@ -38,56 +40,78 @@ Usage Example
3840

3941
See documentation on `read the docs`_ for more complete examples.
4042

41-
42-
Run ``ls`` on two remote hosts in parallel with ``sudo``.
43+
Run ``uname`` on two remote hosts in parallel with ``sudo``.
4344

4445
.. code-block:: python
4546
46-
from pprint import pprint
4747
from pssh.pssh_client import ParallelSSHClient
4848
4949
hosts = ['myhost1', 'myhost2']
5050
client = ParallelSSHClient(hosts)
5151
52-
output = client.run_command('ls -ltrh /tmp/', sudo=True)
53-
pprint(output)
52+
output = client.run_command('uname')
53+
for host, host_output in output.items():
54+
for line in host_output.stdout:
55+
print(line)
5456
5557
:Output:
5658

57-
.. code-block:: python
59+
.. code-block:: shell
60+
61+
Linux
62+
Linux
63+
64+
*******************
65+
Native code client
66+
*******************
5867

59-
{'myhost1':
60-
host=myhost1
61-
cmd=<Greenlet>
62-
channel=<channel>
63-
stdout=<generator>
64-
stderr=<generator>
65-
stdin=<channel>
66-
exception=None
67-
'myhost2':
68-
<..>
69-
}
68+
As of version ``1.2.0``, a new client is supported in ``ParallelSSH`` which offers much greater performance and reduced overhead than the current default client (paramiko). Binary wheel packages with ``libssh2`` included are provided for Linux, OSX and Windows platforms and all supported Python versions.
7069

71-
Standard output buffers are available in output object. Iterating on them can be used to get output as it becomes available. Iteration ends *only when command has finished*, though it may be interrupted and resumed at any point.
70+
The new client is based on ``libssh2`` via the ``ssh2-python`` extension library and supports non-blocking mode natively. In addition, SFTP push/pull operations in the new client have also been implemented in native code without Python's GIL, allowing for much greater performance and significantly reduced overhead.
7271

73-
`Host output <http://parallel-ssh.readthedocs.io/en/latest/output.html>`_ attributes are available in host output object, for example ``output['myhost1'].stdout``.
72+
See < here > for a performance comparison of the two clients.
73+
74+
To make use of this new client, ``ParallelSSHClient`` can be imported from ``pssh.pssh2_client`` instead of ``pssh.pssh_client``. The respective APIs are almost identical, though some things have either not yet been implemented or are not supported in ``libssh2``.
75+
76+
Note that the new client will become the default and will replace the current ``pssh.pssh_client`` in a new major version of the library - ``2.x.x`` - once remaining features have been implemented.
77+
78+
For example:
7479

7580
.. code-block:: python
7681
77-
for host in output:
78-
for line in output[host].stdout:
79-
pprint("Host %s - output: %s" % (host, line))
82+
from pprint import pprint
83+
from pssh.pssh2_client import ParallelSSHClient
8084
81-
:Output:
85+
hosts = ['myhost1', 'myhost2']
86+
client = ParallelSSHClient(hosts)
87+
88+
output = client.run_command('uname')
89+
for host, host_output in output.items():
90+
for line in host_output.stdout:
91+
print(line)
92+
93+
94+
Compared to the current default, the native client currently lacks proxying/tunnelling implementation, as well as SSH agent forwarding. The latter is not currently supported by ``libssh2``.
95+
96+
See documentation for more information on how the two clients compare feature
8297

83-
.. code-block:: shell
8498

85-
Host myhost1 - output: drwxr-xr-x 6 xxx xxx 4.0K Jan 1 00:00 xxx
86-
Host myhost1 - output: <..>
87-
Host myhost2 - output: drwxr-xr-x 6 xxx xxx 4.0K Jan 1 00:00 xxx
88-
Host myhost2 - output: <..>
99+
****************************
100+
Native Code Client Features
101+
****************************
89102

90-
Exit codes become available once output is iterated on to completion *or* ``client.join(output)`` is called.
103+
* Highest performance and least overhead of any currently available Python SSH libraries
104+
* Native non-blocking client based on ``libssh2`` via the ``ssh2-python`` wrapper
105+
* Thread safe - utilises both native threads for blocking calls like authentication and non-blocking network requests
106+
* Natively non-blocking - **no monkey patching of the Python standard library**
107+
* Native binary-like SFTP speeds thanks to SFTP and local file read/write operations being implemented in native code
108+
109+
110+
***********
111+
Exit codes
112+
***********
113+
114+
Once either standard output is iterated on *to completion*, or ``client.join(output)`` is called, exit codes become available in host output. Iteration ends *only when remote command has completed*, though it may be interrupted and resumed at any point.
91115

92116
.. code-block:: python
93117
@@ -100,6 +124,7 @@ Exit codes become available once output is iterated on to completion *or* ``clie
100124
0
101125
0
102126
127+
103128
The client's ``join`` function can be used to block and wait for all parallel commands to finish:
104129

105130
.. code-block:: python
@@ -112,13 +137,15 @@ Similarly, output and exit codes are available after ``client.join`` is called:
112137
113138
output = client.run_command('exit 0')
114139
115-
# Block and gather exit codes. Output is updated in-place
140+
# Wait for commands to complete and gather exit codes.
141+
# Output is updated in-place.
116142
client.join(output)
117143
pprint(output.values()[0].exit_code)
118144
119-
# Output is available
120-
for line in output.values()[0].stdout:
121-
pprint(line)
145+
# Output remains available in output generators
146+
for host, host_output in output.items():
147+
for line in host_output.stdout:
148+
pprint(line)
122149
123150
:Output:
124151
.. code-block:: python
@@ -130,12 +157,14 @@ Similarly, output and exit codes are available after ``client.join`` is called:
130157

131158
In versions prior to ``1.0.0`` only, ``client.join`` would consume standard output.
132159

133-
There is also a built in host logger that can be enabled to log output from remote hosts. The helper function ``pssh.utils.enable_host_logger`` will enable host logging to stdout, for example:
160+
There is also a built in host logger that can be enabled to log output from remote hosts. The helper function ``pssh.utils.enable_host_logger`` will enable host logging to stdout.
161+
162+
To log output without having to iterate over standard output generators, the ``consume_output`` flag can be enabled, for example:
134163

135164
.. code-block:: python
136165
137-
import pssh.utils
138-
pssh.utils.enable_host_logger()
166+
from pssh.utils import enable_host_logger
167+
enable_host_logger()
139168
client.join(client.run_command('uname'), consume_output=True)
140169
141170
:Output:
@@ -147,9 +176,9 @@ There is also a built in host logger that can be enabled to log output from remo
147176
Design And Goals
148177
*****************
149178

150-
``ParallelSSH``'s design goals and motivation are to provide a *library* for running *asynchronous* SSH commands in parallel with little to no load induced on the system by doing so with the intended usage being completely programmatic and non-interactive.
179+
``ParallelSSH``'s design goals and motivation are to provide a *library* for running *non-blocking* asynchronous SSH commands in parallel with little to no load induced on the system by doing so with the intended usage being completely programmatic and non-interactive.
151180

152-
To meet these goals, API driven solutions are preferred first and foremost. This frees up the developer to drive the library via any method desired, be that environment variables, CI driven tasks, command line tools, existing OpenSSH or new configuration files, from within an application et al.
181+
To meet these goals, API driven solutions are preferred first and foremost. This frees up developers to drive the library via any method desired, be that environment variables, CI driven tasks, command line tools, existing OpenSSH or new configuration files, from within an application et al.
153182

154183
********
155184
Scaling
@@ -178,20 +207,21 @@ Output *generation* is done remotely and has no effect on the event loop until o
178207
SFTP/SCP
179208
********
180209

181-
SFTP is supported (SCP version 2) natively, no ``scp`` binary required.
210+
SFTP is supported natively, no ``scp`` binary required.
182211

183212
For example to copy a local file to remote hosts in parallel:
184213

185214
.. code-block:: python
186215
187-
from pssh import ParallelSSHClient, utils
216+
from pssh.pssh_client import ParallelSSHClient
217+
from pssh import utils
188218
from gevent import joinall
189219
190220
utils.enable_logger(utils.logger)
191221
hosts = ['myhost1', 'myhost2']
192222
client = ParallelSSHClient(hosts)
193-
greenlets = client.copy_file('../test', 'test_dir/test')
194-
joinall(greenlets, raise_error=True)
223+
cmds = client.copy_file('../test', 'test_dir/test')
224+
joinall(cmds, raise_error=True)
195225
196226
:Output:
197227
.. code-block:: python
@@ -221,9 +251,9 @@ Frequently asked questions
221251

222252
``ParallelSSH`` is in other words well suited to be the SSH client tools like Fabric and Ansible and others use to run their commands rather than a direct replacement for.
223253

224-
By focusing on providing a well defined, lightweight - actual code is a few hundred lines - library, ``ParallelSSH`` is far better suited for *run this command on X number of hosts* tasks for which frameworks like Fabric, Capistrano and others are overkill and unsuprisignly, as it is not what they are for, ill-suited to and do not perform particularly well with.
254+
By focusing on providing a well defined, lightweight - actual code is a few hundred lines - library, ``ParallelSSH`` is far better suited for *run this command on these hosts* tasks for which frameworks like Fabric, Capistrano and others are overkill and unsuprisignly, as it is not what they are for, ill-suited to and do not perform particularly well with.
225255

226-
Fabric and tools like it are high level deployment frameworks - as opposed to general purpose libraries - for building deployment tasks to perform on hosts matching a role with task chaining, a DSL like syntax and are primarily intended for command line use for which the framework is a good fit for - very far removed from an SSH client *library*.
256+
Fabric and tools like it are high level deployment frameworks - as opposed to general purpose libraries - for building deployment tasks to perform on hosts matching a role with task chaining, a DSL like syntax and are primarily intended for command line use - very far removed from an SSH client *library*.
227257

228258
Fabric in particular is a port of `Capistrano <https://github.com/capistrano/capistrano>`_ from Ruby to Python. Its design goals are to provide a faithful port of Capistrano with its `tasks` and `roles` framework to python with interactive command line being the intended usage.
229259

ci/docker/build-packages.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/bin/bash -xe
2+
3+
for x in `ls -1d ci/docker/{fedora,centos}*`; do
4+
name=`echo "$x" | awk -F/ '{print $3}'`
5+
dist_num=`echo "$name" | sed -r 's/[a-z]+([0-9]+)/\1/'`
6+
docker_tag="parallelssh/ssh2-python:$name"
7+
if [[ $dist_num -gt 20 ]]; then
8+
dist="fc${dist_num}"
9+
else
10+
dist="el${dist_num}"
11+
fi
12+
docker pull $docker_tag || echo
13+
docker build --cache-from $docker_tag $x -t $name
14+
docker tag $name $docker_tag
15+
docker push $docker_tag
16+
sudo rm -rf build dist
17+
docker run -v "$(pwd):/src/" "$name" --rpm-dist $dist -s python -t rpm setup.py
18+
done
19+
20+
for x in `ls -1d ci/docker/{debian,ubuntu}*`; do
21+
name=`echo "$x" | awk -F/ '{print $3}' | awk -F. '{print $1}'`
22+
docker_tag="parallelssh/ssh2-python:$name"
23+
docker pull $docker_tag || echo
24+
docker build --cache-from $docker_tag $x -t $name
25+
docker tag $name $docker_tag
26+
docker push $docker_tag
27+
sudo rm -rf build dist
28+
docker run -v "$(pwd):/src/" "$name" --iteration $name -s python -t deb setup.py
29+
done
30+
31+
sudo chown -R ${USER} *
32+
33+
ls -ltrh *.{rpm,deb}
34+
35+
for x in *.rpm; do
36+
echo "Package: $x"
37+
rpm -qlp $x
38+
done
39+
40+
for x in *.deb; do
41+
echo "Package: $x"
42+
dpkg-deb -c $x
43+
done

ci/docker/centos6/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM cdrx/fpm-centos:6
2+
3+
RUN yum -y install libssh2-devel python-devel python-setuptools git
4+
RUN curl -sLO https://bootstrap.pypa.io/get-pip.py && python get-pip.py && rm -f get-pip.py && pip install -U setuptools wheel

ci/docker/centos7/Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
FROM cdrx/fpm-centos:7
2+
3+
RUN yum -y install libssh2-devel python-devel python-setuptools git
4+
RUN curl -sLO https://bootstrap.pypa.io/get-pip.py && python get-pip.py && rm -f get-pip.py && pip install -U setuptools wheel

0 commit comments

Comments
 (0)