pyinfra 3.0.dev0__py2.py3-none-any.whl → 3.0.2__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 (148) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +115 -97
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +5 -3
  5. pyinfra/api/config.py +139 -39
  6. pyinfra/api/connectors.py +5 -2
  7. pyinfra/api/deploy.py +19 -19
  8. pyinfra/api/exceptions.py +35 -4
  9. pyinfra/api/facts.py +62 -86
  10. pyinfra/api/host.py +102 -15
  11. pyinfra/api/inventory.py +4 -0
  12. pyinfra/api/operation.py +188 -120
  13. pyinfra/api/operations.py +66 -113
  14. pyinfra/api/state.py +53 -34
  15. pyinfra/api/util.py +64 -33
  16. pyinfra/connectors/base.py +65 -20
  17. pyinfra/connectors/chroot.py +15 -13
  18. pyinfra/connectors/docker.py +62 -72
  19. pyinfra/connectors/dockerssh.py +20 -19
  20. pyinfra/connectors/local.py +32 -22
  21. pyinfra/connectors/ssh.py +162 -86
  22. pyinfra/connectors/sshuserclient/client.py +1 -1
  23. pyinfra/connectors/terraform.py +57 -39
  24. pyinfra/connectors/util.py +26 -27
  25. pyinfra/connectors/vagrant.py +27 -26
  26. pyinfra/context.py +1 -0
  27. pyinfra/facts/apk.py +7 -2
  28. pyinfra/facts/apt.py +15 -7
  29. pyinfra/facts/brew.py +28 -13
  30. pyinfra/facts/bsdinit.py +9 -6
  31. pyinfra/facts/cargo.py +6 -3
  32. pyinfra/facts/choco.py +8 -4
  33. pyinfra/facts/deb.py +21 -9
  34. pyinfra/facts/dnf.py +11 -6
  35. pyinfra/facts/docker.py +30 -5
  36. pyinfra/facts/files.py +49 -33
  37. pyinfra/facts/gem.py +7 -2
  38. pyinfra/facts/git.py +14 -21
  39. pyinfra/facts/gpg.py +4 -1
  40. pyinfra/facts/hardware.py +186 -138
  41. pyinfra/facts/launchd.py +7 -2
  42. pyinfra/facts/lxd.py +8 -2
  43. pyinfra/facts/mysql.py +19 -12
  44. pyinfra/facts/npm.py +3 -1
  45. pyinfra/facts/openrc.py +8 -2
  46. pyinfra/facts/pacman.py +13 -5
  47. pyinfra/facts/pip.py +2 -0
  48. pyinfra/facts/pkg.py +5 -1
  49. pyinfra/facts/pkgin.py +7 -2
  50. pyinfra/facts/postgres.py +170 -0
  51. pyinfra/facts/postgresql.py +5 -162
  52. pyinfra/facts/rpm.py +21 -15
  53. pyinfra/facts/runit.py +70 -0
  54. pyinfra/facts/selinux.py +12 -4
  55. pyinfra/facts/server.py +240 -82
  56. pyinfra/facts/snap.py +8 -2
  57. pyinfra/facts/systemd.py +37 -13
  58. pyinfra/facts/sysvinit.py +7 -4
  59. pyinfra/facts/upstart.py +7 -2
  60. pyinfra/facts/util/packaging.py +3 -2
  61. pyinfra/facts/vzctl.py +8 -4
  62. pyinfra/facts/xbps.py +7 -2
  63. pyinfra/facts/yum.py +10 -5
  64. pyinfra/facts/zypper.py +9 -4
  65. pyinfra/operations/apk.py +5 -3
  66. pyinfra/operations/apt.py +28 -25
  67. pyinfra/operations/brew.py +60 -29
  68. pyinfra/operations/bsdinit.py +6 -4
  69. pyinfra/operations/cargo.py +3 -1
  70. pyinfra/operations/choco.py +3 -1
  71. pyinfra/operations/dnf.py +16 -20
  72. pyinfra/operations/docker.py +339 -0
  73. pyinfra/operations/files.py +187 -168
  74. pyinfra/operations/gem.py +3 -1
  75. pyinfra/operations/git.py +23 -25
  76. pyinfra/operations/iptables.py +33 -25
  77. pyinfra/operations/launchd.py +5 -6
  78. pyinfra/operations/lxd.py +7 -4
  79. pyinfra/operations/mysql.py +59 -55
  80. pyinfra/operations/npm.py +8 -1
  81. pyinfra/operations/openrc.py +5 -3
  82. pyinfra/operations/pacman.py +6 -7
  83. pyinfra/operations/pip.py +19 -12
  84. pyinfra/operations/pkg.py +3 -1
  85. pyinfra/operations/pkgin.py +5 -3
  86. pyinfra/operations/postgres.py +349 -0
  87. pyinfra/operations/postgresql.py +18 -335
  88. pyinfra/operations/puppet.py +3 -1
  89. pyinfra/operations/python.py +8 -19
  90. pyinfra/operations/runit.py +182 -0
  91. pyinfra/operations/selinux.py +47 -29
  92. pyinfra/operations/server.py +138 -67
  93. pyinfra/operations/snap.py +3 -1
  94. pyinfra/operations/ssh.py +18 -16
  95. pyinfra/operations/systemd.py +18 -12
  96. pyinfra/operations/sysvinit.py +7 -5
  97. pyinfra/operations/upstart.py +7 -5
  98. pyinfra/operations/util/__init__.py +12 -0
  99. pyinfra/operations/util/docker.py +177 -0
  100. pyinfra/operations/util/files.py +24 -16
  101. pyinfra/operations/util/packaging.py +54 -38
  102. pyinfra/operations/util/service.py +39 -47
  103. pyinfra/operations/vzctl.py +12 -10
  104. pyinfra/operations/xbps.py +5 -3
  105. pyinfra/operations/yum.py +15 -19
  106. pyinfra/operations/zypper.py +9 -10
  107. pyinfra/version.py +5 -2
  108. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/METADATA +51 -58
  109. pyinfra-3.0.2.dist-info/RECORD +168 -0
  110. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/WHEEL +1 -1
  111. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/entry_points.txt +0 -3
  112. pyinfra_cli/__main__.py +4 -3
  113. pyinfra_cli/commands.py +3 -2
  114. pyinfra_cli/exceptions.py +75 -43
  115. pyinfra_cli/inventory.py +52 -31
  116. pyinfra_cli/log.py +10 -2
  117. pyinfra_cli/main.py +88 -65
  118. pyinfra_cli/prints.py +37 -109
  119. pyinfra_cli/util.py +15 -10
  120. tests/test_api/test_api.py +2 -0
  121. tests/test_api/test_api_arguments.py +9 -9
  122. tests/test_api/test_api_deploys.py +15 -19
  123. tests/test_api/test_api_facts.py +4 -5
  124. tests/test_api/test_api_operations.py +18 -20
  125. tests/test_api/test_api_util.py +41 -2
  126. tests/test_cli/test_cli.py +14 -50
  127. tests/test_cli/test_cli_deploy.py +17 -14
  128. tests/test_cli/test_cli_exceptions.py +50 -19
  129. tests/test_cli/test_cli_inventory.py +66 -0
  130. tests/test_cli/util.py +1 -1
  131. tests/test_connectors/test_dockerssh.py +11 -8
  132. tests/test_connectors/test_ssh.py +88 -23
  133. tests/test_connectors/test_sshuserclient.py +1 -1
  134. tests/test_connectors/test_terraform.py +11 -8
  135. tests/test_connectors/test_vagrant.py +6 -6
  136. pyinfra/connectors/ansible.py +0 -175
  137. pyinfra/connectors/mech.py +0 -189
  138. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  139. pyinfra/connectors/winrm.py +0 -312
  140. pyinfra/facts/windows.py +0 -366
  141. pyinfra/facts/windows_files.py +0 -90
  142. pyinfra/operations/windows.py +0 -59
  143. pyinfra/operations/windows_files.py +0 -538
  144. pyinfra-3.0.dev0.dist-info/RECORD +0 -170
  145. tests/test_connectors/test_ansible.py +0 -64
  146. tests/test_connectors/test_mech.py +0 -126
  147. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/LICENSE.md +0 -0
  148. {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.2.dist-info}/top_level.txt +0 -0
