pyinfra 2.9.1__py2.py3-none-any.whl → 3.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.1.dist-info/RECORD +0 -170
  151. pyinfra-2.9.1.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
@@ -3,16 +3,10 @@ from unittest import TestCase
3
3
  from unittest.mock import patch
4
4
 
5
5
  from pyinfra.api.exceptions import InventoryError
6
- from pyinfra.connectors.terraform import make_names_data
6
+ from pyinfra.connectors.terraform import TerraformInventoryConnector
7
7
 
8
8
 
9
- class TestVagrantConnector(TestCase):
10
- def test_make_names_data_no_output_key(self):
11
- with self.assertRaises(InventoryError) as context:
12
- list(make_names_data())
13
-
14
- assert context.exception.args[0] == "No Terraform output key!"
15
-
9
+ class TestTerraformConnector(TestCase):
16
10
  @patch("pyinfra.connectors.terraform.local.shell")
17
11
  def test_make_names_data_no_output(self, fake_shell):
18
12
  fake_shell.return_value = json.dumps(
@@ -24,7 +18,7 @@ class TestVagrantConnector(TestCase):
24
18
  )
25
19
 
26
20
  with self.assertRaises(InventoryError) as context:
27
- list(make_names_data("output_key"))
21
+ list(TerraformInventoryConnector.make_names_data("output_key"))
28
22
 
