pyinfra 0.11.dev3__py3-none-any.whl → 3.6__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/__init__.py +9 -12
- pyinfra/__main__.py +4 -0
- pyinfra/api/__init__.py +19 -3
- pyinfra/api/arguments.py +413 -0
- pyinfra/api/arguments_typed.py +79 -0
- pyinfra/api/command.py +274 -0
- pyinfra/api/config.py +222 -28
- pyinfra/api/connect.py +33 -13
- pyinfra/api/connectors.py +27 -0
- pyinfra/api/deploy.py +65 -66
- pyinfra/api/exceptions.py +73 -18
- pyinfra/api/facts.py +267 -200
- pyinfra/api/host.py +416 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/metadata.py +69 -0
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +309 -369
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +405 -0
- pyinfra/connectors/dockerssh.py +297 -0
- pyinfra/connectors/local.py +238 -0
- pyinfra/connectors/scp/__init__.py +1 -0
- pyinfra/connectors/scp/client.py +204 -0
- pyinfra/connectors/ssh.py +727 -0
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +309 -0
- pyinfra/connectors/sshuserclient/config.py +102 -0
- pyinfra/connectors/terraform.py +135 -0
- pyinfra/connectors/util.py +417 -0
- pyinfra/connectors/vagrant.py +183 -0
- pyinfra/context.py +145 -0
- pyinfra/facts/__init__.py +7 -6
- pyinfra/facts/apk.py +22 -7
- pyinfra/facts/apt.py +117 -60
- pyinfra/facts/brew.py +100 -15
- pyinfra/facts/bsdinit.py +23 -0
- pyinfra/facts/cargo.py +37 -0
- pyinfra/facts/choco.py +47 -0
- pyinfra/facts/crontab.py +195 -0
- pyinfra/facts/deb.py +94 -0
- pyinfra/facts/dnf.py +48 -0
- pyinfra/facts/docker.py +96 -23
- pyinfra/facts/efibootmgr.py +113 -0
- pyinfra/facts/files.py +629 -58
- pyinfra/facts/flatpak.py +77 -0
- pyinfra/facts/freebsd.py +70 -0
- pyinfra/facts/gem.py +19 -6
- pyinfra/facts/git.py +59 -14
- pyinfra/facts/gpg.py +150 -0
- pyinfra/facts/hardware.py +313 -167
- pyinfra/facts/iptables.py +72 -62
- pyinfra/facts/launchd.py +44 -0
- pyinfra/facts/lxd.py +17 -4
- pyinfra/facts/mysql.py +122 -86
- pyinfra/facts/npm.py +17 -9
- pyinfra/facts/openrc.py +71 -0
- pyinfra/facts/opkg.py +246 -0
- pyinfra/facts/pacman.py +50 -7
- pyinfra/facts/pip.py +24 -7
- pyinfra/facts/pipx.py +82 -0
- pyinfra/facts/pkg.py +15 -6
- pyinfra/facts/pkgin.py +35 -0
- pyinfra/facts/podman.py +54 -0
- pyinfra/facts/postgres.py +178 -0
- pyinfra/facts/postgresql.py +6 -147
- pyinfra/facts/rpm.py +105 -0
- pyinfra/facts/runit.py +77 -0
- pyinfra/facts/selinux.py +161 -0
- pyinfra/facts/server.py +762 -285
- pyinfra/facts/snap.py +88 -0
- pyinfra/facts/systemd.py +139 -0
- pyinfra/facts/sysvinit.py +59 -0
- pyinfra/facts/upstart.py +35 -0
- pyinfra/facts/util/__init__.py +17 -0
- pyinfra/facts/util/databases.py +4 -6
- pyinfra/facts/util/packaging.py +37 -6
- pyinfra/facts/util/units.py +30 -0
- pyinfra/facts/util/win_files.py +99 -0
- pyinfra/facts/vzctl.py +20 -13
- pyinfra/facts/xbps.py +35 -0
- pyinfra/facts/yum.py +34 -40
- pyinfra/facts/zfs.py +77 -0
- pyinfra/facts/zypper.py +42 -0
- pyinfra/local.py +45 -83
- pyinfra/operations/__init__.py +12 -0
- pyinfra/operations/apk.py +99 -0
- pyinfra/operations/apt.py +496 -0
- pyinfra/operations/brew.py +232 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +194 -0
- pyinfra/operations/dnf.py +213 -0
- pyinfra/operations/docker.py +492 -0
- pyinfra/operations/files.py +2014 -0
- pyinfra/operations/flatpak.py +95 -0
- pyinfra/operations/freebsd/__init__.py +12 -0
- pyinfra/operations/freebsd/freebsd_update.py +70 -0
- pyinfra/operations/freebsd/pkg.py +219 -0
- pyinfra/operations/freebsd/service.py +116 -0
- pyinfra/operations/freebsd/sysrc.py +92 -0
- pyinfra/operations/gem.py +48 -0
- pyinfra/operations/git.py +420 -0
- pyinfra/operations/iptables.py +312 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +69 -0
- pyinfra/operations/mysql.py +610 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +89 -0
- pyinfra/operations/pacman.py +82 -0
- pyinfra/operations/pip.py +206 -0
- pyinfra/operations/pipx.py +103 -0
- pyinfra/operations/pkg.py +71 -0
- pyinfra/operations/pkgin.py +92 -0
- pyinfra/operations/postgres.py +437 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +41 -0
- pyinfra/operations/python.py +73 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +190 -0
- pyinfra/operations/server.py +1100 -0
- pyinfra/operations/snap.py +118 -0
- pyinfra/operations/ssh.py +217 -0
- pyinfra/operations/systemd.py +150 -0
- pyinfra/operations/sysvinit.py +142 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +407 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +338 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +78 -0
- pyinfra/operations/yum.py +213 -0
- pyinfra/operations/zfs.py +176 -0
- pyinfra/operations/zypper.py +193 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.6.dist-info/METADATA +142 -0
- pyinfra-3.6.dist-info/RECORD +160 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
- pyinfra-3.6.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +793 -0
- pyinfra_cli/commands.py +66 -0
- pyinfra_cli/exceptions.py +155 -65
- pyinfra_cli/inventory.py +233 -89
- pyinfra_cli/log.py +39 -43
- pyinfra_cli/main.py +26 -495
- pyinfra_cli/prints.py +215 -156
- pyinfra_cli/util.py +172 -105
- pyinfra_cli/virtualenv.py +25 -20
- pyinfra/api/connectors/__init__.py +0 -21
- pyinfra/api/connectors/ansible.py +0 -99
- pyinfra/api/connectors/docker.py +0 -178
- pyinfra/api/connectors/local.py +0 -169
- pyinfra/api/connectors/ssh.py +0 -402
- pyinfra/api/connectors/sshuserclient/client.py +0 -105
- pyinfra/api/connectors/sshuserclient/config.py +0 -90
- pyinfra/api/connectors/util.py +0 -63
- pyinfra/api/connectors/vagrant.py +0 -155
- pyinfra/facts/init.py +0 -176
- pyinfra/facts/util/files.py +0 -102
- pyinfra/hook.py +0 -41
- pyinfra/modules/__init__.py +0 -11
- pyinfra/modules/apk.py +0 -64
- pyinfra/modules/apt.py +0 -272
- pyinfra/modules/brew.py +0 -122
- pyinfra/modules/files.py +0 -711
- pyinfra/modules/gem.py +0 -30
- pyinfra/modules/git.py +0 -115
- pyinfra/modules/init.py +0 -344
- pyinfra/modules/iptables.py +0 -271
- pyinfra/modules/lxd.py +0 -45
- pyinfra/modules/mysql.py +0 -347
- pyinfra/modules/npm.py +0 -47
- pyinfra/modules/pacman.py +0 -60
- pyinfra/modules/pip.py +0 -99
- pyinfra/modules/pkg.py +0 -43
- pyinfra/modules/postgresql.py +0 -245
- pyinfra/modules/puppet.py +0 -20
- pyinfra/modules/python.py +0 -37
- pyinfra/modules/server.py +0 -524
- pyinfra/modules/ssh.py +0 -150
- pyinfra/modules/util/files.py +0 -52
- pyinfra/modules/util/packaging.py +0 -118
- pyinfra/modules/vzctl.py +0 -133
- pyinfra/modules/yum.py +0 -171
- pyinfra/pseudo_modules.py +0 -64
- pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
- pyinfra-0.11.dev3.dist-info/METADATA +0 -135
- pyinfra-0.11.dev3.dist-info/RECORD +0 -95
- pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
- pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
- pyinfra_cli/__main__.py +0 -40
- pyinfra_cli/config.py +0 -92
- /pyinfra/{modules/util → connectors}/__init__.py +0 -0
- /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
pyinfra/facts/snap.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from pyinfra.api import FactBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SnapBaseFact(FactBase):
|
|
11
|
+
abstract = True
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
15
|
+
return "snap"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SnapPackage(SnapBaseFact):
|
|
19
|
+
"""
|
|
20
|
+
Returns information for an installed snap package
|
|
21
|
+
|
|
22
|
+
.. code:: python
|
|
23
|
+
|
|
24
|
+
{
|
|
25
|
+
"name": "lxd",
|
|
26
|
+
"publisher": "Canonical✓",
|
|
27
|
+
"snap-id": "J60k4JY0HppjwOjW8dZdYc8obXKxujRu",
|
|
28
|
+
"channel": "4.0/stable",
|
|
29
|
+
"version": "4.0.9"
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
default = dict
|
|
34
|
+
_regexes = {
|
|
35
|
+
"name": "^name:[ ]+(.*)",
|
|
36
|
+
"publisher": r"^publisher:[ ]+(.*)",
|
|
37
|
+
"snap-id": r"^snap-id:[ ]+(.*)",
|
|
38
|
+
"channel": r"^tracking:[ ]+([\w\d.-]+/[\w\d.-]+)[/]?.*$",
|
|
39
|
+
"version": r"^installed:[ ]+([\w\d.-]+).*$",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def command(self, package):
|
|
44
|
+
return f"snap info {package}"
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def process(self, output):
|
|
48
|
+
data = {}
|
|
49
|
+
for line in output:
|
|
50
|
+
for regex_name, regex in self._regexes.items():
|
|
51
|
+
matches = re.match(regex, line)
|
|
52
|
+
if matches:
|
|
53
|
+
data[regex_name] = matches.group(1)
|
|
54
|
+
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SnapPackages(SnapBaseFact):
|
|
59
|
+
"""
|
|
60
|
+
Returns a list of installed snap packages:
|
|
61
|
+
|
|
62
|
+
.. code:: python
|
|
63
|
+
|
|
64
|
+
[
|
|
65
|
+
"core",
|
|
66
|
+
"core18",
|
|
67
|
+
"core20",
|
|
68
|
+
"lxd",
|
|
69
|
+
"snapd"
|
|
70
|
+
]
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
default = list
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def command(self) -> str:
|
|
77
|
+
return "snap list"
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def process(self, output):
|
|
81
|
+
# Discard header output line from snap list command
|
|
82
|
+
# 'snap list' command example output lines:
|
|
83
|
+
# $ snap list
|
|
84
|
+
# Name Version Rev Tracking Publisher Notes
|
|
85
|
+
# core 16-2.57.2 13886 latest/stable canonical✓ core
|
|
86
|
+
# lxd 4.0.9 22753 4.0/stable/… canonical✓ -
|
|
87
|
+
#
|
|
88
|
+
return [snap.split()[0] for snap in output[1:]]
|
pyinfra/facts/systemd.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Dict, Iterable
|
|
5
|
+
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
8
|
+
from pyinfra.api import FactBase, QuoteString, StringCommand
|
|
9
|
+
|
|
10
|
+
# Valid unit names consist of a "name prefix" and a dot and a suffix specifying the unit type.
|
|
11
|
+
# The "unit prefix" must consist of one or more valid characters
|
|
12
|
+
# (ASCII letters, digits, ":", "-", "_", ".", and "\").
|
|
13
|
+
# The total length of the unit name including the suffix must not exceed 256 characters.
|
|
14
|
+
# The type suffix must be one of
|
|
15
|
+
# ".service", ".socket", ".device", ".mount", ".automount",
|
|
16
|
+
# ".swap", ".target", ".path", ".timer", ".slice", or ".scope".
|
|
17
|
+
# Units names can be parameterized by a single argument called the "instance name".
|
|
18
|
+
# A template unit must have a single "@" at the end of the name (right before the type suffix).
|
|
19
|
+
# The name of the full unit is formed by inserting the instance name
|
|
20
|
+
# between "@" and the unit type suffix.
|
|
21
|
+
SYSTEMD_UNIT_NAME_REGEX = (
|
|
22
|
+
r"[a-zA-Z0-9\:\-\_\.\\\@]+\."
|
|
23
|
+
r"(?:service|socket|device|mount|automount|swap|target|path|timer|slice|scope)"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _make_systemctl_cmd(user_mode=False, machine=None, user_name=None):
|
|
28
|
+
# base command for normal and user mode
|
|
29
|
+
systemctl_cmd = ["systemctl --user"] if user_mode else ["systemctl"]
|
|
30
|
+
|
|
31
|
+
# add user and machine flag if given in args
|
|
32
|
+
if machine is not None:
|
|
33
|
+
if user_name is not None:
|
|
34
|
+
systemctl_cmd.append("--machine={1}@{0}".format(machine, user_name))
|
|
35
|
+
else:
|
|
36
|
+
systemctl_cmd.append("--machine={0}".format(machine))
|
|
37
|
+
elif user_name is not None:
|
|
38
|
+
# If only the user is given, assume that the connection should be made to the local machine
|
|
39
|
+
systemctl_cmd.append("--machine={0}@.host".format(user_name))
|
|
40
|
+
|
|
41
|
+
return StringCommand(*systemctl_cmd)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class SystemdStatus(FactBase[Dict[str, bool]]):
|
|
45
|
+
"""
|
|
46
|
+
Returns a dictionary map of systemd units to booleans indicating whether they are active.
|
|
47
|
+
|
|
48
|
+
+ user_mode: whether to use user mode
|
|
49
|
+
+ machine: machine name
|
|
50
|
+
|
|
51
|
+
.. code:: python
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
"ssh.service": True,
|
|
55
|
+
"containerd.service": True,
|
|
56
|
+
"apt-daily.timer": False,
|
|
57
|
+
}
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
@override
|
|
61
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
62
|
+
return "systemctl"
|
|
63
|
+
|
|
64
|
+
default = dict
|
|
65
|
+
|
|
66
|
+
state_key = "SubState"
|
|
67
|
+
state_values = ["running", "waiting", "exited", "listening", "mounted"]
|
|
68
|
+
|
|
69
|
+
@override
|
|
70
|
+
def command(
|
|
71
|
+
self,
|
|
72
|
+
user_mode: bool = False,
|
|
73
|
+
machine: str | None = None,
|
|
74
|
+
user_name: str | None = None,
|
|
75
|
+
services: str | list[str] | None = None,
|
|
76
|
+
) -> StringCommand:
|
|
77
|
+
fact_cmd = _make_systemctl_cmd(
|
|
78
|
+
user_mode=user_mode,
|
|
79
|
+
machine=machine,
|
|
80
|
+
user_name=user_name,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if services is None:
|
|
84
|
+
service_strs = [QuoteString("*")]
|
|
85
|
+
elif isinstance(services, str):
|
|
86
|
+
service_strs = [QuoteString(services)]
|
|
87
|
+
elif isinstance(services, Iterable):
|
|
88
|
+
service_strs = [QuoteString(s) for s in services]
|
|
89
|
+
|
|
90
|
+
return StringCommand(
|
|
91
|
+
fact_cmd,
|
|
92
|
+
"show",
|
|
93
|
+
"--all",
|
|
94
|
+
"--property",
|
|
95
|
+
"Id",
|
|
96
|
+
"--property",
|
|
97
|
+
self.state_key,
|
|
98
|
+
*service_strs,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
@override
|
|
102
|
+
def process(self, output) -> Dict[str, bool]:
|
|
103
|
+
services: Dict[str, bool] = {}
|
|
104
|
+
|
|
105
|
+
current_unit = None
|
|
106
|
+
for line in output:
|
|
107
|
+
line = line.strip()
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
key, value = line.split("=", 1)
|
|
111
|
+
except ValueError:
|
|
112
|
+
current_unit = None # reset current_unit just in case
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
if key == "Id" and re.match(SYSTEMD_UNIT_NAME_REGEX, value):
|
|
116
|
+
current_unit = value
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
if key == self.state_key and current_unit:
|
|
120
|
+
services[current_unit] = value in self.state_values
|
|
121
|
+
|
|
122
|
+
return services
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SystemdEnabled(SystemdStatus):
|
|
126
|
+
"""
|
|
127
|
+
Returns a dictionary map of systemd units to booleans indicating whether they are enabled.
|
|
128
|
+
|
|
129
|
+
.. code:: python
|
|
130
|
+
|
|
131
|
+
{
|
|
132
|
+
"ssh.service": True,
|
|
133
|
+
"containerd.service": True,
|
|
134
|
+
"apt-daily.timer": False,
|
|
135
|
+
}
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
state_key = "UnitFileState"
|
|
139
|
+
state_values = ["enabled", "static"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
8
|
+
from pyinfra.api import FactBase
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class InitdStatus(FactBase):
|
|
12
|
+
"""
|
|
13
|
+
Low level check for every /etc/init.d/* script. Unfortunately many of these
|
|
14
|
+
misbehave and return exit status 0 while also displaying the help info/not
|
|
15
|
+
offering status support.
|
|
16
|
+
|
|
17
|
+
Returns a dict of name -> status.
|
|
18
|
+
|
|
19
|
+
Expected codes found at:
|
|
20
|
+
http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
def command(self) -> str:
|
|
25
|
+
return """
|
|
26
|
+
for SERVICE in `ls /etc/init.d/`; do
|
|
27
|
+
_=`cat /etc/init.d/$SERVICE | grep "### BEGIN INIT INFO"`
|
|
28
|
+
|
|
29
|
+
if [ "$?" = "0" ]; then
|
|
30
|
+
STATUS=`/etc/init.d/$SERVICE status`
|
|
31
|
+
echo "$SERVICE=$?"
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
regex = r"([a-zA-Z0-9\-]+)=([0-9]+)"
|
|
37
|
+
default = dict
|
|
38
|
+
|
|
39
|
+
@override
|
|
40
|
+
def process(self, output) -> dict[str, Optional[bool]]:
|
|
41
|
+
services: dict[str, Optional[bool]] = {}
|
|
42
|
+
|
|
43
|
+
for line in output:
|
|
44
|
+
matches = re.match(self.regex, line)
|
|
45
|
+
if matches:
|
|
46
|
+
intstatus = int(matches.group(2))
|
|
47
|
+
status: Optional[bool] = None
|
|
48
|
+
|
|
49
|
+
# Exit code 0 = OK/running
|
|
50
|
+
if intstatus == 0:
|
|
51
|
+
status = True
|
|
52
|
+
|
|
53
|
+
# Exit codes 1-3 = DOWN/not running
|
|
54
|
+
elif intstatus < 4:
|
|
55
|
+
status = False
|
|
56
|
+
|
|
57
|
+
services[matches.group(1)] = status
|
|
58
|
+
|
|
59
|
+
return services
|
pyinfra/facts/upstart.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from pyinfra.api import FactBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class UpstartStatus(FactBase):
|
|
11
|
+
"""
|
|
12
|
+
Returns a dict of name -> status for upstart managed services.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@override
|
|
16
|
+
def requires_command(self) -> str:
|
|
17
|
+
return "initctl"
|
|
18
|
+
|
|
19
|
+
regex = r"^([a-z\-]+) [a-z]+\/([a-z]+)"
|
|
20
|
+
default = dict
|
|
21
|
+
|
|
22
|
+
@override
|
|
23
|
+
def command(self):
|
|
24
|
+
return "initctl list"
|
|
25
|
+
|
|
26
|
+
@override
|
|
27
|
+
def process(self, output):
|
|
28
|
+
services = {}
|
|
29
|
+
|
|
30
|
+
for line in output:
|
|
31
|
+
matches = re.match(self.regex, line)
|
|
32
|
+
if matches:
|
|
33
|
+
services[matches.group(1)] = matches.group(2) == "running"
|
|
34
|
+
|
|
35
|
+
return services
|
pyinfra/facts/util/__init__.py
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def make_cat_files_command(*filenames: Iterable[str]) -> str:
|
|
5
|
+
commands = []
|
|
6
|
+
|
|
7
|
+
for filename in filenames:
|
|
8
|
+
if "*" in filename:
|
|
9
|
+
# There's no way to test against a glob expression, so accept anything here
|
|
10
|
+
commands.append("cat {0} || true".format(filename))
|
|
11
|
+
else:
|
|
12
|
+
commands.append("! test -f {0} || cat {0}".format(filename))
|
|
13
|
+
|
|
14
|
+
if len(commands) > 1: # if we have multiple, wrap them
|
|
15
|
+
commands = ["({0})".format(command) for command in commands]
|
|
16
|
+
|
|
17
|
+
return " && ".join(commands)
|
pyinfra/facts/util/databases.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
def _remove_prefix(title, remove_prefix):
|
|
2
2
|
if title.startswith(remove_prefix):
|
|
3
|
-
return title[len(remove_prefix):]
|
|
3
|
+
return title[len(remove_prefix) :]
|
|
4
4
|
|
|
5
5
|
return title
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def parse_columns_and_rows(
|
|
9
|
-
lines,
|
|
9
|
+
lines,
|
|
10
|
+
delimiter,
|
|
10
11
|
remove_column_prefix=None,
|
|
11
12
|
title_parser=None,
|
|
12
13
|
):
|
|
@@ -17,10 +18,7 @@ def parse_columns_and_rows(
|
|
|
17
18
|
titles = [title_parser(title) for title in titles]
|
|
18
19
|
|
|
19
20
|
if remove_column_prefix:
|
|
20
|
-
titles = [
|
|
21
|
-
_remove_prefix(title, remove_column_prefix)
|
|
22
|
-
for title in titles
|
|
23
|
-
]
|
|
21
|
+
titles = [_remove_prefix(title, remove_column_prefix) for title in titles]
|
|
24
22
|
|
|
25
23
|
rows = []
|
|
26
24
|
|
pyinfra/facts/util/packaging.py
CHANGED
|
@@ -1,19 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
4
|
+
from typing import Iterable
|
|
2
5
|
|
|
3
6
|
|
|
4
|
-
def parse_packages(regex, output,
|
|
5
|
-
packages = {}
|
|
7
|
+
def parse_packages(regex: str, output: Iterable[str]) -> dict[str, set[str]]:
|
|
8
|
+
packages: dict[str, set[str]] = {}
|
|
6
9
|
|
|
7
10
|
for line in output:
|
|
8
11
|
matches = re.match(regex, line)
|
|
9
12
|
|
|
10
13
|
if matches:
|
|
11
|
-
# Sort out name
|
|
12
14
|
name = matches.group(1)
|
|
13
|
-
if lower:
|
|
14
|
-
name = name.lower()
|
|
15
|
-
|
|
16
15
|
packages.setdefault(name, set())
|
|
17
16
|
packages[name].add(matches.group(2))
|
|
18
17
|
|
|
19
18
|
return packages
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _parse_yum_or_zypper_repositories(output):
|
|
22
|
+
repos = []
|
|
23
|
+
|
|
24
|
+
current_repo: dict[str, str] = {}
|
|
25
|
+
for line in output:
|
|
26
|
+
line = line.strip()
|
|
27
|
+
if not line or line.startswith("#"):
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
if line.startswith("["):
|
|
31
|
+
if current_repo:
|
|
32
|
+
repos.append(current_repo)
|
|
33
|
+
current_repo = {}
|
|
34
|
+
|
|
35
|
+
current_repo["repoid"] = line[1:-1]
|
|
36
|
+
current_repo["name"] = line[1:-1]
|
|
37
|
+
|
|
38
|
+
if current_repo and "=" in line:
|
|
39
|
+
key, value = re.split(r"\s*=\s*", line, maxsplit=1)
|
|
40
|
+
current_repo[key] = value
|
|
41
|
+
|
|
42
|
+
if current_repo:
|
|
43
|
+
repos.append(current_repo)
|
|
44
|
+
|
|
45
|
+
return repos
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
parse_yum_repositories = _parse_yum_or_zypper_repositories
|
|
49
|
+
|
|
50
|
+
parse_zypper_repositories = _parse_yum_or_zypper_repositories
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# from https://stackoverflow.com/a/60708339, but with a few modifications
|
|
2
|
+
from __future__ import annotations # for | in type hints
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
units = {
|
|
7
|
+
"B": 1,
|
|
8
|
+
"KB": 10**3,
|
|
9
|
+
"MB": 10**6,
|
|
10
|
+
"GB": 10**9,
|
|
11
|
+
"TB": 10**12,
|
|
12
|
+
"KIB": 2**10,
|
|
13
|
+
"MIB": 2**20,
|
|
14
|
+
"GIB": 2**30,
|
|
15
|
+
"TIB": 2**40,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def parse_human_readable_size(size: str) -> int:
|
|
20
|
+
size = size.upper()
|
|
21
|
+
if not re.match(r" ", size):
|
|
22
|
+
size = re.sub(r"([KMGT]?I?[B])", r" \1", size)
|
|
23
|
+
number, unit = [string.strip() for string in size.split()]
|
|
24
|
+
return int(float(number) * units[unit])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_size(size: str | int) -> int:
|
|
28
|
+
if isinstance(size, int):
|
|
29
|
+
return size
|
|
30
|
+
return parse_human_readable_size(size)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
WIN_LS_REGEX = re.compile(
|
|
5
|
+
(
|
|
6
|
+
# filetype and mode
|
|
7
|
+
r"^([darhsl\-]{6})\s+"
|
|
8
|
+
# Windows date
|
|
9
|
+
r"([0-9]{1,2}\/[0-9]{1,2}\/[0-9]{4})\s+([0-9]{1,2}:[0-9]{1,2}\s[AP][M])\s+"
|
|
10
|
+
# Size (Note: no size on directories)
|
|
11
|
+
r"([0-9]+)?\s+"
|
|
12
|
+
# Size and Filename
|
|
13
|
+
r"([\w\/\.@-]+)"
|
|
14
|
+
),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
WIN_FLAG_TO_TYPE = {
|
|
18
|
+
"d": "directory",
|
|
19
|
+
"-": "file",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
WIN_FLAG_TO_ATTR = {
|
|
23
|
+
"a": "archive",
|
|
24
|
+
"r": "readonly",
|
|
25
|
+
"h": "hidden",
|
|
26
|
+
"s": "system",
|
|
27
|
+
"l": "link",
|
|
28
|
+
"-": "none",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _parse_time(time):
|
|
33
|
+
# Try matching windows format
|
|
34
|
+
try:
|
|
35
|
+
tmp = datetime.strptime(time, "%m/%d/%Y %H:%M %p")
|
|
36
|
+
return tmp
|
|
37
|
+
except ValueError:
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_win_ls_output(output, wanted_type):
|
|
42
|
+
if output:
|
|
43
|
+
matches = re.match(WIN_LS_REGEX, output)
|
|
44
|
+
if matches:
|
|
45
|
+
if output[5] == "l":
|
|
46
|
+
type = "link"
|
|
47
|
+
else:
|
|
48
|
+
type = WIN_FLAG_TO_TYPE[output[0]]
|
|
49
|
+
|
|
50
|
+
if type != wanted_type:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
hidden = False
|
|
54
|
+
system = False
|
|
55
|
+
readonly = False
|
|
56
|
+
archive = False
|
|
57
|
+
link = False
|
|
58
|
+
|
|
59
|
+
mode_without_first = matches.group(1)[1:]
|
|
60
|
+
|
|
61
|
+
for c in mode_without_first:
|
|
62
|
+
tmp = WIN_FLAG_TO_ATTR[c]
|
|
63
|
+
if tmp != "none":
|
|
64
|
+
if tmp == "hidden":
|
|
65
|
+
hidden = True
|
|
66
|
+
if tmp == "readonly":
|
|
67
|
+
readonly = True
|
|
68
|
+
if tmp == "archive":
|
|
69
|
+
archive = True
|
|
70
|
+
if tmp == "system":
|
|
71
|
+
system = True
|
|
72
|
+
if tmp == "link":
|
|
73
|
+
link = True
|
|
74
|
+
|
|
75
|
+
mode = {
|
|
76
|
+
"archive": archive,
|
|
77
|
+
"hidden": hidden,
|
|
78
|
+
"readonly": readonly,
|
|
79
|
+
"system": system,
|
|
80
|
+
"link": link,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
date_and_time = "{} {}".format(matches.group(2), matches.group(3))
|
|
84
|
+
|
|
85
|
+
size = "0"
|
|
86
|
+
if type == "file":
|
|
87
|
+
size = matches.group(4)
|
|
88
|
+
|
|
89
|
+
out = {
|
|
90
|
+
"type": type,
|
|
91
|
+
"mode": mode,
|
|
92
|
+
"mtime": _parse_time(date_and_time),
|
|
93
|
+
"size": size,
|
|
94
|
+
"name": matches.group(5),
|
|
95
|
+
# TODO: You will need to run another powershell command to
|
|
96
|
+
# get the link target, so bailing on that for now.
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return out
|
pyinfra/facts/vzctl.py
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
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
10
|
class OpenvzContainers(FactBase):
|
|
7
|
-
|
|
11
|
+
"""
|
|
8
12
|
Returns a dict of running OpenVZ containers by CTID:
|
|
9
13
|
|
|
10
14
|
.. code:: python
|
|
11
15
|
|
|
12
16
|
{
|
|
13
17
|
666: {
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
"ip": [],
|
|
19
|
+
"ostemplate": "ubuntu...",
|
|
16
20
|
...
|
|
17
21
|
},
|
|
18
|
-
...
|
|
19
22
|
}
|
|
20
|
-
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def command(self) -> str:
|
|
27
|
+
return "vzlist -a -j"
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def requires_command(self) -> str:
|
|
31
|
+
return "vzlist"
|
|
21
32
|
|
|
22
|
-
command = 'vzlist -a -j'
|
|
23
33
|
default = dict
|
|
24
34
|
|
|
25
|
-
@
|
|
26
|
-
def process(output):
|
|
27
|
-
combined_json =
|
|
35
|
+
@override
|
|
36
|
+
def process(self, output):
|
|
37
|
+
combined_json = "".join(output)
|
|
28
38
|
vz_data = json.loads(combined_json)
|
|
29
39
|
|
|
30
|
-
return {
|
|
31
|
-
int(vz.pop('ctid')): vz
|
|
32
|
-
for vz in vz_data
|
|
33
|
-
}
|
|
40
|
+
return {int(vz.pop("ctid")): vz for vz in vz_data}
|
pyinfra/facts/xbps.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
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
|
+
class XbpsPackages(FactBase):
|
|
11
|
+
"""
|
|
12
|
+
Returns a dict of installed XBPS packages:
|
|
13
|
+
|
|
14
|
+
.. code:: python
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
"package_name": ["version"],
|
|
18
|
+
}
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def requires_command(self) -> str:
|
|
23
|
+
return "xbps-query"
|
|
24
|
+
|
|
25
|
+
default = dict
|
|
26
|
+
|
|
27
|
+
regex = r"^.. ([a-zA-Z0-9_\-\+\.]+)\-([0-9a-z\.]+_[0-9]+)"
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def command(self):
|
|
31
|
+
return "xbps-query -l"
|
|
32
|
+
|
|
33
|
+
@override
|
|
34
|
+
def process(self, output):
|
|
35
|
+
return parse_packages(self.regex, output)
|