pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__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 (126) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +261 -255
  3. pyinfra/api/arguments_typed.py +77 -0
  4. pyinfra/api/command.py +66 -53
  5. pyinfra/api/config.py +27 -22
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +2 -24
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +77 -113
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +17 -25
  13. pyinfra/api/operation.py +232 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +55 -70
  17. pyinfra/connectors/base.py +150 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +227 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +195 -207
  22. pyinfra/connectors/ssh.py +528 -615
  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 +212 -137
  27. pyinfra/connectors/vagrant.py +55 -48
  28. pyinfra/context.py +3 -2
  29. pyinfra/facts/docker.py +1 -0
  30. pyinfra/facts/files.py +45 -32
  31. pyinfra/facts/git.py +3 -1
  32. pyinfra/facts/gpg.py +1 -1
  33. pyinfra/facts/hardware.py +4 -2
  34. pyinfra/facts/iptables.py +5 -3
  35. pyinfra/facts/mysql.py +1 -0
  36. pyinfra/facts/postgres.py +168 -0
  37. pyinfra/facts/postgresql.py +5 -161
  38. pyinfra/facts/selinux.py +3 -1
  39. pyinfra/facts/server.py +77 -30
  40. pyinfra/facts/systemd.py +29 -12
  41. pyinfra/facts/sysvinit.py +10 -10
  42. pyinfra/facts/util/packaging.py +4 -2
  43. pyinfra/local.py +4 -5
  44. pyinfra/operations/apk.py +3 -3
  45. pyinfra/operations/apt.py +25 -47
  46. pyinfra/operations/brew.py +7 -14
  47. pyinfra/operations/bsdinit.py +4 -4
  48. pyinfra/operations/cargo.py +1 -1
  49. pyinfra/operations/choco.py +1 -1
  50. pyinfra/operations/dnf.py +4 -4
  51. pyinfra/operations/files.py +108 -321
  52. pyinfra/operations/gem.py +1 -1
  53. pyinfra/operations/git.py +6 -37
  54. pyinfra/operations/iptables.py +2 -10
  55. pyinfra/operations/launchd.py +1 -1
  56. pyinfra/operations/lxd.py +1 -9
  57. pyinfra/operations/mysql.py +5 -28
  58. pyinfra/operations/npm.py +1 -1
  59. pyinfra/operations/openrc.py +1 -1
  60. pyinfra/operations/pacman.py +3 -3
  61. pyinfra/operations/pip.py +14 -15
  62. pyinfra/operations/pkg.py +1 -1
  63. pyinfra/operations/pkgin.py +3 -3
  64. pyinfra/operations/postgres.py +347 -0
  65. pyinfra/operations/postgresql.py +17 -380
  66. pyinfra/operations/python.py +2 -17
  67. pyinfra/operations/selinux.py +5 -28
  68. pyinfra/operations/server.py +59 -84
  69. pyinfra/operations/snap.py +1 -3
  70. pyinfra/operations/ssh.py +8 -23
  71. pyinfra/operations/systemd.py +7 -7
  72. pyinfra/operations/sysvinit.py +3 -12
  73. pyinfra/operations/upstart.py +4 -4
  74. pyinfra/operations/util/__init__.py +12 -0
  75. pyinfra/operations/util/files.py +2 -2
  76. pyinfra/operations/util/packaging.py +6 -24
  77. pyinfra/operations/util/service.py +18 -37
  78. pyinfra/operations/vzctl.py +2 -2
  79. pyinfra/operations/xbps.py +3 -3
  80. pyinfra/operations/yum.py +4 -4
  81. pyinfra/operations/zypper.py +4 -4
  82. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
  83. pyinfra-3.0b1.dist-info/RECORD +163 -0
  84. pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
  85. pyinfra_cli/__main__.py +2 -0
  86. pyinfra_cli/commands.py +7 -2
  87. pyinfra_cli/exceptions.py +83 -42
  88. pyinfra_cli/inventory.py +19 -4
  89. pyinfra_cli/log.py +17 -3
  90. pyinfra_cli/main.py +133 -90
  91. pyinfra_cli/prints.py +93 -129
  92. pyinfra_cli/util.py +60 -29
  93. tests/test_api/test_api.py +2 -0
  94. tests/test_api/test_api_arguments.py +13 -13
  95. tests/test_api/test_api_deploys.py +28 -29
  96. tests/test_api/test_api_facts.py +60 -98
  97. tests/test_api/test_api_operations.py +100 -200
  98. tests/test_cli/test_cli.py +18 -49
  99. tests/test_cli/test_cli_deploy.py +11 -37
  100. tests/test_cli/test_cli_exceptions.py +50 -19
  101. tests/test_cli/util.py +1 -1
  102. tests/test_connectors/test_chroot.py +6 -6
  103. tests/test_connectors/test_docker.py +4 -4
  104. tests/test_connectors/test_dockerssh.py +38 -50
  105. tests/test_connectors/test_local.py +11 -12
  106. tests/test_connectors/test_ssh.py +66 -107
  107. tests/test_connectors/test_terraform.py +9 -15
  108. tests/test_connectors/test_util.py +24 -46
  109. tests/test_connectors/test_vagrant.py +4 -4
  110. pyinfra/api/operation.pyi +0 -117
  111. pyinfra/connectors/ansible.py +0 -171
  112. pyinfra/connectors/mech.py +0 -186
  113. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  114. pyinfra/connectors/winrm.py +0 -320
  115. pyinfra/facts/windows.py +0 -366
  116. pyinfra/facts/windows_files.py +0 -90
  117. pyinfra/operations/windows.py +0 -59
  118. pyinfra/operations/windows_files.py +0 -551
  119. pyinfra-2.9.2.dist-info/RECORD +0 -170
  120. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  121. tests/test_connectors/test_ansible.py +0 -64
  122. tests/test_connectors/test_mech.py +0 -126
  123. tests/test_connectors/test_winrm.py +0 -76
  124. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
  125. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
  126. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra/facts/server.py CHANGED
