pyinfra 3.0.dev0__py2.py3-none-any.whl → 3.0.2__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 +115 -97
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +5 -3
- pyinfra/api/config.py +139 -39
- pyinfra/api/connectors.py +5 -2
- pyinfra/api/deploy.py +19 -19
- pyinfra/api/exceptions.py +35 -4
- pyinfra/api/facts.py +62 -86
- pyinfra/api/host.py +102 -15
- pyinfra/api/inventory.py +4 -0
- pyinfra/api/operation.py +188 -120
- pyinfra/api/operations.py +66 -113
- pyinfra/api/state.py +53 -34
- pyinfra/api/util.py +64 -33
- pyinfra/connectors/base.py +65 -20
- pyinfra/connectors/chroot.py +15 -13
- pyinfra/connectors/docker.py +62 -72
- pyinfra/connectors/dockerssh.py +20 -19
- pyinfra/connectors/local.py +32 -22
- pyinfra/connectors/ssh.py +162 -86
- pyinfra/connectors/sshuserclient/client.py +1 -1
- pyinfra/connectors/terraform.py +57 -39
- pyinfra/connectors/util.py +26 -27
- pyinfra/connectors/vagrant.py +27 -26
- pyinfra/context.py +1 -0
- pyinfra/facts/apk.py +7 -2
- pyinfra/facts/apt.py +15 -7
- pyinfra/facts/brew.py +28 -13
- pyinfra/facts/bsdinit.py +9 -6
- pyinfra/facts/cargo.py +6 -3
- pyinfra/facts/choco.py +8 -4
- pyinfra/facts/deb.py +21 -9
- pyinfra/facts/dnf.py +11 -6
- pyinfra/facts/docker.py +30 -5
- pyinfra/facts/files.py +49 -33
- pyinfra/facts/gem.py +7 -2
- pyinfra/facts/git.py +14 -21
- pyinfra/facts/gpg.py +4 -1
- pyinfra/facts/hardware.py +186 -138
- pyinfra/facts/launchd.py +7 -2
- pyinfra/facts/lxd.py +8 -2
- pyinfra/facts/mysql.py +19 -12
- pyinfra/facts/npm.py +3 -1
- pyinfra/facts/openrc.py +8 -2
- pyinfra/facts/pacman.py +13 -5
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +5 -1
- pyinfra/facts/pkgin.py +7 -2
- pyinfra/facts/postgres.py +170 -0
- pyinfra/facts/postgresql.py +5 -162
- pyinfra/facts/rpm.py +21 -15
- pyinfra/facts/runit.py +70 -0
- pyinfra/facts/selinux.py +12 -4
- pyinfra/facts/server.py +240 -82
- pyinfra/facts/snap.py +8 -2
- pyinfra/facts/systemd.py +37 -13
- pyinfra/facts/sysvinit.py +7 -4
- pyinfra/facts/upstart.py +7 -2
- pyinfra/facts/util/packaging.py +3 -2
- pyinfra/facts/vzctl.py +8 -4
- pyinfra/facts/xbps.py +7 -2
- pyinfra/facts/yum.py +10 -5
- pyinfra/facts/zypper.py +9 -4
- pyinfra/operations/apk.py +5 -3
- pyinfra/operations/apt.py +28 -25
- pyinfra/operations/brew.py +60 -29
- pyinfra/operations/bsdinit.py +6 -4
- pyinfra/operations/cargo.py +3 -1
- pyinfra/operations/choco.py +3 -1
- pyinfra/operations/dnf.py +16 -20
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +187 -168
- pyinfra/operations/gem.py +3 -1
- pyinfra/operations/git.py +23 -25
- pyinfra/operations/iptables.py +33 -25
- pyinfra/operations/launchd.py +5 -6
- pyinfra/operations/lxd.py +7 -4
- pyinfra/operations/mysql.py +59 -55
- pyinfra/operations/npm.py +8 -1
- pyinfra/operations/openrc.py +5 -3
- pyinfra/operations/pacman.py +6 -7
- pyinfra/operations/pip.py +19 -12
- pyinfra/operations/pkg.py +3 -1
- pyinfra/operations/pkgin.py +5 -3
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -335
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -29
- pyinfra/operations/server.py +138 -67
- pyinfra/operations/snap.py +3 -1
- pyinfra/operations/ssh.py +18 -16
- pyinfra/operations/systemd.py +18 -12
- pyinfra/operations/sysvinit.py +7 -5
- pyinfra/operations/upstart.py +7 -5
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +54 -38
- pyinfra/operations/util/service.py +39 -47
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +5 -3
- pyinfra/operations/yum.py +15 -19
- pyinfra/operations/zypper.py +9 -10
- pyinfra/version.py +5 -2
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/METADATA +51 -58
- pyinfra-3.0.2.dist-info/RECORD +168 -0
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/WHEEL +1 -1
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/entry_points.txt +0 -3
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +3 -2
- pyinfra_cli/exceptions.py +75 -43
- pyinfra_cli/inventory.py +52 -31
- pyinfra_cli/log.py +10 -2
- pyinfra_cli/main.py +88 -65
- pyinfra_cli/prints.py +37 -109
- pyinfra_cli/util.py +15 -10
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +9 -9
- tests/test_api/test_api_deploys.py +15 -19
- tests/test_api/test_api_facts.py +4 -5
- tests/test_api/test_api_operations.py +18 -20
- tests/test_api/test_api_util.py +41 -2
- tests/test_cli/test_cli.py +14 -50
- tests/test_cli/test_cli_deploy.py +17 -14
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/test_cli_inventory.py +66 -0
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_dockerssh.py +11 -8
- tests/test_connectors/test_ssh.py +88 -23
- tests/test_connectors/test_sshuserclient.py +1 -1
- tests/test_connectors/test_terraform.py +11 -8
- tests/test_connectors/test_vagrant.py +6 -6
- pyinfra/connectors/ansible.py +0 -175
- pyinfra/connectors/mech.py +0 -189
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -312
- 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 -538
- pyinfra-3.0.dev0.dist-info/RECORD +0 -170
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/top_level.txt +0 -0
pyinfra_cli/util.py
CHANGED
|
@@ -58,19 +58,20 @@ def exec_file(filename, return_locals: bool = False, is_deploy_code: bool = Fals
|
|
|
58
58
|
PYTHON_CODES[filename] = code
|
|
59
59
|
|
|
60
60
|
# Create some base attributes for our "module"
|
|
61
|
-
data = {
|
|
62
|
-
"__file__": filename,
|
|
63
|
-
}
|
|
61
|
+
data = {"__file__": filename}
|
|
64
62
|
|
|
65
63
|
# Execute the code with locals/globals going into the dict above
|
|
66
64
|
try:
|
|
67
65
|
exec(PYTHON_CODES[filename], data)
|
|
66
|
+
except PyinfraError:
|
|
67
|
+
# Raise pyinfra errors as-is
|
|
68
|
+
raise
|
|
68
69
|
except Exception as e:
|
|
69
|
-
|
|
70
|
-
raise
|
|
70
|
+
# Wrap & re-raise errors in user code so we highlight filename/etc
|
|
71
71
|
raise UnexpectedExternalError(e, filename)
|
|
72
|
+
finally:
|
|
73
|
+
state.current_exec_filename = old_current_exec_filename
|
|
72
74
|
|
|
73
|
-
state.current_exec_filename = old_current_exec_filename
|
|
74
75
|
return data
|
|
75
76
|
|
|
76
77
|
|
|
@@ -149,13 +150,15 @@ def parse_cli_arg(arg):
|
|
|
149
150
|
return arg
|
|
150
151
|
|
|
151
152
|
|
|
152
|
-
def try_import_module_attribute(path, prefix=None,
|
|
153
|
+
def try_import_module_attribute(path, prefix=None, raise_for_none=True):
|
|
153
154
|
if ":" in path:
|
|
154
155
|
# Allow a.module.name:function syntax
|
|
155
156
|
mod_path, attr_name = path.rsplit(":", 1)
|
|
156
|
-
|
|
157
|
+
elif "." in path:
|
|
157
158
|
# And also a.module.name.function
|
|
158
159
|
mod_path, attr_name = path.rsplit(".", 1)
|
|
160
|
+
else:
|
|
161
|
+
return None
|
|
159
162
|
|
|
160
163
|
possible_modules = [mod_path]
|
|
161
164
|
if prefix:
|
|
@@ -176,13 +179,15 @@ def try_import_module_attribute(path, prefix=None, raise_for_no_module=True):
|
|
|
176
179
|
break
|
|
177
180
|
|
|
178
181
|
if module is None:
|
|
179
|
-
if
|
|
182
|
+
if raise_for_none:
|
|
180
183
|
raise CliError(f"No such module: {possible_modules[-1]}")
|
|
181
184
|
return
|
|
182
185
|
|
|
183
186
|
attr = getattr(module, attr_name, None)
|
|
184
187
|
if attr is None:
|
|
185
|
-
|
|
188
|
+
if raise_for_none:
|
|
189
|
+
raise CliError(f"No such attribute in module {possible_modules[-1]}: {attr_name}")
|
|
190
|
+
return
|
|
186
191
|
|
|
187
192
|
return attr
|
|
188
193
|
|
tests/test_api/test_api.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from unittest import TestCase
|
|
2
|
+
from unittest.mock import patch
|
|
2
3
|
|
|
3
4
|
from paramiko import SSHException
|
|
4
5
|
|
|
@@ -60,6 +61,7 @@ class TestInventoryApi(TestCase):
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
class TestStateApi(PatchSSHTestCase):
|
|
64
|
+
@patch("pyinfra.connectors.base.raise_if_bad_type", lambda *args, **kwargs: None)
|
|
63
65
|
def test_fail_percent(self):
|
|
64
66
|
inventory = make_inventory(
|
|
65
67
|
(
|
|
@@ -16,40 +16,40 @@ class TestOperationKwargs(TestCase):
|
|
|
16
16
|
|
|
17
17
|
def test_get_from_host(self):
|
|
18
18
|
config = Config(SUDO="config-value")
|
|
19
|
-
inventory = Inventory(([("somehost", {"_sudo":
|
|
19
|
+
inventory = Inventory(([("somehost", {"_sudo": True})], {}))
|
|
20
20
|
|
|
21
21
|
state = State(config=config, inventory=inventory)
|
|
22
22
|
|
|
23
23
|
kwargs, keys = pop_global_arguments({}, state=state, host=inventory.get_host("somehost"))
|
|
24
|
-
assert kwargs["_sudo"]
|
|
24
|
+
assert kwargs["_sudo"] is True
|
|
25
25
|
|
|
26
26
|
def test_get_from_state_deploy_kwargs(self):
|
|
27
27
|
config = Config(SUDO="config-value")
|
|
28
|
-
inventory = Inventory(([("somehost", {"_sudo":
|
|
28
|
+
inventory = Inventory(([("somehost", {"_sudo": False})], {}))
|
|
29
29
|
somehost = inventory.get_host("somehost")
|
|
30
30
|
|
|
31
31
|
state = State(config=config, inventory=inventory)
|
|
32
|
-
somehost.current_deploy_kwargs = {"_sudo":
|
|
32
|
+
somehost.current_deploy_kwargs = {"_sudo": True}
|
|
33
33
|
|
|
34
34
|
kwargs, keys = pop_global_arguments({}, state=state, host=somehost)
|
|
35
|
-
assert kwargs["_sudo"]
|
|
35
|
+
assert kwargs["_sudo"] is True
|
|
36
36
|
|
|
37
37
|
def test_get_from_kwargs(self):
|
|
38
38
|
config = Config(SUDO="config-value")
|
|
39
|
-
inventory = Inventory(([("somehost", {"_sudo":
|
|
39
|
+
inventory = Inventory(([("somehost", {"_sudo": False})], {}))
|
|
40
40
|
somehost = inventory.get_host("somehost")
|
|
41
41
|
|
|
42
42
|
state = State(config=config, inventory=inventory)
|
|
43
43
|
somehost.current_deploy_kwargs = {
|
|
44
|
-
"_sudo":
|
|
44
|
+
"_sudo": False,
|
|
45
45
|
"_sudo_user": "deploy-kwarg-user",
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
kwargs, keys = pop_global_arguments(
|
|
49
|
-
{"_sudo":
|
|
49
|
+
{"_sudo": True},
|
|
50
50
|
state=state,
|
|
51
51
|
host=somehost,
|
|
52
52
|
)
|
|
53
|
-
assert kwargs["_sudo"]
|
|
53
|
+
assert kwargs["_sudo"] is True
|
|
54
54
|
assert kwargs["_sudo_user"] == "deploy-kwarg-user"
|
|
55
55
|
assert "_sudo" in keys
|
|
@@ -24,7 +24,7 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
24
24
|
|
|
25
25
|
connect_all(state)
|
|
26
26
|
|
|
27
|
-
@deploy
|
|
27
|
+
@deploy()
|
|
28
28
|
def test_deploy(state=None, host=None):
|
|
29
29
|
server.shell(commands=["echo first command"])
|
|
30
30
|
server.shell(commands=["echo second command"])
|
|
@@ -40,20 +40,20 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
40
40
|
run_ops(state)
|
|
41
41
|
|
|
42
42
|
first_op_hash = op_order[0]
|
|
43
|
-
assert state.op_meta[first_op_hash].names == {"test_deploy |
|
|
44
|
-
assert state.ops[somehost][first_op_hash].operation_meta.
|
|
43
|
+
assert state.op_meta[first_op_hash].names == {"test_deploy | server.shell"}
|
|
44
|
+
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
45
45
|
StringCommand("echo first command"),
|
|
46
46
|
]
|
|
47
|
-
assert state.ops[anotherhost][first_op_hash].operation_meta.
|
|
47
|
+
assert state.ops[anotherhost][first_op_hash].operation_meta._commands == [
|
|
48
48
|
StringCommand("echo first command"),
|
|
49
49
|
]
|
|
50
50
|
|
|
51
51
|
second_op_hash = op_order[1]
|
|
52
|
-
assert state.op_meta[second_op_hash].names == {"test_deploy |
|
|
53
|
-
assert state.ops[somehost][second_op_hash].operation_meta.
|
|
52
|
+
assert state.op_meta[second_op_hash].names == {"test_deploy | server.shell"}
|
|
53
|
+
assert state.ops[somehost][second_op_hash].operation_meta._commands == [
|
|
54
54
|
StringCommand("echo second command"),
|
|
55
55
|
]
|
|
56
|
-
assert state.ops[anotherhost][second_op_hash].operation_meta.
|
|
56
|
+
assert state.ops[anotherhost][second_op_hash].operation_meta._commands == [
|
|
57
57
|
StringCommand("echo second command"),
|
|
58
58
|
]
|
|
59
59
|
|
|
@@ -67,10 +67,6 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
67
67
|
assert state.results[somehost].error_ops == 0
|
|
68
68
|
assert state.results[anotherhost].error_ops == 0
|
|
69
69
|
|
|
70
|
-
# And with the different modes
|
|
71
|
-
run_ops(state, serial=True)
|
|
72
|
-
run_ops(state, no_wait=True)
|
|
73
|
-
|
|
74
70
|
disconnect_all(state)
|
|
75
71
|
|
|
76
72
|
def test_nested_deploy(self):
|
|
@@ -87,11 +83,11 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
87
83
|
|
|
88
84
|
connect_all(state)
|
|
89
85
|
|
|
90
|
-
@deploy
|
|
86
|
+
@deploy()
|
|
91
87
|
def test_nested_deploy():
|
|
92
88
|
server.shell(commands=["echo nested command"])
|
|
93
89
|
|
|
94
|
-
@deploy
|
|
90
|
+
@deploy()
|
|
95
91
|
def test_deploy():
|
|
96
92
|
server.shell(commands=["echo first command"])
|
|
97
93
|
test_nested_deploy()
|
|
@@ -108,21 +104,21 @@ class TestDeploysApi(PatchSSHTestCase):
|
|
|
108
104
|
run_ops(state)
|
|
109
105
|
|
|
110
106
|
first_op_hash = op_order[0]
|
|
111
|
-
assert state.op_meta[first_op_hash].names == {"test_deploy |
|
|
112
|
-
assert state.ops[somehost][first_op_hash].operation_meta.
|
|
107
|
+
assert state.op_meta[first_op_hash].names == {"test_deploy | server.shell"}
|
|
108
|
+
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
113
109
|
StringCommand("echo first command"),
|
|
114
110
|
]
|
|
115
111
|
|
|
116
112
|
second_op_hash = op_order[1]
|
|
117
113
|
assert state.op_meta[second_op_hash].names == {
|
|
118
|
-
"test_deploy | test_nested_deploy |
|
|
114
|
+
"test_deploy | test_nested_deploy | server.shell",
|
|
119
115
|
}
|
|
120
|
-
assert state.ops[somehost][second_op_hash].operation_meta.
|
|
116
|
+
assert state.ops[somehost][second_op_hash].operation_meta._commands == [
|
|
121
117
|
StringCommand("echo nested command"),
|
|
122
118
|
]
|
|
123
119
|
|
|
124
120
|
third_op_hash = op_order[2]
|
|
125
|
-
assert state.op_meta[third_op_hash].names == {"test_deploy |
|
|
126
|
-
assert state.ops[somehost][third_op_hash].operation_meta.
|
|
121
|
+
assert state.op_meta[third_op_hash].names == {"test_deploy | server.shell"}
|
|
122
|
+
assert state.ops[somehost][third_op_hash].operation_meta._commands == [
|
|
127
123
|
StringCommand("echo second command"),
|
|
128
124
|
]
|
tests/test_api/test_api_facts.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from unittest.mock import MagicMock, patch
|
|
2
2
|
|
|
3
3
|
from pyinfra.api import Config, State
|
|
4
|
-
from pyinfra.api.arguments import
|
|
4
|
+
from pyinfra.api.arguments import CONNECTOR_ARGUMENT_KEYS, pop_global_arguments
|
|
5
5
|
from pyinfra.api.connect import connect_all
|
|
6
6
|
from pyinfra.api.exceptions import PyinfraError
|
|
7
7
|
from pyinfra.api.facts import get_facts
|
|
@@ -17,7 +17,7 @@ def _get_executor_defaults(state, host):
|
|
|
17
17
|
return {
|
|
18
18
|
key: value
|
|
19
19
|
for key, value in global_argument_defaults.items()
|
|
20
|
-
if key in
|
|
20
|
+
if key in CONNECTOR_ARGUMENT_KEYS
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
|
|
@@ -55,7 +55,6 @@ class TestFactsApi(PatchSSHTestCase):
|
|
|
55
55
|
anotherhost.current_op_global_arguments = {
|
|
56
56
|
"_sudo": True,
|
|
57
57
|
"_sudo_user": "someuser",
|
|
58
|
-
"_use_sudo_password": True,
|
|
59
58
|
"_su_user": "someuser",
|
|
60
59
|
"_timeout": 10,
|
|
61
60
|
"_env": {"HELLO": "WORLD"},
|
|
@@ -237,7 +236,7 @@ class TestFactsApi(PatchSSHTestCase):
|
|
|
237
236
|
|
|
238
237
|
assert fact_data == {host_1: "some-output"}
|
|
239
238
|
fake_run_command.assert_called_with(
|
|
240
|
-
Arch.command,
|
|
239
|
+
Arch().command(),
|
|
241
240
|
print_input=False,
|
|
242
241
|
print_output=False,
|
|
243
242
|
**defaults,
|
|
@@ -310,7 +309,7 @@ class TestHostFactsApi(PatchSSHTestCase):
|
|
|
310
309
|
|
|
311
310
|
assert fact_data == "some-output"
|
|
312
311
|
fake_run_command.assert_called_with(
|
|
313
|
-
Arch.command,
|
|
312
|
+
Arch().command(),
|
|
314
313
|
print_input=False,
|
|
315
314
|
print_output=False,
|
|
316
315
|
**defaults,
|
|
@@ -29,11 +29,11 @@ from ..util import make_inventory
|
|
|
29
29
|
class TestOperationMeta(TestCase):
|
|
30
30
|
def test_operation_meta_repr_no_change(self):
|
|
31
31
|
op_meta = OperationMeta("hash", False)
|
|
32
|
-
assert repr(op_meta) == "OperationMeta(
|
|
32
|
+
assert repr(op_meta) == "OperationMeta(executed=False, maybeChange=False, hash=hash)"
|
|
33
33
|
|
|
34
34
|
def test_operation_meta_repr_changes(self):
|
|
35
35
|
op_meta = OperationMeta("hash", True)
|
|
36
|
-
assert repr(op_meta) == "OperationMeta(
|
|
36
|
+
assert repr(op_meta) == "OperationMeta(executed=False, maybeChange=True, hash=hash)"
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
class TestOperationsApi(PatchSSHTestCase):
|
|
@@ -78,7 +78,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
78
78
|
first_op_hash = op_order[0]
|
|
79
79
|
|
|
80
80
|
# Ensure the op name
|
|
81
|
-
assert state.op_meta[first_op_hash].names == {"
|
|
81
|
+
assert state.op_meta[first_op_hash].names == {"files.file"}
|
|
82
82
|
|
|
83
83
|
# Ensure the global kwargs (same for both hosts)
|
|
84
84
|
somehost_global_arguments = state.ops[somehost][first_op_hash].global_arguments
|
|
@@ -97,7 +97,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
97
97
|
run_ops(state)
|
|
98
98
|
|
|
99
99
|
# Ensure the commands
|
|
100
|
-
assert state.ops[somehost][first_op_hash].operation_meta.
|
|
100
|
+
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
101
101
|
StringCommand("touch /var/log/pyinfra.log"),
|
|
102
102
|
StringCommand("chmod 644 /var/log/pyinfra.log"),
|
|
103
103
|
StringCommand("chown pyinfra:pyinfra /var/log/pyinfra.log"),
|
|
@@ -113,10 +113,6 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
113
113
|
assert state.results[somehost].error_ops == 0
|
|
114
114
|
assert state.results[anotherhost].error_ops == 0
|
|
115
115
|
|
|
116
|
-
# And with the different modes
|
|
117
|
-
run_ops(state, serial=True)
|
|
118
|
-
run_ops(state, no_wait=True)
|
|
119
|
-
|
|
120
116
|
disconnect_all(state)
|
|
121
117
|
|
|
122
118
|
@patch("pyinfra.api.util.open", mock_open(read_data="test!"), create=True)
|
|
@@ -181,7 +177,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
181
177
|
run_ops(state)
|
|
182
178
|
|
|
183
179
|
# Ensure first op used the right (upload) command
|
|
184
|
-
assert state.ops[somehost][first_op_hash].operation_meta.
|
|
180
|
+
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
185
181
|
StringCommand("mkdir -p /home/vagrant"),
|
|
186
182
|
FileUploadCommand("files/file.txt", "/home/vagrant/file.txt"),
|
|
187
183
|
]
|
|
@@ -225,7 +221,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
225
221
|
run_ops(state)
|
|
226
222
|
|
|
227
223
|
# Ensure first op has the right (upload) command
|
|
228
|
-
assert state.ops[somehost][first_op_hash].operation_meta.
|
|
224
|
+
assert state.ops[somehost][first_op_hash].operation_meta._commands == [
|
|
229
225
|
FileDownloadCommand("/home/vagrant/file.txt", "files/file.txt"),
|
|
230
226
|
]
|
|
231
227
|
|
|
@@ -296,7 +292,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
296
292
|
fake_run_local_process.assert_called_with(
|
|
297
293
|
(
|
|
298
294
|
"rsync -ax --delete --rsh "
|
|
299
|
-
'"ssh -o BatchMode=yes"'
|
|
295
|
+
'"ssh -o BatchMode=yes -o \\"StrictHostKeyChecking=accept-new\\""'
|
|
300
296
|
" --rsync-path 'sudo -u root rsync' src vagrant@somehost:dest"
|
|
301
297
|
),
|
|
302
298
|
print_output=False,
|
|
@@ -381,7 +377,8 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
381
377
|
fake_run_local_process.assert_called_with(
|
|
382
378
|
(
|
|
383
379
|
"rsync -ax --delete --rsh "
|
|
384
|
-
"
|
|
380
|
+
'"ssh -o BatchMode=yes -o \\"StrictHostKeyChecking=accept-new\\" '
|
|
381
|
+
"-F '/home/me/ssh_test_config && echo hi'\""
|
|
385
382
|
" --rsync-path 'sudo -u root rsync' src vagrant@somehost:dest"
|
|
386
383
|
),
|
|
387
384
|
print_output=False,
|
|
@@ -393,7 +390,7 @@ class TestOperationsApi(PatchSSHTestCase):
|
|
|
393
390
|
state = State(inventory, Config())
|
|
394
391
|
connect_all(state)
|
|
395
392
|
|
|
396
|
-
with patch("pyinfra.connectors.ssh.
|
|
393
|
+
with patch("pyinfra.connectors.ssh.which", lambda x: None):
|
|
397
394
|
with self.assertRaises(OperationError) as context:
|
|
398
395
|
add_op(state, files.rsync, "src", "dest")
|
|
399
396
|
|
|
@@ -436,11 +433,11 @@ class TestNestedOperationsApi(PatchSSHTestCase):
|
|
|
436
433
|
|
|
437
434
|
try:
|
|
438
435
|
outer_result = server.shell(commands="echo outer")
|
|
439
|
-
assert outer_result.
|
|
436
|
+
assert outer_result._combined_output is None
|
|
440
437
|
|
|
441
438
|
def callback():
|
|
442
439
|
inner_result = server.shell(commands="echo inner")
|
|
443
|
-
assert inner_result.
|
|
440
|
+
assert inner_result._combined_output is not None
|
|
444
441
|
|
|
445
442
|
python.call(function=callback)
|
|
446
443
|
|
|
@@ -450,7 +447,7 @@ class TestNestedOperationsApi(PatchSSHTestCase):
|
|
|
450
447
|
|
|
451
448
|
assert len(state.get_op_order()) == 3
|
|
452
449
|
assert state.results[somehost].success_ops == 3
|
|
453
|
-
assert outer_result.
|
|
450
|
+
assert outer_result._combined_output is not None
|
|
454
451
|
|
|
455
452
|
disconnect_all(state)
|
|
456
453
|
finally:
|
|
@@ -532,18 +529,19 @@ class TestOperationOrdering(PatchSSHTestCase):
|
|
|
532
529
|
# Add op to just the second host - using the context modules such that
|
|
533
530
|
# it replicates a deploy file.
|
|
534
531
|
ctx_host.set(inventory.get_host("anotherhost"))
|
|
535
|
-
first_context_hash = server.user("anotherhost_user").
|
|
532
|
+
first_context_hash = server.user("anotherhost_user")._hash
|
|
536
533
|
|
|
537
534
|
# Add op to just the first host - using the context modules such that
|
|
538
535
|
# it replicates a deploy file.
|
|
539
536
|
ctx_host.set(inventory.get_host("somehost"))
|
|
540
|
-
second_context_hash = server.user("somehost_user").
|
|
537
|
+
second_context_hash = server.user("somehost_user")._hash
|
|
541
538
|
|
|
542
539
|
ctx_state.reset()
|
|
543
540
|
ctx_host.reset()
|
|
544
541
|
|
|
545
542
|
pyinfra.is_cli = False
|
|
546
543
|
|
|
544
|
+
print(state.ops)
|
|
547
545
|
# Ensure there are two ops
|
|
548
546
|
op_order = state.get_op_order()
|
|
549
547
|
assert len(op_order) == 3
|
|
@@ -567,9 +565,9 @@ class TestOperationOrdering(PatchSSHTestCase):
|
|
|
567
565
|
another_host = inventory.get_host("anotherhost")
|
|
568
566
|
|
|
569
567
|
def add_another_op():
|
|
570
|
-
return add_op(state, server.shell, "echo second-op")[another_host].
|
|
568
|
+
return add_op(state, server.shell, "echo second-op")[another_host]._hash
|
|
571
569
|
|
|
572
|
-
first_op_hash = add_op(state, server.shell, "echo first-op")[another_host].
|
|
570
|
+
first_op_hash = add_op(state, server.shell, "echo first-op")[another_host]._hash
|
|
573
571
|
second_op_hash = add_another_op() # note `add_op` will be called on an earlier line
|
|
574
572
|
|
|
575
573
|
op_order = state.get_op_order()
|
tests/test_api/test_api_util.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
from io import BytesIO, StringIO
|
|
1
2
|
from unittest import TestCase
|
|
2
3
|
|
|
3
|
-
from pyinfra.api.util import format_exception, get_caller_frameinfo, try_int
|
|
4
|
+
from pyinfra.api.util import format_exception, get_caller_frameinfo, get_file_io, try_int
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class TestApiUtil(TestCase):
|
|
@@ -15,8 +16,46 @@ class TestApiUtil(TestCase):
|
|
|
15
16
|
return get_caller_frameinfo()
|
|
16
17
|
|
|
17
18
|
frameinfo = _get_caller_frameinfo()
|
|
18
|
-
assert frameinfo.lineno ==
|
|
19
|
+
assert frameinfo.lineno == 18 # called by the line above
|
|
19
20
|
|
|
20
21
|
def test_format_exception(self):
|
|
21
22
|
exception = Exception("I am a message", 1)
|
|
22
23
|
assert format_exception(exception) == "Exception('I am a message', 1)"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TestApiUtilFileIO(TestCase):
|
|
27
|
+
def test_get_file_io_stringio_to_string(self):
|
|
28
|
+
file = StringIO("some string")
|
|
29
|
+
|
|
30
|
+
with get_file_io(file, mode="r") as f:
|
|
31
|
+
data = f.read()
|
|
32
|
+
|
|
33
|
+
assert isinstance(data, str)
|
|
34
|
+
assert data == "some string"
|
|
35
|
+
|
|
36
|
+
def test_get_file_io_stringio_to_bytes(self):
|
|
37
|
+
file = StringIO("some string")
|
|
38
|
+
|
|
39
|
+
with get_file_io(file) as f:
|
|
40
|
+
data = f.read()
|
|
41
|
+
|
|
42
|
+
assert isinstance(data, bytes)
|
|
43
|
+
assert data == b"some string"
|
|
44
|
+
|
|
45
|
+
def test_get_file_io_bytesio_to_bytes(self):
|
|
46
|
+
file = BytesIO(b"some string")
|
|
47
|
+
|
|
48
|
+
with get_file_io(file) as f:
|
|
49
|
+
data = f.read()
|
|
50
|
+
|
|
51
|
+
assert isinstance(data, bytes)
|
|
52
|
+
assert data == b"some string"
|
|
53
|
+
|
|
54
|
+
def test_get_file_io_bytesio_to_string(self):
|
|
55
|
+
file = BytesIO(b"some string")
|
|
56
|
+
|
|
57
|
+
with get_file_io(file, mode="r") as f:
|
|
58
|
+
data = f.read()
|
|
59
|
+
|
|
60
|
+
assert isinstance(data, str)
|
|
61
|
+
assert data == "some string"
|
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
|
|
@@ -50,7 +36,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
50
36
|
def test_deploy_operation(self):
|
|
51
37
|
result = run_cli(
|
|
52
38
|
"-y",
|
|
53
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
39
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
54
40
|
"server.shell",
|
|
55
41
|
"echo hi",
|
|
56
42
|
)
|
|
@@ -59,7 +45,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
59
45
|
def test_deploy_operation_with_all(self):
|
|
60
46
|
result = run_cli(
|
|
61
47
|
"-y",
|
|
62
|
-
path.join("tests", "deploy", "inventory_all.py"),
|
|
48
|
+
path.join("tests", "test_cli", "deploy", "inventory_all.py"),
|
|
63
49
|
"server.shell",
|
|
64
50
|
"echo hi",
|
|
65
51
|
)
|
|
@@ -68,7 +54,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
68
54
|
def test_deploy_operation_json_args(self):
|
|
69
55
|
result = run_cli(
|
|
70
56
|
"-y",
|
|
71
|
-
path.join("tests", "deploy", "inventory_all.py"),
|
|
57
|
+
path.join("tests", "test_cli", "deploy", "inventory_all.py"),
|
|
72
58
|
"server.shell",
|
|
73
59
|
'[["echo hi"], {}]',
|
|
74
60
|
)
|
|
@@ -78,7 +64,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
78
64
|
class TestFactCli(PatchSSHTestCase):
|
|
79
65
|
def test_get_fact(self):
|
|
80
66
|
result = run_cli(
|
|
81
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
67
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
82
68
|
"fact",
|
|
83
69
|
"server.Os",
|
|
84
70
|
)
|
|
@@ -87,7 +73,7 @@ class TestFactCli(PatchSSHTestCase):
|
|
|
87
73
|
|
|
88
74
|
def test_get_fact_with_kwargs(self):
|
|
89
75
|
result = run_cli(
|
|
90
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
76
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
91
77
|
"fact",
|
|
92
78
|
"files.File",
|
|
93
79
|
"path=.",
|
|
@@ -95,29 +81,11 @@ class TestFactCli(PatchSSHTestCase):
|
|
|
95
81
|
assert result.exit_code == 0, result.stdout
|
|
96
82
|
assert '"somehost": null' in result.stdout
|
|
97
83
|
|
|
98
|
-
def test_invalid_fact_module(self):
|
|
99
|
-
result = run_cli(
|
|
100
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
101
|
-
"fact",
|
|
102
|
-
"not_a_module.NotAFact",
|
|
103
|
-
)
|
|
104
|
-
assert result.exit_code == 1, result.stdout
|
|
105
|
-
assert "No such module: pyinfra.facts.not_a_module" in result.stdout
|
|
106
|
-
|
|
107
|
-
def test_invalid_fact_class(self):
|
|
108
|
-
result = run_cli(
|
|
109
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
110
|
-
"fact",
|
|
111
|
-
"server.NotAFact",
|
|
112
|
-
)
|
|
113
|
-
assert result.exit_code == 1, result.stdout
|
|
114
|
-
assert "No such attribute in module pyinfra.facts.server: NotAFact" in result.stdout
|
|
115
|
-
|
|
116
84
|
|
|
117
85
|
class TestExecCli(PatchSSHTestCase):
|
|
118
86
|
def test_exec_command(self):
|
|
119
87
|
result = run_cli(
|
|
120
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
88
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
121
89
|
"exec",
|
|
122
90
|
"--",
|
|
123
91
|
"echo hi",
|
|
@@ -126,7 +94,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
126
94
|
|
|
127
95
|
def test_exec_command_with_options(self):
|
|
128
96
|
result = run_cli(
|
|
129
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
97
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
130
98
|
"exec",
|
|
131
99
|
"--sudo",
|
|
132
100
|
"--sudo-user",
|
|
@@ -144,7 +112,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
144
112
|
|
|
145
113
|
def test_exec_command_with_serial(self):
|
|
146
114
|
result = run_cli(
|
|
147
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
115
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
148
116
|
"exec",
|
|
149
117
|
"--serial",
|
|
150
118
|
"--",
|
|
@@ -154,7 +122,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
154
122
|
|
|
155
123
|
def test_exec_command_with_no_wait(self):
|
|
156
124
|
result = run_cli(
|
|
157
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
125
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
158
126
|
"exec",
|
|
159
127
|
"--no-wait",
|
|
160
128
|
"--",
|
|
@@ -164,7 +132,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
164
132
|
|
|
165
133
|
def test_exec_command_with_debug_operations(self):
|
|
166
134
|
result = run_cli(
|
|
167
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
135
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
168
136
|
"exec",
|
|
169
137
|
"--debug-operations",
|
|
170
138
|
"--",
|
|
@@ -174,7 +142,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
174
142
|
|
|
175
143
|
def test_exec_command_with_debug_facts(self):
|
|
176
144
|
result = run_cli(
|
|
177
|
-
path.join("tests", "deploy", "inventories", "inventory.py"),
|
|
145
|
+
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
178
146
|
"exec",
|
|
179
147
|
"--debug-facts",
|
|
180
148
|
"--",
|
|
@@ -213,15 +181,11 @@ class TestDirectMainExecution(PatchSSHTestCase):
|
|
|
213
181
|
limit=None,
|
|
214
182
|
no_wait=False,
|
|
215
183
|
serial=False,
|
|
216
|
-
winrm_username=None,
|
|
217
|
-
winrm_password=None,
|
|
218
|
-
winrm_port=None,
|
|
219
|
-
winrm_transport=None,
|
|
220
184
|
shell_executable=None,
|
|
221
|
-
quiet=False,
|
|
222
185
|
data=tuple(),
|
|
223
186
|
debug=False,
|
|
224
187
|
debug_facts=False,
|
|
188
|
+
debug_all=False,
|
|
225
189
|
debug_operations=False,
|
|
226
190
|
config_filename="config.py",
|
|
227
191
|
)
|