pyinfra 0.11.dev3__py3-none-any.whl → 3.5.1__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 (203) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +18 -3
  4. pyinfra/api/arguments.py +406 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +67 -18
  12. pyinfra/api/facts.py +253 -202
  13. pyinfra/api/host.py +413 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/operation.py +432 -262
  16. pyinfra/api/operations.py +273 -260
  17. pyinfra/api/state.py +302 -248
  18. pyinfra/api/util.py +291 -368
  19. pyinfra/connectors/base.py +173 -0
  20. pyinfra/connectors/chroot.py +212 -0
  21. pyinfra/connectors/docker.py +381 -0
  22. pyinfra/connectors/dockerssh.py +297 -0
  23. pyinfra/connectors/local.py +238 -0
  24. pyinfra/connectors/scp/__init__.py +1 -0
  25. pyinfra/connectors/scp/client.py +204 -0
  26. pyinfra/connectors/ssh.py +670 -0
  27. pyinfra/connectors/ssh_util.py +114 -0
  28. pyinfra/connectors/sshuserclient/client.py +309 -0
  29. pyinfra/connectors/sshuserclient/config.py +102 -0
  30. pyinfra/connectors/terraform.py +135 -0
  31. pyinfra/connectors/util.py +410 -0
  32. pyinfra/connectors/vagrant.py +183 -0
  33. pyinfra/context.py +145 -0
  34. pyinfra/facts/__init__.py +7 -6
  35. pyinfra/facts/apk.py +22 -7
  36. pyinfra/facts/apt.py +117 -60
  37. pyinfra/facts/brew.py +100 -15
  38. pyinfra/facts/bsdinit.py +23 -0
  39. pyinfra/facts/cargo.py +37 -0
  40. pyinfra/facts/choco.py +47 -0
  41. pyinfra/facts/crontab.py +195 -0
  42. pyinfra/facts/deb.py +94 -0
  43. pyinfra/facts/dnf.py +48 -0
  44. pyinfra/facts/docker.py +96 -23
  45. pyinfra/facts/efibootmgr.py +113 -0
  46. pyinfra/facts/files.py +630 -58
  47. pyinfra/facts/flatpak.py +77 -0
  48. pyinfra/facts/freebsd.py +70 -0
  49. pyinfra/facts/gem.py +19 -6
  50. pyinfra/facts/git.py +59 -14
  51. pyinfra/facts/gpg.py +150 -0
  52. pyinfra/facts/hardware.py +313 -167
  53. pyinfra/facts/iptables.py +72 -62
  54. pyinfra/facts/launchd.py +44 -0
  55. pyinfra/facts/lxd.py +17 -4
  56. pyinfra/facts/mysql.py +122 -86
  57. pyinfra/facts/npm.py +17 -9
  58. pyinfra/facts/openrc.py +71 -0
  59. pyinfra/facts/opkg.py +246 -0
  60. pyinfra/facts/pacman.py +50 -7
  61. pyinfra/facts/pip.py +24 -7
  62. pyinfra/facts/pipx.py +82 -0
  63. pyinfra/facts/pkg.py +15 -6
  64. pyinfra/facts/pkgin.py +35 -0
  65. pyinfra/facts/podman.py +54 -0
  66. pyinfra/facts/postgres.py +178 -0
  67. pyinfra/facts/postgresql.py +6 -147
  68. pyinfra/facts/rpm.py +105 -0
  69. pyinfra/facts/runit.py +77 -0
  70. pyinfra/facts/selinux.py +161 -0
  71. pyinfra/facts/server.py +746 -285
  72. pyinfra/facts/snap.py +88 -0
  73. pyinfra/facts/systemd.py +139 -0
  74. pyinfra/facts/sysvinit.py +59 -0
  75. pyinfra/facts/upstart.py +35 -0
  76. pyinfra/facts/util/__init__.py +17 -0
  77. pyinfra/facts/util/databases.py +4 -6
  78. pyinfra/facts/util/packaging.py +37 -6
  79. pyinfra/facts/util/units.py +30 -0
  80. pyinfra/facts/util/win_files.py +99 -0
  81. pyinfra/facts/vzctl.py +20 -13
  82. pyinfra/facts/xbps.py +35 -0
  83. pyinfra/facts/yum.py +34 -40
  84. pyinfra/facts/zfs.py +77 -0
  85. pyinfra/facts/zypper.py +42 -0
  86. pyinfra/local.py +45 -83
  87. pyinfra/operations/__init__.py +12 -0
  88. pyinfra/operations/apk.py +98 -0
  89. pyinfra/operations/apt.py +488 -0
  90. pyinfra/operations/brew.py +231 -0
  91. pyinfra/operations/bsdinit.py +59 -0
  92. pyinfra/operations/cargo.py +45 -0
  93. pyinfra/operations/choco.py +61 -0
  94. pyinfra/operations/crontab.py +191 -0
  95. pyinfra/operations/dnf.py +210 -0
  96. pyinfra/operations/docker.py +446 -0
  97. pyinfra/operations/files.py +1939 -0
  98. pyinfra/operations/flatpak.py +94 -0
  99. pyinfra/operations/freebsd/__init__.py +12 -0
  100. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  101. pyinfra/operations/freebsd/pkg.py +219 -0
  102. pyinfra/operations/freebsd/service.py +116 -0
  103. pyinfra/operations/freebsd/sysrc.py +92 -0
  104. pyinfra/operations/gem.py +47 -0
  105. pyinfra/operations/git.py +419 -0
  106. pyinfra/operations/iptables.py +311 -0
  107. pyinfra/operations/launchd.py +45 -0
  108. pyinfra/operations/lxd.py +68 -0
  109. pyinfra/operations/mysql.py +609 -0
  110. pyinfra/operations/npm.py +57 -0
  111. pyinfra/operations/openrc.py +63 -0
  112. pyinfra/operations/opkg.py +88 -0
  113. pyinfra/operations/pacman.py +81 -0
  114. pyinfra/operations/pip.py +205 -0
  115. pyinfra/operations/pipx.py +102 -0
  116. pyinfra/operations/pkg.py +70 -0
  117. pyinfra/operations/pkgin.py +91 -0
  118. pyinfra/operations/postgres.py +436 -0
  119. pyinfra/operations/postgresql.py +30 -0
  120. pyinfra/operations/puppet.py +40 -0
  121. pyinfra/operations/python.py +72 -0
  122. pyinfra/operations/runit.py +184 -0
  123. pyinfra/operations/selinux.py +189 -0
  124. pyinfra/operations/server.py +1099 -0
  125. pyinfra/operations/snap.py +117 -0
  126. pyinfra/operations/ssh.py +216 -0
  127. pyinfra/operations/systemd.py +149 -0
  128. pyinfra/operations/sysvinit.py +141 -0
  129. pyinfra/operations/upstart.py +68 -0
  130. pyinfra/operations/util/__init__.py +12 -0
  131. pyinfra/operations/util/docker.py +251 -0
  132. pyinfra/operations/util/files.py +247 -0
  133. pyinfra/operations/util/packaging.py +336 -0
  134. pyinfra/operations/util/service.py +46 -0
  135. pyinfra/operations/vzctl.py +137 -0
  136. pyinfra/operations/xbps.py +77 -0
  137. pyinfra/operations/yum.py +210 -0
  138. pyinfra/operations/zfs.py +175 -0
  139. pyinfra/operations/zypper.py +192 -0
  140. pyinfra/progress.py +44 -32
  141. pyinfra/py.typed +0 -0
  142. pyinfra/version.py +9 -1
  143. pyinfra-3.5.1.dist-info/METADATA +141 -0
  144. pyinfra-3.5.1.dist-info/RECORD +159 -0
  145. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
  146. pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
  147. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
  148. pyinfra_cli/__init__.py +1 -0
  149. pyinfra_cli/cli.py +780 -0
  150. pyinfra_cli/commands.py +66 -0
  151. pyinfra_cli/exceptions.py +155 -65
  152. pyinfra_cli/inventory.py +233 -89
  153. pyinfra_cli/log.py +39 -43
  154. pyinfra_cli/main.py +26 -495
  155. pyinfra_cli/prints.py +215 -156
  156. pyinfra_cli/util.py +172 -105
  157. pyinfra_cli/virtualenv.py +25 -20
  158. pyinfra/api/connectors/__init__.py +0 -21
  159. pyinfra/api/connectors/ansible.py +0 -99
  160. pyinfra/api/connectors/docker.py +0 -178
  161. pyinfra/api/connectors/local.py +0 -169
  162. pyinfra/api/connectors/ssh.py +0 -402
  163. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  164. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  165. pyinfra/api/connectors/util.py +0 -63
  166. pyinfra/api/connectors/vagrant.py +0 -155
  167. pyinfra/facts/init.py +0 -176
  168. pyinfra/facts/util/files.py +0 -102
  169. pyinfra/hook.py +0 -41
  170. pyinfra/modules/__init__.py +0 -11
  171. pyinfra/modules/apk.py +0 -64
  172. pyinfra/modules/apt.py +0 -272
  173. pyinfra/modules/brew.py +0 -122
  174. pyinfra/modules/files.py +0 -711
  175. pyinfra/modules/gem.py +0 -30
  176. pyinfra/modules/git.py +0 -115
  177. pyinfra/modules/init.py +0 -344
  178. pyinfra/modules/iptables.py +0 -271
  179. pyinfra/modules/lxd.py +0 -45
  180. pyinfra/modules/mysql.py +0 -347
  181. pyinfra/modules/npm.py +0 -47
  182. pyinfra/modules/pacman.py +0 -60
  183. pyinfra/modules/pip.py +0 -99
  184. pyinfra/modules/pkg.py +0 -43
  185. pyinfra/modules/postgresql.py +0 -245
  186. pyinfra/modules/puppet.py +0 -20
  187. pyinfra/modules/python.py +0 -37
  188. pyinfra/modules/server.py +0 -524
  189. pyinfra/modules/ssh.py +0 -150
  190. pyinfra/modules/util/files.py +0 -52
  191. pyinfra/modules/util/packaging.py +0 -118
  192. pyinfra/modules/vzctl.py +0 -133
  193. pyinfra/modules/yum.py +0 -171
  194. pyinfra/pseudo_modules.py +0 -64
  195. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  196. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  197. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  198. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  199. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  200. pyinfra_cli/__main__.py +0 -40
  201. pyinfra_cli/config.py +0 -92
  202. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  203. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
