pyinfra 3.3.1__py2.py3-none-any.whl → 3.4__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 (45) hide show
  1. pyinfra/api/arguments.py +8 -16
  2. pyinfra/api/deploy.py +1 -1
  3. pyinfra/api/facts.py +10 -26
  4. pyinfra/api/host.py +10 -4
  5. pyinfra/api/inventory.py +5 -2
  6. pyinfra/api/operation.py +1 -1
  7. pyinfra/api/util.py +20 -6
  8. pyinfra/connectors/docker.py +117 -38
  9. pyinfra/connectors/dockerssh.py +1 -0
  10. pyinfra/connectors/local.py +1 -0
  11. pyinfra/connectors/ssh.py +1 -0
  12. pyinfra/connectors/terraform.py +3 -0
  13. pyinfra/connectors/vagrant.py +3 -0
  14. pyinfra/context.py +14 -5
  15. pyinfra/facts/brew.py +1 -0
  16. pyinfra/facts/docker.py +6 -2
  17. pyinfra/facts/git.py +10 -0
  18. pyinfra/facts/hardware.py +1 -1
  19. pyinfra/facts/opkg.py +1 -0
  20. pyinfra/facts/server.py +81 -23
  21. pyinfra/facts/systemd.py +1 -1
  22. pyinfra/operations/crontab.py +7 -5
  23. pyinfra/operations/docker.py +2 -0
  24. pyinfra/operations/files.py +64 -21
  25. pyinfra/operations/flatpak.py +17 -2
  26. pyinfra/operations/git.py +6 -2
  27. pyinfra/operations/server.py +34 -24
  28. pyinfra/operations/util/docker.py +4 -0
  29. pyinfra/operations/util/files.py +44 -3
  30. {pyinfra-3.3.1.dist-info → pyinfra-3.4.dist-info}/METADATA +5 -4
  31. {pyinfra-3.3.1.dist-info → pyinfra-3.4.dist-info}/RECORD +45 -45
  32. {pyinfra-3.3.1.dist-info → pyinfra-3.4.dist-info}/entry_points.txt +1 -0
  33. pyinfra_cli/inventory.py +1 -1
  34. pyinfra_cli/main.py +4 -2
  35. tests/test_api/test_api_arguments.py +25 -20
  36. tests/test_api/test_api_facts.py +28 -15
  37. tests/test_api/test_api_operations.py +43 -44
  38. tests/test_cli/test_cli.py +17 -17
  39. tests/test_cli/test_cli_inventory.py +4 -4
  40. tests/test_cli/test_context_objects.py +26 -26
  41. tests/test_connectors/test_docker.py +83 -43
  42. tests/test_connectors/test_ssh.py +153 -132
  43. {pyinfra-3.3.1.dist-info → pyinfra-3.4.dist-info}/LICENSE.md +0 -0
  44. {pyinfra-3.3.1.dist-info → pyinfra-3.4.dist-info}/WHEEL +0 -0
  45. {pyinfra-3.3.1.dist-info → pyinfra-3.4.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
- try:
435
- outer_result = server.shell(commands="echo outer")
436
- assert outer_result._combined_output is None
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
- def callback():
439
- inner_result = server.shell(commands="echo inner")
440
- assert inner_result._combined_output is not None
437
+ def callback():
438
+ inner_result = server.shell(commands="echo inner")
439
+ assert inner_result._combined_output is not None
441
440
 
442
- python.call(function=callback)
441
+ python.call(function=callback)
443
442
 
444
- assert len(state.get_op_order()) == 2
443
+ assert len(state.get_op_order()) == 2
445
444
 
446
- run_ops(state)
445
+ run_ops(state)
447
446
 
448
- assert len(state.get_op_order()) == 3
449
- assert state.results[somehost].success_ops == 3
450
- assert outer_result._combined_output is not None
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
- disconnect_all(state)
453
- finally:
454
- pyinfra.is_cli = False
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
- # Add op to both hosts
525
- for name in ("anotherhost", "somehost"):
526
- ctx_host.set(inventory.get_host(name))
527
- server.shell("echo hi") # note this is called twice but on *the same line*
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
- # Add op to just the second host - using the context modules such that
530
- # it replicates a deploy file.
531
- ctx_host.set(inventory.get_host("anotherhost"))
532
- first_context_hash = server.user("anotherhost_user")._hash
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
- # Add op to just the first host - using the context modules such that
535
- # it replicates a deploy file.
536
- ctx_host.set(inventory.get_host("somehost"))
537
- second_context_hash = server.user("somehost_user")._hash
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
- ctx_state.reset()
540
- ctx_host.reset()
538
+ ctx_state.reset()
539
+ ctx_host.reset()
541
540
 
542
- pyinfra.is_cli = False
541
+ pyinfra.is_cli = False
543
542
 
544
- print(state.ops)
545
- # Ensure there are two ops
546
- op_order = state.get_op_order()
547
- assert len(op_order) == 3
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
- # And that the two ops above were called in the expected order
550
- assert op_order[1] == first_context_hash
551
- assert op_order[2] == second_context_hash
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
- # Ensure somehost has two ops and anotherhost only has the one
554
- assert len(state.ops[inventory.get_host("somehost")]) == 2
555
- assert len(state.ops[inventory.get_host("anotherhost")]) == 2
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
@@ -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.stdout
13
+ assert result.exit_code == 0, result.stderr
14
14
 
15
15
  result = run_cli("--help")
16
- assert result.exit_code == 0, result.stdout
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.stdout
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.stdout
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.stdout
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.stdout
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.stdout
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.stdout
72
- assert '"somehost": null' in result.stdout
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.stdout
82
- assert '"somehost": null' in result.stdout
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.stdout
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.stdout
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.stdout
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.stdout
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.stdout
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.stdout
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.stdout
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.stdout, result.stdout
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.stdout
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.stdout, result.stdout
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(None, None, None, None)
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.set(host_obj)
19
- assert host == host_obj
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.set(host_obj)
24
- assert repr(host) == "ContextObject(Host):Host(None)"
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.set(host_obj)
29
- assert str(host_obj) == "None"
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
- setattr(host, "hello", "world")
39
- assert host_obj.hello == host.hello
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 self.assertRaises(AttributeError):
47
- host_obj.hello
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
- setattr(Host, "hello", "class_world")
50
- setattr(host_obj, "hello", "instance_world")
50
+ setattr(Host, "hello", "class_world")
51
+ setattr(host_obj, "hello", "instance_world")
51
52
 
52
- assert host.hello == host.hello
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
- assert len(inventory) == len(inventory_obj)
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
- assert list(iter(inventory)) == list(iter(inventory_obj))
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
- def fake_docker_shell(command, splitlines=None):
15
- if command == "docker run -d not-an-image tail -f /dev/null":
16
- return ["containerid"]
17
-
18
- if command == "docker commit containerid":
19
- return ["sha256:blahsomerandomstringdata"]
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=("@docker",))
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=(("@docker/not-an-image", {"docker_container_id": "abc"}),),
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("@docker/not-an-image")
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=("@docker/not-an-image",))
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=("@docker/a-broken-image",))
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=("@docker/not-an-image",))
55
+ inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
69
56
  state = State(inventory, Config())
70
- host = inventory.get_host("@docker/not-an-image")
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=("@docker/not-an-image",))
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("@docker/not-an-image")
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 = "docker exec -it containerid sh -c {0}".format(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=("@docker/not-an-image",))
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("@docker/not-an-image")
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=("@docker/not-an-image",))
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("@docker/not-an-image")
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=("@docker/not-an-image",))
118
+ inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
132
119
  State(inventory, Config())
133
120
 
134
- host = inventory.get_host("@docker/not-an-image")
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 'docker cp __tempfile__ containerid:not-another-file'",
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=("@docker/not-an-image",))
138
+ inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
152
139
  State(inventory, Config())
153
140
 
154
- host = inventory.get_host("@docker/not-an-image")
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=("@docker/not-an-image",))
151
+ inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
165
152
  State(inventory, Config())
166
153
 
167
- host = inventory.get_host("@docker/not-an-image")
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 'docker cp containerid:not-a-file __tempfile__'",
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=("@docker/not-an-image",))
171
+ inventory = make_inventory(hosts=(f"@{self.connector_name}/not-an-image",))
185
172
  State(inventory, Config())
186
173
 
187
- host = inventory.get_host("@docker/not-an-image")
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"