pyinfra 3.1.1__py2.py3-none-any.whl → 3.3__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 (104) hide show
  1. pyinfra/api/arguments.py +9 -2
  2. pyinfra/api/arguments_typed.py +4 -5
  3. pyinfra/api/command.py +22 -3
  4. pyinfra/api/config.py +5 -2
  5. pyinfra/api/deploy.py +4 -2
  6. pyinfra/api/facts.py +3 -0
  7. pyinfra/api/host.py +15 -7
  8. pyinfra/api/operation.py +2 -1
  9. pyinfra/api/state.py +1 -1
  10. pyinfra/connectors/base.py +34 -8
  11. pyinfra/connectors/chroot.py +7 -2
  12. pyinfra/connectors/docker.py +24 -8
  13. pyinfra/connectors/dockerssh.py +7 -2
  14. pyinfra/connectors/local.py +7 -2
  15. pyinfra/connectors/ssh.py +9 -2
  16. pyinfra/connectors/sshuserclient/client.py +42 -14
  17. pyinfra/connectors/sshuserclient/config.py +2 -0
  18. pyinfra/connectors/terraform.py +1 -1
  19. pyinfra/connectors/util.py +13 -9
  20. pyinfra/context.py +9 -2
  21. pyinfra/facts/apk.py +8 -1
  22. pyinfra/facts/apt.py +68 -0
  23. pyinfra/facts/brew.py +13 -0
  24. pyinfra/facts/bsdinit.py +3 -0
  25. pyinfra/facts/cargo.py +5 -0
  26. pyinfra/facts/choco.py +6 -0
  27. pyinfra/facts/crontab.py +195 -0
  28. pyinfra/facts/deb.py +10 -0
  29. pyinfra/facts/dnf.py +5 -0
  30. pyinfra/facts/docker.py +16 -0
  31. pyinfra/facts/efibootmgr.py +113 -0
  32. pyinfra/facts/files.py +112 -7
  33. pyinfra/facts/flatpak.py +7 -0
  34. pyinfra/facts/freebsd.py +75 -0
  35. pyinfra/facts/gem.py +5 -0
  36. pyinfra/facts/git.py +12 -2
  37. pyinfra/facts/gpg.py +7 -0
  38. pyinfra/facts/hardware.py +13 -0
  39. pyinfra/facts/iptables.py +9 -1
  40. pyinfra/facts/launchd.py +5 -0
  41. pyinfra/facts/lxd.py +5 -0
  42. pyinfra/facts/mysql.py +9 -2
  43. pyinfra/facts/npm.py +5 -0
  44. pyinfra/facts/openrc.py +8 -0
  45. pyinfra/facts/opkg.py +245 -0
  46. pyinfra/facts/pacman.py +9 -1
  47. pyinfra/facts/pip.py +5 -0
  48. pyinfra/facts/pipx.py +82 -0
  49. pyinfra/facts/pkg.py +4 -0
  50. pyinfra/facts/pkgin.py +5 -0
  51. pyinfra/facts/podman.py +54 -0
  52. pyinfra/facts/postgres.py +10 -2
  53. pyinfra/facts/rpm.py +11 -0
  54. pyinfra/facts/runit.py +7 -0
  55. pyinfra/facts/selinux.py +16 -0
  56. pyinfra/facts/server.py +87 -79
  57. pyinfra/facts/snap.py +7 -0
  58. pyinfra/facts/systemd.py +5 -0
  59. pyinfra/facts/sysvinit.py +4 -0
  60. pyinfra/facts/upstart.py +5 -0
  61. pyinfra/facts/util/__init__.py +4 -1
  62. pyinfra/facts/util/units.py +30 -0
  63. pyinfra/facts/vzctl.py +5 -0
  64. pyinfra/facts/xbps.py +6 -1
  65. pyinfra/facts/yum.py +5 -0
  66. pyinfra/facts/zfs.py +41 -21
  67. pyinfra/facts/zypper.py +5 -0
  68. pyinfra/local.py +3 -2
  69. pyinfra/operations/apt.py +36 -22
  70. pyinfra/operations/crontab.py +189 -0
  71. pyinfra/operations/docker.py +61 -56
  72. pyinfra/operations/files.py +65 -1
  73. pyinfra/operations/freebsd/__init__.py +12 -0
  74. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  75. pyinfra/operations/freebsd/pkg.py +219 -0
  76. pyinfra/operations/freebsd/service.py +116 -0
  77. pyinfra/operations/freebsd/sysrc.py +92 -0
  78. pyinfra/operations/git.py +23 -7
  79. pyinfra/operations/opkg.py +88 -0
  80. pyinfra/operations/pip.py +3 -2
  81. pyinfra/operations/pipx.py +90 -0
  82. pyinfra/operations/postgres.py +114 -27
  83. pyinfra/operations/runit.py +2 -0
  84. pyinfra/operations/server.py +9 -181
  85. pyinfra/operations/util/docker.py +44 -22
  86. pyinfra/operations/zfs.py +3 -3
  87. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/LICENSE.md +1 -1
  88. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/METADATA +25 -25
  89. pyinfra-3.3.dist-info/RECORD +187 -0
  90. pyinfra_cli/exceptions.py +5 -0
  91. pyinfra_cli/inventory.py +26 -9
  92. pyinfra_cli/log.py +3 -0
  93. pyinfra_cli/main.py +9 -8
  94. pyinfra_cli/prints.py +19 -4
  95. pyinfra_cli/util.py +3 -0
  96. pyinfra_cli/virtualenv.py +1 -1
  97. tests/test_cli/test_cli_deploy.py +15 -13
  98. tests/test_cli/test_cli_inventory.py +53 -0
  99. tests/test_connectors/test_ssh.py +302 -182
  100. tests/test_connectors/test_sshuserclient.py +68 -1
  101. pyinfra-3.1.1.dist-info/RECORD +0 -172
  102. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/WHEEL +0 -0
  103. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/entry_points.txt +0 -0
  104. {pyinfra-3.1.1.dist-info → pyinfra-3.3.dist-info}/top_level.txt +0 -0
