pyinfra 2.9.1__py2.py3-none-any.whl → 3.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyinfra/api/__init__.py +3 -0
- pyinfra/api/arguments.py +265 -253
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +68 -53
- pyinfra/api/config.py +139 -32
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +7 -26
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +102 -137
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +21 -25
- pyinfra/api/operation.py +240 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +79 -86
- pyinfra/connectors/base.py +147 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +220 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +196 -208
- pyinfra/connectors/ssh.py +530 -613
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +211 -137
- pyinfra/connectors/vagrant.py +60 -53
- pyinfra/context.py +4 -2
- pyinfra/facts/apk.py +2 -0
- pyinfra/facts/apt.py +2 -0
- pyinfra/facts/brew.py +2 -0
- pyinfra/facts/bsdinit.py +2 -0
- pyinfra/facts/cargo.py +2 -0
- pyinfra/facts/choco.py +2 -0
- pyinfra/facts/deb.py +7 -2
- pyinfra/facts/dnf.py +2 -0
- pyinfra/facts/docker.py +19 -0
- pyinfra/facts/files.py +47 -32
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +3 -1
- pyinfra/facts/hardware.py +34 -24
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +13 -6
- pyinfra/facts/npm.py +1 -0
- pyinfra/facts/openrc.py +2 -0
- pyinfra/facts/pacman.py +6 -2
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +2 -0
- pyinfra/facts/pkgin.py +2 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +6 -160
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +80 -36
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +31 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +7 -4
- pyinfra/facts/vzctl.py +2 -0
- pyinfra/facts/xbps.py +2 -0
- pyinfra/facts/yum.py +2 -0
- pyinfra/facts/zypper.py +2 -0
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +6 -4
- pyinfra/operations/apt.py +46 -65
- pyinfra/operations/brew.py +17 -22
- pyinfra/operations/bsdinit.py +9 -7
- pyinfra/operations/cargo.py +4 -2
- pyinfra/operations/choco.py +4 -2
- pyinfra/operations/dnf.py +19 -23
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +188 -386
- pyinfra/operations/gem.py +4 -2
- pyinfra/operations/git.py +24 -53
- pyinfra/operations/iptables.py +29 -35
- pyinfra/operations/launchd.py +6 -7
- pyinfra/operations/lxd.py +8 -13
- pyinfra/operations/mysql.py +62 -81
- pyinfra/operations/npm.py +9 -2
- pyinfra/operations/openrc.py +6 -4
- pyinfra/operations/pacman.py +7 -8
- pyinfra/operations/pip.py +25 -24
- pyinfra/operations/pkg.py +4 -2
- pyinfra/operations/pkgin.py +6 -4
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -379
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -44
- pyinfra/operations/server.py +111 -127
- pyinfra/operations/snap.py +4 -4
- pyinfra/operations/ssh.py +20 -33
- pyinfra/operations/systemd.py +19 -15
- pyinfra/operations/sysvinit.py +9 -16
- pyinfra/operations/upstart.py +9 -7
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +55 -57
- pyinfra/operations/util/service.py +39 -51
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +6 -4
- pyinfra/operations/yum.py +18 -22
- pyinfra/operations/zypper.py +12 -13
- pyinfra/version.py +5 -2
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
- pyinfra-3.0.dist-info/RECORD +167 -0
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
- pyinfra-3.0.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +78 -42
- pyinfra_cli/inventory.py +40 -6
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +95 -127
- pyinfra_cli/util.py +62 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +101 -201
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +105 -93
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +7 -7
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.1.dist-info/RECORD +0 -170
- pyinfra-2.9.1.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
|
@@ -45,18 +45,17 @@ class TestLocalConnector(TestCase):
|
|
|
45
45
|
command = "echo Šablony"
|
|
46
46
|
self.fake_popen_mock().returncode = 0
|
|
47
47
|
|
|
48
|
-
out = host.run_shell_command(command,
|
|
49
|
-
assert len(out) ==
|
|
48
|
+
out = host.run_shell_command(command, _stdin="hello", print_output=True)
|
|
49
|
+
assert len(out) == 2
|
|
50
50
|
|
|
51
|
-
status,
|
|
51
|
+
status, output = out
|
|
52
52
|
assert status is True
|
|
53
53
|
self.fake_popen_mock().stdin.write.assert_called_with(b"hello\n")
|
|
54
54
|
|
|
55
55
|
combined_out = host.run_shell_command(
|
|
56
56
|
command,
|
|
57
|
-
|
|
57
|
+
_stdin="hello",
|
|
58
58
|
print_output=True,
|
|
59
|
-
return_combined_output=True,
|
|
60
59
|
)
|
|
61
60
|
assert len(combined_out) == 2
|
|
62
61
|
|
|
@@ -79,9 +78,9 @@ class TestLocalConnector(TestCase):
|
|
|
79
78
|
self.fake_popen_mock().returncode = 0
|
|
80
79
|
|
|
81
80
|
out = host.run_shell_command(command, print_output=True, print_input=True)
|
|
82
|
-
assert len(out) ==
|
|
81
|
+
assert len(out) == 2
|
|
83
82
|
|
|
84
|
-
status,
|
|
83
|
+
status, output = out
|
|
85
84
|
assert status is True
|
|
86
85
|
|
|
87
86
|
self.fake_popen_mock.assert_called_with(
|
|
@@ -105,8 +104,8 @@ class TestLocalConnector(TestCase):
|
|
|
105
104
|
command = "echo hi"
|
|
106
105
|
self.fake_popen_mock().returncode = 1
|
|
107
106
|
|
|
108
|
-
out = host.run_shell_command(command,
|
|
109
|
-
assert len(out) ==
|
|
107
|
+
out = host.run_shell_command(command, _success_exit_codes=[1])
|
|
108
|
+
assert len(out) == 2
|
|
110
109
|
assert out[0] is True
|
|
111
110
|
|
|
112
111
|
def test_run_shell_command_error(self):
|
|
@@ -118,7 +117,7 @@ class TestLocalConnector(TestCase):
|
|
|
118
117
|
self.fake_popen_mock().returncode = 1
|
|
119
118
|
|
|
120
119
|
out = host.run_shell_command(command)
|
|
121
|
-
assert len(out) ==
|
|
120
|
+
assert len(out) == 2
|
|
122
121
|
assert out[0] is False
|
|
123
122
|
|
|
124
123
|
def test_put_file(self):
|
|
@@ -210,7 +209,7 @@ class TestLocalConnector(TestCase):
|
|
|
210
209
|
command = "echo Šablony"
|
|
211
210
|
self.fake_popen_mock().returncode = 0
|
|
212
211
|
|
|
213
|
-
host.run_shell_command(command,
|
|
212
|
+
host.run_shell_command(command, _stdin=["hello", "abc"], print_output=True)
|
|
214
213
|
self.fake_popen_mock().stdin.write.assert_has_calls(
|
|
215
214
|
[
|
|
216
215
|
call(b"hello\n"),
|
|
@@ -226,7 +225,7 @@ class TestLocalConnector(TestCase):
|
|
|
226
225
|
command = "echo Šablony"
|
|
227
226
|
self.fake_popen_mock().returncode = 0
|
|
228
227
|
|
|
229
|
-
host.run_shell_command(command,
|
|
228
|
+
host.run_shell_command(command, _stdin=StringIO("hello\nabc"), print_output=True)
|
|
230
229
|
self.fake_popen_mock().stdin.write.assert_has_calls(
|
|
231
230
|
[
|
|
232
231
|
call(b"hello\n"),
|
|
@@ -10,7 +10,6 @@ import pyinfra
|
|
|
10
10
|
from pyinfra.api import Config, MaskString, State, StringCommand
|
|
11
11
|
from pyinfra.api.connect import connect_all
|
|
12
12
|
from pyinfra.api.exceptions import ConnectError, PyinfraError
|
|
13
|
-
from pyinfra.connectors.ssh import _get_sftp_connection
|
|
14
13
|
|
|
15
14
|
from ..util import make_inventory
|
|
16
15
|
|
|
@@ -28,7 +27,6 @@ class TestSSHConnector(TestCase):
|
|
|
28
27
|
def setUp(self):
|
|
29
28
|
self.fake_connect_patch = patch("pyinfra.connectors.ssh.SSHClient.connect")
|
|
30
29
|
self.fake_connect_mock = self.fake_connect_patch.start()
|
|
31
|
-
_get_sftp_connection.cache = {}
|
|
32
30
|
|
|
33
31
|
def tearDown(self):
|
|
34
32
|
self.fake_connect_patch.stop()
|
|
@@ -58,8 +56,8 @@ class TestSSHConnector(TestCase):
|
|
|
58
56
|
|
|
59
57
|
assert len(state.active_hosts) == 2
|
|
60
58
|
|
|
61
|
-
@patch("pyinfra.connectors.
|
|
62
|
-
@patch("pyinfra.connectors.
|
|
59
|
+
@patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True)
|
|
60
|
+
@patch("pyinfra.connectors.ssh_util.RSAKey.from_private_key_file")
|
|
63
61
|
def test_connect_exceptions(self, fake_key_open):
|
|
64
62
|
for exception_class in (
|
|
65
63
|
AuthenticationException,
|
|
@@ -83,8 +81,8 @@ class TestSSHConnector(TestCase):
|
|
|
83
81
|
def test_connect_with_rsa_ssh_key(self):
|
|
84
82
|
state = State(make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), Config())
|
|
85
83
|
|
|
86
|
-
with patch("pyinfra.connectors.
|
|
87
|
-
"pyinfra.connectors.
|
|
84
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
85
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
88
86
|
) as fake_key_open:
|
|
89
87
|
fake_key = MagicMock()
|
|
90
88
|
fake_key_open.return_value = fake_key
|
|
@@ -104,10 +102,10 @@ class TestSSHConnector(TestCase):
|
|
|
104
102
|
pkey=fake_key,
|
|
105
103
|
timeout=10,
|
|
106
104
|
username="vagrant",
|
|
107
|
-
_pyinfra_ssh_forward_agent=
|
|
105
|
+
_pyinfra_ssh_forward_agent=False,
|
|
108
106
|
_pyinfra_ssh_config_file=None,
|
|
109
107
|
_pyinfra_ssh_known_hosts_file=None,
|
|
110
|
-
_pyinfra_ssh_strict_host_key_checking=
|
|
108
|
+
_pyinfra_ssh_strict_host_key_checking="accept-new",
|
|
111
109
|
_pyinfra_ssh_paramiko_connect_kwargs=None,
|
|
112
110
|
)
|
|
113
111
|
|
|
@@ -128,8 +126,8 @@ class TestSSHConnector(TestCase):
|
|
|
128
126
|
Config(),
|
|
129
127
|
)
|
|
130
128
|
|
|
131
|
-
with patch("pyinfra.connectors.
|
|
132
|
-
"pyinfra.connectors.
|
|
129
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
130
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
133
131
|
) as fake_key_open:
|
|
134
132
|
fake_key = MagicMock()
|
|
135
133
|
|
|
@@ -150,11 +148,11 @@ class TestSSHConnector(TestCase):
|
|
|
150
148
|
def test_connect_with_rsa_ssh_key_password_from_prompt(self):
|
|
151
149
|
state = State(make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), Config())
|
|
152
150
|
|
|
153
|
-
with patch("pyinfra.connectors.
|
|
154
|
-
"pyinfra.connectors.
|
|
151
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
152
|
+
"pyinfra.connectors.ssh_util.getpass",
|
|
155
153
|
lambda *args, **kwargs: "testpass",
|
|
156
154
|
), patch(
|
|
157
|
-
"pyinfra.connectors.
|
|
155
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
158
156
|
) as fake_key_open:
|
|
159
157
|
fake_key = MagicMock()
|
|
160
158
|
|
|
@@ -177,8 +175,8 @@ class TestSSHConnector(TestCase):
|
|
|
177
175
|
def test_connect_with_rsa_ssh_key_missing_password(self):
|
|
178
176
|
state = State(make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), Config())
|
|
179
177
|
|
|
180
|
-
with patch("pyinfra.connectors.
|
|
181
|
-
"pyinfra.connectors.
|
|
178
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
179
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
182
180
|
) as fake_key_open:
|
|
183
181
|
fake_key_open.side_effect = make_raise_exception_function(PasswordRequiredException)
|
|
184
182
|
|
|
@@ -203,17 +201,17 @@ class TestSSHConnector(TestCase):
|
|
|
203
201
|
fake_fail_from_private_key_file = MagicMock()
|
|
204
202
|
fake_fail_from_private_key_file.side_effect = make_raise_exception_function(SSHException)
|
|
205
203
|
|
|
206
|
-
with patch("pyinfra.connectors.
|
|
207
|
-
"pyinfra.connectors.
|
|
204
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
205
|
+
"pyinfra.connectors.ssh_util.DSSKey.from_private_key_file",
|
|
208
206
|
fake_fail_from_private_key_file,
|
|
209
207
|
), patch(
|
|
210
|
-
"pyinfra.connectors.
|
|
208
|
+
"pyinfra.connectors.ssh_util.ECDSAKey.from_private_key_file",
|
|
211
209
|
fake_fail_from_private_key_file,
|
|
212
210
|
), patch(
|
|
213
|
-
"pyinfra.connectors.
|
|
211
|
+
"pyinfra.connectors.ssh_util.Ed25519Key.from_private_key_file",
|
|
214
212
|
fake_fail_from_private_key_file,
|
|
215
213
|
), patch(
|
|
216
|
-
"pyinfra.connectors.
|
|
214
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
217
215
|
) as fake_key_open:
|
|
218
216
|
|
|
219
217
|
def fake_key_open_fail(*args, **kwargs):
|
|
@@ -236,10 +234,10 @@ class TestSSHConnector(TestCase):
|
|
|
236
234
|
def test_connect_with_dss_ssh_key(self):
|
|
237
235
|
state = State(make_inventory(hosts=(("somehost", {"ssh_key": "testkey"}),)), Config())
|
|
238
236
|
|
|
239
|
-
with patch("pyinfra.connectors.
|
|
240
|
-
"pyinfra.connectors.
|
|
237
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
238
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
241
239
|
) as fake_rsa_key_open, patch(
|
|
242
|
-
"pyinfra.connectors.
|
|
240
|
+
"pyinfra.connectors.ssh_util.DSSKey.from_private_key_file",
|
|
243
241
|
) as fake_key_open: # noqa
|
|
244
242
|
fake_rsa_key_open.side_effect = make_raise_exception_function(SSHException)
|
|
245
243
|
|
|
@@ -259,10 +257,10 @@ class TestSSHConnector(TestCase):
|
|
|
259
257
|
pkey=fake_key,
|
|
260
258
|
timeout=10,
|
|
261
259
|
username="vagrant",
|
|
262
|
-
_pyinfra_ssh_forward_agent=
|
|
260
|
+
_pyinfra_ssh_forward_agent=False,
|
|
263
261
|
_pyinfra_ssh_config_file=None,
|
|
264
262
|
_pyinfra_ssh_known_hosts_file=None,
|
|
265
|
-
_pyinfra_ssh_strict_host_key_checking=
|
|
263
|
+
_pyinfra_ssh_strict_host_key_checking="accept-new",
|
|
266
264
|
_pyinfra_ssh_paramiko_connect_kwargs=None,
|
|
267
265
|
)
|
|
268
266
|
|
|
@@ -283,10 +281,10 @@ class TestSSHConnector(TestCase):
|
|
|
283
281
|
Config(),
|
|
284
282
|
)
|
|
285
283
|
|
|
286
|
-
with patch("pyinfra.connectors.
|
|
287
|
-
"pyinfra.connectors.
|
|
284
|
+
with patch("pyinfra.connectors.ssh_util.path.isfile", lambda *args, **kwargs: True), patch(
|
|
285
|
+
"pyinfra.connectors.ssh_util.RSAKey.from_private_key_file",
|
|
288
286
|
) as fake_rsa_key_open, patch(
|
|
289
|
-
"pyinfra.connectors.
|
|
287
|
+
"pyinfra.connectors.ssh_util.DSSKey.from_private_key_file",
|
|
290
288
|
) as fake_dss_key_open: # noqa
|
|
291
289
|
|
|
292
290
|
def fake_rsa_key_open_fail(*args, **kwargs):
|
|
@@ -318,10 +316,10 @@ class TestSSHConnector(TestCase):
|
|
|
318
316
|
pkey=fake_dss_key,
|
|
319
317
|
timeout=10,
|
|
320
318
|
username="vagrant",
|
|
321
|
-
_pyinfra_ssh_forward_agent=
|
|
319
|
+
_pyinfra_ssh_forward_agent=False,
|
|
322
320
|
_pyinfra_ssh_config_file=None,
|
|
323
321
|
_pyinfra_ssh_known_hosts_file=None,
|
|
324
|
-
_pyinfra_ssh_strict_host_key_checking=
|
|
322
|
+
_pyinfra_ssh_strict_host_key_checking="accept-new",
|
|
325
323
|
_pyinfra_ssh_paramiko_connect_kwargs=None,
|
|
326
324
|
)
|
|
327
325
|
|
|
@@ -362,18 +360,17 @@ class TestSSHConnector(TestCase):
|
|
|
362
360
|
command = "echo Šablony"
|
|
363
361
|
fake_stdout.channel.recv_exit_status.return_value = 0
|
|
364
362
|
|
|
365
|
-
out = host.run_shell_command(command,
|
|
366
|
-
assert len(out) ==
|
|
363
|
+
out = host.run_shell_command(command, _stdin="hello", print_output=True)
|
|
364
|
+
assert len(out) == 2
|
|
367
365
|
|
|
368
|
-
status,
|
|
366
|
+
status, output = out
|
|
369
367
|
assert status is True
|
|
370
368
|
fake_stdin.write.assert_called_with(b"hello\n")
|
|
371
369
|
|
|
372
370
|
combined_out = host.run_shell_command(
|
|
373
371
|
command,
|
|
374
|
-
|
|
372
|
+
_stdin="hello",
|
|
375
373
|
print_output=True,
|
|
376
|
-
return_combined_output=True,
|
|
377
374
|
)
|
|
378
375
|
assert len(combined_out) == 2
|
|
379
376
|
|
|
@@ -397,9 +394,9 @@ class TestSSHConnector(TestCase):
|
|
|
397
394
|
fake_stdout.channel.recv_exit_status.return_value = 0
|
|
398
395
|
|
|
399
396
|
out = host.run_shell_command(command, print_output=True, print_input=True)
|
|
400
|
-
assert len(out) ==
|
|
397
|
+
assert len(out) == 2
|
|
401
398
|
|
|
402
|
-
status,
|
|
399
|
+
status, output = out
|
|
403
400
|
assert status is True
|
|
404
401
|
|
|
405
402
|
fake_ssh.exec_command.assert_called_with(
|
|
@@ -428,8 +425,8 @@ class TestSSHConnector(TestCase):
|
|
|
428
425
|
command = "echo hi"
|
|
429
426
|
fake_stdout.channel.recv_exit_status.return_value = 1
|
|
430
427
|
|
|
431
|
-
out = host.run_shell_command(command,
|
|
432
|
-
assert len(out) ==
|
|
428
|
+
out = host.run_shell_command(command, _success_exit_codes=[1])
|
|
429
|
+
assert len(out) == 2
|
|
433
430
|
assert out[0] is True
|
|
434
431
|
|
|
435
432
|
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
@@ -449,19 +446,29 @@ class TestSSHConnector(TestCase):
|
|
|
449
446
|
fake_stdout.channel.recv_exit_status.return_value = 1
|
|
450
447
|
|
|
451
448
|
out = host.run_shell_command(command)
|
|
452
|
-
assert len(out) ==
|
|
449
|
+
assert len(out) == 2
|
|
453
450
|
assert out[0] is False
|
|
454
451
|
|
|
455
452
|
@patch("pyinfra.connectors.util.getpass")
|
|
456
453
|
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
457
|
-
def
|
|
454
|
+
def test_run_shell_command_sudo_password_automatic_prompt(
|
|
458
455
|
self,
|
|
459
456
|
fake_ssh_client,
|
|
460
457
|
fake_getpass,
|
|
461
458
|
):
|
|
462
459
|
fake_ssh = MagicMock()
|
|
463
|
-
|
|
464
|
-
|
|
460
|
+
first_fake_stdout = MagicMock()
|
|
461
|
+
second_fake_stdout = MagicMock()
|
|
462
|
+
third_fake_stdout = MagicMock()
|
|
463
|
+
|
|
464
|
+
first_fake_stdout.__iter__.return_value = ["sudo: a password is required\r"]
|
|
465
|
+
second_fake_stdout.__iter__.return_value = ["/tmp/pyinfra-sudo-askpass-XXXXXXXXXXXX"]
|
|
466
|
+
|
|
467
|
+
fake_ssh.exec_command.side_effect = [
|
|
468
|
+
(MagicMock(), first_fake_stdout, MagicMock()), # command w/o sudo password
|
|
469
|
+
(MagicMock(), second_fake_stdout, MagicMock()), # SUDO_ASKPASS_COMMAND
|
|
470
|
+
(MagicMock(), third_fake_stdout, MagicMock()), # command with sudo pw
|
|
471
|
+
]
|
|
465
472
|
|
|
466
473
|
fake_ssh_client.return_value = fake_ssh
|
|
467
474
|
fake_getpass.return_value = "password"
|
|
@@ -472,15 +479,18 @@ class TestSSHConnector(TestCase):
|
|
|
472
479
|
host.connect()
|
|
473
480
|
|
|
474
481
|
command = "echo Šablony"
|
|
475
|
-
|
|
476
|
-
|
|
482
|
+
first_fake_stdout.channel.recv_exit_status.return_value = 1
|
|
483
|
+
second_fake_stdout.channel.recv_exit_status.return_value = 0
|
|
484
|
+
third_fake_stdout.channel.recv_exit_status.return_value = 0
|
|
477
485
|
|
|
478
|
-
out = host.run_shell_command(command,
|
|
479
|
-
assert len(out) ==
|
|
486
|
+
out = host.run_shell_command(command, _sudo=True, print_output=True)
|
|
487
|
+
assert len(out) == 2
|
|
480
488
|
|
|
481
|
-
status,
|
|
489
|
+
status, output = out
|
|
482
490
|
assert status is True
|
|
483
491
|
|
|
492
|
+
fake_ssh.exec_command.assert_any_call(("sudo -H -n sh -c 'echo Šablony'"), get_pty=False)
|
|
493
|
+
|
|
484
494
|
fake_ssh.exec_command.assert_called_with(
|
|
485
495
|
(
|
|
486
496
|
"env SUDO_ASKPASS=/tmp/pyinfra-sudo-askpass-XXXXXXXXXXXX "
|
|
@@ -492,7 +502,7 @@ class TestSSHConnector(TestCase):
|
|
|
492
502
|
|
|
493
503
|
@patch("pyinfra.connectors.util.getpass")
|
|
494
504
|
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
495
|
-
def
|
|
505
|
+
def test_run_shell_command_sudo_password_automatic_prompt_with_special_chars_in_password(
|
|
496
506
|
self,
|
|
497
507
|
fake_ssh_client,
|
|
498
508
|
fake_getpass,
|
|
@@ -512,7 +522,7 @@ class TestSSHConnector(TestCase):
|
|
|
512
522
|
]
|
|
513
523
|
|
|
514
524
|
fake_ssh_client.return_value = fake_ssh
|
|
515
|
-
fake_getpass.return_value = "
|
|
525
|
+
fake_getpass.return_value = "p@ss'word';"
|
|
516
526
|
|
|
517
527
|
inventory = make_inventory(hosts=("somehost",))
|
|
518
528
|
State(inventory, Config())
|
|
@@ -524,10 +534,10 @@ class TestSSHConnector(TestCase):
|
|
|
524
534
|
second_fake_stdout.channel.recv_exit_status.return_value = 0
|
|
525
535
|
third_fake_stdout.channel.recv_exit_status.return_value = 0
|
|
526
536
|
|
|
527
|
-
out = host.run_shell_command(command,
|
|
528
|
-
assert len(out) ==
|
|
537
|
+
out = host.run_shell_command(command, _sudo=True, print_output=True)
|
|
538
|
+
assert len(out) == 2
|
|
529
539
|
|
|
530
|
-
status,
|
|
540
|
+
status, output = out
|
|
531
541
|
assert status is True
|
|
532
542
|
|
|
533
543
|
fake_ssh.exec_command.assert_any_call(("sudo -H -n sh -c 'echo Šablony'"), get_pty=False)
|
|
@@ -535,7 +545,7 @@ class TestSSHConnector(TestCase):
|
|
|
535
545
|
fake_ssh.exec_command.assert_called_with(
|
|
536
546
|
(
|
|
537
547
|
"env SUDO_ASKPASS=/tmp/pyinfra-sudo-askpass-XXXXXXXXXXXX "
|
|
538
|
-
"PYINFRA_SUDO_PASSWORD=
|
|
548
|
+
"""PYINFRA_SUDO_PASSWORD='p@ss'"'"'word'"'"';' """
|
|
539
549
|
"sudo -H -A -k sh -c 'echo Šablony'"
|
|
540
550
|
),
|
|
541
551
|
get_pty=False,
|
|
@@ -545,13 +555,13 @@ class TestSSHConnector(TestCase):
|
|
|
545
555
|
#
|
|
546
556
|
|
|
547
557
|
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
548
|
-
@patch("pyinfra.connectors.util.
|
|
558
|
+
@patch("pyinfra.connectors.util.getpass")
|
|
549
559
|
def test_run_shell_command_retry_for_sudo_password(
|
|
550
560
|
self,
|
|
551
|
-
|
|
561
|
+
fake_getpass,
|
|
552
562
|
fake_ssh_client,
|
|
553
563
|
):
|
|
554
|
-
|
|
564
|
+
fake_getpass.return_value = "PASSWORD"
|
|
555
565
|
|
|
556
566
|
fake_ssh = MagicMock()
|
|
557
567
|
fake_stdin = MagicMock()
|
|
@@ -571,13 +581,13 @@ class TestSSHConnector(TestCase):
|
|
|
571
581
|
return_values = [1, 0] # return 0 on the second call
|
|
572
582
|
fake_stdout.channel.recv_exit_status.side_effect = lambda: return_values.pop(0)
|
|
573
583
|
|
|
574
|
-
out = host.run_shell_command(command)
|
|
575
|
-
assert len(out) ==
|
|
584
|
+
out = host.run_shell_command(command, _sudo=True)
|
|
585
|
+
assert len(out) == 2
|
|
576
586
|
assert out[0] is True
|
|
577
|
-
assert
|
|
587
|
+
assert fake_getpass.called
|
|
578
588
|
fake_ssh.exec_command.assert_called_with(
|
|
579
589
|
"env SUDO_ASKPASS=/tmp/pyinfra-sudo-askpass-XXXXXXXXXXXX "
|
|
580
|
-
"PYINFRA_SUDO_PASSWORD=PASSWORD sh -c 'echo hi'",
|
|
590
|
+
"PYINFRA_SUDO_PASSWORD=PASSWORD sudo -H -A -k sh -c 'echo hi'",
|
|
581
591
|
get_pty=False,
|
|
582
592
|
)
|
|
583
593
|
|
|
@@ -624,8 +634,8 @@ class TestSSHConnector(TestCase):
|
|
|
624
634
|
"not-a-file",
|
|
625
635
|
"not another file",
|
|
626
636
|
print_output=True,
|
|
627
|
-
|
|
628
|
-
|
|
637
|
+
_sudo=True,
|
|
638
|
+
_sudo_user="ubuntu",
|
|
629
639
|
)
|
|
630
640
|
|
|
631
641
|
assert status is True
|
|
@@ -675,8 +685,8 @@ class TestSSHConnector(TestCase):
|
|
|
675
685
|
"not-a-file",
|
|
676
686
|
"not another file",
|
|
677
687
|
print_output=True,
|
|
678
|
-
|
|
679
|
-
|
|
688
|
+
_doas=True,
|
|
689
|
+
_doas_user="ubuntu",
|
|
680
690
|
)
|
|
681
691
|
|
|
682
692
|
assert status is True
|
|
@@ -726,7 +736,7 @@ class TestSSHConnector(TestCase):
|
|
|
726
736
|
"not-a-file",
|
|
727
737
|
"not-another-file",
|
|
728
738
|
print_output=True,
|
|
729
|
-
|
|
739
|
+
_su_user="centos",
|
|
730
740
|
)
|
|
731
741
|
|
|
732
742
|
assert status is False
|
|
@@ -763,7 +773,7 @@ class TestSSHConnector(TestCase):
|
|
|
763
773
|
"not-a-file",
|
|
764
774
|
"not-another-file",
|
|
765
775
|
print_output=True,
|
|
766
|
-
|
|
776
|
+
_su_user="centos",
|
|
767
777
|
)
|
|
768
778
|
|
|
769
779
|
assert status is False
|
|
@@ -808,8 +818,8 @@ class TestSSHConnector(TestCase):
|
|
|
808
818
|
"not-a-file",
|
|
809
819
|
"not another file",
|
|
810
820
|
print_output=True,
|
|
811
|
-
|
|
812
|
-
|
|
821
|
+
_sudo=True,
|
|
822
|
+
_sudo_user="ubuntu",
|
|
813
823
|
remote_temp_filename="/a-different-tempfile",
|
|
814
824
|
)
|
|
815
825
|
|
|
@@ -865,8 +875,8 @@ class TestSSHConnector(TestCase):
|
|
|
865
875
|
"not-a-file",
|
|
866
876
|
"not-another-file",
|
|
867
877
|
print_output=True,
|
|
868
|
-
|
|
869
|
-
|
|
878
|
+
_sudo=True,
|
|
879
|
+
_sudo_user="ubuntu",
|
|
870
880
|
)
|
|
871
881
|
|
|
872
882
|
assert status is True
|
|
@@ -876,7 +886,7 @@ class TestSSHConnector(TestCase):
|
|
|
876
886
|
call(
|
|
877
887
|
(
|
|
878
888
|
"sudo -H -n -u ubuntu sh -c 'cp not-a-file "
|
|
879
|
-
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r
|
|
889
|
+
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r /tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508'" # noqa
|
|
880
890
|
),
|
|
881
891
|
get_pty=False,
|
|
882
892
|
),
|
|
@@ -910,8 +920,8 @@ class TestSSHConnector(TestCase):
|
|
|
910
920
|
"not-a-file",
|
|
911
921
|
"not-another-file",
|
|
912
922
|
print_output=True,
|
|
913
|
-
|
|
914
|
-
|
|
923
|
+
_sudo=True,
|
|
924
|
+
_sudo_user="ubuntu",
|
|
915
925
|
)
|
|
916
926
|
|
|
917
927
|
assert status is False
|
|
@@ -921,7 +931,7 @@ class TestSSHConnector(TestCase):
|
|
|
921
931
|
call(
|
|
922
932
|
(
|
|
923
933
|
"sudo -H -n -u ubuntu sh -c 'cp not-a-file "
|
|
924
|
-
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r
|
|
934
|
+
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r /tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508'" # noqa
|
|
925
935
|
),
|
|
926
936
|
get_pty=False,
|
|
927
937
|
),
|
|
@@ -946,8 +956,8 @@ class TestSSHConnector(TestCase):
|
|
|
946
956
|
"not-a-file",
|
|
947
957
|
"not-another-file",
|
|
948
958
|
print_output=True,
|
|
949
|
-
|
|
950
|
-
|
|
959
|
+
_sudo=True,
|
|
960
|
+
_sudo_user="ubuntu",
|
|
951
961
|
)
|
|
952
962
|
|
|
953
963
|
assert status is False
|
|
@@ -957,7 +967,7 @@ class TestSSHConnector(TestCase):
|
|
|
957
967
|
call(
|
|
958
968
|
(
|
|
959
969
|
"sudo -H -n -u ubuntu sh -c 'cp not-a-file "
|
|
960
|
-
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r
|
|
970
|
+
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r /tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508'" # noqa
|
|
961
971
|
),
|
|
962
972
|
get_pty=False,
|
|
963
973
|
),
|
|
@@ -994,7 +1004,7 @@ class TestSSHConnector(TestCase):
|
|
|
994
1004
|
"not-a-file",
|
|
995
1005
|
"not-another-file",
|
|
996
1006
|
print_output=True,
|
|
997
|
-
|
|
1007
|
+
_su_user="centos",
|
|
998
1008
|
)
|
|
999
1009
|
|
|
1000
1010
|
assert status is True
|
|
@@ -1005,7 +1015,7 @@ class TestSSHConnector(TestCase):
|
|
|
1005
1015
|
(
|
|
1006
1016
|
"su centos -c 'sh -c '\"'\"'cp not-a-file "
|
|
1007
1017
|
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508 && chmod +r "
|
|
1008
|
-
"
|
|
1018
|
+
"/tmp/pyinfra-e9c0d3c8ffca943daa0e75511b0a09c84b59c508'\"'\"''"
|
|
1009
1019
|
),
|
|
1010
1020
|
get_pty=False,
|
|
1011
1021
|
),
|
|
@@ -1044,7 +1054,7 @@ class TestSSHConnector(TestCase):
|
|
|
1044
1054
|
)
|
|
1045
1055
|
|
|
1046
1056
|
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
1047
|
-
@patch("
|
|
1057
|
+
@patch("pyinfra.connectors.ssh.sleep")
|
|
1048
1058
|
def test_ssh_connect_fail_retry(self, fake_sleep, fake_ssh_client):
|
|
1049
1059
|
for exception_class in (
|
|
1050
1060
|
SSHException,
|
|
@@ -1052,6 +1062,9 @@ class TestSSHConnector(TestCase):
|
|
|
1052
1062
|
socket_error,
|
|
1053
1063
|
EOFError,
|
|
1054
1064
|
):
|
|
1065
|
+
fake_sleep.reset_mock()
|
|
1066
|
+
fake_ssh_client.reset_mock()
|
|
1067
|
+
|
|
1055
1068
|
inventory = make_inventory(
|
|
1056
1069
|
hosts=("unresposivehost",), override_data={"ssh_connect_retries": 1}
|
|
1057
1070
|
)
|
|
@@ -1060,17 +1073,16 @@ class TestSSHConnector(TestCase):
|
|
|
1060
1073
|
unresposivehost = inventory.get_host("unresposivehost")
|
|
1061
1074
|
assert unresposivehost.data.ssh_connect_retries == 1
|
|
1062
1075
|
|
|
1063
|
-
|
|
1064
|
-
fake_ssh.connect.side_effect = exception_class()
|
|
1065
|
-
fake_ssh_client.return_value = fake_ssh
|
|
1076
|
+
fake_ssh_client().connect.side_effect = exception_class()
|
|
1066
1077
|
|
|
1067
1078
|
with self.assertRaises(ConnectError):
|
|
1068
1079
|
unresposivehost.connect(show_errors=False, raise_exceptions=True)
|
|
1069
|
-
|
|
1070
|
-
|
|
1080
|
+
|
|
1081
|
+
fake_sleep.assert_called_once()
|
|
1082
|
+
assert fake_ssh_client().connect.call_count == 2
|
|
1071
1083
|
|
|
1072
1084
|
@patch("pyinfra.connectors.ssh.SSHClient")
|
|
1073
|
-
@patch("
|
|
1085
|
+
@patch("pyinfra.connectors.ssh.sleep")
|
|
1074
1086
|
def test_ssh_connect_fail_success(self, fake_sleep, fake_ssh_client):
|
|
1075
1087
|
for exception_class in (
|
|
1076
1088
|
SSHException,
|
|
@@ -1078,6 +1090,9 @@ class TestSSHConnector(TestCase):
|
|
|
1078
1090
|
socket_error,
|
|
1079
1091
|
EOFError,
|
|
1080
1092
|
):
|
|
1093
|
+
fake_sleep.reset_mock()
|
|
1094
|
+
fake_ssh_client.reset_mock()
|
|
1095
|
+
|
|
1081
1096
|
inventory = make_inventory(
|
|
1082
1097
|
hosts=("unresposivehost",), override_data={"ssh_connect_retries": 1}
|
|
1083
1098
|
)
|
|
@@ -1086,11 +1101,8 @@ class TestSSHConnector(TestCase):
|
|
|
1086
1101
|
unresposivehost = inventory.get_host("unresposivehost")
|
|
1087
1102
|
assert unresposivehost.data.ssh_connect_retries == 1
|
|
1088
1103
|
|
|
1089
|
-
|
|
1090
|
-
fake_ssh = MagicMock()
|
|
1091
|
-
fake_ssh.connect.side_effect = [exception_class(), connection]
|
|
1092
|
-
fake_ssh_client.return_value = fake_ssh
|
|
1104
|
+
fake_ssh_client().connect.side_effect = [exception_class(), MagicMock()]
|
|
1093
1105
|
|
|
1094
1106
|
unresposivehost.connect(show_errors=False, raise_exceptions=True)
|
|
1095
|
-
|
|
1096
|
-
assert fake_ssh_client.connect.
|
|
1107
|
+
fake_sleep.assert_called_once()
|
|
1108
|
+
assert fake_ssh_client().connect.call_count == 2
|