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/crontab.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Dict, List, Optional, TypedDict, Union
|
|
3
|
+
|
|
4
|
+
from typing_extensions import NotRequired, override
|
|
5
|
+
|
|
6
|
+
from pyinfra.api import FactBase
|
|
7
|
+
from pyinfra.api.util import try_int
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CrontabDict(TypedDict):
|
|
11
|
+
command: NotRequired[str]
|
|
12
|
+
# handles cases like CRON_TZ=UTC
|
|
13
|
+
env: NotRequired[str]
|
|
14
|
+
minute: NotRequired[Union[int, str]]
|
|
15
|
+
hour: NotRequired[Union[int, str]]
|
|
16
|
+
month: NotRequired[Union[int, str]]
|
|
17
|
+
day_of_month: NotRequired[Union[int, str]]
|
|
18
|
+
day_of_week: NotRequired[Union[int, str]]
|
|
19
|
+
comments: NotRequired[List[str]]
|
|
20
|
+
special_time: NotRequired[str]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# for compatibility, also keeps a dict of command -> crontab dict
|
|
24
|
+
class CrontabFile:
|
|
25
|
+
commands: List[CrontabDict]
|
|
26
|
+
|
|
27
|
+
def __init__(self, input_dict: Optional[Dict[str, CrontabDict]] = None):
|
|
28
|
+
super().__init__()
|
|
29
|
+
self.commands = []
|
|
30
|
+
if input_dict:
|
|
31
|
+
for command, others in input_dict.items():
|
|
32
|
+
val = others.copy()
|
|
33
|
+
val["command"] = command
|
|
34
|
+
self.add_item(val)
|
|
35
|
+
|
|
36
|
+
def add_item(self, item: CrontabDict):
|
|
37
|
+
self.commands.append(item)
|
|
38
|
+
|
|
39
|
+
def __len__(self):
|
|
40
|
+
return len(self.commands)
|
|
41
|
+
|
|
42
|
+
def __bool__(self):
|
|
43
|
+
return len(self) > 0
|
|
44
|
+
|
|
45
|
+
def items(self):
|
|
46
|
+
return {item.get("command") or item.get("env"): item for item in self.commands}
|
|
47
|
+
|
|
48
|
+
def get_command(
|
|
49
|
+
self, command: Optional[str] = None, name: Optional[str] = None
|
|
50
|
+
) -> Optional[CrontabDict]:
|
|
51
|
+
assert command or name, "Either command or name must be provided"
|
|
52
|
+
|
|
53
|
+
name_comment = "# pyinfra-name={0}".format(name)
|
|
54
|
+
for cmd in self.commands:
|
|
55
|
+
if cmd.get("command") == command:
|
|
56
|
+
return cmd
|
|
57
|
+
if cmd.get("comments") and name_comment in cmd["comments"]:
|
|
58
|
+
return cmd
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
def get_env(self, env: str) -> Optional[CrontabDict]:
|
|
62
|
+
for cmd in self.commands:
|
|
63
|
+
if cmd.get("env") == env:
|
|
64
|
+
return cmd
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
def get(self, item: str) -> Optional[CrontabDict]:
|
|
68
|
+
return self.get_command(command=item, name=item) or self.get_env(item)
|
|
69
|
+
|
|
70
|
+
def __getitem__(self, item) -> Optional[CrontabDict]:
|
|
71
|
+
return self.get(item)
|
|
72
|
+
|
|
73
|
+
@override
|
|
74
|
+
def __repr__(self):
|
|
75
|
+
return f"CrontabResult({self.commands})"
|
|
76
|
+
|
|
77
|
+
# noinspection PyMethodMayBeStatic
|
|
78
|
+
def format_item(self, item: CrontabDict):
|
|
79
|
+
lines = []
|
|
80
|
+
for comment in item.get("comments", []):
|
|
81
|
+
lines.append(comment)
|
|
82
|
+
|
|
83
|
+
if "env" in item:
|
|
84
|
+
lines.append(item["env"])
|
|
85
|
+
elif "special_time" in item:
|
|
86
|
+
lines.append(f"{item['special_time']} {item['command']}")
|
|
87
|
+
else:
|
|
88
|
+
lines.append(
|
|
89
|
+
f"{item['minute']} {item['hour']} "
|
|
90
|
+
f"{item['day_of_month']} {item['month']} {item['day_of_week']} "
|
|
91
|
+
f"{item['command']}"
|
|
92
|
+
)
|
|
93
|
+
return "\n".join(lines)
|
|
94
|
+
|
|
95
|
+
@override
|
|
96
|
+
def __str__(self):
|
|
97
|
+
return "\n".join(self.format_item(item) for item in self.commands)
|
|
98
|
+
|
|
99
|
+
def to_json(self):
|
|
100
|
+
return self.commands
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
_crontab_env_re = re.compile(r"^\s*([A-Z_]+)=(.*)$")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class Crontab(FactBase[CrontabFile]):
|
|
107
|
+
"""
|
|
108
|
+
Returns a dictionary of CrontabFile.
|
|
109
|
+
|
|
110
|
+
.. code:: python
|
|
111
|
+
|
|
112
|
+
# CrontabFile.items()
|
|
113
|
+
{
|
|
114
|
+
"/path/to/command": {
|
|
115
|
+
"minute": "*",
|
|
116
|
+
"hour": "*",
|
|
117
|
+
"month": "*",
|
|
118
|
+
"day_of_month": "*",
|
|
119
|
+
"day_of_week": "*",
|
|
120
|
+
},
|
|
121
|
+
"echo another command": {
|
|
122
|
+
"special_time": "@daily",
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
# or CrontabFile.to_json()
|
|
126
|
+
[
|
|
127
|
+
{
|
|
128
|
+
"command": "/path/to/command",
|
|
129
|
+
"minute": "*",
|
|
130
|
+
"hour": "*",
|
|
131
|
+
"month": "*",
|
|
132
|
+
"day_of_month": "*",
|
|
133
|
+
"day_of_week": "*",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"command": "echo another command",
|
|
137
|
+
"special_time": "@daily",
|
|
138
|
+
}
|
|
139
|
+
]
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
default = CrontabFile
|
|
143
|
+
|
|
144
|
+
@override
|
|
145
|
+
def requires_command(self, user=None) -> str:
|
|
146
|
+
return "crontab"
|
|
147
|
+
|
|
148
|
+
@override
|
|
149
|
+
def command(self, user=None):
|
|
150
|
+
if user:
|
|
151
|
+
return "crontab -l -u {0} || true".format(user)
|
|
152
|
+
return "crontab -l || true"
|
|
153
|
+
|
|
154
|
+
@override
|
|
155
|
+
def process(self, output):
|
|
156
|
+
crons = CrontabFile()
|
|
157
|
+
current_comments = []
|
|
158
|
+
|
|
159
|
+
for line in output:
|
|
160
|
+
line = line.strip()
|
|
161
|
+
if not line or line.startswith("#"):
|
|
162
|
+
current_comments.append(line)
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
if line.startswith("@"):
|
|
166
|
+
special_time, command = line.split(None, 1)
|
|
167
|
+
item = CrontabDict(
|
|
168
|
+
command=command,
|
|
169
|
+
special_time=special_time,
|
|
170
|
+
comments=current_comments,
|
|
171
|
+
)
|
|
172
|
+
crons.add_item(item)
|
|
173
|
+
|
|
174
|
+
elif _crontab_env_re.match(line):
|
|
175
|
+
# handle environment variables
|
|
176
|
+
item = CrontabDict(
|
|
177
|
+
env=line,
|
|
178
|
+
comments=current_comments,
|
|
179
|
+
)
|
|
180
|
+
crons.add_item(item)
|
|
181
|
+
else:
|
|
182
|
+
minute, hour, day_of_month, month, day_of_week, command = line.split(None, 5)
|
|
183
|
+
item = CrontabDict(
|
|
184
|
+
command=command,
|
|
185
|
+
minute=try_int(minute),
|
|
186
|
+
hour=try_int(hour),
|
|
187
|
+
month=try_int(month),
|
|
188
|
+
day_of_month=try_int(day_of_month),
|
|
189
|
+
day_of_week=try_int(day_of_week),
|
|
190
|
+
comments=current_comments,
|
|
191
|
+
)
|
|
192
|
+
crons.add_item(item)
|
|
193
|
+
|
|
194
|
+
current_comments = []
|
|
195
|
+
return crons
|
pyinfra/facts/deb.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import shlex
|
|
5
|
+
|
|
6
|
+
from typing_extensions import override
|
|
7
|
+
|
|
8
|
+
from pyinfra.api import FactBase
|
|
9
|
+
|
|
10
|
+
from .util.packaging import parse_packages
|
|
11
|
+
|
|
12
|
+
DEB_PACKAGE_NAME_REGEX = r"[a-zA-Z0-9\+\-\.]+"
|
|
13
|
+
DEB_PACKAGE_VERSION_REGEX = r"[a-zA-Z0-9:~\.\-\+]+"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DebArch(FactBase):
|
|
17
|
+
"""
|
|
18
|
+
Returns the architecture string used in apt repository sources, eg ``amd64``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def command(self) -> str:
|
|
23
|
+
return "dpkg --print-architecture"
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def requires_command(self) -> str:
|
|
27
|
+
return "dpkg"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DebPackages(FactBase):
|
|
31
|
+
"""
|
|
32
|
+
Returns a dict of installed dpkg packages:
|
|
33
|
+
|
|
34
|
+
.. code:: python
|
|
35
|
+
|
|
36
|
+
{
|
|
37
|
+
"package_name": ["version"],
|
|
38
|
+
}
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def command(self) -> str:
|
|
43
|
+
return "dpkg -l"
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
def requires_command(self) -> str:
|
|
47
|
+
return "dpkg"
|
|
48
|
+
|
|
49
|
+
default = dict
|
|
50
|
+
|
|
51
|
+
regex = r"^[i|h]i\s+({0}):?[a-zA-Z0-9]*\s+({1}).+$".format(
|
|
52
|
+
DEB_PACKAGE_NAME_REGEX,
|
|
53
|
+
DEB_PACKAGE_VERSION_REGEX,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
def process(self, output):
|
|
58
|
+
return parse_packages(self.regex, output)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class DebPackage(FactBase):
|
|
62
|
+
"""
|
|
63
|
+
Returns information on a .deb archive or installed package.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
_regexes = {
|
|
67
|
+
"name": r"^Package:\s+({0})$".format(DEB_PACKAGE_NAME_REGEX),
|
|
68
|
+
"version": r"^Version:\s+({0})$".format(DEB_PACKAGE_VERSION_REGEX),
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@override
|
|
72
|
+
def requires_command(self, package) -> str:
|
|
73
|
+
return "dpkg"
|
|
74
|
+
|
|
75
|
+
@override
|
|
76
|
+
def command(self, package):
|
|
77
|
+
return "! test -e {0} && (dpkg -s {0} 2>/dev/null || true) || dpkg -I {0}".format(
|
|
78
|
+
shlex.quote(package)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
@override
|
|
82
|
+
def process(self, output):
|
|
83
|
+
data = {}
|
|
84
|
+
|
|
85
|
+
for line in output:
|
|
86
|
+
line = line.strip()
|
|
87
|
+
for key, regex in self._regexes.items():
|
|
88
|
+
matches = re.match(regex, line)
|
|
89
|
+
if matches:
|
|
90
|
+
value = matches.group(1)
|
|
91
|
+
data[key] = value
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
return data
|
pyinfra/facts/dnf.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
from pyinfra.api import FactBase
|
|
6
|
+
|
|
7
|
+
from .util import make_cat_files_command
|
|
8
|
+
from .util.packaging import parse_yum_repositories
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DnfRepositories(FactBase):
|
|
12
|
+
"""
|
|
13
|
+
Returns a list of installed dnf repositories:
|
|
14
|
+
|
|
15
|
+
.. code:: python
|
|
16
|
+
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
"repoid": "baseos",
|
|
20
|
+
"name": "AlmaLinux $releasever - BaseOS",
|
|
21
|
+
"mirrorlist": "https://mirrors.almalinux.org/mirrorlist/$releasever/baseos",
|
|
22
|
+
"enabled": "1",
|
|
23
|
+
"gpgcheck": "1",
|
|
24
|
+
"countme": "1",
|
|
25
|
+
"gpgkey": "file:///etc/pki/rpm-gpg/RPM-GPG-KEY-AlmaLinux-9",
|
|
26
|
+
"metadata_expire": "86400",
|
|
27
|
+
"enabled_metadata": "1"
|
|
28
|
+
},
|
|
29
|
+
]
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
@override
|
|
33
|
+
def command(self) -> str:
|
|
34
|
+
return make_cat_files_command(
|
|
35
|
+
"/etc/dnf.conf",
|
|
36
|
+
"/etc/dnf.repos.d/*.repo",
|
|
37
|
+
"/etc/yum.repos.d/*.repo",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
@override
|
|
41
|
+
def requires_command(self) -> str:
|
|
42
|
+
return "dnf"
|
|
43
|
+
|
|
44
|
+
default = list
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def process(self, output):
|
|
48
|
+
return parse_yum_repositories(output)
|
pyinfra/facts/docker.py
CHANGED
|
@@ -1,78 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Facts about Docker containers, volumes and networks. These facts give you information from the view
|
|
3
|
+
of the current inventory host. See the :doc:`../connectors/docker` to use Docker containers as
|
|
4
|
+
inventory directly.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
1
9
|
import json
|
|
2
10
|
|
|
11
|
+
from typing_extensions import override
|
|
12
|
+
|
|
3
13
|
from pyinfra.api import FactBase
|
|
4
14
|
|
|
5
15
|
|
|
6
16
|
class DockerFactBase(FactBase):
|
|
7
17
|
abstract = True
|
|
8
18
|
|
|
19
|
+
docker_type: str
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
def requires_command(self, *args, **kwargs) -> str:
|
|
23
|
+
return "docker"
|
|
24
|
+
|
|
25
|
+
@override
|
|
9
26
|
def process(self, output):
|
|
10
|
-
output =
|
|
27
|
+
output = "".join(output)
|
|
11
28
|
return json.loads(output)
|
|
12
29
|
|
|
13
30
|
|
|
14
31
|
class DockerSystemInfo(DockerFactBase):
|
|
15
|
-
|
|
32
|
+
"""
|
|
16
33
|
Returns ``docker system info`` output in JSON format.
|
|
17
|
-
|
|
34
|
+
"""
|
|
18
35
|
|
|
19
|
-
|
|
36
|
+
@override
|
|
37
|
+
def command(self) -> str:
|
|
38
|
+
return 'docker system info --format="{{json .}}"'
|
|
20
39
|
|
|
21
40
|
|
|
22
41
|
# All Docker objects
|
|
23
42
|
#
|
|
24
43
|
|
|
44
|
+
|
|
25
45
|
class DockerContainers(DockerFactBase):
|
|
26
|
-
|
|
46
|
+
"""
|
|
27
47
|
Returns ``docker inspect`` output for all Docker containers.
|
|
28
|
-
|
|
48
|
+
"""
|
|
29
49
|
|
|
30
|
-
|
|
50
|
+
@override
|
|
51
|
+
def command(self) -> str:
|
|
52
|
+
return """
|
|
53
|
+
ids=$(docker ps -qa) && [ -n "$ids" ] && docker container inspect $ids || echo "[]"
|
|
54
|
+
""".strip()
|
|
31
55
|
|
|
32
56
|
|
|
33
57
|
class DockerImages(DockerFactBase):
|
|
34
|
-
|
|
58
|
+
"""
|
|
35
59
|
Returns ``docker inspect`` output for all Docker images.
|
|
36
|
-
|
|
60
|
+
"""
|
|
37
61
|
|
|
38
|
-
|
|
62
|
+
@override
|
|
63
|
+
def command(self) -> str:
|
|
64
|
+
return """
|
|
65
|
+
ids=$(docker images -q) && [ -n "$ids" ] && docker image inspect $ids || echo "[]"
|
|
66
|
+
""".strip()
|
|
39
67
|
|
|
40
68
|
|
|
41
69
|
class DockerNetworks(DockerFactBase):
|
|
42
|
-
|
|
70
|
+
"""
|
|
43
71
|
Returns ``docker inspect`` output for all Docker networks.
|
|
44
|
-
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
def command(self) -> str:
|
|
76
|
+
return "docker network inspect `docker network ls -q`"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class DockerVolumes(DockerFactBase):
|
|
80
|
+
"""
|
|
81
|
+
Returns ``docker inspect`` output for all Docker volumes.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@override
|
|
85
|
+
def command(self) -> str:
|
|
86
|
+
return "docker volume inspect `docker volume ls -q`"
|
|
45
87
|
|
|
46
|
-
|
|
88
|
+
|
|
89
|
+
class DockerPlugins(DockerFactBase):
|
|
90
|
+
"""
|
|
91
|
+
Returns ``docker plugin inspect`` output for all Docker plugins.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
@override
|
|
95
|
+
def command(self) -> str:
|
|
96
|
+
return """
|
|
97
|
+
ids=$(docker plugin ls -q) && [ -n "$ids" ] && docker plugin inspect $ids || echo "[]"
|
|
98
|
+
""".strip()
|
|
47
99
|
|
|
48
100
|
|
|
49
101
|
# Single Docker objects
|
|
50
102
|
#
|
|
51
103
|
|
|
104
|
+
|
|
52
105
|
class DockerSingleMixin(DockerFactBase):
|
|
106
|
+
@override
|
|
53
107
|
def command(self, object_id):
|
|
54
|
-
return
|
|
108
|
+
return "docker {0} inspect {1} 2>&- || true".format(
|
|
109
|
+
self.docker_type,
|
|
110
|
+
object_id,
|
|
111
|
+
)
|
|
55
112
|
|
|
56
113
|
|
|
57
114
|
class DockerContainer(DockerSingleMixin):
|
|
58
|
-
|
|
115
|
+
"""
|
|
59
116
|
Returns ``docker inspect`` output for a single Docker container.
|
|
60
|
-
|
|
117
|
+
"""
|
|
61
118
|
|
|
62
|
-
docker_type =
|
|
119
|
+
docker_type = "container"
|
|
63
120
|
|
|
64
121
|
|
|
65
122
|
class DockerImage(DockerSingleMixin):
|
|
66
|
-
|
|
123
|
+
"""
|
|
67
124
|
Returns ``docker inspect`` output for a single Docker image.
|
|
68
|
-
|
|
125
|
+
"""
|
|
69
126
|
|
|
70
|
-
docker_type =
|
|
127
|
+
docker_type = "image"
|
|
71
128
|
|
|
72
129
|
|
|
73
130
|
class DockerNetwork(DockerSingleMixin):
|
|
74
|
-
|
|
131
|
+
"""
|
|
75
132
|
Returns ``docker inspect`` output for a single Docker network.
|
|
76
|
-
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
docker_type = "network"
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class DockerVolume(DockerSingleMixin):
|
|
139
|
+
"""
|
|
140
|
+
Returns ``docker inspect`` output for a single Docker container.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
docker_type = "volume"
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class DockerPlugin(DockerSingleMixin):
|
|
147
|
+
"""
|
|
148
|
+
Returns ``docker plugin inspect`` output for a single Docker plugin.
|
|
149
|
+
"""
|
|
77
150
|
|
|
78
|
-
docker_type =
|
|
151
|
+
docker_type = "plugin"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, TypedDict
|
|
4
|
+
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from pyinfra.api import FactBase
|
|
8
|
+
|
|
9
|
+
BootEntry = Tuple[bool, str]
|
|
10
|
+
EFIBootMgrInfoDict = TypedDict(
|
|
11
|
+
"EFIBootMgrInfoDict",
|
|
12
|
+
{
|
|
13
|
+
"BootNext": Optional[int],
|
|
14
|
+
"BootCurrent": Optional[int],
|
|
15
|
+
"Timeout": Optional[int],
|
|
16
|
+
"BootOrder": Optional[List[int]],
|
|
17
|
+
"Entries": Dict[int, BootEntry],
|
|
18
|
+
},
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EFIBootMgr(FactBase[Optional[EFIBootMgrInfoDict]]):
|
|
23
|
+
"""
|
|
24
|
+
Returns information about the UEFI boot variables:
|
|
25
|
+
|
|
26
|
+
.. code:: python
|
|
27
|
+
|
|
28
|
+
{
|
|
29
|
+
"BootNext": 6,
|
|
30
|
+
"BootCurrent": 6,
|
|
31
|
+
"Timeout": 0,
|
|
32
|
+
"BootOrder": [1,4,3],
|
|
33
|
+
"Entries": {
|
|
34
|
+
1: (True, "myefi1"),
|
|
35
|
+
2: (False, "myefi2.efi"),
|
|
36
|
+
3: (True, "myefi3.efi"),
|
|
37
|
+
4: (True, "grub2.efi"),
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def requires_command(self, *args: Any, **kwargs: Any) -> str:
|
|
44
|
+
return "efibootmgr"
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def command(self) -> str:
|
|
48
|
+
# FIXME: Use '|| true' to properly handle the case where
|
|
49
|
+
# 'efibootmgr' is run on a non-UEFI system
|
|
50
|
+
return "efibootmgr || true"
|
|
51
|
+
|
|
52
|
+
@override
|
|
53
|
+
def process(self, output: Iterable[str]) -> Optional[EFIBootMgrInfoDict]:
|
|
54
|
+
# This parsing code closely follows the printing code of efibootmgr
|
|
55
|
+
# at <https://github.com/rhboot/efibootmgr/blob/main/src/efibootmgr.c#L2020-L2048>
|
|
56
|
+
|
|
57
|
+
info: EFIBootMgrInfoDict = {
|
|
58
|
+
"BootNext": None,
|
|
59
|
+
"BootCurrent": None,
|
|
60
|
+
"Timeout": None,
|
|
61
|
+
"BootOrder": [],
|
|
62
|
+
"Entries": {},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
output = iter(output)
|
|
66
|
+
|
|
67
|
+
line: Optional[str] = next(output, None)
|
|
68
|
+
|
|
69
|
+
if line is None:
|
|
70
|
+
# efibootmgr run on a non-UEFI system, likely printed
|
|
71
|
+
# "EFI variables are not supported on this system."
|
|
72
|
+
# to stderr
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
# 1. Maybe have BootNext
|
|
76
|
+
if line and line.startswith("BootNext: "):
|
|
77
|
+
info["BootNext"] = int(line[len("BootNext: ") :], 16)
|
|
78
|
+
line = next(output, None)
|
|
79
|
+
|
|
80
|
+
# 2. Maybe have BootCurrent
|
|
81
|
+
if line and line.startswith("BootCurrent: "):
|
|
82
|
+
info["BootCurrent"] = int(line[len("BootCurrent: ") :], 16)
|
|
83
|
+
line = next(output, None)
|
|
84
|
+
|
|
85
|
+
# 3. Maybe have Timeout
|
|
86
|
+
if line and line.startswith("Timeout: "):
|
|
87
|
+
info["Timeout"] = int(line[len("Timeout: ") : -len(" seconds")])
|
|
88
|
+
line = next(output, None)
|
|
89
|
+
|
|
90
|
+
# 4. `show_order`
|
|
91
|
+
if line and line.startswith("BootOrder: "):
|
|
92
|
+
entries = line[len("BootOrder: ") :]
|
|
93
|
+
info["BootOrder"] = list(map(lambda x: int(x, 16), entries.split(",")))
|
|
94
|
+
line = next(output, None)
|
|
95
|
+
|
|
96
|
+
# 5. `show_vars`: The actual boot entries
|
|
97
|
+
while line is not None and line.startswith("Boot"):
|
|
98
|
+
number = int(line[4:8], 16)
|
|
99
|
+
|
|
100
|
+
# Entries marked with a * are active
|
|
101
|
+
active = line[8:9] == "*"
|
|
102
|
+
|
|
103
|
+
# TODO: Maybe split and parse (name vs. arguments ?), might require --verbose ?
|
|
104
|
+
entry = line[10:]
|
|
105
|
+
info["Entries"][number] = (active, entry)
|
|
106
|
+
line = next(output, None)
|
|
107
|
+
|
|
108
|
+
# 6. `show_mirror`
|
|
109
|
+
# Currently not implemented, since I haven't actually encountered this in the wild.
|
|
110
|
+
if line is not None:
|
|
111
|
+
raise ValueError(f"Unexpected line '{line}' while parsing")
|
|
112
|
+
|
|
113
|
+
return info
|