pyinfra 0.11.dev3__py3-none-any.whl → 3.5.1__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 +18 -3
- pyinfra/api/arguments.py +406 -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 +67 -18
- pyinfra/api/facts.py +253 -202
- pyinfra/api/host.py +413 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +291 -368
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +381 -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 +670 -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 +410 -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 +630 -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 +746 -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 +98 -0
- pyinfra/operations/apt.py +488 -0
- pyinfra/operations/brew.py +231 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +191 -0
- pyinfra/operations/dnf.py +210 -0
- pyinfra/operations/docker.py +446 -0
- pyinfra/operations/files.py +1939 -0
- pyinfra/operations/flatpak.py +94 -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 +47 -0
- pyinfra/operations/git.py +419 -0
- pyinfra/operations/iptables.py +311 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +68 -0
- pyinfra/operations/mysql.py +609 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +88 -0
- pyinfra/operations/pacman.py +81 -0
- pyinfra/operations/pip.py +205 -0
- pyinfra/operations/pipx.py +102 -0
- pyinfra/operations/pkg.py +70 -0
- pyinfra/operations/pkgin.py +91 -0
- pyinfra/operations/postgres.py +436 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +40 -0
- pyinfra/operations/python.py +72 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +189 -0
- pyinfra/operations/server.py +1099 -0
- pyinfra/operations/snap.py +117 -0
- pyinfra/operations/ssh.py +216 -0
- pyinfra/operations/systemd.py +149 -0
- pyinfra/operations/sysvinit.py +141 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +251 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +336 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +77 -0
- pyinfra/operations/yum.py +210 -0
- pyinfra/operations/zfs.py +175 -0
- pyinfra/operations/zypper.py +192 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.5.1.dist-info/METADATA +141 -0
- pyinfra-3.5.1.dist-info/RECORD +159 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
- pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +780 -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,310 @@
|
|
|
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
|
|
20
27
|
from pyinfra.api.util import (
|
|
21
|
-
|
|
28
|
+
get_kwargs_str,
|
|
29
|
+
log_error_or_warning,
|
|
22
30
|
log_host_command_error,
|
|
23
|
-
|
|
24
|
-
underscore,
|
|
31
|
+
print_host_combined_output,
|
|
25
32
|
)
|
|
33
|
+
from pyinfra.connectors.util import CommandOutput
|
|
34
|
+
from pyinfra.context import ctx_host, ctx_state
|
|
26
35
|
from pyinfra.progress import progress_spinner
|
|
27
36
|
|
|
37
|
+
from .arguments import CONNECTOR_ARGUMENT_KEYS
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
FACT_LOCK = BoundedSemaphore()
|
|
39
|
+
if TYPE_CHECKING:
|
|
40
|
+
from pyinfra.api import Host, State
|
|
32
41
|
|
|
42
|
+
SUDO_REGEX = r"^sudo: unknown user"
|
|
43
|
+
SU_REGEXES = (
|
|
44
|
+
r"^su: user .+ does not exist",
|
|
45
|
+
r"^su: unknown login",
|
|
46
|
+
)
|
|
33
47
|
|
|
34
|
-
def is_fact(name):
|
|
35
|
-
return name in FACTS
|
|
36
48
|
|
|
49
|
+
T = TypeVar("T")
|
|
37
50
|
|
|
38
|
-
def get_fact_names():
|
|
39
|
-
'''
|
|
40
|
-
Returns a list of available facts in camel_case format.
|
|
41
|
-
'''
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
class FactBase(Generic[T]):
|
|
53
|
+
name: str
|
|
44
54
|
|
|
55
|
+
abstract: bool = True
|
|
45
56
|
|
|
46
|
-
|
|
47
|
-
'''
|
|
48
|
-
Metaclass to dynamically build the facts index.
|
|
49
|
-
'''
|
|
57
|
+
shell_executable: str | None = None
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
if attrs.get('abstract'):
|
|
53
|
-
return
|
|
59
|
+
command: Callable[..., str | StringCommand]
|
|
54
60
|
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
def requires_command(self, *args, **kwargs) -> str | None:
|
|
62
|
+
return None
|
|
57
63
|
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
@override
|
|
65
|
+
def __init_subclass__(cls) -> None:
|
|
66
|
+
super().__init_subclass__()
|
|
67
|
+
module_name = cls.__module__.replace("pyinfra.facts.", "")
|
|
68
|
+
cls.name = f"{module_name}.{cls.__name__}"
|
|
60
69
|
|
|
70
|
+
# Check that fact's `command` method does not inadvertently take a global
|
|
71
|
+
# argument, most commonly `name`.
|
|
72
|
+
if hasattr(cls, "command") and callable(cls.command):
|
|
73
|
+
command_args = set(inspect.signature(cls.command).parameters.keys())
|
|
74
|
+
global_args = set([name for name, _ in all_global_arguments()])
|
|
75
|
+
command_global_args = command_args & global_args
|
|
61
76
|
|
|
62
|
-
|
|
63
|
-
|
|
77
|
+
if len(command_global_args) > 0:
|
|
78
|
+
names = ", ".join(command_global_args)
|
|
79
|
+
raise TypeError(f"{cls.name}'s arguments {names} are reserved for global arguments")
|
|
64
80
|
|
|
65
81
|
@staticmethod
|
|
66
|
-
def default():
|
|
67
|
-
|
|
82
|
+
def default() -> T:
|
|
83
|
+
"""
|
|
68
84
|
Set the default attribute to be a type (eg list/dict).
|
|
69
|
-
|
|
85
|
+
"""
|
|
70
86
|
|
|
71
|
-
|
|
72
|
-
def process(output):
|
|
73
|
-
return '\n'.join(output)
|
|
87
|
+
return cast(T, None)
|
|
74
88
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for i, arg in enumerate(args)
|
|
79
|
-
}
|
|
89
|
+
def process(self, output: Iterable[str]) -> T:
|
|
90
|
+
# NOTE: TypeVar does not support a default, so we have to cast this str -> T
|
|
91
|
+
return cast(T, "\n".join(output))
|
|
80
92
|
|
|
93
|
+
def process_pipeline(self, args, output):
|
|
94
|
+
return {arg: self.process([output[i]]) for i, arg in enumerate(args)}
|
|
81
95
|
|
|
82
|
-
class ShortFactBase(object, metaclass=FactMeta):
|
|
83
|
-
fact = None
|
|
84
96
|
|
|
97
|
+
class ShortFactBase(Generic[T]):
|
|
98
|
+
name: str
|
|
99
|
+
fact: Type[FactBase]
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
@override
|
|
102
|
+
def __init_subclass__(cls) -> None:
|
|
103
|
+
super().__init_subclass__()
|
|
104
|
+
module_name = cls.__module__.replace("pyinfra.facts.", "")
|
|
105
|
+
cls.name = f"{module_name}.{cls.__name__}"
|
|
88
106
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for host, data in facts.items()
|
|
92
|
-
}
|
|
107
|
+
def process_data(self, data):
|
|
108
|
+
return data
|
|
93
109
|
|
|
94
110
|
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
'''
|
|
111
|
+
def get_short_facts(state: "State", host: "Host", short_fact, **kwargs):
|
|
112
|
+
fact_data = get_fact(state, host, short_fact.fact, **kwargs)
|
|
113
|
+
return short_fact().process_data(fact_data)
|
|
99
114
|
|
|
100
|
-
# Create an instance of the fact
|
|
101
|
-
fact = FACTS[name]()
|
|
102
115
|
|
|
103
|
-
|
|
104
|
-
|
|
116
|
+
def _make_command(command_attribute, host_args):
|
|
117
|
+
if callable(command_attribute):
|
|
118
|
+
host_args.pop("self", None)
|
|
119
|
+
return command_attribute(**host_args)
|
|
120
|
+
return command_attribute
|
|
105
121
|
|
|
106
|
-
logger.debug('Getting fact: {0} (ensure_hosts: {1})'.format(
|
|
107
|
-
name, ensure_hosts,
|
|
108
|
-
))
|
|
109
122
|
|
|
123
|
+
def _handle_fact_kwargs(state: "State", host: "Host", cls, args, kwargs):
|
|
110
124
|
args = args or []
|
|
125
|
+
kwargs = kwargs or {}
|
|
111
126
|
|
|
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 = []
|
|
199
|
-
|
|
200
|
-
try:
|
|
201
|
-
status, stdout, _ = greenlet.get()
|
|
202
|
-
|
|
203
|
-
except (timeout_error, socket_error, SSHException) as e:
|
|
204
|
-
failed_hosts.add(host)
|
|
205
|
-
log_host_command_error(
|
|
206
|
-
host, e,
|
|
207
|
-
timeout=timeout,
|
|
208
|
-
)
|
|
209
|
-
|
|
210
|
-
data = fact.default()
|
|
211
|
-
|
|
212
|
-
if status and stdout:
|
|
213
|
-
data = fact.process(stdout)
|
|
214
|
-
|
|
215
|
-
hostname_facts[host] = data
|
|
216
|
-
|
|
217
|
-
log_name = click.style(name, bold=True)
|
|
218
|
-
|
|
219
|
-
filtered_args = list(filter(None, args))
|
|
220
|
-
if filtered_args:
|
|
221
|
-
log = 'Loaded fact {0}: {1}'.format(log_name, tuple(filtered_args))
|
|
222
|
-
else:
|
|
223
|
-
log = 'Loaded fact {0}'.format(log_name)
|
|
127
|
+
# Start with a (shallow) copy of current operation kwargs if any
|
|
128
|
+
ctx_kwargs: dict[str, Any] = (
|
|
129
|
+
cast(dict[str, Any], host.current_op_global_arguments) or {}
|
|
130
|
+
).copy()
|
|
131
|
+
# Update with the input kwargs (overrides)
|
|
132
|
+
ctx_kwargs.update(kwargs)
|
|
224
133
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
else:
|
|
228
|
-
logger.debug(log)
|
|
134
|
+
# Pop executor kwargs, pass remaining
|
|
135
|
+
global_kwargs, _ = pop_global_arguments(state, host, cast(dict[str, Any], ctx_kwargs))
|
|
229
136
|
|
|
230
|
-
|
|
231
|
-
if not ignore_errors:
|
|
232
|
-
state.fail_hosts(failed_hosts)
|
|
137
|
+
fact_kwargs = {key: value for key, value in kwargs.items() if key not in global_kwargs}
|
|
233
138
|
|
|
234
|
-
|
|
235
|
-
|
|
139
|
+
if args or fact_kwargs:
|
|
140
|
+
# Merges args & kwargs into a single kwargs dictionary
|
|
141
|
+
fact_kwargs = getcallargs(cls().command, *args, **fact_kwargs)
|
|
236
142
|
|
|
237
|
-
return
|
|
143
|
+
return fact_kwargs, global_kwargs
|
|
238
144
|
|
|
239
145
|
|
|
240
|
-
def
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
'''
|
|
146
|
+
def get_facts(state, *args, **kwargs):
|
|
147
|
+
def get_host_fact(host, *args, **kwargs):
|
|
148
|
+
with ctx_host.use(host):
|
|
149
|
+
return get_fact(state, host, *args, **kwargs)
|
|
245
150
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
151
|
+
with ctx_state.use(state):
|
|
152
|
+
greenlet_to_host = {
|
|
153
|
+
state.pool.spawn(get_host_fact, host, *args, **kwargs): host
|
|
154
|
+
for host in state.inventory.iter_active_hosts()
|
|
155
|
+
}
|
|
250
156
|
|
|
251
|
-
|
|
252
|
-
|
|
157
|
+
results = {}
|
|
158
|
+
|
|
159
|
+
with progress_spinner(greenlet_to_host.values()) as progress:
|
|
160
|
+
for greenlet in gevent.iwait(greenlet_to_host.keys()):
|
|
161
|
+
host = greenlet_to_host[greenlet]
|
|
162
|
+
results[host] = greenlet.get()
|
|
163
|
+
progress(host)
|
|
164
|
+
|
|
165
|
+
return results
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_fact(
|
|
169
|
+
state: "State",
|
|
170
|
+
host: "Host",
|
|
171
|
+
cls: type[FactBase],
|
|
172
|
+
args: Optional[Any] = None,
|
|
173
|
+
kwargs: Optional[Any] = None,
|
|
174
|
+
ensure_hosts: Optional[Any] = None,
|
|
175
|
+
apply_failed_hosts: bool = True,
|
|
176
|
+
) -> Any:
|
|
177
|
+
if issubclass(cls, ShortFactBase):
|
|
178
|
+
return get_short_facts(
|
|
179
|
+
state,
|
|
180
|
+
host,
|
|
181
|
+
cls,
|
|
182
|
+
args=args,
|
|
183
|
+
kwargs=kwargs,
|
|
184
|
+
ensure_hosts=ensure_hosts,
|
|
185
|
+
apply_failed_hosts=apply_failed_hosts,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return _get_fact(
|
|
189
|
+
state,
|
|
190
|
+
host,
|
|
191
|
+
cls,
|
|
192
|
+
args,
|
|
193
|
+
kwargs,
|
|
194
|
+
ensure_hosts,
|
|
195
|
+
apply_failed_hosts,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _get_fact(
|
|
200
|
+
state: "State",
|
|
201
|
+
host: "Host",
|
|
202
|
+
cls: type[FactBase],
|
|
203
|
+
args: Optional[list] = None,
|
|
204
|
+
kwargs: Optional[dict] = None,
|
|
205
|
+
ensure_hosts: Optional[Any] = None,
|
|
206
|
+
apply_failed_hosts: bool = True,
|
|
207
|
+
) -> Any:
|
|
208
|
+
fact = cls()
|
|
209
|
+
name = fact.name
|
|
210
|
+
|
|
211
|
+
fact_kwargs, global_kwargs = _handle_fact_kwargs(state, host, cls, args, kwargs)
|
|
212
|
+
|
|
213
|
+
kwargs_str = get_kwargs_str(fact_kwargs)
|
|
214
|
+
logger.debug(
|
|
215
|
+
"Getting fact: %s (%s) (ensure_hosts: %r)",
|
|
216
|
+
name,
|
|
217
|
+
kwargs_str,
|
|
218
|
+
ensure_hosts,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if not host.connected:
|
|
222
|
+
host.connect(
|
|
223
|
+
reason=f"to load fact: {name} ({kwargs_str})",
|
|
224
|
+
raise_exceptions=True,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
# Facts can override the shell (winrm powershell vs cmd support)
|
|
228
|
+
if fact.shell_executable:
|
|
229
|
+
global_kwargs["_shell_executable"] = fact.shell_executable
|
|
230
|
+
|
|
231
|
+
command = _make_command(fact.command, fact_kwargs)
|
|
232
|
+
requires_command = _make_command(fact.requires_command, fact_kwargs)
|
|
233
|
+
if requires_command:
|
|
234
|
+
command = StringCommand(
|
|
235
|
+
# Command doesn't exist, return 0 *or* run & return fact command
|
|
236
|
+
"!",
|
|
237
|
+
"command",
|
|
238
|
+
"-v",
|
|
239
|
+
requires_command,
|
|
240
|
+
">/dev/null",
|
|
241
|
+
"||",
|
|
242
|
+
command,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
status = False
|
|
246
|
+
output = CommandOutput([])
|
|
247
|
+
|
|
248
|
+
executor_kwargs = {
|
|
249
|
+
key: value for key, value in global_kwargs.items() if key in CONNECTOR_ARGUMENT_KEYS
|
|
250
|
+
}
|
|
253
251
|
|
|
254
|
-
|
|
252
|
+
try:
|
|
253
|
+
status, output = host.run_shell_command(
|
|
254
|
+
command,
|
|
255
|
+
print_output=state.print_fact_output,
|
|
256
|
+
print_input=state.print_fact_input,
|
|
257
|
+
**executor_kwargs,
|
|
258
|
+
)
|
|
259
|
+
except (timeout_error, socket_error, SSHException) as e:
|
|
260
|
+
log_host_command_error(
|
|
261
|
+
host,
|
|
262
|
+
e,
|
|
263
|
+
timeout=global_kwargs.get("_timeout"),
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
stdout_lines, stderr_lines = output.stdout_lines, output.stderr_lines
|
|
267
|
+
|
|
268
|
+
data = fact.default()
|
|
269
|
+
|
|
270
|
+
if status:
|
|
271
|
+
if stdout_lines:
|
|
272
|
+
data = fact.process(stdout_lines)
|
|
273
|
+
elif stderr_lines:
|
|
274
|
+
# If we have error output and that error is sudo or su stating the user
|
|
275
|
+
# does not exist, do not fail but instead return the default fact value.
|
|
276
|
+
# This allows for users that don't currently but may be created during
|
|
277
|
+
# other operations.
|
|
278
|
+
first_line = stderr_lines[0]
|
|
279
|
+
if executor_kwargs["_sudo_user"] and re.match(SUDO_REGEX, first_line):
|
|
280
|
+
status = True
|
|
281
|
+
if executor_kwargs["_su_user"] and any(re.match(regex, first_line) for regex in SU_REGEXES):
|
|
282
|
+
status = True
|
|
283
|
+
|
|
284
|
+
if status:
|
|
285
|
+
log_message = "{0}{1}".format(
|
|
286
|
+
host.print_prefix,
|
|
287
|
+
"Loaded fact {0}{1}".format(
|
|
288
|
+
click.style(name, bold=True),
|
|
289
|
+
f" ({get_kwargs_str(kwargs)})" if kwargs else "",
|
|
290
|
+
),
|
|
291
|
+
)
|
|
292
|
+
if state.print_fact_info:
|
|
293
|
+
logger.info(log_message)
|
|
294
|
+
else:
|
|
295
|
+
logger.debug(log_message)
|
|
255
296
|
else:
|
|
256
|
-
|
|
257
|
-
|
|
297
|
+
if not state.print_fact_output:
|
|
298
|
+
print_host_combined_output(host, output)
|
|
299
|
+
|
|
300
|
+
log_error_or_warning(
|
|
301
|
+
host,
|
|
302
|
+
global_kwargs["_ignore_errors"],
|
|
303
|
+
description=("could not load fact: {0} {1}").format(name, get_kwargs_str(fact_kwargs)),
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Check we've not failed
|
|
307
|
+
if apply_failed_hosts and not status and not global_kwargs["_ignore_errors"]:
|
|
308
|
+
state.fail_hosts({host})
|
|
258
309
|
|
|
259
|
-
|
|
310
|
+
return data
|