pyinfra 2.9.1__py2.py3-none-any.whl → 3.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyinfra/api/__init__.py +3 -0
- pyinfra/api/arguments.py +265 -253
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +68 -53
- pyinfra/api/config.py +139 -32
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +7 -26
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +102 -137
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +21 -25
- pyinfra/api/operation.py +240 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +79 -86
- pyinfra/connectors/base.py +147 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +220 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +196 -208
- pyinfra/connectors/ssh.py +530 -613
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +211 -137
- pyinfra/connectors/vagrant.py +60 -53
- pyinfra/context.py +4 -2
- pyinfra/facts/apk.py +2 -0
- pyinfra/facts/apt.py +2 -0
- pyinfra/facts/brew.py +2 -0
- pyinfra/facts/bsdinit.py +2 -0
- pyinfra/facts/cargo.py +2 -0
- pyinfra/facts/choco.py +2 -0
- pyinfra/facts/deb.py +7 -2
- pyinfra/facts/dnf.py +2 -0
- pyinfra/facts/docker.py +19 -0
- pyinfra/facts/files.py +47 -32
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +3 -1
- pyinfra/facts/hardware.py +34 -24
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +13 -6
- pyinfra/facts/npm.py +1 -0
- pyinfra/facts/openrc.py +2 -0
- pyinfra/facts/pacman.py +6 -2
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +2 -0
- pyinfra/facts/pkgin.py +2 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +6 -160
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +80 -36
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +31 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +7 -4
- pyinfra/facts/vzctl.py +2 -0
- pyinfra/facts/xbps.py +2 -0
- pyinfra/facts/yum.py +2 -0
- pyinfra/facts/zypper.py +2 -0
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +6 -4
- pyinfra/operations/apt.py +46 -65
- pyinfra/operations/brew.py +17 -22
- pyinfra/operations/bsdinit.py +9 -7
- pyinfra/operations/cargo.py +4 -2
- pyinfra/operations/choco.py +4 -2
- pyinfra/operations/dnf.py +19 -23
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +188 -386
- pyinfra/operations/gem.py +4 -2
- pyinfra/operations/git.py +24 -53
- pyinfra/operations/iptables.py +29 -35
- pyinfra/operations/launchd.py +6 -7
- pyinfra/operations/lxd.py +8 -13
- pyinfra/operations/mysql.py +62 -81
- pyinfra/operations/npm.py +9 -2
- pyinfra/operations/openrc.py +6 -4
- pyinfra/operations/pacman.py +7 -8
- pyinfra/operations/pip.py +25 -24
- pyinfra/operations/pkg.py +4 -2
- pyinfra/operations/pkgin.py +6 -4
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -379
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -44
- pyinfra/operations/server.py +111 -127
- pyinfra/operations/snap.py +4 -4
- pyinfra/operations/ssh.py +20 -33
- pyinfra/operations/systemd.py +19 -15
- pyinfra/operations/sysvinit.py +9 -16
- pyinfra/operations/upstart.py +9 -7
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +55 -57
- pyinfra/operations/util/service.py +39 -51
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +6 -4
- pyinfra/operations/yum.py +18 -22
- pyinfra/operations/zypper.py +12 -13
- pyinfra/version.py +5 -2
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
- pyinfra-3.0.dist-info/RECORD +167 -0
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
- pyinfra-3.0.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +78 -42
- pyinfra_cli/inventory.py +40 -6
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +95 -127
- pyinfra_cli/util.py +62 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +101 -201
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +105 -93
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +7 -7
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.1.dist-info/RECORD +0 -170
- pyinfra-2.9.1.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
pyinfra/operations/files.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
The files operations handles filesystem state, file uploads and template generation.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import os
|
|
6
8
|
import posixpath
|
|
7
9
|
import sys
|
|
@@ -9,10 +11,11 @@ import traceback
|
|
|
9
11
|
from datetime import timedelta
|
|
10
12
|
from fnmatch import fnmatch
|
|
11
13
|
from io import StringIO
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import IO, Any, Union
|
|
12
16
|
|
|
13
17
|
from jinja2 import TemplateRuntimeError, TemplateSyntaxError, UndefinedError
|
|
14
18
|
|
|
15
|
-
import pyinfra
|
|
16
19
|
from pyinfra import host, logger, state
|
|
17
20
|
from pyinfra.api import (
|
|
18
21
|
FileDownloadCommand,
|
|
@@ -28,6 +31,7 @@ from pyinfra.api import (
|
|
|
28
31
|
from pyinfra.api.command import make_formatted_string_command
|
|
29
32
|
from pyinfra.api.util import (
|
|
30
33
|
get_call_location,
|
|
34
|
+
get_file_io,
|
|
31
35
|
get_file_sha1,
|
|
32
36
|
get_path_permissions_mode,
|
|
33
37
|
get_template,
|
|
@@ -54,23 +58,21 @@ from .util import files as file_utils
|
|
|
54
58
|
from .util.files import adjust_regex, ensure_mode_int, get_timestamp, sed_replace, unix_path_join
|
|
55
59
|
|
|
56
60
|
|
|
57
|
-
@operation(
|
|
58
|
-
pipeline_facts={"file": "dest"},
|
|
59
|
-
)
|
|
61
|
+
@operation()
|
|
60
62
|
def download(
|
|
61
|
-
src,
|
|
62
|
-
dest,
|
|
63
|
-
user=None,
|
|
64
|
-
group=None,
|
|
65
|
-
mode=None,
|
|
66
|
-
cache_time=None,
|
|
63
|
+
src: str,
|
|
64
|
+
dest: str,
|
|
65
|
+
user: str | None = None,
|
|
66
|
+
group: str | None = None,
|
|
67
|
+
mode: str | None = None,
|
|
68
|
+
cache_time: int | None = None,
|
|
67
69
|
force=False,
|
|
68
|
-
sha256sum=None,
|
|
69
|
-
sha1sum=None,
|
|
70
|
-
md5sum=None,
|
|
71
|
-
headers=None,
|
|
70
|
+
sha256sum: str | None = None,
|
|
71
|
+
sha1sum: str | None = None,
|
|
72
|
+
md5sum: str | None = None,
|
|
73
|
+
headers: dict[str, str] | None = None,
|
|
72
74
|
insecure=False,
|
|
73
|
-
proxy=None,
|
|
75
|
+
proxy: str | None = None,
|
|
74
76
|
):
|
|
75
77
|
"""
|
|
76
78
|
Download files from remote locations using ``curl`` or ``wget``.
|
|
@@ -101,7 +103,6 @@ def download(
|
|
|
101
103
|
"""
|
|
102
104
|
|
|
103
105
|
info = host.get_fact(File, path=dest)
|
|
104
|
-
host_datetime = host.get_fact(Date).replace(tzinfo=None)
|
|
105
106
|
|
|
106
107
|
# Destination is a directory?
|
|
107
108
|
if info is False:
|
|
@@ -122,8 +123,8 @@ def download(
|
|
|
122
123
|
if cache_time:
|
|
123
124
|
# Time on files is not tz-aware, and will be the same tz as the server's time,
|
|
124
125
|
# so we can safely remove the tzinfo from the Date fact before comparison.
|
|
125
|
-
|
|
126
|
-
if info["mtime"] and info["mtime"] <
|
|
126
|
+
ctime = host.get_fact(Date).replace(tzinfo=None) - timedelta(seconds=cache_time)
|
|
127
|
+
if info["mtime"] and info["mtime"] < ctime:
|
|
127
128
|
download = True
|
|
128
129
|
|
|
129
130
|
if sha1sum:
|
|
@@ -140,11 +141,10 @@ def download(
|
|
|
140
141
|
|
|
141
142
|
# If we download, always do user/group/mode as SSH user may be different
|
|
142
143
|
if download:
|
|
143
|
-
temp_file =
|
|
144
|
+
temp_file = host.get_temp_filename(dest)
|
|
144
145
|
|
|
145
|
-
curl_args = ["-sSLf"]
|
|
146
|
-
|
|
147
|
-
wget_args = ["-q"]
|
|
146
|
+
curl_args: list[Union[str, StringCommand]] = ["-sSLf"]
|
|
147
|
+
wget_args: list[Union[str, StringCommand]] = ["-q"]
|
|
148
148
|
|
|
149
149
|
if proxy:
|
|
150
150
|
curl_args.append(f"--proxy {proxy}")
|
|
@@ -221,40 +221,20 @@ def download(
|
|
|
221
221
|
md5sum,
|
|
222
222
|
QuoteString("MD5 did not match!"),
|
|
223
223
|
)
|
|
224
|
-
|
|
225
|
-
host.create_fact(
|
|
226
|
-
File,
|
|
227
|
-
kwargs={"path": dest},
|
|
228
|
-
data={"mode": mode, "group": group, "user": user, "mtime": host_datetime},
|
|
229
|
-
)
|
|
230
|
-
|
|
231
|
-
# Remove any checksum facts as we don't know the correct values
|
|
232
|
-
for value, fact_cls in (
|
|
233
|
-
(sha1sum, Sha1File),
|
|
234
|
-
(sha256sum, Sha256File),
|
|
235
|
-
(md5sum, Md5File),
|
|
236
|
-
):
|
|
237
|
-
fact_kwargs = {"path": dest}
|
|
238
|
-
if value:
|
|
239
|
-
host.create_fact(fact_cls, kwargs=fact_kwargs, data=value)
|
|
240
|
-
else:
|
|
241
|
-
host.delete_fact(fact_cls, kwargs=fact_kwargs)
|
|
242
|
-
|
|
243
224
|
else:
|
|
244
225
|
host.noop("file {0} has already been downloaded".format(dest))
|
|
245
226
|
|
|
246
227
|
|
|
247
|
-
@operation
|
|
228
|
+
@operation()
|
|
248
229
|
def line(
|
|
249
|
-
path,
|
|
250
|
-
line,
|
|
230
|
+
path: str,
|
|
231
|
+
line: str,
|
|
251
232
|
present=True,
|
|
252
|
-
replace=None,
|
|
253
|
-
flags=None,
|
|
233
|
+
replace: str | None = None,
|
|
234
|
+
flags: list[str] | None = None,
|
|
254
235
|
backup=False,
|
|
255
236
|
interpolate_variables=False,
|
|
256
237
|
escape_regex_characters=False,
|
|
257
|
-
assume_present=False,
|
|
258
238
|
ensure_newline=False,
|
|
259
239
|
):
|
|
260
240
|
"""
|
|
@@ -267,7 +247,6 @@ def line(
|
|
|
267
247
|
+ flags: list of flags to pass to sed when replacing/deleting
|
|
268
248
|
+ backup: whether to backup the file (see below)
|
|
269
249
|
+ interpolate_variables: whether to interpolate variables in ``replace``
|
|
270
|
-
+ assume_present: whether to assume a matching line already exists in the file
|
|
271
250
|
+ escape_regex_characters: whether to escape regex characters from the matching line
|
|
272
251
|
+ ensure_newline: ensures that the appended line is on a new line
|
|
273
252
|
|
|
@@ -290,7 +269,7 @@ def line(
|
|
|
290
269
|
it will be append to the end of the file.
|
|
291
270
|
|
|
292
271
|
Ensure new line:
|
|
293
|
-
This will ensure that the ``line`` being appended is always on a
|
|
272
|
+
This will ensure that the ``line`` being appended is always on a separate new
|
|
294
273
|
line in case the file doesn't end with a newline character.
|
|
295
274
|
|
|
296
275
|
|
|
@@ -354,15 +333,12 @@ def line(
|
|
|
354
333
|
# match_line = "{0}.*$".format(match_line)
|
|
355
334
|
|
|
356
335
|
# Is there a matching line in this file?
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
pattern=match_line,
|
|
364
|
-
interpolate_variables=interpolate_variables,
|
|
365
|
-
)
|
|
336
|
+
present_lines = host.get_fact(
|
|
337
|
+
FindInFile,
|
|
338
|
+
path=path,
|
|
339
|
+
pattern=match_line,
|
|
340
|
+
interpolate_variables=interpolate_variables,
|
|
341
|
+
)
|
|
366
342
|
|
|
367
343
|
# If replace present, use that over the matching line
|
|
368
344
|
if replace:
|
|
@@ -407,58 +383,27 @@ def line(
|
|
|
407
383
|
|
|
408
384
|
# No line and we want it, append it
|
|
409
385
|
if not present_lines and present:
|
|
410
|
-
# If
|
|
411
|
-
#
|
|
412
|
-
if
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
echo_command=echo_command,
|
|
426
|
-
sed_replace_command=sed_replace_command,
|
|
386
|
+
# If we're doing replacement, only append if the *replacement* line
|
|
387
|
+
# does not exist (as we are appending the replacement).
|
|
388
|
+
if replace:
|
|
389
|
+
# Ensure replace explicitly matches a whole line
|
|
390
|
+
replace_line = replace
|
|
391
|
+
if not replace_line.startswith("^"):
|
|
392
|
+
replace_line = f"^{replace_line}"
|
|
393
|
+
if not replace_line.endswith("$"):
|
|
394
|
+
replace_line = f"{replace_line}$"
|
|
395
|
+
|
|
396
|
+
present_lines = host.get_fact(
|
|
397
|
+
FindInFile,
|
|
398
|
+
path=path,
|
|
399
|
+
pattern=replace_line,
|
|
400
|
+
interpolate_variables=interpolate_variables,
|
|
427
401
|
)
|
|
428
402
|
|
|
429
|
-
|
|
403
|
+
if not present_lines:
|
|
404
|
+
yield echo_command
|
|
430
405
|
else:
|
|
431
|
-
|
|
432
|
-
# does not exist (as we are appending the replacement).
|
|
433
|
-
if replace:
|
|
434
|
-
# Ensure replace explicitly matches a whole line
|
|
435
|
-
replace_line = replace
|
|
436
|
-
if not replace_line.startswith("^"):
|
|
437
|
-
replace_line = f"^{replace_line}"
|
|
438
|
-
if not replace_line.endswith("$"):
|
|
439
|
-
replace_line = f"{replace_line}$"
|
|
440
|
-
|
|
441
|
-
present_lines = host.get_fact(
|
|
442
|
-
FindInFile,
|
|
443
|
-
path=path,
|
|
444
|
-
pattern=replace_line,
|
|
445
|
-
interpolate_variables=interpolate_variables,
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
if not present_lines:
|
|
449
|
-
yield echo_command
|
|
450
|
-
else:
|
|
451
|
-
host.noop('line "{0}" exists in {1}'.format(replace or line, path))
|
|
452
|
-
|
|
453
|
-
host.create_fact(
|
|
454
|
-
FindInFile,
|
|
455
|
-
kwargs={
|
|
456
|
-
"path": path,
|
|
457
|
-
"pattern": match_line,
|
|
458
|
-
"interpolate_variables": interpolate_variables,
|
|
459
|
-
},
|
|
460
|
-
data=[replace or line],
|
|
461
|
-
)
|
|
406
|
+
host.noop('line "{0}" exists in {1}'.format(replace or line, path))
|
|
462
407
|
|
|
463
408
|
# Line(s) exists and we want to remove them, replace with nothing
|
|
464
409
|
elif present_lines and not present:
|
|
@@ -471,32 +416,21 @@ def line(
|
|
|
471
416
|
interpolate_variables=interpolate_variables,
|
|
472
417
|
)
|
|
473
418
|
|
|
474
|
-
host.delete_fact(
|
|
475
|
-
FindInFile,
|
|
476
|
-
kwargs={
|
|
477
|
-
"path": path,
|
|
478
|
-
"pattern": match_line,
|
|
479
|
-
"interpolate_variables": interpolate_variables,
|
|
480
|
-
},
|
|
481
|
-
)
|
|
482
|
-
|
|
483
419
|
# Line(s) exists and we have want to ensure they're correct
|
|
484
420
|
elif present_lines and present:
|
|
485
421
|
# If any of lines are different, sed replace them
|
|
486
422
|
if replace and any(line != replace for line in present_lines):
|
|
487
423
|
yield sed_replace_command
|
|
488
|
-
del present_lines[:] # TODO: use .clear() when py3+
|
|
489
|
-
present_lines.append(replace)
|
|
490
424
|
else:
|
|
491
425
|
host.noop('line "{0}" exists in {1}'.format(replace or line, path))
|
|
492
426
|
|
|
493
427
|
|
|
494
|
-
@operation
|
|
428
|
+
@operation()
|
|
495
429
|
def replace(
|
|
496
|
-
path,
|
|
497
|
-
text=None,
|
|
498
|
-
replace=None,
|
|
499
|
-
flags=None,
|
|
430
|
+
path: str,
|
|
431
|
+
text: str | None = None,
|
|
432
|
+
replace: str | None = None,
|
|
433
|
+
flags: list[str] | None = None,
|
|
500
434
|
backup=False,
|
|
501
435
|
interpolate_variables=False,
|
|
502
436
|
match=None, # deprecated
|
|
@@ -561,32 +495,21 @@ def replace(
|
|
|
561
495
|
backup=backup,
|
|
562
496
|
interpolate_variables=interpolate_variables,
|
|
563
497
|
)
|
|
564
|
-
host.create_fact(
|
|
565
|
-
FindInFile,
|
|
566
|
-
kwargs={
|
|
567
|
-
"path": path,
|
|
568
|
-
"pattern": text,
|
|
569
|
-
"interpolate_variables": interpolate_variables,
|
|
570
|
-
},
|
|
571
|
-
data=[],
|
|
572
|
-
)
|
|
573
498
|
else:
|
|
574
499
|
host.noop('string "{0}" does not exist in {1}'.format(text, path))
|
|
575
500
|
|
|
576
501
|
|
|
577
|
-
@operation(
|
|
578
|
-
pipeline_facts={"find_files": "destination"},
|
|
579
|
-
)
|
|
502
|
+
@operation()
|
|
580
503
|
def sync(
|
|
581
|
-
src,
|
|
582
|
-
dest,
|
|
583
|
-
user=None,
|
|
584
|
-
group=None,
|
|
585
|
-
mode=None,
|
|
586
|
-
dir_mode=None,
|
|
504
|
+
src: str,
|
|
505
|
+
dest: str,
|
|
506
|
+
user: str | None = None,
|
|
507
|
+
group: str | None = None,
|
|
508
|
+
mode: str | None = None,
|
|
509
|
+
dir_mode: str | None = None,
|
|
587
510
|
delete=False,
|
|
588
|
-
exclude=None,
|
|
589
|
-
exclude_dir=None,
|
|
511
|
+
exclude: str | list[str] | tuple[str] | None = None,
|
|
512
|
+
exclude_dir: str | list[str] | tuple[str] | None = None,
|
|
590
513
|
add_deploy_dir=True,
|
|
591
514
|
):
|
|
592
515
|
"""
|
|
@@ -648,7 +571,7 @@ def sync(
|
|
|
648
571
|
put_files = []
|
|
649
572
|
ensure_dirnames = []
|
|
650
573
|
for dirpath, dirnames, filenames in os.walk(src, topdown=True):
|
|
651
|
-
remote_dirpath = os.path.normpath(os.path.relpath(dirpath, src))
|
|
574
|
+
remote_dirpath = Path(os.path.normpath(os.path.relpath(dirpath, src))).as_posix()
|
|
652
575
|
|
|
653
576
|
# Filter excluded dirs
|
|
654
577
|
for child_dir in dirnames[:]:
|
|
@@ -682,8 +605,8 @@ def sync(
|
|
|
682
605
|
if dest_link_info:
|
|
683
606
|
dest_to_ensure = dest_link_info["link_target"]
|
|
684
607
|
|
|
685
|
-
yield from directory(
|
|
686
|
-
dest_to_ensure,
|
|
608
|
+
yield from directory._inner(
|
|
609
|
+
path=dest_to_ensure,
|
|
687
610
|
user=user,
|
|
688
611
|
group=group,
|
|
689
612
|
mode=dir_mode or get_path_permissions_mode(src),
|
|
@@ -691,8 +614,8 @@ def sync(
|
|
|
691
614
|
|
|
692
615
|
# Ensure any remote dirnames
|
|
693
616
|
for dir_path_curr, dir_mode_curr in ensure_dirnames:
|
|
694
|
-
yield from directory(
|
|
695
|
-
unix_path_join(dest, dir_path_curr),
|
|
617
|
+
yield from directory._inner(
|
|
618
|
+
path=unix_path_join(dest, dir_path_curr),
|
|
696
619
|
user=user,
|
|
697
620
|
group=group,
|
|
698
621
|
mode=dir_mode or dir_mode_curr,
|
|
@@ -700,9 +623,9 @@ def sync(
|
|
|
700
623
|
|
|
701
624
|
# Put each file combination
|
|
702
625
|
for local_filename, remote_filename in put_files:
|
|
703
|
-
yield from put(
|
|
704
|
-
local_filename,
|
|
705
|
-
remote_filename,
|
|
626
|
+
yield from put._inner(
|
|
627
|
+
src=local_filename,
|
|
628
|
+
dest=remote_filename,
|
|
706
629
|
user=user,
|
|
707
630
|
group=group,
|
|
708
631
|
mode=mode or get_path_permissions_mode(local_filename),
|
|
@@ -720,7 +643,7 @@ def sync(
|
|
|
720
643
|
if exclude and any(fnmatch(filename, match) for match in exclude):
|
|
721
644
|
continue
|
|
722
645
|
|
|
723
|
-
yield from file(filename, present=False)
|
|
646
|
+
yield from file._inner(path=filename, present=False)
|
|
724
647
|
|
|
725
648
|
|
|
726
649
|
@memoize
|
|
@@ -729,7 +652,7 @@ def show_rsync_warning():
|
|
|
729
652
|
|
|
730
653
|
|
|
731
654
|
@operation(is_idempotent=False)
|
|
732
|
-
def rsync(src, dest, flags
|
|
655
|
+
def rsync(src: str, dest: str, flags: list[str] | None = None):
|
|
733
656
|
"""
|
|
734
657
|
Use ``rsync`` to sync a local directory to the remote system. This operation will actually call
|
|
735
658
|
the ``rsync`` binary on your system.
|
|
@@ -744,6 +667,8 @@ def rsync(src, dest, flags=["-ax", "--delete"]):
|
|
|
744
667
|
global arguments.
|
|
745
668
|
"""
|
|
746
669
|
|
|
670
|
+
if flags is None:
|
|
671
|
+
flags = ["-ax", "--delete"]
|
|
747
672
|
show_rsync_warning()
|
|
748
673
|
|
|
749
674
|
try:
|
|
@@ -754,11 +679,11 @@ def rsync(src, dest, flags=["-ax", "--delete"]):
|
|
|
754
679
|
yield RsyncCommand(src, dest, flags)
|
|
755
680
|
|
|
756
681
|
|
|
757
|
-
def _create_remote_dir(
|
|
682
|
+
def _create_remote_dir(remote_filename, user, group):
|
|
758
683
|
# Always use POSIX style path as local might be Windows, remote always *nix
|
|
759
684
|
remote_dirname = posixpath.dirname(remote_filename)
|
|
760
685
|
if remote_dirname:
|
|
761
|
-
yield from directory(
|
|
686
|
+
yield from directory._inner(
|
|
762
687
|
path=remote_dirname,
|
|
763
688
|
user=user,
|
|
764
689
|
group=group,
|
|
@@ -771,14 +696,10 @@ def _create_remote_dir(state, host, remote_filename, user, group):
|
|
|
771
696
|
# We don't (currently) cache the local state, so there's nothing we can
|
|
772
697
|
# update to flag the local file as present.
|
|
773
698
|
is_idempotent=False,
|
|
774
|
-
pipeline_facts={
|
|
775
|
-
"file": "src",
|
|
776
|
-
"sha1_file": "src",
|
|
777
|
-
},
|
|
778
699
|
)
|
|
779
700
|
def get(
|
|
780
|
-
src,
|
|
781
|
-
dest,
|
|
701
|
+
src: str,
|
|
702
|
+
dest: str,
|
|
782
703
|
add_deploy_dir=True,
|
|
783
704
|
create_local_dir=False,
|
|
784
705
|
force=False,
|
|
@@ -819,11 +740,11 @@ def get(
|
|
|
819
740
|
|
|
820
741
|
# No remote file, so assume exists and download it "blind"
|
|
821
742
|
if not remote_file or force:
|
|
822
|
-
yield FileDownloadCommand(src, dest, remote_temp_filename=
|
|
743
|
+
yield FileDownloadCommand(src, dest, remote_temp_filename=host.get_temp_filename(dest))
|
|
823
744
|
|
|
824
745
|
# No local file, so always download
|
|
825
746
|
elif not os.path.exists(dest):
|
|
826
|
-
yield FileDownloadCommand(src, dest, remote_temp_filename=
|
|
747
|
+
yield FileDownloadCommand(src, dest, remote_temp_filename=host.get_temp_filename(dest))
|
|
827
748
|
|
|
828
749
|
# Remote file exists - check if it matches our local
|
|
829
750
|
else:
|
|
@@ -832,21 +753,16 @@ def get(
|
|
|
832
753
|
|
|
833
754
|
# Check sha1sum, upload if needed
|
|
834
755
|
if local_sum != remote_sum:
|
|
835
|
-
yield FileDownloadCommand(src, dest, remote_temp_filename=
|
|
756
|
+
yield FileDownloadCommand(src, dest, remote_temp_filename=host.get_temp_filename(dest))
|
|
836
757
|
|
|
837
758
|
|
|
838
|
-
@operation(
|
|
839
|
-
pipeline_facts={
|
|
840
|
-
"file": "dest",
|
|
841
|
-
"sha1_file": "dest",
|
|
842
|
-
},
|
|
843
|
-
)
|
|
759
|
+
@operation()
|
|
844
760
|
def put(
|
|
845
|
-
src,
|
|
846
|
-
dest,
|
|
847
|
-
user=None,
|
|
848
|
-
group=None,
|
|
849
|
-
mode=None,
|
|
761
|
+
src: str | IO[Any],
|
|
762
|
+
dest: str,
|
|
763
|
+
user: str | None = None,
|
|
764
|
+
group: str | None = None,
|
|
765
|
+
mode: int | str | bool | None = None,
|
|
850
766
|
add_deploy_dir=True,
|
|
851
767
|
create_remote_dir=True,
|
|
852
768
|
force=False,
|
|
@@ -907,6 +823,7 @@ def put(
|
|
|
907
823
|
|
|
908
824
|
# Assume string filename
|
|
909
825
|
else:
|
|
826
|
+
assert isinstance(src, (str, Path))
|
|
910
827
|
# Add deploy directory?
|
|
911
828
|
if add_deploy_dir and state.cwd:
|
|
912
829
|
src = os.path.join(state.cwd, src)
|
|
@@ -921,7 +838,7 @@ def put(
|
|
|
921
838
|
raise IOError("No such file: {0}".format(local_file))
|
|
922
839
|
|
|
923
840
|
if mode is True:
|
|
924
|
-
if os.path.isfile(local_file):
|
|
841
|
+
if isinstance(local_file, str) and os.path.isfile(local_file):
|
|
925
842
|
mode = get_path_permissions_mode(local_file)
|
|
926
843
|
else:
|
|
927
844
|
logger.warning(
|
|
@@ -934,19 +851,20 @@ def put(
|
|
|
934
851
|
|
|
935
852
|
remote_file = host.get_fact(File, path=dest)
|
|
936
853
|
|
|
937
|
-
if not remote_file and host.get_fact(Directory, path=dest):
|
|
854
|
+
if not remote_file and bool(host.get_fact(Directory, path=dest)):
|
|
855
|
+
assert isinstance(src, str)
|
|
938
856
|
dest = unix_path_join(dest, os.path.basename(src))
|
|
939
857
|
remote_file = host.get_fact(File, path=dest)
|
|
940
858
|
|
|
941
859
|
if create_remote_dir:
|
|
942
|
-
yield from _create_remote_dir(
|
|
860
|
+
yield from _create_remote_dir(dest, user, group)
|
|
943
861
|
|
|
944
862
|
# No remote file, always upload and user/group/mode if supplied
|
|
945
863
|
if not remote_file or force:
|
|
946
864
|
yield FileUploadCommand(
|
|
947
865
|
local_file,
|
|
948
866
|
dest,
|
|
949
|
-
remote_temp_filename=
|
|
867
|
+
remote_temp_filename=host.get_temp_filename(dest),
|
|
950
868
|
)
|
|
951
869
|
|
|
952
870
|
if user or group:
|
|
@@ -964,7 +882,7 @@ def put(
|
|
|
964
882
|
yield FileUploadCommand(
|
|
965
883
|
local_file,
|
|
966
884
|
dest,
|
|
967
|
-
remote_temp_filename=
|
|
885
|
+
remote_temp_filename=host.get_temp_filename(dest),
|
|
968
886
|
)
|
|
969
887
|
|
|
970
888
|
if user or group:
|
|
@@ -989,17 +907,17 @@ def put(
|
|
|
989
907
|
if not changed:
|
|
990
908
|
host.noop("file {0} is already uploaded".format(dest))
|
|
991
909
|
|
|
992
|
-
# Now we've uploaded the file and ensured user/group/mode, update the relevant fact data
|
|
993
|
-
host.create_fact(Sha1File, kwargs={"path": dest}, data=local_sum)
|
|
994
|
-
host.create_fact(
|
|
995
|
-
File,
|
|
996
|
-
kwargs={"path": dest},
|
|
997
|
-
data={"user": user, "group": group, "mode": mode},
|
|
998
|
-
)
|
|
999
|
-
|
|
1000
910
|
|
|
1001
|
-
@operation
|
|
1002
|
-
def template(
|
|
911
|
+
@operation()
|
|
912
|
+
def template(
|
|
913
|
+
src: str | IO[Any],
|
|
914
|
+
dest: str,
|
|
915
|
+
user: str | None = None,
|
|
916
|
+
group: str | None = None,
|
|
917
|
+
mode: str | None = None,
|
|
918
|
+
create_remote_dir=True,
|
|
919
|
+
**data,
|
|
920
|
+
):
|
|
1003
921
|
'''
|
|
1004
922
|
Generate a template using jinja2 and write it to the remote system.
|
|
1005
923
|
|
|
@@ -1081,23 +999,21 @@ def template(src, dest, user=None, group=None, mode=None, create_remote_dir=True
|
|
|
1081
999
|
data.setdefault("host", host)
|
|
1082
1000
|
data.setdefault("state", state)
|
|
1083
1001
|
data.setdefault("inventory", state.inventory)
|
|
1084
|
-
data.setdefault("facts", pyinfra.facts)
|
|
1085
1002
|
|
|
1086
1003
|
# Render and make file-like it's output
|
|
1087
1004
|
try:
|
|
1088
1005
|
output = get_template(src).render(data)
|
|
1089
1006
|
except (TemplateRuntimeError, TemplateSyntaxError, UndefinedError) as e:
|
|
1090
|
-
trace_frames = traceback.extract_tb(sys.exc_info()[2])
|
|
1091
1007
|
trace_frames = [
|
|
1092
1008
|
frame
|
|
1093
|
-
for frame in
|
|
1009
|
+
for frame in traceback.extract_tb(sys.exc_info()[2])
|
|
1094
1010
|
if frame[2] in ("template", "<module>", "top-level template code")
|
|
1095
1011
|
] # thank you https://github.com/saltstack/salt/blob/master/salt/utils/templates.py
|
|
1096
1012
|
|
|
1097
1013
|
line_number = trace_frames[-1][1]
|
|
1098
1014
|
|
|
1099
1015
|
# Quickly read the line in question and one above/below for nicer debugging
|
|
1100
|
-
with
|
|
1016
|
+
with get_file_io(src, "r") as f:
|
|
1101
1017
|
template_lines = f.readlines()
|
|
1102
1018
|
|
|
1103
1019
|
template_lines = [line.strip() for line in template_lines]
|
|
@@ -1110,16 +1026,16 @@ def template(src, dest, user=None, group=None, mode=None, create_remote_dir=True
|
|
|
1110
1026
|
e,
|
|
1111
1027
|
"\n".join(relevant_lines),
|
|
1112
1028
|
),
|
|
1113
|
-
)
|
|
1029
|
+
) from None
|
|
1114
1030
|
|
|
1115
1031
|
output_file = StringIO(output)
|
|
1116
1032
|
# Set the template attribute for nicer debugging
|
|
1117
|
-
output_file.template = src
|
|
1033
|
+
output_file.template = src # type: ignore[attr-defined]
|
|
1118
1034
|
|
|
1119
1035
|
# Pass to the put function
|
|
1120
|
-
yield from put(
|
|
1121
|
-
output_file,
|
|
1122
|
-
dest,
|
|
1036
|
+
yield from put._inner(
|
|
1037
|
+
src=output_file,
|
|
1038
|
+
dest=dest,
|
|
1123
1039
|
user=user,
|
|
1124
1040
|
group=group,
|
|
1125
1041
|
mode=mode,
|
|
@@ -1149,21 +1065,18 @@ def _raise_or_remove_invalid_path(fs_type, path, force, force_backup, force_back
|
|
|
1149
1065
|
raise OperationError("{0} exists and is not a {1}".format(path, fs_type))
|
|
1150
1066
|
|
|
1151
1067
|
|
|
1152
|
-
@operation(
|
|
1153
|
-
pipeline_facts={"link": "path"},
|
|
1154
|
-
)
|
|
1068
|
+
@operation()
|
|
1155
1069
|
def link(
|
|
1156
|
-
path,
|
|
1157
|
-
target=None,
|
|
1070
|
+
path: str,
|
|
1071
|
+
target: str | None = None,
|
|
1158
1072
|
present=True,
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
group=None,
|
|
1073
|
+
user: str | None = None,
|
|
1074
|
+
group: str | None = None,
|
|
1162
1075
|
symbolic=True,
|
|
1163
1076
|
create_remote_dir=True,
|
|
1164
1077
|
force=False,
|
|
1165
1078
|
force_backup=True,
|
|
1166
|
-
force_backup_dir=None,
|
|
1079
|
+
force_backup_dir: str | None = None,
|
|
1167
1080
|
):
|
|
1168
1081
|
"""
|
|
1169
1082
|
Add/remove/update links.
|
|
@@ -1171,7 +1084,6 @@ def link(
|
|
|
1171
1084
|
+ path: the name of the link
|
|
1172
1085
|
+ target: the file/directory the link points to
|
|
1173
1086
|
+ present: whether the link should exist
|
|
1174
|
-
+ assume_present: whether to assume the link exists
|
|
1175
1087
|
+ user: user to own the link
|
|
1176
1088
|
+ group: group to own the link
|
|
1177
1089
|
+ symbolic: whether to make a symbolic link (vs hard link)
|
|
@@ -1198,22 +1110,6 @@ def link(
|
|
|
1198
1110
|
path="/etc/issue2",
|
|
1199
1111
|
target="/etc/issue",
|
|
1200
1112
|
)
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
# Complex example demonstrating the assume_present option
|
|
1204
|
-
from pyinfra.operations import apt, files
|
|
1205
|
-
|
|
1206
|
-
install_nginx = apt.packages(
|
|
1207
|
-
name="Install nginx",
|
|
1208
|
-
packages=["nginx"],
|
|
1209
|
-
)
|
|
1210
|
-
|
|
1211
|
-
files.link(
|
|
1212
|
-
name="Remove default nginx site",
|
|
1213
|
-
path="/etc/nginx/sites-enabled/default",
|
|
1214
|
-
present=False,
|
|
1215
|
-
assume_present=install_nginx.changed,
|
|
1216
|
-
)
|
|
1217
1113
|
"""
|
|
1218
1114
|
|
|
1219
1115
|
path = _validate_path(path)
|
|
@@ -1223,8 +1119,7 @@ def link(
|
|
|
1223
1119
|
|
|
1224
1120
|
info = host.get_fact(Link, path=path)
|
|
1225
1121
|
|
|
1226
|
-
#
|
|
1227
|
-
if info is False:
|
|
1122
|
+
if info is False: # not a link
|
|
1228
1123
|
yield from _raise_or_remove_invalid_path(
|
|
1229
1124
|
"link",
|
|
1230
1125
|
path,
|
|
@@ -1238,49 +1133,28 @@ def link(
|
|
|
1238
1133
|
if symbolic:
|
|
1239
1134
|
add_args.append("-s")
|
|
1240
1135
|
|
|
1241
|
-
add_cmd = StringCommand(" ".join(add_args), QuoteString(target), QuoteString(path))
|
|
1242
1136
|
remove_cmd = StringCommand("rm", "-f", QuoteString(path))
|
|
1243
1137
|
|
|
1244
|
-
|
|
1245
|
-
|
|
1138
|
+
if not present:
|
|
1139
|
+
if info:
|
|
1140
|
+
yield remove_cmd
|
|
1141
|
+
else:
|
|
1142
|
+
host.noop("link {link} does not exist")
|
|
1143
|
+
return
|
|
1144
|
+
|
|
1145
|
+
assert target is not None # appease typing QuoteString below
|
|
1146
|
+
add_cmd = StringCommand(" ".join(add_args), QuoteString(target), QuoteString(path))
|
|
1147
|
+
|
|
1148
|
+
if info is None: # create
|
|
1246
1149
|
if create_remote_dir:
|
|
1247
|
-
yield from _create_remote_dir(
|
|
1150
|
+
yield from _create_remote_dir(path, user, group)
|
|
1248
1151
|
|
|
1249
1152
|
yield add_cmd
|
|
1250
1153
|
|
|
1251
1154
|
if user or group:
|
|
1252
1155
|
yield file_utils.chown(path, user, group, dereference=False)
|
|
1253
1156
|
|
|
1254
|
-
|
|
1255
|
-
Link,
|
|
1256
|
-
kwargs={"path": path},
|
|
1257
|
-
data={"link_target": target, "group": group, "user": user},
|
|
1258
|
-
)
|
|
1259
|
-
|
|
1260
|
-
# It exists and we don't want it
|
|
1261
|
-
elif (assume_present or info) and not present:
|
|
1262
|
-
yield remove_cmd
|
|
1263
|
-
host.delete_fact(Link, kwargs={"path": path})
|
|
1264
|
-
|
|
1265
|
-
# Exists and want to ensure it's state
|
|
1266
|
-
elif (assume_present or info) and present:
|
|
1267
|
-
if assume_present and not info:
|
|
1268
|
-
info = {"link_target": None, "group": None, "user": None}
|
|
1269
|
-
host.create_fact(Link, kwargs={"path": path}, data=info)
|
|
1270
|
-
|
|
1271
|
-
# If we have an absolute path - prepend to any non-absolute values from the fact
|
|
1272
|
-
# and/or the source.
|
|
1273
|
-
if os.path.isabs(path):
|
|
1274
|
-
link_dirname = os.path.dirname(path)
|
|
1275
|
-
|
|
1276
|
-
if not os.path.isabs(target):
|
|
1277
|
-
target = os.path.normpath(unix_path_join(link_dirname, target))
|
|
1278
|
-
|
|
1279
|
-
if info and not os.path.isabs(info["link_target"]):
|
|
1280
|
-
info["link_target"] = os.path.normpath(
|
|
1281
|
-
unix_path_join(link_dirname, info["link_target"]),
|
|
1282
|
-
)
|
|
1283
|
-
|
|
1157
|
+
else: # edit
|
|
1284
1158
|
changed = False
|
|
1285
1159
|
|
|
1286
1160
|
# If the target is wrong, remove & recreate the link
|
|
@@ -1288,47 +1162,34 @@ def link(
|
|
|
1288
1162
|
changed = True
|
|
1289
1163
|
yield remove_cmd
|
|
1290
1164
|
yield add_cmd
|
|
1291
|
-
info["link_target"] = target
|
|
1292
1165
|
|
|
1293
1166
|
# Check user/group
|
|
1294
|
-
if (
|
|
1295
|
-
(not info and (user or group))
|
|
1296
|
-
or (user and info["user"] != user)
|
|
1297
|
-
or (group and info["group"] != group)
|
|
1298
|
-
):
|
|
1167
|
+
if (user and info["user"] != user) or (group and info["group"] != group):
|
|
1299
1168
|
yield file_utils.chown(path, user, group, dereference=False)
|
|
1300
1169
|
changed = True
|
|
1301
|
-
if user:
|
|
1302
|
-
info["user"] = user
|
|
1303
|
-
if group:
|
|
1304
|
-
info["group"] = group
|
|
1305
1170
|
|
|
1306
1171
|
if not changed:
|
|
1307
1172
|
host.noop("link {0} already exists".format(path))
|
|
1308
1173
|
|
|
1309
1174
|
|
|
1310
|
-
@operation(
|
|
1311
|
-
pipeline_facts={"file": "path"},
|
|
1312
|
-
)
|
|
1175
|
+
@operation()
|
|
1313
1176
|
def file(
|
|
1314
|
-
path,
|
|
1177
|
+
path: str,
|
|
1315
1178
|
present=True,
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
mode=None,
|
|
1179
|
+
user: str | None = None,
|
|
1180
|
+
group: str | None = None,
|
|
1181
|
+
mode: int | str | None = None,
|
|
1320
1182
|
touch=False,
|
|
1321
1183
|
create_remote_dir=True,
|
|
1322
1184
|
force=False,
|
|
1323
1185
|
force_backup=True,
|
|
1324
|
-
force_backup_dir=None,
|
|
1186
|
+
force_backup_dir: str | None = None,
|
|
1325
1187
|
):
|
|
1326
1188
|
"""
|
|
1327
1189
|
Add/remove/update files.
|
|
1328
1190
|
|
|
1329
1191
|
+ path: name/path of the remote file
|
|
1330
1192
|
+ present: whether the file should exist
|
|
1331
|
-
+ assume_present: whether to assume the file exists
|
|
1332
1193
|
+ user: user to own the files
|
|
1333
1194
|
+ group: group to own the files
|
|
1334
1195
|
+ mode: permissions of the files as an integer, eg: 755
|
|
@@ -1364,8 +1225,7 @@ def file(
|
|
|
1364
1225
|
mode = ensure_mode_int(mode)
|
|
1365
1226
|
info = host.get_fact(File, path=path)
|
|
1366
1227
|
|
|
1367
|
-
#
|
|
1368
|
-
if info is False:
|
|
1228
|
+
if info is False: # not a file
|
|
1369
1229
|
yield from _raise_or_remove_invalid_path(
|
|
1370
1230
|
"file",
|
|
1371
1231
|
path,
|
|
@@ -1375,10 +1235,16 @@ def file(
|
|
|
1375
1235
|
)
|
|
1376
1236
|
info = None
|
|
1377
1237
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1238
|
+
if not present:
|
|
1239
|
+
if info:
|
|
1240
|
+
yield StringCommand("rm", "-f", QuoteString(path))
|
|
1241
|
+
else:
|
|
1242
|
+
host.noop("file {0} does not exist")
|
|
1243
|
+
return
|
|
1244
|
+
|
|
1245
|
+
if info is None: # create
|
|
1380
1246
|
if create_remote_dir:
|
|
1381
|
-
yield from _create_remote_dir(
|
|
1247
|
+
yield from _create_remote_dir(path, user, group)
|
|
1382
1248
|
|
|
1383
1249
|
yield StringCommand("touch", QuoteString(path))
|
|
1384
1250
|
|
|
@@ -1387,23 +1253,7 @@ def file(
|
|
|
1387
1253
|
if user or group:
|
|
1388
1254
|
yield file_utils.chown(path, user, group)
|
|
1389
1255
|
|
|
1390
|
-
|
|
1391
|
-
File,
|
|
1392
|
-
kwargs={"path": path},
|
|
1393
|
-
data={"mode": mode, "group": group, "user": user},
|
|
1394
|
-
)
|
|
1395
|
-
|
|
1396
|
-
# It exists and we don't want it
|
|
1397
|
-
elif (assume_present or info) and not present:
|
|
1398
|
-
yield StringCommand("rm", "-f", QuoteString(path))
|
|
1399
|
-
host.delete_fact(File, kwargs={"path": path})
|
|
1400
|
-
|
|
1401
|
-
# It exists & we want to ensure its state
|
|
1402
|
-
elif (assume_present or info) and present:
|
|
1403
|
-
if assume_present and not info:
|
|
1404
|
-
info = {"mode": None, "group": None, "user": None}
|
|
1405
|
-
host.create_fact(File, kwargs={"path": path}, data=info)
|
|
1406
|
-
|
|
1256
|
+
else: # update
|
|
1407
1257
|
changed = False
|
|
1408
1258
|
|
|
1409
1259
|
if touch:
|
|
@@ -1413,40 +1263,28 @@ def file(
|
|
|
1413
1263
|
# Check mode
|
|
1414
1264
|
if mode and (not info or info["mode"] != mode):
|
|
1415
1265
|
yield file_utils.chmod(path, mode)
|
|
1416
|
-
info["mode"] = mode
|
|
1417
1266
|
changed = True
|
|
1418
1267
|
|
|
1419
1268
|
# Check user/group
|
|
1420
|
-
if (
|
|
1421
|
-
(not info and (user or group))
|
|
1422
|
-
or (user and info["user"] != user)
|
|
1423
|
-
or (group and info["group"] != group)
|
|
1424
|
-
):
|
|
1269
|
+
if (user and info["user"] != user) or (group and info["group"] != group):
|
|
1425
1270
|
yield file_utils.chown(path, user, group)
|
|
1426
1271
|
changed = True
|
|
1427
|
-
if user:
|
|
1428
|
-
info["user"] = user
|
|
1429
|
-
if group:
|
|
1430
|
-
info["group"] = group
|
|
1431
1272
|
|
|
1432
1273
|
if not changed:
|
|
1433
1274
|
host.noop("file {0} already exists".format(path))
|
|
1434
1275
|
|
|
1435
1276
|
|
|
1436
|
-
@operation(
|
|
1437
|
-
pipeline_facts={"directory": "path"},
|
|
1438
|
-
)
|
|
1277
|
+
@operation()
|
|
1439
1278
|
def directory(
|
|
1440
|
-
path,
|
|
1279
|
+
path: str,
|
|
1441
1280
|
present=True,
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
mode=None,
|
|
1281
|
+
user: str | None = None,
|
|
1282
|
+
group: str | None = None,
|
|
1283
|
+
mode: int | str | None = None,
|
|
1446
1284
|
recursive=False,
|
|
1447
1285
|
force=False,
|
|
1448
1286
|
force_backup=True,
|
|
1449
|
-
force_backup_dir=None,
|
|
1287
|
+
force_backup_dir: str | None = None,
|
|
1450
1288
|
_no_check_owner_mode=False,
|
|
1451
1289
|
_no_fail_on_link=False,
|
|
1452
1290
|
):
|
|
@@ -1455,7 +1293,6 @@ def directory(
|
|
|
1455
1293
|
|
|
1456
1294
|
+ path: path of the remote folder
|
|
1457
1295
|
+ present: whether the folder should exist
|
|
1458
|
-
+ assume_present: whether to assume the directory exists
|
|
1459
1296
|
+ user: user to own the folder
|
|
1460
1297
|
+ group: group to own the folder
|
|
1461
1298
|
+ mode: permissions of the folder
|
|
@@ -1494,8 +1331,7 @@ def directory(
|
|
|
1494
1331
|
mode = ensure_mode_int(mode)
|
|
1495
1332
|
info = host.get_fact(Directory, path=path)
|
|
1496
1333
|
|
|
1497
|
-
#
|
|
1498
|
-
if info is False:
|
|
1334
|
+
if info is False: # not a directory
|
|
1499
1335
|
if _no_fail_on_link and host.get_fact(Link, path=path):
|
|
1500
1336
|
host.noop("directory {0} already exists (as a link)".format(path))
|
|
1501
1337
|
return
|
|
@@ -1508,31 +1344,21 @@ def directory(
|
|
|
1508
1344
|
)
|
|
1509
1345
|
info = None
|
|
1510
1346
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1347
|
+
if not present:
|
|
1348
|
+
if info:
|
|
1349
|
+
yield StringCommand("rm", "-rf", QuoteString(path))
|
|
1350
|
+
else:
|
|
1351
|
+
host.noop("directory {0} does not exist")
|
|
1352
|
+
return
|
|
1353
|
+
|
|
1354
|
+
if info is None: # create
|
|
1513
1355
|
yield StringCommand("mkdir", "-p", QuoteString(path))
|
|
1514
1356
|
if mode:
|
|
1515
1357
|
yield file_utils.chmod(path, mode, recursive=recursive)
|
|
1516
1358
|
if user or group:
|
|
1517
1359
|
yield file_utils.chown(path, user, group, recursive=recursive)
|
|
1518
1360
|
|
|
1519
|
-
|
|
1520
|
-
Directory,
|
|
1521
|
-
kwargs={"path": path},
|
|
1522
|
-
data={"mode": mode, "group": group, "user": user},
|
|
1523
|
-
)
|
|
1524
|
-
|
|
1525
|
-
# It exists and we don't want it
|
|
1526
|
-
elif (assume_present or info) and not present:
|
|
1527
|
-
yield StringCommand("rm", "-rf", QuoteString(path))
|
|
1528
|
-
host.delete_fact(Directory, kwargs={"path": path})
|
|
1529
|
-
|
|
1530
|
-
# It exists & we want to ensure its state
|
|
1531
|
-
elif (assume_present or info) and present:
|
|
1532
|
-
if assume_present and not info:
|
|
1533
|
-
info = {"mode": None, "group": None, "user": None}
|
|
1534
|
-
host.create_fact(Directory, kwargs={"path": path}, data=info)
|
|
1535
|
-
|
|
1361
|
+
else: # update
|
|
1536
1362
|
if _no_check_owner_mode:
|
|
1537
1363
|
return
|
|
1538
1364
|
|
|
@@ -1540,27 +1366,18 @@ def directory(
|
|
|
1540
1366
|
|
|
1541
1367
|
if mode and (not info or info["mode"] != mode):
|
|
1542
1368
|
yield file_utils.chmod(path, mode, recursive=recursive)
|
|
1543
|
-
info["mode"] = mode
|
|
1544
1369
|
changed = True
|
|
1545
1370
|
|
|
1546
|
-
if (
|
|
1547
|
-
(not info and (user or group))
|
|
1548
|
-
or (user and info["user"] != user)
|
|
1549
|
-
or (group and info["group"] != group)
|
|
1550
|
-
):
|
|
1371
|
+
if (user and info["user"] != user) or (group and info["group"] != group):
|
|
1551
1372
|
yield file_utils.chown(path, user, group, recursive=recursive)
|
|
1552
1373
|
changed = True
|
|
1553
|
-
if user:
|
|
1554
|
-
info["user"] = user
|
|
1555
|
-
if group:
|
|
1556
|
-
info["group"] = group
|
|
1557
1374
|
|
|
1558
1375
|
if not changed:
|
|
1559
1376
|
host.noop("directory {0} already exists".format(path))
|
|
1560
1377
|
|
|
1561
1378
|
|
|
1562
|
-
@operation(
|
|
1563
|
-
def flags(path, flags=None, present=True):
|
|
1379
|
+
@operation()
|
|
1380
|
+
def flags(path: str, flags: list[str] | None = None, present=True):
|
|
1564
1381
|
"""
|
|
1565
1382
|
Set/clear file flags.
|
|
1566
1383
|
|
|
@@ -1601,35 +1418,26 @@ def flags(path, flags=None, present=True):
|
|
|
1601
1418
|
prefix = "" if present else "no"
|
|
1602
1419
|
new_flags = ",".join([prefix + flag for flag in sorted(to_change)])
|
|
1603
1420
|
yield StringCommand("chflags", new_flags, QuoteString(path))
|
|
1604
|
-
host.create_fact(
|
|
1605
|
-
Flags,
|
|
1606
|
-
kwargs={"path": path},
|
|
1607
|
-
data=list(current_set | set(to_change))
|
|
1608
|
-
if present
|
|
1609
|
-
else list(current_set - set(to_change)),
|
|
1610
|
-
)
|
|
1611
1421
|
else:
|
|
1612
1422
|
host.noop(
|
|
1613
1423
|
f'\'{path}\' already has \'{",".join(flags)}\' {"set" if present else "clear"}',
|
|
1614
1424
|
)
|
|
1615
1425
|
|
|
1616
1426
|
|
|
1617
|
-
@operation(
|
|
1618
|
-
pipeline_facts={"file": "path"},
|
|
1619
|
-
)
|
|
1427
|
+
@operation()
|
|
1620
1428
|
def block(
|
|
1621
|
-
path,
|
|
1622
|
-
content=None,
|
|
1429
|
+
path: str,
|
|
1430
|
+
content: str | list[str] | None = None,
|
|
1623
1431
|
present=True,
|
|
1624
|
-
line=None,
|
|
1432
|
+
line: str | None = None,
|
|
1625
1433
|
backup=False,
|
|
1626
1434
|
escape_regex_characters=False,
|
|
1627
1435
|
try_prevent_shell_expansion=False,
|
|
1628
1436
|
before=False,
|
|
1629
1437
|
after=False,
|
|
1630
|
-
marker=None,
|
|
1631
|
-
begin=None,
|
|
1632
|
-
end=None,
|
|
1438
|
+
marker: str | None = None,
|
|
1439
|
+
begin: str | None = None,
|
|
1440
|
+
end: str | None = None,
|
|
1633
1441
|
):
|
|
1634
1442
|
"""
|
|
1635
1443
|
Ensure content, surrounded by the appropriate markers, is present (or not) in the file.
|
|
@@ -1718,7 +1526,7 @@ def block(
|
|
|
1718
1526
|
# standard awk doesn't have an "in-place edit" option so we write to a tempfile and
|
|
1719
1527
|
# if edits were successful move to dest i.e. we do: <out_prep> ... do some work ... <real_out>
|
|
1720
1528
|
q_path = QuoteString(path)
|
|
1721
|
-
out_prep = 'OUT="$(TMPDIR=/tmp mktemp -t pyinfra.XXXXXX)" && '
|
|
1529
|
+
out_prep = StringCommand('OUT="$(TMPDIR=/tmp mktemp -t pyinfra.XXXXXX)" && ')
|
|
1722
1530
|
if backup:
|
|
1723
1531
|
out_prep = StringCommand(
|
|
1724
1532
|
"cp",
|
|
@@ -1777,6 +1585,7 @@ def block(
|
|
|
1777
1585
|
f"\n{the_block}\n{here}",
|
|
1778
1586
|
)
|
|
1779
1587
|
elif current == []: # markers not found and have a pattern to match (not start or end)
|
|
1588
|
+
assert isinstance(line, str)
|
|
1780
1589
|
regex = adjust_regex(line, escape_regex_characters)
|
|
1781
1590
|
print_before = "{ print }" if before else ""
|
|
1782
1591
|
print_after = "{ print }" if after else ""
|
|
@@ -1805,9 +1614,11 @@ def block(
|
|
|
1805
1614
|
out_prep,
|
|
1806
1615
|
prog,
|
|
1807
1616
|
q_path,
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1617
|
+
(
|
|
1618
|
+
'"' + "\n".join(content) + '"'
|
|
1619
|
+
if not try_prevent_shell_expansion
|
|
1620
|
+
else "'" + "\n".join(content) + "'"
|
|
1621
|
+
),
|
|
1811
1622
|
"> $OUT &&",
|
|
1812
1623
|
real_out,
|
|
1813
1624
|
)
|
|
@@ -1816,11 +1627,6 @@ def block(
|
|
|
1816
1627
|
|
|
1817
1628
|
if cmd:
|
|
1818
1629
|
yield cmd
|
|
1819
|
-
host.create_fact(
|
|
1820
|
-
Block,
|
|
1821
|
-
kwargs={"path": path, "marker": marker, "begin": begin, "end": end},
|
|
1822
|
-
data=content,
|
|
1823
|
-
)
|
|
1824
1630
|
else: # remove the marked_block
|
|
1825
1631
|
if content:
|
|
1826
1632
|
logger.warning("'content' ignored when removing a marked_block")
|
|
@@ -1829,9 +1635,5 @@ def block(
|
|
|
1829
1635
|
elif current == []:
|
|
1830
1636
|
host.noop("no remove required: markers not found")
|
|
1831
1637
|
else:
|
|
1832
|
-
cmd = f"awk '/{mark_1}/,/{mark_2}/ {{next}} 1'"
|
|
1638
|
+
cmd = StringCommand(f"awk '/{mark_1}/,/{mark_2}/ {{next}} 1'")
|
|
1833
1639
|
yield StringCommand(out_prep, cmd, q_path, "> $OUT &&", real_out)
|
|
1834
|
-
host.delete_fact(
|
|
1835
|
-
Block,
|
|
1836
|
-
kwargs={"path": path, "marker": marker, "begin": begin, "end": end},
|
|
1837
|
-
)
|