pyinfra 0.11.dev3__py3-none-any.whl → 3.5.1__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 (203) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +18 -3
  4. pyinfra/api/arguments.py +406 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +67 -18
  12. pyinfra/api/facts.py +253 -202
  13. pyinfra/api/host.py +413 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/operation.py +432 -262
  16. pyinfra/api/operations.py +273 -260
  17. pyinfra/api/state.py +302 -248
  18. pyinfra/api/util.py +291 -368
  19. pyinfra/connectors/base.py +173 -0
  20. pyinfra/connectors/chroot.py +212 -0
  21. pyinfra/connectors/docker.py +381 -0
  22. pyinfra/connectors/dockerssh.py +297 -0
  23. pyinfra/connectors/local.py +238 -0
  24. pyinfra/connectors/scp/__init__.py +1 -0
  25. pyinfra/connectors/scp/client.py +204 -0
  26. pyinfra/connectors/ssh.py +670 -0
  27. pyinfra/connectors/ssh_util.py +114 -0
  28. pyinfra/connectors/sshuserclient/client.py +309 -0
  29. pyinfra/connectors/sshuserclient/config.py +102 -0
  30. pyinfra/connectors/terraform.py +135 -0
  31. pyinfra/connectors/util.py +410 -0
  32. pyinfra/connectors/vagrant.py +183 -0
  33. pyinfra/context.py +145 -0
  34. pyinfra/facts/__init__.py +7 -6
  35. pyinfra/facts/apk.py +22 -7
  36. pyinfra/facts/apt.py +117 -60
  37. pyinfra/facts/brew.py +100 -15
  38. pyinfra/facts/bsdinit.py +23 -0
  39. pyinfra/facts/cargo.py +37 -0
  40. pyinfra/facts/choco.py +47 -0
  41. pyinfra/facts/crontab.py +195 -0
  42. pyinfra/facts/deb.py +94 -0
  43. pyinfra/facts/dnf.py +48 -0
  44. pyinfra/facts/docker.py +96 -23
  45. pyinfra/facts/efibootmgr.py +113 -0
  46. pyinfra/facts/files.py +630 -58
  47. pyinfra/facts/flatpak.py +77 -0
  48. pyinfra/facts/freebsd.py +70 -0
  49. pyinfra/facts/gem.py +19 -6
  50. pyinfra/facts/git.py +59 -14
  51. pyinfra/facts/gpg.py +150 -0
  52. pyinfra/facts/hardware.py +313 -167
  53. pyinfra/facts/iptables.py +72 -62
  54. pyinfra/facts/launchd.py +44 -0
  55. pyinfra/facts/lxd.py +17 -4
  56. pyinfra/facts/mysql.py +122 -86
  57. pyinfra/facts/npm.py +17 -9
  58. pyinfra/facts/openrc.py +71 -0
  59. pyinfra/facts/opkg.py +246 -0
  60. pyinfra/facts/pacman.py +50 -7
  61. pyinfra/facts/pip.py +24 -7
  62. pyinfra/facts/pipx.py +82 -0
  63. pyinfra/facts/pkg.py +15 -6
  64. pyinfra/facts/pkgin.py +35 -0
  65. pyinfra/facts/podman.py +54 -0
  66. pyinfra/facts/postgres.py +178 -0
  67. pyinfra/facts/postgresql.py +6 -147
  68. pyinfra/facts/rpm.py +105 -0
  69. pyinfra/facts/runit.py +77 -0
  70. pyinfra/facts/selinux.py +161 -0
  71. pyinfra/facts/server.py +746 -285
  72. pyinfra/facts/snap.py +88 -0
  73. pyinfra/facts/systemd.py +139 -0
  74. pyinfra/facts/sysvinit.py +59 -0
  75. pyinfra/facts/upstart.py +35 -0
  76. pyinfra/facts/util/__init__.py +17 -0
  77. pyinfra/facts/util/databases.py +4 -6
  78. pyinfra/facts/util/packaging.py +37 -6
  79. pyinfra/facts/util/units.py +30 -0
  80. pyinfra/facts/util/win_files.py +99 -0
  81. pyinfra/facts/vzctl.py +20 -13
  82. pyinfra/facts/xbps.py +35 -0
  83. pyinfra/facts/yum.py +34 -40
  84. pyinfra/facts/zfs.py +77 -0
  85. pyinfra/facts/zypper.py +42 -0
  86. pyinfra/local.py +45 -83
  87. pyinfra/operations/__init__.py +12 -0
  88. pyinfra/operations/apk.py +98 -0
  89. pyinfra/operations/apt.py +488 -0
  90. pyinfra/operations/brew.py +231 -0
  91. pyinfra/operations/bsdinit.py +59 -0
  92. pyinfra/operations/cargo.py +45 -0
  93. pyinfra/operations/choco.py +61 -0
  94. pyinfra/operations/crontab.py +191 -0
  95. pyinfra/operations/dnf.py +210 -0
  96. pyinfra/operations/docker.py +446 -0
  97. pyinfra/operations/files.py +1939 -0
  98. pyinfra/operations/flatpak.py +94 -0
  99. pyinfra/operations/freebsd/__init__.py +12 -0
  100. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  101. pyinfra/operations/freebsd/pkg.py +219 -0
  102. pyinfra/operations/freebsd/service.py +116 -0
  103. pyinfra/operations/freebsd/sysrc.py +92 -0
  104. pyinfra/operations/gem.py +47 -0
  105. pyinfra/operations/git.py +419 -0
  106. pyinfra/operations/iptables.py +311 -0
  107. pyinfra/operations/launchd.py +45 -0
  108. pyinfra/operations/lxd.py +68 -0
  109. pyinfra/operations/mysql.py +609 -0
  110. pyinfra/operations/npm.py +57 -0
  111. pyinfra/operations/openrc.py +63 -0
  112. pyinfra/operations/opkg.py +88 -0
  113. pyinfra/operations/pacman.py +81 -0
  114. pyinfra/operations/pip.py +205 -0
  115. pyinfra/operations/pipx.py +102 -0
  116. pyinfra/operations/pkg.py +70 -0
  117. pyinfra/operations/pkgin.py +91 -0
  118. pyinfra/operations/postgres.py +436 -0
  119. pyinfra/operations/postgresql.py +30 -0
  120. pyinfra/operations/puppet.py +40 -0
  121. pyinfra/operations/python.py +72 -0
  122. pyinfra/operations/runit.py +184 -0
  123. pyinfra/operations/selinux.py +189 -0
  124. pyinfra/operations/server.py +1099 -0
  125. pyinfra/operations/snap.py +117 -0
  126. pyinfra/operations/ssh.py +216 -0
  127. pyinfra/operations/systemd.py +149 -0
  128. pyinfra/operations/sysvinit.py +141 -0
  129. pyinfra/operations/upstart.py +68 -0
  130. pyinfra/operations/util/__init__.py +12 -0
  131. pyinfra/operations/util/docker.py +251 -0
  132. pyinfra/operations/util/files.py +247 -0
  133. pyinfra/operations/util/packaging.py +336 -0
  134. pyinfra/operations/util/service.py +46 -0
  135. pyinfra/operations/vzctl.py +137 -0
  136. pyinfra/operations/xbps.py +77 -0
  137. pyinfra/operations/yum.py +210 -0
  138. pyinfra/operations/zfs.py +175 -0
  139. pyinfra/operations/zypper.py +192 -0
  140. pyinfra/progress.py +44 -32
  141. pyinfra/py.typed +0 -0
  142. pyinfra/version.py +9 -1
  143. pyinfra-3.5.1.dist-info/METADATA +141 -0
  144. pyinfra-3.5.1.dist-info/RECORD +159 -0
  145. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
  146. pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
  147. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
  148. pyinfra_cli/__init__.py +1 -0
  149. pyinfra_cli/cli.py +780 -0
  150. pyinfra_cli/commands.py +66 -0
  151. pyinfra_cli/exceptions.py +155 -65
  152. pyinfra_cli/inventory.py +233 -89
  153. pyinfra_cli/log.py +39 -43
  154. pyinfra_cli/main.py +26 -495
  155. pyinfra_cli/prints.py +215 -156
  156. pyinfra_cli/util.py +172 -105
  157. pyinfra_cli/virtualenv.py +25 -20
  158. pyinfra/api/connectors/__init__.py +0 -21
  159. pyinfra/api/connectors/ansible.py +0 -99
  160. pyinfra/api/connectors/docker.py +0 -178
  161. pyinfra/api/connectors/local.py +0 -169
  162. pyinfra/api/connectors/ssh.py +0 -402
  163. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  164. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  165. pyinfra/api/connectors/util.py +0 -63
  166. pyinfra/api/connectors/vagrant.py +0 -155
  167. pyinfra/facts/init.py +0 -176
  168. pyinfra/facts/util/files.py +0 -102
  169. pyinfra/hook.py +0 -41
  170. pyinfra/modules/__init__.py +0 -11
  171. pyinfra/modules/apk.py +0 -64
  172. pyinfra/modules/apt.py +0 -272
  173. pyinfra/modules/brew.py +0 -122
  174. pyinfra/modules/files.py +0 -711
  175. pyinfra/modules/gem.py +0 -30
  176. pyinfra/modules/git.py +0 -115
  177. pyinfra/modules/init.py +0 -344
  178. pyinfra/modules/iptables.py +0 -271
  179. pyinfra/modules/lxd.py +0 -45
  180. pyinfra/modules/mysql.py +0 -347
  181. pyinfra/modules/npm.py +0 -47
  182. pyinfra/modules/pacman.py +0 -60
  183. pyinfra/modules/pip.py +0 -99
  184. pyinfra/modules/pkg.py +0 -43
  185. pyinfra/modules/postgresql.py +0 -245
  186. pyinfra/modules/puppet.py +0 -20
  187. pyinfra/modules/python.py +0 -37
  188. pyinfra/modules/server.py +0 -524
  189. pyinfra/modules/ssh.py +0 -150
  190. pyinfra/modules/util/files.py +0 -52
  191. pyinfra/modules/util/packaging.py +0 -118
  192. pyinfra/modules/vzctl.py +0 -133
  193. pyinfra/modules/yum.py +0 -171
  194. pyinfra/pseudo_modules.py +0 -64
  195. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  196. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  197. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  198. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  199. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  200. pyinfra_cli/__main__.py +0 -40
  201. pyinfra_cli/config.py +0 -92
  202. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  203. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
