From 043506bbc2a30a865a15629900e225a00b710d76 Mon Sep 17 00:00:00 2001 From: simonhammes Date: Sat, 2 Nov 2024 02:15:17 +0100 Subject: [PATCH 1/3] Fix tilde expansion for LocalConnector.put_file() Fixes #1235 --- pyinfra/connectors/local.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyinfra/connectors/local.py b/pyinfra/connectors/local.py index d7a32b722..dd08f0c1c 100644 --- a/pyinfra/connectors/local.py +++ b/pyinfra/connectors/local.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Tuple import click +import shlex from typing_extensions import Unpack from pyinfra import logger @@ -118,6 +119,12 @@ def put_file( bool: Indicating success or failure """ + if remote_filename.startswith("~/"): + # Do not quote leading tilde to ensure that it gets properly expanded by the shell + remote_filename = f"~/{shlex.quote(remote_filename[2:])}" + else: + remote_filename = QuoteString(remote_filename) + _, temp_filename = mkstemp() try: @@ -133,7 +140,7 @@ def put_file( # Copy the file using `cp` such that we support sudo/su status, output = self.run_shell_command( - StringCommand("cp", temp_filename, QuoteString(remote_filename)), + StringCommand("cp", temp_filename, remote_filename), print_output=print_output, print_input=print_input, **arguments, @@ -146,6 +153,7 @@ def put_file( if print_output: click.echo( + # TODO: Check if the modification of remote_filename affects the output "{0}file copied: {1}".format(self.host.print_prefix, remote_filename), err=True, ) From a48fdff7197933a160ea96172afa8e71fc4bb056 Mon Sep 17 00:00:00 2001 From: simonhammes Date: Sat, 2 Nov 2024 02:26:33 +0100 Subject: [PATCH 2/3] Add tests for local connector --- tests/test_connectors/test_local.py | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test_connectors/test_local.py b/tests/test_connectors/test_local.py index bb52095ad..4080135a1 100644 --- a/tests/test_connectors/test_local.py +++ b/tests/test_connectors/test_local.py @@ -158,6 +158,44 @@ def test_put_file_with_spaces(self): stdin=PIPE, ) + def test_put_file_tilde_expansion(self): + inventory = make_inventory(hosts=("@local",)) + State(inventory, Config()) + + host = inventory.get_host("@local") + + fake_process = MagicMock(returncode=0) + self.fake_popen_mock.return_value = fake_process + + host.put_file("not-a-file", "~/file-in-home-directory", print_output=True) + + self.fake_popen_mock.assert_called_with( + "sh -c 'cp __tempfile__ ~/file-in-home-directory'", + shell=True, + stdout=PIPE, + stderr=PIPE, + stdin=PIPE, + ) + + def test_put_file_tilde_expansion_with_spaces(self): + inventory = make_inventory(hosts=("@local",)) + State(inventory, Config()) + + host = inventory.get_host("@local") + + fake_process = MagicMock(returncode=0) + self.fake_popen_mock.return_value = fake_process + + host.put_file("not-a-file", "~/not another file with spaces", print_output=True) + + self.fake_popen_mock.assert_called_with( + "sh -c 'cp __tempfile__ ~/'\"'\"'not another file with spaces'\"'\"''", + shell=True, + stdout=PIPE, + stderr=PIPE, + stdin=PIPE, + ) + def test_put_file_error(self): inventory = make_inventory(hosts=("@local",)) State(inventory, Config()) From 0c89c25f25e992a9380a6641f1064720d5d251a8 Mon Sep 17 00:00:00 2001 From: simonhammes Date: Sat, 2 Nov 2024 02:28:13 +0100 Subject: [PATCH 3/3] Fix CS --- pyinfra/connectors/local.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyinfra/connectors/local.py b/pyinfra/connectors/local.py index dd08f0c1c..df4fd0169 100644 --- a/pyinfra/connectors/local.py +++ b/pyinfra/connectors/local.py @@ -1,10 +1,10 @@ import os +import shlex from shutil import which from tempfile import mkstemp from typing import TYPE_CHECKING, Tuple import click -import shlex from typing_extensions import Unpack from pyinfra import logger