pyinfra/facts/systemd.py CHANGED
@@ -1,6 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
4
+ from typing import Dict, Iterable
2
5
 
3
- from pyinfra.api import FactBase
6
+ from pyinfra.api import FactBase, QuoteString, StringCommand
4
7
 
5
8
  # Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
6
9
  # The "unit prefix" must consist of one or more valid characters
@@ -21,21 +24,19 @@ SYSTEMD_UNIT_NAME_REGEX = (
21
24
 
22
25
  def _make_systemctl_cmd(user_mode=False, machine=None, user_name=None):
23
26
  # base command for normal and user mode
24
- systemctl_cmd = "systemctl --user" if user_mode else "systemctl"
27
+ systemctl_cmd = ["systemctl --user"] if user_mode else ["systemctl"]
25
28
 
26
29
  # add user and machine flag if given in args
27
30
  if machine is not None:
28
31
  if user_name is not None:
29
- machine_opt = "--machine={1}@{0}".format(machine, user_name)
32
+ systemctl_cmd.append("--machine={1}@{0}".format(machine, user_name))
30
33
  else:
31
- machine_opt = "--machine={0}".format(machine)
32
-
33
- systemctl_cmd = "{0} {1}".format(systemctl_cmd, machine_opt)
34
+ systemctl_cmd.append("--machine={0}".format(machine))
34
35
 
35
- return systemctl_cmd
36
+ return StringCommand(*systemctl_cmd)
36
37
 
37
38
 
38
- class SystemdStatus(FactBase):
39
+ class SystemdStatus(FactBase[Dict[str, bool]]):
39
40
  """
40
41
  Returns a dictionary map of systemd units to booleans indicating whether they are active.
41
42
 
@@ -51,24 +52,47 @@ class SystemdStatus(FactBase):
51
52
  }
52
53
  """
53
54
 
54
- requires_command = "systemctl"
55
+ def requires_command(self, *args, **kwargs) -> str:
56
+ return "systemctl"
55
57
 
56
58
  default = dict
57
59
 
58
60
  state_key = "SubState"
59
61
  state_values = ["running", "waiting", "exited"]
60
62
 
61
- def command(self, user_mode=False, machine=None, user_name=None):
63
+ def command(
64
+ self,
65
+ user_mode: bool = False,
66
+ machine: str | None = None,
67
+ user_name: str | None = None,
68
+ services: str | list[str] | None = None,
69
+ ) -> StringCommand:
62
70
  fact_cmd = _make_systemctl_cmd(
63
71
  user_mode=user_mode,
64
72
  machine=machine,
65
73
  user_name=user_name,
66
74
  )
67
75
 
68
- return f"{fact_cmd} show --all --property Id --property {self.state_key} '*'"
76
+ if services is None:
77
+ service_strs = [QuoteString("*")]
78
+ elif isinstance(services, str):
79
+ service_strs = [QuoteString(services)]
80
+ elif isinstance(services, Iterable):
81
+ service_strs = [QuoteString(s) for s in services]
82
+
83
+ return StringCommand(
84
+ fact_cmd,
85
+ "show",
86
+ "--all",
87
+ "--property",
88
+ "Id",
89
+ "--property",
90
+ self.state_key,
91
+ *service_strs,
92
+ )
69
93
 
70
- def process(self, output):
71
- services = {}
94
+ def process(self, output) -> Dict[str, bool]:
95
+ services: Dict[str, bool] = {}
72
96
 
73
97
  current_unit = None
74
98
  for line in output:
pyinfra/facts/sysvinit.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
  from typing import Optional
3
5
 
@@ -7,7 +9,7 @@ from pyinfra.api import FactBase
7
9
  class InitdStatus(FactBase):
8
10
  """
9
11
  Low level check for every /etc/init.d/* script. Unfortunately many of these
10
- 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
11
13
  offering status support.
12
14
 
13
15
  Returns a dict of name -> status.
@@ -16,7 +18,8 @@ class InitdStatus(FactBase):
16
18
  http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
17
19
  """
18
20
 
19
- command = """
21
+ def command(self) -> str:
22
+ return """
20
23
  for SERVICE in `ls /etc/init.d/`; do
21
24
  _=`cat /etc/init.d/$SERVICE | grep "### BEGIN INIT INFO"`
22
25
 
@@ -30,8 +33,8 @@ class InitdStatus(FactBase):
30
33
  regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
31
34
  default = dict
32
35
 
33
- def process(self, output):
34
- services = {}
36
+ def process(self, output) -> dict[str, Optional[bool]]:
37
+ services: dict[str, Optional[bool]] = {}
35
38
 
36
39
  for line in output:
37
40
  matches = re.match(self.regex, line)
pyinfra/facts/upstart.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import re
2
4
 
3
5
  from pyinfra.api import FactBase
@@ -8,12 +10,15 @@ class UpstartStatus(FactBase):
8
10
  Returns a dict of name -> status for upstart managed services.
9
11
  """
10
12
 
11
- command = "initctl list"
12
- requires_command = "initctl"
13
+ def requires_command(self) -> str:
14
+ return "initctl"
13
15
 
14
16
  regex = r"^([a-z\-]+) [a-z]+\/([a-z]+)"
15
17
  default = dict
16
18
 
19
+ def command(self):
20
+ return "initctl list"
21
+
17
22
  def process(self, output):
18
23
  services = {}
19
24
 
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import re
4
+ from typing import Iterable
4
5
 
5
6
 
6
- def parse_packages(regex, output):
7
+ def parse_packages(regex: str, output: Iterable[str]) -> dict[str, set[str]]:
7
8
  packages: dict[str, set[str]] = {}
8
9
 
9
10
  for line in output:
@@ -34,7 +35,7 @@ def _parse_yum_or_zypper_repositories(output):
34
35
  current_repo["name"] = line[1:-1]
35
36
 
36
37
  if current_repo and "=" in line:
37
- key, value = line.split("=", 1)
38
+ key, value = re.split(r"\s*=\s*", line, maxsplit=1)
38
39
  current_repo[key] = value
39
40
 
40
41
  if current_repo:
pyinfra/facts/vzctl.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
3
5
  from pyinfra.api import FactBase
@@ -18,13 +20,15 @@ class OpenvzContainers(FactBase):
18
20
  }
19
21
  """
20
22
 
21
- command = "vzlist -a -j"
22
- requires_command = "vzlist"
23
+ def command(self) -> str:
24
+ return "vzlist -a -j"
25
+
26
+ def requires_command(self) -> str:
27
+ return "vzlist"
23
28
 
24
29
  default = dict
25
30
 
26
- @staticmethod
27
- def process(output):
31
+ def process(self, output):
28
32
  combined_json = "".join(output)
29
33
  vz_data = json.loads(combined_json)
30
34
 
pyinfra/facts/xbps.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util.packaging import parse_packages
@@ -14,12 +16,15 @@ class XbpsPackages(FactBase):
14
16
  }
