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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ sysrsync.run(source='/home/user/files',
| sync_source_contents | bool | True | Abstracts the elusive trailing slash behaviour that `source` normally has when using rsync directly, i.e. when a trailing slash is present in `source`, the folder's content is synchronized with destination. When no trailing slash is present, the folder itself is synchronized with destination. |
| options | Optional[Iterable[str]] | None | List of options to be used right after rsync call, e.g. `['-a', '-v']` translates to `rsync -a -v` |
| private_key | Optional[str] | None | Configures an explicit key to be used with rsync --rsh command |
| rsh_port | Optional[int] | None | Specify port to be used for --rsh command |
| strict_host_key_checking | Optional[bool] | None | set StrictHostKeyChecking property for rsh #cf. https://superuser.com/questions/125324/how-can-i-avoid-sshs-host-verification-for-known-hosts |

**returns**: `List[str]` -> the compiled list of commands to be used directly in `subprocess.run`

Expand All @@ -135,4 +137,5 @@ sysrsync.run(source='/home/user/files',
- Make changes
- Lint with `poetry run pylint ./sysrsync`
- Test with `poetry run python -m nose tests/*`
- Run end-to-end tests with `bash end-to-end-tests/run-tests.sh`
- Submit changes with a pull request
28 changes: 28 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
version: "2.1"
services:
openssh-server:
build:
context: ./end-to-end-tests
dockerfile: Dockerfile.ssh-server
container_name: openssh-server
hostname: openssh-server #optional
environment:
PUID: 1000
PGID: 1000
PUBLIC_KEY_FILE: /keys/test-key.pub
SUDO_ACCESS: false
PASSWORD_ACCESS: false
USER_NAME: test
volumes:
- ./end-to-end-tests/keys:/keys:ro
ports:
- 2222:2222
sysrsync-client:
build:
context: ./end-to-end-tests
dockerfile: Dockerfile.sysrsync-client
volumes:
- ./:/app
command: python end-to-end-tests/tests.py
working_dir: /app
3 changes: 3 additions & 0 deletions end-to-end-tests/Dockerfile.ssh-server
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM linuxserver/openssh-server:version-8.8_p1-r1

RUN apk add --no-cache rsync
3 changes: 3 additions & 0 deletions end-to-end-tests/Dockerfile.sysrsync-client
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
FROM python:3.10-alpine3.16

RUN apk add --no-cache rsync openssh
38 changes: 38 additions & 0 deletions end-to-end-tests/keys/test-key
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
1 change: 1 addition & 0 deletions end-to-end-tests/keys/test-key.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC0nNJXgZA6Hd1snjFt6A4Ed6gOaZj564ISS1uHRtX7KML0Gf1RRnlGcuecu+F4V+wNeH3FPskna5rmhdjoiHcOse3/a1mLsXQkOUT4RdAOZp9omW9WzA0j7K4B1sFTySG24xe0alYYWIamXfICwbvnzpC9G+X5GIuCADM3yG8169H5oAsppgguO6mzHxbXMQKACA2I9uIbURTBCWxrBmDR6z5f3YpsM1MyrFg8o8K/Wzob2MqPCgwdseaPmSCmjFniv610uMccB1mrXFgOSOlvHk7szyW9N7tlJnNVvYG9fU7PN+0ztQnOo+Jvd/Z48NCHKIm3GLuKnomniN0p+rm5jTl7s7OZWXJwa+Y9X2Q8ID8XA7KroxYED/4QUgYxMmsSCai695TFl/gU6NKcpwF5ReTPqvQImxOg6P7pyRcVBUJPq+knPFkaG/jtDX7AmmOVGd8SmHKj4Fhx9P5HeW0cQYpfoEpFSRQTkP/xSiSUsAM+f5wMUhfm4JG/vwg+55c= gchamon@github.com
14 changes: 14 additions & 0 deletions end-to-end-tests/run-tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]:-$0}"; )" &> /dev/null && pwd 2> /dev/null; )";

cd $SCRIPT_DIR/..

set -euo pipefail

echo starting ssh server...
docker-compose up --detach openssh-server

echo running tests
docker-compose up --exit-code-from sysrsync-client sysrsync-client

