pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__py2.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/api/__init__.py +3 -0
- pyinfra/api/arguments.py +261 -255
- pyinfra/api/arguments_typed.py +77 -0
- pyinfra/api/command.py +66 -53
- pyinfra/api/config.py +27 -22
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +2 -24
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +77 -113
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +17 -25
- pyinfra/api/operation.py +232 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +55 -70
- pyinfra/connectors/base.py +150 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +227 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +195 -207
- pyinfra/connectors/ssh.py +528 -615
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +212 -137
- pyinfra/connectors/vagrant.py +55 -48
- pyinfra/context.py +3 -2
- pyinfra/facts/docker.py +1 -0
- pyinfra/facts/files.py +45 -32
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +1 -1
- pyinfra/facts/hardware.py +4 -2
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/mysql.py +1 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -161
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +77 -30
- pyinfra/facts/systemd.py +29 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/util/packaging.py +4 -2
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +3 -3
- pyinfra/operations/apt.py +25 -47
- pyinfra/operations/brew.py +7 -14
- pyinfra/operations/bsdinit.py +4 -4
- pyinfra/operations/cargo.py +1 -1
- pyinfra/operations/choco.py +1 -1
- pyinfra/operations/dnf.py +4 -4
- pyinfra/operations/files.py +108 -321
- pyinfra/operations/gem.py +1 -1
- pyinfra/operations/git.py +6 -37
- pyinfra/operations/iptables.py +2 -10
- pyinfra/operations/launchd.py +1 -1
- pyinfra/operations/lxd.py +1 -9
- pyinfra/operations/mysql.py +5 -28
- pyinfra/operations/npm.py +1 -1
- pyinfra/operations/openrc.py +1 -1
- pyinfra/operations/pacman.py +3 -3
- pyinfra/operations/pip.py +14 -15
- pyinfra/operations/pkg.py +1 -1
- pyinfra/operations/pkgin.py +3 -3
- pyinfra/operations/postgres.py +347 -0
- pyinfra/operations/postgresql.py +17 -380
- pyinfra/operations/python.py +2 -17
- pyinfra/operations/selinux.py +5 -28
- pyinfra/operations/server.py +59 -84
- pyinfra/operations/snap.py +1 -3
- pyinfra/operations/ssh.py +8 -23
- pyinfra/operations/systemd.py +7 -7
- pyinfra/operations/sysvinit.py +3 -12
- pyinfra/operations/upstart.py +4 -4
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/files.py +2 -2
- pyinfra/operations/util/packaging.py +6 -24
- pyinfra/operations/util/service.py +18 -37
- pyinfra/operations/vzctl.py +2 -2
- pyinfra/operations/xbps.py +3 -3
- pyinfra/operations/yum.py +4 -4
- pyinfra/operations/zypper.py +4 -4
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
- pyinfra-3.0b1.dist-info/RECORD +163 -0
- pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +2 -0
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +83 -42
- pyinfra_cli/inventory.py +19 -4
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +93 -129
- pyinfra_cli/util.py +60 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +100 -200
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +66 -107
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +4 -4
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.2.dist-info/RECORD +0 -170
- pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra/api/host.py
CHANGED
|
@@ -1,22 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from contextlib import contextmanager
|
|
2
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
TYPE_CHECKING,
|
|
6
|
+
Any,
|
|
7
|
+
Callable,
|
|
8
|
+
Generator,
|
|
9
|
+
Optional,
|
|
10
|
+
Type,
|
|
11
|
+
TypeVar,
|
|
12
|
+
Union,
|
|
13
|
+
cast,
|
|
14
|
+
overload,
|
|
15
|
+
)
|
|
16
|
+
from uuid import uuid4
|
|
3
17
|
|
|
4
18
|
import click
|
|
5
|
-
from
|
|
19
|
+
from typing_extensions import Unpack
|
|
6
20
|
|
|
7
21
|
from pyinfra import logger
|
|
8
|
-
from pyinfra.connectors.
|
|
22
|
+
from pyinfra.connectors.base import BaseConnector
|
|
23
|
+
from pyinfra.connectors.util import CommandOutput, remove_any_sudo_askpass_file
|
|
9
24
|
|
|
10
25
|
from .connectors import get_execution_connector
|
|
11
26
|
from .exceptions import ConnectError
|
|
12
|
-
from .facts import
|
|
27
|
+
from .facts import FactBase, ShortFactBase, get_host_fact
|
|
28
|
+
from .util import memoize, sha1_hash
|
|
13
29
|
|
|
14
30
|
if TYPE_CHECKING:
|
|
31
|
+
from pyinfra.api.arguments import AllArguments
|
|
15
32
|
from pyinfra.api.inventory import Inventory
|
|
16
33
|
from pyinfra.api.state import State
|
|
17
34
|
|
|
18
35
|
|
|
19
|
-
def extract_callable_datas(datas:
|
|
36
|
+
def extract_callable_datas(datas: list[Union[Callable[..., Any], Any]]) -> Generator[Any, Any, Any]:
|
|
20
37
|
for data in datas:
|
|
21
38
|
# Support for dynamic data, ie @deploy wrapped data defaults where
|
|
22
39
|
# the data is stored on the state temporarily.
|
|
@@ -31,7 +48,7 @@ class HostData:
|
|
|
31
48
|
Combines multiple AttrData's to search for attributes.
|
|
32
49
|
"""
|
|
33
50
|
|
|
34
|
-
override_datas:
|
|
51
|
+
override_datas: dict[str, Any]
|
|
35
52
|
|
|
36
53
|
def __init__(self, host: "Host", *datas):
|
|
37
54
|
self.__dict__["host"] = host
|
|
@@ -82,28 +99,36 @@ class Host:
|
|
|
82
99
|
data.
|
|
83
100
|
"""
|
|
84
101
|
|
|
85
|
-
connection = None
|
|
86
102
|
state: "State"
|
|
103
|
+
connector_cls: type[BaseConnector]
|
|
104
|
+
connector: BaseConnector
|
|
105
|
+
connected: bool = False
|
|
87
106
|
|
|
88
107
|
# Current context inside an @operation function (op gen stage)
|
|
89
108
|
in_op: bool = False
|
|
109
|
+
in_callback_op: bool = False
|
|
90
110
|
current_op_hash: Optional[str] = None
|
|
91
|
-
|
|
111
|
+
current_op_global_arguments: Optional["AllArguments"] = None
|
|
92
112
|
|
|
93
|
-
# Current context inside a @deploy function
|
|
113
|
+
# Current context inside a @deploy function which become part of the op data
|
|
94
114
|
in_deploy: bool = False
|
|
95
115
|
current_deploy_name: Optional[str] = None
|
|
96
116
|
current_deploy_kwargs = None
|
|
97
|
-
|
|
117
|
+
|
|
118
|
+
# @deploy decorator data is a bit different - we need to handle the case
|
|
119
|
+
# where we're evaluating an operation at runtime (current_op_) but also
|
|
120
|
+
# when ordering operations (current_) outside of an operation context.
|
|
121
|
+
current_op_deploy_data: Optional[dict[str, Any]] = None
|
|
122
|
+
current_deploy_data: Optional[dict[str, Any]] = None
|
|
98
123
|
|
|
99
124
|
# Current context during operation execution
|
|
100
|
-
executing_op_hash = None
|
|
101
|
-
nested_executing_op_hash = None
|
|
125
|
+
executing_op_hash: Optional[str] = None
|
|
126
|
+
nested_executing_op_hash: Optional[str] = None
|
|
102
127
|
|
|
103
|
-
loop_position:
|
|
128
|
+
loop_position: list[int]
|
|
104
129
|
|
|
105
130
|
# Arbitrary data dictionary connectors may use
|
|
106
|
-
connector_data:
|
|
131
|
+
connector_data: dict[str, Any]
|
|
107
132
|
|
|
108
133
|
def loop(self, iterable):
|
|
109
134
|
self.loop_position.append(0)
|
|
@@ -117,26 +142,20 @@ class Host:
|
|
|
117
142
|
name: str,
|
|
118
143
|
inventory: "Inventory",
|
|
119
144
|
groups,
|
|
120
|
-
|
|
145
|
+
connector_cls=get_execution_connector("ssh"),
|
|
121
146
|
):
|
|
122
147
|
self.inventory = inventory
|
|
123
148
|
self.groups = groups
|
|
124
|
-
self.
|
|
149
|
+
self.connector_cls = connector_cls
|
|
125
150
|
self.name = name
|
|
126
151
|
|
|
127
152
|
self.loop_position = []
|
|
128
153
|
|
|
129
154
|
self.connector_data = {}
|
|
130
|
-
self.current_op_global_kwargs = {}
|
|
131
|
-
|
|
132
|
-
# Fact data store
|
|
133
|
-
# TODO: how to not have Any here?
|
|
134
|
-
self.facts: Dict[str, Any] = {}
|
|
135
|
-
self.facts_lock = BoundedSemaphore()
|
|
136
155
|
|
|
137
156
|
# Append only list of operation hashes as called on this host, used to
|
|
138
157
|
# generate a DAG to create the final operation order.
|
|
139
|
-
self.op_hash_order:
|
|
158
|
+
self.op_hash_order: list[str] = []
|
|
140
159
|
|
|
141
160
|
# Create the (waterfall data: override, host, group, global)
|
|
142
161
|
self.data = HostData(
|
|
@@ -149,16 +168,20 @@ class Host:
|
|
|
149
168
|
self.get_deploy_data,
|
|
150
169
|
)
|
|
151
170
|
|
|
171
|
+
def init(self, state: "State") -> None:
|
|
172
|
+
self.state = state
|
|
173
|
+
self.connector = self.connector_cls(state, self)
|
|
174
|
+
|
|
175
|
+
longest_name_len = max([len(host.name) for host in self.inventory])
|
|
176
|
+
padding_diff = longest_name_len - len(self.name)
|
|
177
|
+
self.print_prefix_padding = "".join(" " for _ in range(0, padding_diff))
|
|
178
|
+
|
|
152
179
|
def __str__(self):
|
|
153
180
|
return "{0}".format(self.name)
|
|
154
181
|
|
|
155
182
|
def __repr__(self):
|
|
156
183
|
return "Host({0})".format(self.name)
|
|
157
184
|
|
|
158
|
-
@property
|
|
159
|
-
def connected(self) -> bool:
|
|
160
|
-
return self.connection is not None
|
|
161
|
-
|
|
162
185
|
@property
|
|
163
186
|
def host_data(self):
|
|
164
187
|
return self.inventory.get_host_data(self.name)
|
|
@@ -168,30 +191,37 @@ class Host:
|
|
|
168
191
|
return self.inventory.get_groups_data(self.groups)
|
|
169
192
|
|
|
170
193
|
@property
|
|
171
|
-
def print_prefix(self):
|
|
194
|
+
def print_prefix(self) -> str:
|
|
172
195
|
if self.nested_executing_op_hash:
|
|
173
|
-
return "{0}[{1}] {2} ".format(
|
|
196
|
+
return "{0}[{1}] {2}{3} ".format(
|
|
174
197
|
click.style(""), # reset
|
|
175
198
|
click.style(self.name, bold=True),
|
|
176
199
|
click.style("nested", "blue"),
|
|
200
|
+
self.print_prefix_padding,
|
|
177
201
|
)
|
|
178
202
|
|
|
179
|
-
return "{0}[{1}] ".format(
|
|
203
|
+
return "{0}[{1}]{2} ".format(
|
|
180
204
|
click.style(""), # reset
|
|
181
205
|
click.style(self.name, bold=True),
|
|
206
|
+
self.print_prefix_padding,
|
|
182
207
|
)
|
|
183
208
|
|
|
184
|
-
def style_print_prefix(self, *args, **kwargs):
|
|
185
|
-
return "{0}[{1}] ".format(
|
|
209
|
+
def style_print_prefix(self, *args, **kwargs) -> str:
|
|
210
|
+
return "{0}[{1}]{2} ".format(
|
|
186
211
|
click.style(""), # reset
|
|
187
212
|
click.style(self.name, *args, **kwargs),
|
|
213
|
+
self.print_prefix_padding,
|
|
188
214
|
)
|
|
189
215
|
|
|
190
|
-
def
|
|
191
|
-
|
|
192
|
-
|
|
216
|
+
def log(self, message, log_func=logger.info):
|
|
217
|
+
log_func(f"{self.print_prefix}{message}")
|
|
218
|
+
|
|
219
|
+
def log_styled(self, message, log_func=logger.info, **kwargs):
|
|
220
|
+
message_styled = click.style(message, **kwargs)
|
|
221
|
+
self.log(message_styled, log_func=log_func)
|
|
193
222
|
|
|
194
|
-
|
|
223
|
+
def get_deploy_data(self):
|
|
224
|
+
return self.current_op_deploy_data or self.current_deploy_data or {}
|
|
195
225
|
|
|
196
226
|
def noop(self, description):
|
|
197
227
|
"""
|
|
@@ -201,8 +231,25 @@ class Host:
|
|
|
201
231
|
handler = logger.info if self.state.print_noop_info else logger.debug
|
|
202
232
|
handler("{0}noop: {1}".format(self.print_prefix, description))
|
|
203
233
|
|
|
234
|
+
def when(self, condition: Callable[[], bool]):
|
|
235
|
+
return self.deploy(
|
|
236
|
+
"",
|
|
237
|
+
cast("AllArguments", {"_if": [condition]}),
|
|
238
|
+
{},
|
|
239
|
+
in_deploy=False,
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def arguments(self, **arguments: Unpack["AllArguments"]):
|
|
243
|
+
return self.deploy("", arguments, {}, in_deploy=False)
|
|
244
|
+
|
|
204
245
|
@contextmanager
|
|
205
|
-
def deploy(
|
|
246
|
+
def deploy(
|
|
247
|
+
self,
|
|
248
|
+
name: str,
|
|
249
|
+
kwargs: Optional["AllArguments"],
|
|
250
|
+
data: Optional[dict],
|
|
251
|
+
in_deploy: bool = True,
|
|
252
|
+
):
|
|
206
253
|
"""
|
|
207
254
|
Wraps a group of operations as a deploy, this should not be used
|
|
208
255
|
directly, instead use ``pyinfra.api.deploy.deploy``.
|
|
@@ -219,6 +266,13 @@ class Host:
|
|
|
219
266
|
old_deploy_data = self.current_deploy_data
|
|
220
267
|
self.in_deploy = in_deploy
|
|
221
268
|
|
|
269
|
+
# Combine any old _ifs with the new ones
|
|
270
|
+
if old_deploy_kwargs and kwargs:
|
|
271
|
+
old_ifs = old_deploy_kwargs["_if"]
|
|
272
|
+
new_ifs = kwargs["_if"]
|
|
273
|
+
if old_ifs and new_ifs:
|
|
274
|
+
kwargs["_if"] = old_ifs + new_ifs
|
|
275
|
+
|
|
222
276
|
# Set the new values
|
|
223
277
|
self.current_deploy_name = name
|
|
224
278
|
self.current_deploy_kwargs = kwargs
|
|
@@ -245,33 +299,55 @@ class Host:
|
|
|
245
299
|
old_deploy_data,
|
|
246
300
|
)
|
|
247
301
|
|
|
248
|
-
|
|
249
|
-
|
|
302
|
+
@memoize
|
|
303
|
+
def _get_temp_directory(self):
|
|
304
|
+
temp_directory = self.state.config.TEMP_DIR
|
|
250
305
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return get_host_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
|
|
306
|
+
if temp_directory is None:
|
|
307
|
+
# Unfortunate, but very hard to avoid, circular dependency, this method is memoized so
|
|
308
|
+
# performance isn't a concern.
|
|
309
|
+
from pyinfra.facts.server import TmpDir
|
|
256
310
|
|
|
257
|
-
|
|
258
|
-
"""
|
|
259
|
-
Get a fact for this host without using any cached value, always re-fetch the fact data
|
|
260
|
-
from the host and then cache it.
|
|
261
|
-
"""
|
|
262
|
-
return reload_host_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
|
|
311
|
+
temp_directory = self.get_fact(TmpDir)
|
|
263
312
|
|
|
264
|
-
|
|
313
|
+
if not temp_directory:
|
|
314
|
+
temp_directory = self.state.config.DEFAULT_TEMP_DIR
|
|
315
|
+
|
|
316
|
+
return temp_directory
|
|
317
|
+
|
|
318
|
+
def get_temp_filename(self, hash_key: Optional[str] = None, hash_filename: bool = True):
|
|
265
319
|
"""
|
|
266
|
-
|
|
320
|
+
Generate a temporary filename for this deploy.
|
|
267
321
|
"""
|
|
268
|
-
return create_host_fact(self.state, self, name_or_cls, data, kwargs=kwargs)
|
|
269
322
|
|
|
270
|
-
|
|
323
|
+
temp_directory = self._get_temp_directory()
|
|
324
|
+
|
|
325
|
+
if not hash_key:
|
|
326
|
+
hash_key = str(uuid4())
|
|
327
|
+
|
|
328
|
+
if hash_filename:
|
|
329
|
+
hash_key = sha1_hash(hash_key)
|
|
330
|
+
|
|
331
|
+
return "{0}/pyinfra-{1}".format(temp_directory, hash_key)
|
|
332
|
+
|
|
333
|
+
# Host facts
|
|
334
|
+
#
|
|
335
|
+
|
|
336
|
+
T = TypeVar("T")
|
|
337
|
+
|
|
338
|
+
@overload
|
|
339
|
+
def get_fact(self, name_or_cls: Type[FactBase[T]], *args, **kwargs) -> T:
|
|
340
|
+
...
|
|
341
|
+
|
|
342
|
+
@overload
|
|
343
|
+
def get_fact(self, name_or_cls: Type[ShortFactBase[T]], *args, **kwargs) -> T:
|
|
344
|
+
...
|
|
345
|
+
|
|
346
|
+
def get_fact(self, name_or_cls, *args, **kwargs):
|
|
271
347
|
"""
|
|
272
|
-
|
|
348
|
+
Get a fact for this host, reading from the cache if present.
|
|
273
349
|
"""
|
|
274
|
-
return
|
|
350
|
+
return get_host_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
|
|
275
351
|
|
|
276
352
|
# Connector proxy
|
|
277
353
|
#
|
|
@@ -286,11 +362,11 @@ class Host:
|
|
|
286
362
|
"""
|
|
287
363
|
|
|
288
364
|
self._check_state()
|
|
289
|
-
if not self.
|
|
365
|
+
if not self.connected:
|
|
290
366
|
self.state.trigger_callbacks("host_before_connect", self)
|
|
291
367
|
|
|
292
368
|
try:
|
|
293
|
-
self.
|
|
369
|
+
self.connector.connect()
|
|
294
370
|
except ConnectError as e:
|
|
295
371
|
if show_errors:
|
|
296
372
|
log_message = "{0}{1}".format(
|
|
@@ -316,8 +392,7 @@ class Host:
|
|
|
316
392
|
|
|
317
393
|
logger.info(log_message)
|
|
318
394
|
self.state.trigger_callbacks("host_connect", self)
|
|
319
|
-
|
|
320
|
-
return self.connection
|
|
395
|
+
self.connected = True
|
|
321
396
|
|
|
322
397
|
def disconnect(self):
|
|
323
398
|
"""
|
|
@@ -325,50 +400,43 @@ class Host:
|
|
|
325
400
|
"""
|
|
326
401
|
self._check_state()
|
|
327
402
|
|
|
328
|
-
# Disconnect is an optional function for
|
|
329
|
-
disconnect_func = getattr(self.
|
|
403
|
+
# Disconnect is an optional function for connectors if needed
|
|
404
|
+
disconnect_func = getattr(self.connector, "disconnect", None)
|
|
330
405
|
if disconnect_func:
|
|
331
|
-
return disconnect_func(
|
|
406
|
+
return disconnect_func()
|
|
332
407
|
|
|
333
408
|
# TODO: consider whether this should be here!
|
|
334
409
|
remove_any_sudo_askpass_file(self)
|
|
335
410
|
|
|
336
411
|
self.state.trigger_callbacks("host_disconnect", self)
|
|
337
412
|
|
|
338
|
-
def run_shell_command(self, *args, **kwargs):
|
|
413
|
+
def run_shell_command(self, *args, **kwargs) -> tuple[bool, CommandOutput]:
|
|
339
414
|
"""
|
|
340
415
|
Low level method to execute a shell command on the host via it's configured connector.
|
|
341
416
|
"""
|
|
342
417
|
self._check_state()
|
|
343
|
-
return self.
|
|
418
|
+
return self.connector.run_shell_command(*args, **kwargs)
|
|
344
419
|
|
|
345
|
-
def put_file(self, *args, **kwargs):
|
|
420
|
+
def put_file(self, *args, **kwargs) -> bool:
|
|
346
421
|
"""
|
|
347
422
|
Low level method to upload a file to the host via it's configured connector.
|
|
348
423
|
"""
|
|
349
424
|
self._check_state()
|
|
350
|
-
return self.
|
|
425
|
+
return self.connector.put_file(*args, **kwargs)
|
|
351
426
|
|
|
352
|
-
def get_file(self, *args, **kwargs):
|
|
427
|
+
def get_file(self, *args, **kwargs) -> bool:
|
|
353
428
|
"""
|
|
354
429
|
Low level method to download a file from the host via it's configured connector.
|
|
355
430
|
"""
|
|
356
431
|
self._check_state()
|
|
357
|
-
return self.
|
|
358
|
-
|
|
359
|
-
# Rsync - optional executor specific ability
|
|
432
|
+
return self.connector.get_file(*args, **kwargs)
|
|
360
433
|
|
|
361
|
-
|
|
362
|
-
check_can_rsync_func = getattr(self.executor, "check_can_rsync", None)
|
|
363
|
-
if check_can_rsync_func:
|
|
364
|
-
return check_can_rsync_func(self)
|
|
434
|
+
# Rsync - optional connector specific ability
|
|
365
435
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
),
|
|
370
|
-
)
|
|
436
|
+
def check_can_rsync(self) -> None:
|
|
437
|
+
self._check_state()
|
|
438
|
+
return self.connector.check_can_rsync()
|
|
371
439
|
|
|
372
|
-
def rsync(self, *args, **kwargs):
|
|
440
|
+
def rsync(self, *args, **kwargs) -> bool:
|
|
373
441
|
self._check_state()
|
|
374
|
-
return self.
|
|
442
|
+
return self.connector.rsync(*args, **kwargs)
|
pyinfra/api/inventory.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from collections import defaultdict
|
|
2
|
-
from typing import TYPE_CHECKING, Any, Iterator
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Iterator
|
|
3
5
|
|
|
4
6
|
from .connectors import get_all_connectors, get_execution_connectors
|
|
5
7
|
from .exceptions import NoConnectorError, NoGroupError, NoHostError
|
|
@@ -9,7 +11,7 @@ if TYPE_CHECKING:
|
|
|
9
11
|
from pyinfra.api.state import State
|
|
10
12
|
|
|
11
13
|
|
|
12
|
-
def extract_name_data(names:
|
|
14
|
+
def extract_name_data(names: list[Any]):
|
|
13
15
|
for name in names:
|
|
14
16
|
data = {}
|
|
15
17
|
|
|
@@ -38,8 +40,8 @@ class Inventory:
|
|
|
38
40
|
def __init__(self, names_data, override_data=None, **groups):
|
|
39
41
|
# Setup basics
|
|
40
42
|
self.groups = defaultdict(list) # lists of Host objects
|
|
41
|
-
self.host_data = defaultdict(dict) # dict of name -> data
|
|
42
|
-
self.group_data = defaultdict(dict) # dict of name -> data
|
|
43
|
+
self.host_data: dict[str, dict] = defaultdict(dict) # dict of name -> data
|
|
44
|
+
self.group_data: dict[str, dict] = defaultdict(dict) # dict of name -> data
|
|
43
45
|
self.override_data = override_data or {}
|
|
44
46
|
|
|
45
47
|
names, data = names_data
|
|
@@ -48,14 +50,14 @@ class Inventory:
|
|
|
48
50
|
self.data = data
|
|
49
51
|
|
|
50
52
|
# Create the actual host instances and groups
|
|
51
|
-
self.
|
|
53
|
+
self.make_hosts_and_groups(names, groups)
|
|
52
54
|
|
|
53
|
-
def make_hosts_and_groups(self, names, groups):
|
|
55
|
+
def make_hosts_and_groups(self, names, groups) -> None:
|
|
54
56
|
all_connectors = get_all_connectors()
|
|
55
57
|
execution_connectors = get_execution_connectors()
|
|
56
58
|
|
|
57
59
|
# Map name -> data
|
|
58
|
-
name_to_data = defaultdict(dict)
|
|
60
|
+
name_to_data: dict[str, dict] = defaultdict(dict)
|
|
59
61
|
# Map name -> group names
|
|
60
62
|
name_to_group_names = defaultdict(list)
|
|
61
63
|
|
|
@@ -73,14 +75,14 @@ class Inventory:
|
|
|
73
75
|
for name, data in extract_name_data(names):
|
|
74
76
|
name_to_data[name].update(data)
|
|
75
77
|
|
|
76
|
-
# Now, use the above to fill self.host_data and populate
|
|
77
|
-
|
|
78
|
+
# Now, use the above to fill self.host_data and populate names_connectors
|
|
79
|
+
names_connectors = []
|
|
78
80
|
|
|
79
81
|
for name, _ in extract_name_data(names):
|
|
80
82
|
host_data = name_to_data[name]
|
|
81
83
|
|
|
82
84
|
# Default to executing commands with the ssh connector
|
|
83
|
-
|
|
85
|
+
connector_cls = execution_connectors["ssh"]
|
|
84
86
|
|
|
85
87
|
if name[0] == "@":
|
|
86
88
|
connector_name = name[1:]
|
|
@@ -96,7 +98,7 @@ class Inventory:
|
|
|
96
98
|
|
|
97
99
|
# Execution connector? Simple, just set it for their host
|
|
98
100
|
if connector_name in execution_connectors:
|
|
99
|
-
|
|
101
|
+
connector_cls = execution_connectors[connector_name]
|
|
100
102
|
|
|
101
103
|
names_data = all_connectors[connector_name].make_names_data(arg_string)
|
|
102
104
|
connector_inventory_name = name
|
|
@@ -111,7 +113,7 @@ class Inventory:
|
|
|
111
113
|
|
|
112
114
|
# Assign the name/data/groups from the connector
|
|
113
115
|
self.host_data[sub_name] = sub_data
|
|
114
|
-
|
|
116
|
+
names_connectors.append((sub_name, connector_cls))
|
|
115
117
|
name_to_group_names[sub_name].extend(sub_groups)
|
|
116
118
|
|
|
117
119
|
# If we have a connector inventory name, copy any groups attached
|
|
@@ -124,10 +126,10 @@ class Inventory:
|
|
|
124
126
|
# Now we can actually make Host instances
|
|
125
127
|
hosts: dict[str, "Host"] = {}
|
|
126
128
|
|
|
127
|
-
for name,
|
|
129
|
+
for name, connector_cls in names_connectors:
|
|
128
130
|
host_groups = name_to_group_names[name]
|
|
129
131
|
|
|
130
|
-
host = Host(name, inventory=self, groups=host_groups,
|
|
132
|
+
host = Host(name, inventory=self, groups=host_groups, connector_cls=connector_cls)
|
|
131
133
|
hosts[name] = host
|
|
132
134
|
|
|
133
135
|
# And push into any groups
|
|
@@ -135,7 +137,7 @@ class Inventory:
|
|
|
135
137
|
if host not in self.groups[group_name]:
|
|
136
138
|
self.groups[group_name].append(host)
|
|
137
139
|
|
|
138
|
-
|
|
140
|
+
self.hosts = hosts
|
|
139
141
|
|
|
140
142
|
def __len__(self) -> int:
|
|
141
143
|
"""
|
|
@@ -241,13 +243,3 @@ class Inventory:
|
|
|
241
243
|
data.update(self.get_group_data(group))
|
|
242
244
|
|
|
243
245
|
return data
|
|
244
|
-
|
|
245
|
-
def get_deploy_data(self):
|
|
246
|
-
"""
|
|
247
|
-
Gets any default data attached to the current deploy, if any.
|
|
248
|
-
"""
|
|
249
|
-
|
|
250
|
-
if self.state and self.state.deploy_data:
|
|
251
|
-
return self.state.deploy_data
|
|
252
|
-
|
|
253
|
-
return {}
|