15
17
  """
16
18
 
17
- command = "xbps-query -l"
18
- requires_command = "xbps-query"
19
+ def requires_command(self) -> str:
20
+ return "xbps-query"
19
21
 
20
22
  default = dict
21
23
 
22
24
  regex = r"^.. ([a-zA-Z0-9_\-\+]+)\-([0-9a-z_\.]+)"
23
25
 
26
+ def command(self):
27
+ return "xbps-query -l"
28
+
24
29
  def process(self, output):
25
30
  return parse_packages(self.regex, output)
pyinfra/facts/yum.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util import make_cat_files_command
@@ -21,11 +23,14 @@ class YumRepositories(FactBase):
21
23
  ]
22
24
  """
23
25
 
24
- command = make_cat_files_command(
25
- "/etc/yum.conf",
26
- "/etc/yum.repos.d/*.repo",
27
- )
28
- requires_command = "yum"
26
+ def command(self) -> str:
27
+ return make_cat_files_command(
28
+ "/etc/yum.conf",
29
+ "/etc/yum.repos.d/*.repo",
30
+ )
31
+
32
+ def requires_command(self) -> str:
33
+ return "yum"
29
34
 
30
35
  default = list
31
36
 
pyinfra/facts/zypper.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
  from .util import make_cat_files_command
