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
@@ -1,7 +1,6 @@
1
1
  from os import path
2
2
  from unittest import TestCase
3
3
 
4
- from pyinfra.context import ctx_state
5
4
  from pyinfra_cli.main import _main
6
5
 
7
6
  from ..paramiko_util import PatchSSHTestCase
@@ -17,23 +16,10 @@ class TestCliEagerFlags(TestCase):
17
16
  assert result.exit_code == 0, result.stdout
18
17
 
19
18
 
20
- class TestDeployCli(PatchSSHTestCase):
21
- def setUp(self):
22
- ctx_state.reset()
23
-
24
- def test_invalid_deploy(self):
25
- result = run_cli(
26
- "@local",
27
- "not-a-file.py",
28
- )
29
- assert result.exit_code == 1, result.stdout
30
- assert "No deploy file: not-a-file.py" in result.stdout
31
-
32
-
33
19
  class TestOperationCli(PatchSSHTestCase):
34
20
  def test_invalid_operation_module(self):
35
21
  result = run_cli(
36
- path.join("tests", "deploy", "inventories", "inventory.py"),
22
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
37
23
  "not_a_module.shell",
38
24
  )
39
25
  assert result.exit_code == 1, result.stdout
@@ -41,7 +27,7 @@ class TestOperationCli(PatchSSHTestCase):
41
27
 
42
28
  def test_invalid_operation_function(self):
43
29
  result = run_cli(
44
- path.join("tests", "deploy", "inventories", "inventory.py"),
30
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
45
31
  "server.not_an_operation",
46
32
  )
47
33
  assert result.exit_code == 1, result.stdout
@@ -49,7 +35,8 @@ class TestOperationCli(PatchSSHTestCase):
49
35
 
50
36
  def test_deploy_operation(self):
51
37
  result = run_cli(
52
- path.join("tests", "deploy", "inventories", "inventory.py"),
38
+ "-y",
39
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
53
40
  "server.shell",
54
41
  "echo hi",
55
42
  )
@@ -57,7 +44,8 @@ class TestOperationCli(PatchSSHTestCase):
57
44
 
58
45
  def test_deploy_operation_with_all(self):
59
46
  result = run_cli(
60
- path.join("tests", "deploy", "inventory_all.py"),
47
+ "-y",
48
+ path.join("tests", "test_cli", "deploy", "inventory_all.py"),
61
49
  "server.shell",
62
50
  "echo hi",
63
51
  )
@@ -65,7 +53,8 @@ class TestOperationCli(PatchSSHTestCase):
65
53
 
66
54
  def test_deploy_operation_json_args(self):
67
55
  result = run_cli(
68
- path.join("tests", "deploy", "inventory_all.py"),
56
+ "-y",
57
+ path.join("tests", "test_cli", "deploy", "inventory_all.py"),
69
58
  "server.shell",
70
59
  '[["echo hi"], {}]',
71
60
  )
@@ -75,7 +64,7 @@ class TestOperationCli(PatchSSHTestCase):
75
64
  class TestFactCli(PatchSSHTestCase):
76
65
  def test_get_fact(self):
77
66
  result = run_cli(
78
- path.join("tests", "deploy", "inventories", "inventory.py"),
67
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
79
68
  "fact",
80
69
  "server.Os",
81
70
  )
@@ -84,7 +73,7 @@ class TestFactCli(PatchSSHTestCase):
84
73
 
85
74
  def test_get_fact_with_kwargs(self):