pyinfra/facts/opkg.py ADDED
@@ -0,0 +1,245 @@
1
+ """
2
+ Gather the information provided by ``opkg`` on OpenWrt systems:
3
+ + ``opkg`` configuration
4
+ + feeds configuration
5
+ + list of installed packages
6
+ + list of packages with available upgrades
7
+
8
+
9
+ see https://openwrt.org/docs/guide-user/additional-software/opkg
10
+ """
11
+
12
+ import re
13
+ from typing import Dict, NamedTuple, Union
14
+
15
+ from typing_extensions import override
16
+
17
+ from pyinfra import logger
18
+ from pyinfra.api import FactBase
19
+ from pyinfra.facts.util.packaging import parse_packages
20
+
21
+ # TODO - change NamedTuple to dataclass Opkgbut need to figure out how to get json serialization
22
+ # to work without changing core code
23
+
24
+
25
+ class OpkgPkgUpgradeInfo(NamedTuple):
26
+ installed: str
27
+ available: str
28
+
29
+
30
+ class OpkgConfInfo(NamedTuple):
31
+ paths: Dict[str, str] # list of paths, e.g. {'root':'/', 'ram':'/tmp}
32
+ list_dir: str # where package lists are stored, e.g. /var/opkg-lists
33
+ options: Dict[
34
+ str, Union[str, bool]
35
+ ] # mapping from option to value, e.g. {'check_signature': True}
36
+ arch_cfg: Dict[str, int] # priorities for architectures
37
+
38
+
39
+ class OpkgFeedInfo(NamedTuple):
40
+ url: str # url for the feed
41
+ fmt: str # format of the feed, e.g. "src/gz"
42
+ kind: str # whether it comes from the 'distribution' or is 'custom'
43
+
44
+
45
+ class OpkgConf(FactBase):
46
+ """
47
+ Returns a NamedTuple with the current configuration:
48
+
49
+ .. code:: python
50
+
51
+ ConfInfo(
52
+ paths = {
53
+ "root": "/",
54
+ "ram": "/tmp",
55
+ },
56
+ list_dir = "/opt/opkg-lists",
57
+ options = {
58
+ "overlay_root": "/overlay"
59
+ },
60
+ arch_cfg = {
61
+ "all": 1,
62
+ "noarch": 1,
63
+ "i386_pentium": 10
64
+ }
65
+ )
66
+
67
+ """
68
+
69
+ regex = re.compile(
70
+ r"""
71
+ ^(?:\s*)
72
+ (?:
73
+ (?:arch\s+(?P<arch>\w+)\s+(?P<priority>\d+))|
74
+ (?:dest\s+(?P<dest>\w+)\s+(?P<dest_path>[\w/\-]+))|
75
+ (?:lists_dir\s+(?P<lists_dir>ext)\s+(?P<list_path>[\w/\-]+))|
76
+ (?:option\s+(?P<option>\w+)(?:\s+(?P<value>[^#]+))?)
77
+ )?
78
+ (?:\s*\#.*)?
79
+ $
80
+ """,
81
+ re.X,
82
+ )
83
+
84
+ @staticmethod
85
+ def default():
86
+ return OpkgConfInfo({}, "", {}, {})
87
+
88
+ @override
89
+ def command(self) -> str:
90
+ return "cat /etc/opkg.conf"
91
+
92
+ @override
93
+ def process(self, output):
94
+ dest, lists_dir, options, arch_cfg = {}, "", {}, {}
95
+ for line in output:
96
+ match = self.regex.match(line)
97
+
98
+ if match is None:
99
+ logger.warning(f"Opkg: could not parse opkg.conf line '{line}'")
100
+ elif match.group("arch") is not None:
101
+ arch_cfg[match.group("arch")] = int(match.group("priority"))
102
+ elif match.group("dest") is not None:
103
+ dest[match.group("dest")] = match.group("dest_path")
104
+ elif match.group("lists_dir") is not None:
105
+ lists_dir = match.group("list_path")
106
+ elif match.group("option") is not None:
107
+ options[match.group("option")] = match.group("value") or True
108
+
109
+ return OpkgConfInfo(dest, lists_dir, options, arch_cfg)
110
+
111
+
112
+ class OpkgFeeds(FactBase):
113
+ """
114
+ Returns a dictionary containing the information for the distribution-provided and
115
+ custom opkg feeds:
116
+
117
+ .. code:: python
118
+
119
+ {
120
+ 'openwrt_base': FeedInfo(url='http://downloads ... /i386_pentium/base', fmt='src/gz', kind='distribution'), # noqa: E501
121
+ 'openwrt_core': FeedInfo(url='http://downloads ... /x86/geode/packages', fmt='src/gz', kind='distribution'), # noqa: E501
122
+ 'openwrt_luci': FeedInfo(url='http://downloads ... /i386_pentium/luci', fmt='src/gz', kind='distribution'),# noqa: E501
123
+ 'openwrt_packages': FeedInfo(url='http://downloads ... /i386_pentium/packages', fmt='src/gz', kind='distribution'),# noqa: E501
124
+ 'openwrt_routing': FeedInfo(url='http://downloads ... /i386_pentium/routing', fmt='src/gz', kind='distribution'),# noqa: E501
125
+ 'openwrt_telephony': FeedInfo(url='http://downloads ... /i386_pentium/telephony', fmt='src/gz', kind='distribution') # noqa: E501
126
+ }
127
+ """
128
+
129
+ regex = re.compile(
130
+ r"^(CUSTOM)|(?:\s*(?P<fmt>[\w/]+)\s+(?P<name>[\w]+)\s+(?P<url>[\w./:]+))?(?:\s*#.*)?$"
131
+ )
132
+ default = dict
133
+
134
+ @override
135
+ def command(self) -> str:
136
+ return "cat /etc/opkg/distfeeds.conf; echo CUSTOM; cat /etc/opkg/customfeeds.conf"
137
+
138
+ @override
139
+ def process(self, output):
140
+ feeds, kind = {}, "distribution"
141
+ for line in output:
142
+ match = self.regex.match(line)
143
+
144
+ if match is None:
145
+ logger.warning(f"Opkg: could not parse /etc/opkg/*feeds.conf line '{line}'")
146
+ elif match.group(0) == "CUSTOM":
147
+ kind = "custom"
148
+ elif match.group("name") is not None:
149
+ feeds[match.group("name")] = OpkgFeedInfo(
150
+ match.group("url"), match.group("fmt"), kind
151
+ )
152
+
153
+ return feeds
154
+
155
+
156
+ class OpkgInstallableArchitectures(FactBase):
157
+ """
158
+ Returns a dictionary containing the currently installable architectures for this system along
159
+ with their priority:
160
+
161
+ .. code:: python
162
+
163
+ {
164
+ 'all': 1,
165
+ 'i386_pentium': 10,
166
+ 'noarch': 1
167
+ }
168
+ """
169
+
170
+ regex = re.compile(r"^(?:\s*arch\s+(?P<arch>[\w]+)\s+(?P<prio>\d+))?(\s*#.*)?$")
171
+ default = dict
172
+
173
+ @override
174
+ def command(self) -> str:
175
+ return "/bin/opkg print-architecture"
176
+
177
+ @override
178
+ def process(self, output):
179
+ arch_list = {}
180
+ for line in output:
181
+ match = self.regex.match(line)
182
+
183
+ if match is None:
184
+ logger.warning(f"could not parse arch line '{line}'")
185
+ elif match.group("arch") is not None:
186
+ arch_list[match.group("arch")] = int(match.group("prio"))
187
+
188
+ return arch_list
189
+
190
+
191
+ class OpkgPackages(FactBase):
192
+ """
193
+ Returns a dict of installed opkg packages:
194
+
195
+ .. code:: python
196
+
197
+ {
198
+ 'package_name': ['version'],
199
+ ...
200
+ }
201
+ """
202
+
203
+ regex = r"^([a-zA-Z0-9][\w\-\.]*)\s-\s([\w\-\.]+)"
204
+ default = dict
205
+
206
+ @override
207
+ def command(self) -> str:
208
+ return "/bin/opkg list-installed"
209
+
210
+ @override
211
+ def process(self, output):
212
+ return parse_packages(self.regex, sorted(output))
213
+
214
+
215
+ class OpkgUpgradeablePackages(FactBase):
216
+ """
217
+ Returns a dict of installed and upgradable opkg packages:
218
+
219
+ .. code:: python
220
+
221
+ {
222
+ 'package_name': (installed='1.2.3', available='1.2.8')
223
+ ...
224
+ }
225
+ """
226
+
227
+ regex = re.compile(r"^([a-zA-Z0-9][\w\-.]*)\s-\s([\w\-.]+)\s-\s([\w\-.]+)")
228
+ default = dict
229
+ use_default_on_error = True
230
+
231
+ @override
232
+ def command(self) -> str:
233
+ return "/bin/opkg list-upgradable" # yes, really spelled that way
234
+
235
+ @override
236
+ def process(self, output):
237
+ result = {}
238
+ for line in output:
239
+ match = self.regex.match(line)
240
+ if match and len(match.groups()) == 3:
241
+ result[match.group(1)] = OpkgPkgUpgradeInfo(match.group(2), match.group(3))
242
+ else:
243
+ logger.warning(f"Opkg: could not list-upgradable line '{line}'")
244
+
245
+ return result
pyinfra/facts/pacman.py CHANGED
@@ -2,11 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import shlex
4
4
 
