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.
- pyinfra/__init__.py +9 -12
- pyinfra/__main__.py +4 -0
- pyinfra/api/__init__.py +18 -3
- pyinfra/api/arguments.py +406 -0
- pyinfra/api/arguments_typed.py +79 -0
- pyinfra/api/command.py +274 -0
- pyinfra/api/config.py +222 -28
- pyinfra/api/connect.py +33 -13
- pyinfra/api/connectors.py +27 -0
- pyinfra/api/deploy.py +65 -66
- pyinfra/api/exceptions.py +67 -18
- pyinfra/api/facts.py +253 -202
- pyinfra/api/host.py +413 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +291 -368
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +381 -0
- pyinfra/connectors/dockerssh.py +297 -0
- pyinfra/connectors/local.py +238 -0
- pyinfra/connectors/scp/__init__.py +1 -0
- pyinfra/connectors/scp/client.py +204 -0
- pyinfra/connectors/ssh.py +670 -0
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +309 -0
- pyinfra/connectors/sshuserclient/config.py +102 -0
- pyinfra/connectors/terraform.py +135 -0
- pyinfra/connectors/util.py +410 -0
- pyinfra/connectors/vagrant.py +183 -0
- pyinfra/context.py +145 -0
- pyinfra/facts/__init__.py +7 -6
- pyinfra/facts/apk.py +22 -7
- pyinfra/facts/apt.py +117 -60
- pyinfra/facts/brew.py +100 -15
- pyinfra/facts/bsdinit.py +23 -0
- pyinfra/facts/cargo.py +37 -0
- pyinfra/facts/choco.py +47 -0
- pyinfra/facts/crontab.py +195 -0
- pyinfra/facts/deb.py +94 -0
- pyinfra/facts/dnf.py +48 -0
- pyinfra/facts/docker.py +96 -23
- pyinfra/facts/efibootmgr.py +113 -0
- pyinfra/facts/files.py +630 -58
- pyinfra/facts/flatpak.py +77 -0
- pyinfra/facts/freebsd.py +70 -0
- pyinfra/facts/gem.py +19 -6
- pyinfra/facts/git.py +59 -14
- pyinfra/facts/gpg.py +150 -0
- pyinfra/facts/hardware.py +313 -167
- pyinfra/facts/iptables.py +72 -62
- pyinfra/facts/launchd.py +44 -0
- pyinfra/facts/lxd.py +17 -4
- pyinfra/facts/mysql.py +122 -86
- pyinfra/facts/npm.py +17 -9
- pyinfra/facts/openrc.py +71 -0
- pyinfra/facts/opkg.py +246 -0
- pyinfra/facts/pacman.py +50 -7
- pyinfra/facts/pip.py +24 -7
- pyinfra/facts/pipx.py +82 -0
- pyinfra/facts/pkg.py +15 -6
- pyinfra/facts/pkgin.py +35 -0
- pyinfra/facts/podman.py +54 -0
- pyinfra/facts/postgres.py +178 -0
- pyinfra/facts/postgresql.py +6 -147
- pyinfra/facts/rpm.py +105 -0
- pyinfra/facts/runit.py +77 -0
- pyinfra/facts/selinux.py +161 -0
- pyinfra/facts/server.py +746 -285
- pyinfra/facts/snap.py +88 -0
- pyinfra/facts/systemd.py +139 -0
- pyinfra/facts/sysvinit.py +59 -0
- pyinfra/facts/upstart.py +35 -0
- pyinfra/facts/util/__init__.py +17 -0
- pyinfra/facts/util/databases.py +4 -6
- pyinfra/facts/util/packaging.py +37 -6
- pyinfra/facts/util/units.py +30 -0
- pyinfra/facts/util/win_files.py +99 -0
- pyinfra/facts/vzctl.py +20 -13
- pyinfra/facts/xbps.py +35 -0
- pyinfra/facts/yum.py +34 -40
- pyinfra/facts/zfs.py +77 -0
- pyinfra/facts/zypper.py +42 -0
- pyinfra/local.py +45 -83
- pyinfra/operations/__init__.py +12 -0
- pyinfra/operations/apk.py +98 -0
- pyinfra/operations/apt.py +488 -0
- pyinfra/operations/brew.py +231 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +191 -0
- pyinfra/operations/dnf.py +210 -0
- pyinfra/operations/docker.py +446 -0
- pyinfra/operations/files.py +1939 -0
- pyinfra/operations/flatpak.py +94 -0
- pyinfra/operations/freebsd/__init__.py +12 -0
- pyinfra/operations/freebsd/freebsd_update.py +70 -0
- pyinfra/operations/freebsd/pkg.py +219 -0
- pyinfra/operations/freebsd/service.py +116 -0
- pyinfra/operations/freebsd/sysrc.py +92 -0
- pyinfra/operations/gem.py +47 -0
- pyinfra/operations/git.py +419 -0
- pyinfra/operations/iptables.py +311 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +68 -0
- pyinfra/operations/mysql.py +609 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +88 -0
- pyinfra/operations/pacman.py +81 -0
- pyinfra/operations/pip.py +205 -0
- pyinfra/operations/pipx.py +102 -0
- pyinfra/operations/pkg.py +70 -0
- pyinfra/operations/pkgin.py +91 -0
- pyinfra/operations/postgres.py +436 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +40 -0
- pyinfra/operations/python.py +72 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +189 -0
- pyinfra/operations/server.py +1099 -0
- pyinfra/operations/snap.py +117 -0
- pyinfra/operations/ssh.py +216 -0
- pyinfra/operations/systemd.py +149 -0
- pyinfra/operations/sysvinit.py +141 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +251 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +336 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +77 -0
- pyinfra/operations/yum.py +210 -0
- pyinfra/operations/zfs.py +175 -0
- pyinfra/operations/zypper.py +192 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.5.1.dist-info/METADATA +141 -0
- pyinfra-3.5.1.dist-info/RECORD +159 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
- pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +780 -0
- pyinfra_cli/commands.py +66 -0
- pyinfra_cli/exceptions.py +155 -65
- pyinfra_cli/inventory.py +233 -89
- pyinfra_cli/log.py +39 -43
- pyinfra_cli/main.py +26 -495
- pyinfra_cli/prints.py +215 -156
- pyinfra_cli/util.py +172 -105
- pyinfra_cli/virtualenv.py +25 -20
- pyinfra/api/connectors/__init__.py +0 -21
- pyinfra/api/connectors/ansible.py +0 -99
- pyinfra/api/connectors/docker.py +0 -178
- pyinfra/api/connectors/local.py +0 -169
- pyinfra/api/connectors/ssh.py +0 -402
- pyinfra/api/connectors/sshuserclient/client.py +0 -105
- pyinfra/api/connectors/sshuserclient/config.py +0 -90
- pyinfra/api/connectors/util.py +0 -63
- pyinfra/api/connectors/vagrant.py +0 -155
- pyinfra/facts/init.py +0 -176
- pyinfra/facts/util/files.py +0 -102
- pyinfra/hook.py +0 -41
- pyinfra/modules/__init__.py +0 -11
- pyinfra/modules/apk.py +0 -64
- pyinfra/modules/apt.py +0 -272
- pyinfra/modules/brew.py +0 -122
- pyinfra/modules/files.py +0 -711
- pyinfra/modules/gem.py +0 -30
- pyinfra/modules/git.py +0 -115
- pyinfra/modules/init.py +0 -344
- pyinfra/modules/iptables.py +0 -271
- pyinfra/modules/lxd.py +0 -45
- pyinfra/modules/mysql.py +0 -347
- pyinfra/modules/npm.py +0 -47
- pyinfra/modules/pacman.py +0 -60
- pyinfra/modules/pip.py +0 -99
- pyinfra/modules/pkg.py +0 -43
- pyinfra/modules/postgresql.py +0 -245
- pyinfra/modules/puppet.py +0 -20
- pyinfra/modules/python.py +0 -37
- pyinfra/modules/server.py +0 -524
- pyinfra/modules/ssh.py +0 -150
- pyinfra/modules/util/files.py +0 -52
- pyinfra/modules/util/packaging.py +0 -118
- pyinfra/modules/vzctl.py +0 -133
- pyinfra/modules/yum.py +0 -171
- pyinfra/pseudo_modules.py +0 -64
- pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
- pyinfra-0.11.dev3.dist-info/METADATA +0 -135
- pyinfra-0.11.dev3.dist-info/RECORD +0 -95
- pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
- pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
- pyinfra_cli/__main__.py +0 -40
- pyinfra_cli/config.py +0 -92
- /pyinfra/{modules/util → connectors}/__init__.py +0 -0
- /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)
|