Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
version: 2.1

orbs:
python: circleci/python@0.3.2

jobs:
python_test:
parameters:
python_ver:
type: string
default: "3.6"
docker:
- image: circleci/python:<< parameters.python_ver >>
steps:
- checkout
- python/load-cache:
dependency-file: requirements_dev.txt
key: depsv3-{{ .Branch }}.{{ arch }}-PY<< parameters.python_ver >>
- run:
name: Deps
command: |
sudo apt-get install openssh-server
- python/save-cache:
dependency-file: requirements_dev.txt
key: depsv3-{{ .Branch }}.{{ arch }}-PY<< parameters.python_ver >>
- run:
command: |
pip install -U -r requirements_dev.txt
name: Build
- run:
command: |
eval "$(ssh-agent -s)"
pytest --cov-append --cov=pssh tests/test_imports.py tests/test_output.py tests/test_utils.py
pytest --reruns 5 --cov-append --cov=pssh tests/miko
pytest --reruns 10 --cov-append --cov=pssh tests/native/test_tunnel.py tests/native/test_agent.py
pytest --reruns 5 --cov-append --cov=pssh tests/native/test_*_client.py
pytest --reruns 5 --cov-append --cov=pssh tests/ssh
flake8 pssh
cd doc; make html; cd ..
# Test building from source distribution
python setup.py sdist
cd dist; pip install *; cd ..
python setup.py check --restructuredtext
name: Test
- run:
command: codecov
name: Coverage

osx:
parameters:
xcode_ver:
type: string
default: "11.6.0"
macos:
xcode: << parameters.xcode_ver >>
environment:
HOMEBREW_NO_AUTO_UPDATE: 1
steps:
- checkout
- run:
name: deps
command: |
pip3 install twine
which twine
- run:
name: Build Wheel
command: |
./ci/osx-wheel.sh
- store_artifacts:
path: wheels
- run:
name: Upload Wheel
command: |
twine upload --skip-existing -u $PYPI_U -p $PYPI_P wheels/*

manylinux:
machine:
image: ubuntu-1604:201903-01
steps:
- checkout
- run:
name: sdist
command: python setup.py sdist
- python/load-cache:
key: manylinuxdepsv6-{{ .Branch }}.{{ arch }}
dependency-file: requirements.txt
- run:
name: Deps
command: |
sudo apt-get install python-pip
pip install -U pip
pip install twine
which twine
- python/save-cache:
key: manylinuxdepsv6-{{ .Branch }}.{{ arch }}
dependency-file: requirements.txt
- run:
name: Build Wheels
command: |
if [[ -z "${CIRCLE_PULL_REQUEST}" ]]; then
echo "$DOCKER_PASSWORD" | docker login -u="$DOCKER_USERNAME" --password-stdin;
fi
./ci/travis/build-manylinux.sh
- run:
name: PyPi Upload
command: |
twine upload --skip-existing -u $PYPI_USER -p $PYPI_PASSWORD dist/* wheelhouse/*

workflows:
version: 2.1
main:
jobs:
- python_test:
matrix:
parameters:
python_ver:
- "3.6"
- "3.7"
- "3.8"
filters:
tags:
ignore: /.*/
- manylinux:
context: Docker
filters:
tags:
only: /.*/
branches:
ignore: /.*/
- osx:
matrix:
parameters:
xcode_ver:
- "11.6.0"
- "11.1.0"
context: Docker
filters:
tags:
only: /.*/
branches:
ignore: /.*/
9 changes: 9 additions & 0 deletions Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
Change Log
============

1.12.2
++++++

Fixes
------

* `ParallelSSHClient.copy_file` with recurse enabled and absolute destination path would create empty directory in home directory of user - #197.
* `ParallelSSHClient.copy_file` and `scp_recv` with recurse enabled would not create remote directories when copying empty local directories.

1.12.1
++++++

