pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__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 +261 -255
- pyinfra/api/arguments_typed.py +77 -0
- pyinfra/api/command.py +66 -53
- pyinfra/api/config.py +27 -22
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +2 -24
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +77 -113
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +17 -25
- pyinfra/api/operation.py +232 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +55 -70
- pyinfra/connectors/base.py +150 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +227 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +195 -207
- pyinfra/connectors/ssh.py +528 -615
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +212 -137
- pyinfra/connectors/vagrant.py +55 -48
- pyinfra/context.py +3 -2
- pyinfra/facts/docker.py +1 -0
- pyinfra/facts/files.py +45 -32
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +1 -1
- pyinfra/facts/hardware.py +4 -2
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/mysql.py +1 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -161
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +77 -30
- pyinfra/facts/systemd.py +29 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/util/packaging.py +4 -2
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +3 -3
- pyinfra/operations/apt.py +25 -47
- pyinfra/operations/brew.py +7 -14
- pyinfra/operations/bsdinit.py +4 -4
- pyinfra/operations/cargo.py +1 -1
- pyinfra/operations/choco.py +1 -1
- pyinfra/operations/dnf.py +4 -4
- pyinfra/operations/files.py +108 -321
- pyinfra/operations/gem.py +1 -1
- pyinfra/operations/git.py +6 -37
- pyinfra/operations/iptables.py +2 -10
- pyinfra/operations/launchd.py +1 -1
- pyinfra/operations/lxd.py +1 -9
- pyinfra/operations/mysql.py +5 -28
- pyinfra/operations/npm.py +1 -1
- pyinfra/operations/openrc.py +1 -1
- pyinfra/operations/pacman.py +3 -3
- pyinfra/operations/pip.py +14 -15
- pyinfra/operations/pkg.py +1 -1
- pyinfra/operations/pkgin.py +3 -3
- pyinfra/operations/postgres.py +347 -0
- pyinfra/operations/postgresql.py +17 -380
- pyinfra/operations/python.py +2 -17
- pyinfra/operations/selinux.py +5 -28
- pyinfra/operations/server.py +59 -84
- pyinfra/operations/snap.py +1 -3
- pyinfra/operations/ssh.py +8 -23
- pyinfra/operations/systemd.py +7 -7
- pyinfra/operations/sysvinit.py +3 -12
- pyinfra/operations/upstart.py +4 -4
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/files.py +2 -2
- pyinfra/operations/util/packaging.py +6 -24
- pyinfra/operations/util/service.py +18 -37
- pyinfra/operations/vzctl.py +2 -2
- pyinfra/operations/xbps.py +3 -3
- pyinfra/operations/yum.py +4 -4
- pyinfra/operations/zypper.py +4 -4
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
- pyinfra-3.0b1.dist-info/RECORD +163 -0
- pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +2 -0
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +83 -42
- pyinfra_cli/inventory.py +19 -4
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +93 -129
- pyinfra_cli/util.py +60 -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 +100 -200
- 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 +66 -107
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +4 -4
- 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.2.dist-info/RECORD +0 -170
- pyinfra-2.9.2.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.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
tests/test_cli/test_cli.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
path.join("tests", "
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
29
|
-
|
|
30
|
-
|
|
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
|
@@ -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
|
-
|
|
67
|
-
|
|
66
|
+
_stdin="hello",
|
|
67
|
+
_get_pty=True,
|
|
68
68
|
print_output=True,
|
|
69
69
|
)
|
|
70
|
-
assert len(out) ==
|
|
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,
|
|
97
|
-
assert len(out) ==
|
|
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) ==
|
|
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
|
-
|
|
87
|
-
|
|
86
|
+
_stdin="hello",
|
|
87
|
+
_get_pty=True,
|
|
88
88
|
print_output=True,
|
|
89
89
|
)
|
|
90
|
-
assert len(out) ==
|
|
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,
|
|
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
|
-
|
|
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
|
|
30
|
-
|
|
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
|
|
33
|
-
return (True, ["
|
|
25
|
+
if str(command) == "docker commit containerid":
|
|
26
|
+
return (True, CommandOutput([OutputLine("stdout", "sha256:blahsomerandomstringdata")]))
|
|
34
27
|
|
|
35
|
-
if command == "docker
|
|
36
|
-
return (True, [
|
|
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,
|
|
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,
|
|
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
|
-
|
|
132
|
-
|
|
124
|
+
_stdin="hello",
|
|
125
|
+
_get_pty=True,
|
|
133
126
|
print_output=True,
|
|
134
127
|
)
|
|
135
|
-
assert len(out) ==
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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:
|