@@ -21,10 +23,13 @@ class ZypperRepositories(FactBase):
21
23
  ]
22
24
  """
23
25
 
24
- command = make_cat_files_command(
25
- "/etc/zypp/repos.d/*.repo",
26
- )
27
- requires_command = "zypper"
26
+ def command(self) -> str:
27
+ return make_cat_files_command(
28
+ "/etc/zypp/repos.d/*.repo",
29
+ )
30
+
31
+ def requires_command(self) -> str:
32
+ return "zypper"
28
33
 
29
34
  default = list
30
35
 
pyinfra/operations/apk.py CHANGED
@@ -2,6 +2,8 @@
2
2
  Manage apk packages.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.apk import ApkPackages
@@ -23,7 +25,7 @@ def upgrade(available: bool = False):
23
25
  yield "apk upgrade"
24
26
 
25
27
 
26
- _upgrade = upgrade # noqa: E305
28
+ _upgrade = upgrade._inner # noqa: E305
27
29
 
28
30
 
29
31
  @operation(is_idempotent=False)
@@ -35,12 +37,12 @@ def update():
35
37
  yield "apk update"
36
38
 
37
39
 
38
- _update = update # noqa: E305
40
+ _update = update._inner # noqa: E305
39
41
 
40
42
 
41
43
  @operation()
42
44
  def packages(
43
- packages=None,
45
+ packages: str | list[str] | None = None,
44
46
  present=True,
45
47
  latest=False,
46
48
  update=False,
pyinfra/operations/apt.py CHANGED
@@ -2,10 +2,12 @@
2
2
  Manage apt packages and repositories.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from datetime import timedelta
6
8
  from urllib.parse import urlparse
7
9
 
8
- from pyinfra import host, state
10
+ from pyinfra import host
9
11
  from pyinfra.api import OperationError, operation
10
12
  from pyinfra.facts.apt import AptKeys, AptSources, parse_apt_repo
11
13
  from pyinfra.facts.deb import DebPackage, DebPackages
@@ -19,7 +21,7 @@ from .util.packaging import ensure_packages
19
21
  APT_UPDATE_FILENAME = "/var/lib/apt/periodic/update-success-stamp"
20
22
 
21
23
 
22
- def noninteractive_apt(command, force=False):
24
+ def noninteractive_apt(command: str, force=False):
23
25
  args = ["DEBIAN_FRONTEND=noninteractive apt-get -y"]
24
26
 
25
27
  if force:
@@ -37,7 +39,7 @@ def noninteractive_apt(command, force=False):
37
39
 
38
40
 
39
41
  @operation()
40
- def key(src=None, keyserver=None, keyid=None):
42
+ def key(src: str | None = None, keyserver: str | None = None, keyid: str | list[str] | None = None):
41
43
  """