86
75
  result = run_cli(
87
- path.join("tests", "deploy", "inventories", "inventory.py"),
76
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
88
77
  "fact",
89
78
  "files.File",
90
79
  "path=.",
@@ -92,29 +81,11 @@ class TestFactCli(PatchSSHTestCase):
92
81
  assert result.exit_code == 0, result.stdout
93
82
  assert '"somehost": null' in result.stdout
94
83
 
95
- def test_invalid_fact_module(self):
96
- result = run_cli(
97
- path.join("tests", "deploy", "inventories", "inventory.py"),
98
- "fact",
99
- "not_a_module.NotAFact",
100
- )
101
- assert result.exit_code == 1, result.stdout
102
- assert "No such module: pyinfra.facts.not_a_module" in result.stdout
103
-
104
- def test_invalid_fact_class(self):
105
- result = run_cli(
106
- path.join("tests", "deploy", "inventories", "inventory.py"),
107
- "fact",
108
- "server.NotAFact",
109
- )
110
- assert result.exit_code == 1, result.stdout
111
- assert "No such attribute in module pyinfra.facts.server: NotAFact" in result.stdout
112
-
113
84
 
114
85
  class TestExecCli(PatchSSHTestCase):
115
86
  def test_exec_command(self):
116
87
  result = run_cli(
117
- path.join("tests", "deploy", "inventories", "inventory.py"),
88
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
118
89
  "exec",
119
90
  "--",
120
91
  "echo hi",
@@ -123,7 +94,7 @@ class TestExecCli(PatchSSHTestCase):
123
94
 
124
95
  def test_exec_command_with_options(self):
125
96
  result = run_cli(
126
- path.join("tests", "deploy", "inventories", "inventory.py"),
97
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
127
98
  "exec",
128
99
  "--sudo",
129
100
  "--sudo-user",
@@ -141,7 +112,7 @@ class TestExecCli(PatchSSHTestCase):
141
112
 
142
113
  def test_exec_command_with_serial(self):
143
114
  result = run_cli(
144
- path.join("tests", "deploy", "inventories", "inventory.py"),
115
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
145
116
  "exec",
146
117
  "--serial",
147
118
  "--",
@@ -151,7 +122,7 @@ class TestExecCli(PatchSSHTestCase):
151
122
 
152
123
  def test_exec_command_with_no_wait(self):
153
124
  result = run_cli(
154
- path.join("tests", "deploy", "inventories", "inventory.py"),
125
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
155
126
  "exec",
156
127
  "--no-wait",
157
128
  "--",
@@ -161,7 +132,7 @@ class TestExecCli(PatchSSHTestCase):
161
132
 
162
133
  def test_exec_command_with_debug_operations(self):
163
134
  result = run_cli(
164
- path.join("tests", "deploy", "inventories", "inventory.py"),
135
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
165
136
  "exec",
166
137
  "--debug-operations",
167
138
  "--",
@@ -171,7 +142,7 @@ class TestExecCli(PatchSSHTestCase):
171
142
 
172
143
  def test_exec_command_with_debug_facts(self):
173
144
  result = run_cli(
174
- path.join("tests", "deploy", "inventories", "inventory.py"),
145
+ path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
175
146
  "exec",
176
147
  "--debug-facts",
177
148
  "--",
@@ -206,18 +177,16 @@ class TestDirectMainExecution(PatchSSHTestCase):
206
177
  parallel=None,
207
178
  fail_percent=0,
208
179
  dry=False,
180
+ yes=True,
209
181
  limit=None,
210
182
  no_wait=False,
211
183
  serial=False,
212
- winrm_username=None,
213
- winrm_password=None,
214
- winrm_port=None,
215
- winrm_transport=None,
216
184
  shell_executable=None,
217
185
  quiet=False,
218
186
  data=tuple(),
219
187
  debug=False,
220
188
  debug_facts=False,
189
+ debug_all=False,
221
190
  debug_operations=False,
222
191
  config_filename="config.py",
223
192
  )
@@ -1,32 +1,22 @@
1
1
  from os import path
2
2
  from random import shuffle
3
3
 
4
- from pyinfra import inventory, state
4
+ from pyinfra import state
5
5
  from pyinfra.context import ctx_state
6
6
 
7
7
  from ..paramiko_util import PatchSSHTestCase
8
8
  from .util import run_cli
9
9
 
10
10
 
11
- class TestCliDeploy(PatchSSHTestCase):
12
- def test_interdependent_deploy(self):
13
- ctx_state.reset()
14
-
15
- result = run_cli(
16
- "somehost",
17
- path.join("tests", "deploy", "deploy_interdependent.py"),
18
- f'--chdir={path.join("tests", "deploy")}',
11
+ class TestCliDeployState(PatchSSHTestCase):
12
+ def _run_cli(self, hosts, filename):
13
+ return run_cli(
14
+ "-y",
15
+ ",".join(hosts),
16
+ path.join("tests", "test_cli", "deploy", filename),
17
+ f'--chdir={path.join("tests", "test_cli", "deploy")}',
19
18
  )
20
- assert result.exit_code == 0, result.stdout
21
-
22
- # Check every operation had commands/changes - this ensures that each
23
- # combo (add/remove/add) always had changes.
24
- for host, ops in state.ops.items():
25
- for _, op in ops.items():
26
- assert len(op["commands"]) > 0
27
19
 
28
-
29
- class TestCliDeployState(PatchSSHTestCase):
30
20
  def _assert_op_data(self, correct_op_name_and_host_names):
31
21
  op_order = state.get_op_order()
32
22
 
@@ -40,7 +30,7 @@ class TestCliDeployState(PatchSSHTestCase):
40
30
  op_hash = op_order[i]
41
31
  op_meta = state.op_meta[op_hash]
42
32
 
43
- assert list(op_meta["names"])[0] == correct_op_name
33
+ assert list(op_meta.names)[0] == correct_op_name
44
34
 
45
35
  for host in state.inventory:
46
36
  if correct_host_names is True or host.name in correct_host_names:
@@ -92,11 +82,7 @@ class TestCliDeployState(PatchSSHTestCase):
92
82
  hosts = ["somehost", "anotherhost", "someotherhost"]
93
83
  shuffle(hosts)
94
84
 
95
- result = run_cli(
96
- ",".join(hosts),
97
- path.join("tests", "deploy", "deploy.py"),
98
- f'--chdir={path.join("tests", "deploy")}',
99
- )
85
+ result = self._run_cli(hosts, "deploy.py")
100
86
  assert result.exit_code == 0, result.stdout
101
87
 
102
88
  self._assert_op_data(correct_op_name_and_host_names)
@@ -121,19 +107,7 @@ class TestCliDeployState(PatchSSHTestCase):
121
107
  hosts = ["somehost", "anotherhost", "someotherhost"]
122
108
  shuffle(hosts)
123
109
 
124
- result = run_cli(
125
- ",".join(hosts),
126
- path.join("tests", "deploy", "deploy_random.py"),
127
- f'--chdir={path.join("tests", "deploy")}',
128
- )
110
+ result = self._run_cli(hosts, "deploy_random.py")
129
111
  assert result.exit_code == 0, result.stdout
130
112
 
131
113
  self._assert_op_data(correct_op_name_and_host_names)
132
-
133
- for hostname, expected_fact_count in (
134
- ("somehost", 2),
135
- ("anotherhost", 0),
136
- ("someotherhost", 1),
137
- ):
138
- host = inventory.get_host(hostname)
139
- assert len(host.facts) == expected_fact_count
@@ -1,34 +1,27 @@
1
+ import sys
2
+ from os import path
1
3
  from unittest import TestCase
2
4
 
5
+ import pytest
3
6
  from click.testing import CliRunner
4
7
 
5
- from pyinfra_cli.exceptions import CliError
8
+ from pyinfra.api import OperationError
9
+ from pyinfra.api.exceptions import ArgumentTypeError
10
+ from pyinfra_cli.exceptions import CliError, UnexpectedExternalError, WrappedError
6
11
  from pyinfra_cli.main import cli
7
12
 
13
+ from .util import run_cli
14
+
8
15
 
9
16
  class TestCliExceptions(TestCase):
10
17
  @classmethod
11
18
  def setUpClass(cls):
12
- cls.test_cli = CliRunner()
13
- cls.old_cli_show = CliError.show
14
-
15
- @classmethod
16
- def tearDownClass(cls):
17
- CliError.show = cls.old_cli_show
18
-
19
- def setUp(self):
20
- self.exception = None
21
- CliError.show = lambda e: self.capture_cli_error(e)
22
-
23
- def capture_cli_error(self, e):
24
- self.exception = e
25
- self.old_cli_show()
19
+ cls.runner = CliRunner()
26
20
 
27
21
  def assert_cli_exception(self, args, message):
28
- self.test_cli.invoke(cli, args)
29
-
30
- self.assertIsInstance(self.exception, CliError)
31
- assert self.exception.message == message
22
+ result = self.runner.invoke(cli, args, standalone_mode=False)
23
+ self.assertIsInstance(result.exception, CliError)
24
+ assert getattr(result.exception, "message") == message
32
25
 
33
26
  def test_bad_deploy_file(self):
34
27
  self.assert_cli_exception(
@@ -53,3 +46,41 @@ class TestCliExceptions(TestCase):
53
46
  ["my-server.net", "fact", "server.NotAFact"],
54
47
  "No such attribute in module pyinfra.facts.server: NotAFact",
55
48
  )
49
+
50
+
51
+ class TestCliDeployExceptions(TestCase):
52
+ def _run_cli(self, hosts, filename):
53
+ return run_cli(
54
+ "-y",
55
+ ",".join(hosts),
56
+ path.join("tests", "test_cli", "deploy_fails", filename),
57
+ f'--chdir={path.join("tests", "test_cli", "deploy_fails")}',
58
+ )
59
+
60
+ def test_invalid_argument_type(self):
61
+ result = self._run_cli(["@local"], "invalid_argument_type.py")
62
+ assert isinstance(result.exception, WrappedError)
63
+ assert isinstance(result.exception.exception, ArgumentTypeError)
64
+ assert (
65
+ result.exception.exception.args[0]
66
+ == "Invalid argument `_sudo`:: None is not an instance of bool"
67
+ )
68
+
69
+ def test_invalid_operation_arg(self):
70
+ result = self._run_cli(["@local"], "invalid_operation_arg.py")
71
+ assert isinstance(result.exception, UnexpectedExternalError)
72
+ assert isinstance(result.exception.exception, TypeError)
73
+ assert result.exception.filename == "invalid_operation_arg.py"
74
+ assert result.exception.exception.args[0] == "missing a required argument: 'commands'"
75
+
76
+ @pytest.mark.skipif(
77
+ sys.platform.startswith("win"),
78
+ reason="The operation is not compatible with Windows",
79
+ )
80
+ def test_operation_error(self):
81
+ result = self._run_cli(["@local"], "operation_error.py")
82
+ assert isinstance(result.exception, WrappedError)
83
+ assert isinstance(result.exception.exception, OperationError)
84
+ assert (
85
+ result.exception.exception.args[0] == "operation_error.py exists and is not a directory"
86
+ )
tests/test_cli/util.py CHANGED
@@ -10,7 +10,7 @@ def run_cli(*arguments):
10
10
  cwd = getcwd()
11
11
  pyinfra.is_cli = True
12
12
  runner = CliRunner()
13
- result = runner.invoke(cli, arguments)
13
+ result = runner.invoke(cli, arguments, standalone_mode=False)
14
14
  pyinfra.is_cli = False
15
15
  chdir(cwd)
16
16
  return result
@@ -63,11 +63,11 @@ class TestChrootConnector(TestCase):
63
63
  self.fake_popen_mock().returncode = 0
64
64
  out = host.run_shell_command(
65
65
  command,
66
- stdin="hello",
67
- get_pty=True,
66
+ _stdin="hello",
67
+ _get_pty=True,
68
68
  print_output=True,
69
69
  )
70
- assert len(out) == 3
70
+ assert len(out) == 2
71
71
  assert out[0] is True
72
72
 
73
73
  command = make_unix_command(command).get_raw_value()
@@ -93,8 +93,8 @@ class TestChrootConnector(TestCase):
93
93
  command = "echo hoi"
94
94
  self.fake_popen_mock().returncode = 1
95
95
 
96
- out = host.run_shell_command(command, success_exit_codes=[1])
97
- assert len(out) == 3
96
+ out = host.run_shell_command(command, _success_exit_codes=[1])
97
+ assert len(out) == 2
98
98
  assert out[0] is True
99
99
 
100
100
  def test_run_shell_command_error(self):
@@ -108,7 +108,7 @@ class TestChrootConnector(TestCase):
108
108
  self.fake_popen_mock().returncode = 1
109
109
 
110
110
  out = host.run_shell_command(command)
111
- assert len(out) == 3
111
+ assert len(out) == 2
112
112
  assert out[0] is False
113
113
 
114
114
  def test_put_file(self):
@@ -83,11 +83,11 @@ class TestDockerConnector(TestCase):
83
83
  host.connect()
84
84
  out = host.run_shell_command(
85
85
  command,
86
- stdin="hello",
87
- get_pty=True,
86
+ _stdin="hello",
87
+ _get_pty=True,
88
88
  print_output=True,
89
89
  )
90
- assert len(out) == 3
90
+ assert len(out) == 2
91
91
  assert out[0] is True
92
92
 
93
93
  command = make_unix_command(command).get_raw_value()
@@ -112,7 +112,7 @@ class TestDockerConnector(TestCase):
112
112
 
113
113
  host = inventory.get_host("@docker/not-an-image")
114
114
  host.connect()
115
- out = host.run_shell_command(command, success_exit_codes=[1])
115
+ out = host.run_shell_command(command, _success_exit_codes=[1])
116
116
  assert out[0] is True
117
117
 
118
118
  def test_run_shell_command_error(self):
@@ -7,47 +7,40 @@ from unittest.mock import MagicMock, mock_open, patch
7
7
  from pyinfra.api import Config, State
8
8
  from pyinfra.api.connect import connect_all
9
9
  from pyinfra.api.exceptions import InventoryError, PyinfraError
10
- from pyinfra.connectors.util import make_unix_command
10
+ from pyinfra.connectors.util import CommandOutput, OutputLine, make_unix_command
11
11
 
12
12
  from ..util import make_inventory
13
13
 
14
14
 
15
15
  def fake_ssh_docker_shell(
16
- state,
17
- host,
16
+ self,
18
17
  command,
19
- get_pty=False,
20
- timeout=None,
21
- stdin=None,
22
- success_exit_codes=None,
23
18
  print_output=False,
24
19
  print_input=False,
25
- return_combined_output=False,
26
- use_sudo_password=False,
27
20
  **command_kwargs,
28
21
  ):
29
- if host.data.ssh_hostname not in ("somehost", "anotherhost"):
30
- raise PyinfraError("Invalid host", host.data.ssh_hostname)
22
+ if str(command) == "docker run -d not-an-image tail -f /dev/null":
23
+ return (True, CommandOutput([OutputLine("stdout", "containerid")]))
31
24
 
32
- if command == "docker run -d not-an-image tail -f /dev/null":
33
- return (True, ["containerid"], [])
25
+ if str(command) == "docker commit containerid":
26
+ return (True, CommandOutput([OutputLine("stdout", "sha256:blahsomerandomstringdata")]))
34
27
 
35
- if command == "docker commit containerid":
36
- return (True, ["sha256:blahsomerandomstringdata"], [])
37
-
38
- if command == "docker rm -f containerid":
39
- return (True, [], [])
28
+ if str(command) == "docker rm -f containerid":
29
+ return (True, CommandOutput([]))
40
30
 
41
31
  if str(command).startswith("rm -f"):
42
- return (True, [], [])
32
+ return (True, CommandOutput([]))
33
+
34
+ if "$TMPDIR" in str(command):
35
+ return (True, CommandOutput([]))
43
36
 
44
37
  # This is a bit messy. But it's easier than trying to swap out a mock
45
38
  # when it needs to be used...
46
39
  if fake_ssh_docker_shell.custom_command:
47
- custom_command, status, stdout, stderr = fake_ssh_docker_shell.custom_command
40
+ custom_command, status, output = fake_ssh_docker_shell.custom_command
48
41
  if str(command) == custom_command:
49
42
  fake_ssh_docker_shell.ran_custom_command = True
50
- return (status, stdout, stderr)
43
+ return (status, output)
51
44
 
52
45
  raise PyinfraError("Invalid Command: {0}".format(command))
53
46
 
@@ -59,8 +52,8 @@ def get_docker_command(command):
59
52
  return docker_command
60
53
 
61
54
 
62
- @patch("pyinfra.connectors.ssh.connect", MagicMock())
63
- @patch("pyinfra.connectors.ssh.run_shell_command", fake_ssh_docker_shell)
55
+ @patch("pyinfra.connectors.ssh.SSHConnector.connect", MagicMock())
56
+ @patch("pyinfra.connectors.ssh.SSHConnector.run_shell_command", fake_ssh_docker_shell)
64
57
  @patch("pyinfra.api.util.open", mock_open(read_data="test!"), create=True)
65
58
  class TestDockerSSHConnector(TestCase):
66
59
  def setUp(self):
@@ -122,17 +115,17 @@ class TestDockerSSHConnector(TestCase):
122
115
 
123
116
  command = "echo hi"
124
117
 
125
- fake_ssh_docker_shell.custom_command = [get_docker_command(command), True, [], []]
118
+ fake_ssh_docker_shell.custom_command = [get_docker_command(command), True, []]
126
119
 
127
120
  host = inventory.get_host("@dockerssh/somehost:not-an-image")
128
121
  host.connect()
129
122
  out = host.run_shell_command(
130
123
  command,
131
- stdin="hello",
132
- get_pty=True,
124
+ _stdin="hello",
125
+ _get_pty=True,
133
126
  print_output=True,
134
127
  )
135
- assert len(out) == 3
128
+ assert len(out) == 2
136
129
  assert out[0] is True
137
130
  assert fake_ssh_docker_shell.ran_custom_command
138
131
 
@@ -141,47 +134,46 @@ class TestDockerSSHConnector(TestCase):
141
134
  state = State(inventory, Config())
142
135
 
143
136
  command = "echo hi"
144
- fake_ssh_docker_shell.custom_command = [get_docker_command(command), False, [], []]
137
+ fake_ssh_docker_shell.custom_command = [get_docker_command(command), False, []]
145
138
 
146
139
  host = inventory.get_host("@dockerssh/somehost:not-an-image")
147
140
  host.connect(state)
148
- out = host.run_shell_command(command, get_pty=True)
141
+ out = host.run_shell_command(command, _get_pty=True)
149
142
  assert out[0] is False
150
143
  assert fake_ssh_docker_shell.ran_custom_command
151
144
 
152
145
  @patch("pyinfra.connectors.dockerssh.mkstemp", lambda: (None, "local_tempfile"))
153
146
  @patch("pyinfra.connectors.docker.os.close", lambda f: None)
154
- @patch("pyinfra.connectors.dockerssh.ssh.put_file")
147
+ @patch("pyinfra.connectors.ssh.SSHConnector.put_file")
155
148
  def test_put_file(self, fake_put_file):
156
149
  fake_ssh_docker_shell.custom_command = [
157
150
  "docker cp remote_tempfile containerid:not-another-file",
158
151
  True,
159
152
  [],
160
- [],
161
153
  ]
162
154
 
163
155
  inventory = make_inventory(hosts=("@dockerssh/somehost:not-an-image",))
164
- state = State(inventory, Config())
165
- state.get_temp_filename = lambda _: "remote_tempfile"
156
+ State(inventory, Config())
166
157
 
167
158
  host = inventory.get_host("@dockerssh/somehost:not-an-image")
159
+ host.get_temp_filename = lambda _: "remote_tempfile"
168
160
  host.connect()
169
161
 
170
162
  host.put_file("not-a-file", "not-another-file", print_output=True)
171
163
 
172
164
  # ensure copy from local to remote host
173
- fake_put_file.assert_called_with(state, host, "local_tempfile", "remote_tempfile")
165
+ fake_put_file.assert_called_with("local_tempfile", "remote_tempfile")
174
166
 
175
167
  # ensure copy from remote host to remote docker container
176
168
  assert fake_ssh_docker_shell.ran_custom_command
177
169
 
178
- @patch("pyinfra.connectors.dockerssh.ssh.put_file")
170
+ @patch("pyinfra.connectors.ssh.SSHConnector.put_file")
179
171
  def test_put_file_error(self, fake_put_file):
180
172
  inventory = make_inventory(hosts=("@dockerssh/somehost:not-an-image",))
181
- state = State(inventory, Config())
182
- state.get_temp_filename = lambda _: "remote_tempfile"
173
+ State(inventory, Config())
183
174
 
184
175
  host = inventory.get_host("@dockerssh/somehost:not-an-image")
176
+ host.get_temp_filename = lambda _: "remote_tempfile"
185
177
  host.connect()
186
178
 
187
179
  # SSH error
@@ -193,8 +185,7 @@ class TestDockerSSHConnector(TestCase):
193
185
  fake_ssh_docker_shell.custom_command = [
194
186
  "docker cp remote_tempfile containerid:not-another-file",
195
187
  False,
196
- [],
197
- ["docker error"],
188
+ CommandOutput([OutputLine("stderr", "docker error")]),
198
189
  ]
199
190
  fake_put_file.return_value = True
200
191
 
@@ -202,44 +193,42 @@ class TestDockerSSHConnector(TestCase):
202
193
  host.put_file("not-a-file", "not-another-file", print_output=True)
203
194
  assert str(e.exception) == "docker error"
204
195
 
205
- @patch("pyinfra.connectors.dockerssh.ssh.get_file")
196
+ @patch("pyinfra.connectors.ssh.SSHConnector.get_file")
206
197
  def test_get_file(self, fake_get_file):
207
198
  fake_ssh_docker_shell.custom_command = [
208
199
  "docker cp containerid:not-a-file remote_tempfile",
209
200
  True,
210
201
  [],
211
- [],
212
202
  ]
213
203
 
214
204
  inventory = make_inventory(hosts=("@dockerssh/somehost:not-an-image",))
215
- state = State(inventory, Config())
216
- state.get_temp_filename = lambda _: "remote_tempfile"
205
+ State(inventory, Config())
217
206
 
218
207
  host = inventory.get_host("@dockerssh/somehost:not-an-image")
208
+ host.get_temp_filename = lambda _: "remote_tempfile"
219
209
  host.connect()
220
210
 
221
211
  host.get_file("not-a-file", "not-another-file", print_output=True)
222
212
 
223
213
  # ensure copy from local to remote host
224
- fake_get_file.assert_called_with(state, host, "remote_tempfile", "not-another-file")
214
+ fake_get_file.assert_called_with("remote_tempfile", "not-another-file")
225
215
 
226
216
  # ensure copy from remote host to remote docker container
227
217
  assert fake_ssh_docker_shell.ran_custom_command
228
218
 
229
- @patch("pyinfra.connectors.dockerssh.ssh.get_file")
219
+ @patch("pyinfra.connectors.ssh.SSHConnector.get_file")
230
220
  def test_get_file_error(self, fake_get_file):
231
221
  fake_ssh_docker_shell.custom_command = [
232
222
  "docker cp containerid:not-a-file remote_tempfile",
233
223
  False,
234
- [],
235
- ["docker error"],
224
+ CommandOutput([OutputLine("stderr", "docker error")]),
236
225
  ]
237
226
 
238
227
  inventory = make_inventory(hosts=("@dockerssh/somehost:not-an-image",))
239
- state = State(inventory, Config())
240
- state.get_temp_filename = lambda _: "remote_tempfile"
228
+ State(inventory, Config())
241
229
 
242
230
  host = inventory.get_host("@dockerssh/somehost:not-an-image")
231
+ host.get_temp_filename = lambda _: "remote_tempfile"
243
232
  host.connect()
244
233
 
245
234
  fake_get_file.return_value = True
@@ -253,7 +242,6 @@ class TestDockerSSHConnector(TestCase):
253
242
  "docker cp containerid:not-a-file remote_tempfile",
254
243
  True,
255
244
  [],
256
- [],
257
245
  ]
258
246
  fake_get_file.return_value = False
259
247
  with self.assertRaises(IOError) as ex: