diff --git a/README.md b/README.md index 5f2610e..c08b991 100644 --- a/README.md +++ b/README.md @@ -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` @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..13959ff --- /dev/null +++ b/docker-compose.yml @@ -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 diff --git a/end-to-end-tests/Dockerfile.ssh-server b/end-to-end-tests/Dockerfile.ssh-server new file mode 100644 index 0000000..a06a5a6 --- /dev/null +++ b/end-to-end-tests/Dockerfile.ssh-server @@ -0,0 +1,3 @@ +FROM linuxserver/openssh-server:version-8.8_p1-r1 + +RUN apk add --no-cache rsync diff --git a/end-to-end-tests/Dockerfile.sysrsync-client b/end-to-end-tests/Dockerfile.sysrsync-client new file mode 100644 index 0000000..c300146 --- /dev/null +++ b/end-to-end-tests/Dockerfile.sysrsync-client @@ -0,0 +1,3 @@ +FROM python:3.10-alpine3.16 + +RUN apk add --no-cache rsync openssh diff --git a/end-to-end-tests/keys/test-key b/end-to-end-tests/keys/test-key new file mode 100644 index 0000000..e8c60b0 --- /dev/null +++ b/end-to-end-tests/keys/test-key @@ -0,0 +1,38 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +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 +-----END OPENSSH PRIVATE KEY----- diff --git a/end-to-end-tests/keys/test-key.pub b/end-to-end-tests/keys/test-key.pub new file mode 100644 index 0000000..db46f04 --- /dev/null +++ b/end-to-end-tests/keys/test-key.pub @@ -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 diff --git a/end-to-end-tests/run-tests.sh b/end-to-end-tests/run-tests.sh new file mode 100755 index 0000000..db3f54a --- /dev/null +++ b/end-to-end-tests/run-tests.sh @@ -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 diff --git a/end-to-end-tests/test-cases/file with spaces b/end-to-end-tests/test-cases/file with spaces new file mode 100644 index 0000000..e69de29 diff --git a/end-to-end-tests/test-cases/test_file b/end-to-end-tests/test-cases/test_file new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/end-to-end-tests/test-cases/test_file @@ -0,0 +1 @@ +test diff --git a/end-to-end-tests/tests.py b/end-to-end-tests/tests.py new file mode 100644 index 0000000..7f50b07 --- /dev/null +++ b/end-to-end-tests/tests.py @@ -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() diff --git a/pyproject.toml b/pyproject.toml index 537b192..82b01a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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 "] readme = "README.md" @@ -19,4 +19,3 @@ nose = "*" pylint = "*" rope = "*" typed-ast = "<1.5.0,>=1.4.0" - diff --git a/sysrsync/command_maker.py b/sysrsync/command_maker.py index d60d300..89fb468 100644 --- a/sysrsync/command_maker.py +++ b/sysrsync/command_maker.py @@ -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() @@ -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: diff --git a/sysrsync/helpers/rsync.py b/sysrsync/helpers/rsync.py index 68f0bff..a194737 100644 --- a/sysrsync/helpers/rsync.py +++ b/sysrsync/helpers/rsync.py @@ -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 @@ -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}"] diff --git a/sysrsync/runner.py b/sysrsync/runner.py index 8f5b141..c0b40f4 100644 --- a/sysrsync/runner.py +++ b/sysrsync/runner.py @@ -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 diff --git a/tests/directories_helper.py b/tests/directories_helper.py index af3402d..dc64c08 100644 --- a/tests/directories_helper.py +++ b/tests/directories_helper.py @@ -1,99 +1,96 @@ -from sysrsync.helpers import directories -from nose.tools import eq_ - - -def test_strip_trailing_slash(): - """test strip trailing slash""" - test_dir = '/a/' - expect = '/a' - result = directories.strip_trailing_slash(test_dir) - - eq_(expect, result) - - -def test_skip_strip_trailing_slash(): - """test skip strip trailing slash when not necessary""" - test_dir = '/a' - result = directories.strip_trailing_slash(test_dir) - - eq_(result, test_dir) +import unittest +from sysrsync.helpers import directories -def test_add_trailing_slash(): - """test add trailing slash""" - test_dir = '/a' - expect = '/a/' - result = directories.add_trailing_slash(test_dir) - eq_(expect, result) +class TestDirectoriesHelper(unittest.TestCase): + def test_strip_trailing_slash(self): + """test strip trailing slash""" + test_dir = '/a/' + expect = '/a' + result = directories.strip_trailing_slash(test_dir) + self.assertEqual(expect, result) -def test_skip_add_trailing_slash(): - """test skip add trailing slash when not necessary""" - test_dir = '/a/' - result = directories.add_trailing_slash(test_dir) + def test_skip_strip_trailing_slash(self): + """test skip strip trailing slash when not necessary""" + test_dir = '/a' + result = directories.strip_trailing_slash(test_dir) - eq_(result, test_dir) + self.assertEqual(result, test_dir) + def test_add_trailing_slash(self): + """test add trailing slash""" + test_dir = '/a' + expect = '/a/' + result = directories.add_trailing_slash(test_dir) -def test_sanitize_trailing_slash(): - """test sanitize trailing slash when syncing source contents""" - source, target = '/a', '/b/' - expect_source, expect_target = '/a/', '/b' - result_source, result_target = directories.sanitize_trailing_slash( - source, target) + self.assertEqual(expect, result) - eq_(expect_source, result_source) - eq_(expect_target, result_target) + def test_skip_add_trailing_slash(self): + """test skip add trailing slash when not necessary""" + test_dir = '/a/' + result = directories.add_trailing_slash(test_dir) + self.assertEqual(result, test_dir) -def test_sanitize_trailing_slash_no_action_needed(): - """test sanitize trailing slash when syncing source contents when already sanitized""" - source, target = '/a/', '/b' - expect_source, expect_target = '/a/', '/b' - result_source, result_target = directories.sanitize_trailing_slash( - source, target) + def test_sanitize_trailing_slash(self): + """test sanitize trailing slash when syncing source contents""" + source, target = '/a', '/b/' + expect_source, expect_target = '/a/', '/b' + result_source, result_target = directories.sanitize_trailing_slash( + source, target) - eq_(expect_source, result_source) - eq_(expect_target, result_target) + self.assertEqual(expect_source, result_source) + self.assertEqual(expect_target, result_target) + def test_sanitize_trailing_slash_no_action_needed(self): + """test sanitize trailing slash when syncing source contents when already sanitized""" + source, target = '/a/', '/b' + expect_source, expect_target = '/a/', '/b' + result_source, result_target = directories.sanitize_trailing_slash( + source, target) -def test_sanitize_trailing_slash_whole_source(): - """test sanitize trailing slash when syncing whole source""" - source, target = '/a/', '/b/' - expect_source, expect_target = '/a', '/b' - result_source, result_target = directories.sanitize_trailing_slash( - source, target, sync_sourcedir_contents=False) + self.assertEqual(expect_source, result_source) + self.assertEqual(expect_target, result_target) - eq_(expect_source, result_source) - eq_(expect_target, result_target) + def test_sanitize_trailing_slash_whole_source(self): + """test sanitize trailing slash when syncing whole source""" + source, target = '/a/', '/b/' + expect_source, expect_target = '/a', '/b' + result_source, result_target = directories.sanitize_trailing_slash( + source, target, sync_sourcedir_contents=False) + self.assertEqual(expect_source, result_source) + self.assertEqual(expect_target, result_target) -def test_sanitize_trailing_slash_whole_source_no_action_needed(): - """test sanitize trailing slash when syncing whole source when already sanitized""" - source, target = '/a', '/b/' - expect_source, expect_target = '/a', '/b' - result_source, result_target = directories.sanitize_trailing_slash( - source, target, sync_sourcedir_contents=False) + def test_sanitize_trailing_slash_whole_source_no_action_needed(self): + """test sanitize trailing slash when syncing whole source when already sanitized""" + source, target = '/a', '/b/' + expect_source, expect_target = '/a', '/b' + result_source, result_target = directories.sanitize_trailing_slash( + source, target, sync_sourcedir_contents=False) - eq_(expect_source, result_source) - eq_(expect_target, result_target) + self.assertEqual(expect_source, result_source) + self.assertEqual(expect_target, result_target) + def test_dir_with_ssh(self): + """should compose string with ssh for rsync connection""" + directory = '/a' + ssh = 'host' + expect = 'host:/a' + result = directories.get_directory_with_ssh(directory, ssh) -def test_dir_with_ssh(): - """should compose string with ssh for rsync connection""" - directory = '/a' - ssh = 'host' - expect = 'host:/a' - result = directories.get_directory_with_ssh(directory, ssh) + self.assertEqual(result, expect) - eq_(result, expect) + def test_dir_without_ssh(self): + """should return directory when ssh is None""" + directory = '/a' + ssh = None + result = directories.get_directory_with_ssh(directory, ssh) + self.assertEqual(result, directory) -def test_dir_without_ssh(): - """should return directory when ssh is None""" - directory = '/a' - ssh = None - result = directories.get_directory_with_ssh(directory, ssh) - eq_(result, directory) +if __name__ == '__main__': + unittest.main() diff --git a/tests/iterators_helper.py b/tests/iterators_helper.py index ae5f33f..e50a20a 100644 --- a/tests/iterators_helper.py +++ b/tests/iterators_helper.py @@ -1,26 +1,30 @@ +import unittest + from sysrsync.helpers import iterators -from nose.tools import eq_ -def test_list_flatten(): - list_input = [1, [2, 3], [4]] - expect = [1, 2, 3, 4] - result = iterators.flatten(list_input) +class TestIteratorsHelper(unittest.TestCase): + def test_list_flatten(self): + list_input = [1, [2, 3], [4]] + expect = [1, 2, 3, 4] + result = iterators.flatten(list_input) - eq_(expect, result) + self.assertEqual(expect, result) + def test_tuple_flatten(self): + tuple_input = (1, [2, 3], [4]) + expect = [1, 2, 3, 4] + result = iterators.flatten(tuple_input) -def test_tuple_flatten(): - tuple_input = (1, [2, 3], [4]) - expect = [1, 2, 3, 4] - result = iterators.flatten(tuple_input) + self.assertEqual(expect, result) - eq_(expect, result) + def test_tuples_and_lists_list_flatten(self): + tuple_input = (1, (2, 3), [4]) + expect = [1, 2, 3, 4] + result = iterators.flatten(tuple_input) + self.assertEqual(expect, result) -def test_tuples_and_lists_list_flatten(): - tuple_input = (1, (2, 3), [4]) - expect = [1, 2, 3, 4] - result = iterators.flatten(tuple_input) - eq_(expect, result) +if __name__ == '__main__': + unittest.main() diff --git a/tests/package.py b/tests/package.py new file mode 100644 index 0000000..0f6ce8c --- /dev/null +++ b/tests/package.py @@ -0,0 +1,125 @@ +import unittest +from tempfile import NamedTemporaryFile + +from sysrsync import get_exclusions +from sysrsync.command_maker import get_rsync_command +from sysrsync.exceptions import RemotesError, PrivateKeyError + + +class TestPackage(unittest.TestCase): + def test_get_exclusions(self): + """should map list of exclusions to a list with each element following a --exclude statement""" + exclusions = ['a', 'b'] + expect = ['--exclude', 'a', '--exclude', 'b'] + result = get_exclusions(exclusions) + + self.assertEqual(expect, result) + + def test_get_exclusions_already_in_rsync_format(self): + """should ignore --exclude in exclusions""" + exclusions = ['--exclude', 'a', '--exclude', 'b'] + expect = ['--exclude', 'a', '--exclude', 'b'] + result = get_exclusions(exclusions) + + self.assertEqual(expect, result) + + def test_simple_rsync_command(self): + source = '/a' + target = '/b' + expect = 'rsync /a/ /b'.split() + result = get_rsync_command(source, target) + + self.assertEqual(expect, result) + + def test_rsync_options(self): + source = '/a' + target = '/b' + options = ['-a', '--verbose'] + expect = 'rsync -a --verbose /a/ /b'.split() + result = get_rsync_command(source, target, options=options) + + self.assertEqual(expect, result) + + def test_simple_rsync_command_content_false(self): + source = '/a' + target = '/b' + expect = 'rsync /a /b'.split() + result = get_rsync_command(source, target, sync_source_contents=False) + + self.assertEqual(expect, result) + + def test_rsync_exclusions(self): + source = '/a' + target = '/b' + exclusions = ['file1', 'file2'] + expect = 'rsync /a/ /b --exclude file1 --exclude file2'.split() + result = get_rsync_command(source, target, exclusions=exclusions) + + self.assertEqual(expect, result) + + def test_rsync_exclusions_source_ssh(self): + source = '/a' + source_ssh = 'host1' + target = '/b' + exclusions = ['file1', 'file2'] + expect = 'rsync host1:/a/ /b --exclude file1 --exclude file2'.split() + result = get_rsync_command(source, target, exclusions=exclusions, source_ssh=source_ssh) + + self.assertEqual(expect, result) + + def test_rsync_exclusions_target_ssh(self): + source = '/a' + target_ssh = 'host1' + target = '/b' + exclusions = ['file1', 'file2'] + expect = 'rsync /a/ host1:/b --exclude file1 --exclude file2'.split() + result = get_rsync_command(source, target, exclusions=exclusions, destination_ssh=target_ssh) + + self.assertEqual(expect, result) + + def test_rsync_throws_both_remotes(self): + """raises RemotesError when both source and destination are remotes""" + source_ssh = 'host1' + source = '/a' + target_ssh = 'host2' + target = '/b' + with self.assertRaises(RemotesError): + get_rsync_command(source, target, source_ssh=source_ssh, destination_ssh=target_ssh) + + def test_rsync_private_key(self): + """test if correctly creates rsh option when passing a private key""" + with NamedTemporaryFile() as temp_file: + source_dir = '/home/user/files/' + target_dir = '/home/server/files' + destination_ssh = 'myserver' + temp_file_name = temp_file.name + port = 2222 + strict_host_key_checking = False + expect = ['rsync', + "--rsh", + f'ssh -i {temp_file_name} -p {port} -o "StrictHostKeyChecking no"', + source_dir, + f'{destination_ssh}:{target_dir}'] + actual = get_rsync_command(source=source_dir, + destination=target_dir, + destination_ssh=destination_ssh, + private_key=temp_file_name, + rsh_port=port, + strict_host_key_checking=strict_host_key_checking) + self.assertEqual(expect, actual) + + def test_rsync_private_key_missing(self): + """test if get_rsync_command raises PrivateKeyError when key missing""" + source_dir = '/home/user/files/' + target_dir = '/home/server/files' + destination_ssh = 'myserver' + private_key = 'this_file_does_not_exist' + with self.assertRaises(PrivateKeyError): + get_rsync_command(source=source_dir, + destination=target_dir, + destination_ssh=destination_ssh, + private_key=private_key) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/sysrsync.py b/tests/sysrsync.py deleted file mode 100644 index a027b5e..0000000 --- a/tests/sysrsync.py +++ /dev/null @@ -1,121 +0,0 @@ -from tempfile import TemporaryFile - -from nose.tools import eq_, raises - -from sysrsync import get_exclusions -from sysrsync.command_maker import get_rsync_command -from sysrsync.exceptions import RemotesError, PrivateKeyError - - -def test_get_exclusions(): - """should map list of exclusions to a list with each element following a --exclude statement""" - exclusions = ['a', 'b'] - expect = ['--exclude', 'a', '--exclude', 'b'] - result = get_exclusions(exclusions) - - eq_(expect, result) - - -def test_get_exclusions_already_in_rsync_format(): - """should ignore --exclude in exclusions""" - exclusions = ['--exclude', 'a', '--exclude', 'b'] - expect = ['--exclude', 'a', '--exclude', 'b'] - result = get_exclusions(exclusions) - - eq_(expect, result) - - -def test_simple_rsync_command(): - source = '/a' - target = '/b' - expect = 'rsync /a/ /b'.split() - result = get_rsync_command(source, target) - - eq_(expect, result) - - -def test_rsync_options(): - source = '/a' - target = '/b' - options = ['-a', '--verbose'] - expect = 'rsync -a --verbose /a/ /b'.split() - result = get_rsync_command(source, target, options=options) - - eq_(expect, result) - - -def test_simple_rsync_command_content_false(): - source = '/a' - target = '/b' - expect = 'rsync /a /b'.split() - result = get_rsync_command(source, target, sync_source_contents=False) - - eq_(expect, result) - - -def test_rsync_exclusions(): - source = '/a' - target = '/b' - exclusions = ['file1', 'file2'] - expect = 'rsync /a/ /b --exclude file1 --exclude file2'.split() - result = get_rsync_command(source, target, exclusions=exclusions) - - eq_(expect, result) - - -def test_rsync_exclusions_source_ssh(): - source = '/a' - source_ssh = 'host1' - target = '/b' - exclusions = ['file1', 'file2'] - expect = 'rsync host1:/a/ /b --exclude file1 --exclude file2'.split() - result = get_rsync_command(source, target, exclusions=exclusions, source_ssh=source_ssh) - - eq_(expect, result) - - -def test_rsync_exclusions_target_ssh(): - source = '/a' - target_ssh = 'host1' - target = '/b' - exclusions = ['file1', 'file2'] - expect = 'rsync /a/ host1:/b --exclude file1 --exclude file2'.split() - result = get_rsync_command(source, target, exclusions=exclusions, destination_ssh=target_ssh) - - eq_(expect, result) - - -@raises(RemotesError) -def test_rsync_throws_both_remotes(): - """raises RemotesError when both source and destination are remotes""" - source_ssh = 'host1' - source = '/a' - target_ssh = 'host2' - target = '/b' - get_rsync_command(source, target, source_ssh=source_ssh, destination_ssh=target_ssh) - - -def test_rsync_private_key(): - """test if correctly creates rsh option when passing a private key""" - with TemporaryFile() as temp_file: - source_dir = '/home/user/files/' - target_dir = '/home/server/files' - destination_ssh = 'myserver' - expect = ['rsync', f"--rsh='ssh -i {temp_file}'", source_dir, f'{destination_ssh}:{target_dir}'] - actual = get_rsync_command(source=source_dir, - destination=target_dir, - destination_ssh=destination_ssh, - private_key=temp_file) - eq_(expect, actual) - -@raises(PrivateKeyError) -def test_rsync_private_key(): - """test if get_rsync_command raises PrivateKeyError when key missing""" - source_dir = '/home/user/files/' - target_dir = '/home/server/files' - destination_ssh = 'myserver' - private_key = 'this_file_does_not_exist' - actual = get_rsync_command(source=source_dir, - destination=target_dir, - destination_ssh=destination_ssh, - private_key=private_key)