pyinfra/facts/iptables.py CHANGED
@@ -1,56 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ from typing_extensions import override
4
+
1
5
  from pyinfra.api import FactBase
2
6
 
3
7
  # Mapping for iptables code arguments to variable names
4
8
  IPTABLES_ARGS = {
5
- '-A': 'chain',
6
- '-j': 'jump',
7
-
9
+ "-A": "chain",
10
+ "-j": "jump",
8
11
  # Boolean matches
9
- '-p': 'protocol',
10
- '-s': 'source',
11
- '-d': 'destination',
12
- '-i': 'in_interface',
13
- '-o': 'out_interface',
14
-
12
+ "-p": "protocol",
13
+ "-s": "source",
14
+ "-d": "destination",
15
+ "-i": "in_interface",
16
+ "-o": "out_interface",
15
17
  # Logging
16
- '--log-prefix': 'log_prefix',
17
-
18
+ "--log-prefix": "log_prefix",
18
19
  # NAT exit rules
19
- '--to-destination': 'to_destination',
20
- '--to-source': 'to_source',
21
- '--to-ports': 'to_ports',
20
+ "--to-destination": "to_destination",
21
+ "--to-source": "to_source",
22
+ "--to-ports": "to_ports",
22
23
  }
23
24
 
24
25
 