@@ -1,11 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import re
3
5
  import shutil
4
6
  from datetime import datetime
5
7
  from tempfile import mkdtemp
8
+ from typing import Dict, List, NewType, Optional, Union
6
9
 
7
10
  from dateutil.parser import parse as parse_date
8
11
  from distro import distro
12
+ from typing_extensions import NotRequired, TypedDict
9
13
 
10
14
  from pyinfra.api import FactBase, ShortFactBase
11
15
  from pyinfra.api.util import try_int
@@ -37,6 +41,14 @@ class Path(FactBase):
37
41
  command = "echo $PATH"
38
42
 
39
43
 
44
+ class TmpDir(FactBase):
45
+ """
46
+ Returns the temporary directory of the current server, if configured.
47
+ """
48
+
49
+ command = "echo $TMPDIR"
50
+
51
+
40
52
  class Hostname(FactBase):
41
53
  """
42
54
  Returns the current hostname of the server.
@@ -62,7 +74,7 @@ class KernelVersion(FactBase):
62
74
 
63
75
 
64
76
  # Deprecated/renamed -> Kernel
65
- class Os(FactBase):
77
+ class Os(FactBase[str]):
66
78
  """
67
79
  Returns the OS name according to ``uname``.
68
80
 
@@ -74,7 +86,7 @@ class Os(FactBase):
74
86
 
75
87
 
76
88
  # Deprecated/renamed -> KernelVersion
77
- class OsVersion(FactBase):
89
+ class OsVersion(FactBase[str]):
78
90
  """
79
91
  Returns the OS version according to ``uname``.
80
92
 
@@ -85,7 +97,7 @@ class OsVersion(FactBase):
85
97
  command = "uname -r"
86
98
 
87
99
 
88
- class Arch(FactBase):
100
+ class Arch(FactBase[str]):
89
101
  """
90
102
  Returns the system architecture according to ``uname``.