5
+ from typing_extensions import override
6
+
5
7
  from pyinfra.api import FactBase
6
8
 
7
9
  from .util.packaging import parse_packages
8
10
 
9
- PACMAN_REGEX = r"^([0-9a-zA-Z\-]+)\s([0-9\._+a-z\-]+)"
11
+ PACMAN_REGEX = r"^([0-9a-zA-Z\-_]+)\s([0-9\._+a-z\-:]+)"
10
12
 
11
13
 
12
14
  class PacmanUnpackGroup(FactBase):
@@ -21,15 +23,18 @@ class PacmanUnpackGroup(FactBase):
21
23
  ]
22
24
  """
23
25
 
26
+ @override
24
27
  def requires_command(self, *args, **kwargs) -> str:
25
28
  return "pacman"
26
29
 
27
30
  default = list
28
31
 
32
+ @override
29
33
  def command(self, package):
30
34
  # Accept failure here (|| true) for invalid/unknown packages
31
35
  return 'pacman -S --print-format "%n" {0} || true'.format(shlex.quote(package))
32
36
 
37
+ @override
33
38
  def process(self, output):
34
39
  return output
35
40
 
@@ -45,13 +50,16 @@ class PacmanPackages(FactBase):
45
50
  }
46
51
  """
47
52
 