42
44
  Add apt gpg keys with ``apt-key``.
43
45
 
@@ -48,6 +50,11 @@ def key(src=None, keyserver=None, keyid=None):
48
50
  keyserver/id:
49
51
  These must be provided together.
50
52
 
53
+ .. warning::
54
+ ``apt-key`` is deprecated in Debian, it is recommended NOT to use this
55
+ operation and instead follow the instructions here:
56
+ https://wiki.debian.org/DebianRepository/UseThirdParty
57
+
51
58
  **Examples:**
52
59
 
53
60
  .. code:: python
@@ -103,7 +110,7 @@ def key(src=None, keyserver=None, keyid=None):
103
110
 
104
111
 
105
112
  @operation()
106
- def repo(src, present=True, filename=None):
113
+ def repo(src: str, present=True, filename: str | None = None):
107
114
  """
108
115
  Add/remove apt repositories.
109
116
 
@@ -138,7 +145,7 @@ def repo(src, present=True, filename=None):
138
145
 
139
146
  # Doesn't exist and we want it
140
147
  if not is_present and present:
141
- yield from files.line(
148
+ yield from files.line._inner(
142
149
  path=filename,
143
150
  line=src,
144
151
  escape_regex_characters=True,
@@ -146,7 +153,7 @@ def repo(src, present=True, filename=None):
146
153
 
147
154
  # Exists and we don't want it
148
155
  elif is_present and not present:
149
- yield from files.line(
156
+ yield from files.line._inner(
150
157
  path=filename,
151
158
  line=src,
152
159
  present=False,
@@ -162,7 +169,7 @@ def repo(src, present=True, filename=None):
162
169
 
163
170
 
164
171
  @operation(is_idempotent=False)
165
- def ppa(src, present=True):
172
+ def ppa(src: str, present=True):
166
173
  """