91
103
  """
@@ -95,7 +107,7 @@ class Arch(FactBase):
95
107
  command = "uname -m"
96
108
 
97
109
 
98
- class Command(FactBase):
110
+ class Command(FactBase[str]):
99
111
  """
100
112
  Returns the raw output lines of a given command.
101
113
  """
@@ -105,7 +117,7 @@ class Command(FactBase):
105
117
  return command
106
118
 
107
119
 
108
- class Which(FactBase):
120
+ class Which(FactBase[Optional[str]]):
109
121
  """
110
122
  Returns the path of a given command, if available.
111
123
  """
@@ -115,7 +127,7 @@ class Which(FactBase):
115
127
  return "which {0} || true".format(command)
116
128
 
117
129
 
118
- class Date(FactBase):
130
+ class Date(FactBase[datetime]):
119
131
  """
120
132
  Returns the current datetime on the server.
121
133
  """
@@ -124,11 +136,11 @@ class Date(FactBase):
124
136
  default = datetime.now
125
137
 
126
138
  @staticmethod
127
- def process(output):
139
+ def process(output) -> datetime:
128
140
  return datetime.strptime(output[0], ISO_DATE_FORMAT)
129
141
 
130
142
 
131
- class MacosVersion(FactBase):
143
+ class MacosVersion(FactBase[str]):
132
144
  """
133
145
  Returns the installed MacOS version.
134
146
  """
@@ -137,7 +149,13 @@ class MacosVersion(FactBase):
137
149
  requires_command = "sw_vers"
138
150
 
139
151
 
140
- class Mounts(FactBase):
152
+ class MountsDict(TypedDict):
153
+ device: str
154
+ type: str
155
+ options: list[str]
156
+
157
+
158
+ class Mounts(FactBase[Dict[str, MountsDict]]):
141
159
  """
142
160
  Returns a dictionary of mounted filesystems and information.
143
161
 
@@ -159,8 +177,8 @@ class Mounts(FactBase):
159
177
  default = dict
160
178
 
161
179
  @staticmethod
162
- def process(output):
163
- devices = {}
180
+ def process(output) -> dict[str, MountsDict]:
181
+ devices: dict[str, MountsDict] = {}
164
182
 
165
183
  for line in output:
166
184
  is_map = False
@@ -285,9 +303,14 @@ class Sysctl(FactBase):
285
303
  }
286
304
  """
287
305
 
288
- command = "sysctl -a"
289
306
  default = dict
290
307
 
308
+ @staticmethod
309
+ def command(keys=None):
310
+ if keys is None:
311
+ return "sysctl -a"
312
+ return f"sysctl {' '.join(keys)}"
313
+
291
314
  @staticmethod
292
315
  def process(output):
293
316
  sysctls = {}
@@ -317,7 +340,7 @@ class Sysctl(FactBase):
317
340
  return sysctls
318
341
 
319
342
 
320
- class Groups(FactBase):
343
+ class Groups(FactBase[List[str]]):
321
344
  """
322
345
  Returns a list of groups on the system.
323
346
  """
@@ -326,8 +349,8 @@ class Groups(FactBase):
326
349
  default = list
327
350
 
328
351
  @staticmethod
329
- def process(output):
330
- groups = []
352
+ def process(output) -> list[str]:
353
+ groups: list[str] = []
331
354
 
332
355
  for line in output:
333
356
  if ":" in line:
@@ -336,7 +359,20 @@ class Groups(FactBase):
336
359
  return groups
337
360
 
338
361
 
339
- class Crontab(FactBase):
362
+ CrontabCommand = NewType("CrontabCommand", int)
363
+
364
+
365
+ class CrontabDict(TypedDict):
366
+ minute: NotRequired[Union[int, str]]
367
+ hour: NotRequired[Union[int, str]]
368
+ month: NotRequired[Union[int, str]]
369
+ day_of_month: NotRequired[Union[int, str]]
370
+ day_of_week: NotRequired[Union[int, str]]
371
+ comments: Optional[list[str]]
372
+ special_time: NotRequired[str]
373
+
374
+
375
+ class Crontab(FactBase[Dict[CrontabCommand, CrontabDict]]):
340
376
  """