53
+ @override
48
54
  def command(self) -> str:
49
55
  return "pacman -Q"
50
56
 
57
+ @override
51
58
  def requires_command(self, *args, **kwargs) -> str:
52
59
  return "pacman"
53
60
 
54
61
  default = dict
55
62
 
63
+ @override
56
64
  def process(self, output):
57
65
  return parse_packages(PACMAN_REGEX, output)
pyinfra/facts/pip.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from pyinfra.api import FactBase
4
6
 
5
7
  from .util.packaging import parse_packages
@@ -21,13 +23,16 @@ class PipPackages(FactBase):
21
23
  default = dict
22
24
  pip_command = "pip"
23
25
 
26
+ @override
24
27
  def requires_command(self, pip=None):
25
28
  return pip or self.pip_command
26
29
 
30
+ @override
27
31
  def command(self, pip=None):
28
32
  pip = pip or self.pip_command
29
33
  return "{0} freeze --all".format(pip)
30
34
 
35
+ @override
31
36
  def process(self, output):
32
37
  return parse_packages(PIP_REGEX, output)
33
38
 
pyinfra/facts/pipx.py ADDED
@@ -0,0 +1,82 @@
1
+ import re
2
+
3
+ from typing_extensions import override
4
+
5
+ from pyinfra.api import FactBase
6
+
7
+ from .util.packaging import parse_packages
8
+
9
+
10
+ # TODO: move to an utils file
11
+ def parse_environment(output):
12
+ environment_REGEX = r"^(?P<key>[A-Z_]+)=(?P<value>.*)$"
13
+ environment_variables = {}
14
+
15
+ for line in output:
16
+ matches = re.match(environment_REGEX, line)
17
+
18
+ if matches:
19
+ environment_variables[matches.group("key")] = matches.group("value")
20
+
21
+ return environment_variables
22
+
23
+
24
+ PIPX_REGEX = r"^([a-zA-Z0-9_\-\+\.]+)\s+([0-9\.]+[a-z0-9\-]*)$"
25
+
26
+
27
+ class PipxPackages(FactBase):
28
+ """
29
+ Returns a dict of installed pipx packages:
30
+
31
+ .. code:: python
32
+
33
+ {
34
+ "package_name": ["version"],
35
+ }
36
+ """
37
+
38
+ default = dict
39
+
40
+ @override
41
+ def requires_command(self) -> str:
42
+ return "pipx"
43
+
44
+ @override
45
+ def command(self) -> str:
46
+ return "pipx list --short"
47
+
48
+ @override
49
+ def process(self, output):
50
+ return parse_packages(PIPX_REGEX, output)
51
+
52
+
53
+ class PipxEnvironment(FactBase):
54
+ """
55
+ Returns a dict of pipx environment variables:
56
+
57
+ .. code:: python
58
+
59
+ {
60
+ "PIPX_HOME": "/home/doodba/.local/pipx",
61
+ "PIPX_BIN_DIR": "/home/doodba/.local/bin",
62
+ "PIPX_SHARED_LIBS": "/home/doodba/.local/pipx/shared",
63
+ "PIPX_LOCAL_VENVS": "/home/doodba/.local/pipx/venvs",
64
+ "PIPX_LOG_DIR": "/home/doodba/.local/pipx/logs",
65
+ "PIPX_TRASH_DIR": "/home/doodba/.local/pipx/.trash",
66
+ "PIPX_VENV_CACHEDIR": "/home/doodba/.local/pipx/.cache",
67
+ }
68
+ """
69
+
70
+ default = dict
71
+
72
+ @override
73
+ def requires_command(self) -> str:
74
+ return "pipx"
75
+
76
+ @override
77
+ def command(self) -> str:
78
+ return "pipx environment"
79
+
80
+ @override
81
+ def process(self, output):
82
+ return parse_environment(output)
pyinfra/facts/pkg.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from pyinfra.api import FactBase
4
6
 