167
174
  Add/remove Ubuntu ppa repositories.
168
175
 
@@ -192,7 +199,7 @@ def ppa(src, present=True):
192
199
 
193
200
 
194
201
  @operation()
195
- def deb(src, present=True, force=False):
202
+ def deb(src: str, present=True, force=False):
196
203
  """
197
204
  Add/remove ``.deb`` file packages.
198
205
 
@@ -224,27 +231,23 @@ def deb(src, present=True, force=False):
224
231
  # If source is a url
225
232
  if urlparse(src).scheme:
226
233
  # Generate a temp filename
227
- temp_filename = state.get_temp_filename(src)
234
+ temp_filename = host.get_temp_filename(src)
228
235
 
229
236
  # Ensure it's downloaded
230
- yield from files.download(src=src, dest=temp_filename)
237
+ yield from files.download._inner(src=src, dest=temp_filename)
231
238
 
232
239
  # Override the source with the downloaded file
233
240
  src = temp_filename
234
241
 
235
242
  # Check for file .deb information (if file is present)
236
- info = host.get_fact(DebPackage, name=src)
243
+ info = host.get_fact(DebPackage, package=src)
237
244
  current_packages = host.get_fact(DebPackages)
238
245
 
239
246
  exists = False
240
247
 
241
248
  # We have deb info! Check against installed packages
242
- if info:
243
- if (
244
- info["name"] in current_packages
245
- and info.get("version") in current_packages[info["name"]]
246
- ):
247
- exists = True
249
+ if info and info.get("version") in current_packages.get(info.get("name"), {}):
250
+ exists = True
248
251
 
249
252
  # Package does not exist and we want?
250
253
  if present:
@@ -277,7 +280,7 @@ def deb(src, present=True, force=False):
277
280
  "unless the ``cache_time`` argument is provided."
278
281
  ),
279
282
  )
280
- def update(cache_time=None):
283
+ def update(cache_time: int | None = None):
281
284
  """
282
285
  Updates apt repositories.
283
286
 
@@ -335,7 +338,7 @@ def upgrade(auto_remove: bool = False):
335
338
 
336
339
  # Upgrade all packages and remove unneeded transitive dependencies
337
340
  apt.upgrade(
338
- name="Upgrade apt packages and remove unneeded dependencies"
341
+ name="Upgrade apt packages and remove unneeded dependencies",
339
342
  auto_remove=True
340
343
  )