docker-compose down
Empty file.
1 change: 1 addition & 0 deletions end-to-end-tests/test-cases/test_file
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test
32 changes: 32 additions & 0 deletions end-to-end-tests/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import inspect
import os
import sys

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir)

import unittest
import sysrsync


class TestE2E(unittest.TestCase):
def test_send_file(self):
sysrsync.run(source="end-to-end-tests/test-cases/test_file",
destination="/tmp/target_test_file",
destination_ssh="test@openssh-server",
private_key="end-to-end-tests/keys/test-key",
rsh_port=2222,
strict_host_key_checking=False)

def test_send_file_with_spaces(self):
sysrsync.run(source="end-to-end-tests/test-cases/file with spaces",
destination="/tmp/target_test_file",
destination_ssh="test@openssh-server",
private_key="end-to-end-tests/keys/test-key",
rsh_port=2222,
strict_host_key_checking=False)


if __name__ == '__main__':
unittest.main()
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[tool]
[tool.poetry]
name = "sysrsync"
version = "1.0.1"
version = "1.1.0"
description = "Simple and safe python wrapper for calling system rsync"
authors = ["Gabriel Chamon <gchamon@live.com>"]
readme = "README.md"
Expand All @@ -19,4 +19,3 @@ nose = "*"
pylint = "*"
rope = "*"
typed-ast = "<1.5.0,>=1.4.0"

8 changes: 5 additions & 3 deletions sysrsync/command_maker.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ def get_rsync_command(source: str,
exclusions: Optional[Iterable[str]] = None,
sync_source_contents: bool = True,
options: Optional[Iterable[str]] = None,
private_key: Optional[str] = None) -> List[str]:
private_key: Optional[str] = None,
rsh_port: Optional[int] = None,
strict_host_key_checking: Optional[bool] = None) -> List[str]:
if source_ssh is not None and destination_ssh is not None:
raise RemotesError()

Expand All @@ -31,8 +33,8 @@ def get_rsync_command(source: str,
if exclusions
else [])

rsh = (get_rsh_command(private_key)
if private_key is not None
rsh = (get_rsh_command(private_key, rsh_port, strict_host_key_checking)
if any((private_key, rsh_port, strict_host_key_checking))
else [])

if options is None:
Expand Down
26 changes: 20 additions & 6 deletions sysrsync/helpers/rsync.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import os
from pathlib import Path
from typing import Iterable
from typing import Iterable, List, Optional

from sysrsync.exceptions import PrivateKeyError
from sysrsync.helpers.iterators import flatten
Expand All @@ -12,10 +12,24 @@ def get_exclusions(exclusions: Iterable[str]) -> Iterable[str]:
if exclusion != '--exclude'))


def get_rsh_command(private_key: str):
expanded_key_file = os.path.expandvars(os.path.expanduser(private_key))
def get_rsh_command(private_key: Optional[str] = None, port: Optional[int] = None, strict_host_key_checking: Optional[bool] = None):

if not Path(expanded_key_file).exists():
raise PrivateKeyError(expanded_key_file)
args: List[str] = []

return [f"--rsh='ssh -i {expanded_key_file}'"]
if private_key is not None:
expanded_key_file = os.path.expandvars(os.path.expanduser(private_key))

if not Path(expanded_key_file).exists():
raise PrivateKeyError(expanded_key_file)

args.extend(["-i", expanded_key_file])

if port is not None:
args.extend(["-p", str(port)])

if strict_host_key_checking is not None:
args.extend(["-o", f'"StrictHostKeyChecking {"yes" if strict_host_key_checking else "no"}"'])

string_args = " ".join(args)

return ["--rsh", f"ssh {string_args}"]
3 changes: 2 additions & 1 deletion sysrsync/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@

def run(cwd=os.getcwd(), strict=True, verbose=False, **kwargs):
rsync_command = get_rsync_command(**kwargs)

rsync_string = ' '.join(rsync_command)

if verbose is True:
print(f'[sysrsync runner] running command on "{cwd}":')
print(rsync_string)
process = subprocess.run(rsync_string, cwd=cwd, shell=True)
process = subprocess.run(rsync_command, cwd=cwd, shell=False)

if strict is True:
code = process.returncode
Expand Down
Loading