341
377
  Returns a dictionary of cron command -> execution time.
342
378
 
@@ -368,7 +404,7 @@ class Crontab(FactBase):
368
404
 
369
405
  @staticmethod
370
406
  def process(output):
371
- crons = {}
407
+ crons: dict[Command, CrontabDict] = {}
372
408
  current_comments = []
373
409
 
374
410
  for line in output:
@@ -478,7 +514,14 @@ class Users(FactBase):
478
514
  return users
479
515
 
480
516
 
481
- class LinuxDistribution(FactBase):
517
+ class LinuxDistributionDict(TypedDict):
518
+ name: Optional[str]
519
+ major: Optional[int]
520
+ minor: Optional[int]
521
+ release_meta: Dict
522
+
523
+
524
+ class LinuxDistribution(FactBase[LinuxDistributionDict]):
482
525
  """
483
526
  Returns a dict of the Linux distribution version. Ubuntu, Debian, CentOS,
484
527
  Fedora & Gentoo currently. Also contains any key/value items located in
@@ -516,7 +559,7 @@ class LinuxDistribution(FactBase):
516
559
  }
517
560
 
518
561
  @staticmethod
519
- def default():
562
+ def default() -> LinuxDistributionDict:
520
563
  return {
521
564
  "name": None,
522
565
  "major": None,
@@ -524,7 +567,7 @@ class LinuxDistribution(FactBase):
524
567
  "release_meta": {},
525
568
  }
526
569
 
527
- def process(self, output):
570
+ def process(self, output) -> LinuxDistributionDict:
528
571
  parts = {}
529
572
  for part in "\n".join(output).strip().split("---"):
530
573
  if not part.strip():
@@ -578,7 +621,7 @@ class LinuxDistribution(FactBase):
578
621
  return release_info
579
622
 
580
623
 
581
- class LinuxName(ShortFactBase):
624
+ class LinuxName(ShortFactBase[str]):
582
625
  """
583
626
  Returns the name of the Linux distribution. Shortcut for
584
627
  ``host.get_fact(LinuxDistribution)['name']``.
@@ -591,7 +634,11 @@ class LinuxName(ShortFactBase):
591
634
  return data["name"]
592
635
 
593
636
 
594
- class Selinux(FactBase):
637
+ class SelinuxDict(TypedDict):
638
+ mode: Optional[str]
639
+
640
+
641
+ class Selinux(FactBase[SelinuxDict]):
595
642
  """
596
643
  Discovers the SELinux related facts on the target host.
597
644
 
@@ -606,12 +653,12 @@ class Selinux(FactBase):
606
653
  requires_command = "sestatus"
607
654
 
608
655
  @staticmethod
609
- def default():
656
+ def default() -> SelinuxDict:
610
657
  return {
611
658
  "mode": None,
612
659
  }
613
660
 
614
- def process(self, output):
661
+ def process(self, output) -> SelinuxDict:
615
662
  selinux_info = self.default()
616
663
 
617
664
  match = re.match(r"^SELinux status:\s+(\S+)", "\n".join(output))
@@ -624,7 +671,7 @@ class Selinux(FactBase):
624
671
  return selinux_info
625
672
 
626
673
 
627
- class LinuxGui(FactBase):
674
+ class LinuxGui(FactBase[List[str]]):
628
675
  """
629
676
  Returns a list of available Linux GUIs.
630
677
  """
@@ -640,7 +687,7 @@ class LinuxGui(FactBase):
640
687
  "/usr/bin/xfce4-session": "XFCE 4",
641
688
  }
642
689
 
643
- def process(self, output):
690
+ def process(self, output) -> list[str]:
644
691
  gui_names = []
645
692
 
646
693
  for line in output:
@@ -651,7 +698,7 @@ class LinuxGui(FactBase):
651
698
  return gui_names
652
699
 
653
700
 
654
- class HasGui(ShortFactBase):
701
+ class HasGui(ShortFactBase[bool]):
655
702
  """
