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
pyinfra/modules/server.py DELETED
@@ -1,524 +0,0 @@
1
- '''
2
- The server module takes care of os-level state. Targets POSIX compatibility, tested on
3
- Linux/BSD.
4
- '''
5
-
6
- import shlex
7
-
8
- from io import StringIO
9
-
10
- from pyinfra.api import operation
11
-
12
- from . import files
13
- from .util.files import chmod, sed_replace
14
-
15
-
16
- @operation
17
- def wait(state, host, port=None):
18
- '''
19
- Waits for a port to come active on the target machine. Requires netstat, checks every
20
- 1s.
21
-
22
- + port: port number to wait for
23
- '''
24
-
25
- yield r'''
26
- while ! (netstat -an | grep LISTEN | grep -e "\.{0}" -e ":{0}"); do
27
- echo "waiting for port {0}..."
28
- sleep 1
29
- done
30
- '''.format(port)
31
-
32
-
33
- @operation
34
- def shell(state, host, commands, chdir=None):
35
- '''
36
- Run raw shell code.
37
-
38
- + commands: command or list of commands to execute on the remote server
39
- + chdir: directory to cd into before executing commands
40
- '''
41
-
42
- # Ensure we have a list
43
- if isinstance(commands, str):
44
- commands = [commands]
45
-
46
- for command in commands:
47
- if chdir:
48
- yield 'cd {0} && ({1})'.format(chdir, command)
49
- else:
50
- yield command
51
-
52
-
53
- @operation
54
- def script(state, host, filename, chdir=None):
55
- '''
56
- Upload and execute a local script on the remote host.
57
-
58
- + filename: local script filename to upload & execute
59
- + chdir: directory to cd into before executing the script
60
- '''
61
-
62
- temp_file = state.get_temp_filename(filename)
63
- yield files.put(state, host, filename, temp_file)
64
-
65
- yield chmod(temp_file, '+x')
66
-
67
- if chdir:
68
- yield 'cd {0} && {1}'.format(chdir, temp_file)
69
- else:
70
- yield temp_file
71
-
72
-
73
- @operation
74
- def script_template(state, host, template_filename, chdir=None, **data):
75
- '''
76
- Generate, upload and execute a local script template on the remote host.
77
-
78
- + template_filename: local script template filename
79
- + chdir: directory to cd into before executing the script
80
- '''
81
-
82
- temp_file = state.get_temp_filename('{0}{1}'.format(template_filename, data))
83
- yield files.template(state, host, template_filename, temp_file, **data)
84
-
85
- yield chmod(temp_file, '+x')
86
-
87
- if chdir:
88
- yield 'cd {0} && {1}'.format(chdir, temp_file)
89
- else:
90
- yield temp_file
91
-
92
-
93
- @operation
94
- def modprobe(state, host, name, present=True, force=False):
95
- '''
96
- Load/unload kernel modules.
97
-
98
- + name: name of the module to manage
99
- + present: whether the module should be loaded or not
100
- + force: whether to force any add/remove modules
101
- '''
102
-
103
- modules = host.fact.kernel_modules
104
- is_present = name in modules
105
-
106
- args = ''
107
- if force:
108
- args = ' -f'
109
-
110
- # Module is loaded and we don't want it?
111
- if not present and is_present:
112
- yield 'modprobe{0} -r {1}'.format(args, name)
113
-
114
- # Module isn't loaded and we want it?
115
- elif present and not is_present:
116
- yield 'modprobe{0} {1}'.format(args, name)
117
-
118
-
119
- @operation
120
- def mount(
121
- state, host, name,
122
- mounted=True, options=None,
123
- # TODO: do we want to manage fstab here?
124
- # update_fstab=False, device=None, fs_type=None,
125
- ):
126
- '''
127
- Manage mounted filesystems.
128
-
129
- + name: the path of the mounted filesystem
130
- + mounted: whether the filesystem should be mounted
131
- + options: the mount options
132
-
133
- Options:
134
- If the currently mounted filesystem does not have all of the provided
135
- options it will be remounted with the options provided.
136
-
137
- ``/etc/fstab``:
138
- This operation does not attempt to modify the on disk fstab file - for
139
- that you should use the `files.line operation <./files.html#files-line>`_.
140
- '''
141
-
142
- options = options or []
143
- options_string = ','.join(options)
144
-
145
- mounts = host.fact.mounts
146
- is_mounted = name in mounts
147
-
148
- # Want mount but don't have?
149
- if mounted and not is_mounted:
150
- yield 'mount{0} {1}'.format(
151
- ' -o {0}'.format(options_string) if options_string else '',
152
- name,
153
- )
154
-
155
- # Want no mount but mounted?
156
- elif mounted is False and is_mounted:
157
- yield 'umount {0}'.format(name)
158
-
159
- # Want mount and is mounted! Check the options
160
- elif is_mounted and mounted and options:
161
- mounted_options = mounts[name]['options']
162
- needed_options = set(options) - set(mounted_options)
163
- if needed_options:
164
- yield 'mount -o remount,{0} {1}'.format(options_string, name)
165
-
166
-
167
- @operation
168
- def hostname(state, host, hostname, hostname_file=None):
169
- '''
170
- Set the system hostname.
171
-
172
- + hostname: the hostname that should be set
173
- + hostname_file: the file that permanently sets the hostname
174
-
175
- Hostname file:
176
- By default pyinfra will auto detect this by targetting ``/etc/hostname``
177
- on Linux and ``/etc/myname`` on OpenBSD.
178
-
179
- To completely disable writing the hostname file, set ``hostname_file=False``.
180
- '''
181
-
182
- if hostname_file is None:
183
- os = host.fact.os
184
-
185
- if os == 'Linux':
186
- hostname_file = '/etc/hostname'
187
- elif os == 'OpenBSD':
188
- hostname_file = '/etc/myname'
189
-
190
- current_hostname = host.fact.hostname
191
-
192
- if current_hostname != hostname:
193
- yield 'hostname {0}'.format(hostname)
194
-
195
- if hostname_file:
196
- # Create a whole new hostname file
197
- file = StringIO('{0}\n'.format(hostname))
198
-
199
- # And ensure it exists
200
- yield files.put(
201
- state, host,
202
- file, hostname_file,
203
- )
204
-
205
-
206
- @operation
207
- def sysctl(
208
- state, host, name, value,
209
- persist=False, persist_file='/etc/sysctl.conf',
210
- ):
211
- '''
212
- Edit sysctl configuration.
213
-
214
- + name: name of the sysctl setting to ensure
215
- + value: the value or list of values the sysctl should be
216
- + persist: whether to write this sysctl to the config
217
- + persist_file: file to write the sysctl to persist on reboot
218
- '''
219
-
220
- string_value = (
221
- ' '.join(value)
222
- if isinstance(value, list)
223
- else value
224
- )
225
-
226
- existing_value = host.fact.sysctl.get(name)
227
- if not existing_value or existing_value != value:
228
- yield 'sysctl {0}={1}'.format(name, string_value)
229
-
230
- if persist:
231
- yield files.line(
232
- state, host,
233
- persist_file,
234
- '{0}[[:space:]]*=[[:space:]]*{1}'.format(name, string_value),
235
- replace='{0} = {1}'.format(name, string_value),
236
- )
237
-
238
-
239
- @operation
240
- def crontab(
241
- state, host, command, present=True, user=None, name=None,
242
- minute='*', hour='*', month='*', day_of_week='*', day_of_month='*',
243
- ):
244
- '''
245
- Add/remove/update crontab entries.
246
-
247
- + command: the command for the cron
248
- + present: whether this cron command should exist
249
- + user: the user whose crontab to manage
250
- + name: name the cronjob so future changes to the command will overwrite
251
- + minute: which minutes to execute the cron
252
- + hour: which hours to execute the cron
253
- + month: which months to execute the cron
254
- + day_of_week: which day of the week to execute the cron
255
- + day_of_month: which day of the month to execute the cron
256
-
257
- Cron commands:
258
- Unless ``name`` is specified the command is used to identify crontab entries.
259
- This means commands must be unique within a given users crontab. If you require
260
- multiple identical commands, provide a different name argument for each.
261
- '''
262
-
263
- def comma_sep(value):
264
- if isinstance(value, (list, tuple)):
265
- return ','.join('{0}'.format(v) for v in value)
266
- return value
267
-
268
- minute = comma_sep(minute)
269
- hour = comma_sep(hour)
270
- month = comma_sep(month)
271
- day_of_week = comma_sep(day_of_week)
272
- day_of_month = comma_sep(day_of_month)
273
-
274
- crontab = host.fact.crontab(user)
275
- name_comment = '# pyinfra-name={0}'.format(name)
276
-
277
- existing_crontab = crontab.get(command)
278
- existing_crontab_match = command
279
-
280
- if not existing_crontab and name: # find the crontab by name if provided
281
- for cmd, details in crontab.items():
282
- if name_comment in details['comments']:
283
- existing_crontab = details
284
- existing_crontab_match = cmd
285
-
286
- exists = existing_crontab is not None
287
-
288
- edit_commands = []
289
- temp_filename = state.get_temp_filename()
290
-
291
- new_crontab_line = '{minute} {hour} {day_of_month} {month} {day_of_week} {command}'.format(
292
- command=command,
293
- minute=minute,
294
- hour=hour,
295
- month=month,
296
- day_of_week=day_of_week,
297
- day_of_month=day_of_month,
298
- )
299
- existing_crontab_match = '.*{0}.*'.format(existing_crontab_match)
300
-
301
- # Don't want the cron and it does exist? Remove the line
302
- if not present and exists:
303
- edit_commands.append(sed_replace(
304
- temp_filename, existing_crontab_match, '',
305
- ))
306
-
307
- # Want the cron but it doesn't exist? Append the line
308
- elif present and not exists:
309
- if name:
310
- edit_commands.append('echo {0} >> {1}'.format(
311
- shlex.quote(name_comment), temp_filename,
312
- ))
313
-
314
- edit_commands.append('echo {0} >> {1}'.format(
315
- shlex.quote(new_crontab_line), temp_filename,
316
- ))
317
-
318
- # We have the cron and it exists, do it's details? If not, replace the line
319
- elif present and exists:
320
- if any((
321
- minute != existing_crontab['minute'],
322
- hour != existing_crontab['hour'],
323
- month != existing_crontab['month'],
324
- day_of_week != existing_crontab['day_of_week'],
325
- day_of_month != existing_crontab['day_of_month'],
326
- )):
327
- edit_commands.append(sed_replace(
328
- temp_filename, existing_crontab_match, new_crontab_line,
329
- ))
330
-
331
- if edit_commands:
332
- crontab_args = []
333
- if user:
334
- crontab_args.append('-u {0}'.format(user))
335
-
336
- # List the crontab into a temporary file if it exists
337
- if crontab:
338
- yield 'crontab -l {0} > {1}'.format(' '.join(crontab_args), temp_filename)
339
-
340
- # Now yield any edits
341
- for edit_command in edit_commands:
342
- yield edit_command
343
-
344
- # Finally, use the tempfile to write a new crontab
345
- yield 'crontab {0} {1}'.format(' '.join(crontab_args), temp_filename)
346
-
347
-
348
- @operation
349
- def group(
350
- state, host, name, present=True, system=False, gid=None,
351
- ):
352
- '''
353
- Add/remove system groups.
354
-
355
- + name: name of the group to ensure
356
- + present: whether the group should be present or not
357
- + system: whether to create a system group
358
-
359
- System users:
360
- System users don't exist on BSD, so the argument is ignored for BSD targets.
361
- '''
362
-
363
- groups = host.fact.groups or []
364
- is_present = name in groups
365
-
366
- # Group exists but we don't want them?
367
- if not present and is_present:
368
- yield 'groupdel {0}'.format(name)
369
-
370
- # Group doesn't exist and we want it?
371
- elif present and not is_present:
372
- args = []
373
-
374
- # BSD doesn't do system users
375
- if system and 'BSD' not in host.fact.os:
376
- args.append('-r')
377
-
378
- args.append(name)
379
-
380
- if gid:
381
- args.append('--gid {0}'.format(gid))
382
-
383
- yield 'groupadd {0}'.format(' '.join(args))
384
-
385
-
386
- @operation
387
- def user(
388
- state, host, name,
389
- present=True, home=None, shell=None, group=None, groups=None,
390
- public_keys=None, delete_keys=False, ensure_home=True,
391
- system=False, uid=None,
392
- ):
393
- '''
394
- Add/remove/update system users & their ssh `authorized_keys`.
395
-
396
- + name: name of the user to ensure
397
- + present: whether this user should exist
398
- + home: the users home directory
399
- + shell: the users shell
400
- + group: the users primary group
401
- + groups: the users secondary groups
402
- + public_keys: list of public keys to attach to this user, ``home`` must be specified
403
- + delete_keys: whether to remove any keys not specified in ``public_keys``
404
- + ensure_home: whether to ensure the ``home`` directory exists
405
- + system: whether to create a system account
406
-
407
- Home directory:
408
- When ``ensure_home`` or ``public_keys`` are provided, ``home`` defaults to
409
- ``/home/{name}``.
410
- '''
411
-
412
- users = host.fact.users or {}
413
- user = users.get(name)
414
-
415
- if groups is None:
416
- groups = []
417
-
418
- if home is None:
419
- home = '/home/{0}'.format(name)
420
-
421
- # User not wanted?
422
- if not present:
423
- if user:
424
- yield 'userdel {0}'.format(name)
425
- return
426
-
427
- # User doesn't exist but we want them?
428
- if present and user is None:
429
- # Create the user w/home/shell
430
- args = []
431
-
432
- if home:
433
- args.append('-d {0}'.format(home))
434
-
435
- if shell:
436
- args.append('-s {0}'.format(shell))
437
-
438
- if group:
439
- args.append('-g {0}'.format(group))
440
-
441
- if groups:
442
- args.append('-G {0}'.format(','.join(groups)))
443
-
444
- if system and 'BSD' not in host.fact.os:
445
- args.append('-r')
446
-
447
- if uid:
448
- args.append('--uid {0}'.format(uid))
449
-
450
- yield 'useradd {0} {1}'.format(' '.join(args), name)
451
-
452
- # User exists and we want them, check home/shell/keys
453
- else:
454
- args = []
455
-
456
- # Check homedir
457
- if home and user['home'] != home:
458
- args.append('-d {0}'.format(home))
459
-
460
- # Check shell
461
- if shell and user['shell'] != shell:
462
- args.append('-s {0}'.format(shell))
463
-
464
- # Check primary group
465
- if group and user['group'] != group:
466
- args.append('-g {0}'.format(group))
467
-
468
- # Check secondary groups, if defined
469
- if groups and set(user['groups']) != set(groups):
470
- args.append('-G {0}'.format(','.join(groups)))
471
-
472
- # Need to mod the user?
473
- if args:
474
- yield 'usermod {0} {1}'.format(' '.join(args), name)
475
-
476
- # Ensure home directory ownership
477
- if ensure_home:
478
- yield files.directory(
479
- state, host, home,
480
- user=name, group=name,
481
- )
482
-
483
- # Add SSH keys
484
- if public_keys is not None:
485
- # Ensure .ssh directory
486
- # note that this always outputs commands unless the SSH user has access to the
487
- # authorized_keys file, ie the SSH user is the user defined in this function
488
- yield files.directory(
489
- state, host,
490
- '{0}/.ssh'.format(home),
491
- user=name, group=name,
492
- mode=700,
493
- )
494
-
495
- filename = '{0}/.ssh/authorized_keys'.format(home)
496
-
497
- if delete_keys:
498
- # Create a whole new authorized_keys file
499
- keys_file = StringIO('{0}\n'.format(
500
- '\n'.join(public_keys),
501
- ))
502
-
503
- # And ensure it exists
504
- yield files.put(
505
- state, host,
506
- keys_file, filename,
507
- user=name, group=name,
508
- mode=600,
509
- )
510
-
511
- else:
512
- # Ensure authorized_keys exists
513
- yield files.file(
514
- state, host, filename,
515
- user=name, group=name,
516
- mode=600,
517
- )
518
-
519
- # And every public key is present
520
- for key in public_keys:
521
- yield files.line(
522
- state, host,
523
- filename, key,
524
- )
pyinfra/modules/ssh.py DELETED
@@ -1,150 +0,0 @@
1
- '''
2
- Execute commands and up/download files *from* the remote host.
3
-
4
- Eg: ``pyinfra -> inventory-host.net <-> another-host.net``
5
- '''
6
-
7
- from pyinfra.api import operation, OperationError
8
-
9
- from . import files
10
-
11
-
12
- @operation
13
- def keyscan(state, host, hostname, force=False):
14
- '''
15
- Check/add hosts to the ``~/.ssh/known_hosts`` file.
16
-
17
- + hostname: hostname that should have a key in ``known_hosts``
18
- + force: if the key already exists, remove and rescan
19
- '''
20
-
21
- yield files.directory(
22
- state, host,
23
- '~/.ssh',
24
- mode=700,
25
- )
26
-
27
- hostname_present = host.fact.find_in_file(
28
- '~/.ssh/known_hosts',
29
- hostname,
30
- )
31
-
32
- keyscan_command = 'ssh-keyscan {0} >> ~/.ssh/known_hosts'.format(hostname)
33
-
34
- if not hostname_present:
35
- yield keyscan_command
36
-
37
- elif force:
38
- yield 'ssh-keygen -R {0}'.format(hostname)
39
- yield keyscan_command
40
-
41
-
42
- @operation
43
- def command(state, host, hostname, command, ssh_user=None):
44
- '''
45
- Execute commands on other servers over SSH.
46
-
47
- + hostname: the hostname to connect to
48
- + command: the command to execute
49
- + ssh_user: connect with this user
50
- '''
51
-
52
- connection_target = hostname
53
- if ssh_user:
54
- connection_target = '@'.join((ssh_user, hostname))
55
-
56
- yield 'ssh {0} "{1}"'.format(connection_target, command)
57
-
58
-
59
- @operation
60
- def upload(
61
- state, host, hostname, filename,
62
- remote_filename=None, use_remote_sudo=False,
63
- ssh_keyscan=False, ssh_user=None,
64
- ):
65
- '''
66
- Upload files to other servers using ``scp``.
67
-
68
- + hostname: hostname to upload to
69
- + filename: file to upload
70
- + remote_filename: where to upload the file to (defaults to ``filename``)
71
- + use_remote_sudo: upload to a temporary location and move using sudo
72
- + ssh_keyscan: execute ``ssh.keyscan`` before uploading the file
73
- + ssh_user: connect with this user
74
- '''
75
-
76
- remote_filename = remote_filename or filename
77
-
78
- # Figure out where we're connecting (host or user@host)
79
- connection_target = hostname
80
- if ssh_user:
81
- connection_target = '@'.join((ssh_user, hostname))
82
-
83
- if ssh_keyscan:
84
- yield keyscan(state, host, hostname)
85
-
86
- # If we're not using sudo on the remote side, just scp the file over
87
- if not use_remote_sudo:
88
- yield 'scp {0} {1}:{2}'.format(filename, connection_target, remote_filename)
89
-
90
- else:
91
- # Otherwise - we need a temporary location for the file
92
- temp_remote_filename = state.get_temp_filename()
93
-
94
- # scp it to the temporary location
95
- upload_cmd = 'scp {0} {1}:{2}'.format(
96
- filename, connection_target, temp_remote_filename,
97
- )
98
-
99
- yield upload_cmd
100
-
101
- # And sudo sudo to move it
102
- yield command(state, host, connection_target, 'sudo mv {0} {1}'.format(
103
- temp_remote_filename, remote_filename,
104
- ))
105
-
106
-
107
- @operation
108
- def download(
109
- state, host, hostname, filename,
110
- local_filename=None, force=False,
111
- ssh_keyscan=False, ssh_user=None,
112
- ):
113
- '''
114
- Download files from other servers using ``scp``.
115
-
116
- + hostname: hostname to upload to
117
- + filename: file to download
118
- + local_filename: where to download the file to (defaults to ``filename``)
119
- + force: always download the file, even if present locally
120
- + ssh_keyscan: execute ``ssh.keyscan`` before uploading the file
121
- + ssh_user: connect with this user
122
- '''
123
-
124
- local_filename = local_filename or filename
125
-
126
- # Get local file info
127
- local_file_info = host.fact.file(local_filename)
128
-
129
- # Local file exists but isn't a file?
130
- if local_file_info is False:
131
- raise OperationError(
132
- 'Local destination {0} already exists and is not a file'.format(
133
- local_filename,
134
- ),
135
- )
136
-
137
- # If the local file exists and we're not forcing a re-download, no-op
138
- if local_file_info and not force:
139
- return
140
-
141
- # Figure out where we're connecting (host or user@host)
142
- connection_target = hostname
143
- if ssh_user:
144
- connection_target = '@'.join((ssh_user, hostname))
145
-
146
- if ssh_keyscan:
147
- yield keyscan(state, host, hostname)
148
-
149
- # Download the file with scp
150
- yield 'scp {0}:{1} {2}'.format(connection_target, filename, local_filename)