5
7
  from .util.packaging import parse_packages
@@ -19,8 +21,10 @@ class PkgPackages(FactBase):
19
21
  regex = r"^([a-zA-Z0-9_\-\+]+)\-([0-9a-z\.]+)"
20
22
  default = dict
21
23
 
24
+ @override
22
25
  def command(self) -> str:
23
26
  return "pkg info || pkg_info || true"
24
27
 
28
+ @override
25
29
  def process(self, output):
26
30
  return parse_packages(self.regex, output)
pyinfra/facts/pkgin.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from pyinfra.api import FactBase
4
6
 
5
7
  from .util.packaging import parse_packages
@@ -18,13 +20,16 @@ class PkginPackages(FactBase):
18
20
  }
19
21
  """
20
22
 
23
+ @override
21
24
  def command(self) -> str:
22
25
  return "pkgin list"
23
26
 
27
+ @override
24
28
  def requires_command(self) -> str:
25
29
  return "pkgin"
26
30
 
27
31
  default = dict
28
32
 
33
+ @override
29
34
  def process(self, output):
30
35
  return parse_packages(PKGIN_REGEX, output)
@@ -0,0 +1,54 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any, Dict, Iterable, List, TypeVar
5
+
6
+ from typing_extensions import override
7
+
8
+ from pyinfra.api import FactBase
9
+
10
+ T = TypeVar("T")
11
+
12
+
13
+ class PodmanFactBase(FactBase[T]):
14
+ """
15
+ Base for facts using `podman` to retrieve
16
+ """
17
+
18
+ abstract = True
19
+
20
+ @override
21
+ def requires_command(self, *args, **kwargs) -> str:
22
+ return "podman"
23
+
24
+
25
+ class PodmanSystemInfo(PodmanFactBase[Dict[str, Any]]):
26
+ """
27
+ Output of 'podman system info'
28
+ """
29
+
30
+ @override
31
+ def command(self) -> str:
32
+ return "podman system info --format=json"
33
+
34
+ @override
35
+ def process(self, output: Iterable[str]) -> Dict[str, Any]:
36
+ output = json.loads(("").join(output))
37
+ assert isinstance(output, dict)
38
+ return output
39
+
40
+
41
+ class PodmanPs(PodmanFactBase[List[Dict[str, Any]]]):
42
+ """
43
+ Output of 'podman ps'
44
+ """
45
+
46
+ @override
47
+ def command(self) -> str:
48
+ return "podman ps --format=json --all"
49
+
50
+ @override
51
+ def process(self, output: Iterable[str]) -> List[Dict[str, Any]]:
52
+ output = json.loads(("").join(output))
53
+ assert isinstance(output, list)
54
+ return output # type: ignore
pyinfra/facts/postgres.py CHANGED
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from typing_extensions import override
4
+
3
5
  from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
4
6
  from pyinfra.api.util import try_int
5
7
 
@@ -49,15 +51,18 @@ class PostgresFactBase(FactBase):
49
51
 
50
52
  psql_command: str
51
53
 
54
+ @override
52
55
  def requires_command(self, *args, **kwargs):
53
56
  return "psql"
54
57
 
58
+ @override
55
59
  def command(
56
60
  self,
57
61
  psql_user=None,
58
62
  psql_password=None,
59
63
  psql_host=None,
60
64
  psql_port=None,
65
+ psql_database=None,
61
66
  ):
62
67
  return make_execute_psql_command(
63
68
  self.psql_command,
@@ -65,6 +70,7 @@ class PostgresFactBase(FactBase):
65
70
  password=psql_password,
66
71
  host=psql_host,
67
72
  port=psql_port,
73
+ database=psql_database,
68
74
  )
69
75
 
70
76
 
@@ -87,6 +93,7 @@ class PostgresRoles(PostgresFactBase):
87
93
  default = dict
88
94
  psql_command = "SELECT * FROM pg_catalog.pg_roles"
89
95
 
96
+ @override
90
97
  def process(self, output):
91
98
  # Remove the last line of the output (row count)
92
99
  output = output[:-1]
@@ -137,8 +144,9 @@ class PostgresDatabases(PostgresFactBase):
137
144
  """
