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/operations.py
CHANGED
|
@@ -1,318 +1,335 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import traceback
|
|
1
5
|
from itertools import product
|
|
2
|
-
from socket import
|
|
3
|
-
|
|
4
|
-
timeout as timeout_error,
|
|
5
|
-
)
|
|
6
|
-
from types import FunctionType
|
|
6
|
+
from socket import error as socket_error, timeout as timeout_error
|
|
7
|
+
from typing import TYPE_CHECKING, Optional, cast
|
|
7
8
|
|
|
8
9
|
import click
|
|
9
10
|
import gevent
|
|
10
|
-
|
|
11
11
|
from paramiko import SSHException
|
|
12
12
|
|
|
13
|
-
import pyinfra
|
|
14
|
-
|
|
15
13
|
from pyinfra import logger
|
|
16
|
-
from pyinfra.
|
|
17
|
-
from pyinfra.
|
|
14
|
+
from pyinfra.connectors.util import CommandOutput, OutputLine
|
|
15
|
+
from pyinfra.context import ctx_host, ctx_state
|
|
18
16
|
from pyinfra.progress import progress_spinner
|
|
19
17
|
|
|
18
|
+
from .arguments import CONNECTOR_ARGUMENT_KEYS, ConnectorArguments
|
|
19
|
+
from .command import FunctionCommand, PyinfraCommand, StringCommand
|
|
20
|
+
from .exceptions import PyinfraError
|
|
21
|
+
from .util import (
|
|
22
|
+
format_exception,
|
|
23
|
+
log_error_or_warning,
|
|
24
|
+
log_host_command_error,
|
|
25
|
+
log_operation_start,
|
|
26
|
+
print_host_combined_output,
|
|
27
|
+
)
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
logger.info('{0}{1}'.format(
|
|
25
|
-
host.print_prefix,
|
|
26
|
-
click.style(
|
|
27
|
-
'Skipped',
|
|
28
|
-
'blue',
|
|
29
|
-
),
|
|
30
|
-
))
|
|
31
|
-
return True
|
|
32
|
-
|
|
33
|
-
op_data = state.ops[host][op_hash]
|
|
34
|
-
op_meta = state.op_meta[op_hash]
|
|
35
|
-
|
|
36
|
-
logger.debug('Starting operation {0} on {1}'.format(
|
|
37
|
-
', '.join(op_meta['names']), host,
|
|
38
|
-
))
|
|
39
|
-
|
|
40
|
-
state.ops_run.add(op_hash)
|
|
41
|
-
|
|
42
|
-
# ...loop through each command
|
|
43
|
-
for i, command in enumerate(op_data['commands']):
|
|
44
|
-
status = False
|
|
45
|
-
|
|
46
|
-
shell_executable = op_meta['shell_executable']
|
|
47
|
-
sudo = op_meta['sudo']
|
|
48
|
-
sudo_user = op_meta['sudo_user']
|
|
49
|
-
su_user = op_meta['su_user']
|
|
50
|
-
preserve_sudo_env = op_meta['preserve_sudo_env']
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from .inventory import Host
|
|
31
|
+
from .state import State
|
|
51
32
|
|
|
52
|
-
# As dicts, individual commands can override meta settings (ie on a
|
|
53
|
-
# per-host basis generated during deploy).
|
|
54
|
-
if isinstance(command, dict):
|
|
55
|
-
if 'sudo' in command:
|
|
56
|
-
sudo = command['sudo']
|
|
57
33
|
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
# Run a single host operation
|
|
35
|
+
#
|
|
60
36
|
|
|
61
|
-
if 'su_user' in command:
|
|
62
|
-
su_user = command['su_user']
|
|
63
37
|
|
|
64
|
-
|
|
65
|
-
|
|
38
|
+
def run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
39
|
+
state.trigger_callbacks("operation_host_start", host, op_hash)
|
|
66
40
|
|
|
67
|
-
|
|
41
|
+
if op_hash not in state.ops[host]:
|
|
42
|
+
logger.info("{0}{1}".format(host.print_prefix, click.style("Skipped", "blue")))
|
|
43
|
+
return True
|
|
68
44
|
|
|
69
|
-
|
|
45
|
+
op_meta = state.get_op_meta(op_hash)
|
|
46
|
+
logger.debug("Starting operation %r on %s", op_meta.names, host)
|
|
70
47
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
func, args, kwargs = command
|
|
48
|
+
if host.executing_op_hash is None:
|
|
49
|
+
host.executing_op_hash = op_hash
|
|
50
|
+
else:
|
|
51
|
+
host.nested_executing_op_hash = op_hash
|
|
76
52
|
|
|
53
|
+
try:
|
|
54
|
+
return _run_host_op(state, host, op_hash)
|
|
55
|
+
finally:
|
|
56
|
+
if host.nested_executing_op_hash:
|
|
57
|
+
host.nested_executing_op_hash = None
|
|
58
|
+
else:
|
|
59
|
+
host.executing_op_hash = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
|
|
63
|
+
op_data = state.get_op_data_for_host(host, op_hash)
|
|
64
|
+
global_arguments = op_data.global_arguments
|
|
65
|
+
|
|
66
|
+
ignore_errors = global_arguments["_ignore_errors"]
|
|
67
|
+
continue_on_error = global_arguments["_continue_on_error"]
|
|
68
|
+
timeout = global_arguments.get("_timeout", 0)
|
|
69
|
+
|
|
70
|
+
# Extract retry arguments
|
|
71
|
+
retries = global_arguments.get("_retries", 0)
|
|
72
|
+
retry_delay = global_arguments.get("_retry_delay", 5)
|
|
73
|
+
retry_until = global_arguments.get("_retry_until", None)
|
|
74
|
+
|
|
75
|
+
executor_kwarg_keys = CONNECTOR_ARGUMENT_KEYS
|
|
76
|
+
# See: https://github.com/python/mypy/issues/10371
|
|
77
|
+
base_connector_arguments: ConnectorArguments = cast(
|
|
78
|
+
ConnectorArguments,
|
|
79
|
+
{key: global_arguments[key] for key in executor_kwarg_keys if key in global_arguments}, # type: ignore[literal-required] # noqa
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
retry_attempt = 0
|
|
83
|
+
did_error = False
|
|
84
|
+
executed_commands = 0
|
|
85
|
+
commands: list[PyinfraCommand] = []
|
|
86
|
+
all_output_lines: list[OutputLine] = []
|
|
87
|
+
|
|
88
|
+
# Retry loop
|
|
89
|
+
while retry_attempt <= retries:
|
|
90
|
+
did_error = False
|
|
91
|
+
executed_commands = 0
|
|
92
|
+
commands = []
|
|
93
|
+
all_output_lines = []
|
|
94
|
+
|
|
95
|
+
for command in op_data.command_generator():
|
|
96
|
+
commands.append(command)
|
|
97
|
+
status = False
|
|
98
|
+
connector_arguments = base_connector_arguments.copy()
|
|
99
|
+
connector_arguments.update(command.connector_arguments)
|
|
100
|
+
|
|
101
|
+
if not isinstance(command, PyinfraCommand):
|
|
102
|
+
raise TypeError("{0} is an invalid pyinfra command!".format(command))
|
|
103
|
+
|
|
104
|
+
if isinstance(command, FunctionCommand):
|
|
77
105
|
try:
|
|
78
|
-
status =
|
|
79
|
-
state, host,
|
|
80
|
-
*args, **kwargs
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
# Custom functions could do anything, so expect anything!
|
|
106
|
+
status = command.execute(state, host, connector_arguments)
|
|
84
107
|
except Exception as e:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
),
|
|
93
|
-
))
|
|
94
|
-
|
|
95
|
-
# Non-function mean files to copy
|
|
96
|
-
else:
|
|
97
|
-
method_type, first_file, second_file = command
|
|
98
|
-
|
|
99
|
-
if method_type == 'upload':
|
|
100
|
-
method = host.put_file
|
|
101
|
-
elif method_type == 'download':
|
|
102
|
-
method = host.get_file
|
|
103
|
-
else:
|
|
104
|
-
raise TypeError('{0} is an invalid pyinfra command!'.format(command))
|
|
108
|
+
# Custom functions could do anything, so expect anything!
|
|
109
|
+
logger.warning(traceback.format_exc())
|
|
110
|
+
host.log_styled(
|
|
111
|
+
f"Unexpected error in Python callback: {format_exception(e)}",
|
|
112
|
+
fg="red",
|
|
113
|
+
log_func=logger.warning,
|
|
114
|
+
)
|
|
105
115
|
|
|
116
|
+
elif isinstance(command, StringCommand):
|
|
117
|
+
output_lines = CommandOutput([])
|
|
106
118
|
try:
|
|
107
|
-
status =
|
|
119
|
+
status, output_lines = command.execute(
|
|
108
120
|
state,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
sudo=sudo,
|
|
112
|
-
sudo_user=sudo_user,
|
|
113
|
-
su_user=su_user,
|
|
114
|
-
shell_executable=shell_executable,
|
|
115
|
-
print_output=state.print_output,
|
|
121
|
+
host,
|
|
122
|
+
connector_arguments,
|
|
116
123
|
)
|
|
124
|
+
except (timeout_error, socket_error, SSHException) as e:
|
|
125
|
+
log_host_command_error(host, e, timeout=timeout)
|
|
126
|
+
all_output_lines.extend(output_lines)
|
|
127
|
+
# If we failed and have not already printed the stderr, print it
|
|
128
|
+
if status is False and not state.print_output:
|
|
129
|
+
print_host_combined_output(host, output_lines)
|
|
117
130
|
|
|
131
|
+
else:
|
|
132
|
+
try:
|
|
133
|
+
status = command.execute(state, host, connector_arguments)
|
|
118
134
|
except (timeout_error, socket_error, SSHException, IOError) as e:
|
|
119
|
-
log_host_command_error(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
log_host_command_error(host, e, timeout=timeout)
|
|
136
|
+
|
|
137
|
+
# Break the loop to trigger a failure
|
|
138
|
+
if status is False:
|
|
139
|
+
did_error = True
|
|
140
|
+
if continue_on_error is True:
|
|
141
|
+
continue
|
|
142
|
+
break
|
|
143
|
+
|
|
144
|
+
executed_commands += 1
|
|
145
|
+
|
|
146
|
+
# Check if we should retry
|
|
147
|
+
should_retry = False
|
|
148
|
+
if retry_attempt < retries:
|
|
149
|
+
# Retry on error
|
|
150
|
+
if did_error:
|
|
151
|
+
should_retry = True
|
|
152
|
+
# Retry on condition if no error
|
|
153
|
+
elif retry_until and not did_error:
|
|
154
|
+
try:
|
|
155
|
+
output_data = {
|
|
156
|
+
"stdout_lines": [
|
|
157
|
+
line.line for line in all_output_lines if line.buffer_name == "stdout"
|
|
158
|
+
],
|
|
159
|
+
"stderr_lines": [
|
|
160
|
+
line.line for line in all_output_lines if line.buffer_name == "stderr"
|
|
161
|
+
],
|
|
162
|
+
"commands": [str(command) for command in commands],
|
|
163
|
+
"executed_commands": executed_commands,
|
|
164
|
+
"host": host.name,
|
|
165
|
+
"operation": ", ".join(state.get_op_meta(op_hash).names) or "Operation",
|
|
166
|
+
}
|
|
167
|
+
should_retry = retry_until(output_data)
|
|
168
|
+
except Exception as e:
|
|
169
|
+
host.log_styled(
|
|
170
|
+
f"Error in retry_until function: {format_exception(e)}",
|
|
171
|
+
fg="red",
|
|
172
|
+
log_func=logger.warning,
|
|
123
173
|
)
|
|
124
174
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
175
|
+
if should_retry:
|
|
176
|
+
retry_attempt += 1
|
|
177
|
+
state.trigger_callbacks("operation_host_retry", host, op_hash, retry_attempt, retries)
|
|
178
|
+
op_name = ", ".join(state.get_op_meta(op_hash).names) or "Operation"
|
|
179
|
+
host.log_styled(
|
|
180
|
+
f"Retrying {op_name} (attempt {retry_attempt}/{retries}) after {retry_delay}s...",
|
|
181
|
+
fg="yellow",
|
|
182
|
+
log_func=logger.info,
|
|
183
|
+
)
|
|
184
|
+
time.sleep(retry_delay)
|
|
185
|
+
continue
|
|
128
186
|
|
|
129
|
-
|
|
130
|
-
status, combined_output_lines = host.run_shell_command(
|
|
131
|
-
state,
|
|
132
|
-
command.strip(),
|
|
133
|
-
sudo=sudo,
|
|
134
|
-
sudo_user=sudo_user,
|
|
135
|
-
su_user=su_user,
|
|
136
|
-
preserve_sudo_env=preserve_sudo_env,
|
|
137
|
-
shell_executable=shell_executable,
|
|
138
|
-
timeout=op_meta['timeout'],
|
|
139
|
-
get_pty=op_meta['get_pty'],
|
|
140
|
-
env=op_meta['env'],
|
|
141
|
-
print_output=state.print_output,
|
|
142
|
-
return_combined_output=True,
|
|
143
|
-
)
|
|
187
|
+
break
|
|
144
188
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
e,
|
|
149
|
-
timeout=op_meta['timeout'],
|
|
150
|
-
)
|
|
189
|
+
# Handle results
|
|
190
|
+
op_success = return_status = not did_error
|
|
191
|
+
host_results = state.get_results_for_host(host)
|
|
151
192
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if type_ == 'stderr':
|
|
156
|
-
logger.error('{0}{1}'.format(
|
|
157
|
-
host.print_prefix,
|
|
158
|
-
click.style(line, 'red'),
|
|
159
|
-
))
|
|
160
|
-
else:
|
|
161
|
-
logger.error('{0}{1}'.format(
|
|
162
|
-
host.print_prefix,
|
|
163
|
-
line,
|
|
164
|
-
))
|
|
165
|
-
else:
|
|
166
|
-
raise TypeError('{0} is an invalid pyinfra command!'.format(command))
|
|
193
|
+
if did_error is False:
|
|
194
|
+
host_results.ops += 1
|
|
195
|
+
host_results.success_ops += 1
|
|
167
196
|
|
|
168
|
-
|
|
169
|
-
if
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
197
|
+
_status_log = "Success" if executed_commands > 0 else "No changes"
|
|
198
|
+
if retry_attempt > 0:
|
|
199
|
+
_status_log = f"{_status_log} on retry {retry_attempt}"
|
|
200
|
+
|
|
201
|
+
_click_log_status = click.style(_status_log, "green")
|
|
202
|
+
logger.info("{0}{1}".format(host.print_prefix, _click_log_status))
|
|
173
203
|
|
|
174
|
-
|
|
204
|
+
state.trigger_callbacks("operation_host_success", host, op_hash, retry_attempt)
|
|
175
205
|
else:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
logger.info('{0}{1}'.format(
|
|
181
|
-
host.print_prefix,
|
|
182
|
-
click.style(
|
|
183
|
-
'Success' if len(op_data['commands']) > 0 else 'No changes',
|
|
184
|
-
'green',
|
|
185
|
-
),
|
|
186
|
-
))
|
|
187
|
-
|
|
188
|
-
# Trigger any success handler
|
|
189
|
-
if op_meta['on_success']:
|
|
190
|
-
op_meta['on_success'](state, host, op_hash)
|
|
206
|
+
if ignore_errors:
|
|
207
|
+
host_results.ignored_error_ops += 1
|
|
208
|
+
else:
|
|
209
|
+
host_results.error_ops += 1
|
|
191
210
|
|
|
192
|
-
|
|
211
|
+
if executed_commands:
|
|
212
|
+
host_results.partial_ops += 1
|
|
193
213
|
|
|
194
|
-
|
|
195
|
-
|
|
214
|
+
_command_description = f"executed {executed_commands} commands"
|
|
215
|
+
if retry_attempt > 0:
|
|
216
|
+
_command_description = (
|
|
217
|
+
f"{_command_description} (failed after {retry_attempt}/{retries} retries)"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
log_error_or_warning(host, ignore_errors, _command_description, continue_on_error)
|
|
221
|
+
|
|
222
|
+
# Ignored, op "completes" w/ ignored error
|
|
223
|
+
if ignore_errors:
|
|
224
|
+
host_results.ops += 1
|
|
225
|
+
return_status = True
|
|
226
|
+
|
|
227
|
+
# Unignored error -> False
|
|
228
|
+
state.trigger_callbacks("operation_host_error", host, op_hash, retry_attempt, retries)
|
|
229
|
+
|
|
230
|
+
op_data.operation_meta.set_complete(
|
|
231
|
+
op_success,
|
|
232
|
+
commands,
|
|
233
|
+
CommandOutput(all_output_lines),
|
|
234
|
+
retry_attempts=retry_attempt,
|
|
235
|
+
max_retries=retries,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return return_status
|
|
196
239
|
|
|
197
|
-
if op_meta['ignore_errors']:
|
|
198
|
-
logger.warning('{0}{1}'.format(
|
|
199
|
-
host.print_prefix,
|
|
200
|
-
click.style('Error (ignored)', 'yellow'),
|
|
201
|
-
))
|
|
202
|
-
else:
|
|
203
|
-
logger.error('{0}{1}'.format(
|
|
204
|
-
host.print_prefix,
|
|
205
|
-
click.style('Error', 'red'),
|
|
206
|
-
))
|
|
207
240
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
op_meta['on_error'](state, host, op_hash)
|
|
241
|
+
# Run all operations strategies
|
|
242
|
+
#
|
|
211
243
|
|
|
212
|
-
# Ignored, op "completes" w/ ignored error
|
|
213
|
-
if op_meta['ignore_errors']:
|
|
214
|
-
state.results[host]['ops'] += 1
|
|
215
244
|
|
|
216
|
-
|
|
217
|
-
|
|
245
|
+
def _run_host_op_with_context(state: "State", host: "Host", op_hash: str):
|
|
246
|
+
with ctx_host.use(host):
|
|
247
|
+
return run_host_op(state, host, op_hash)
|
|
218
248
|
|
|
219
249
|
|
|
220
|
-
def
|
|
221
|
-
|
|
250
|
+
def _run_host_ops(state: "State", host: "Host", progress=None):
|
|
251
|
+
"""
|
|
222
252
|
Run all ops for a single server.
|
|
223
|
-
|
|
253
|
+
"""
|
|
224
254
|
|
|
225
|
-
logger.debug(
|
|
255
|
+
logger.debug("Running all ops on %s", host)
|
|
226
256
|
|
|
227
257
|
for op_hash in state.get_op_order():
|
|
228
|
-
op_meta = state.
|
|
258
|
+
op_meta = state.get_op_meta(op_hash)
|
|
259
|
+
log_operation_start(op_meta)
|
|
229
260
|
|
|
230
|
-
|
|
231
|
-
click.style('--> Starting operation:', 'blue'),
|
|
232
|
-
click.style(', '.join(op_meta['names']), bold=True),
|
|
233
|
-
click.style(host.name, bold=True),
|
|
234
|
-
))
|
|
235
|
-
|
|
236
|
-
result = _run_server_op(state, host, op_hash)
|
|
261
|
+
result = _run_host_op_with_context(state, host, op_hash)
|
|
237
262
|
|
|
238
263
|
# Trigger CLI progress if provided
|
|
239
264
|
if progress:
|
|
240
265
|
progress((host, op_hash))
|
|
241
266
|
|
|
242
267
|
if result is False:
|
|
243
|
-
raise PyinfraError(
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
268
|
+
raise PyinfraError(
|
|
269
|
+
"Error in operation {0} on {1}".format(
|
|
270
|
+
", ".join(op_meta.names),
|
|
271
|
+
host,
|
|
272
|
+
),
|
|
273
|
+
)
|
|
249
274
|
|
|
250
275
|
|
|
251
|
-
def _run_serial_ops(state):
|
|
252
|
-
|
|
276
|
+
def _run_serial_ops(state: "State"):
|
|
277
|
+
"""
|
|
253
278
|
Run all ops for all servers, one server at a time.
|
|
254
|
-
|
|
279
|
+
"""
|
|
255
280
|
|
|
256
|
-
for host in list(state.inventory):
|
|
281
|
+
for host in list(state.inventory.iter_active_hosts()):
|
|
257
282
|
host_operations = product([host], state.get_op_order())
|
|
258
283
|
with progress_spinner(host_operations) as progress:
|
|
259
284
|
try:
|
|
260
|
-
|
|
261
|
-
state,
|
|
285
|
+
_run_host_ops(
|
|
286
|
+
state,
|
|
287
|
+
host,
|
|
262
288
|
progress=progress,
|
|
263
289
|
)
|
|
264
290
|
except PyinfraError:
|
|
265
291
|
state.fail_hosts({host})
|
|
266
292
|
|
|
267
293
|
|
|
268
|
-
def _run_no_wait_ops(state):
|
|
269
|
-
|
|
294
|
+
def _run_no_wait_ops(state: "State"):
|
|
295
|
+
"""
|
|
270
296
|
Run all ops for all servers at once.
|
|
271
|
-
|
|
297
|
+
"""
|
|
272
298
|
|
|
273
|
-
hosts_operations = product(state.inventory, state.get_op_order())
|
|
299
|
+
hosts_operations = product(state.inventory.iter_active_hosts(), state.get_op_order())
|
|
274
300
|
with progress_spinner(hosts_operations) as progress:
|
|
275
301
|
# Spawn greenlet for each host to run *all* ops
|
|
302
|
+
if state.pool is None:
|
|
303
|
+
raise PyinfraError("No pool found on state.")
|
|
276
304
|
greenlets = [
|
|
277
305
|
state.pool.spawn(
|
|
278
|
-
|
|
306
|
+
_run_host_ops,
|
|
307
|
+
state,
|
|
308
|
+
host,
|
|
279
309
|
progress=progress,
|
|
280
310
|
)
|
|
281
|
-
for host in state.inventory
|
|
311
|
+
for host in state.inventory.iter_active_hosts()
|
|
282
312
|
]
|
|
283
313
|
gevent.joinall(greenlets)
|
|
284
314
|
|
|
285
315
|
|
|
286
|
-
def _run_single_op(state, op_hash):
|
|
287
|
-
|
|
316
|
+
def _run_single_op(state: "State", op_hash: str):
|
|
317
|
+
"""
|
|
288
318
|
Run a single operation for all servers. Can be configured to run in serial.
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
op_meta = state.op_meta[op_hash]
|
|
292
|
-
|
|
293
|
-
op_types = []
|
|
319
|
+
"""
|
|
294
320
|
|
|
295
|
-
|
|
296
|
-
op_types.append('serial')
|
|
321
|
+
state.trigger_callbacks("operation_start", op_hash)
|
|
297
322
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
logger.info('{0} {1} {2}'.format(
|
|
302
|
-
click.style('--> Starting{0}operation:'.format(
|
|
303
|
-
' {0} '.format(', '.join(op_types)) if op_types else ' ',
|
|
304
|
-
), 'blue'),
|
|
305
|
-
click.style(', '.join(op_meta['names']), bold=True),
|
|
306
|
-
tuple(op_meta['args']) if op_meta['args'] else '',
|
|
307
|
-
))
|
|
323
|
+
op_meta = state.get_op_meta(op_hash)
|
|
324
|
+
log_operation_start(op_meta)
|
|
308
325
|
|
|
309
326
|
failed_hosts = set()
|
|
310
327
|
|
|
311
|
-
if op_meta[
|
|
312
|
-
with progress_spinner(state.inventory) as progress:
|
|
328
|
+
if op_meta.global_arguments["_serial"]:
|
|
329
|
+
with progress_spinner(state.inventory.iter_active_hosts()) as progress:
|
|
313
330
|
# For each host, run the op
|
|
314
|
-
for host in state.inventory:
|
|
315
|
-
result =
|
|
331
|
+
for host in state.inventory.iter_active_hosts():
|
|
332
|
+
result = _run_host_op_with_context(state, host, op_hash)
|
|
316
333
|
progress(host)
|
|
317
334
|
|
|
318
335
|
if not result:
|
|
@@ -320,23 +337,21 @@ def _run_single_op(state, op_hash):
|
|
|
320
337
|
|
|
321
338
|
else:
|
|
322
339
|
# Start with the whole inventory in one batch
|
|
323
|
-
batches = [state.inventory]
|
|
340
|
+
batches = [list(state.inventory.iter_active_hosts())]
|
|
324
341
|
|
|
325
342
|
# If parallel set break up the inventory into a series of batches
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
hosts = list(state.inventory)
|
|
329
|
-
|
|
330
|
-
batches = [
|
|
331
|
-
hosts[i:i + parallel]
|
|
332
|
-
for i in range(0, len(hosts), parallel)
|
|
333
|
-
]
|
|
343
|
+
parallel = op_meta.global_arguments["_parallel"]
|
|
344
|
+
if parallel:
|
|
345
|
+
hosts = list(state.inventory.iter_active_hosts())
|
|
346
|
+
batches = [hosts[i : i + parallel] for i in range(0, len(hosts), parallel)]
|
|
334
347
|
|
|
335
348
|
for batch in batches:
|
|
336
349
|
with progress_spinner(batch) as progress:
|
|
337
350
|
# Spawn greenlet for each host
|
|
351
|
+
if state.pool is None:
|
|
352
|
+
raise PyinfraError("No pool found on state.")
|
|
338
353
|
greenlet_to_host = {
|
|
339
|
-
state.pool.spawn(
|
|
354
|
+
state.pool.spawn(_run_host_op_with_context, state, host, op_hash): host
|
|
340
355
|
for host in batch
|
|
341
356
|
}
|
|
342
357
|
|
|
@@ -351,34 +366,32 @@ def _run_single_op(state, op_hash):
|
|
|
351
366
|
failed_hosts.add(host)
|
|
352
367
|
|
|
353
368
|
# Now all the batches/hosts are complete, fail any failures
|
|
354
|
-
|
|
355
|
-
state.fail_hosts(failed_hosts)
|
|
369
|
+
state.fail_hosts(failed_hosts)
|
|
356
370
|
|
|
357
|
-
|
|
358
|
-
print()
|
|
371
|
+
state.trigger_callbacks("operation_end", op_hash)
|
|
359
372
|
|
|
360
373
|
|
|
361
|
-
def run_ops(state, serial=False, no_wait=False):
|
|
362
|
-
|
|
374
|
+
def run_ops(state: "State", serial: bool = False, no_wait: bool = False):
|
|
375
|
+
"""
|
|
363
376
|
Runs all operations across all servers in a configurable manner.
|
|
364
377
|
|
|
365
378
|
Args:
|
|
366
379
|
state (``pyinfra.api.State`` obj): the deploy state to execute
|
|
367
380
|
serial (boolean): whether to run operations host by host
|
|
368
381
|
no_wait (boolean): whether to wait for all hosts between operations
|
|
369
|
-
|
|
382
|
+
"""
|
|
370
383
|
|
|
371
384
|
# Flag state as deploy in process
|
|
372
|
-
state.
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
+
state.is_executing = True
|
|
386
|
+
|
|
387
|
+
with ctx_state.use(state):
|
|
388
|
+
# Run all ops, but server by server
|
|
389
|
+
if serial:
|
|
390
|
+
_run_serial_ops(state)
|
|
391
|
+
# Run all the ops on each server in parallel (not waiting at each operation)
|
|
392
|
+
elif no_wait:
|
|
393
|
+
_run_no_wait_ops(state)
|
|
394
|
+
# Default: run all ops in order, waiting at each for all servers to complete
|
|
395
|
+
else:
|
|
396
|
+
for op_hash in state.get_op_order():
|
|
397
|
+
_run_single_op(state, op_hash)
|