pyinfra 2.9.2__py2.py3-none-any.whl → 3.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.2.dist-info/RECORD +0 -170
  151. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
@@ -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
- cache_time = host.get_fact(Date).replace(tzinfo=None) - timedelta(seconds=cache_time)
126
- if info["mtime"] and info["mtime"] < cache_time:
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 = state.get_temp_filename(dest)
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 seperate new
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
- if assume_present:
358
- present_lines = [line]
359
- else:
360
- present_lines = host.get_fact(
361
- FindInFile,
362
- path=path,
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 the file does not exist - it *might* be created, so we handle it
411
- # dynamically with a little script.
412
- if present_lines is None:
413
- yield make_formatted_string_command(
414
- """
415
- if [ -f '{target}' ]; then
416
- ( grep {match_line} '{target}' && \
417
- {sed_replace_command}) 2> /dev/null || \
418
- {echo_command} ;
419
- else
420
- {echo_command} ;
421
- fi
422
- """,
423
- target=QuoteString(path),
424
- match_line=QuoteString(match_line),
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
- # File exists but has no matching lines - append it.
403
+ if not present_lines:
404
+ yield echo_command
430
405
  else:
431
- # If we're doing replacement, only append if the *replacement* line
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=["-ax", "--delete"]):
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(state, host, remote_filename, user, group):
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=state.get_temp_filename(dest))
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=state.get_temp_filename(dest))
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=state.get_temp_filename(dest))
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(state, host, dest, user, group)
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=state.get_temp_filename(dest),
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=state.get_temp_filename(dest),
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(src, dest, user=None, group=None, mode=None, create_remote_dir=True, **data):
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 trace_frames
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 open(src, "r") as f:
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
- assume_present=False,
1160
- user=None,
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
- # Not a link?
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
- # No link and we want it
1245
- if not assume_present and info is None and present:
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(state, host, path, user, group)
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
- host.create_fact(
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
- assume_present=False,
1317
- user=None,
1318
- group=None,
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
- # Not a file?!
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
- # Doesn't exist & we want it
1379
- if not assume_present and info is None and present:
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(state, host, path, user, group)
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
- host.create_fact(
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
- assume_present=False,
1443
- user=None,
1444
- group=None,
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
- # Not a directory?!
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
- # Doesn't exist & we want it
1512
- if not assume_present and info is None and present:
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
- host.create_fact(
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(pipeline_facts={"flags": "path"})
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
- '"' + "\n".join(content) + '"'
1809
- if not try_prevent_shell_expansion
1810
- else "'" + "\n".join(content) + "'",
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
- )