138
145
 
139
146
  default = dict
140
- psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), * FROM pg_catalog.pg_database"
147
+ psql_command = "SELECT pg_catalog.pg_encoding_to_char(encoding), *, pg_catalog.pg_get_userbyid(datdba) AS owner FROM pg_catalog.pg_database" # noqa: E501
141
148
 
149
+ @override
142
150
  def process(self, output):
143
151
  # Remove the last line of the output (row count)
144
152
  output = output[:-1]
@@ -153,7 +161,7 @@ class PostgresDatabases(PostgresFactBase):
153
161
 
154
162
  for details in rows:
155
163
  details["encoding"] = details.pop("pg_encoding_to_char")
156
-
164
+ details["owner"] = details.pop("owner")
157
165
  for key, value in list(details.items()):
158
166
  if key.endswith("id") or key in (
159
167
  "dba",
pyinfra/facts/rpm.py CHANGED
@@ -3,6 +3,8 @@ from __future__ import annotations
3
3
  import re
4
4
  import shlex
5
5
 
6
+ from typing_extensions import override
7
+
6
8
  from pyinfra.api import FactBase
7
9
 
8
10
  from .util.packaging import parse_packages
@@ -22,14 +24,17 @@ class RpmPackages(FactBase):
22
24
  }
23
25
  """
24
26
 
27
+ @override
25
28
  def command(self) -> str:
26
29
  return "rpm --queryformat {0} -qa".format(shlex.quote(rpm_query_format))
27
30
 
31
+ @override
28
32
  def requires_command(self) -> str:
29
33
  return "rpm"
30
34
 
31
35
  default = dict
32
36
 
37
+ @override
33
38
  def process(self, output):
34
39
  return parse_packages(rpm_regex, output)
35
40
 
@@ -46,9 +51,11 @@ class RpmPackage(FactBase):
46
51
  }
47
52
  """