341
344
  """
@@ -370,17 +373,17 @@ def dist_upgrade():
370
373
 
371
374
  @operation()
372
375
  def packages(
373
- packages=None,
376
+ packages: str | list[str] | None = None,
374
377
  present=True,
375
378
  latest=False,
376
379
  update=False,
377
- cache_time=None,
380
+ cache_time: int | None = None,
378
381
  upgrade=False,
379
382
  force=False,
380
383
  no_recommends=False,
381
384
  allow_downgrades=False,
382
- extra_install_args=None,
383
- extra_uninstall_args=None,
385
+ extra_install_args: str | None = None,
386
+ extra_uninstall_args: str | None = None,
384
387
  ):
385
388
  """
386
389
  Install/remove/update packages & update apt.
@@ -432,10 +435,10 @@ def packages(
432
435
  """
433
436
 
434
437
  if update:
435
- yield from _update(cache_time=cache_time)
438
+ yield from _update._inner(cache_time=cache_time)
436
439
 
437
440
  if upgrade:
438
- yield from _upgrade()
441
+ yield from _upgrade._inner()
439
442
 
440
443
  install_command_args = ["install"]
441
444
  if no_recommends is True:
@@ -2,6 +2,10 @@
2
2
  Manage brew packages on mac/OSX. See https://brew.sh/
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
7
+ import urllib.parse
8
+
5
9
  from pyinfra import host
6
10
  from pyinfra.api import operation
7
11
  from pyinfra.facts.brew import BrewCasks, BrewPackages, BrewTaps, BrewVersion, new_cask_cli
@@ -35,7 +39,7 @@ _upgrade = upgrade # noqa: E305
35
39
 
36
40
  @operation()
37
41
  def packages(
38
- packages=None,
42
+ packages: str | list[str] | None = None,
39
43
  present=True,
40
44
  latest=False,
41
45
  update=False,
@@ -73,10 +77,10 @@ def packages(
73
77
  """
74
78
 
75
79
  if update:
76
- yield from _update()
80
+ yield from _update._inner()
77
81
 
78
82
  if upgrade:
79
- yield from _upgrade()
83
+ yield from _upgrade._inner()
80
84
 
81
85
  yield from ensure_packages(
82
86
  host,
@@ -91,27 +95,22 @@ def packages(
91
95
  )
92
96
 
93
97
 
94
- def cask_args(host):
98
+ def cask_args():
95
99
  return ("", " --cask") if new_cask_cli(host.get_fact(BrewVersion)) else ("cask ", "")
96
100
 
97
101
 
98
- @operation(
99
- is_idempotent=False,
100
- pipeline_facts={"brew_version": ""},
101
- )
102
+ @operation(is_idempotent=False)
102
103
  def cask_upgrade():
103
104
  """
104
105
  Upgrades all brew casks.
105
106
  """
106
107
 
107
- yield "brew %supgrade%s" % cask_args(host)
108
+ yield "brew %supgrade%s" % cask_args()
108
109
 
109
110
 
110
- @operation(
111
- pipeline_facts={"brew_version": ""},
112
- )
111
+ @operation()
113
112
  def casks(
114
- casks=None,
113
+ casks: str | list[str] | None = None,
115
114
  present=True,
116
115
  latest=False,
117
116
  upgrade=False,
@@ -141,9 +140,9 @@ def casks(
141
140
  """
142
141
 
143
142
  if upgrade:
144
- yield from cask_upgrade()
143
+ yield from cask_upgrade._inner()
145
144
 
146
- args = cask_args(host)
145
+ args = cask_args()
147
146
 
148
147
  yield from ensure_packages(
149
148
  host,
@@ -159,12 +158,13 @@ def casks(
159
158
 
160
159
 
161
160
  @operation()
162
- def tap(src, present=True):
161
+ def tap(src: str | None = None, present=True, url: str | None = None):
163
162
  """
164
163
  Add/remove brew taps.
165
164
 
166
165
  + src: the name of the tap
167
166
  + present: whether this tap should be present or not
167
+ + url: the url of the tap. See https://docs.brew.sh/Taps
168
168
 
169
169
  **Examples:**
170
170
 
@@ -175,6 +175,19 @@ def tap(src, present=True):
175
175
  src="includeos/includeos",
176
176
  )
177
177
 
178
+ # Just url is equivalent to
179
+ # `brew tap kptdev/kpt https://github.com/kptdev/kpt`
180
+ brew.tap(
181
+ url="https://github.com/kptdev/kpt",
182
+ )
183
+
184
+ # src and url is equivalent to
185
+ # `brew tap example/project https://github.example.com/project`
186
+ brew.tap(
187
+ src="example/project",
188
+ url="https://github.example.com/project",
189
+ )
190
+
178
191
  # Multiple taps
179
192
  for tap in ["includeos/includeos", "ktr0731/evans"]:
180
193
  brew.tap(
@@ -184,17 +197,35 @@ def tap(src, present=True):
184
197
 
185
198
  """
186
199
 
200
+ if not (src or url):
201
+ host.noop("no tap was specified")
202
+ return
203
+
204
+ src = src or str(urllib.parse.urlparse(url).path).strip("/")
205
+
206
+ if len(src.split("/")) != 2:
207
+ host.noop("src '{0}' doesn't have two components.".format(src))
208
+ return
209
+
187
210
  taps = host.get_fact(BrewTaps)
188
- is_tapped = src in taps
189
-
190
- if present:
191
- if is_tapped:
192
- host.noop("tap {0} already exists".format(src))
193
- else:
194
- yield "brew tap {0}".format(src)
195
-
196
- elif not present:
197
- if is_tapped:
198
- yield "brew untap {0}".format(src)
199
- else:
200
- host.noop("tap {0} does not exist".format(src))
211
+ already_tapped = src in taps
212
+
213
+ if present and already_tapped:
214
+ host.noop("tap {0} already exists".format(src))
215
+ return
216
+
217
+ if already_tapped:
218
+ yield "brew untap {0}".format(src)
219
+ return
220
+
221
+ if not present:
222
+ host.noop("tap {0} does not exist".format(src))
223
+ return
224
+
225
+ cmd = "brew tap {0}".format(src)
226
+
227
+ if url is not None:
228
+ cmd = " ".join([cmd, url])
229
+
230
+ yield cmd
231
+ return
@@ -2,6 +2,8 @@
2
2
  Manage BSD init services (``/etc/rc.d``, ``/usr/local/etc/rc.d``).
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.bsdinit import RcdStatus
@@ -13,12 +15,12 @@ from .util.service import handle_service_control
13
15
 
14
16
  @operation()
15
17
  def service(
16
- service,
18
+ service: str,
17
19
  running=True,
18
20
  restarted=False,
19
21
  reloaded=False,
20
- command=None,
21
- enabled=None,
22
+ command: str | None = None,
23
+ enabled: bool | None = None,
22
24
  ):
23
25
  """
24
26
  Manage the state of BSD init services.
@@ -49,7 +51,7 @@ def service(
49
51
 
50
52
  # BSD init is simple, just add/remove <service>_enabled="YES"
51
53
  if isinstance(enabled, bool):
52
- yield from files.line(
54
+ yield from files.line._inner(
53
55
  path="/etc/rc.conf.local",
54
56
  line="^{0}_enable=".format(service),
55
57
  replace='{0}_enable="YES"'.format(service),
@@ -2,6 +2,8 @@
2
2
  Manage cargo (aka Rust) packages.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.cargo import CargoPackages
@@ -10,7 +12,7 @@ from .util.packaging import ensure_packages
10
12
 
11
13
 
12
14
  @operation()
13
- def packages(packages=None, present=True, latest=False):
15
+ def packages(packages: str | list[str] | None = None, present=True, latest=False):
14
16
  """
15
17
  Install/remove/update cargo packages.
16
18