Expand Down
1 change: 1 addition & 0 deletions pssh/clients/base/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ def mkdir(self, sftp, directory, _parent_path=None):
def _copy_dir(self, local_dir, remote_dir, sftp):
"""Call copy_file on every file in the specified directory, copying
them to the specified remote directory."""
self.mkdir(sftp, remote_dir)
file_list = os.listdir(local_dir)
for file_name in file_list:
local_path = os.path.join(local_dir, file_name)
Expand Down
45 changes: 21 additions & 24 deletions pssh/clients/native/single.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import logging
import os
from collections import deque
from warnings import warn

from gevent import sleep, spawn
Expand Down Expand Up @@ -325,8 +326,7 @@ def _mkdir(self, sftp, directory):
raise SFTPIOError(msg, directory, self.host, error)
logger.debug("Created remote directory %s", directory)

def copy_file(self, local_file, remote_file, recurse=False,
sftp=None, _dir=None):
def copy_file(self, local_file, remote_file, recurse=False, sftp=None):
"""Copy local file to host via SFTP.

:param local_file: Local filepath to copy to remote host
Expand Down Expand Up @@ -383,7 +383,7 @@ def sftp_put(self, sftp, local_file, remote_file):
logger.error(msg, remote_file, ex)
raise SFTPIOError(msg, remote_file, ex)

def mkdir(self, sftp, directory, _parent_path=None):
def mkdir(self, sftp, directory):
"""Make directory via SFTP channel.

Parent paths in the directory are created if they do not exist.
Expand All @@ -395,27 +395,20 @@ def mkdir(self, sftp, directory, _parent_path=None):

Catches and logs at error level remote IOErrors on creating directory.
"""
try:
_dir, sub_dirs = directory.split('/', 1)
except ValueError:
_dir = directory.split('/', 1)[0]
sub_dirs = None
if not _dir and directory.startswith('/'):
_paths_to_create = deque()
for d in directory.split('/'):
if not d:
continue
_paths_to_create.append(d)
cwd = '' if directory.startswith('/') else '.'
while _paths_to_create:
cur_dir = _paths_to_create.popleft()
cwd = '/'.join([cwd, cur_dir])
try:
_dir, sub_dirs = sub_dirs.split(os.path.sep, 1)
except ValueError:
return True
if _parent_path is not None:
_dir = '/'.join((_parent_path, _dir))
try:
self._eagain(sftp.stat, _dir)
except (SFTPHandleError, SFTPProtocolError) as ex:
logger.debug("Stat for %s failed with %s", _dir, ex)
self._mkdir(sftp, _dir)
if sub_dirs is not None:
if directory.startswith('/'):
_dir = ''.join(('/', _dir))
return self.mkdir(sftp, sub_dirs, _parent_path=_dir)
self._eagain(sftp.stat, cwd)
except (SFTPHandleError, SFTPProtocolError) as ex:
logger.debug("Stat for %s failed with %s", cwd, ex)
self._mkdir(sftp, cwd)

def copy_remote_file(self, remote_file, local_file, recurse=False,
sftp=None, encoding='utf-8'):
Expand Down Expand Up @@ -470,7 +463,7 @@ def scp_recv(self, remote_file, local_file, recurse=False, sftp=None,
encoding='utf-8'):
"""Copy remote file to local host via SCP.

Note - Remote directory listings are gather via SFTP when
Note - Remote directory listings are gathered via SFTP when
``recurse`` is enabled - SCP lacks directory list support.
Enabling recursion therefore involves creating an extra SFTP channel
and requires SFTP support on the server.
Expand Down Expand Up @@ -504,6 +497,10 @@ def scp_recv(self, remote_file, local_file, recurse=False, sftp=None,
except SFTPError:
pass
else:
try:
os.makedirs(local_file)
except OSError:
pass
file_list = self._sftp_readdir(dir_h)
return self._scp_recv_dir(file_list, remote_file,
local_file, sftp,
Expand Down
Loading