48
53
 
54
+ @override
49
55
  def requires_command(self, package) -> str:
50
56
  return "rpm"
51
57
 
58
+ @override
52
59
  def command(self, package) -> str:
53
60
  return (
54
61
  "rpm --queryformat {0} -q {1} || "
@@ -56,6 +63,7 @@ class RpmPackage(FactBase):
56
63
  "rpm --queryformat {0} -qp {1} 2> /dev/null"
57
64
  ).format(shlex.quote(rpm_query_format), shlex.quote(package))
58
65
 
66
+ @override
59
67
  def process(self, output):
60
68
  for line in output:
61
69
  matches = re.match(rpm_regex, line)
@@ -73,9 +81,11 @@ class RpmPackageProvides(FactBase):
73
81
 
74
82
  default = list
75
83
 
84
+ @override
76
85
  def requires_command(self, *args, **kwargs) -> str:
77
86
  return "repoquery"
78
87
 
88
+ @override
79
89
  def command(self, package):
80
90
  # Accept failure here (|| true) for invalid/unknown packages
81
91
  return "repoquery --queryformat {0} --whatprovides {1} || true".format(
@@ -83,6 +93,7 @@ class RpmPackageProvides(FactBase):
83
93
  shlex.quote(package),
84
94
  )
85
95
 
96
+ @override
86
97
  def process(self, output):
87
98
  packages = []
88
99
 
pyinfra/facts/runit.py CHANGED
@@ -1,3 +1,5 @@
1
+ from typing_extensions import override
2
+
1
3
  from pyinfra.api import FactBase
2
4
 
3
5
 
@@ -20,9 +22,11 @@ class RunitStatus(FactBase):
20
22
 
21
23
  default = dict
22
24
 
25
+ @override
23
26
  def requires_command(self, *args, **kwargs) -> str:
24
27
  return "sv"
25
28
 
29
+ @override
26
30
  def command(self, service=None, svdir="/var/service") -> str:
27
31
  if service is None:
28
32
  return (
@@ -32,6 +36,7 @@ class RunitStatus(FactBase):
32
36
  else:
33
37
  return 'SVDIR="{0}" sv status "{1}"'.format(svdir, service)
34
38
 
39
+ @override
35
40
  def process(self, output):
36
41
  services = {}
37
42
  for line in output:
@@ -60,11 +65,13 @@ class RunitManaged(FactBase):
60
65
 
61
66
  default = set
62
67
 
68
+ @override
63
69
  def command(self, service=None, svdir="/var/service"):
64
70
  if service is None:
65
71
  return 'cd "{0}" && find -mindepth 1 -maxdepth 1 -type l -printf "%f\n"'.format(svdir)
66
72
  else:
67
73
  return 'cd "{0}" && test -h "{1}" && echo "{1}" || true'.format(svdir, service)
68
74
 
75
+ @override
69
76
  def process(self, output):
70
77
  return set(output)