25
26
  def parse_iptables_rule(line):
26
- '''
27
+ """
27
28
  Parse one iptables rule. Returns a dict where each iptables code argument
28
29
  is mapped to a name using IPTABLES_ARGS.
29
- '''
30
+ """
30
31
 
31
32
  bits = line.split()
32
33
 
33
- definition = {}
34
+ definition: dict = {}
34
35
 
35
36
  key = None
36
- args = []
37
+ args: list[str] = []
37
38
  not_arg = False
38
39
 
39
- def add_args():
40
- arg_string = ' '.join(args)
40
+ def add_args() -> None:
41
+ arg_string = " ".join(args)
41
42
 
42
- if key in IPTABLES_ARGS:
43
- definition_key = (
44
- 'not_{0}'.format(IPTABLES_ARGS[key])
45
- if not_arg
46
- else IPTABLES_ARGS[key]
47
- )
43
+ if key and key in IPTABLES_ARGS:
44
+ definition_key = "not_{0}".format(IPTABLES_ARGS[key]) if not_arg else IPTABLES_ARGS[key]
48
45
  definition[definition_key] = arg_string
49
46
  else:
50
- definition.setdefault('extras', []).extend((key, arg_string))
47
+ definition.setdefault("extras", []).extend((key, arg_string))
51
48
 
52
49
  for bit in bits:
53
- if bit == '!':
50
+ if bit == "!":
54
51
  if key:
55
52
  add_args()
56
53
  args = []
@@ -58,7 +55,7 @@ def parse_iptables_rule(line):
58
55
 
59
56
  not_arg = True
60
57
 
61
- elif bit.startswith('-'):
58
+ elif bit.startswith("-"):
62
59
  if key:
63
60
  add_args()
64
61
  args = []
@@ -72,74 +69,85 @@ def parse_iptables_rule(line):
72
69
  if key:
73
70
  add_args()
74
71
 
75
- if 'extras' in definition:
76
- definition['extras'] = set(definition['extras'])
72
+ if "extras" in definition:
73
+ definition["extras"] = set(definition["extras"])
77
74
 
78
75
  return definition
79
76
 
80
77
 
81
78
  class IptablesRules(FactBase):
82
- '''
79
+ """
83
80
  Returns a list of iptables rules for a specific table:
