pyinfra 3.6__py3-none-any.whl → 3.6.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/api/arguments.py +2 -2
- pyinfra/api/arguments_typed.py +18 -23
- pyinfra/api/command.py +9 -3
- pyinfra/api/deploy.py +1 -1
- pyinfra/api/exceptions.py +6 -0
- pyinfra/api/facts.py +3 -3
- pyinfra/api/inventory.py +2 -2
- pyinfra/api/operation.py +9 -4
- pyinfra/api/operations.py +13 -11
- pyinfra/connectors/sshuserclient/client.py +47 -28
- pyinfra/facts/crontab.py +3 -1
- pyinfra/facts/files.py +12 -2
- pyinfra/facts/flatpak.py +1 -1
- pyinfra/operations/docker.py +8 -0
- pyinfra/operations/postgres.py +6 -1
- pyinfra/operations/util/docker.py +8 -0
- {pyinfra-3.6.dist-info → pyinfra-3.6.1.dist-info}/METADATA +1 -1
- {pyinfra-3.6.dist-info → pyinfra-3.6.1.dist-info}/RECORD +24 -24
- pyinfra_cli/cli.py +7 -4
- pyinfra_cli/inventory.py +26 -1
- pyinfra_cli/util.py +1 -1
- {pyinfra-3.6.dist-info → pyinfra-3.6.1.dist-info}/WHEEL +0 -0
- {pyinfra-3.6.dist-info → pyinfra-3.6.1.dist-info}/entry_points.txt +0 -0
- {pyinfra-3.6.dist-info → pyinfra-3.6.1.dist-info}/licenses/LICENSE.md +0 -0
pyinfra/api/arguments.py
CHANGED
|
@@ -70,12 +70,12 @@ class ConnectorArguments(TypedDict, total=False):
|
|
|
70
70
|
_success_exit_codes: Iterable[int]
|
|
71
71
|
_timeout: int
|
|
72
72
|
_get_pty: bool
|
|
73
|
-
_stdin: Union[str, Iterable[str]]
|
|
73
|
+
_stdin: Union[str, list[str], Iterable[str]]
|
|
74
74
|
|
|
75
75
|
# Retry arguments
|
|
76
76
|
_retries: int
|
|
77
77
|
_retry_delay: Union[int, float]
|
|
78
|
-
_retry_until:
|
|
78
|
+
_retry_until: Callable[[dict], bool]
|
|
79
79
|
|
|
80
80
|
# Temp directory argument
|
|
81
81
|
_temp_dir: str
|
pyinfra/api/arguments_typed.py
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
4
|
-
TYPE_CHECKING,
|
|
5
|
-
Callable,
|
|
6
|
-
Generator,
|
|
7
|
-
Generic,
|
|
8
|
-
Iterable,
|
|
9
|
-
List,
|
|
10
|
-
Mapping,
|
|
11
|
-
Optional,
|
|
12
|
-
Union,
|
|
13
|
-
)
|
|
3
|
+
from typing import TYPE_CHECKING, Callable, Generator, Generic, Iterable, List, Mapping, Union
|
|
14
4
|
|
|
15
5
|
from typing_extensions import ParamSpec, Protocol
|
|
16
6
|
|
|
@@ -36,36 +26,41 @@ class PyinfraOperation(Generic[P], Protocol):
|
|
|
36
26
|
#
|
|
37
27
|
# Auth
|
|
38
28
|
_sudo: bool = False,
|
|
39
|
-
_sudo_user:
|
|
29
|
+
_sudo_user: None | str = None,
|
|
40
30
|
_use_sudo_login: bool = False,
|
|
41
|
-
_sudo_password:
|
|
31
|
+
_sudo_password: None | str = None,
|
|
42
32
|
_preserve_sudo_env: bool = False,
|
|
43
|
-
_su_user:
|
|
33
|
+
_su_user: None | str = None,
|
|
44
34
|
_use_su_login: bool = False,
|
|
45
35
|
_preserve_su_env: bool = False,
|
|
46
|
-
_su_shell:
|
|
36
|
+
_su_shell: None | str = None,
|
|
47
37
|
_doas: bool = False,
|
|
48
|
-
_doas_user:
|
|
38
|
+
_doas_user: None | str = None,
|
|
49
39
|
# Shell arguments
|
|
50
|
-
_shell_executable:
|
|
51
|
-
_chdir:
|
|
52
|
-
_env:
|
|
40
|
+
_shell_executable: None | str = None,
|
|
41
|
+
_chdir: None | str = None,
|
|
42
|
+
_env: None | Mapping[str, str] = None,
|
|
53
43
|
# Connector control
|
|
54
44
|
_success_exit_codes: Iterable[int] = (0,),
|
|
55
|
-
_timeout:
|
|
45
|
+
_timeout: None | int = None,
|
|
56
46
|
_get_pty: bool = False,
|
|
57
|
-
_stdin: Union[
|
|
47
|
+
_stdin: None | Union[str, list[str], Iterable[str]] = None,
|
|
48
|
+
# Retry arguments
|
|
49
|
+
_retries: None | int = None,
|
|
50
|
+
_retry_delay: None | Union[int, float] = None,
|
|
51
|
+
_retry_until: None | Callable[[dict], bool] = None,
|
|
52
|
+
_temp_dir: None | str = None,
|
|
58
53
|
#
|
|
59
54
|
# MetaArguments
|
|
60
55
|
#
|
|
61
|
-
name:
|
|
56
|
+
name: None | str = None,
|
|
62
57
|
_ignore_errors: bool = False,
|
|
63
58
|
_continue_on_error: bool = False,
|
|
64
59
|
_if: Union[List[Callable[[], bool]], Callable[[], bool], None] = None,
|
|
65
60
|
#
|
|
66
61
|
# ExecutionArguments
|
|
67
62
|
#
|
|
68
|
-
_parallel:
|
|
63
|
+
_parallel: None | int = None,
|
|
69
64
|
_run_once: bool = False,
|
|
70
65
|
_serial: bool = False,
|
|
71
66
|
#
|
pyinfra/api/command.py
CHANGED
|
@@ -242,13 +242,19 @@ class FunctionCommand(PyinfraCommand):
|
|
|
242
242
|
self.function(*self.args, **self.kwargs)
|
|
243
243
|
return
|
|
244
244
|
|
|
245
|
-
def execute_function() -> None:
|
|
245
|
+
def execute_function() -> None | Exception:
|
|
246
246
|
with ctx_config.use(state.config.copy()):
|
|
247
247
|
with ctx_host.use(host):
|
|
248
|
-
|
|
248
|
+
try:
|
|
249
|
+
self.function(*self.args, **self.kwargs)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
return e
|
|
252
|
+
return None
|
|
249
253
|
|
|
250
254
|
greenlet = gevent.spawn(execute_function)
|
|
251
|
-
|
|
255
|
+
exception = greenlet.get()
|
|
256
|
+
if exception is not None:
|
|
257
|
+
raise exception
|
|
252
258
|
|
|
253
259
|
|
|
254
260
|
class RsyncCommand(PyinfraCommand):
|
pyinfra/api/deploy.py
CHANGED
|
@@ -41,7 +41,7 @@ def add_deploy(state: "State", deploy_func: Callable[..., Any], *args, **kwargs)
|
|
|
41
41
|
).format(get_call_location()),
|
|
42
42
|
)
|
|
43
43
|
|
|
44
|
-
hosts = kwargs.pop("host", state.inventory.
|
|
44
|
+
hosts = kwargs.pop("host", state.inventory.get_active_hosts())
|
|
45
45
|
if isinstance(hosts, Host):
|
|
46
46
|
hosts = [hosts]
|
|
47
47
|
|
pyinfra/api/exceptions.py
CHANGED
|
@@ -54,6 +54,12 @@ class OperationValueError(OperationError, ValueError):
|
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
56
|
|
|
57
|
+
class NestedOperationError(OperationError):
|
|
58
|
+
"""
|
|
59
|
+
Exception raised when a nested (immediately executed) operation fails.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
|
|
57
63
|
class DeployError(PyinfraError):
|
|
58
64
|
"""
|
|
59
65
|
User exception for raising in deploys or sub deploys.
|
pyinfra/api/facts.py
CHANGED
|
@@ -14,7 +14,7 @@ import inspect
|
|
|
14
14
|
import re
|
|
15
15
|
from inspect import getcallargs
|
|
16
16
|
from socket import error as socket_error, timeout as timeout_error
|
|
17
|
-
from typing import TYPE_CHECKING, Any, Callable, Generic,
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Type, TypeVar, cast
|
|
18
18
|
|
|
19
19
|
import click
|
|
20
20
|
import gevent
|
|
@@ -87,7 +87,7 @@ class FactBase(Generic[T]):
|
|
|
87
87
|
|
|
88
88
|
return cast(T, None)
|
|
89
89
|
|
|
90
|
-
def process(self, output:
|
|
90
|
+
def process(self, output: list[str]) -> T:
|
|
91
91
|
# NOTE: TypeVar does not support a default, so we have to cast this str -> T
|
|
92
92
|
return cast(T, "\n".join(output))
|
|
93
93
|
|
|
@@ -152,7 +152,7 @@ def get_facts(state, *args, **kwargs):
|
|
|
152
152
|
with ctx_state.use(state):
|
|
153
153
|
greenlet_to_host = {
|
|
154
154
|
state.pool.spawn(get_host_fact, host, *args, **kwargs): host
|
|
155
|
-
for host in state.inventory.
|
|
155
|
+
for host in state.inventory.get_active_hosts()
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
results = {}
|
pyinfra/api/inventory.py
CHANGED
|
@@ -158,11 +158,11 @@ class Inventory:
|
|
|
158
158
|
|
|
159
159
|
return iter(self.hosts.values())
|
|
160
160
|
|
|
161
|
-
def
|
|
161
|
+
def get_active_hosts(self) -> list["Host"]:
|
|
162
162
|
"""
|
|
163
163
|
Iterates over active inventory hosts.
|
|
164
164
|
"""
|
|
165
|
-
return
|
|
165
|
+
return list(self.state.active_hosts)
|
|
166
166
|
|
|
167
167
|
def len_active_hosts(self) -> int:
|
|
168
168
|
"""
|
pyinfra/api/operation.py
CHANGED
|
@@ -22,7 +22,7 @@ from pyinfra.context import ctx_host, ctx_state
|
|
|
22
22
|
from .arguments import EXECUTION_KWARG_KEYS, AllArguments, pop_global_arguments
|
|
23
23
|
from .arguments_typed import PyinfraOperation
|
|
24
24
|
from .command import PyinfraCommand, StringCommand
|
|
25
|
-
from .exceptions import OperationValueError, PyinfraError
|
|
25
|
+
from .exceptions import NestedOperationError, OperationValueError, PyinfraError
|
|
26
26
|
from .host import Host
|
|
27
27
|
from .operations import run_host_op
|
|
28
28
|
from .state import State, StateOperationHostData, StateOperationMeta, StateStage
|
|
@@ -221,7 +221,7 @@ def add_op(state: State, op_func, *args, **kwargs):
|
|
|
221
221
|
),
|
|
222
222
|
)
|
|
223
223
|
|
|
224
|
-
hosts = kwargs.pop("host", state.inventory.
|
|
224
|
+
hosts = kwargs.pop("host", state.inventory.get_active_hosts())
|
|
225
225
|
if isinstance(hosts, Host):
|
|
226
226
|
hosts = [hosts]
|
|
227
227
|
|
|
@@ -266,7 +266,9 @@ def _wrap_operation(func: Callable[P, Generator], _set_in_op: bool = True) -> Py
|
|
|
266
266
|
state = context.state
|
|
267
267
|
host = context.host
|
|
268
268
|
|
|
269
|
-
if
|
|
269
|
+
if pyinfra.is_cli and (
|
|
270
|
+
state.current_stage < StateStage.Prepare or state.current_stage > StateStage.Execute
|
|
271
|
+
):
|
|
270
272
|
raise Exception("Cannot call operations outside of Prepare/Execute stages")
|
|
271
273
|
|
|
272
274
|
if host.in_op:
|
|
@@ -470,8 +472,11 @@ def execute_immediately(state, host, op_hash):
|
|
|
470
472
|
op_meta = state.get_op_meta(op_hash)
|
|
471
473
|
op_data = state.get_op_data_for_host(host, op_hash)
|
|
472
474
|
op_data.parent_op_hash = host.executing_op_hash
|
|
475
|
+
|
|
473
476
|
log_operation_start(op_meta, op_types=["nested"], prefix="")
|
|
474
|
-
|
|
477
|
+
|
|
478
|
+
if run_host_op(state, host, op_hash) is False:
|
|
479
|
+
raise NestedOperationError(op_hash)
|
|
475
480
|
|
|
476
481
|
|
|
477
482
|
def _get_arg_value(arg):
|
pyinfra/api/operations.py
CHANGED
|
@@ -4,7 +4,7 @@ import time
|
|
|
4
4
|
import traceback
|
|
5
5
|
from itertools import product
|
|
6
6
|
from socket import error as socket_error, timeout as timeout_error
|
|
7
|
-
from typing import TYPE_CHECKING,
|
|
7
|
+
from typing import TYPE_CHECKING, cast
|
|
8
8
|
|
|
9
9
|
import click
|
|
10
10
|
import gevent
|
|
@@ -17,7 +17,7 @@ from pyinfra.progress import progress_spinner
|
|
|
17
17
|
|
|
18
18
|
from .arguments import CONNECTOR_ARGUMENT_KEYS, ConnectorArguments
|
|
19
19
|
from .command import FunctionCommand, PyinfraCommand, StringCommand
|
|
20
|
-
from .exceptions import PyinfraError
|
|
20
|
+
from .exceptions import NestedOperationError, PyinfraError
|
|
21
21
|
from .util import (
|
|
22
22
|
format_exception,
|
|
23
23
|
log_error_or_warning,
|
|
@@ -35,7 +35,7 @@ if TYPE_CHECKING:
|
|
|
35
35
|
#
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def run_host_op(state: "State", host: "Host", op_hash: str) ->
|
|
38
|
+
def run_host_op(state: "State", host: "Host", op_hash: str) -> bool:
|
|
39
39
|
state.trigger_callbacks("operation_host_start", host, op_hash)
|
|
40
40
|
|
|
41
41
|
if op_hash not in state.ops[host]:
|
|
@@ -59,7 +59,7 @@ def run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
59
59
|
host.executing_op_hash = None
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def _run_host_op(state: "State", host: "Host", op_hash: str) ->
|
|
62
|
+
def _run_host_op(state: "State", host: "Host", op_hash: str) -> bool:
|
|
63
63
|
op_data = state.get_op_data_for_host(host, op_hash)
|
|
64
64
|
global_arguments = op_data.global_arguments
|
|
65
65
|
|
|
@@ -104,6 +104,8 @@ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
|
104
104
|
if isinstance(command, FunctionCommand):
|
|
105
105
|
try:
|
|
106
106
|
status = command.execute(state, host, connector_arguments)
|
|
107
|
+
except NestedOperationError:
|
|
108
|
+
host.log_styled("Error in nested operation", fg="red", log_func=logger.error)
|
|
107
109
|
except Exception as e:
|
|
108
110
|
# Custom functions could do anything, so expect anything!
|
|
109
111
|
logger.warning(traceback.format_exc())
|
|
@@ -278,7 +280,7 @@ def _run_serial_ops(state: "State"):
|
|
|
278
280
|
Run all ops for all servers, one server at a time.
|
|
279
281
|
"""
|
|
280
282
|
|
|
281
|
-
for host in list(state.inventory.
|
|
283
|
+
for host in list(state.inventory.get_active_hosts()):
|
|
282
284
|
host_operations = product([host], state.get_op_order())
|
|
283
285
|
with progress_spinner(host_operations) as progress:
|
|
284
286
|
try:
|
|
@@ -296,7 +298,7 @@ def _run_no_wait_ops(state: "State"):
|
|
|
296
298
|
Run all ops for all servers at once.
|
|
297
299
|
"""
|
|
298
300
|
|
|
299
|
-
hosts_operations = product(state.inventory.
|
|
301
|
+
hosts_operations = product(state.inventory.get_active_hosts(), state.get_op_order())
|
|
300
302
|
with progress_spinner(hosts_operations) as progress:
|
|
301
303
|
# Spawn greenlet for each host to run *all* ops
|
|
302
304
|
if state.pool is None:
|
|
@@ -308,7 +310,7 @@ def _run_no_wait_ops(state: "State"):
|
|
|
308
310
|
host,
|
|
309
311
|
progress=progress,
|
|
310
312
|
)
|
|
311
|
-
for host in state.inventory.
|
|
313
|
+
for host in state.inventory.get_active_hosts()
|
|
312
314
|
]
|
|
313
315
|
gevent.joinall(greenlets)
|
|
314
316
|
|
|
@@ -326,9 +328,9 @@ def _run_single_op(state: "State", op_hash: str):
|
|
|
326
328
|
failed_hosts = set()
|
|
327
329
|
|
|
328
330
|
if op_meta.global_arguments["_serial"]:
|
|
329
|
-
with progress_spinner(state.inventory.
|
|
331
|
+
with progress_spinner(state.inventory.get_active_hosts()) as progress:
|
|
330
332
|
# For each host, run the op
|
|
331
|
-
for host in state.inventory.
|
|
333
|
+
for host in state.inventory.get_active_hosts():
|
|
332
334
|
result = _run_host_op_with_context(state, host, op_hash)
|
|
333
335
|
progress(host)
|
|
334
336
|
|
|
@@ -337,12 +339,12 @@ def _run_single_op(state: "State", op_hash: str):
|
|
|
337
339
|
|
|
338
340
|
else:
|
|
339
341
|
# Start with the whole inventory in one batch
|
|
340
|
-
batches = [list(state.inventory.
|
|
342
|
+
batches = [list(state.inventory.get_active_hosts())]
|
|
341
343
|
|
|
342
344
|
# If parallel set break up the inventory into a series of batches
|
|
343
345
|
parallel = op_meta.global_arguments["_parallel"]
|
|
344
346
|
if parallel:
|
|
345
|
-
hosts = list(state.inventory.
|
|
347
|
+
hosts = list(state.inventory.get_active_hosts())
|
|
346
348
|
batches = [hosts[i : i + parallel] for i in range(0, len(hosts), parallel)]
|
|
347
349
|
|
|
348
350
|
for batch in batches:
|
|
@@ -28,15 +28,17 @@ HOST_KEYS_LOCK = BoundedSemaphore()
|
|
|
28
28
|
class StrictPolicy(MissingHostKeyPolicy):
|
|
29
29
|
@override
|
|
30
30
|
def missing_host_key(self, client, hostname, key):
|
|
31
|
-
logger.error("No host key for
|
|
32
|
-
raise SSHException(
|
|
33
|
-
"StrictPolicy: No host key for {0} found in known_hosts".format(hostname),
|
|
34
|
-
)
|
|
31
|
+
logger.error("No host key for %s found in known_hosts", hostname)
|
|
32
|
+
raise SSHException(f"StrictPolicy: No host key for {hostname} found in known_hosts")
|
|
35
33
|
|
|
36
34
|
|
|
37
35
|
def append_hostkey(client, hostname, key):
|
|
38
36
|
"""Append hostname to the clients host_keys_file"""
|
|
39
37
|
|
|
38
|
+
if client._host_keys_filename is None:
|
|
39
|
+
logger.warning("No host keys filename, not saving key for: %s", hostname)
|
|
40
|
+
return
|
|
41
|
+
|
|
40
42
|
with HOST_KEYS_LOCK:
|
|
41
43
|
# The paramiko client saves host keys incorrectly whereas the host keys object does
|
|
42
44
|
# this correctly, so use that with the client filename variable.
|
|
@@ -67,30 +69,28 @@ class AcceptNewPolicy(MissingHostKeyPolicy):
|
|
|
67
69
|
)
|
|
68
70
|
|
|
69
71
|
append_hostkey(client, hostname, key)
|
|
70
|
-
logger.warning("Added host key for
|
|
72
|
+
logger.warning("Added host key for %s to known_hosts", hostname)
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
class AskPolicy(MissingHostKeyPolicy):
|
|
74
76
|
@override
|
|
75
77
|
def missing_host_key(self, client, hostname, key):
|
|
76
78
|
should_continue = input(
|
|
77
|
-
"No host key for {
|
|
78
|
-
hostname,
|
|
79
|
-
),
|
|
79
|
+
f"No host key for {hostname} found in known_hosts, do you want to continue [y/n] ",
|
|
80
80
|
)
|
|
81
81
|
if should_continue.lower() != "y":
|
|
82
82
|
raise SSHException(
|
|
83
|
-
"AskPolicy: No host key for {
|
|
83
|
+
f"AskPolicy: No host key for {hostname} found in known_hosts",
|
|
84
84
|
)
|
|
85
85
|
append_hostkey(client, hostname, key)
|
|
86
|
-
logger.warning("Added host key for
|
|
86
|
+
logger.warning("Added host key for %s to known_hosts", hostname)
|
|
87
87
|
return
|
|
88
88
|
|
|
89
89
|
|
|
90
90
|
class WarningPolicy(MissingHostKeyPolicy):
|
|
91
91
|
@override
|
|
92
92
|
def missing_host_key(self, client, hostname, key):
|
|
93
|
-
logger.warning("No host key for
|
|
93
|
+
logger.warning("No host key for %s found in known_hosts", hostname)
|
|
94
94
|
|
|
95
95
|
|
|
96
96
|
def get_missing_host_key_policy(policy):
|
|
@@ -102,7 +102,7 @@ def get_missing_host_key_policy(policy):
|
|
|
102
102
|
return StrictPolicy()
|
|
103
103
|
if policy == "accept-new":
|
|
104
104
|
return AcceptNewPolicy()
|
|
105
|
-
raise SSHException("Invalid value StrictHostKeyChecking={}"
|
|
105
|
+
raise SSHException(f"Invalid value StrictHostKeyChecking={policy}")
|
|
106
106
|
|
|
107
107
|
|
|
108
108
|
@memoize
|
|
@@ -120,17 +120,24 @@ def get_ssh_config(user_config_file=None):
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
@memoize
|
|
123
|
-
def get_host_keys(
|
|
123
|
+
def get_host_keys(filenames):
|
|
124
|
+
"""
|
|
125
|
+
Load host keys from one or more files.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
filenames: A tuple of filenames to load host keys from.
|
|
129
|
+
"""
|
|
124
130
|
with HOST_KEYS_LOCK:
|
|
125
131
|
host_keys = HostKeys()
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
133
|
+
for filename in filenames:
|
|
134
|
+
try:
|
|
135
|
+
host_keys.load(filename)
|
|
136
|
+
# When paramiko encounters a bad host keys line it sometimes bails the
|
|
137
|
+
# entire load incorrectly.
|
|
138
|
+
# See: https://github.com/paramiko/paramiko/pull/1990
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.warning("Failed to load host keys from %s: %s", filename, e)
|
|
134
141
|
|
|
135
142
|
return host_keys
|
|
136
143
|
|
|
@@ -157,7 +164,7 @@ class SSHClient(ParamikoClient):
|
|
|
157
164
|
config,
|
|
158
165
|
forward_agent,
|
|
159
166
|
missing_host_key_policy,
|
|
160
|
-
|
|
167
|
+
host_keys_files,
|
|
161
168
|
keep_alive,
|
|
162
169
|
) = self.parse_config(
|
|
163
170
|
hostname,
|
|
@@ -169,11 +176,13 @@ class SSHClient(ParamikoClient):
|
|
|
169
176
|
config.update(kwargs)
|
|
170
177
|
|
|
171
178
|
if _pyinfra_ssh_known_hosts_file:
|
|
172
|
-
|
|
179
|
+
host_keys_files = (path.expanduser(_pyinfra_ssh_known_hosts_file),)
|
|
173
180
|
|
|
174
181
|
# Overwrite paramiko empty defaults with @memoize-d host keys object
|
|
175
|
-
self._host_keys = get_host_keys(
|
|
176
|
-
|
|
182
|
+
self._host_keys = get_host_keys(host_keys_files)
|
|
183
|
+
# Use the first file for writing new host keys
|
|
184
|
+
if len(host_keys_files) > 0:
|
|
185
|
+
self._host_keys_filename = host_keys_files[0]
|
|
177
186
|
|
|
178
187
|
if _pyinfra_ssh_paramiko_connect_kwargs:
|
|
179
188
|
config.update(_pyinfra_ssh_paramiko_connect_kwargs)
|
|
@@ -217,11 +226,18 @@ class SSHClient(ParamikoClient):
|
|
|
217
226
|
keep_alive = 0
|
|
218
227
|
forward_agent = False
|
|
219
228
|
missing_host_key_policy = get_missing_host_key_policy(strict_host_key_checking)
|
|
220
|
-
|
|
229
|
+
host_keys_files = (path.expanduser("~/.ssh/known_hosts"),)
|
|
221
230
|
|
|
222
231
|
ssh_config = get_ssh_config(ssh_config_file)
|
|
223
232
|
if not ssh_config:
|
|
224
|
-
return
|
|
233
|
+
return (
|
|
234
|
+
hostname,
|
|
235
|
+
cfg,
|
|
236
|
+
forward_agent,
|
|
237
|
+
missing_host_key_policy,
|
|
238
|
+
host_keys_files,
|
|
239
|
+
keep_alive,
|
|
240
|
+
)
|
|
225
241
|
|
|
226
242
|
host_config = ssh_config.lookup(hostname)
|
|
227
243
|
forward_agent = host_config.get("forwardagent") == "yes"
|
|
@@ -233,7 +249,10 @@ class SSHClient(ParamikoClient):
|
|
|
233
249
|
)
|
|
234
250
|
|
|
235
251
|
if "userknownhostsfile" in host_config:
|
|
236
|
-
|
|
252
|
+
# OpenSSH supports multiple space-separated known hosts files
|
|
253
|
+
host_keys_files = tuple(
|
|
254
|
+
path.expanduser(f) for f in host_config["userknownhostsfile"].split()
|
|
255
|
+
)
|
|
237
256
|
|
|
238
257
|
if "hostname" in host_config:
|
|
239
258
|
hostname = host_config["hostname"]
|
|
@@ -275,7 +294,7 @@ class SSHClient(ParamikoClient):
|
|
|
275
294
|
sock = c.gateway(hostname, cfg["port"], target, target_config["port"])
|
|
276
295
|
cfg["sock"] = sock
|
|
277
296
|
|
|
278
|
-
return hostname, cfg, forward_agent, missing_host_key_policy,
|
|
297
|
+
return hostname, cfg, forward_agent, missing_host_key_policy, host_keys_files, keep_alive
|
|
279
298
|
|
|
280
299
|
@staticmethod
|
|
281
300
|
def derive_shorthand(ssh_config, host_string):
|
pyinfra/facts/crontab.py
CHANGED
|
@@ -52,9 +52,11 @@ class CrontabFile:
|
|
|
52
52
|
|
|
53
53
|
name_comment = "# pyinfra-name={0}".format(name)
|
|
54
54
|
for cmd in self.commands:
|
|
55
|
+
if "command" not in cmd:
|
|
56
|
+
continue
|
|
55
57
|
if cmd.get("command") == command:
|
|
56
58
|
return cmd
|
|
57
|
-
if cmd.get("comments"
|
|
59
|
+
if name_comment in cmd.get("comments", []):
|
|
58
60
|
return cmd
|
|
59
61
|
return None
|
|
60
62
|
|
pyinfra/facts/files.py
CHANGED
|
@@ -547,6 +547,8 @@ class FindFilesBase(FactBase):
|
|
|
547
547
|
command.append("-regex")
|
|
548
548
|
command.append(maybe_quote(regex))
|
|
549
549
|
|
|
550
|
+
command.extend(args)
|
|
551
|
+
|
|
550
552
|
command.append("||")
|
|
551
553
|
command.append("true")
|
|
552
554
|
|
|
@@ -664,13 +666,21 @@ class Block(FactBase):
|
|
|
664
666
|
|
|
665
667
|
class FileContents(FactBase):
|
|
666
668
|
"""
|
|
667
|
-
Returns the contents of a file as a list of lines
|
|
669
|
+
Returns the contents of a file as a list of lines, or ``None`` if the file does not exist.
|
|
668
670
|
"""
|
|
669
671
|
|
|
670
672
|
@override
|
|
671
673
|
def command(self, path):
|
|
672
|
-
|
|
674
|
+
self.missing_flag = "{0}{1}".format(MISSING, path)
|
|
675
|
+
return make_formatted_string_command(
|
|
676
|
+
"( test -e {0} && cat {0} ) || echo {1}",
|
|
677
|
+
QuoteString(path),
|
|
678
|
+
QuoteString(self.missing_flag),
|
|
679
|
+
)
|
|
673
680
|
|
|
674
681
|
@override
|
|
675
682
|
def process(self, output):
|
|
683
|
+
# If output is the missing flag, the file doesn't exist
|
|
684
|
+
if output and output[0] == self.missing_flag:
|
|
685
|
+
return None
|
|
676
686
|
return output
|
pyinfra/facts/flatpak.py
CHANGED
pyinfra/operations/docker.py
CHANGED
|
@@ -32,6 +32,8 @@ def container(
|
|
|
32
32
|
present: bool = True,
|
|
33
33
|
force: bool = False,
|
|
34
34
|
start: bool = True,
|
|
35
|
+
restart_policy: str | None = None,
|
|
36
|
+
auto_remove: bool = False,
|
|
35
37
|
):
|
|
36
38
|
"""
|
|
37
39
|
Manage Docker containers
|
|
@@ -47,6 +49,8 @@ def container(
|
|
|
47
49
|
+ force: remove a container with same name and create a new one
|
|
48
50
|
+ present: whether the container should be up and running
|
|
49
51
|
+ start: start or stop the container
|
|
52
|
+
+ restart_policy: restart policy to apply when a container exits
|
|
53
|
+
+ auto_remove: automatically remove the container and its associated anonymous volumes when it exits
|
|
50
54
|
|
|
51
55
|
**Examples:**
|
|
52
56
|
|
|
@@ -64,6 +68,8 @@ def container(
|
|
|
64
68
|
networks=["proxy", "services"],
|
|
65
69
|
volumes=["nginx_data:/usr/share/nginx/html"],
|
|
66
70
|
pull_always=True,
|
|
71
|
+
restart_policy="unless-stopped",
|
|
72
|
+
auto_remove=True,
|
|
67
73
|
)
|
|
68
74
|
|
|
69
75
|
# Stop a container
|
|
@@ -89,6 +95,8 @@ def container(
|
|
|
89
95
|
env_vars or list(),
|
|
90
96
|
labels or list(),
|
|
91
97
|
pull_always,
|
|
98
|
+
restart_policy,
|
|
99
|
+
auto_remove,
|
|
92
100
|
)
|
|
93
101
|
existent_container = host.get_fact(DockerContainer, object_id=container)
|
|
94
102
|
|
pyinfra/operations/postgres.py
CHANGED
|
@@ -53,7 +53,12 @@ def sql(
|
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
|
|
56
|
-
@operation(
|
|
56
|
+
@operation(
|
|
57
|
+
idempotent_notice=(
|
|
58
|
+
"This operation will always execute commands when a password is provided, "
|
|
59
|
+
"as pyinfra cannot reliably validate the current password."
|
|
60
|
+
),
|
|
61
|
+
)
|
|
57
62
|
def role(
|
|
58
63
|
role: str,
|
|
59
64
|
present: bool = True,
|
|
@@ -165,6 +165,8 @@ class ContainerSpec:
|
|
|
165
165
|
env_vars: list[str] = field(default_factory=list)
|
|
166
166
|
labels: list[str] = field(default_factory=list)
|
|
167
167
|
pull_always: bool = False
|
|
168
|
+
restart_policy: str | None = None
|
|
169
|
+
auto_remove: bool = False
|
|
168
170
|
|
|
169
171
|
def container_create_args(self):
|
|
170
172
|
args = []
|
|
@@ -186,6 +188,12 @@ class ContainerSpec:
|
|
|
186
188
|
if self.pull_always:
|
|
187
189
|
args.append("--pull always")
|
|
188
190
|
|
|
191
|
+
if self.restart_policy:
|
|
192
|
+
args.append("--restart {0}".format(self.restart_policy))
|
|
193
|
+
|
|
194
|
+
if self.auto_remove:
|
|
195
|
+
args.append("--rm")
|
|
196
|
+
|
|
189
197
|
args.append(self.image)
|
|
190
198
|
|
|
191
199
|
return args
|
|
@@ -6,20 +6,20 @@ pyinfra/progress.py,sha256=X3hXZ4Flh_L9FE4ZEWxWoG0R4dA5UPd1FCO-Exd5Xtc,4193
|
|
|
6
6
|
pyinfra/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
7
|
pyinfra/version.py,sha256=LZf50PHDzEZv65w0G-iMICoQ9US0U5LWHAOEmNtkF3I,216
|
|
8
8
|
pyinfra/api/__init__.py,sha256=89brynFpoKWCE513go2pK2wfbGaU1V1gajX3UMm9LVA,964
|
|
9
|
-
pyinfra/api/arguments.py,sha256=
|
|
10
|
-
pyinfra/api/arguments_typed.py,sha256=
|
|
11
|
-
pyinfra/api/command.py,sha256=
|
|
9
|
+
pyinfra/api/arguments.py,sha256=0c_xENyzPlAyD41lXpL5rPbxU8Rny-uWyXgd8izcg9w,12439
|
|
10
|
+
pyinfra/api/arguments_typed.py,sha256=8a3vPKkz28i9Q1Byo3DDJRLL2CuM8dbPGH-12Afh0ZY,2434
|
|
11
|
+
pyinfra/api/command.py,sha256=X-gK2K8oyoHAPu4HE4tI93G57mkY4nGx7GmQZwEgV3M,7949
|
|
12
12
|
pyinfra/api/config.py,sha256=gVDV-aGh6LYOnHtBaivICrd3RBfjFRWy3-K9sG__eP8,9321
|
|
13
13
|
pyinfra/api/connect.py,sha256=jkx07iUL29u9pHHKH4WcNtvxwOA4DIbF7ixguFyuFjo,1984
|
|
14
14
|
pyinfra/api/connectors.py,sha256=nie7JuLxMSC6gqPjmjuCisQ11R-eAQDtMMWF6YbSQ48,659
|
|
15
|
-
pyinfra/api/deploy.py,sha256=
|
|
16
|
-
pyinfra/api/exceptions.py,sha256=
|
|
17
|
-
pyinfra/api/facts.py,sha256=
|
|
15
|
+
pyinfra/api/deploy.py,sha256=xR5VRGkRoax3dBVLX8vn_pS2x9RL0u3IrCWCPeJ_SFQ,3166
|
|
16
|
+
pyinfra/api/exceptions.py,sha256=Nf6QFRzQBLfqb1kmVY0JXwLZ0YAIZE4nxUcxBEgRSqA,2141
|
|
17
|
+
pyinfra/api/facts.py,sha256=u_8M4ijdZnQszdRpIWPTGtZ_fSWt_Zv_jUIq-uJsBAA,9870
|
|
18
18
|
pyinfra/api/host.py,sha256=192rj8fsrHXudfnxzPlYxljXU24pReeWjXtrcCe9Kj4,14214
|
|
19
|
-
pyinfra/api/inventory.py,sha256=
|
|
19
|
+
pyinfra/api/inventory.py,sha256=tMvnAstPqAGdj8bAxCeH27KxlueUHj7VnBNVBRXqWZM,7859
|
|
20
20
|
pyinfra/api/metadata.py,sha256=73BjwxKKA4mgP7D60K7Z8YIwPC11YN4IXaq26d209BI,1884
|
|
21
|
-
pyinfra/api/operation.py,sha256=
|
|
22
|
-
pyinfra/api/operations.py,sha256=
|
|
21
|
+
pyinfra/api/operation.py,sha256=Ae_V_8ObPEQntU5_vXbOn2KeMvKTBYO1LSvjJFT2zQ0,17176
|
|
22
|
+
pyinfra/api/operations.py,sha256=kUGpC9ytA7Zaw9cJZ7zFTtLFFuyMqr2PAWaOmyAzeD0,13846
|
|
23
23
|
pyinfra/api/state.py,sha256=cj-JvxOljeDshWvRpq8AMQxdGaUaht8KyuyR3mEsI-Y,12859
|
|
24
24
|
pyinfra/api/util.py,sha256=fwlgiFGFpHPQQ6BVT3SRDDunZBS3fZ7ApTEFuRI6r5M,13276
|
|
25
25
|
pyinfra/connectors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -36,7 +36,7 @@ pyinfra/connectors/vagrant.py,sha256=0TT73ks64I4Yl-JSZjMBbpWA3VYBkqqLB-fUS8pS8GY
|
|
|
36
36
|
pyinfra/connectors/scp/__init__.py,sha256=jnO-_8GfkKWhsFcDjAxjOkuUT2RbS22b8P_xPrX889U,44
|
|
37
37
|
pyinfra/connectors/scp/client.py,sha256=l_fPsbgz-7U6Y50ssuKKPFxD_cFoIPtaVXMCYDotbDI,6399
|
|
38
38
|
pyinfra/connectors/sshuserclient/__init__.py,sha256=Qc4RO2wknSWIiNTwOeQ0y2TeiuKHmyWDW2Dz4MOo9CE,44
|
|
39
|
-
pyinfra/connectors/sshuserclient/client.py,sha256=
|
|
39
|
+
pyinfra/connectors/sshuserclient/client.py,sha256=WUmpo2LUOSXucUwVHb3EgLVxQIoCJ7pBFqFiCynjlMI,11069
|
|
40
40
|
pyinfra/connectors/sshuserclient/config.py,sha256=FZkPrUYXkURZcFUHBGWw9lLC9uiH3DJ0rBYXJePchxw,2774
|
|
41
41
|
pyinfra/facts/__init__.py,sha256=myTXSOZmAqmU88Fyifn035h9Lr6Gj2mlka_jDcXyKGw,347
|
|
42
42
|
pyinfra/facts/apk.py,sha256=UEMHzhx2Wx3qq-OcjetWgE2iZ7_EjI-bszLxSN6PJa0,799
|
|
@@ -45,13 +45,13 @@ pyinfra/facts/brew.py,sha256=nE6YVc2S9zasyJPZmPR5FMeGKPViZYEcpnnBQlDf1EU,2792
|
|
|
45
45
|
pyinfra/facts/bsdinit.py,sha256=SVY4hagjyy1yz8FKWhIbX9fHm5AugvTFl4xQh2FFO74,631
|
|
46
46
|
pyinfra/facts/cargo.py,sha256=qgOClhwZm4COcncDzOZccCzs67nPBi_x6VGiF2UA0sA,687
|
|
47
47
|
pyinfra/facts/choco.py,sha256=mpLleSqNqiaGRgyrhgceno2iPB1_1yjn8UJ90pvOZCs,886
|
|
48
|
-
pyinfra/facts/crontab.py,sha256=
|
|
48
|
+
pyinfra/facts/crontab.py,sha256=mm5eHCA0jL0zDflZKUyEk3V1j_opVZLvEezgwEXrucs,5820
|
|
49
49
|
pyinfra/facts/deb.py,sha256=1dR1puwY5wyyhhYYwaEBLjKU9sIyaNBNBlamVZ2KQg0,2074
|
|
50
50
|
pyinfra/facts/dnf.py,sha256=wXatfZWVrrdLY7LM-vHKMg8Md1FiwkqHxmgRYbQqw90,1208
|
|
51
51
|
pyinfra/facts/docker.py,sha256=fqIqMR6HwSYpTUAjhCX8Hk57pcyL6ShIl98H32Ly6HM,3233
|
|
52
52
|
pyinfra/facts/efibootmgr.py,sha256=JPJSokE_RV9JstEPJRECnqSU-B0JCxmrocY8zBOva7M,3555
|
|
53
|
-
pyinfra/facts/files.py,sha256=
|
|
54
|
-
pyinfra/facts/flatpak.py,sha256=
|
|
53
|
+
pyinfra/facts/files.py,sha256=y6aOC8k2UH6421t3NXgc01BfPmcy7pLFdQACJYlHFWE,19798
|
|
54
|
+
pyinfra/facts/flatpak.py,sha256=Fwyaq2YP0SL_ChlGk3cTvujtb7bg1XBdICaEY1dzbBY,1696
|
|
55
55
|
pyinfra/facts/freebsd.py,sha256=za42Di2M2-hcSTPei1YE6BsJxqapm9jysshs9hKJZaY,2161
|
|
56
56
|
pyinfra/facts/gem.py,sha256=aX2-vcEqkxUIP0UJ_SVp9bf4B944oyDjsuujjs5q_9w,654
|
|
57
57
|
pyinfra/facts/git.py,sha256=Zfzpdccsz2InviirJO17EkEFTVNqYQclSlXJIRFkD_s,1699
|
|
@@ -99,7 +99,7 @@ pyinfra/operations/cargo.py,sha256=mXWd6pb0IR6kzJMmPHwXZN-VJ-B_y8AdOFlrRzDQOZI,1
|
|
|
99
99
|
pyinfra/operations/choco.py,sha256=nIj4bWhChOd5DkybpbD-oupaoODgS7lYx6Vrou5ksuc,1547
|
|
100
100
|
pyinfra/operations/crontab.py,sha256=L1U_fBvgXkbfbpzb6OzUBrrY-RuvvPlbW5FqDmAT8rI,6644
|
|
101
101
|
pyinfra/operations/dnf.py,sha256=wMFUoUB679bVydt01N7Sd7Cs16RhAaLca-zsmQU86rk,5727
|
|
102
|
-
pyinfra/operations/docker.py,sha256=
|
|
102
|
+
pyinfra/operations/docker.py,sha256=vsNm8PVh7_oIi8RmnpHeOekX-1gliNTwvzo9-2zVJY8,14525
|
|
103
103
|
pyinfra/operations/files.py,sha256=jqkYIPn94z4MRqH1eU-ooOJzuQyle8wYiyDvFfXDMPI,68492
|
|
104
104
|
pyinfra/operations/flatpak.py,sha256=Eif5KZkWOVElKF4hL5xOyk_oZEOziHqyyDxGHZ1KPYk,2366
|
|
105
105
|
pyinfra/operations/gem.py,sha256=YtVUKVp1zYPAxy2t1ryw-vgucBVYJASOxhauLOvRj6U,1175
|
|
@@ -116,7 +116,7 @@ pyinfra/operations/pip.py,sha256=uTfK36_vBNqCXsWMU4iypOLhnhKduJK6f0b9srlMEqg,603
|
|
|
116
116
|
pyinfra/operations/pipx.py,sha256=oWcJXKogC43cKNsf625FU4ClIAV6KZA26o2cRoa3avQ,2844
|
|
117
117
|
pyinfra/operations/pkg.py,sha256=m5okKIXU1xIIcNQXQnFKXbL97ZcokmGn-hnruokE7is,2341
|
|
118
118
|
pyinfra/operations/pkgin.py,sha256=6bZyvdjYDqn-0a-r23O_122r1QSjHP8SkJiWZ_k231A,2037
|
|
119
|
-
pyinfra/operations/postgres.py,sha256=
|
|
119
|
+
pyinfra/operations/postgres.py,sha256=FN7wDYgtjqShbzSr-_CKbYY0PSlj2XdVijzaveUYols,13601
|
|
120
120
|
pyinfra/operations/postgresql.py,sha256=agZjL2W4yxigk9ThIC0V_3wvmcWVdX308aJO24WkN6g,833
|
|
121
121
|
pyinfra/operations/puppet.py,sha256=e9vO6SQnkMoyVWjy3oP08GaXgyIPajoA2QJ4g4ib4-M,907
|
|
122
122
|
pyinfra/operations/python.py,sha256=5IXcywwhwITPRJAs8BEL5H5vSPvk_QFMbhF8Iuexp_s,2067
|
|
@@ -139,22 +139,22 @@ pyinfra/operations/freebsd/pkg.py,sha256=3AyfI0-_9F4ho47KqZsOMQocwNtTF2q9g0i6Tff
|
|
|
139
139
|
pyinfra/operations/freebsd/service.py,sha256=1f7nTHELnhs3HBSrMFsmopVgYFMIwB8Se88yneRQ8Rw,3198
|
|
140
140
|
pyinfra/operations/freebsd/sysrc.py,sha256=eg7u_JsCge_uKq3Ysc_mohUc6qgJrOZStp_B_l2Hav4,2330
|
|
141
141
|
pyinfra/operations/util/__init__.py,sha256=ZAHjeCXtLo0TIOSfZ9h0Sh5IXXRCspfHs3RR1l8tQCE,366
|
|
142
|
-
pyinfra/operations/util/docker.py,sha256=
|
|
142
|
+
pyinfra/operations/util/docker.py,sha256=aOL8iEOPlg5LpC1ix37_XbzwEG5V05wz-PhyrsBKWQI,12152
|
|
143
143
|
pyinfra/operations/util/files.py,sha256=PFJDccNTwXK4tIoFB8ycRj7yD1x7LpSflBy7mPQtJCg,7148
|
|
144
144
|
pyinfra/operations/util/packaging.py,sha256=RXZgUlWqEBtArK7wJfXE2Ndvl_aP0YjjksxxCnPpexk,12086
|
|
145
145
|
pyinfra/operations/util/service.py,sha256=kJd1zj4-sAaGIp5Ts7yAJznogWaGr8oQTztwenLAr7Y,1309
|
|
146
146
|
pyinfra_cli/__init__.py,sha256=G0X7tNdqT45uWuK3aHIKxMdDeCgJ7zHo6vbxoG6zy_8,284
|
|
147
|
-
pyinfra_cli/cli.py,sha256=
|
|
147
|
+
pyinfra_cli/cli.py,sha256=xD24zgSB1VdOx9F8avD405lj-ASWsTsZ21TC_nYdTkY,21418
|
|
148
148
|
pyinfra_cli/commands.py,sha256=J-mCJYvDebJ8M7o3HreB2zToa871-xO6_KjVhPLeHho,1832
|
|
149
149
|
pyinfra_cli/exceptions.py,sha256=RRaOprL7SmVv--FLy4x7fxeTitx9wYI0Y3_h01LfhJA,4901
|
|
150
|
-
pyinfra_cli/inventory.py,sha256=
|
|
150
|
+
pyinfra_cli/inventory.py,sha256=tU0qaF5jFlYu0tbs961QLdjHU0wJL3Tr7Sk9TM36Ggw,12648
|
|
151
151
|
pyinfra_cli/log.py,sha256=mD96MH2owQQ5AsYRw7osCKENdp-E3Wum5IDr6qhSIa4,2268
|
|
152
152
|
pyinfra_cli/main.py,sha256=1CR3IS-O6BkAzkn7UW6pdKktTmN4Qnt0_jPdkhueRM8,936
|
|
153
153
|
pyinfra_cli/prints.py,sha256=1h6vgKVRKUxcGz_HdyEEDUvkp-lgiiVGwx3hc9rw24A,10434
|
|
154
|
-
pyinfra_cli/util.py,sha256=
|
|
154
|
+
pyinfra_cli/util.py,sha256=P5ULsCweyNc7OkqmQTEplNfzNtNyxhQWfcp2540YeE0,6721
|
|
155
155
|
pyinfra_cli/virtualenv.py,sha256=wRNxOPcUkbD_Pzuj-Lnrz1KxGmsLlb2ObmCTFrdD-S8,2474
|
|
156
|
-
pyinfra-3.6.dist-info/METADATA,sha256=
|
|
157
|
-
pyinfra-3.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
158
|
-
pyinfra-3.6.dist-info/entry_points.txt,sha256=b1nLI6oVRvkeQDS00xcYGdGl4XVR_3tMbKs6T58-NW4,507
|
|
159
|
-
pyinfra-3.6.dist-info/licenses/LICENSE.md,sha256=BzCnRYLJv0yb-FJuEd_XOrrQSOEQKzIVo0yHT8taNnM,1076
|
|
160
|
-
pyinfra-3.6.dist-info/RECORD,,
|
|
156
|
+
pyinfra-3.6.1.dist-info/METADATA,sha256=CbbyXsZMgFh0FnvreQoIElKZRPl-UzFiDupEpWsGMPQ,5773
|
|
157
|
+
pyinfra-3.6.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
158
|
+
pyinfra-3.6.1.dist-info/entry_points.txt,sha256=b1nLI6oVRvkeQDS00xcYGdGl4XVR_3tMbKs6T58-NW4,507
|
|
159
|
+
pyinfra-3.6.1.dist-info/licenses/LICENSE.md,sha256=BzCnRYLJv0yb-FJuEd_XOrrQSOEQKzIVo0yHT8taNnM,1076
|
|
160
|
+
pyinfra-3.6.1.dist-info/RECORD,,
|
pyinfra_cli/cli.py
CHANGED
|
@@ -595,10 +595,6 @@ def _set_config(
|
|
|
595
595
|
if path.exists(config_filename):
|
|
596
596
|
exec_file(config_filename)
|
|
597
597
|
|
|
598
|
-
# Lock the current config, this allows us to restore this version after
|
|
599
|
-
# executing deploy files that may alter them.
|
|
600
|
-
config.lock_current_state()
|
|
601
|
-
|
|
602
598
|
# Arg based config overrides
|
|
603
599
|
if sudo:
|
|
604
600
|
config.SUDO = True
|
|
@@ -632,6 +628,11 @@ def _set_config(
|
|
|
632
628
|
if retry_delay is not None:
|
|
633
629
|
config.RETRY_DELAY = retry_delay
|
|
634
630
|
|
|
631
|
+
# Lock the current config, this allows us to restore this version after
|
|
632
|
+
# executing deploy files that may alter them. This must happen after CLI
|
|
633
|
+
# args are applied so they persist across multiple deploy files.
|
|
634
|
+
config.lock_current_state()
|
|
635
|
+
|
|
635
636
|
return config
|
|
636
637
|
|
|
637
638
|
|
|
@@ -772,6 +773,8 @@ def _prepare_exec_operations(state, config, operations):
|
|
|
772
773
|
def _prepare_deploy_operations(state, config, operations):
|
|
773
774
|
# Number of "steps" to make = number of files * number of hosts
|
|
774
775
|
for i, filename in enumerate(operations):
|
|
776
|
+
config.lock_current_state()
|
|
777
|
+
|
|
775
778
|
_log_styled_msg = click.style(filename, bold=True)
|
|
776
779
|
logger.info("Loading: {0}".format(_log_styled_msg))
|
|
777
780
|
|
pyinfra_cli/inventory.py
CHANGED
|
@@ -177,8 +177,11 @@ def make_inventory(
|
|
|
177
177
|
"nor refers to a python module"
|
|
178
178
|
)
|
|
179
179
|
return Inventory.empty()
|
|
180
|
-
|
|
180
|
+
elif callable(inventory_func):
|
|
181
181
|
return make_inventory_from_func(inventory_func, override_data)
|
|
182
|
+
else:
|
|
183
|
+
# The inventory is an iterable (list/tuple) of hosts from a module attribute
|
|
184
|
+
return make_inventory_from_iterable(inventory_func, override_data)
|
|
182
185
|
|
|
183
186
|
|
|
184
187
|
def make_inventory_from_func(
|
|
@@ -235,6 +238,28 @@ def make_inventory_from_func(
|
|
|
235
238
|
)
|
|
236
239
|
|
|
237
240
|
|
|
241
|
+
def make_inventory_from_iterable(
|
|
242
|
+
hosts: List[HostType],
|
|
243
|
+
override_data: Optional[Dict[Any, Any]] = None,
|
|
244
|
+
):
|
|
245
|
+
"""
|
|
246
|
+
Builds a ``pyinfra.api.Inventory`` from an iterable of hosts loaded from a module attribute.
|
|
247
|
+
"""
|
|
248
|
+
logger.warning("Loading inventory via module attribute is in alpha!")
|
|
249
|
+
|
|
250
|
+
if not isinstance(hosts, (list, tuple)):
|
|
251
|
+
raise TypeError(f"Inventory attribute is not a list or tuple: {type(hosts).__name__}")
|
|
252
|
+
|
|
253
|
+
for host in hosts:
|
|
254
|
+
if not isinstance(host, ALLOWED_HOST_TYPES):
|
|
255
|
+
raise TypeError(f"Invalid host in inventory: {host}")
|
|
256
|
+
|
|
257
|
+
return Inventory(
|
|
258
|
+
(list(hosts), {}),
|
|
259
|
+
override_data=override_data,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
|
|
238
263
|
def make_inventory_from_files(
|
|
239
264
|
inventory_filename: str,
|
|
240
265
|
override_data=None,
|
pyinfra_cli/util.py
CHANGED
|
@@ -218,7 +218,7 @@ def _parallel_load_hosts(state: "State", callback: Callable, name: str):
|
|
|
218
218
|
return e
|
|
219
219
|
|
|
220
220
|
greenlet_to_host = {
|
|
221
|
-
state.pool.spawn(load_file, host): host for host in state.inventory.
|
|
221
|
+
state.pool.spawn(load_file, host): host for host in state.inventory.get_active_hosts()
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
with progress_spinner(greenlet_to_host.values()) as progress:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|