@@ -1,402 +0,0 @@
1
- from getpass import getpass
2
- from os import path
3
- from socket import (
4
- error as socket_error,
5
- gaierror,
6
- )
7
-
8
- import click
9
-
10
- from paramiko import (
11
- AuthenticationException,
12
- MissingHostKeyPolicy,
13
- PasswordRequiredException,
14
- RSAKey,
15
- SFTPClient,
16
- SSHException,
17
- )
18
- from paramiko.agent import AgentRequestHandler
19
-
20
- import pyinfra
21
-
22
- from pyinfra import logger
23
- from pyinfra.api.exceptions import PyinfraError
24
- from pyinfra.api.util import get_file_io, make_command, memoize
25
-
26
- from .sshuserclient import SSHClient
27
- from .util import read_buffers_into_queue, split_combined_output
28
-
29
-
30
- def _log_connect_error(host, message, data):
31
- logger.error('{0}{1} ({2})'.format(
32
- host.print_prefix,
33
- click.style(message, 'red'),
34
- data,
35
- ))
36
-
37
-
38
- def _get_private_key(state, key_filename, key_password):
39
- if key_filename in state.private_keys:
40
- return state.private_keys[key_filename]
41
-
42
- ssh_key_filenames = [
43
- # Global from executed directory
44
- path.expanduser(key_filename),
45
- ]
46
-
47
- # Relative to the deploy
48
- if state.deploy_dir:
49
- ssh_key_filenames.append(
50
- path.join(state.deploy_dir, key_filename),
51
- )
52
-
53
- for filename in ssh_key_filenames:
54
- if not path.isfile(filename):
55
- continue
56
-
57
- # First, lets try the key without a password
58
- try:
59
- key = RSAKey.from_private_key_file(
60
- filename=filename,
61
- )
62
- break
63
-
64
- # Key is encrypted!
65
- except PasswordRequiredException:
66
- # If password is not provided, but we're in CLI mode, ask for it. I'm not a
67
- # huge fan of having CLI specific code in here, but it doesn't really fit
68
- # anywhere else without duplicating lots of key related code into cli.py.
69
- if not key_password:
70
- if pyinfra.is_cli:
71
- key_password = getpass(
72
- 'Enter password for private key: {0}: '.format(
73
- key_filename,
74
- ),
75
- )
76
-
77
- # API mode and no password? We can't continue!
78
- else:
79
- raise PyinfraError(
80
- 'Private key file ({0}) is encrypted, set ssh_key_password to '
81
- 'use this key'.format(key_filename),
82
- )
83
-
84
- # Now, try opening the key with the password
85
- try:
86
- key = RSAKey.from_private_key_file(
87
- filename=filename,
88
- password=key_password,
89
- )
90
- break
91
-
92
- except SSHException:
93
- raise PyinfraError(
94
- 'Incorrect password for private key: {0}'.format(
95
- key_filename,
96
- ),
97
- )
98
-
99
- # No break, so no key found
100
- else:
101
- raise IOError('No such private key file: {0}'.format(key_filename))
102
-
103
- state.private_keys[key_filename] = key
104
- return key
105
-
106
-
107
- def _make_paramiko_kwargs(state, host):
108
- kwargs = {
109
- 'allow_agent': False,
110
- 'look_for_keys': False,
111
- }
112
-
113
- for key, value in (
114
- ('username', host.data.ssh_user),
115
- ('port', int(host.data.ssh_port or 0)),
116
- ('timeout', state.config.CONNECT_TIMEOUT),
117
- ):
118
- if value:
119
- kwargs[key] = value
120
-
121
- # Password auth (boo!)
122
- if host.data.ssh_password:
123
- kwargs['password'] = host.data.ssh_password
124
-
125
- # Key auth!
126
- elif host.data.ssh_key:
127
- kwargs['pkey'] = _get_private_key(
128
- state,
129
- key_filename=host.data.ssh_key,
130
- key_password=host.data.ssh_key_password,
131
- )
132
-
133
- # No key or password, so let's have paramiko look for SSH agents and user keys
134
- else:
135
- kwargs['allow_agent'] = True
136
- kwargs['look_for_keys'] = True
137
-
138
- return kwargs
139
-
140
-
141
- def connect(state, host, for_fact=None):
142
- '''
143
- Connect to a single host. Returns the SSH client if succesful. Stateless by
144
- design so can be run in parallel.
145
- '''
146
-
147
- kwargs = _make_paramiko_kwargs(state, host)
148
- logger.debug('Connecting to: {0} ({1})'.format(host.name, kwargs))
149
-
150
- # Hostname can be provided via SSH config (alias), data, or the hosts name
151
- hostname = kwargs.pop(
152
- 'hostname',
153
- host.data.ssh_hostname or host.name,
154
- )
155
-
156
- try:
157
- # Create new client & connect to the host
158
- client = SSHClient()
159
- client.set_missing_host_key_policy(MissingHostKeyPolicy())
160
- client.connect(hostname, **kwargs)
161
-
162
- # Enable SSH forwarding
163
- session = client.get_transport().open_session()
164
- AgentRequestHandler(session)
165
-
166
- # Log
167
- log_message = '{0}{1}'.format(
168
- host.print_prefix,
169
- click.style('Connected', 'green'),
170
- )
171
-
172
- if for_fact:
173
- log_message = '{0}{1}'.format(
174
- log_message,
175
- ' (for {0} fact)'.format(for_fact),
176
- )
177
-
178
- logger.info(log_message)
179
-
180
- return client
181
-
182
- except AuthenticationException:
183
- auth_kwargs = {}
184
-
185
- for key, value in kwargs.items():
186
- if key in ('username', 'password'):
187
- auth_kwargs[key] = value
188
- continue
189
-
190
- if key == 'pkey' and value:
191
- auth_kwargs['key'] = host.data.ssh_key
192
-
193
- auth_args = ', '.join(
194
- '{0}={1}'.format(key, value)
195
- for key, value in auth_kwargs.items()
196
- )
197
-
198
- _log_connect_error(host, 'Authentication error', auth_args)
199
-
200
- except SSHException as e:
201
- _log_connect_error(host, 'SSH error', e)
202
-
203
- except gaierror:
204
- _log_connect_error(host, 'Could not resolve hostname', hostname)
205
-
206
- except socket_error as e:
207
- _log_connect_error(host, 'Could not connect', e)
208
-
209
- except EOFError as e:
210
- _log_connect_error(host, 'EOF error', e)
211
-
212
-
213
- def run_shell_command(
214
- state, host, command,
215
- get_pty=False, timeout=None, print_output=False,
216
- return_combined_output=False,
217
- **command_kwargs
218
- ):
219
- '''
220
- Execute a command on the specified host.
221
-
222
- Args:
223
- state (``pyinfra.api.State`` obj): state object for this command
224
- hostname (string): hostname of the target
225
- command (string): actual command to execute
226
- sudo (boolean): whether to wrap the command with sudo
227
- sudo_user (string): user to sudo to
228
- get_pty (boolean): whether to get a PTY before executing the command
229
- env (dict): envrionment variables to set
230
- timeout (int): timeout for this command to complete before erroring
231
-
232
- Returns:
233
- tuple: (exit_code, stdout, stderr)
234
- stdout and stderr are both lists of strings from each buffer.
235
- '''
236
-
237
- command = make_command(command, **command_kwargs)
238
-
239
- logger.debug('Running command on {0}: (pty={1}) {2}'.format(
240
- host.name, get_pty, command,
241
- ))
242
-
243
- if print_output:
244
- print('{0}>>> {1}'.format(host.print_prefix, command))
245
-
246
- # Run it! Get stdout, stderr & the underlying channel
247
- _, stdout_buffer, stderr_buffer = host.connection.exec_command(
248
- command,
249
- get_pty=get_pty,
250
- )
251
-
252
- combined_output = read_buffers_into_queue(
253
- host,
254
- stdout_buffer,
255
- stderr_buffer,
256
- timeout=timeout,
257
- print_output=print_output,
258
- )
259
-
260
- logger.debug('Waiting for exit status...')
261
- exit_status = stdout_buffer.channel.recv_exit_status()
262
- logger.debug('Command exit status: {0}'.format(exit_status))
263
-
264
- status = exit_status == 0
265
-
266
- if return_combined_output:
267
- return status, combined_output
268
-
269
- stdout, stderr = split_combined_output(combined_output)
270
- return status, stdout, stderr
271
-
272
-
273
- @memoize
274
- def _get_sftp_connection(host):
275
- transport = host.connection.get_transport()
276
- return SFTPClient.from_transport(transport)
277
-
278
-
279
- def _get_file(host, remote_filename, filename_or_io):
280
- with get_file_io(filename_or_io) as file_io:
281
- sftp = _get_sftp_connection(host)
282
- sftp.getfo(remote_filename, file_io)
283
-
284
-
285
- def get_file(
286
- state, host, remote_filename, filename_or_io,
287
- sudo=False, sudo_user=None, su_user=None, print_output=False,
288
- **command_kwargs
289
- ):
290
- '''
291
- Download a file from the remote host using SFTP. Supports download files
292
- with sudo by copying to a temporary directory with read permissions,
293
- downloading and then removing the copy.
294
- '''
295
-
296
- if sudo or su_user:
297
- # Get temp file location
298
- temp_file = state.get_temp_filename(remote_filename)
299
-
300
- # Copy the file to the tempfile location and add read permissions
301
- command = 'cp {0} {1} && chmod +r {0}'.format(remote_filename, temp_file)
302
-
303
- copy_status, _, stderr = run_shell_command(
304
- state, host, command,
305
- sudo=sudo, sudo_user=sudo_user, su_user=su_user,
306
- print_output=print_output,
307
- **command_kwargs
308
- )
309
-
310
- if copy_status is False:
311
- logger.error('File download copy temp error: {0}'.format('\n'.join(stderr)))
312
- return False
313
-
314
- try:
315
- _get_file(host, temp_file, filename_or_io)
316
-
317
- # Ensure that, even if we encounter an error, we (attempt to) remove the
318
- # temporary copy of the file.
319
- finally:
320
- remove_status, _, stderr = run_shell_command(
321
- state, host, 'rm -f {0}'.format(temp_file),
322
- sudo=sudo, sudo_user=sudo_user, su_user=su_user,
323
- print_output=print_output,
324
- **command_kwargs
325
- )
326
-
327
- if remove_status is False:
328
- logger.error('File download remove temp error: {0}'.format('\n'.join(stderr)))
329
- return False
330
-
331
- else:
332
- _get_file(host, remote_filename, filename_or_io)
333
-
334
- if print_output:
335
- print('{0}file downloaded: {1}'.format(host.print_prefix, remote_filename))
336
-
337
- return True
338
-
339
-
340
- def _put_file(host, filename_or_io, remote_location):
341
- with get_file_io(filename_or_io) as file_io:
342
- # Upload it via SFTP
343
- sftp = _get_sftp_connection(host)
344
-
345
- try:
346
- sftp.putfo(file_io, remote_location)
347
- except IOError as e:
348
- # IO mismatch errors might indicate full disks
349
- message = getattr(e, 'message', None)
350
- if message and message.startswith('size mismatch in put! 0 !='):
351
- raise IOError('{0} (disk may be full)'.format(e.message))
352
-
353
- raise
354
-
355
-
356
- def put_file(
357
- state, host, filename_or_io, remote_filename,
358
- sudo=False, sudo_user=None, su_user=None, print_output=False,
359
- **command_kwargs
360
- ):
361
- '''
362
- Upload file-ios to the specified host using SFTP. Supports uploading files
363
- with sudo by uploading to a temporary directory then moving & chowning.
364
- '''
365
-
366
- # sudo/su are a little more complicated, as you can only sftp with the SSH
367
- # user connected, so upload to tmp and copy/chown w/sudo and/or su_user
368
- if sudo or su_user:
369
- # Get temp file location
370
- temp_file = state.get_temp_filename(remote_filename)
371
- _put_file(host, filename_or_io, temp_file)
372
-
373
- # Execute run_shell_command w/sudo and/or su_user
374
- command = 'mv {0} {1}'.format(temp_file, remote_filename)
375
-
376
- # Move it to the su_user if present
377
- if su_user:
378
- command = '{0} && chown {1} {2}'.format(command, su_user, remote_filename)
379
-
380
- # Otherwise any sudo_user
381
- elif sudo_user:
382
- command = '{0} && chown {1} {2}'.format(command, sudo_user, remote_filename)
383
-
384
- status, _, stderr = run_shell_command(
385
- state, host, command,
386
- sudo=sudo, sudo_user=sudo_user, su_user=su_user,
387
- print_output=print_output,
388
- **command_kwargs
389
- )
390
-
391
- if status is False:
392
- logger.error('File upload error: {0}'.format('\n'.join(stderr)))
393
- return False
394
-
395
- # No sudo and no su_user, so just upload it!
396
- else:
397
- _put_file(host, filename_or_io, remote_filename)
398
-
399
- if print_output:
400
- print('{0}file uploaded: {1}'.format(host.print_prefix, remote_filename))
401
-
402
- return True
@@ -1,105 +0,0 @@
1
- '''
2
- This file as originally part of the "sshuserclient" pypi package. The GitHub
3
- source has now vanished (https://github.com/tobald/sshuserclient).
4
- '''
5
-
6
- import os
7
-
8
- from paramiko import (
9
- AutoAddPolicy,
10
- ProxyCommand,
11
- SSHClient as ParamikoClient,
12
- )
13
-
14
- from pyinfra.api.util import memoize
15
-
16
- from .config import SSHConfig
17
-
18
-
19
- @memoize
20
- def get_ssh_config():
21
- user_config_file = os.path.expanduser('~/.ssh/config')
22
- if os.path.exists(user_config_file):
23
- with open(user_config_file) as f:
24
- ssh_config = SSHConfig()
25
- ssh_config.parse(f)
26
- return ssh_config
27
-
28
-
29
- class SSHClient(ParamikoClient):
30
- '''
31
- An SSHClient which honors ssh_config and supports proxyjumping
32
- original idea at http://bitprophet.org/blog/2012/11/05/gateway-solutions/
33
- '''
34
-
35
- def connect(self, hostname, **kwargs):
36
- self.hostname, self.config = self.parse_config(hostname)
37
- self.config.update(kwargs)
38
- super(SSHClient, self).connect(hostname, **self.config)
39
-
40
- def gateway(self, target, target_port):
41
- transport = self.get_transport()
42
- return transport.open_channel(
43
- 'direct-tcpip',
44
- (target, target_port),
45
- (self.hostname, self.config['port']))
46
-
47
- def parse_config(self, hostname):
48
- cfg = {'port': 22}
49
-
50
- ssh_config = get_ssh_config()
51
- host_config = ssh_config.lookup(hostname)
52
-
53
- if 'hostname' in host_config:
54
- hostname = host_config['hostname']
55
- if 'user' in host_config:
56
- cfg['username'] = host_config['user']
57
- if 'identityfile' in host_config:
58
- cfg['key_filename'] = host_config['identityfile']
59
- if 'port' in host_config:
60
- cfg['port'] = int(host_config['port'])
61
- if 'proxycommand' in host_config:
62
- cfg['sock'] = ProxyCommand(host_config['proxycommand'])
63
- elif 'proxyjump' in host_config:
64
- hops = host_config['proxyjump'].split(',')
65
- sock = None
66
- for i, hop in enumerate(hops):
67
- hop_hostname, hop_config = self.derive_shorthand(hop)
68
- c = SSHClient()
69
- c.set_missing_host_key_policy(AutoAddPolicy())
70
- c.connect(hop_hostname, sock=sock, **hop_config)
71
- if i == len(hops) - 1:
72
- target = hostname
73
- target_config = {'port': cfg['port']}
74
- else:
75
- target, target_config = self.derive_shorthand(
76
- hops[i + 1])
77
- sock = c.gateway(target, target_config['port'])
78
- cfg['sock'] = sock
79
-
80
- return hostname, cfg
81
-
82
- def derive_shorthand(self, host_string):
83
- config = {}
84
- user_hostport = host_string.rsplit('@', 1)
85
- hostport = user_hostport.pop()
86
- user = user_hostport[0] if user_hostport and user_hostport[0] else None
87
- if user:
88
- config['username'] = user
89
-
90
- # IPv6: can't reliably tell where addr ends and port begins, so don't
91
- # try (and don't bother adding special syntax either, user should avoid
92
- # this situation by using port=).
93
- if hostport.count(':') > 1:
94
- host = hostport
95
- config['port'] = 22
96
- # IPv4: can split on ':' reliably.
97
- else:
98
- host_port = hostport.rsplit(':', 1)
99
- host = host_port.pop(0) or None
100
- if host_port and host_port[0]:
101
- config['port'] = int(host_port[0])
102
- else:
103
- config['port'] = 22
104
-
105
- return host, config
@@ -1,90 +0,0 @@
1
- '''
2
- This file as originally part of the "sshuserclient" pypi package. The GitHub
3
- source has now vanished (https://github.com/tobald/sshuserclient).
4
- '''
5
-
6
- import glob
7
- import os
8
- import re
9
-
10
- from paramiko import SSHConfig as ParamkoSSHConfig
11
-
12
-
13
- class SSHConfig(ParamkoSSHConfig):
14
- '''
15
- an SSHConfig that supports includes directives
16
- https://github.com/paramiko/paramiko/pull/1194
17
- '''
18
-
19
- SETTINGS_REGEX = re.compile(r'(\w+)(?:\s*=\s*|\s+)(.+)')
20
-
21
- def parse(self, file_obj, parsed_files=None):
22
- '''
23
- Read an OpenSSH config from the given file object.
24
-
25
- :param file_obj: a file-like object to read the config file from
26
- '''
27
- host = {'host': ['*'], 'config': {}}
28
- for line in file_obj:
29
- # Strip any leading or trailing whitespace from the line.
30
- # Refer to https://github.com/paramiko/paramiko/issues/499
31
- line = line.strip()
32
- if not line or line.startswith('#'):
33
- continue
34
-
35
- match = re.match(self.SETTINGS_REGEX, line)
36
- if not match:
37
- raise Exception('Unparsable line {}'.format(line))
38
- key = match.group(1).lower()
39
- value = match.group(2)
40
-
41
- if key == 'host':
42
- self._config.append(host)
43
- host = {
44
- 'host': self._get_hosts(value),
45
- 'config': {},
46
- }
47
- elif key == 'proxycommand' and value.lower() == 'none':
48
- # Store 'none' as None; prior to 3.x, it will get stripped out
49
- # at the end (for compatibility with issue #415). After 3.x, it
50
- # will simply not get stripped, leaving a nice explicit marker.
51
- host['config'][key] = None
52
- elif key == 'include':
53
- # support for Include directive in ssh_config
54
- path = value
55
- # the path can be relative to its parent configuration file
56
- if os.path.isabs(path) is False and path[0] != '~':
57
- folder = os.path.dirname(file_obj.name)
58
- path = os.path.join(folder, path)
59
-
60
- # expand the user home path
61
- path = os.path.expanduser(path)
62
- if parsed_files is None:
63
- parsed_files = []
64
-
65
- # parse every included file
66
- for filename in glob.iglob(path):
67
- if os.path.isfile(filename):
68
- if filename in parsed_files:
69
- raise Exception(
70
- 'Include loop detected in ssh config file: %s' % filename,
71
- )
72
- with open(filename) as fd:
73
- parsed_files.append(filename)
74
- self.parse(fd, parsed_files)
75
-
76
- else:
77
- if value.startswith('"') and value.endswith('"'):
78
- value = value[1:-1]
79
-
80
- # identityfile, localforward, remoteforward keys are special
81
- # cases, since they are allowed to be specified multiple times
82
- # and they should be tried in order of specification.
83
- if key in ['identityfile', 'localforward', 'remoteforward']:
84
- if key in host['config']:
85
- host['config'][key].append(value)
86
- else:
87
- host['config'][key] = [value]
88
- elif key not in host['config']:
89
- host['config'][key] = value
90
- self._config.append(host)
@@ -1,63 +0,0 @@
1
- from socket import timeout as timeout_error
2
-
3
- import click
4
- import gevent
5
-
6
- from gevent.queue import Queue
7
-
8
- from pyinfra.api.util import read_buffer
9
-
10
-
11
- def read_buffers_into_queue(host, stdout_buffer, stderr_buffer, timeout, print_output):
12
- output_queue = Queue()
13
-
14
- # Iterate through outputs to get an exit status and generate desired list
15
- # output, done in two greenlets so stdout isn't printed before stderr. Not
16
- # attached to state.pool to avoid blocking it with 2x n-hosts greenlets.
17
- stdout_reader = gevent.spawn(
18
- read_buffer,
19
- 'stdout',
20
- stdout_buffer,
21
- output_queue,
22
- print_output=print_output,
23
- print_func=lambda line: '{0}{1}'.format(host.print_prefix, line),
24
- )
25
- stderr_reader = gevent.spawn(
26
- read_buffer,
27
- 'stderr',
28
- stderr_buffer,
29
- output_queue,
30
- print_output=print_output,
31
- print_func=lambda line: '{0}{1}'.format(
32
- host.print_prefix, click.style(line, 'red'),
33
- ),
34
- )
35
-
36
- # Wait on output, with our timeout (or None)
37
- greenlets = gevent.wait((stdout_reader, stderr_reader), timeout=timeout)
38
-
39
- # Timeout doesn't raise an exception, but gevent.wait returns the greenlets
40
- # which did complete. So if both haven't completed, we kill them and fail
41
- # with a timeout.
42
- if len(greenlets) != 2:
43
- stdout_reader.kill()
44
- stderr_reader.kill()
45
-
46
- raise timeout_error()
47
-
48
- return list(output_queue.queue)
49
-
50
-
51
- def split_combined_output(combined_output):
52
- stdout = []
53
- stderr = []
54
-
55
- for type_, line in combined_output:
56
- if type_ == 'stdout':
57
- stdout.append(line)
58
- elif type_ == 'stderr':
59
- stderr.append(line)
60
- else: # pragma: no cover
61
- raise ValueError('Incorrect output line type: {0}'.format(type_))
62
-
63
- return stdout, stderr