pyinfra 2.9.2__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.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.2.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.2.dist-info/RECORD +0 -170
  151. pyinfra-2.9.2.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.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
@@ -1,244 +1,232 @@
1
- """
2
- The ``@local`` connector executes changes on the local machine using subprocesses.
3
- """
4
-
5
1
  import os
6
- from distutils.spawn import find_executable
2
+ from shutil import which
7
3
  from tempfile import mkstemp
8
- from typing import TYPE_CHECKING
4
+ from typing import TYPE_CHECKING, Tuple
9
5
 
10
6
  import click
7
+ from typing_extensions import Unpack
11
8
 
12
9
  from pyinfra import logger
13
10
  from pyinfra.api.command import QuoteString, StringCommand
14
- from pyinfra.api.connectors import BaseConnectorMeta
15
11
  from pyinfra.api.exceptions import InventoryError
16
12
  from pyinfra.api.util import get_file_io
17
13
 
14
+ from .base import BaseConnector
18
15
  from .util import (
16
+ CommandOutput,
19
17
  execute_command_with_sudo_retry,
20
18
  make_unix_command_for_host,
21
19
  run_local_process,
22
- split_combined_output,
23
20
  )
24
21
 
25
22
  if TYPE_CHECKING:
26
- from pyinfra.api.host import Host
27
- from pyinfra.api.state import State
28
-
29
-
30
- class Meta(BaseConnectorMeta):
31
- handles_execution = True
32
-
33
-
34
- def make_names_data(_=None):
35
- if _ is not None:
36
- raise InventoryError("Cannot have more than one @local")
37
-
38
- yield "@local", {}, ["@local"]
39
-
40
-
41
- def connect(state: "State", host: "Host"):
42
- return True
23
+ from pyinfra.api.arguments import ConnectorArguments
43
24
 
44
25
 
45
- def run_shell_command(
46
- state: "State",
47
- host: "Host",
48
- command,
49
- get_pty: bool = False, # ignored
50
- timeout=None,
51
- stdin=None,
52
- success_exit_codes=None,
53
- print_output: bool = False,
54
- print_input: bool = False,
55
- return_combined_output: bool = False,
56
- **command_kwargs,
57
- ):
26
+ class LocalConnector(BaseConnector):
58
27
  """
59
- Execute a command on the local machine.
60
-
61
- Args:
62
- state (``pyinfra.api.State`` object): state object for this command
63
- host (``pyinfra.api.Host`` object): the target host
64
- command (string): actual command to execute
65
- sudo (boolean): whether to wrap the command with sudo
66
- sudo_user (string): user to sudo to
67
- env (dict): environment variables to set
68
- timeout (int): timeout for this command to complete before erroring
69
-
70
- Returns:
71
- tuple: (exit_code, stdout, stderr)
72
- stdout and stderr are both lists of strings from each buffer.
73
- """
74
-
75
- def execute_command():
76
- unix_command = make_unix_command_for_host(state, host, command, **command_kwargs)
77
- actual_command = unix_command.get_raw_value()
78
-
79
- logger.debug("--> Running command on localhost: %s", unix_command)
80
-
81
- if print_input:
82
- click.echo("{0}>>> {1}".format(host.print_prefix, unix_command), err=True)
83
-
84
- return run_local_process(
85
- actual_command,
86
- stdin=stdin,
87
- timeout=timeout,
88
- print_output=print_output,
89
- print_prefix=host.print_prefix,
90
- )
91
-
92
- return_code, combined_output = execute_command_with_sudo_retry(
93
- host,
94
- command_kwargs,
95
- execute_command,
96
- )
97
-
98
- if success_exit_codes:
99
- status = return_code in success_exit_codes
100
- else:
101
- status = return_code == 0
28
+ The ``@local`` connector executes changes on the local machine using
29
+ subprocesses. **This connector is only compatible with MacOS & Linux hosts**.
102
30
 
103
- if return_combined_output:
104
- return status, combined_output
105
-
106
- stdout, stderr = split_combined_output(combined_output)
107
- return status, stdout, stderr
31
+ Examples:
108
32
 
33
+ .. code::
109
34
 
110
- def put_file(
111
- state: "State",
112
- host: "Host",
113
- filename_or_io,
114
- remote_filename,
115
- remote_temp_filename=None, # ignored
116
- print_output: bool = False,
117
- print_input: bool = False,
118
- **command_kwargs,
119
- ):
120
- """
121
- Upload a local file or IO object by copying it to a temporary directory
122
- and then writing it to the upload location.
35
+ # Install nginx
36
+ pyinfra inventory.py apt.packages nginx update=true _sudo=true
123
37
  """
124
38
 