84
81
 
85
82
  .. code:: python
86
83
 
87
- {
88
- 'chain': 'PREROUTING',
89
- 'jump': 'DNAT'
90
- },
91
- ...
92
- '''
84
+ [
85
+ {
86
+ "chain": "PREROUTING",
87
+ "jump": "DNAT",
88
+ },
89
+ ]
90
+ """
93
91
 
94
92
  default = list
95
93
 
96
- def command(self, table='filter'):
97
- return 'iptables-save -t {0}'.format(table)
94
+ @override
95
+ def command(self, table="filter"):
96
+ return "iptables-save -t {0}".format(table)
98
97
 
98
+ @override
99
99
  def process(self, output):
100
100
  rules = []
101
101
 
102
102
  for line in output:
103
- if line.startswith('-'):
103
+ if line.startswith("-"):
104
104
  rules.append(parse_iptables_rule(line))
105
105
 
106
106
  return rules
107
107
 
108
108
 
109
109
  class Ip6tablesRules(IptablesRules):
110
- '''
110
+ """
111
111
  Returns a list of ip6tables rules for a specific table:
112
112
 
113
113
  .. code:: python
114
114
 
115
- {
116
- 'chain': 'PREROUTING',
117
- 'jump': 'DNAT'
118
- },
119
- ...
120
- '''
115
+ [
116
+ {
117
+ "chain": "PREROUTING",
118
+ "jump": "DNAT",
119
+ },
120
+ ]
121
+ """
121
122
 
122
- def command(self, table='filter'):
123
- return 'ip6tables-save -t {0}'.format(table)
123
+ @override
124
+ def command(self, table="filter"):
125
+ return "ip6tables-save -t {0}".format(table)
124
126
 
125
127
 
