pyinfra 3.3.1__py2.py3-none-any.whl → 3.4.1__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/arguments.py +9 -16
- pyinfra/api/deploy.py +1 -1
- pyinfra/api/facts.py +10 -26
- pyinfra/api/host.py +10 -4
- pyinfra/api/inventory.py +5 -2
- pyinfra/api/operation.py +1 -1
- pyinfra/api/util.py +20 -6
- pyinfra/connectors/docker.py +117 -38
- pyinfra/connectors/dockerssh.py +1 -0
- pyinfra/connectors/local.py +1 -0
- pyinfra/connectors/ssh.py +1 -0
- pyinfra/connectors/terraform.py +3 -0
- pyinfra/connectors/vagrant.py +3 -0
- pyinfra/context.py +14 -5
- pyinfra/facts/brew.py +1 -0
- pyinfra/facts/docker.py +6 -2
- pyinfra/facts/git.py +10 -0
- pyinfra/facts/hardware.py +1 -1
- pyinfra/facts/opkg.py +1 -0
- pyinfra/facts/server.py +81 -23
- pyinfra/facts/systemd.py +1 -1
- pyinfra/operations/crontab.py +7 -5
- pyinfra/operations/docker.py +2 -0
- pyinfra/operations/files.py +64 -21
- pyinfra/operations/flatpak.py +17 -2
- pyinfra/operations/git.py +6 -2
- pyinfra/operations/server.py +34 -24
- pyinfra/operations/util/docker.py +4 -0
- pyinfra/operations/util/files.py +44 -3
- {pyinfra-3.3.1.dist-info → pyinfra-3.4.1.dist-info}/METADATA +5 -4
- {pyinfra-3.3.1.dist-info → pyinfra-3.4.1.dist-info}/RECORD +45 -45
- {pyinfra-3.3.1.dist-info → pyinfra-3.4.1.dist-info}/entry_points.txt +1 -0
- pyinfra_cli/inventory.py +1 -1
- pyinfra_cli/main.py +4 -2
- tests/test_api/test_api_arguments.py +25 -20
- tests/test_api/test_api_facts.py +28 -15
- tests/test_api/test_api_operations.py +43 -44
- tests/test_cli/test_cli.py +17 -17
- tests/test_cli/test_cli_inventory.py +4 -4
- tests/test_cli/test_context_objects.py +26 -26
- tests/test_connectors/test_docker.py +83 -43
- tests/test_connectors/test_ssh.py +153 -132
- {pyinfra-3.3.1.dist-info → pyinfra-3.4.1.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.3.1.dist-info → pyinfra-3.4.1.dist-info}/WHEEL +0 -0
- {pyinfra-3.3.1.dist-info → pyinfra-3.4.1.dist-info}/top_level.txt +0 -0
|
@@ -426,32 +426,31 @@ class TestNestedOperationsApi(PatchSSHTestCase):
|
|
|
426
426
|
|
|
427
427
|
somehost = inventory.get_host("somehost")
|
|
428
428
|
|
|
429
|
-
ctx_state.set(state)
|
|
430
|
-
ctx_host.set(somehost)
|
|
431
|
-
|
|
432
429
|
pyinfra.is_cli = True
|
|
433
430
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
431
|
+
with ctx_state.use(state):
|
|
432
|
+
with ctx_host.use(somehost):
|
|
433
|
+
try:
|
|
434
|
+
outer_result = server.shell(commands="echo outer")
|
|
435
|
+
assert outer_result._combined_output is None
|
|
437
436
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
437
|
+
def callback():
|
|
438
|
+
inner_result = server.shell(commands="echo inner")
|
|
439
|
+
assert inner_result._combined_output is not None
|
|
441
440
|
|
|
442
|
-
|
|
441
|
+
python.call(function=callback)
|
|
443
442
|
|
|
444
|
-
|
|
443
|
+
assert len(state.get_op_order()) == 2
|
|
445
444
|
|
|
446
|
-
|
|
445
|
+
run_ops(state)
|
|
447
446
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
447
|
+
assert len(state.get_op_order()) == 3
|
|
448
|
+
assert state.results[somehost].success_ops == 3
|
|
449
|
+
assert outer_result._combined_output is not None
|
|
451
450
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
451
|
+
disconnect_all(state)
|
|
452
|
+
finally:
|
|
453
|
+
pyinfra.is_cli = False
|
|
455
454
|
|
|
456
455
|
|
|
457
456
|
class TestOperationFailures(PatchSSHTestCase):
|
|
@@ -519,40 +518,40 @@ class TestOperationOrdering(PatchSSHTestCase):
|
|
|
519
518
|
state.current_deploy_filename = __file__
|
|
520
519
|
|
|
521
520
|
pyinfra.is_cli = True
|
|
522
|
-
ctx_state.set(state)
|
|
523
521
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
522
|
+
with ctx_state.use(state):
|
|
523
|
+
# Add op to both hosts
|
|
524
|
+
for name in ("anotherhost", "somehost"):
|
|
525
|
+
with ctx_host.use(inventory.get_host(name)):
|
|
526
|
+
server.shell("echo hi") # note this is called twice but on *the same line*
|
|
528
527
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
528
|
+
# Add op to just the second host - using the context modules such that
|
|
529
|
+
# it replicates a deploy file.
|
|
530
|
+
ctx_host.set(inventory.get_host("anotherhost"))
|
|
531
|
+
first_context_hash = server.user("anotherhost_user")._hash
|
|
533
532
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
533
|
+
# Add op to just the first host - using the context modules such that
|
|
534
|
+
# it replicates a deploy file.
|
|
535
|
+
ctx_host.set(inventory.get_host("somehost"))
|
|
536
|
+
second_context_hash = server.user("somehost_user")._hash
|
|
538
537
|
|
|
539
|
-
|
|
540
|
-
|
|
538
|
+
ctx_state.reset()
|
|
539
|
+
ctx_host.reset()
|
|
541
540
|
|
|
542
|
-
|
|
541
|
+
pyinfra.is_cli = False
|
|
543
542
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
543
|
+
print(state.ops)
|
|
544
|
+
# Ensure there are two ops
|
|
545
|
+
op_order = state.get_op_order()
|
|
546
|
+
assert len(op_order) == 3
|
|
548
547
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
548
|
+
# And that the two ops above were called in the expected order
|
|
549
|
+
assert op_order[1] == first_context_hash
|
|
550
|
+
assert op_order[2] == second_context_hash
|
|
552
551
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
552
|
+
# Ensure somehost has two ops and anotherhost only has the one
|
|
553
|
+
assert len(state.ops[inventory.get_host("somehost")]) == 2
|
|
554
|
+
assert len(state.ops[inventory.get_host("anotherhost")]) == 2
|
|
556
555
|
|
|
557
556
|
# In API mode, pyinfra *overrides* the line numbers such that whenever an
|
|
558
557
|
# operation or deploy is added it is simply appended. This makes sense as
|
tests/test_cli/test_cli.py
CHANGED
|
@@ -10,10 +10,10 @@ from .util import run_cli
|
|
|
10
10
|
class TestCliEagerFlags(TestCase):
|
|
11
11
|
def test_print_help(self):
|
|
12
12
|
result = run_cli("--version")
|
|
13
|
-
assert result.exit_code == 0, result.
|
|
13
|
+
assert result.exit_code == 0, result.stderr
|
|
14
14
|
|
|
15
15
|
result = run_cli("--help")
|
|
16
|
-
assert result.exit_code == 0, result.
|
|
16
|
+
assert result.exit_code == 0, result.stderr
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class TestOperationCli(PatchSSHTestCase):
|
|
@@ -22,7 +22,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
22
22
|
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
23
23
|
"not_a_module.shell",
|
|
24
24
|
)
|
|
25
|
-
assert result.exit_code == 1, result.
|
|
25
|
+
assert result.exit_code == 1, result.stderr
|
|
26
26
|
assert "No such module: not_a_module"
|
|
27
27
|
|
|
28
28
|
def test_invalid_operation_function(self):
|
|
@@ -30,7 +30,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
30
30
|
path.join("tests", "test_cli", "deploy", "inventories", "inventory.py"),
|
|
31
31
|
"server.not_an_operation",
|
|
32
32
|
)
|
|
33
|
-
assert result.exit_code == 1, result.
|
|
33
|
+
assert result.exit_code == 1, result.stderr
|
|
34
34
|
assert "No such operation: server.not_an_operation"
|
|
35
35
|
|
|
36
36
|
def test_deploy_operation(self):
|
|
@@ -40,7 +40,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
40
40
|
"server.shell",
|
|
41
41
|
"echo hi",
|
|
42
42
|
)
|
|
43
|
-
assert result.exit_code == 0, result.
|
|
43
|
+
assert result.exit_code == 0, result.stderr
|
|
44
44
|
|
|
45
45
|
def test_deploy_operation_with_all(self):
|
|
46
46
|
result = run_cli(
|
|
@@ -49,7 +49,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
49
49
|
"server.shell",
|
|
50
50
|
"echo hi",
|
|
51
51
|
)
|
|
52
|
-
assert result.exit_code == 0, result.
|
|
52
|
+
assert result.exit_code == 0, result.stderr
|
|
53
53
|
|
|
54
54
|
def test_deploy_operation_json_args(self):
|
|
55
55
|
result = run_cli(
|
|
@@ -58,7 +58,7 @@ class TestOperationCli(PatchSSHTestCase):
|
|
|
58
58
|
"server.shell",
|
|
59
59
|
'[["echo hi"], {}]',
|
|
60
60
|
)
|
|
61
|
-
assert result.exit_code == 0, result.
|
|
61
|
+
assert result.exit_code == 0, result.stderr
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
class TestFactCli(PatchSSHTestCase):
|
|
@@ -68,8 +68,8 @@ class TestFactCli(PatchSSHTestCase):
|
|
|
68
68
|
"fact",
|
|
69
69
|
"server.Os",
|
|
70
70
|
)
|
|
71
|
-
assert result.exit_code == 0, result.
|
|
72
|
-
assert '"somehost": null' in result.
|
|
71
|
+
assert result.exit_code == 0, result.stderr
|
|
72
|
+
assert '"somehost": null' in result.stderr
|
|
73
73
|
|
|
74
74
|
def test_get_fact_with_kwargs(self):
|
|
75
75
|
result = run_cli(
|
|
@@ -78,8 +78,8 @@ class TestFactCli(PatchSSHTestCase):
|
|
|
78
78
|
"files.File",
|
|
79
79
|
"path=.",
|
|
80
80
|
)
|
|
81
|
-
assert result.exit_code == 0, result.
|
|
82
|
-
assert '"somehost": null' in result.
|
|
81
|
+
assert result.exit_code == 0, result.stderr
|
|
82
|
+
assert '"somehost": null' in result.stderr
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
class TestExecCli(PatchSSHTestCase):
|
|
@@ -90,7 +90,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
90
90
|
"--",
|
|
91
91
|
"echo hi",
|
|
92
92
|
)
|
|
93
|
-
assert result.exit_code == 0, result.
|
|
93
|
+
assert result.exit_code == 0, result.stderr
|
|
94
94
|
|
|
95
95
|
def test_exec_command_with_options(self):
|
|
96
96
|
result = run_cli(
|
|
@@ -108,7 +108,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
108
108
|
"--",
|
|
109
109
|
"echo hi",
|
|
110
110
|
)
|
|
111
|
-
assert result.exit_code == 0, result.
|
|
111
|
+
assert result.exit_code == 0, result.stderr
|
|
112
112
|
|
|
113
113
|
def test_exec_command_with_serial(self):
|
|
114
114
|
result = run_cli(
|
|
@@ -118,7 +118,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
118
118
|
"--",
|
|
119
119
|
"echo hi",
|
|
120
120
|
)
|
|
121
|
-
assert result.exit_code == 0, result.
|
|
121
|
+
assert result.exit_code == 0, result.stderr
|
|
122
122
|
|
|
123
123
|
def test_exec_command_with_no_wait(self):
|
|
124
124
|
result = run_cli(
|
|
@@ -128,7 +128,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
128
128
|
"--",
|
|
129
129
|
"echo hi",
|
|
130
130
|
)
|
|
131
|
-
assert result.exit_code == 0, result.
|
|
131
|
+
assert result.exit_code == 0, result.stderr
|
|
132
132
|
|
|
133
133
|
def test_exec_command_with_debug_operations(self):
|
|
134
134
|
result = run_cli(
|
|
@@ -138,7 +138,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
138
138
|
"--",
|
|
139
139
|
"echo hi",
|
|
140
140
|
)
|
|
141
|
-
assert result.exit_code == 0, result.
|
|
141
|
+
assert result.exit_code == 0, result.stderr
|
|
142
142
|
|
|
143
143
|
def test_exec_command_with_debug_facts(self):
|
|
144
144
|
result = run_cli(
|
|
@@ -148,7 +148,7 @@ class TestExecCli(PatchSSHTestCase):
|
|
|
148
148
|
"--",
|
|
149
149
|
"echo hi",
|
|
150
150
|
)
|
|
151
|
-
assert result.exit_code == 0, result.
|
|
151
|
+
assert result.exit_code == 0, result.stderr
|
|
152
152
|
|
|
153
153
|
|
|
154
154
|
class TestDirectMainExecution(PatchSSHTestCase):
|
|
@@ -80,7 +80,7 @@ class TestCliInventory(PatchSSHTestCase):
|
|
|
80
80
|
assert result.exit_code == 0, result.stdout
|
|
81
81
|
assert (
|
|
82
82
|
'Ignoring variable "_hosts" in inventory file since it starts with a leading underscore'
|
|
83
|
-
in result.
|
|
83
|
+
in result.stderr
|
|
84
84
|
)
|
|
85
85
|
assert inventory.hosts == {}
|
|
86
86
|
|
|
@@ -97,9 +97,9 @@ class TestCliInventory(PatchSSHTestCase):
|
|
|
97
97
|
)
|
|
98
98
|
|
|
99
99
|
assert result.exit_code == 0, result.stdout
|
|
100
|
-
assert 'Ignoring variable "dict_hosts" in inventory file' in result.
|
|
100
|
+
assert 'Ignoring variable "dict_hosts" in inventory file' in result.stderr, result.stdout
|
|
101
101
|
assert (
|
|
102
|
-
'Ignoring variable "generator_hosts" in inventory file' in result.
|
|
102
|
+
'Ignoring variable "generator_hosts" in inventory file' in result.stderr
|
|
103
103
|
), result.stdout
|
|
104
104
|
assert inventory.hosts == {}
|
|
105
105
|
|
|
@@ -115,5 +115,5 @@ class TestCliInventory(PatchSSHTestCase):
|
|
|
115
115
|
)
|
|
116
116
|
|
|
117
117
|
assert result.exit_code == 0, result.stdout
|
|
118
|
-
assert 'Ignoring host group "issue_662"' in result.
|
|
118
|
+
assert 'Ignoring host group "issue_662"' in result.stderr, result.stdout
|
|
119
119
|
assert inventory.hosts == {}
|
|
@@ -5,8 +5,8 @@ from pyinfra.api import Host, Inventory
|
|
|
5
5
|
from pyinfra.context import ctx_host, ctx_inventory
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def _create_host():
|
|
9
|
-
return Host(
|
|
8
|
+
def _create_host(name: str = "host"):
|
|
9
|
+
return Host(name, Inventory(([name], {})), None, None)
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class TestHostContextObject(TestCase):
|
|
@@ -15,44 +15,44 @@ class TestHostContextObject(TestCase):
|
|
|
15
15
|
|
|
16
16
|
def test_context_host_eq(self):
|
|
17
17
|
host_obj = _create_host()
|
|
18
|
-
ctx_host.
|
|
19
|
-
|
|
18
|
+
with ctx_host.use(host_obj):
|
|
19
|
+
assert host == host_obj
|
|
20
20
|
|
|
21
21
|
def test_context_host_repr(self):
|
|
22
|
-
host_obj = _create_host()
|
|
23
|
-
ctx_host.
|
|
24
|
-
|
|
22
|
+
host_obj = _create_host("somehost")
|
|
23
|
+
with ctx_host.use(host_obj):
|
|
24
|
+
assert repr(host) == "ContextObject(Host):Host(somehost)"
|
|
25
25
|
|
|
26
26
|
def test_context_host_str(self):
|
|
27
27
|
host_obj = _create_host()
|
|
28
|
-
ctx_host.
|
|
29
|
-
|
|
28
|
+
with ctx_host.use(host_obj):
|
|
29
|
+
assert str(host_obj) == "host"
|
|
30
30
|
|
|
31
31
|
def test_context_host_attr(self):
|
|
32
32
|
host_obj = _create_host()
|
|
33
|
-
ctx_host.set(host_obj)
|
|
34
33
|
|
|
35
34
|
with self.assertRaises(AttributeError):
|
|
36
35
|
host_obj.hello
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
with ctx_host.use(host_obj):
|
|
38
|
+
setattr(host, "hello", "world")
|
|
39
|
+
assert host_obj.hello == host.hello
|
|
40
40
|
|
|
41
41
|
def test_context_host_class_attr(self):
|
|
42
42
|
host_obj = _create_host()
|
|
43
|
-
ctx_host.set(host_obj)
|
|
44
|
-
assert ctx_host.isset() is True
|
|
45
43
|
|
|
46
|
-
with
|
|
47
|
-
|
|
44
|
+
with ctx_host.use(host_obj):
|
|
45
|
+
assert ctx_host.isset() is True
|
|
46
|
+
|
|
47
|
+
with self.assertRaises(AttributeError):
|
|
48
|
+
host_obj.hello
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
setattr(Host, "hello", "class_world")
|
|
51
|
+
setattr(host_obj, "hello", "instance_world")
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
assert host.hello == host.hello
|
|
53
54
|
|
|
54
55
|
# Reset and check fallback to class variable
|
|
55
|
-
ctx_host.reset()
|
|
56
56
|
assert ctx_host.isset() is False
|
|
57
57
|
assert host.hello == "class_world"
|
|
58
58
|
|
|
@@ -60,14 +60,14 @@ class TestHostContextObject(TestCase):
|
|
|
60
60
|
class TestInventoryContextObject(TestCase):
|
|
61
61
|
def test_context_inventory_len(self):
|
|
62
62
|
inventory_obj = Inventory(("host", "anotherhost"))
|
|
63
|
-
ctx_inventory.set(inventory_obj)
|
|
64
|
-
assert ctx_inventory.isset() is True
|
|
65
63
|
|
|
66
|
-
|
|
64
|
+
with ctx_inventory.use(inventory_obj):
|
|
65
|
+
assert ctx_inventory.isset() is True
|
|
66
|
+
assert len(inventory) == len(inventory_obj)
|
|
67
67
|
|
|
68
68
|
def test_context_inventory_iter(self):
|
|
69
69
|
inventory_obj = Inventory(("host", "anotherhost"))
|
|
70
|
-
ctx_inventory.set(inventory_obj)
|
|
71
|
-
assert ctx_inventory.isset() is True
|
|
72
70
|
|
|
73
|
-
|
|
71
|
+
with ctx_inventory.use(inventory_obj):
|
|
72
|
+
assert ctx_inventory.isset() is True
|
|
73
|
+
assert list(iter(inventory)) == list(iter(inventory_obj))
|
|
@@ -11,26 +11,13 @@ from pyinfra.connectors.util import make_unix_command
|
|
|
11
11
|
from ..util import make_inventory
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
class TestContainerConnector(TestCase):
|
|
15
|
+
# we use this class as a template to prevent the decorators from being invoked twice on
|
|
16
|
+
# the podman test class (since it needs to override fake_docker_shell)
|
|
17
|
+
__test__ = False # this class should not be tested.
|
|
18
|
+
cli_cmd = "docker"
|
|
19
|
+
connector_name = "docker"
|
|
20
20
|
|
|
21
|
-
if command == "docker rm -f containerid":
|
|
22
|
-
return []
|
|
23
|
-
|
|
24
|
-
raise PyinfraError("Invalid command: {0}".format(command))
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@patch("pyinfra.connectors.docker.local.shell", fake_docker_shell)
|
|
28
|
-
@patch("pyinfra.connectors.docker.mkstemp", lambda: (None, "__tempfile__"))
|
|
29
|
-
@patch("pyinfra.connectors.docker.os.remove", lambda f: None)
|
|
30
|
-
@patch("pyinfra.connectors.docker.os.close", lambda f: None)
|
|
31
|
-
@patch("pyinfra.connectors.docker.open", mock_open(read_data="test!"), create=True)
|
|
32
|
-
@patch("pyinfra.api.util.open", mock_open(read_data="test!"), create=True)
|
|
33
|
-
class TestDockerConnector(TestCase):
|
|
34
21
|
def setUp(self):
|
|
35
22
|
self.fake_popen_patch = patch("pyinfra.connectors.util.Popen")
|
|
36
23
|
self.fake_popen_mock = self.fake_popen_patch.start()
|
|
@@ -40,46 +27,46 @@ class TestDockerConnector(TestCase):
|
|
|
40
27
|
|
|
41
28
|
def test_missing_image(self):
|
|
42
29
|
with self.assertRaises(InventoryError):
|
|
43
|
-
make_inventory(hosts=("@
|
|
30
|
+
make_inventory(hosts=(f"@{self.connector_name}",))
|
|
44
31
|
|
|
45
32
|
def test_user_provided_container_id(self):
|
|
46
33
|
inventory = make_inventory(
|
|
47
|
-
hosts=(("@
|
|
34
|
+
hosts=((f"@{self.connector_name}/not-an-image", {"docker_container_id": "abc"}),),
|
|
48
35
|
)
|
|
49
36
|
State(inventory, Config())
|
|
50
|
-
host = inventory.get_host("@
|
|
37
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
51
38
|
host.connect()
|
|
52
39
|
assert host.data.docker_container_id == "abc"
|
|
53
40
|
|
|
54
41
|
def test_connect_all(self):
|
|
55
|
-
inventory = make_inventory(hosts=("@
|
|
42
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
56
43
|
state = State(inventory, Config())
|
|
57
44
|
connect_all(state)
|
|
58
45
|
assert len(state.active_hosts) == 1
|
|
59
46
|
|
|
60
47
|
def test_connect_all_error(self):
|
|
61
|
-
inventory = make_inventory(hosts=("@
|
|
48
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/a-broken-image",))
|
|
62
49
|
state = State(inventory, Config())
|
|
63
50
|
|
|
64
51
|
with self.assertRaises(PyinfraError):
|
|
65
52
|
connect_all(state)
|
|
66
53
|
|
|
67
54
|
def test_connect_disconnect_host(self):
|
|
68
|
-
inventory = make_inventory(hosts=("@
|
|
55
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
69
56
|
state = State(inventory, Config())
|
|
70
|
-
host = inventory.get_host("@
|
|
57
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
71
58
|
host.connect(reason=True)
|
|
72
59
|
assert len(state.active_hosts) == 0
|
|
73
60
|
host.disconnect()
|
|
74
61
|
|
|
75
62
|
def test_run_shell_command(self):
|
|
76
|
-
inventory = make_inventory(hosts=("@
|
|
63
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
77
64
|
State(inventory, Config())
|
|
78
65
|
|
|
79
66
|
command = "echo hi"
|
|
80
67
|
self.fake_popen_mock().returncode = 0
|
|
81
68
|
|
|
82
|
-
host = inventory.get_host("@
|
|
69
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
83
70
|
host.connect()
|
|
84
71
|
out = host.run_shell_command(
|
|
85
72
|
command,
|
|
@@ -92,7 +79,7 @@ class TestDockerConnector(TestCase):
|
|
|
92
79
|
|
|
93
80
|
command = make_unix_command(command).get_raw_value()
|
|
94
81
|
command = shlex.quote(command)
|
|
95
|
-
docker_command = "
|
|
82
|
+
docker_command = f"{self.cli_cmd} exec -it containerid sh -c {command}"
|
|
96
83
|
shell_command = make_unix_command(docker_command).get_raw_value()
|
|
97
84
|
|
|
98
85
|
self.fake_popen_mock.assert_called_with(
|
|
@@ -104,34 +91,34 @@ class TestDockerConnector(TestCase):
|
|
|
104
91
|
)
|
|
105
92
|
|
|
106
93
|
def test_run_shell_command_success_exit_codes(self):
|
|
107
|
-
inventory = make_inventory(hosts=("@
|
|
94
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
108
95
|
State(inventory, Config())
|
|
109
96
|
|
|
110
97
|
command = "echo hi"
|
|
111
98
|
self.fake_popen_mock().returncode = 1
|
|
112
99
|
|
|
113
|
-
host = inventory.get_host("@
|
|
100
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
114
101
|
host.connect()
|
|
115
102
|
out = host.run_shell_command(command, _success_exit_codes=[1])
|
|
116
103
|
assert out[0] is True
|
|
117
104
|
|
|
118
105
|
def test_run_shell_command_error(self):
|
|
119
|
-
inventory = make_inventory(hosts=("@
|
|
106
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
120
107
|
state = State(inventory, Config())
|
|
121
108
|
|
|
122
109
|
command = "echo hi"
|
|
123
110
|
self.fake_popen_mock().returncode = 1
|
|
124
111
|
|
|
125
|
-
host = inventory.get_host("@
|
|
112
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
126
113
|
host.connect(state)
|
|
127
114
|
out = host.run_shell_command(command)
|
|
128
115
|
assert out[0] is False
|
|
129
116
|
|
|
130
117
|
def test_put_file(self):
|
|
131
|
-
inventory = make_inventory(hosts=("@
|
|
118
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
132
119
|
State(inventory, Config())
|
|
133
120
|
|
|
134
|
-
host = inventory.get_host("@
|
|
121
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
135
122
|
host.connect()
|
|
136
123
|
|
|
137
124
|
fake_process = MagicMock(returncode=0)
|
|
@@ -140,7 +127,7 @@ class TestDockerConnector(TestCase):
|
|
|
140
127
|
host.put_file("not-a-file", "not-another-file", print_output=True)
|
|
141
128
|
|
|
142
129
|
self.fake_popen_mock.assert_called_with(
|
|
143
|
-
"sh -c '
|
|
130
|
+
f"sh -c '{self.cli_cmd} cp __tempfile__ containerid:not-another-file'",
|
|
144
131
|
shell=True,
|
|
145
132
|
stdout=PIPE,
|
|
146
133
|
stderr=PIPE,
|
|
@@ -148,10 +135,10 @@ class TestDockerConnector(TestCase):
|
|
|
148
135
|
)
|
|
149
136
|
|
|
150
137
|
def test_put_file_error(self):
|
|
151
|
-
inventory = make_inventory(hosts=("@
|
|
138
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
152
139
|
State(inventory, Config())
|
|
153
140
|
|
|
154
|
-
host = inventory.get_host("@
|
|
141
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
155
142
|
host.connect()
|
|
156
143
|
|
|
157
144
|
fake_process = MagicMock(returncode=1)
|
|
@@ -161,10 +148,10 @@ class TestDockerConnector(TestCase):
|
|
|
161
148
|
host.put_file("not-a-file", "not-another-file", print_output=True)
|
|
162
149
|
|
|
163
150
|
def test_get_file(self):
|
|
164
|
-
inventory = make_inventory(hosts=("@
|
|
151
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
165
152
|
State(inventory, Config())
|
|
166
153
|
|
|
167
|
-
host = inventory.get_host("@
|
|
154
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
168
155
|
host.connect()
|
|
169
156
|
|
|
170
157
|
fake_process = MagicMock(returncode=0)
|
|
@@ -173,7 +160,7 @@ class TestDockerConnector(TestCase):
|
|
|
173
160
|
host.get_file("not-a-file", "not-another-file", print_output=True)
|
|
174
161
|
|
|
175
162
|
self.fake_popen_mock.assert_called_with(
|
|
176
|
-
"sh -c '
|
|
163
|
+
f"sh -c '{self.cli_cmd} cp containerid:not-a-file __tempfile__'",
|
|
177
164
|
shell=True,
|
|
178
165
|
stdout=PIPE,
|
|
179
166
|
stderr=PIPE,
|
|
@@ -181,10 +168,10 @@ class TestDockerConnector(TestCase):
|
|
|
181
168
|
)
|
|
182
169
|
|
|
183
170
|
def test_get_file_error(self):
|
|
184
|
-
inventory = make_inventory(hosts=("@
|
|
171
|
+
inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
|
|
185
172
|
State(inventory, Config())
|
|
186
173
|
|
|
187
|
-
host = inventory.get_host("@
|
|
174
|
+
host = inventory.get_host(f"@{self.connector_name}/not-an-image")
|
|
188
175
|
host.connect()
|
|
189
176
|
|
|
190
177
|
fake_process = MagicMock(returncode=1)
|
|
@@ -192,3 +179,56 @@ class TestDockerConnector(TestCase):
|
|
|
192
179
|
|
|
193
180
|
with self.assertRaises(IOError):
|
|
194
181
|
host.get_file("not-a-file", "not-another-file", print_output=True)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# Reuse the container testing code for docker and podman
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def fake_docker_shell(command, splitlines=None):
|
|
188
|
+
if command == "docker run -d not-an-image tail -f /dev/null":
|
|
189
|
+
return ["containerid"]
|
|
190
|
+
|
|
191
|
+
if command == "docker commit containerid":
|
|
192
|
+
return ["sha256:blahsomerandomstringdata"]
|
|
193
|
+
|
|
194
|
+
if command == "docker rm -f containerid":
|
|
195
|
+
return []
|
|
196
|
+
|
|
197
|
+
raise PyinfraError("Invalid command: {0}".format(command))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@patch("pyinfra.connectors.docker.local.shell", fake_docker_shell)
|
|
201
|
+
@patch("pyinfra.connectors.docker.mkstemp", lambda: (None, "__tempfile__"))
|
|
202
|
+
@patch("pyinfra.connectors.docker.os.remove", lambda f: None)
|
|
203
|
+
@patch("pyinfra.connectors.docker.os.close", lambda f: None)
|
|
204
|
+
@patch("pyinfra.connectors.docker.open", mock_open(read_data="test!"), create=True)
|
|
205
|
+
@patch("pyinfra.api.util.open", mock_open(read_data="test!"), create=True)
|
|
206
|
+
class TestDocker2Connector(TestContainerConnector):
|
|
207
|
+
__test__ = True
|
|
208
|
+
cli_cmd = "docker"
|
|
209
|
+
connector_name = "docker"
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def fake_podman_shell(command, splitlines=None):
|
|
213
|
+
if command == "podman run -d not-an-image tail -f /dev/null":
|
|
214
|
+
return ["containerid"]
|
|
215
|
+
|
|
216
|
+
if command == "podman commit containerid":
|
|
217
|
+
return ["sha256:blahsomerandomstringdata"]
|
|
218
|
+
|
|
219
|
+
if command == "podman rm -f containerid":
|
|
220
|
+
return []
|
|
221
|
+
|
|
222
|
+
raise PyinfraError("Invalid command: {0}".format(command))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@patch("pyinfra.connectors.docker.local.shell", fake_podman_shell)
|
|
226
|
+
@patch("pyinfra.connectors.docker.mkstemp", lambda: (None, "__tempfile__"))
|
|
227
|
+
@patch("pyinfra.connectors.docker.os.remove", lambda f: None)
|
|
228
|
+
@patch("pyinfra.connectors.docker.os.close", lambda f: None)
|
|
229
|
+
@patch("pyinfra.connectors.docker.open", mock_open(read_data="test!"), create=True)
|
|
230
|
+
@patch("pyinfra.api.util.open", mock_open(read_data="test!"), create=True)
|
|
231
|
+
class TestPodmanConnector(TestContainerConnector):
|
|
232
|
+
__test__ = True
|
|
233
|
+
cli_cmd = "podman"
|
|
234
|
+
connector_name = "podman"
|