656
703
  Returns a boolean indicating the remote side has GUI capabilities. Linux only.
657
704
  """
@@ -659,11 +706,11 @@ class HasGui(ShortFactBase):
659
706
  fact = LinuxGui
660
707
 
661
708
  @staticmethod
662
- def process_data(data):
709
+ def process_data(data) -> bool:
663
710
  return len(data) > 0
664
711
 
665
712
 
666
- class Locales(FactBase):
713
+ class Locales(FactBase[List[str]]):
667
714
  """
668
715
  Returns installed locales on the target host.
669
716
 
@@ -676,7 +723,7 @@ class Locales(FactBase):
676
723
  requires_command = "locale"
677
724
  default = list
678
725
 
679
- def process(self, output):
726
+ def process(self, output) -> list[str]:
680
727
  # replace utf8 with UTF-8 to match names in /etc/locale.gen
681
728
  # return a list of enabled locales
682
729
  return [line.replace("utf8", "UTF-8") for line in output]
pyinfra/facts/systemd.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import re
2
+ from typing import Dict, Iterable
2
3
 
3
- from pyinfra.api import FactBase
4
+ from pyinfra.api import FactBase, FactTypeError, QuoteString, StringCommand
4
5
 
5
6
  # Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
6
7
  # The "unit prefix" must consist of one or more valid characters