29
23
  assert (
30
24
  context.exception.args[0]
@@ -36,7 +30,7 @@ class TestVagrantConnector(TestCase):
36
30
  fake_shell.return_value = json.dumps({"output_key": "wrongvalue"})
37
31
 
38
32
  with self.assertRaises(InventoryError) as context:
39
- list(make_names_data("output_key"))
33
+ list(TerraformInventoryConnector.make_names_data("output_key"))
40
34
 
41
35
  assert (
42
36
  context.exception.args[0]
@@ -48,7 +42,7 @@ class TestVagrantConnector(TestCase):
48
42
  fake_shell.return_value = json.dumps({"output_key": [None]})
49
43
 
50
44
  with self.assertRaises(InventoryError) as context:
51
- list(make_names_data("output_key"))
45
+ list(TerraformInventoryConnector.make_names_data("output_key"))
52
46
 
53
47
  assert (
54
48
  context.exception.args[0]
@@ -58,7 +52,7 @@ class TestVagrantConnector(TestCase):
58
52
  @patch("pyinfra.connectors.terraform.local.shell")
59
53
  def test_make_names_data(self, fake_shell):
60
54
  fake_shell.return_value = json.dumps({"output_key": ["somehost"]})
61
- data = list(make_names_data("output_key"))
55
+ data = list(TerraformInventoryConnector.make_names_data("output_key"))
62
56
 
63
57
  assert data == [
64
58
  (
@@ -71,7 +65,7 @@ class TestVagrantConnector(TestCase):
71
65
  @patch("pyinfra.connectors.terraform.local.shell")
72
66
  def test_make_names_data_nested(self, fake_shell):
73
67
  fake_shell.return_value = json.dumps({"output_key": {"nested_key": ["somehost"]}})
74
- data = list(make_names_data("output_key.nested_key"))
68
+ data = list(TerraformInventoryConnector.make_names_data("output_key.nested_key"))
75
69
 
76
70
  assert data == [
77
71
  (
@@ -88,7 +82,7 @@ class TestVagrantConnector(TestCase):
88
82
  "ssh_hostname": "hostname",
89
83
  }
90
84
  fake_shell.return_value = json.dumps({"output_key": [host]})
91
- data = list(make_names_data("output_key"))
85
+ data = list(TerraformInventoryConnector.make_names_data("output_key"))
92
86
 
93
87
  assert data == [
94
88
  (
@@ -106,7 +100,7 @@ class TestVagrantConnector(TestCase):
106
100
  fake_shell.return_value = json.dumps({"output_key": [host]})
107
101
 
108
102
  with self.assertRaises(InventoryError) as context:
109
- list(make_names_data("output_key"))
103
+ list(TerraformInventoryConnector.make_names_data("output_key"))
110
104
 
111
105
  assert (
112
106
  context.exception.args[0]
@@ -3,90 +3,68 @@
3
3
  from unittest import TestCase
4
4
 
5
5
  from pyinfra.api import Config, State
6
- from pyinfra.connectors.util import (
7
- make_unix_command,
8
- make_unix_command_for_host,
9
- split_combined_output,
10
- )
6
+ from pyinfra.connectors.util import make_unix_command, make_unix_command_for_host
11
7
 
12
8
  from ..util import make_inventory
13
9
 
14
10
 
15
- class TestConnectorUtil(TestCase):
16
- def test_split_combined_output_works(self):
17
- results = split_combined_output(
18
- [
19
- ("stdout", "stdout1"),
20
- ("stdout", "stdout2"),
21
- ("stderr", "stderr1"),
22
- ("stdout", "stdout3"),
23
- ],
24
- )
25
-
26
- assert results == (["stdout1", "stdout2", "stdout3"], ["stderr1"])
27
-
28
- def test_split_combined_output_raises(self):
29
- with self.assertRaises(ValueError):
30
- split_combined_output(["nope", ""])
31
-
32
-
33
11
  class TestMakeUnixCommandConnectorUtil(TestCase):
34
12
  def test_command(self):
35
13
  command = make_unix_command("echo Šablony")
36
14
  assert command.get_raw_value() == "sh -c 'echo Šablony'"
37
15
 
38
16
  def test_doas_command(self):
39
- command = make_unix_command("uptime", doas=True)
17
+ command = make_unix_command("uptime", _doas=True)
40
18
  assert command.get_raw_value() == "doas -n sh -c uptime"
41
19
 
42
20
  def test_doas_user_command(self):
43
- command = make_unix_command("uptime", doas=True, doas_user="pyinfra")
21
+ command = make_unix_command("uptime", _doas=True, _doas_user="pyinfra")
44
22
  assert command.get_raw_value() == "doas -n -u pyinfra sh -c uptime"
45
23
 
46
24
  def test_sudo_command(self):
47
- command = make_unix_command("uptime", sudo=True)
25
+ command = make_unix_command("uptime", _sudo=True)
48
26
  assert command.get_raw_value() == "sudo -H -n sh -c uptime"
49
27
 
50
28
  def test_sudo_multi_arg_command(self):
51
- command = make_unix_command("echo hi", sudo=True, preserve_sudo_env=True)
29
+ command = make_unix_command("echo hi", _sudo=True, _preserve_sudo_env=True)
52
30
  assert command.get_raw_value() == "sudo -H -n -E sh -c 'echo hi'"
53
31
 
54
32
  def test_sudo_preserve_env_command(self):
55
- command = make_unix_command("uptime", sudo=True, preserve_sudo_env=True)
33
+ command = make_unix_command("uptime", _sudo=True, _preserve_sudo_env=True)
56
34
  assert command.get_raw_value() == "sudo -H -n -E sh -c uptime"
57
35
 
58
36
  def test_use_sudo_login_command(self):
59
- command = make_unix_command("uptime", sudo=True, use_sudo_login=True)
37
+ command = make_unix_command("uptime", _sudo=True, _use_sudo_login=True)
60
38
  assert command.get_raw_value() == "sudo -H -n -i sh -c uptime"
61
39
 
62
40
  def test_sudo_user_command(self):
63
- command = make_unix_command("uptime", sudo=True, sudo_user="pyinfra")
41
+ command = make_unix_command("uptime", _sudo=True, _sudo_user="pyinfra")
64
42
  assert command.get_raw_value() == "sudo -H -n -u pyinfra sh -c uptime"
65
43
 
66
44
  def test_su_command(self):
67
- command = make_unix_command("uptime", su_user="pyinfra")
45
+ command = make_unix_command("uptime", _su_user="pyinfra")
68
46
  assert command.get_raw_value() == "su pyinfra -c 'sh -c uptime'"
69
47
 
70
48
  def test_su_multi_arg_command(self):
71
- command = make_unix_command("echo hi", su_user="pyinfra")
49
+ command = make_unix_command("echo hi", _su_user="pyinfra")
72
50
  assert command.get_raw_value() == "su pyinfra -c 'sh -c '\"'\"'echo hi'\"'\"''"
73
51
 
74
52
  def test_use_su_login_command(self):
75
- command = make_unix_command("uptime", su_user="pyinfra", use_su_login=True)
53
+ command = make_unix_command("uptime", _su_user="pyinfra", _use_su_login=True)
76
54
  assert command.get_raw_value() == "su -l pyinfra -c 'sh -c uptime'"
77
55
 
78
56
  def test_preserve_su_env_command(self):
79
- command = make_unix_command("uptime", su_user="pyinfra", preserve_su_env=True)
57
+ command = make_unix_command("uptime", _su_user="pyinfra", _preserve_su_env=True)
80
58
  assert command.get_raw_value() == "su -m pyinfra -c 'sh -c uptime'"
81
59
 
82
60
  def test_su_shell_command(self):
83
- command = make_unix_command("uptime", su_user="pyinfra", su_shell="bash")
61
+ command = make_unix_command("uptime", _su_user="pyinfra", _su_shell="bash")
84
62
  assert command.get_raw_value() == "su -s `which bash` pyinfra -c 'sh -c uptime'"
85
63
 
86
64
  def test_command_env(self):
87
65
  command = make_unix_command(
88
66
  "uptime",
89
- env={
67
+ _env={
90
68
  "key": "value",
91
69
  "anotherkey": "anothervalue",
92
70
  },
@@ -97,23 +75,23 @@ class TestMakeUnixCommandConnectorUtil(TestCase):
97
75
  ]
98
76
 
99
77
  def test_command_chdir(self):
100
- command = make_unix_command("uptime", chdir="/opt/somedir")
78
+ command = make_unix_command("uptime", _chdir="/opt/somedir")
101
79
  assert command.get_raw_value() == "sh -c 'cd /opt/somedir && uptime'"
102
80
 
103
81
  def test_custom_shell_command(self):
104
- command = make_unix_command("uptime", shell_executable="bash")
82
+ command = make_unix_command("uptime", _shell_executable="bash")
105
83
  assert command.get_raw_value() == "bash -c uptime"
106
84
 
107
85
  def test_mixed_command(self):
108
86
  command = make_unix_command(
109
87
  "echo hi",
110
- chdir="/opt/somedir",
111
- env={"key": "value"},
112
- sudo=True,
113
- sudo_user="root",
114
- preserve_sudo_env=True,
115
- su_user="pyinfra",
116
- shell_executable="bash",
88
+ _chdir="/opt/somedir",
89
+ _env={"key": "value"},
90
+ _sudo=True,
91
+ _sudo_user="root",
92
+ _preserve_sudo_env=True,
93
+ _su_user="pyinfra",
94
+ _shell_executable="bash",
117
95
  )
118
96
  assert command.get_raw_value() == (
119
97
  "sudo -H -n -E -u root " # sudo bit
@@ -125,7 +103,7 @@ class TestMakeUnixCommandConnectorUtil(TestCase):
125
103
  def test_command_exists_su_config_only(self):
126
104
  """
127
105
  This tests covers a bug that appeared when `make_unix_command` is called
128
- with `su_user=False` (default) but `SU_USER` set on the config object,
106
+ with `_su_user=False` (default) but `SU_USER` set on the config object,
129
107
  resulting in an empty command output.
130
108
  """
131
109
  state = State(make_inventory(), Config(SU_USER=True))
@@ -3,7 +3,7 @@ from unittest import TestCase
3
3
  from unittest.mock import mock_open, patch
4
4
 
5
5
  from pyinfra.api.exceptions import InventoryError
6
- from pyinfra.connectors.vagrant import get_vagrant_options, make_names_data
6
+ from pyinfra.connectors.vagrant import VagrantInventoryConnector, get_vagrant_options
7
7
 
8
8
  FAKE_VAGRANT_OPTIONS = {
9
9
  "groups": {
@@ -69,13 +69,13 @@ class TestVagrantConnector(TestCase):
69
69
  )
70
70
  @patch("pyinfra.connectors.vagrant.path.exists", lambda path: True)
71
71
  def test_make_names_data_with_options(self):
72
- data = make_names_data()
72
+ data = list(VagrantInventoryConnector.make_names_data())
73
73
 
74
74
  assert data == [
75
75
  (
76
76
  "@vagrant/ubuntu16",
77
77
  {
78
- "ssh_port": "2222",
78
+ "ssh_port": 2222,
79
79
  "ssh_user": "vagrant",
80
80
  "ssh_hostname": "127.0.0.1",
81
81
  "ssh_key": "path/to/key",
@@ -85,7 +85,7 @@ class TestVagrantConnector(TestCase):
85
85
  (
86
86
  "@vagrant/centos7",
87
87
  {
88
- "ssh_port": "2200",
88
+ "ssh_port": 2200,
89
89
  "ssh_user": "vagrant",
90
90
  "ssh_hostname": "127.0.0.1",
91
91
  "ssh_key": "path/to/key",
@@ -103,13 +103,13 @@ class TestVagrantConnector(TestCase):
103
103
  ]
104
104
 
105
105
  def test_make_names_data_with_limit(self):
106
- data = make_names_data(limit=("ubuntu16",))
106
+ data = list(VagrantInventoryConnector.make_names_data(name=("ubuntu16",)))
107
107
 
108
108
  assert data == [
109
109
  (
110
110
  "@vagrant/ubuntu16",
111
111
  {
112
- "ssh_port": "2222",
112
+ "ssh_port": 2222,
113
113
  "ssh_user": "vagrant",
114
114
  "ssh_hostname": "127.0.0.1",
115
115
  "ssh_key": "path/to/key",
@@ -120,4 +120,4 @@ class TestVagrantConnector(TestCase):
120
120
 
121
121
  def test_make_names_data_no_matches(self):
122
122
  with self.assertRaises(InventoryError):
123
- make_names_data(limit="nope")
123
+ list(VagrantInventoryConnector.make_names_data(name="nope"))
pyinfra/api/operation.pyi DELETED
@@ -1,117 +0,0 @@
1
- from typing import (
2
- Callable,
3
- Dict,
4
- Generator,
5
- Generic,
6
- Iterable,
7
- List,
8
- Mapping,
9
- Protocol,
10
- Tuple,
11
- overload,
12
- )
13
-
14
- from typing_extensions import ParamSpec
15
-
16
- from pyinfra.api.command import (
17
- FileDownloadCommand,
18
- FileUploadCommand,
19
- FunctionCommand,
20
- StringCommand,
21
- )
22
- from pyinfra.api.host import Host
23
- from pyinfra.api.state import State
24
-
25
- P = ParamSpec("P")
26
-
27
- Command = str | StringCommand | FileDownloadCommand | FileUploadCommand | FunctionCommand
28
-
29
- class OperationMeta:
30
- changed: bool
31
- commands: List[str] | None
32
- hash: str | None
33
-
34
- stdout_lines: List[str]
35
- stdout: str
36
- stderr_lines: List[str]
37
- stderr: str
38
-
39
- class Operation(Generic[P], Protocol):
40
- def __call__(
41
- self,
42
- _sudo: bool | None = None,
43
- _sudo_user: str | None = None,
44
- _use_sudo_login: bool | None = None,
45
- _use_sudo_password: bool | None = None,
46
- _preserve_sudo_env: bool | None = None,
47
- _su_user: str | None = None,
48
- _use_su_login: bool | None = None,
49
- _preserve_su_env: bool | None = None,
50
- _su_shell: str | None = None,
51
- _doas: bool | None = None,
52
- _doas_user: str | None = None,
53
- _shell_executable: str | None = None,
54
- _chdir: str | None = None,
55
- _env: Mapping[str, str] | None = None,
56
- _success_exit_codes: Iterable[int] | None = None,
57
- _timeout: int | None = None,
58
- _get_pty: bool | None = None,
59
- _stdin: str | List[str] | Tuple[str, ...] | None = None,
60
- name: str | None = None,
61
- _ignore_errors: bool | None = None,
62
- _continue_on_error: bool | None = None,
63
- _precondition: str | None = None,
64
- _postcondition: str | None = None,
65
- _on_success: Callable[[State, Host, str], None] | None = None,
66
- _on_error: Callable[[State, Host, str], None] | None = None,
67
- _parallel: int | None = None,
68
- _run_once: bool | None = None,
69
- _serial: bool | None = None,
70
- *args: P.args,
71
- **kwargs: P.kwargs,
72
- ) -> OperationMeta: ...
73
-
74
- def add_op(
75
- state: State,
76
- op_func: Operation[P],
77
- _sudo: bool | None = None,
78
- _sudo_user: str | None = None,
79
- _use_sudo_login: bool | None = None,
80
- _use_sudo_password: bool | None = None,
81
- _preserve_sudo_env: bool | None = None,
82
- _su_user: str | None = None,
83
- _use_su_login: bool | None = None,
84
- _preserve_su_env: bool | None = None,
85
- _su_shell: str | None = None,
86
- _doas: bool | None = None,
87
- _doas_user: str | None = None,
88
- _shell_executable: str | None = None,
89
- _chdir: str | None = None,
90
- _env: Mapping[str, str] | None = None,
91
- _success_exit_codes: Iterable[int] | None = None,
92
- _timeout: int | None = None,
93
- _get_pty: bool | None = None,
94
- _stdin: str | List[str] | Tuple[str, ...] | None = None,
95
- name: str | None = None,
96
- _ignore_errors: bool | None = None,
97
- _continue_on_error: bool | None = None,
98
- _precondition: str | None = None,
99
- _postcondition: str | None = None,
100
- _on_success: Callable[[State, Host, str], None] | None = None,
101
- _on_error: Callable[[State, Host, str], None] | None = None,
102
- _parallel: int | None = None,
103
- _run_once: bool | None = None,
104
- _serial: bool | None = None,
105
- host: Iterable[Host] | Host | None = None,
106
- *args: P.args,
107
- **kwargs: P.kwargs,
108
- ) -> Dict[Host, OperationMeta]: ...
109
- @overload
110
- def operation(func: Callable[P, Generator[Command, None, None]]) -> Operation[P]: ...
111
- @overload
112
- def operation(
113
- pipeline_facts=None,
114
- is_idempotent: bool = True,
115
- idempotent_notice=None,
116
- frame_offset=1,
117
- ) -> Callable[[Callable[P, Generator[Command, None, None]]], Operation[P]]: ...
@@ -1,171 +0,0 @@
1
- """
2
- **Note**: this connector is a work in progress! While it parses the list of
3
- hosts OK, it doesn't handle nested groups properly yet.
4
-
5
- The `@ansible` connector can be used to parse Ansible inventory files.
6
-
7
- .. code:: python
8
-
9
- # Load an Ansible inventory relative to the current directory
10
- pyinfra @ansible/path/to/inventory
11
-
12
- # Load using an absolute path
13
- pyinfra @ansible//absolute/path/to/inventory
14
- """
15
- import json
16
- import re
17
- from collections import defaultdict
18
- from configparser import ConfigParser
19
- from os import path
20
- from typing import TYPE_CHECKING, Optional
21
-
22
- from pyinfra import logger
23
- from pyinfra.api.exceptions import InventoryError
24
- from pyinfra.api.util import memoize
25
-
26
- if TYPE_CHECKING:
27
- from pyinfra.api.host import Host
28
-
29
- try:
30
- import yaml
31
- except ImportError:
32
- yaml = None # type: ignore
33
-
34
-
35
- @memoize
36
- def show_warning():
37
- logger.warning("The @ansible connector is in alpha!")
38
-
39
-
40
- def make_names_data(inventory_filename: Optional[str] = None):
41
- show_warning()
42
-
43
- if not inventory_filename:
44
- raise InventoryError("No Ansible inventory filename provided!")
45
-
46
- if not path.exists(inventory_filename):
47
- raise InventoryError(
48
- ("Could not find Ansible inventory file: {0}").format(inventory_filename),
49
- )
50
-
51
- return parse_inventory(inventory_filename)
52
-
53
-
54
- def parse_inventory(inventory_filename: str):
55
- # fallback to INI if no extension
56
- extension = inventory_filename.split(".")[-1] if "." in inventory_filename else "ini"
57
-
58
- # host:set(groups) mapping
59
- host_to_groups = {}
60
-
61
- if extension in ["ini"]:
62
- host_to_groups = parse_inventory_ini(inventory_filename)
63
- elif extension in ["json"]:
64
- with open(inventory_filename, encoding="utf-8") as inventory_file:
65
- inventory_tree = json.load(inventory_file)
66
- # close file early
67
- host_to_groups = parse_inventory_tree(inventory_tree)
68
- elif extension in ["yaml", "yml"]:
69
- if yaml is None:
70
- raise Exception(
71
- (
72
- "To parse YAML Ansible inventories requires `pyyaml`. "
73
- "Install it with `pip install pyyaml`."
74
- ),
75
- )
76
- with open(inventory_filename, encoding="utf-8") as inventory_file:
77
- inventory_tree = yaml.safe_load(inventory_file)
78
- # close file early
79
- host_to_groups = parse_inventory_tree(inventory_tree)
80
- else:
81
- raise InventoryError(("Ansible inventory file format not supported: {0}").format(extension))
82
-
83
- return [(host, {}, sorted(list(host_to_groups.get(host, [])))) for host in host_to_groups]
84
-
85
-
86
- def parse_inventory_ini(inventory_filename: str):
87
- config = ConfigParser(
88
- delimiters=(" "), # we only handle the hostnames for now
89
- allow_no_value=True, # we don't by default have = values
90
- interpolation=None, # remove any Python interpolation
91
- )
92
- config.read(inventory_filename)
93
-
94
- host_to_groups = defaultdict(set)
95
- group_to_hosts = defaultdict(set)
96
- hosts = []
97
-
98
- # First pass - load hosts/groups of hosts
99
- for section in config.sections():
100
- if ":" in section: # ignore :children and :vars sections this time
101
- continue
102
-
103
- options = config.options(section)
104
- for host in _parse_ansible_hosts(options):
105
- hosts.append(host)
106
- host_to_groups[host].add(section)
107
- group_to_hosts[section].add(host)
108
-
109
- # Second pass - load any children groups
110
- for section in config.sections():
111
- if not section.endswith(":children"): # we only support :children for now
112
- continue
113
-
114
- group_name = section.replace(":children", "")
115
-
116
- options = config.options(section)
117
- for sub_group_name in options:
118
- sub_group_hosts = group_to_hosts[sub_group_name]
119
- for host in sub_group_hosts:
120
- host_to_groups[host].add(group_name)
121
-
122
- return host_to_groups
123
-
124
-
125
- def _parse_ansible_hosts(hosts):
126
- for host in hosts:
127
- expand_match = re.search(r"\[[0-9:]+\]", host)
128
- if expand_match:
129
- expand_string = host[expand_match.start() : expand_match.end()]
130
- bits = expand_string[1:-1].split(":") # remove the [] either side
131
-
132
- zfill = 0
133
- if bits[0].startswith("0"):
134
- zfill = len(bits[0])
135
-
136
- start, end = int(bits[0]), int(bits[1])
137
- step = int(bits[2]) if len(bits) > 2 else 1
138
-
139
- for n in range(start, end + 1, step):
140
- number_as_string = "{0}".format(n)
141
- if zfill:
142
- number_as_string = number_as_string.zfill(zfill)
143
-
144
- hostname = host.replace(expand_string, number_as_string)
145
- yield hostname
146
- else:
147
- yield host
148
-
149
-
150
- def parse_inventory_tree(inventory_tree, host_to_groups=dict(), group_stack=set()):
151
- for group in inventory_tree:
152
- # set logic adds tolerance for duplicate group names
153
- groups = group_stack.union({group})
154
-
155
- if "hosts" in inventory_tree[group]:
156
- for host in inventory_tree[group]["hosts"]:
157
- append_groups_to_host(host, groups, host_to_groups)
158
-
159
- if "children" in inventory_tree[group]:
160
- # recursively parse inventory tree
161
- parse_inventory_tree(inventory_tree[group]["children"], host_to_groups, groups)
162
-
163
- return host_to_groups
164
-
165
-
166
- def append_groups_to_host(host: "Host", groups, host_to_groups):
167
- if host in host_to_groups:
168
- # set logic handles de-duplication
169
- host_to_groups[host] = host_to_groups[host].union(groups)
170
- else:
171
- host_to_groups[host] = groups