126
- class IptablesChains(IptablesRules):
127
- '''
128
+ class IptablesChains(FactBase):
129
+ """
128
130
  Returns a dict of iptables chains & policies:
129
131
 
130
132
  .. code:: python
131
133
 
132
- 'NAME': 'POLICY',
133
- ...
134
- '''
134
+ {
135
+ "NAME": "POLICY",
136
+ }
137
+ """
135
138
 
136
139
  default = dict
137
140
 
141
+ @override
142
+ def command(self, table="filter"):
143
+ return "iptables-save -t {0}".format(table)
144
+
145
+ @override
138
146
  def process(self, output):
139
147
  chains = {}
140
148
 
141
149
  for line in output:
142
- if line.startswith(':'):
150
+ if line.startswith(":"):
143
151
  line = line[1:]
144
152
 
145
153
  name, policy, _ = line.split()
@@ -149,14 +157,16 @@ class IptablesChains(IptablesRules):
149
157
 
150
158
 
151
159
  class Ip6tablesChains(IptablesChains):
152
- '''
160
+ """
153
161
  Returns a dict of ip6tables chains & policies:
154
162
 
155
163
  .. code:: python
156
164
 
157
- 'NAME': 'POLICY',
158
- ...
159
- '''
165
+ {
166
+ "NAME": "POLICY",
167
+ }
168
+ """
160
169
 
161
- def command(self, table='filter'):
162
- return 'ip6tables-save -t {0}'.format(table)
170
+ @override
171
+ def command(self, table="filter"):
172
+ return "ip6tables-save -t {0}".format(table)
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing_extensions import override
4
+
5
+ from pyinfra.api import FactBase
6
+
7
+
8
+ class LaunchdStatus(FactBase):
9
+ """
10
+ Returns a dict of name -> status for launchd managed services.
11
+ """
12
+
13
+ @override
14
+ def command(self) -> str:
15
+ return "launchctl list"
16
+
17
+ @override
18
+ def requires_command(self) -> str:
19
+ return "launchctl"
20
+
21
+ default = dict
22
+
23
+ @override
24
+ def process(self, output):
25
+ services = {}
26
+
27
+ for line in output:
28
+ bits = line.split()
29
+
30
+ if not bits or bits[0] == "PID":
31
+ continue
32
+
33
+ name = bits[2]
34
+ status = False
35
+
36
+ try:
37
+ int(bits[0])
38
+ status = True
39
+ except ValueError:
40
+ pass
41
+
42
+ services[name] = status
43
+
44
+ return services
pyinfra/facts/lxd.py CHANGED
@@ -1,16 +1,29 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
 
5
+ from typing_extensions import override
6
+
3
7
  from pyinfra.api import FactBase
4
8
 
5
9
 
6
- class LXDContainers(FactBase):
7
- '''
10
+ class LxdContainers(FactBase):
11
+ """
8
12
  Returns a list of running LXD containers
9
- '''
13
+ """
14
+
15
+ @override
16
+ def command(self) -> str:
17
+ return "lxc list --format json --fast"
18
+
19
+ @override
20
+ def requires_command(self) -> str:
21
+ return "lxc"
10
22
 
11
- command = 'lxc list --format json --fast'
12
23
  default = list
13
24
 
25
+ @override
14
26
  def process(self, output):
27
+ output = list(output)
15
28
  assert len(output) == 1
16
29
  return json.loads(output[0])
pyinfra/facts/mysql.py CHANGED
@@ -1,20 +1,23 @@
1
- import re
1
+ from __future__ import annotations
2
2
 
3
+ import re
3
4
  from collections import defaultdict
4
5
 
5
- from pyinfra.api import FactBase
6
+ from typing_extensions import override
7
+
8
+ from pyinfra.api import FactBase, MaskString, QuoteString, StringCommand
6
9
  from pyinfra.api.util import try_int
7
10
 
8
11
  from .util.databases import parse_columns_and_rows
9
12
 
10
13
 
11
14
  def make_mysql_command(
12
- database=None,
13
- user=None,
14
- password=None,
15
- host=None,
16
- port=None,
17
- executable='mysql',
15
+ database: str | None = None,
16
+ user: str | None = None,
17
+ password: str | None = None,
18
+ host: str | None = None,
19
+ port: int | None = None,
20
+ executable="mysql",
18
21
  ):
19
22
  target_bits = [executable]
20
23
 
