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.
Files changed (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.1.dist-info/RECORD +0 -170
  151. pyinfra-2.9.1.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {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, stdin="hello", print_output=True)
49
- assert len(out) == 3
48
+ out = host.run_shell_command(command, _stdin="hello", print_output=True)
49
+ assert len(out) == 2
50
50
 
51
- status, stdout, stderr = out
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
- stdin="hello",
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) == 3
81
+ assert len(out) == 2
83
82
 
84
- status, stdout, stderr = out
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, success_exit_codes=[1])
109
- assert len(out) == 3
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) == 3
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, stdin=["hello", "abc"], print_output=True)
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, stdin=StringIO("hello\nabc"), print_output=True)
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.ssh.path.isfile", lambda *args, **kwargs: True)
62
- @patch("pyinfra.connectors.ssh.RSAKey.from_private_key_file")
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
87
- "pyinfra.connectors.ssh.RSAKey.from_private_key_file",
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=None,
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=None,
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
132
- "pyinfra.connectors.ssh.RSAKey.from_private_key_file",
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
154
- "pyinfra.connectors.ssh.getpass",
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.ssh.RSAKey.from_private_key_file",
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
181
- "pyinfra.connectors.ssh.RSAKey.from_private_key_file",
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
207
- "pyinfra.connectors.ssh.DSSKey.from_private_key_file",
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.ssh.ECDSAKey.from_private_key_file",
208
+ "pyinfra.connectors.ssh_util.ECDSAKey.from_private_key_file",
211
209
  fake_fail_from_private_key_file,
212
210
  ), patch(
213
- "pyinfra.connectors.ssh.Ed25519Key.from_private_key_file",
211
+ "pyinfra.connectors.ssh_util.Ed25519Key.from_private_key_file",
214
212
  fake_fail_from_private_key_file,
215
213
  ), patch(
216
- "pyinfra.connectors.ssh.RSAKey.from_private_key_file",
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
240
- "pyinfra.connectors.ssh.RSAKey.from_private_key_file",
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.ssh.DSSKey.from_private_key_file",
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=None,
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=None,
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.ssh.path.isfile", lambda *args, **kwargs: True), patch(
287
- "pyinfra.connectors.ssh.RSAKey.from_private_key_file",
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.ssh.DSSKey.from_private_key_file",
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=None,
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=None,
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, stdin="hello", print_output=True)
366
- assert len(out) == 3
363
+ out = host.run_shell_command(command, _stdin="hello", print_output=True)
364
+ assert len(out) == 2
367
365
 
368
- status, stdout, stderr = out
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
- stdin="hello",
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) == 3
397
+ assert len(out) == 2
401
398
 
402
- status, stdout, stderr = out
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, success_exit_codes=[1])
432
- assert len(out) == 3
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) == 3
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 test_run_shell_command_sudo_password_prompt(
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
- fake_stdout = MagicMock()
464
- fake_ssh.exec_command.return_value = MagicMock(), fake_stdout, MagicMock()
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
- fake_stdout.__iter__.return_value = ["/tmp/pyinfra-sudo-askpass-XXXXXXXXXXXX"]
476
- fake_stdout.channel.recv_exit_status.return_value = 0
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, sudo=True, use_sudo_password=True, print_output=True)
479
- assert len(out) == 3
486
+ out = host.run_shell_command(command, _sudo=True, print_output=True)
487
+ assert len(out) == 2
480
488
 
481
- status, stdout, stderr = out
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 test_run_shell_command_sudo_password_automatic_prompt(
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 = "password"
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, sudo=True, print_output=True)
528
- assert len(out) == 3
537
+ out = host.run_shell_command(command, _sudo=True, print_output=True)
538
+ assert len(out) == 2
529
539
 
530
- status, stdout, stderr = out
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=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._get_sudo_password")
558
+ @patch("pyinfra.connectors.util.getpass")
549
559
  def test_run_shell_command_retry_for_sudo_password(
550
560
  self,
551
- fake_get_sudo_password,
561
+ fake_getpass,
552
562
  fake_ssh_client,
553
563
  ):
554
- fake_get_sudo_password.return_value = "PASSWORD"
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) == 3
584
+ out = host.run_shell_command(command, _sudo=True)
585
+ assert len(out) == 2
576
586
  assert out[0] is True
577
- assert fake_get_sudo_password.called
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
- sudo=True,
628
- sudo_user="ubuntu",
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
- doas=True,
679
- doas_user="ubuntu",
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
- su_user="centos",
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
- su_user="centos",
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
- sudo=True,
812
- sudo_user="ubuntu",
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
- sudo=True,
869
- sudo_user="ubuntu",
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 not-a-file'" # noqa
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
- sudo=True,
914
- sudo_user="ubuntu",
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 not-a-file'" # noqa
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
- sudo=True,
950
- sudo_user="ubuntu",
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 not-a-file'" # noqa
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
- su_user="centos",
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
- "not-a-file'\"'\"''"
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("time.sleep")
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
- fake_ssh = MagicMock()
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
- assert fake_sleep.called_once()
1070
- assert fake_ssh_client.connect.called_twice()
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("time.sleep")
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
- connection = MagicMock()
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
- assert fake_sleep.called_once()
1096
- assert fake_ssh_client.connect.called_twice()
1107
+ fake_sleep.assert_called_once()
1108
+ assert fake_ssh_client().connect.call_count == 2