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/context.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The `ContextObject` and `ContextManager` provide context specific variables that
|
|
3
|
+
are imported and used throughout pyinfra and end user deploy code (CLI mode).
|
|
4
|
+
|
|
5
|
+
These variables always represent the current executing pyinfra context.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from contextlib import contextmanager
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
from typing import TYPE_CHECKING
|
|
11
|
+
|
|
12
|
+
from gevent.local import local
|
|
13
|
+
from typing_extensions import override
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from pyinfra.api.config import Config
|
|
17
|
+
from pyinfra.api.host import Host
|
|
18
|
+
from pyinfra.api.inventory import Inventory
|
|
19
|
+
from pyinfra.api.state import State
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class container:
|
|
23
|
+
module = None
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ContextObject:
|
|
27
|
+
_container_cls = container
|
|
28
|
+
_base_cls: ModuleType
|
|
29
|
+
|
|
30
|
+
def __init__(self) -> None:
|
|
31
|
+
self._container = self._container_cls()
|
|
32
|
+
self._container.module = None
|
|
33
|
+
|
|
34
|
+
def _get_module(self):
|
|
35
|
+
return self._container.module
|
|
36
|
+
|
|
37
|
+
@override
|
|
38
|
+
def __repr__(self):
|
|
39
|
+
return "ContextObject({0}):{1}".format(
|
|
40
|
+
self._base_cls.__name__,
|
|
41
|
+
repr(self._get_module()),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return str(self._get_module())
|
|
47
|
+
|
|
48
|
+
@override
|
|
49
|
+
def __dir__(self):
|
|
50
|
+
return dir(self._base_cls)
|
|
51
|
+
|
|
52
|
+
def __getattr__(self, key):
|
|
53
|
+
if self._get_module() is None:
|
|
54
|
+
return getattr(self._base_cls, key)
|
|
55
|
+
return getattr(self._get_module(), key)
|
|
56
|
+
|
|
57
|
+
@override
|
|
58
|
+
def __setattr__(self, key, value):
|
|
59
|
+
if key in ("_container", "_base_cls"):
|
|
60
|
+
return super().__setattr__(key, value)
|
|
61
|
+
|
|
62
|
+
mod = self._get_module()
|
|
63
|
+
if mod is None:
|
|
64
|
+
raise TypeError("Cannot assign to context base module")
|
|
65
|
+
return setattr(mod, key, value)
|
|
66
|
+
|
|
67
|
+
def __iter__(self):
|
|
68
|
+
mod = self._get_module()
|
|
69
|
+
if mod is None:
|
|
70
|
+
raise ValueError("Context not set")
|
|
71
|
+
return iter(mod)
|
|
72
|
+
|
|
73
|
+
def __len__(self):
|
|
74
|
+
mod = self._get_module()
|
|
75
|
+
if mod is None:
|
|
76
|
+
raise ValueError("Context not set")
|
|
77
|
+
return len(mod)
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def __eq__(self, other):
|
|
81
|
+
return self._get_module() == other
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def __hash__(self):
|
|
85
|
+
return hash(self._get_module())
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class LocalContextObject(ContextObject):
|
|
89
|
+
_container_cls = local
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class ContextManager:
|
|
93
|
+
def __init__(self, key, context_cls):
|
|
94
|
+
self.context = context_cls()
|
|
95
|
+
|
|
96
|
+
def get(self):
|
|
97
|
+
return getattr(self.context._container, "module", None)
|
|
98
|
+
|
|
99
|
+
def set(self, module):
|
|
100
|
+
self.context._container.module = module
|
|
101
|
+
|
|
102
|
+
def set_base(self, module):
|
|
103
|
+
self.context._base_cls = module
|
|
104
|
+
|
|
105
|
+
def reset(self) -> None:
|
|
106
|
+
self.context._container.module = None
|
|
107
|
+
|
|
108
|
+
def isset(self):
|
|
109
|
+
return self.get() is not None
|
|
110
|
+
|
|
111
|
+
@contextmanager
|
|
112
|
+
def use(self, module):
|
|
113
|
+
old_module = self.get()
|
|
114
|
+
if old_module is module:
|
|
115
|
+
yield # if we're double-setting, nothing to do
|
|
116
|
+
return
|
|
117
|
+
self.set(module)
|
|
118
|
+
yield
|
|
119
|
+
self.set(old_module)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
ctx_state = ContextManager("state", ContextObject)
|
|
123
|
+
state: "State" = ctx_state.context
|
|
124
|
+
|
|
125
|
+
ctx_inventory = ContextManager("inventory", ContextObject)
|
|
126
|
+
inventory: "Inventory" = ctx_inventory.context
|
|
127
|
+
|
|
128
|
+
# Config can be modified mid-deploy, so we use a local object here which
|
|
129
|
+
# is based on a copy of the state config.
|
|
130
|
+
ctx_config = ContextManager("config", LocalContextObject)
|
|
131
|
+
config: "Config" = ctx_config.context
|
|
132
|
+
|
|
133
|
+
# Hosts are prepared in parallel each in a greenlet, so we use a local to
|
|
134
|
+
# point at different host objects in each greenlet.
|
|
135
|
+
ctx_host = ContextManager("host", LocalContextObject)
|
|
136
|
+
host: "Host" = ctx_host.context
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def init_base_classes() -> None:
|
|
140
|
+
from pyinfra.api import Config, Host, Inventory, State
|
|
141
|
+
|
|
142
|
+
ctx_config.set_base(Config)
|
|
143
|
+
ctx_inventory.set_base(Inventory)
|
|
144
|
+
ctx_host.set_base(Host)
|
|
145
|
+
ctx_state.set_base(State)
|
pyinfra/facts/__init__.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
# This file only exists to support:
|
|
2
|
+
# from pyinfra import facts
|
|
3
|
+
# facts.X.Y
|
|
4
|
+
|
|
1
5
|
from glob import glob
|
|
2
6
|
from os import path
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
module_filenames = glob(path.join(path.dirname(__file__), '*.py'))
|
|
8
|
+
module_filenames = glob(path.join(path.dirname(__file__), "*.py"))
|
|
6
9
|
module_names = [path.basename(name)[:-3] for name in module_filenames]
|
|
7
|
-
__all__ = [name for name in module_names if name !=
|
|
8
|
-
|
|
10
|
+
__all__ = [name for name in module_names if name != "__init__"]
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
from . import * # noqa
|
|
12
|
+
from . import * # noqa
|
pyinfra/facts/apk.py
CHANGED
|
@@ -1,22 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
1
5
|
from pyinfra.api import FactBase
|
|
2
6
|
|
|
3
7
|
from .util.packaging import parse_packages
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
# Source: https://superuser.com/a/1472405
|
|
10
|
+
# Modified to return version and release inside a single group and removed extra capturing groups
|
|
11
|
+
APK_REGEX = r"(.+)-([^-]+-r[^-]+) \S+ \{\S+\} \(.+?\)"
|
|
6
12
|
|
|
7
13
|
|
|
8
14
|
class ApkPackages(FactBase):
|
|
9
|
-
|
|
15
|
+
"""
|
|
10
16
|
Returns a dict of installed apk packages:
|
|
11
17
|
|
|
12
18
|
.. code:: python
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
{
|
|
21
|
+
"package_name": ["version"],
|
|
22
|
+
}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
@override
|
|
26
|
+
def command(self) -> str:
|
|
27
|
+
return "apk list --installed"
|
|
28
|
+
|
|
29
|
+
@override
|
|
30
|
+
def requires_command(self) -> str:
|
|
31
|
+
return "apk"
|
|
17
32
|
|
|
18
|
-
|
|
19
|
-
deafult = dict
|
|
33
|
+
default = dict
|
|
20
34
|
|
|
35
|
+
@override
|
|
21
36
|
def process(self, output):
|
|
22
37
|
return parse_packages(APK_REGEX, output)
|
pyinfra/facts/apt.py
CHANGED
|
@@ -1,12 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
|
|
5
|
+
from typing_extensions import TypedDict, override
|
|
6
|
+
|
|
3
7
|
from pyinfra.api import FactBase
|
|
4
8
|
|
|
5
|
-
from .
|
|
9
|
+
from .gpg import GpgFactBase
|
|
10
|
+
from .util import make_cat_files_command
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def noninteractive_apt(command: str, force=False):
|
|
14
|
+
args = ["DEBIAN_FRONTEND=noninteractive apt-get -y"]
|
|
15
|
+
|
|
16
|
+
if force:
|
|
17
|
+
args.append("--force-yes")
|
|
18
|
+
|
|
19
|
+
args.extend(
|
|
20
|
+
(
|
|
21
|
+
'-o Dpkg::Options::="--force-confdef"',
|
|
22
|
+
'-o Dpkg::Options::="--force-confold"',
|
|
23
|
+
command,
|
|
24
|
+
),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return " ".join(args)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
APT_CHANGES_RE = re.compile(
|
|
31
|
+
r"^(\d+) upgraded, (\d+) newly installed, (\d+) to remove and (\d+) not upgraded.$"
|
|
32
|
+
)
|
|
6
33
|
|
|
7
34
|
|
|
8
35
|
def parse_apt_repo(name):
|
|
9
|
-
regex = r
|
|
36
|
+
regex = r"^(deb(?:-src)?)(?:\s+\[([^\]]+)\])?\s+([^\s]+)\s+([^\s]+)\s+([a-z-\s\d]*)$"
|
|
10
37
|
|
|
11
38
|
matches = re.match(regex, name)
|
|
12
39
|
|
|
@@ -18,39 +45,51 @@ def parse_apt_repo(name):
|
|
|
18
45
|
options_string = matches.group(2)
|
|
19
46
|
if options_string:
|
|
20
47
|
for option in options_string.split():
|
|
21
|
-
key, value = option.split(
|
|
22
|
-
if
|
|
23
|
-
value = value.split(
|
|
48
|
+
key, value = option.split("=", 1)
|
|
49
|
+
if "," in value:
|
|
50
|
+
value = value.split(",")
|
|
24
51
|
|
|
25
52
|
options[key] = value
|
|
26
53
|
|
|
27
54
|
return {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
"options": options,
|
|
56
|
+
"type": matches.group(1),
|
|
57
|
+
"url": matches.group(3),
|
|
58
|
+
"distribution": matches.group(4),
|
|
59
|
+
"components": list(matches.group(5).split()),
|
|
33
60
|
}
|
|
34
61
|
|
|
35
62
|
|
|
36
63
|
class AptSources(FactBase):
|
|
37
|
-
|
|
64
|
+
"""
|
|
38
65
|
Returns a list of installed apt sources:
|
|
39
66
|
|
|
40
67
|
.. code:: python
|
|
41
68
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
"type": "deb",
|
|
72
|
+
"url": "http://archive.ubuntu.org",
|
|
73
|
+
"distribution": "trusty",
|
|
74
|
+
"components", ["main", "multiverse"],
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
@override
|
|
80
|
+
def command(self) -> str:
|
|
81
|
+
return make_cat_files_command(
|
|
82
|
+
"/etc/apt/sources.list",
|
|
83
|
+
"/etc/apt/sources.list.d/*.list",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@override
|
|
87
|
+
def requires_command(self) -> str:
|
|
88
|
+
return "apt" # if apt installed, above should exist
|
|
89
|
+
|
|
52
90
|
default = list
|
|
53
91
|
|
|
92
|
+
@override
|
|
54
93
|
def process(self, output):
|
|
55
94
|
repos = []
|
|
56
95
|
|
|
@@ -62,46 +101,64 @@ class AptSources(FactBase):
|
|
|
62
101
|
return repos
|
|
63
102
|
|
|
64
103
|
|
|
65
|
-
class
|
|
66
|
-
|
|
67
|
-
Returns
|
|
104
|
+
class AptKeys(GpgFactBase):
|
|
105
|
+
"""
|
|
106
|
+
Returns information on GPG keys apt has in its keychain:
|
|
68
107
|
|
|
69
108
|
.. code:: python
|
|
70
109
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
{
|
|
111
|
+
"KEY-ID": {
|
|
112
|
+
"length": 4096,
|
|
113
|
+
"uid": "Oxygem <hello@oxygem.com>"
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
# This requires both apt-key *and* apt-key itself requires gpg
|
|
119
|
+
@override
|
|
120
|
+
def command(self) -> str:
|
|
121
|
+
return "! command -v gpg || apt-key list --with-colons"
|
|
122
|
+
|
|
123
|
+
@override
|
|
124
|
+
def requires_command(self) -> str:
|
|
125
|
+
return "apt-key"
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class AptSimulationDict(TypedDict):
|
|
129
|
+
upgraded: int
|
|
130
|
+
newly_installed: int
|
|
131
|
+
removed: int
|
|
132
|
+
not_upgraded: int
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class SimulateOperationWillChange(FactBase[AptSimulationDict]):
|
|
136
|
+
"""
|
|
137
|
+
Simulate an 'apt-get' operation and try to detect if any changes would be performed.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
@override
|
|
141
|
+
def command(self, command: str) -> str:
|
|
142
|
+
# LC_ALL=C: Ensure the output is in english, as we want to parse it
|
|
143
|
+
return "LC_ALL=C " + noninteractive_apt(f"{command} --dry-run")
|
|
144
|
+
|
|
145
|
+
@override
|
|
146
|
+
def requires_command(self, command: str) -> str:
|
|
147
|
+
return "apt-get"
|
|
148
|
+
|
|
149
|
+
@override
|
|
150
|
+
def process(self, output) -> AptSimulationDict:
|
|
151
|
+
# We are looking for a line similar to
|
|
152
|
+
# "3 upgraded, 0 newly installed, 0 to remove and 0 not upgraded."
|
|
99
153
|
for line in output:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
154
|
+
result = APT_CHANGES_RE.match(line)
|
|
155
|
+
if result is not None:
|
|
156
|
+
return {
|
|
157
|
+
"upgraded": int(result[1]),
|
|
158
|
+
"newly_installed": int(result[2]),
|
|
159
|
+
"removed": int(result[3]),
|
|
160
|
+
"not_upgraded": int(result[4]),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
# We did not find the line we expected:
|
|
164
|
+
raise Exception("Did not find proposed changes in output")
|
pyinfra/facts/brew.py
CHANGED
|
@@ -1,46 +1,131 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from pyinfra import logger
|
|
1
8
|
from pyinfra.api import FactBase
|
|
2
9
|
|
|
3
10
|
from .util.packaging import parse_packages
|
|
4
11
|
|
|
5
|
-
BREW_REGEX = r
|
|
12
|
+
BREW_REGEX = r"^([^\s]+)\s([0-9\._+a-z\-]+)"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def new_cask_cli(version):
|
|
16
|
+
"""
|
|
17
|
+
Returns true if brew is version 2.6.0 or later and thus has the new CLI for casks.
|
|
18
|
+
i.e. we need to use brew list --cask instead of brew cask list
|
|
19
|
+
See https://brew.sh/2020/12/01/homebrew-2.6.0/
|
|
20
|
+
The version string returned by BrewVersion is a list of major, minor, patch version numbers
|
|
21
|
+
"""
|
|
22
|
+
return (version[0] >= 3) or ((version[0] >= 2) and version[1] >= 6)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
VERSION_MATCHER = re.compile(r"^Homebrew\s+(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+).*$")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def unknown_version():
|
|
29
|
+
return [0, 0, 0]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BrewVersion(FactBase):
|
|
33
|
+
"""
|
|
34
|
+
Returns the version of brew installed as a semantic versioning tuple:
|
|
35
|
+
|
|
36
|
+
.. code:: python
|
|
37
|
+
|
|
38
|
+
[major, minor, patch]
|
|
39
|
+
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@override
|
|
43
|
+
def command(self) -> str:
|
|
44
|
+
return "brew --version"
|
|
45
|
+
|
|
46
|
+
@override
|
|
47
|
+
def requires_command(self) -> str:
|
|
48
|
+
return "brew"
|
|
49
|
+
|
|
50
|
+
@override
|
|
51
|
+
@staticmethod
|
|
52
|
+
def default():
|
|
53
|
+
return [0, 0, 0]
|
|
54
|
+
|
|
55
|
+
@override
|
|
56
|
+
def process(self, output):
|
|
57
|
+
out = list(output)[0]
|
|
58
|
+
m = VERSION_MATCHER.match(out)
|
|
59
|
+
if m is not None:
|
|
60
|
+
return [int(m.group(key)) for key in ["major", "minor", "patch"]]
|
|
61
|
+
logger.warning("could not parse version string from brew: %s", out)
|
|
62
|
+
return self.default()
|
|
6
63
|
|
|
7
64
|
|
|
8
65
|
class BrewPackages(FactBase):
|
|
9
|
-
|
|
66
|
+
"""
|
|
10
67
|
Returns a dict of installed brew packages:
|
|
11
68
|
|
|
12
69
|
.. code:: python
|
|
13
70
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
71
|
+
{
|
|
72
|
+
"package_name": ["version"],
|
|
73
|
+
}
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
@override
|
|
77
|
+
def command(self) -> str:
|
|
78
|
+
return "brew list --versions"
|
|
79
|
+
|
|
80
|
+
@override
|
|
81
|
+
def requires_command(self) -> str:
|
|
82
|
+
return "brew"
|
|
17
83
|
|
|
18
|
-
|
|
19
|
-
deafult = dict
|
|
84
|
+
default = dict
|
|
20
85
|
|
|
86
|
+
@override
|
|
21
87
|
def process(self, output):
|
|
22
88
|
return parse_packages(BREW_REGEX, output)
|
|
23
89
|
|
|
24
90
|
|
|
25
91
|
class BrewCasks(BrewPackages):
|
|
26
|
-
|
|
92
|
+
"""
|
|
27
93
|
Returns a dict of installed brew casks:
|
|
28
94
|
|
|
29
95
|
.. code:: python
|
|
30
96
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
97
|
+
{
|
|
98
|
+
"package_name": ["version"],
|
|
99
|
+
}
|
|
100
|
+
"""
|
|
34
101
|
|
|
35
|
-
|
|
102
|
+
@override
|
|
103
|
+
def command(self) -> str:
|
|
104
|
+
return (
|
|
105
|
+
r'if brew --version | grep -q -e "Homebrew\ +(1\.|2\.[0-5]).*" 1>/dev/null;'
|
|
106
|
+
r"then brew cask list --versions; else brew list --cask --versions; fi"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
@override
|
|
110
|
+
def requires_command(self) -> str:
|
|
111
|
+
return "brew"
|
|
36
112
|
|
|
37
113
|
|
|
38
114
|
class BrewTaps(FactBase):
|
|
39
|
-
|
|
115
|
+
"""
|
|
40
116
|
Returns a list of brew taps.
|
|
41
|
-
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
@override
|
|
120
|
+
def command(self) -> str:
|
|
121
|
+
return "brew tap"
|
|
122
|
+
|
|
123
|
+
@override
|
|
124
|
+
def requires_command(self) -> str:
|
|
125
|
+
return "brew"
|
|
42
126
|
|
|
43
|
-
|
|
127
|
+
default = list
|
|
44
128
|
|
|
129
|
+
@override
|
|
45
130
|
def process(self, output):
|
|
46
131
|
return output
|
pyinfra/facts/bsdinit.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing_extensions import override
|
|
4
|
+
|
|
5
|
+
from .sysvinit import InitdStatus
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RcdStatus(InitdStatus):
|
|
9
|
+
"""
|
|
10
|
+
Same as ``initd_status`` but for BSD (/etc/rc.d) systems. Unlike Linux/init.d,
|
|
11
|
+
BSD init scripts are well behaved and as such their output can be trusted.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
@override
|
|
15
|
+
def command(self) -> str:
|
|
16
|
+
return """
|
|
17
|
+
for SERVICE in `find /etc/rc.d /usr/local/etc/rc.d -type f`; do
|
|
18
|
+
$SERVICE status 2> /dev/null || $SERVICE check 2> /dev/null
|
|
19
|
+
echo "`basename $SERVICE`=$?"
|
|
20
|
+
done
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
default = dict
|
pyinfra/facts/cargo.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# encoding: utf8
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing_extensions import override
|
|
6
|
+
|
|
7
|
+
from pyinfra.api import FactBase
|
|
8
|
+
|
|
9
|
+
from .util.packaging import parse_packages
|
|
10
|
+
|
|
11
|
+
CARGO_REGEX = r"^([a-zA-Z0-9\-]+)\sv([0-9\.]+)"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CargoPackages(FactBase):
|
|
15
|
+
"""
|
|
16
|
+
Returns a dict of installed cargo packages globally:
|
|
17
|
+
|
|
18
|
+
.. code:: python
|
|
19
|
+
|
|
20
|
+
{
|
|
21
|
+
"package_name": ["version"],
|
|
22
|
+
}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
default = dict
|
|
26
|
+
|
|
27
|
+
@override
|
|
28
|
+
def command(self) -> str:
|
|
29
|
+
return "cargo install --list"
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
def requires_command(self) -> str:
|
|
33
|
+
return "cargo"
|
|
34
|
+
|
|
35
|
+
@override
|
|
36
|
+
def process(self, output):
|
|
37
|
+
return parse_packages(CARGO_REGEX, output)
|
pyinfra/facts/choco.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
CHOCO_REGEX = r"^([a-zA-Z0-9\.\-\+\_]+)\s([0-9\.]+)$"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ChocoPackages(FactBase):
|
|
13
|
+
"""
|
|
14
|
+
Returns a dict of installed choco (Chocolatey) packages:
|
|
15
|
+
|
|
16
|
+
.. code:: python
|
|
17
|
+
|
|
18
|
+
{
|
|
19
|
+
"package_name": ["version"],
|
|
20
|
+
}
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
@override
|
|
24
|
+
def command(self) -> str:
|
|
25
|
+
return "choco list"
|
|
26
|
+
|
|
27
|
+
shell_executable = "ps"
|
|
28
|
+
|
|
29
|
+
default = dict
|
|
30
|
+
|
|
31
|
+
@override
|
|
32
|
+
def process(self, output):
|
|
33
|
+
return parse_packages(CHOCO_REGEX, output)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ChocoVersion(FactBase):
|
|
37
|
+
"""
|
|
38
|
+
Returns the choco (Chocolatey) version.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@override
|
|
42
|
+
def command(self) -> str:
|
|
43
|
+
return "choco --version"
|
|
44
|
+
|
|
45
|
+
@override
|
|
46
|
+
def process(self, output):
|
|
47
|
+
return "".join(output).replace("\n", "")
|