pyinfra 3.0.dev0__py2.py3-none-any.whl → 3.0.1__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.
- pyinfra/api/__init__.py +3 -0
- pyinfra/api/arguments.py +115 -97
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +5 -3
- pyinfra/api/config.py +139 -39
- pyinfra/api/connectors.py +5 -2
- pyinfra/api/deploy.py +19 -19
- pyinfra/api/exceptions.py +35 -4
- pyinfra/api/facts.py +62 -86
- pyinfra/api/host.py +102 -15
- pyinfra/api/inventory.py +4 -0
- pyinfra/api/operation.py +184 -118
- pyinfra/api/operations.py +66 -113
- pyinfra/api/state.py +53 -34
- pyinfra/api/util.py +64 -33
- pyinfra/connectors/base.py +65 -20
- pyinfra/connectors/chroot.py +15 -13
- pyinfra/connectors/docker.py +62 -72
- pyinfra/connectors/dockerssh.py +20 -19
- pyinfra/connectors/local.py +32 -22
- pyinfra/connectors/ssh.py +162 -86
- pyinfra/connectors/sshuserclient/client.py +1 -1
- pyinfra/connectors/terraform.py +57 -39
- pyinfra/connectors/util.py +26 -27
- pyinfra/connectors/vagrant.py +27 -26
- pyinfra/context.py +1 -0
- pyinfra/facts/apk.py +7 -2
- pyinfra/facts/apt.py +15 -7
- pyinfra/facts/brew.py +28 -13
- pyinfra/facts/bsdinit.py +9 -6
- pyinfra/facts/cargo.py +6 -3
- pyinfra/facts/choco.py +8 -4
- pyinfra/facts/deb.py +21 -9
- pyinfra/facts/dnf.py +11 -6
- pyinfra/facts/docker.py +30 -5
- pyinfra/facts/files.py +49 -33
- pyinfra/facts/gem.py +7 -2
- pyinfra/facts/git.py +14 -21
- pyinfra/facts/gpg.py +4 -1
- pyinfra/facts/hardware.py +186 -138
- pyinfra/facts/launchd.py +7 -2
- pyinfra/facts/lxd.py +8 -2
- pyinfra/facts/mysql.py +19 -12
- pyinfra/facts/npm.py +3 -1
- pyinfra/facts/openrc.py +8 -2
- pyinfra/facts/pacman.py +13 -5
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +5 -1
- pyinfra/facts/pkgin.py +7 -2
- pyinfra/facts/postgres.py +170 -0
- pyinfra/facts/postgresql.py +5 -162
- pyinfra/facts/rpm.py +21 -15
- pyinfra/facts/runit.py +70 -0
- pyinfra/facts/selinux.py +12 -4
- pyinfra/facts/server.py +240 -82
- pyinfra/facts/snap.py +8 -2
- pyinfra/facts/systemd.py +37 -13
- pyinfra/facts/sysvinit.py +7 -4
- pyinfra/facts/upstart.py +7 -2
- pyinfra/facts/util/packaging.py +3 -2
- pyinfra/facts/vzctl.py +8 -4
- pyinfra/facts/xbps.py +7 -2
- pyinfra/facts/yum.py +10 -5
- pyinfra/facts/zypper.py +9 -4
- pyinfra/operations/apk.py +5 -3
- pyinfra/operations/apt.py +28 -25
- pyinfra/operations/brew.py +60 -29
- pyinfra/operations/bsdinit.py +6 -4
- pyinfra/operations/cargo.py +3 -1
- pyinfra/operations/choco.py +3 -1
- pyinfra/operations/dnf.py +16 -20
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +187 -168
- pyinfra/operations/gem.py +3 -1
- pyinfra/operations/git.py +23 -25
- pyinfra/operations/iptables.py +33 -25
- pyinfra/operations/launchd.py +5 -6
- pyinfra/operations/lxd.py +7 -4
- pyinfra/operations/mysql.py +59 -55
- pyinfra/operations/npm.py +8 -1
- pyinfra/operations/openrc.py +5 -3
- pyinfra/operations/pacman.py +6 -7
- pyinfra/operations/pip.py +19 -12
- pyinfra/operations/pkg.py +3 -1
- pyinfra/operations/pkgin.py +5 -3
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -335
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -29
- pyinfra/operations/server.py +138 -67
- pyinfra/operations/snap.py +3 -1
- pyinfra/operations/ssh.py +18 -16
- pyinfra/operations/systemd.py +18 -12
- pyinfra/operations/sysvinit.py +7 -5
- pyinfra/operations/upstart.py +7 -5
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +54 -38
- pyinfra/operations/util/service.py +39 -47
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +5 -3
- pyinfra/operations/yum.py +15 -19
- pyinfra/operations/zypper.py +9 -10
- pyinfra/version.py +5 -2
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/METADATA +51 -58
- pyinfra-3.0.1.dist-info/RECORD +168 -0
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/WHEEL +1 -1
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/entry_points.txt +0 -3
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +3 -2
- pyinfra_cli/exceptions.py +75 -43
- pyinfra_cli/inventory.py +52 -31
- pyinfra_cli/log.py +10 -2
- pyinfra_cli/main.py +88 -65
- pyinfra_cli/prints.py +37 -109
- pyinfra_cli/util.py +15 -10
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +9 -9
- tests/test_api/test_api_deploys.py +15 -19
- tests/test_api/test_api_facts.py +4 -5
- tests/test_api/test_api_operations.py +18 -20
- tests/test_api/test_api_util.py +41 -2
- tests/test_cli/test_cli.py +14 -50
- tests/test_cli/test_cli_deploy.py +10 -12
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/test_cli_inventory.py +66 -0
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_dockerssh.py +11 -8
- tests/test_connectors/test_ssh.py +88 -23
- tests/test_connectors/test_sshuserclient.py +1 -1
- tests/test_connectors/test_terraform.py +11 -8
- tests/test_connectors/test_vagrant.py +6 -6
- pyinfra/connectors/ansible.py +0 -175
- pyinfra/connectors/mech.py +0 -189
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -312
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -538
- pyinfra-3.0.dev0.dist-info/RECORD +0 -170
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/top_level.txt +0 -0
pyinfra/facts/git.py
CHANGED
|
@@ -5,32 +5,29 @@ import re
|
|
|
5
5
|
from pyinfra.api.facts import FactBase
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class
|
|
9
|
-
requires_command
|
|
8
|
+
class GitFactBase(FactBase):
|
|
9
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
10
|
+
return "git"
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
|
|
13
|
+
class GitBranch(GitFactBase):
|
|
14
|
+
def command(self, repo) -> str:
|
|
13
15
|
return "! test -d {0} || (cd {0} && git describe --all)".format(repo)
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
def process(output):
|
|
17
|
+
def process(self, output):
|
|
17
18
|
return re.sub(r"(heads|tags)/", r"", "\n".join(output))
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class GitConfig(
|
|
21
|
+
class GitConfig(GitFactBase):
|
|
21
22
|
default = dict
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@staticmethod
|
|
26
|
-
def command(repo=None):
|
|
24
|
+
def command(self, repo=None) -> str:
|
|
27
25
|
if repo is None:
|
|
28
26
|
return "git config --global -l || true"
|
|
29
27
|
|
|
30
28
|
return "! test -d {0} || (cd {0} && git config --local -l)".format(repo)
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
def process(output):
|
|
30
|
+
def process(self, output):
|
|
34
31
|
items: dict[str, list[str]] = {}
|
|
35
32
|
|
|
36
33
|
for line in output:
|
|
@@ -40,19 +37,15 @@ class GitConfig(FactBase):
|
|
|
40
37
|
return items
|
|
41
38
|
|
|
42
39
|
|
|
43
|
-
class GitTrackingBranch(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
@staticmethod
|
|
47
|
-
def command(repo):
|
|
40
|
+
class GitTrackingBranch(GitFactBase):
|
|
41
|
+
def command(self, repo) -> str:
|
|
48
42
|
return r"! test -d {0} || (cd {0} && git status --branch --porcelain)".format(repo)
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
def process(output):
|
|
44
|
+
def process(self, output):
|
|
52
45
|
if not output:
|
|
53
46
|
return None
|
|
54
47
|
|
|
55
|
-
m = re.search(r"\.{3}(\S+)\b", output[0])
|
|
48
|
+
m = re.search(r"\.{3}(\S+)\b", list(output)[0])
|
|
56
49
|
if m:
|
|
57
50
|
return m.group(1)
|
|
58
51
|
return None
|
pyinfra/facts/gpg.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from urllib.parse import urlparse
|
|
2
4
|
|
|
3
5
|
from pyinfra.api import FactBase
|
|
@@ -6,7 +8,8 @@ from pyinfra.api import FactBase
|
|
|
6
8
|
class GpgFactBase(FactBase):
|
|
7
9
|
abstract = True
|
|
8
10
|
|
|
9
|
-
requires_command
|
|
11
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
12
|
+
return "gpg"
|
|
10
13
|
|
|
11
14
|
key_record_type = "pub"
|
|
12
15
|
subkey_record_type = "sub"
|
pyinfra/facts/hardware.py
CHANGED
|
@@ -5,17 +5,17 @@ import re
|
|
|
5
5
|
from pyinfra.api import FactBase, ShortFactBase
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
class Cpus(FactBase):
|
|
8
|
+
class Cpus(FactBase[int]):
|
|
9
9
|
"""
|
|
10
10
|
Returns the number of CPUs on this server.
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
|
-
command
|
|
13
|
+
def command(self) -> str:
|
|
14
|
+
return "getconf NPROCESSORS_ONLN 2> /dev/null || getconf _NPROCESSORS_ONLN"
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
def process(output):
|
|
16
|
+
def process(self, output):
|
|
17
17
|
try:
|
|
18
|
-
return int(output[0])
|
|
18
|
+
return int(list(output)[0])
|
|
19
19
|
except ValueError:
|
|
20
20
|
pass
|
|
21
21
|
|
|
@@ -25,11 +25,13 @@ class Memory(FactBase):
|
|
|
25
25
|
Returns the memory installed in this server, in MB.
|
|
26
26
|
"""
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
def requires_command(self) -> str:
|
|
29
|
+
return "vmstat"
|
|
30
|
+
|
|
31
|
+
def command(self) -> str:
|
|
32
|
+
return "vmstat -s"
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
def process(output):
|
|
34
|
+
def process(self, output):
|
|
33
35
|
data = {}
|
|
34
36
|
|
|
35
37
|
for line in output:
|
|
@@ -75,10 +77,12 @@ class BlockDevices(FactBase):
|
|
|
75
77
|
}
|
|
76
78
|
"""
|
|
77
79
|
|
|
78
|
-
command = "df"
|
|
79
80
|
regex = r"([a-zA-Z0-9\/\-_]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]+)\s+([0-9]{1,3})%\s+([a-zA-Z\/0-9\-_]+)" # noqa: E501
|
|
80
81
|
default = dict
|
|
81
82
|
|
|
83
|
+
def command(self) -> str:
|
|
84
|
+
return "df"
|
|
85
|
+
|
|
82
86
|
def process(self, output):
|
|
83
87
|
devices = {}
|
|
84
88
|
|
|
@@ -99,157 +103,201 @@ class BlockDevices(FactBase):
|
|
|
99
103
|
return devices
|
|
100
104
|
|
|
101
105
|
|
|
102
|
-
nettools_1_regexes = [
|
|
103
|
-
(
|
|
104
|
-
r"^inet addr:([0-9\.]+).+Bcast:([0-9\.]+).+Mask:([0-9\.]+)$",
|
|
105
|
-
("ipv4", "address", "broadcast", "netmask"),
|
|
106
|
-
),
|
|
107
|
-
(
|
|
108
|
-
r"^inet6 addr: ([0-9a-z:]+)\/([0-9]+) Scope:Global",
|
|
109
|
-
("ipv6", "address", "mask_bits"),
|
|
110
|
-
),
|
|
111
|
-
]
|
|
112
|
-
|
|
113
|
-
nettools_2_regexes = [
|
|
114
|
-
(
|
|
115
|
-
r"^inet ([0-9\.]+)\s+netmask ([0-9\.fx]+)(?:\s+broadcast ([0-9\.]+))?$",
|
|
116
|
-
("ipv4", "address", "netmask", "broadcast"),
|
|
117
|
-
),
|
|
118
|
-
(
|
|
119
|
-
r"^inet6 ([0-9a-z:]+)\s+prefixlen ([0-9]+)",
|
|
120
|
-
("ipv6", "address", "mask_bits"),
|
|
121
|
-
),
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
iproute2_regexes = [
|
|
125
|
-
(
|
|
126
|
-
r"^inet ([0-9\.]+)\/([0-9]{1,2})(?:\s+brd ([0-9\.]+))?",
|
|
127
|
-
("ipv4", "address", "mask_bits", "broadcast"),
|
|
128
|
-
),
|
|
129
|
-
(
|
|
130
|
-
r"^inet6 ([0-9a-z:]+)\/([0-9]{1,3})",
|
|
131
|
-
("ipv6", "address", "mask_bits"),
|
|
132
|
-
),
|
|
133
|
-
]
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
def _parse_regexes(regexes, lines):
|
|
137
|
-
data: dict[str, dict] = {
|
|
138
|
-
"ipv4": {},
|
|
139
|
-
"ipv6": {},
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
for line in lines:
|
|
143
|
-
for regex, groups in regexes:
|
|
144
|
-
matches = re.match(regex, line)
|
|
145
|
-
if matches:
|
|
146
|
-
ip_data = {}
|
|
147
|
-
|
|
148
|
-
for i, group in enumerate(groups[1:]):
|
|
149
|
-
ip_data[group] = matches.group(i + 1)
|
|
150
|
-
|
|
151
|
-
if "mask_bits" in ip_data:
|
|
152
|
-
ip_data["mask_bits"] = int(ip_data["mask_bits"])
|
|
153
|
-
|
|
154
|
-
target_group = data[groups[0]]
|
|
155
|
-
if target_group.get("address"):
|
|
156
|
-
target_group.setdefault("additional_ips", []).append(ip_data)
|
|
157
|
-
else:
|
|
158
|
-
target_group.update(ip_data)
|
|
159
|
-
|
|
160
|
-
break
|
|
161
|
-
|
|
162
|
-
return data
|
|
163
|
-
|
|
164
|
-
|
|
165
106
|
class NetworkDevices(FactBase):
|
|
166
107
|
"""
|
|
167
108
|
Gets & returns a dict of network devices. See the ``ipv4_addresses`` and
|
|
168
109
|
``ipv6_addresses`` facts for easier-to-use shortcuts to get device addresses.
|
|
169
110
|
|
|
170
111
|
.. code:: python
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
"
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"address": "fe80::",
|
|
186
|
-
"mask_bits":
|
|
187
|
-
}
|
|
188
|
-
|
|
112
|
+
"enp1s0": {
|
|
113
|
+
"ether": "12:34:56:78:9A:BC",
|
|
114
|
+
"mtu": 1500,
|
|
115
|
+
"state": "UP",
|
|
116
|
+
"ipv4": {
|
|
117
|
+
"address": "192.168.1.100",
|
|
118
|
+
"mask_bits": 24,
|
|
119
|
+
"netmask": "255.255.255.0"
|
|
120
|
+
},
|
|
121
|
+
"ipv6": {
|
|
122
|
+
"address": "2001:db8:85a3::8a2e:370:7334",
|
|
123
|
+
"mask_bits": 64,
|
|
124
|
+
"additional_ips": [
|
|
125
|
+
{
|
|
126
|
+
"address": "fe80::1234:5678:9abc:def0",
|
|
127
|
+
"mask_bits": 64
|
|
128
|
+
}
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
"incusbr0": {
|
|
133
|
+
"ether": "DE:AD:BE:EF:CA:FE",
|
|
134
|
+
"mtu": 1500,
|
|
135
|
+
"state": "UP",
|
|
136
|
+
"ipv4": {
|
|
137
|
+
"address": "10.0.0.1",
|
|
138
|
+
"mask_bits": 24,
|
|
139
|
+
"netmask": "255.255.255.0"
|
|
189
140
|
},
|
|
141
|
+
"ipv6": {
|
|
142
|
+
"address": "fe80::dead:beef:cafe:babe",
|
|
143
|
+
"mask_bits": 64,
|
|
144
|
+
"additional_ips": [
|
|
145
|
+
{
|
|
146
|
+
"address": "2001:db8:1234:5678::1",
|
|
147
|
+
"mask_bits": 64
|
|
148
|
+
}
|
|
149
|
+
]
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"lo": {
|
|
153
|
+
"mtu": 65536,
|
|
154
|
+
"state": "UP",
|
|
155
|
+
"ipv6": {
|
|
156
|
+
"address": "::1",
|
|
157
|
+
"mask_bits": 128
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
"veth98806fd6": {
|
|
161
|
+
"ether": "AA:BB:CC:DD:EE:FF",
|
|
162
|
+
"mtu": 1500,
|
|
163
|
+
"state": "UP"
|
|
164
|
+
},
|
|
165
|
+
"vethda29df81": {
|
|
166
|
+
"ether": "11:22:33:44:55:66",
|
|
167
|
+
"mtu": 1500,
|
|
168
|
+
"state": "UP"
|
|
169
|
+
},
|
|
170
|
+
"wlo1": {
|
|
171
|
+
"ether": "77:88:99:AA:BB:CC",
|
|
172
|
+
"mtu": 1500,
|
|
173
|
+
"state": "UNKNOWN"
|
|
190
174
|
}
|
|
191
175
|
"""
|
|
192
176
|
|
|
193
|
-
command = "ip addr show 2> /dev/null || ifconfig"
|
|
194
177
|
default = dict
|
|
195
178
|
|
|
179
|
+
def command(self) -> str:
|
|
180
|
+
return "ip addr show 2> /dev/null || ifconfig -a"
|
|
181
|
+
|
|
196
182
|
# Definition of valid interface names for Linux:
|
|
197
183
|
# https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/net/core/dev.c?h=v5.1.3#n1020
|
|
198
|
-
_start_regexes = [
|
|
199
|
-
(
|
|
200
|
-
r"^([^/: \s]+)\s+Link encap:",
|
|
201
|
-
lambda lines: _parse_regexes(nettools_1_regexes, lines),
|
|
202
|
-
),
|
|
203
|
-
(
|
|
204
|
-
r"^([^/: \s]+): flags=",
|
|
205
|
-
lambda lines: _parse_regexes(nettools_2_regexes, lines),
|
|
206
|
-
),
|
|
207
|
-
(
|
|
208
|
-
r"^[0-9]+: ([^/: \s]+): ",
|
|
209
|
-
lambda lines: _parse_regexes(iproute2_regexes, lines),
|
|
210
|
-
),
|
|
211
|
-
]
|
|
212
|
-
|
|
213
184
|
def process(self, output):
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
185
|
+
def mask(value):
|
|
186
|
+
try:
|
|
187
|
+
if value.startswith("0x"):
|
|
188
|
+
mask_bits = bin(int(value, 16)).count("1")
|
|
189
|
+
else:
|
|
190
|
+
mask_bits = int(value)
|
|
191
|
+
netmask = ".".join(
|
|
192
|
+
str((0xFFFFFFFF << (32 - b) >> mask_bits) & 0xFF) for b in (24, 16, 8, 0)
|
|
193
|
+
)
|
|
194
|
+
except ValueError:
|
|
195
|
+
mask_bits = sum(bin(int(x)).count("1") for x in value.split("."))
|
|
196
|
+
netmask = value
|
|
220
197
|
|
|
221
|
-
|
|
222
|
-
line = line.strip()
|
|
198
|
+
return mask_bits, netmask
|
|
223
199
|
|
|
224
|
-
|
|
200
|
+
# Strip lines and merge them as a block of text
|
|
201
|
+
output = "\n".join(map(str.strip, output))
|
|
225
202
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
new_matches = re.match(regex, line)
|
|
203
|
+
# Splitting the output into sections per network device
|
|
204
|
+
device_sections = re.split(r"\n(?=\d+: [^\s/:]|[^\s/:]+:.*mtu )", output)
|
|
229
205
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
matched = True
|
|
206
|
+
# Dictionary to hold all device information
|
|
207
|
+
all_devices = {}
|
|
233
208
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
209
|
+
for section in device_sections:
|
|
210
|
+
# Extracting the device name
|
|
211
|
+
device_name_match = re.match(r"^(?:\d+: )?([^\s/:]+):", section)
|
|
212
|
+
if not device_name_match:
|
|
213
|
+
continue
|
|
214
|
+
device_name = device_name_match.group(1)
|
|
215
|
+
|
|
216
|
+
# Regular expressions to match different parts of the output
|
|
217
|
+
ether_re = re.compile(r"ether ([0-9A-Fa-f:]{17})")
|
|
218
|
+
mtu_re = re.compile(r"mtu (\d+)")
|
|
219
|
+
ipv4_re = (
|
|
220
|
+
# ip a
|
|
221
|
+
re.compile(
|
|
222
|
+
r"inet (?P<address>\d+\.\d+\.\d+\.\d+)/(?P<mask>\d+)(?: metric \d+)?(?: brd (?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
|
|
223
|
+
),
|
|
224
|
+
# ifconfig -a
|
|
225
|
+
re.compile(
|
|
226
|
+
r"inet (?P<address>\d+\.\d+\.\d+\.\d+)\s+netmask\s+(?P<mask>(?:\d+\.\d+\.\d+\.\d+)|(?:[0-9a-fA-FxX]+))(?:\s+broadcast\s+(?P<broadcast>\d+\.\d+\.\d+\.\d+))?" # noqa: E501
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
ipv6_re = (
|
|
230
|
+
# ip a
|
|
231
|
+
re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)/(?P<mask>\d+)"),
|
|
232
|
+
# ifconfig -a
|
|
233
|
+
re.compile(r"inet6\s+(?P<address>[0-9a-fA-F:]+)\s+prefixlen\s+(?P<mask>\d+)"),
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Parsing the output
|
|
237
|
+
ether = ether_re.search(section)
|
|
238
|
+
mtu = mtu_re.search(section)
|
|
239
|
+
|
|
240
|
+
# Building the result dictionary for the device
|
|
241
|
+
device_info = {}
|
|
242
|
+
if ether:
|
|
243
|
+
device_info["ether"] = ether.group(1)
|
|
244
|
+
if mtu:
|
|
245
|
+
device_info["mtu"] = int(mtu.group(1))
|
|
246
|
+
|
|
247
|
+
device_info["state"] = (
|
|
248
|
+
"UP" if "UP" in section else "DOWN" if "DOWN" in section else "UNKNOWN"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# IPv4 Addresses
|
|
252
|
+
ipv4_matches: list[re.Match[str]]
|
|
253
|
+
for ipv4_re_ in ipv4_re:
|
|
254
|
+
ipv4_matches = list(ipv4_re_.finditer(section))
|
|
255
|
+
if len(ipv4_matches):
|
|
256
|
+
break
|
|
238
257
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
258
|
+
if len(ipv4_matches):
|
|
259
|
+
ipv4_info = []
|
|
260
|
+
for ipv4 in ipv4_matches:
|
|
261
|
+
address = ipv4.group("address")
|
|
262
|
+
mask_value = ipv4.group("mask")
|
|
263
|
+
mask_bits, netmask = mask(mask_value)
|
|
264
|
+
try:
|
|
265
|
+
broadcast = ipv4.group("broadcast")
|
|
266
|
+
except IndexError:
|
|
267
|
+
broadcast = None
|
|
268
|
+
|
|
269
|
+
ipv4_info.append(
|
|
270
|
+
{
|
|
271
|
+
"address": address,
|
|
272
|
+
"mask_bits": mask_bits,
|
|
273
|
+
"netmask": netmask,
|
|
274
|
+
"broadcast": broadcast,
|
|
275
|
+
},
|
|
276
|
+
)
|
|
277
|
+
device_info["ipv4"] = ipv4_info[0]
|
|
278
|
+
if len(ipv4_matches) > 1:
|
|
279
|
+
device_info["ipv4"]["additional_ips"] = ipv4_info[1:] # type: ignore[index]
|
|
280
|
+
|
|
281
|
+
# IPv6 Addresses
|
|
282
|
+
ipv6_matches: list[re.Match[str]]
|
|
283
|
+
for ipv6_re_ in ipv6_re:
|
|
284
|
+
ipv6_matches = list(ipv6_re_.finditer(section))
|
|
285
|
+
if ipv6_matches:
|
|
242
286
|
break
|
|
243
287
|
|
|
244
|
-
if
|
|
245
|
-
|
|
288
|
+
if len(ipv6_matches):
|
|
289
|
+
ipv6_info = []
|
|
290
|
+
for ipv6 in ipv6_matches:
|
|
291
|
+
address = ipv6.group("address")
|
|
292
|
+
mask_bits = ipv6.group("mask")
|
|
293
|
+
ipv6_info.append({"address": address, "mask_bits": int(mask_bits)})
|
|
294
|
+
device_info["ipv6"] = ipv6_info[0]
|
|
295
|
+
if len(ipv6_matches) > 1:
|
|
296
|
+
device_info["ipv6"]["additional_ips"] = ipv6_info[1:] # type: ignore[index]
|
|
246
297
|
|
|
247
|
-
|
|
248
|
-
if matches:
|
|
249
|
-
assert handler is not None
|
|
250
|
-
devices[matches.group(1)] = handler(line_buffer)
|
|
298
|
+
all_devices[device_name] = device_info
|
|
251
299
|
|
|
252
|
-
return
|
|
300
|
+
return all_devices
|
|
253
301
|
|
|
254
302
|
|
|
255
303
|
class Ipv4Addrs(ShortFactBase):
|
|
@@ -276,7 +324,7 @@ class Ipv4Addrs(ShortFactBase):
|
|
|
276
324
|
ips = []
|
|
277
325
|
|
|
278
326
|
ip_details = details.get(self.ip_type)
|
|
279
|
-
if not ip_details:
|
|
327
|
+
if not ip_details or not ip_details.get("address"):
|
|
280
328
|
continue
|
|
281
329
|
|
|
282
330
|
ips.append(ip_details["address"])
|
|
@@ -335,7 +383,7 @@ class Ipv4Addresses(ShortFactBase):
|
|
|
335
383
|
|
|
336
384
|
for interface, details in data.items():
|
|
337
385
|
ip_details = details.get(self.ip_type)
|
|
338
|
-
if not ip_details:
|
|
386
|
+
if not ip_details or not ip_details.get("address"):
|
|
339
387
|
continue # pragma: no cover
|
|
340
388
|
|
|
341
389
|
addresses[interface] = ip_details["address"]
|
pyinfra/facts/launchd.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from pyinfra.api import FactBase
|
|
2
4
|
|
|
3
5
|
|
|
@@ -6,8 +8,11 @@ class LaunchdStatus(FactBase):
|
|
|
6
8
|
Returns a dict of name -> status for launchd managed services.
|
|
7
9
|
"""
|
|
8
10
|
|
|
9
|
-
command
|
|
10
|
-
|
|
11
|
+
def command(self) -> str:
|
|
12
|
+
return "launchctl list"
|
|
13
|
+
|
|
14
|
+
def requires_command(self) -> str:
|
|
15
|
+
return "launchctl"
|
|
11
16
|
|
|
12
17
|
default = dict
|
|
13
18
|
|
pyinfra/facts/lxd.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
|
|
@@ -8,11 +10,15 @@ class LxdContainers(FactBase):
|
|
|
8
10
|
Returns a list of running LXD containers
|
|
9
11
|
"""
|
|
10
12
|
|
|
11
|
-
command
|
|
12
|
-
|
|
13
|
+
def command(self) -> str:
|
|
14
|
+
return "lxc list --format json --fast"
|
|
15
|
+
|
|
16
|
+
def requires_command(self) -> str:
|
|
17
|
+
return "lxc"
|
|
13
18
|
|
|
14
19
|
default = list
|
|
15
20
|
|
|
16
21
|
def process(self, output):
|
|
22
|
+
output = list(output)
|
|
17
23
|
assert len(output) == 1
|
|
18
24
|
return json.loads(output[0])
|
pyinfra/facts/mysql.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from collections import defaultdict
|
|
3
5
|
|
|
@@ -8,11 +10,11 @@ from .util.databases import parse_columns_and_rows
|
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def make_mysql_command(
|
|
11
|
-
database=None,
|
|
12
|
-
user=None,
|
|
13
|
-
password=None,
|
|
14
|
-
host=None,
|
|
15
|
-
port=None,
|
|
13
|
+
database: str | None = None,
|
|
14
|
+
user: str | None = None,
|
|
15
|
+
password: str | None = None,
|
|
16
|
+
host: str | None = None,
|
|
17
|
+
port: int | None = None,
|
|
16
18
|
executable="mysql",
|
|
17
19
|
):
|
|
18
20
|
target_bits = [executable]
|
|
@@ -37,7 +39,11 @@ def make_mysql_command(
|
|
|
37
39
|
return StringCommand(*target_bits)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def make_execute_mysql_command(
|
|
42
|
+
def make_execute_mysql_command(
|
|
43
|
+
command: str | StringCommand,
|
|
44
|
+
ignore_errors=False,
|
|
45
|
+
**mysql_kwargs,
|
|
46
|
+
):
|
|
41
47
|
commands_bits = [
|
|
42
48
|
make_mysql_command(**mysql_kwargs),
|
|
43
49
|
"-Be",
|
|
@@ -54,9 +60,11 @@ class MysqlFactBase(FactBase):
|
|
|
54
60
|
abstract = True
|
|
55
61
|
|
|
56
62
|
mysql_command: str
|
|
57
|
-
requires_command = "mysql"
|
|
58
63
|
ignore_errors = False
|
|
59
64
|
|
|
65
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
66
|
+
return "mysql"
|
|
67
|
+
|
|
60
68
|
def command(
|
|
61
69
|
self,
|
|
62
70
|
# Details for speaking to MySQL via `mysql` CLI via `mysql` CLI
|
|
@@ -64,7 +72,7 @@ class MysqlFactBase(FactBase):
|
|
|
64
72
|
mysql_password=None,
|
|
65
73
|
mysql_host=None,
|
|
66
74
|
mysql_port=None,
|
|
67
|
-
):
|
|
75
|
+
) -> StringCommand:
|
|
68
76
|
return make_execute_mysql_command(
|
|
69
77
|
self.mysql_command,
|
|
70
78
|
ignore_errors=self.ignore_errors,
|
|
@@ -128,8 +136,7 @@ class MysqlUsers(MysqlFactBase):
|
|
|
128
136
|
default = dict
|
|
129
137
|
mysql_command = "SELECT * FROM mysql.user"
|
|
130
138
|
|
|
131
|
-
|
|
132
|
-
def process(output):
|
|
139
|
+
def process(self, output):
|
|
133
140
|
rows = parse_columns_and_rows(output, "\t")
|
|
134
141
|
|
|
135
142
|
users = {}
|
|
@@ -188,7 +195,7 @@ class MysqlUserGrants(MysqlFactBase):
|
|
|
188
195
|
# Ignore errors as SHOW GRANTS will error if the user does not exist
|
|
189
196
|
ignore_errors = True
|
|
190
197
|
|
|
191
|
-
def command(
|
|
198
|
+
def command( # type: ignore[override]
|
|
192
199
|
self,
|
|
193
200
|
user,
|
|
194
201
|
hostname="localhost",
|
|
@@ -197,7 +204,7 @@ class MysqlUserGrants(MysqlFactBase):
|
|
|
197
204
|
mysql_password=None,
|
|
198
205
|
mysql_host=None,
|
|
199
206
|
mysql_port=None,
|
|
200
|
-
):
|
|
207
|
+
) -> StringCommand:
|
|
201
208
|
self.mysql_command = 'SHOW GRANTS FOR "{0}"@"{1}"'.format(user, hostname)
|
|
202
209
|
|
|
203
210
|
return super().command(
|
pyinfra/facts/npm.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# encoding: utf8
|
|
2
|
+
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
from pyinfra.api import FactBase
|
|
4
5
|
|
|
@@ -20,7 +21,8 @@ class NpmPackages(FactBase):
|
|
|
20
21
|
|
|
21
22
|
default = dict
|
|
22
23
|
|
|
23
|
-
requires_command =
|
|
24
|
+
def requires_command(self, directory=None) -> str:
|
|
25
|
+
return "npm"
|
|
24
26
|
|
|
25
27
|
def command(self, directory=None):
|
|
26
28
|
if directory:
|
pyinfra/facts/openrc.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
|
|
@@ -9,7 +11,6 @@ class OpenrcStatus(FactBase):
|
|
|
9
11
|
"""
|
|
10
12
|
|
|
11
13
|
default = dict
|
|
12
|
-
requires_command = "rc-status"
|
|
13
14
|
regex = (
|
|
14
15
|
r"\s+([a-zA-Z0-9\-_]+)"
|
|
15
16
|
r"\s+\[\s+"
|
|
@@ -19,6 +20,9 @@ class OpenrcStatus(FactBase):
|
|
|
19
20
|
r"\s+\]"
|
|
20
21
|
)
|
|
21
22
|
|
|
23
|
+
def requires_command(self, runlevel="default") -> str:
|
|
24
|
+
return "rc-status"
|
|
25
|
+
|
|
22
26
|
def command(self, runlevel="default"):
|
|
23
27
|
return "rc-status {0}".format(runlevel)
|
|
24
28
|
|
|
@@ -39,7 +43,9 @@ class OpenrcEnabled(FactBase):
|
|
|
39
43
|
"""
|
|
40
44
|
|
|
41
45
|
default = dict
|
|
42
|
-
|
|
46
|
+
|
|
47
|
+
def requires_command(self, runlevel="default") -> str:
|
|
48
|
+
return "rc-update"
|
|
43
49
|
|
|
44
50
|
def command(self, runlevel="default"):
|
|
45
51
|
self.runlevel = runlevel
|