@@ -21,21 +22,19 @@ SYSTEMD_UNIT_NAME_REGEX = (
21
22
 
22
23
  def _make_systemctl_cmd(user_mode=False, machine=None, user_name=None):
23
24
  # base command for normal and user mode
24
- systemctl_cmd = "systemctl --user" if user_mode else "systemctl"
25
+ systemctl_cmd = ["systemctl --user"] if user_mode else ["systemctl"]
25
26
 
26
27
  # add user and machine flag if given in args
27
28
  if machine is not None:
28
29
  if user_name is not None:
29
- machine_opt = "--machine={1}@{0}".format(machine, user_name)
30
+ systemctl_cmd.append("--machine={1}@{0}".format(machine, user_name))
30
31
  else:
31
- machine_opt = "--machine={0}".format(machine)
32
+ systemctl_cmd.append("--machine={0}".format(machine))
32
33
 
33
- systemctl_cmd = "{0} {1}".format(systemctl_cmd, machine_opt)
34
+ return StringCommand(*systemctl_cmd)
34
35
 
35
- return systemctl_cmd
36
36
 
37
-
38
- class SystemdStatus(FactBase):
37
+ class SystemdStatus(FactBase[Dict[str, bool]]):
39
38
  """
40
39
  Returns a dictionary map of systemd units to booleans indicating whether they are active.
41
40
 
@@ -58,17 +57,35 @@ class SystemdStatus(FactBase):
58
57
  state_key = "SubState"
59
58
  state_values = ["running", "waiting", "exited"]
60
59
 
61
- def command(self, user_mode=False, machine=None, user_name=None):
60
+ def command(self, user_mode=False, machine=None, user_name=None, services=None):
62
61
  fact_cmd = _make_systemctl_cmd(
63
62
  user_mode=user_mode,
64
63
  machine=machine,
65
64
  user_name=user_name,
66
65
  )
67
66
 
68
- return f"{fact_cmd} show --all --property Id --property {self.state_key} '*'"
67
+ if services is None:
68
+ service_strs = [QuoteString("*")]
69
+ elif isinstance(services, str):
70
+ service_strs = [QuoteString(services)]
71
+ elif isinstance(services, Iterable):
72
+ service_strs = [QuoteString(s) for s in services]
73
+ else:
74
+ raise FactTypeError(f"Invalid type passed for services argument: {type(services)}")
75
+
76
+ return StringCommand(
77
+ fact_cmd,
78
+ "show",
79
+ "--all",
80
+ "--property",
81
+ "Id",
82
+ "--property",
83
+ self.state_key,
84
+ *service_strs,
85
+ )
69
86
 
70
- def process(self, output):
71
- services = {}
87
+ def process(self, output) -> Dict[str, bool]:
88
+ services: Dict[str, bool] = {}
72
89
 
73
90
  current_unit = None
74
91
  for line in output:
pyinfra/facts/sysvinit.py CHANGED
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ from typing import Optional
2
5
 
3
6
  from pyinfra.api import FactBase
4
7
 
@@ -6,7 +9,7 @@ from pyinfra.api import FactBase
6
9
  class InitdStatus(FactBase):
7
10
  """
8
11
  Low level check for every /etc/init.d/* script. Unfortunately many of these
9
- mishehave and return exit status 0 while also displaying the help info/not
12
+ misbehave and return exit status 0 while also displaying the help info/not
10
13
  offering status support.
11
14
 
12
15
  Returns a dict of name -> status.
@@ -29,26 +32,23 @@ class InitdStatus(FactBase):
29
32
  regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
30
33
  default = dict
31
34
 
32
- def process(self, output):
33
- services = {}
35
+ def process(self, output) -> dict[str, Optional[bool]]:
36
+ services: dict[str, Optional[bool]] = {}
34
37
 
35
38
  for line in output:
36
39
  matches = re.match(self.regex, line)
37
40
  if matches:
38
- status = int(matches.group(2))
41
+ intstatus = int(matches.group(2))
42
+ status: Optional[bool] = None
39
43
 
40
44
  # Exit code 0 = OK/running
41
- if status == 0:
45
+ if intstatus == 0:
42
46
  status = True
43
47
 
44
48
  # Exit codes 1-3 = DOWN/not running
45
- elif status < 4:
49
+ elif intstatus < 4:
46
50
  status = False
47
51
 
48
- # Exit codes 4+ = unknown
49
- else:
50
- status = None
51
-
52
52
  services[matches.group(1)] = status
53
53
 
54
54
  return services
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
 
3
5
 
4
6
  def parse_packages(regex, output):
5
- packages = {}
7
+ packages: dict[str, set[str]] = {}
6
8
 
7
9
  for line in output:
8
10
  matches = re.match(regex, line)
@@ -18,7 +20,7 @@ def parse_packages(regex, output):
18
20
  def _parse_yum_or_zypper_repositories(output):
19
21
  repos = []
20
22
 
21
- current_repo = {}
23
+ current_repo: dict[str, str] = {}
22
24
  for line in output:
23
25
  line = line.strip()
24
26
  if not line or line.startswith("#"):
pyinfra/local.py CHANGED
@@ -6,7 +6,7 @@ import pyinfra
6
6
  from pyinfra import config, host, logger, state
7
7
  from pyinfra.api.exceptions import PyinfraError
8
8
  from pyinfra.api.util import get_file_path
9
- from pyinfra.connectors.util import run_local_process, split_combined_output
9
+ from pyinfra.connectors.util import run_local_process
10
10
  from pyinfra.context import ctx_state
11
11
 
12
12
 
@@ -76,19 +76,18 @@ def shell(
76
76
  if print_input:
77
77
  click.echo("{0}>>> {1}".format(print_prefix, command), err=True)
78
78
 
79
- return_code, combined_output = run_local_process(
79
+ return_code, output = run_local_process(
80
80
  command,
81
81
  print_output=print_output,
82
82
  print_prefix=print_prefix,
83
83
  )
84
- stdout, stderr = split_combined_output(combined_output)
85
84
 
86
85
  if return_code > 0 and not ignore_errors:
87
86
  raise PyinfraError(
88
- "Local command failed: {0}\n{1}".format(command, stderr),
87
+ "Local command failed: {0}\n{1}".format(command, output.stderr),
89
88
  )
90
89
 
91
- all_stdout.extend(stdout)
90
+ all_stdout.extend(output.stdout_lines)
92
91
 
93
92
  if not splitlines:
94
93
  return "\n".join(all_stdout)
pyinfra/operations/apk.py CHANGED
@@ -23,7 +23,7 @@ def upgrade(available: bool = False):
23
23
  yield "apk upgrade"
24
24
 
25
25
 
26
- _upgrade = upgrade # noqa: E305
26
+ _upgrade = upgrade._inner # noqa: E305
27
27
 
28
28
 
29
29
  @operation(is_idempotent=False)
@@ -35,10 +35,10 @@ def update():
35
35
  yield "apk update"
36
36
 
37
37
 
38
- _update = update # noqa: E305
38
+ _update = update._inner # noqa: E305
39
39
 
40
40
 
41
- @operation
41
+ @operation()
42
42
  def packages(
43
43
  packages=None,
44
44
  present=True,
pyinfra/operations/apt.py CHANGED
@@ -2,10 +2,10 @@
2
2
  Manage apt packages and repositories.
3
3
  """
4
4
 
5
- from datetime import datetime, timedelta
5
+ from datetime import timedelta
6
6
  from urllib.parse import urlparse
7
7
 
8
- from pyinfra import host, state
8
+ from pyinfra import host
9
9
  from pyinfra.api import OperationError, operation
10
10
  from pyinfra.facts.apt import AptKeys, AptSources, parse_apt_repo
11
11
  from pyinfra.facts.deb import DebPackage, DebPackages
@@ -36,7 +36,7 @@ def noninteractive_apt(command, force=False):
36
36
  return " ".join(args)
37
37
 
38
38
 
39
- @operation
39
+ @operation()
40
40
  def key(src=None, keyserver=None, keyid=None):
41
41
  """
42
42
  Add apt gpg keys with ``apt-key``.
@@ -78,10 +78,6 @@ def key(src=None, keyserver=None, keyid=None):
78
78
  yield "(wget -O - {0} || curl -sSLf {0}) | apt-key add -".format(src)
79
79
  else:
80
80
  yield "apt-key add {0}".format(src)
81
-
82
- if keyid:
83
- for kid in keyid:
84
- existing_keys[kid] = {}
85
81
  else:
86
82
  host.noop("All keys from {0} are already available in the apt keychain".format(src))
87
83
 
@@ -98,8 +94,6 @@ def key(src=None, keyserver=None, keyid=None):
98
94
  keyserver,
99
95
  " ".join(needed_keys),
100
96
  )
101
- for kid in keyid:
102
- existing_keys[kid] = {}
103
97
  else:
104
98
  host.noop(
105
99
  "Keys {0} are already available in the apt keychain".format(
@@ -108,7 +102,7 @@ def key(src=None, keyserver=None, keyid=None):
108
102
  )
109
103
 
110
104
 
111
- @operation
105
+ @operation()
112
106
  def repo(src, present=True, filename=None):
113
107
  """
114
108
  Add/remove apt repositories.
@@ -144,24 +138,20 @@ def repo(src, present=True, filename=None):
144
138
 
145
139
  # Doesn't exist and we want it
146
140
  if not is_present and present:
147
- yield from files.line(
148
- filename,
149
- src,
141
+ yield from files.line._inner(
142
+ path=filename,
143
+ line=src,
150
144
  escape_regex_characters=True,
151
145
  )
152
- apt_sources.append(repo)
153
146
 
154
147
  # Exists and we don't want it
155
148
  elif is_present and not present:
156
- yield from files.line(
157
- filename,
158
- src,
149
+ yield from files.line._inner(
150
+ path=filename,
151
+ line=src,
159
152
  present=False,
160
- assume_present=True,
161
153
  escape_regex_characters=True,
162
154
  )
163
- apt_sources.remove(repo)
164
-
165
155
  else:
166
156
  host.noop(
167
157
  'apt repo "{0}" {1}'.format(
@@ -201,7 +191,7 @@ def ppa(src, present=True):
201
191
  yield 'apt-add-repository -y --remove "{0}"'.format(src)
202
192
 
203
193
 
204
- @operation
194
+ @operation()
205
195
  def deb(src, present=True, force=False):
206
196
  """
207
197
  Add/remove ``.deb`` file packages.
@@ -234,10 +224,10 @@ def deb(src, present=True, force=False):
234
224
  # If source is a url
235
225
  if urlparse(src).scheme:
236
226
  # Generate a temp filename
237
- temp_filename = state.get_temp_filename(src)
227
+ temp_filename = host.get_temp_filename(src)
238
228
 
239
229
  # Ensure it's downloaded
240
- yield from files.download(src, temp_filename)
230
+ yield from files.download._inner(src=src, dest=temp_filename)
241
231
 
242
232
  # Override the source with the downloaded file
243
233
  src = temp_filename
@@ -266,9 +256,6 @@ def deb(src, present=True, force=False):
266
256
  # Now reinstall, and critically configure, the package - if there are still
267
257
  # missing deps, now we error
268
258
  yield "dpkg --force-confdef --force-confold -i {0}".format(src)
269
-
270
- if info:
271
- current_packages[info["name"]] = [info.get("version")]
272
259
  else:
273
260
  host.noop("deb {0} is installed".format(original_src))
274
261
 
@@ -279,7 +266,6 @@ def deb(src, present=True, force=False):
279
266
  noninteractive_apt("remove", force=force),
280
267
  info["name"],
281
268
  )
282
- current_packages.pop(info["name"])
283
269
  else:
284
270
  host.noop("deb {0} is not installed".format(original_src))
285
271
 
@@ -326,14 +312,6 @@ def update(cache_time=None):
326
312
  # cache_time to work.
327
313
  if cache_time:
328
314
  yield "touch {0}".format(APT_UPDATE_FILENAME)
329
- if cache_info is None:
330
- host.create_fact(
331
- File,
332
- kwargs={"path": APT_UPDATE_FILENAME},
333
- data={"mtime": datetime.utcnow()},
334
- )
335
- else:
336
- cache_info["mtime"] = datetime.utcnow()
337
315
 
338
316
 
339
317
  _update = update # noqa: E305
@@ -390,7 +368,7 @@ def dist_upgrade():
390
368
  yield noninteractive_apt("dist-upgrade")
391
369
 
392
370
 
393
- @operation
371
+ @operation()
394
372
  def packages(
395
373
  packages=None,
396
374
  present=True,
@@ -454,29 +432,29 @@ def packages(
454
432
  """
455
433
 
456
434
  if update:
457
- yield from _update(cache_time=cache_time)
435
+ yield from _update._inner(cache_time=cache_time)
458
436
 
459
437
  if upgrade:
460
- yield from _upgrade()
438
+ yield from _upgrade._inner()
461
439
 
462
- install_command = ["install"]
440
+ install_command_args = ["install"]
463
441
  if no_recommends is True:
464
- install_command.append("--no-install-recommends")
442
+ install_command_args.append("--no-install-recommends")
465
443
  if allow_downgrades:
466
- install_command.append("--allow-downgrades")
444
+ install_command_args.append("--allow-downgrades")
467
445
 
468
- upgrade_command = " ".join(install_command)
446
+ upgrade_command = " ".join(install_command_args)
469
447
 
470
448
  if extra_install_args:
471
- install_command.append(extra_install_args)
449
+ install_command_args.append(extra_install_args)
472
450
 
473
- install_command = " ".join(install_command)
451
+ install_command = " ".join(install_command_args)
474
452
 
475
- uninstall_command = ["remove"]
453
+ uninstall_command_args = ["remove"]
476
454
  if extra_uninstall_args:
477
- uninstall_command.append(extra_uninstall_args)
455
+ uninstall_command_args.append(extra_uninstall_args)
478
456
 
479
- uninstall_command = " ".join(uninstall_command)
457
+ uninstall_command = " ".join(uninstall_command_args)
480
458
 
481
459
  # Compare/ensure packages are present/not
482
460
  yield from ensure_packages(