@@ -27,35 +30,56 @@ def make_mysql_command(
27
30
 
28
31
  if password:
29
32
  # Quote the password as it may contain special characters
30
- target_bits.append('-p"{0}"'.format(password))
33
+ target_bits.append(MaskString('-p"{0}"'.format(password)))
31
34
 
32
35
  if host:
33
- target_bits.append('-h{0}'.format(host))
36
+ target_bits.append("-h{0}".format(host))
34
37
 
35
38
  if port:
36
- target_bits.append('-P{0}'.format(port))
39
+ target_bits.append("-P{0}".format(port))
37
40
 
38
- return ' '.join(target_bits)
41
+ return StringCommand(*target_bits)
39
42
 
40
43
 
41
- def make_execute_mysql_command(command, **mysql_kwargs):
42
- return '{0} -Be "{1}"'.format(
44
+ def make_execute_mysql_command(
45
+ command: str | StringCommand,
46
+ ignore_errors=False,
47
+ **mysql_kwargs,
48
+ ):
49
+ commands_bits = [
43
50
  make_mysql_command(**mysql_kwargs),
44
- command.replace('"', '\\"'),
45
- )
51
+ "-Be",
52
+ QuoteString(command), # quote this whole item as a single shell argument
53
+ ]
54
+
55
+ if ignore_errors:
56
+ commands_bits.extend(["||", "true"])
57
+
58
+ return StringCommand(*commands_bits)
46
59
 
47
60
 
48
61
  class MysqlFactBase(FactBase):
49
62
  abstract = True
50
63
 
64
+ mysql_command: str
65
+ ignore_errors = False
66
+
67
+ @override
68
+ def requires_command(self, *args, **kwargs) -> str:
69
+ return "mysql"
70
+
71
+ @override
51
72
  def command(
52
73
  self,
53
74
  # Details for speaking to MySQL via `mysql` CLI via `mysql` CLI
54
- mysql_user=None, mysql_password=None,
55
- mysql_host=None, mysql_port=None,
56
- ):
75
+ mysql_user=None,
76
+ mysql_password=None,
77
+ mysql_host=None,
78
+ mysql_port=None,
79
+ ) -> StringCommand:
57
80
  return make_execute_mysql_command(
58
81
  self.mysql_command,
82
+ ignore_errors=self.ignore_errors,
59
83
  user=mysql_user,
60
84
  password=mysql_password,
61
85
  host=mysql_host,
@@ -64,130 +88,142 @@ class MysqlFactBase(FactBase):
64
88
 
65
89
 
66
90
  class MysqlDatabases(MysqlFactBase):
67
- '''
91
+ """
68
92
  Returns a dict of existing MySQL databases and associated data:
69
93
 
70
94
  .. code:: python
71
95
 
72
- 'mysql': {
73
- 'character_set': 'latin1',
74
- 'collation_name': 'latin1_swedish_ci'
75
- },
76
- ...
77
- '''
96
+ {
97
+ "mysql": {
98
+ "character_set": "latin1",
99
+ "collation_name": "latin1_swedish_ci"
100
+ },
101
+ }
102
+ """
78
103
 
79
104
  default = dict
80
- mysql_command = 'SELECT * FROM information_schema.SCHEMATA'
105
+ mysql_command = "SELECT * FROM information_schema.SCHEMATA"
81
106
 
107
+ @override
82
108
  def process(self, output):
83
109
  rows = parse_columns_and_rows(
84
- output, '\t',
110
+ output,
111
+ "\t",
85
112
  title_parser=lambda title: title.lower(),
86
113
  )
87
114
 
88
115
  databases = {}
89
116
 
90
117
  for details in rows:
91
- databases[details.pop('schema_name')] = {
92
- 'character_set': details['default_character_set_name'],
93
- 'collation_name': details['default_collation_name'],
118
+ databases[details.pop("schema_name")] = {
119
+ "character_set": details["default_character_set_name"],
120
+ "collation_name": details["default_collation_name"],
94
121
  }
95
122
 
96
123
  return databases
97
124
 
98
125
 
99
126
  class MysqlUsers(MysqlFactBase):
100
- '''
127
+ """
101
128
  Returns a dict of MySQL ``user@host``'s and their associated data:
102
129
 
103
130
  .. code:: python
104
131
 
105
- 'user@host': {
106
- 'privileges': ['Alter', 'Grant'],
107
- 'max_connections': 5,
108
- ...
109
- },
110
- ...
111
- '''
132
+ {
133
+ "user@host": {
134
+ "privileges": ["Alter", "Grant"],
135
+ 'max_connections': 5,
136
+ ...
137
+ },
138
+ }
139
+ """
112
140
 
113
141
  default = dict
114
- mysql_command = 'SELECT * FROM mysql.user'
142
+ mysql_command = "SELECT * FROM mysql.user"
115
143
 
116
- @staticmethod
117
- def process(output):
118
- rows = parse_columns_and_rows(output, '\t')
144
+ @override
145
+ def process(self, output):
146
+ rows = parse_columns_and_rows(output, "\t")
119
147
 
120
148
  users = {}
121
149
 
122
150
  for details in rows:
123
- if details.get('Host') is None or details.get('User') is None:
151
+ if details.get("Host") is None or details.get("User") is None:
124
152
  continue # pragma: no cover
125
153
 
126
154
  privileges = []
127
155
 
128
156
  for key, value in list(details.items()):
129
- if key.endswith('_priv') and details.pop(key) == 'Y':
130
- privileges.append(key.replace('_priv', ''))
157
+ if key.endswith("_priv") and details.pop(key) == "Y":
158
+ privileges.append(key.replace("_priv", ""))
131
159
 
132
- if key.startswith('max_'):
160
+ if key.startswith("max_"):
133
161
  details[key] = try_int(value)
134
162
 
135
- if key in ('password_expired', 'is_role'):
136
- details[key] = value == 'Y'
163
+ if key in ("password_expired", "is_role"):
164
+ details[key] = value == "Y"
137
165
 
138
- details['privileges'] = sorted(privileges)
166
+ details["privileges"] = sorted(privileges)
139
167
 
140
168
  # Attach the user in the format user@host
141
- users['{0}@{1}'.format(
142
- details.pop('User'), details.pop('Host'),
143
- )] = details
169
+ users[
170
+ "{0}@{1}".format(
171
+ details.pop("User"),
172
+ details.pop("Host"),
173
+ )
174
+ ] = details
144
175
 
145
176
  return users
146
177
 
147
178
 
148
- MYSQL_GRANT_REGEX = re.compile(
149
- r"^GRANT ([A-Z,\s]+) ON (\*|`[a-z_\\]+`\.\*|'[a-z_]+') TO '[a-z_]+'@'[a-z]+'(.*)",
179
+ MYSQL_GRANT_REGEX = (
180
+ r"^GRANT ([A-Z,\s]+) ON ((?:\*|`[a-z_\\]+`)\.(?:\*|`[a-z_]+`)) "
181
+ r"TO `[A-Z0-9a-z_\-]+`@`(?:%|[A-Z0-9a-z_\.\-]+)`(.*)"
150
182
  )
151
183
 
152
184
 
153
185
  class MysqlUserGrants(MysqlFactBase):
154
- '''
155
- Returns a dict of ``<database>`.<table>`` with granted privileges for each:
186
+ """
187
+ Returns a dict of ``<database>`.<table>`` with a set of granted privileges for each:
156
188
 
157
189
  .. code:: python
158
190
 
159
- '`pyinfra_stuff`.*': {
160
- 'privileges': [
161
- 'SELECT',
162
- 'INSERT'
163
- ],
164
- "with_grant_option": false
165
- },
166
- ...
167
- '''
191
+ {
192
+ "`pyinfra_stuff`.*": {
193
+ "SELECT",
194
+ "INSERT",
195
+ "GRANT OPTION",
196
+ },
197
+ }
198
+ """
168
199
 
169
200
  default = dict
201
+ # Ignore errors as SHOW GRANTS will error if the user does not exist
202
+ ignore_errors = True
170
203
 
171
- def command(
172
- self, user,
173
- hostname='localhost',
204
+ @override
205
+ def command( # type: ignore[override]
206
+ self,
207
+ user,
208
+ hostname="localhost",
174
209
  # Details for speaking to MySQL via `mysql` CLI via `mysql` CLI
175
- mysql_user=None, mysql_password=None,
176
- mysql_host=None, mysql_port=None,
177
- ):
210
+ mysql_user=None,
211
+ mysql_password=None,
212
+ mysql_host=None,
213
+ mysql_port=None,
214
+ ) -> StringCommand:
178
215
  self.mysql_command = 'SHOW GRANTS FOR "{0}"@"{1}"'.format(user, hostname)
179
216
 
180
- return super(MysqlUserGrants, self).command(
181
- mysql_user, mysql_password,
182
- mysql_host, mysql_port,
217
+ return super().command(
218
+ mysql_user,
219
+ mysql_password,
220
+ mysql_host,
221
+ mysql_port,
183
222
  )
184
223
 
185
- @staticmethod
186
- def process(output):
187
- database_table_privileges = defaultdict(lambda: {
188
- 'privileges': set(),
189
- 'with_grant_option': False,
190
- })
224
+ @override
225
+ def process(self, output):
226
+ database_table_privileges = defaultdict(set)
191
227
 
192
228
  for line in output:
193
229
  matches = re.match(MYSQL_GRANT_REGEX, line)
@@ -197,13 +233,13 @@ class MysqlUserGrants(MysqlFactBase):
197
233
  privileges, database_table, extras = matches.groups()
198
234
 
199
235
  # MySQL outputs this pre-escaped
200
- database_table = database_table.replace('\\\\', '\\')
236
+ database_table = database_table.replace("\\\\", "\\")
201
237
 
202
- for privilege in privileges.split(','):
238
+ for privilege in privileges.split(","):
203
239
  privilege = privilege.strip()
204
- database_table_privileges[database_table]['privileges'].add(privilege)
240
+ database_table_privileges[database_table].add(privilege)
205
241
 
206
- if 'WITH GRANT OPTION' in extras:
207
- database_table_privileges[database_table]['with_grant_option'] = True
242
+ if "WITH GRANT OPTION" in extras:
243
+ database_table_privileges[database_table].add("GRANT OPTION")
208
244
 
209
245
  return database_table_privileges
pyinfra/facts/npm.py CHANGED
@@ -1,30 +1,38 @@
1
1
  # encoding: utf8
2
+ from __future__ import annotations
3
+
4
+ from typing_extensions import override
2
5
 
3
6
  from pyinfra.api import FactBase
4
7
 
5
8
  from .util.packaging import parse_packages
6
9
 
7
- NPM_REGEX = r'^[└├]\─\─\s([a-zA-Z0-9\-]+)@([0-9\.]+)$'
10
+ NPM_REGEX = r"^[└├]\─\─\s([a-zA-Z0-9\-]+)@([0-9\.]+)$"
8
11
 
9
12
 
10
13
  class NpmPackages(FactBase):
11
- '''
14
+ """
12
15
  Returns a dict of installed npm packages globally or in a given directory:
13
16
 
14
17
  .. code:: python
15
18
 
16
- 'package_name': ['version'],
17
- ...
18
- '''
19
+ {
20
+ "package_name": ["version"],
21
+ }
22
+ """
19
23
 
20
- command = 'npm list -g --depth=0'
21
24
  default = dict
22
25
 
26
+ @override
27
+ def requires_command(self, directory=None) -> str:
28
+ return "npm"
29
+
30
+ @override
23
31
  def command(self, directory=None):
24
32
  if directory:
25
- return 'cd {0} && npm list -g --depth=0'.format(directory)
26
- else:
27
- return 'npm list -g --depth=0'
33
+ return ("cd {0} && npm list -g --depth=0").format(directory)
34
+ return "npm list -g --depth=0"
28
35
 
36
+ @override
29
37
  def process(self, output):
30
38
  return parse_packages(NPM_REGEX, output)