125
- _, temp_filename = mkstemp()
126
-
127
- try:
128
- # Load our file or IO object and write it to the temporary file
129
- with get_file_io(filename_or_io) as file_io:
130
- with open(temp_filename, "wb") as temp_f:
131
- data = file_io.read()
132
-
133
- if isinstance(data, str):
134
- data = data.encode()
135
-
136
- temp_f.write(data)
137
-
138
- # Copy the file using `cp` such that we support sudo/su
139
- status, _, stderr = run_shell_command(
140
- state,
141
- host,
142
- StringCommand("cp", temp_filename, QuoteString(remote_filename)),
143
- print_output=print_output,
144
- print_input=print_input,
145
- **command_kwargs,
146
- )
39
+ handles_execution = True
147
40
 
148
- if not status:
149
- raise IOError("\n".join(stderr))
150
- finally:
151
- os.remove(temp_filename)
152
-
153
- if print_output:
154
- click.echo(
155
- "{0}file copied: {1}".format(host.print_prefix, remote_filename),
156
- err=True,
41
+ @staticmethod
42
+ def make_names_data(name=None):
43
+ if name is not None:
44
+ raise InventoryError("Cannot have more than one @local")
45
+
46
+ yield "@local", {}, ["@local"]
47
+
48
+ def run_shell_command(
49
+ self,
50
+ command: StringCommand,
51
+ print_output: bool = False,
52
+ print_input: bool = False,
53
+ **arguments: Unpack["ConnectorArguments"],
54
+ ) -> Tuple[bool, CommandOutput]:
55
+ """
56
+ Execute a command on the local machine.
57
+
58
+ Args:
59
+ command (StringCommand): actual command to execute
60
+ print_output (bool): whether to print command output
61
+ print_input (bool): whether to print command input
62
+ arguments: (ConnectorArguments): connector global arguments
63
+
64
+ Returns:
65
+ tuple: (bool, CommandOutput)
66
+ Bool indicating success and CommandOutput with stdout/stderr lines.
67
+ """
68
+
69
+ arguments.pop("_get_pty", False)
70
+ _timeout = arguments.pop("_timeout", None)
71
+ _stdin = arguments.pop("_stdin", None)
72
+ _success_exit_codes = arguments.pop("_success_exit_codes", None)
73
+
74
+ def execute_command() -> Tuple[int, CommandOutput]:
75
+ unix_command = make_unix_command_for_host(self.state, self.host, command, **arguments)
76
+ actual_command = unix_command.get_raw_value()
77
+
78
+ logger.debug("--> Running command on localhost: %s", unix_command)
79
+
80
+ if print_input:
81
+ click.echo("{0}>>> {1}".format(self.host.print_prefix, unix_command), err=True)
82
+
83
+ return run_local_process(
84
+ actual_command,
85
+ stdin=_stdin,
86
+ timeout=_timeout,
87
+ print_output=print_output,
88
+ print_prefix=self.host.print_prefix,
89
+ )
90
+
91
+ return_code, combined_output = execute_command_with_sudo_retry(
92
+ self.host,
93
+ arguments,
94
+ execute_command,
157
95
  )
158
96
 
159
- return status
97
+ if _success_exit_codes:
98
+ status = return_code in _success_exit_codes
99
+ else:
100
+ status = return_code == 0
160
101
 
102
+ return status, combined_output
161
103
 
162
- def get_file(
163
- state: "State",
164
- host: "Host",
165
- remote_filename,
166
- filename_or_io,
167
- remote_temp_filename=None, # ignored
168
- print_output: bool = False,
169
- print_input: bool = False,
170
- **command_kwargs,
171
- ):
172
- """
173
- Download a local file by copying it to a temporary location and then writing
174
- it to our filename or IO object.
175
- """
176
-
177
- _, temp_filename = mkstemp()
178
-
179
- try:
180
- # Copy the file using `cp` such that we support sudo/su
181
- status, _, stderr = run_shell_command(
182
- state,
183
- host,
184
- "cp {0} {1}".format(remote_filename, temp_filename),
104
+ def put_file(
105
+ self,
106
+ filename_or_io,
107
+ remote_filename,
108
+ remote_temp_filename=None, # ignored
109
+ print_output: bool = False,
110
+ print_input: bool = False,
111
+ **arguments,
112
+ ) -> bool:
113
+ """
114
+ Upload a local file or IO object by copying it to a temporary directory
115
+ and then writing it to the upload location.
116
+
117
+ Returns:
118
+ bool: Indicating success or failure
119
+ """
120
+
121
+ _, temp_filename = mkstemp()
122
+
123
+ try:
124
+ # Load our file or IO object and write it to the temporary file
125
+ with get_file_io(filename_or_io) as file_io:
126
+ with open(temp_filename, "wb") as temp_f:
127
+ data = file_io.read()
128
+
129
+ if isinstance(data, str):
130
+ data = data.encode()
131
+
132
+ temp_f.write(data)
133
+
134
+ # Copy the file using `cp` such that we support sudo/su
135
+ status, output = self.run_shell_command(
136
+ StringCommand("cp", temp_filename, QuoteString(remote_filename)),
137
+ print_output=print_output,
138
+ print_input=print_input,
139
+ **arguments,
140
+ )
141
+
142
+ if not status:
143
+ raise IOError(output.stderr)
144
+ finally:
145
+ os.remove(temp_filename)
146
+
147
+ if print_output:
148
+ click.echo(
149
+ "{0}file copied: {1}".format(self.host.print_prefix, remote_filename),
150
+ err=True,
151
+ )
152
+
153
+ return status
154
+
155
+ def get_file(
156
+ self,
157
+ remote_filename,
158
+ filename_or_io,
159
+ remote_temp_filename=None, # ignored
160
+ print_output: bool = False,
161
+ print_input: bool = False,
162
+ **arguments,
163
+ ) -> bool:
164
+ """
165
+ Download a local file by copying it to a temporary location and then writing
166
+ it to our filename or IO object.
167
+
168
+ Returns:
169
+ bool: Indicating success or failure
170
+ """
171
+
172
+ _, temp_filename = mkstemp()
173
+
174
+ try:
175
+ # Copy the file using `cp` such that we support sudo/su
176
+ status, output = self.run_shell_command(
177
+ StringCommand("cp", remote_filename, temp_filename),
178
+ print_output=print_output,
179
+ print_input=print_input,
180
+ **arguments,
181
+ )
182
+
183
+ if not status:
184
+ raise IOError(output.stderr)
185
+
186
+ # Load our file or IO object and write it to the temporary file
187
+ with open(temp_filename, encoding="utf-8") as temp_f:
188
+ with get_file_io(filename_or_io, "wb") as file_io:
189
+ data_bytes: bytes
190
+
191
+ data = temp_f.read()
192
+ if isinstance(data, str):
193
+ data_bytes = data.encode()
194
+ else:
195
+ data_bytes = data
196
+
197
+ file_io.write(data_bytes)
198
+ finally:
199
+ os.remove(temp_filename)
200
+
201
+ if print_output:
202
+ click.echo(
203
+ "{0}file copied: {1}".format(self.host.print_prefix, remote_filename),
204
+ err=True,
205
+ )
206
+
207
+ return True
208
+
209
+ def check_can_rsync(self):
210
+ if not which("rsync"):
211
+ raise NotImplementedError("The `rsync` binary is not available on this system.")
212
+
213
+ def rsync(
214
+ self,
215
+ src,
216
+ dest,
217
+ flags,
218
+ print_output: bool = False,
219
+ print_input: bool = False,
220
+ **arguments,
221
+ ) -> bool:
222
+ status, output = self.run_shell_command(
223
+ StringCommand("rsync", " ".join(flags), src, dest),
185
224
  print_output=print_output,
186
225
  print_input=print_input,
187
- **command_kwargs,
226
+ **arguments,
188
227
  )
189
228
 
190
229
  if not status:
191
- raise IOError("\n".join(stderr))
192
-
193
- # Load our file or IO object and write it to the temporary file
194
- with open(temp_filename, encoding="utf-8") as temp_f:
195
- with get_file_io(filename_or_io, "wb") as file_io:
196
- data_bytes: bytes
197
-
198
- data = temp_f.read()
199
- if isinstance(data, str):
200
- data_bytes = data.encode()
201
- else:
202
- data_bytes = data
203
-
204
- file_io.write(data_bytes)
205
- finally:
206
- os.remove(temp_filename)
207
-
208
- if print_output:
209
- click.echo(
210
- "{0}file copied: {1}".format(host.print_prefix, remote_filename),
211
- err=True,
212
- )
230
+ raise IOError(output.stderr)
213
231
 
214
- return True
215
-
216
-
217
- def check_can_rsync(host):
218
- if not find_executable("rsync"):
219
- raise NotImplementedError("The `rsync` binary is not available on this system.")
220
-
221
-
222
- def rsync(
223
- state: "State",
224
- host: "Host",
225
- src,
226
- dest,
227
- flags,
228
- print_output: bool = False,
229
- print_input: bool = False,
230
- **command_kwargs,
231
- ):
232
- status, _, stderr = run_shell_command(
233
- state,
234
- host,
235
- "rsync {0} {1} {2}".format(" ".join(flags), src, dest),
236
- print_output=print_output,
237
- print_input=print_input,
238
- **command_kwargs,
239
- )
240
-
241
- if not status:
242
- raise IOError("\n".join(stderr))
243
-
244
- return True
232
+ return True