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/api/facts.py
CHANGED
|
@@ -1,259 +1,326 @@
|
|
|
1
|
-
|
|
1
|
+
"""
|
|
2
2
|
The pyinfra facts API. Facts enable pyinfra to collect remote server state which
|
|
3
3
|
is used to "diff" with the desired state, producing the final commands required
|
|
4
4
|
for a deploy.
|
|
5
|
-
'''
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
Note that the facts API does *not* use the global currently in context host so
|
|
7
|
+
it's possible to call facts on hosts out of context (ie give me the IP of this
|
|
8
|
+
other host B while I operate on this host A).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import inspect
|
|
14
|
+
import re
|
|
15
|
+
from inspect import getcallargs
|
|
16
|
+
from socket import error as socket_error, timeout as timeout_error
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Iterable, Optional, Type, TypeVar, cast
|
|
12
18
|
|
|
13
19
|
import click
|
|
14
20
|
import gevent
|
|
15
|
-
|
|
16
|
-
from gevent.lock import BoundedSemaphore
|
|
17
21
|
from paramiko import SSHException
|
|
22
|
+
from typing_extensions import override
|
|
18
23
|
|
|
19
24
|
from pyinfra import logger
|
|
25
|
+
from pyinfra.api import StringCommand
|
|
26
|
+
from pyinfra.api.arguments import all_global_arguments, pop_global_arguments
|
|
27
|
+
from pyinfra.api.exceptions import FactProcessError
|
|
20
28
|
from pyinfra.api.util import (
|
|
21
|
-
|
|
29
|
+
get_kwargs_str,
|
|
30
|
+
log_error_or_warning,
|
|
22
31
|
log_host_command_error,
|
|
23
|
-
|
|
24
|
-
underscore,
|
|
32
|
+
print_host_combined_output,
|
|
25
33
|
)
|
|
34
|
+
from pyinfra.connectors.util import CommandOutput
|
|
35
|
+
from pyinfra.context import ctx_host, ctx_state
|
|
26
36
|
from pyinfra.progress import progress_spinner
|
|
27
37
|
|
|
38
|
+
from .arguments import CONNECTOR_ARGUMENT_KEYS
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
FACT_LOCK = BoundedSemaphore()
|
|
40
|
+
if TYPE_CHECKING:
|
|
41
|
+
from pyinfra.api import Host, State
|
|
32
42
|
|
|
43
|
+
SUDO_REGEX = r"^sudo: unknown user"
|
|
44
|
+
SU_REGEXES = (
|
|
45
|
+
r"^su: user .+ does not exist",
|
|
46
|
+
r"^su: unknown login",
|
|
47
|
+
)
|
|
33
48
|
|
|
34
|
-
def is_fact(name):
|
|
35
|
-
return name in FACTS
|
|
36
49
|
|
|
50
|
+
T = TypeVar("T")
|
|
37
51
|
|
|
38
|
-
def get_fact_names():
|
|
39
|
-
'''
|
|
40
|
-
Returns a list of available facts in camel_case format.
|
|
41
|
-
'''
|
|
42
52
|
|
|
43
|
-
|
|
53
|
+
class FactBase(Generic[T]):
|
|
54
|
+
name: str
|
|
44
55
|
|
|
56
|
+
abstract: bool = True
|
|
45
57
|
|
|
46
|
-
|
|
47
|
-
'''
|
|
48
|
-
Metaclass to dynamically build the facts index.
|
|
49
|
-
'''
|
|
58
|
+
shell_executable: str | None = None
|
|
50
59
|
|
|
51
|
-
|
|
52
|
-
if attrs.get('abstract'):
|
|
53
|
-
return
|
|
60
|
+
command: Callable[..., str | StringCommand]
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
62
|
+
def requires_command(self, *args, **kwargs) -> str | None:
|
|
63
|
+
return None
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
@override
|
|
66
|
+
def __init_subclass__(cls) -> None:
|
|
67
|
+
super().__init_subclass__()
|
|
68
|
+
module_name = cls.__module__.replace("pyinfra.facts.", "")
|
|
69
|
+
cls.name = f"{module_name}.{cls.__name__}"
|
|
60
70
|
|
|
71
|
+
# Check that fact's `command` method does not inadvertently take a global
|
|
72
|
+
# argument, most commonly `name`.
|
|
73
|
+
if hasattr(cls, "command") and callable(cls.command):
|
|
74
|
+
command_args = set(inspect.signature(cls.command).parameters.keys())
|
|
75
|
+
global_args = set([name for name, _ in all_global_arguments()])
|
|
76
|
+
command_global_args = command_args & global_args
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
if len(command_global_args) > 0:
|
|
79
|
+
names = ", ".join(command_global_args)
|
|
80
|
+
raise TypeError(f"{cls.name}'s arguments {names} are reserved for global arguments")
|
|
64
81
|
|
|
65
82
|
@staticmethod
|
|
66
|
-
def default():
|
|
67
|
-
|
|
83
|
+
def default() -> T:
|
|
84
|
+
"""
|
|
68
85
|
Set the default attribute to be a type (eg list/dict).
|
|
69
|
-
|
|
86
|
+
"""
|
|
70
87
|
|
|
71
|
-
|
|
72
|
-
def process(output):
|
|
73
|
-
return '\n'.join(output)
|
|
88
|
+
return cast(T, None)
|
|
74
89
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for i, arg in enumerate(args)
|
|
79
|
-
}
|
|
90
|
+
def process(self, output: Iterable[str]) -> T:
|
|
91
|
+
# NOTE: TypeVar does not support a default, so we have to cast this str -> T
|
|
92
|
+
return cast(T, "\n".join(output))
|
|
80
93
|
|
|
94
|
+
def process_pipeline(self, args, output):
|
|
95
|
+
return {arg: self.process([output[i]]) for i, arg in enumerate(args)}
|
|
81
96
|
|
|
82
|
-
class ShortFactBase(object, metaclass=FactMeta):
|
|
83
|
-
fact = None
|
|
84
97
|
|
|
98
|
+
class ShortFactBase(Generic[T]):
|
|
99
|
+
name: str
|
|
100
|
+
fact: Type[FactBase]
|
|
85
101
|
|
|
86
|
-
|
|
87
|
-
|
|
102
|
+
@override
|
|
103
|
+
def __init_subclass__(cls) -> None:
|
|
104
|
+
super().__init_subclass__()
|
|
105
|
+
module_name = cls.__module__.replace("pyinfra.facts.", "")
|
|
106
|
+
cls.name = f"{module_name}.{cls.__name__}"
|
|
88
107
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for host, data in facts.items()
|
|
92
|
-
}
|
|
108
|
+
def process_data(self, data):
|
|
109
|
+
return data
|
|
93
110
|
|
|
94
111
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'''
|
|
112
|
+
def get_short_facts(state: "State", host: "Host", short_fact, **kwargs):
|
|
113
|
+
fact_data = get_fact(state, host, short_fact.fact, **kwargs)
|
|
114
|
+
return short_fact().process_data(fact_data)
|
|
99
115
|
|
|
100
|
-
# Create an instance of the fact
|
|
101
|
-
fact = FACTS[name]()
|
|
102
116
|
|
|
103
|
-
|
|
104
|
-
|
|
117
|
+
def _make_command(command_attribute, host_args):
|
|
118
|
+
if callable(command_attribute):
|
|
119
|
+
host_args.pop("self", None)
|
|
120
|
+
return command_attribute(**host_args)
|
|
121
|
+
return command_attribute
|
|
105
122
|
|
|
106
|
-
logger.debug('Getting fact: {0} (ensure_hosts: {1})'.format(
|
|
107
|
-
name, ensure_hosts,
|
|
108
|
-
))
|
|
109
123
|
|
|
124
|
+
def _handle_fact_kwargs(state: "State", host: "Host", cls, args, kwargs):
|
|
110
125
|
args = args or []
|
|
126
|
+
kwargs = kwargs or {}
|
|
111
127
|
|
|
112
|
-
#
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
# Timeout for operations !== timeout for connect (config.CONNECT_TIMEOUT)
|
|
119
|
-
timeout = None
|
|
120
|
-
|
|
121
|
-
# Get the current op meta
|
|
122
|
-
current_op_hash = state.current_op_hash
|
|
123
|
-
current_op_meta = state.op_meta.get(current_op_hash)
|
|
124
|
-
|
|
125
|
-
# If inside an operation, fetch config meta
|
|
126
|
-
if current_op_meta:
|
|
127
|
-
sudo = current_op_meta['sudo']
|
|
128
|
-
sudo_user = current_op_meta['sudo_user']
|
|
129
|
-
su_user = current_op_meta['su_user']
|
|
130
|
-
ignore_errors = current_op_meta['ignore_errors']
|
|
131
|
-
timeout = current_op_meta['timeout']
|
|
132
|
-
|
|
133
|
-
# Make a hash which keeps facts unique - but usable cross-deploy/threads.
|
|
134
|
-
# Locks are used to maintain order.
|
|
135
|
-
fact_hash = make_hash((name, args, sudo, sudo_user, su_user, ignore_errors))
|
|
136
|
-
|
|
137
|
-
# Already got this fact? Unlock and return 'em
|
|
138
|
-
current_facts = state.facts.get(fact_hash, {})
|
|
139
|
-
if current_facts:
|
|
140
|
-
if not ensure_hosts or all(
|
|
141
|
-
host in current_facts for host in ensure_hosts
|
|
142
|
-
):
|
|
143
|
-
return current_facts
|
|
144
|
-
|
|
145
|
-
with FACT_LOCK:
|
|
146
|
-
# Add any hosts we must have, whether considered in the inventory or not
|
|
147
|
-
# (these hosts might be outside the --limit or current op limit_hosts).
|
|
148
|
-
hosts = set(state.inventory)
|
|
149
|
-
if ensure_hosts:
|
|
150
|
-
hosts.update(ensure_hosts)
|
|
151
|
-
|
|
152
|
-
# Execute the command for each state inventory in a greenlet
|
|
153
|
-
greenlet_to_host = {}
|
|
154
|
-
|
|
155
|
-
for host in hosts:
|
|
156
|
-
if host in current_facts:
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
# if host in state.ready_hosts:
|
|
160
|
-
# continue
|
|
161
|
-
|
|
162
|
-
# Work out the command
|
|
163
|
-
command = fact.command
|
|
164
|
-
|
|
165
|
-
if ismethod(command) or isfunction(command):
|
|
166
|
-
# Generate actual arguments by pasing strings as jinja2 templates
|
|
167
|
-
host_args = [get_arg_value(state, host, arg) for arg in args]
|
|
168
|
-
|
|
169
|
-
command = command(*host_args)
|
|
170
|
-
|
|
171
|
-
greenlet = state.fact_pool.spawn(
|
|
172
|
-
host.run_shell_command, state, command,
|
|
173
|
-
sudo=sudo, sudo_user=sudo_user,
|
|
174
|
-
su_user=su_user, timeout=timeout,
|
|
175
|
-
print_output=state.print_fact_output,
|
|
176
|
-
)
|
|
177
|
-
greenlet_to_host[greenlet] = host
|
|
178
|
-
|
|
179
|
-
# Wait for all the commands to execute
|
|
180
|
-
progress_prefix = 'fact: {0}'.format(name)
|
|
181
|
-
if args:
|
|
182
|
-
progress_prefix = '{0}{1}'.format(progress_prefix, args)
|
|
183
|
-
|
|
184
|
-
with progress_spinner(
|
|
185
|
-
greenlet_to_host.values(),
|
|
186
|
-
prefix_message=progress_prefix,
|
|
187
|
-
) as progress:
|
|
188
|
-
for greenlet in gevent.iwait(greenlet_to_host.keys()):
|
|
189
|
-
host = greenlet_to_host[greenlet]
|
|
190
|
-
progress(host)
|
|
191
|
-
|
|
192
|
-
hostname_facts = {}
|
|
193
|
-
failed_hosts = set()
|
|
194
|
-
|
|
195
|
-
# Collect the facts and any failures
|
|
196
|
-
for greenlet, host in greenlet_to_host.items():
|
|
197
|
-
status = False
|
|
198
|
-
stdout = []
|
|
128
|
+
# Start with a (shallow) copy of current operation kwargs if any
|
|
129
|
+
ctx_kwargs: dict[str, Any] = (
|
|
130
|
+
cast(dict[str, Any], host.current_op_global_arguments) or {}
|
|
131
|
+
).copy()
|
|
132
|
+
# Update with the input kwargs (overrides)
|
|
133
|
+
ctx_kwargs.update(kwargs)
|
|
199
134
|
|
|
200
|
-
|
|
201
|
-
|
|
135
|
+
# Pop executor kwargs, pass remaining
|
|
136
|
+
global_kwargs, _ = pop_global_arguments(state, host, cast(dict[str, Any], ctx_kwargs))
|
|
202
137
|
|
|
203
|
-
|
|
204
|
-
failed_hosts.add(host)
|
|
205
|
-
log_host_command_error(
|
|
206
|
-
host, e,
|
|
207
|
-
timeout=timeout,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
data = fact.default()
|
|
138
|
+
fact_kwargs = {key: value for key, value in kwargs.items() if key not in global_kwargs}
|
|
211
139
|
|
|
212
|
-
|
|
213
|
-
|
|
140
|
+
if args or fact_kwargs:
|
|
141
|
+
# Merges args & kwargs into a single kwargs dictionary
|
|
142
|
+
fact_kwargs = getcallargs(cls().command, *args, **fact_kwargs)
|
|
214
143
|
|
|
215
|
-
|
|
144
|
+
return fact_kwargs, global_kwargs
|
|
216
145
|
|
|
217
|
-
log_name = click.style(name, bold=True)
|
|
218
146
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
log = 'Loaded fact {0}'.format(log_name)
|
|
147
|
+
def get_facts(state, *args, **kwargs):
|
|
148
|
+
def get_host_fact(host, *args, **kwargs):
|
|
149
|
+
with ctx_host.use(host):
|
|
150
|
+
return get_fact(state, host, *args, **kwargs)
|
|
224
151
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
# Check we've not failed
|
|
231
|
-
if not ignore_errors:
|
|
232
|
-
state.fail_hosts(failed_hosts)
|
|
233
|
-
|
|
234
|
-
# Assign the facts
|
|
235
|
-
state.facts.setdefault(fact_hash, {}).update(hostname_facts)
|
|
236
|
-
|
|
237
|
-
return state.facts[fact_hash]
|
|
152
|
+
with ctx_state.use(state):
|
|
153
|
+
greenlet_to_host = {
|
|
154
|
+
state.pool.spawn(get_host_fact, host, *args, **kwargs): host
|
|
155
|
+
for host in state.inventory.iter_active_hosts()
|
|
156
|
+
}
|
|
238
157
|
|
|
158
|
+
results = {}
|
|
159
|
+
|
|
160
|
+
with progress_spinner(greenlet_to_host.values()) as progress:
|
|
161
|
+
for greenlet in gevent.iwait(greenlet_to_host.keys()):
|
|
162
|
+
host = greenlet_to_host[greenlet]
|
|
163
|
+
results[host] = greenlet.get()
|
|
164
|
+
progress(host)
|
|
165
|
+
|
|
166
|
+
return results
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def get_fact(
|
|
170
|
+
state: "State",
|
|
171
|
+
host: "Host",
|
|
172
|
+
cls: type[FactBase],
|
|
173
|
+
args: Optional[Any] = None,
|
|
174
|
+
kwargs: Optional[Any] = None,
|
|
175
|
+
ensure_hosts: Optional[Any] = None,
|
|
176
|
+
apply_failed_hosts: bool = True,
|
|
177
|
+
) -> Any:
|
|
178
|
+
if issubclass(cls, ShortFactBase):
|
|
179
|
+
return get_short_facts(
|
|
180
|
+
state,
|
|
181
|
+
host,
|
|
182
|
+
cls,
|
|
183
|
+
args=args,
|
|
184
|
+
kwargs=kwargs,
|
|
185
|
+
ensure_hosts=ensure_hosts,
|
|
186
|
+
apply_failed_hosts=apply_failed_hosts,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return _get_fact(
|
|
190
|
+
state,
|
|
191
|
+
host,
|
|
192
|
+
cls,
|
|
193
|
+
args,
|
|
194
|
+
kwargs,
|
|
195
|
+
ensure_hosts,
|
|
196
|
+
apply_failed_hosts,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _get_fact(
|
|
201
|
+
state: "State",
|
|
202
|
+
host: "Host",
|
|
203
|
+
cls: type[FactBase],
|
|
204
|
+
args: Optional[list] = None,
|
|
205
|
+
kwargs: Optional[dict] = None,
|
|
206
|
+
ensure_hosts: Optional[Any] = None,
|
|
207
|
+
apply_failed_hosts: bool = True,
|
|
208
|
+
) -> Any:
|
|
209
|
+
fact = cls()
|
|
210
|
+
name = fact.name
|
|
211
|
+
|
|
212
|
+
fact_kwargs, global_kwargs = _handle_fact_kwargs(state, host, cls, args, kwargs)
|
|
213
|
+
|
|
214
|
+
kwargs_str = get_kwargs_str(fact_kwargs)
|
|
215
|
+
logger.debug(
|
|
216
|
+
"Getting fact: %s (%s) (ensure_hosts: %r)",
|
|
217
|
+
name,
|
|
218
|
+
kwargs_str,
|
|
219
|
+
ensure_hosts,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
if not host.connected:
|
|
223
|
+
host.connect(
|
|
224
|
+
reason=f"to load fact: {name} ({kwargs_str})",
|
|
225
|
+
raise_exceptions=True,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Facts can override the shell (winrm powershell vs cmd support)
|
|
229
|
+
if fact.shell_executable:
|
|
230
|
+
global_kwargs["_shell_executable"] = fact.shell_executable
|
|
231
|
+
|
|
232
|
+
command = _make_command(fact.command, fact_kwargs)
|
|
233
|
+
requires_command = _make_command(fact.requires_command, fact_kwargs)
|
|
234
|
+
if requires_command:
|
|
235
|
+
command = StringCommand(
|
|
236
|
+
# Command doesn't exist, return 0 *or* run & return fact command
|
|
237
|
+
"!",
|
|
238
|
+
"command",
|
|
239
|
+
"-v",
|
|
240
|
+
requires_command,
|
|
241
|
+
">/dev/null",
|
|
242
|
+
"||",
|
|
243
|
+
command,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
status = False
|
|
247
|
+
output = CommandOutput([])
|
|
248
|
+
|
|
249
|
+
executor_kwargs = {
|
|
250
|
+
key: value for key, value in global_kwargs.items() if key in CONNECTOR_ARGUMENT_KEYS
|
|
251
|
+
}
|
|
239
252
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
253
|
+
try:
|
|
254
|
+
status, output = host.run_shell_command(
|
|
255
|
+
command,
|
|
256
|
+
print_output=state.print_fact_output,
|
|
257
|
+
print_input=state.print_fact_input,
|
|
258
|
+
**executor_kwargs,
|
|
259
|
+
)
|
|
260
|
+
except (timeout_error, socket_error, SSHException) as e:
|
|
261
|
+
log_host_command_error(
|
|
262
|
+
host,
|
|
263
|
+
e,
|
|
264
|
+
timeout=global_kwargs.get("_timeout"),
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
stdout_lines, stderr_lines = output.stdout_lines, output.stderr_lines
|
|
268
|
+
|
|
269
|
+
data = fact.default()
|
|
270
|
+
|
|
271
|
+
if status:
|
|
272
|
+
if stdout_lines:
|
|
273
|
+
try:
|
|
274
|
+
data = fact.process(stdout_lines)
|
|
275
|
+
except FactProcessError as e:
|
|
276
|
+
log_error_or_warning(
|
|
277
|
+
host,
|
|
278
|
+
global_kwargs["_ignore_errors"],
|
|
279
|
+
description=("could not process fact: {0} {1}").format(
|
|
280
|
+
name, get_kwargs_str(fact_kwargs)
|
|
281
|
+
),
|
|
282
|
+
exception=e,
|
|
283
|
+
)
|
|
245
284
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
285
|
+
# Check we've not failed
|
|
286
|
+
if apply_failed_hosts and not global_kwargs["_ignore_errors"]:
|
|
287
|
+
state.fail_hosts({host})
|
|
288
|
+
|
|
289
|
+
elif stderr_lines:
|
|
290
|
+
# If we have error output and that error is sudo or su stating the user
|
|
291
|
+
# does not exist, do not fail but instead return the default fact value.
|
|
292
|
+
# This allows for users that don't currently but may be created during
|
|
293
|
+
# other operations.
|
|
294
|
+
first_line = stderr_lines[0]
|
|
295
|
+
if executor_kwargs["_sudo_user"] and re.match(SUDO_REGEX, first_line):
|
|
296
|
+
status = True
|
|
297
|
+
if executor_kwargs["_su_user"] and any(re.match(regex, first_line) for regex in SU_REGEXES):
|
|
298
|
+
status = True
|
|
299
|
+
|
|
300
|
+
if status:
|
|
301
|
+
log_message = "{0}{1}".format(
|
|
302
|
+
host.print_prefix,
|
|
303
|
+
"Loaded fact {0}{1}".format(
|
|
304
|
+
click.style(name, bold=True),
|
|
305
|
+
f" ({get_kwargs_str(kwargs)})" if kwargs else "",
|
|
306
|
+
),
|
|
307
|
+
)
|
|
308
|
+
if state.print_fact_info:
|
|
309
|
+
logger.info(log_message)
|
|
310
|
+
else:
|
|
311
|
+
logger.debug(log_message)
|
|
312
|
+
else:
|
|
313
|
+
if not state.print_fact_output:
|
|
314
|
+
print_host_combined_output(host, output)
|
|
250
315
|
|
|
251
|
-
|
|
252
|
-
|
|
316
|
+
log_error_or_warning(
|
|
317
|
+
host,
|
|
318
|
+
global_kwargs["_ignore_errors"],
|
|
319
|
+
description=("could not load fact: {0} {1}").format(name, get_kwargs_str(fact_kwargs)),
|
|
320
|
+
)
|
|
253
321
|
|
|
254
|
-
#
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
fact_data = get_facts(state, name, ensure_hosts=(host,))
|
|
322
|
+
# Check we've not failed
|
|
323
|
+
if apply_failed_hosts and not status and not global_kwargs["_ignore_errors"]:
|
|
324
|
+
state.fail_hosts({host})
|
|
258
325
|
|
|
259
|
-
|
|
326
|
+
return data
|