pyinfra 3.0.dev0__py2.py3-none-any.whl → 3.0.1__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 +115 -97
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +5 -3
- pyinfra/api/config.py +139 -39
- pyinfra/api/connectors.py +5 -2
- pyinfra/api/deploy.py +19 -19
- pyinfra/api/exceptions.py +35 -4
- pyinfra/api/facts.py +62 -86
- pyinfra/api/host.py +102 -15
- pyinfra/api/inventory.py +4 -0
- pyinfra/api/operation.py +184 -118
- pyinfra/api/operations.py +66 -113
- pyinfra/api/state.py +53 -34
- pyinfra/api/util.py +64 -33
- pyinfra/connectors/base.py +65 -20
- pyinfra/connectors/chroot.py +15 -13
- pyinfra/connectors/docker.py +62 -72
- pyinfra/connectors/dockerssh.py +20 -19
- pyinfra/connectors/local.py +32 -22
- pyinfra/connectors/ssh.py +162 -86
- pyinfra/connectors/sshuserclient/client.py +1 -1
- pyinfra/connectors/terraform.py +57 -39
- pyinfra/connectors/util.py +26 -27
- pyinfra/connectors/vagrant.py +27 -26
- pyinfra/context.py +1 -0
- pyinfra/facts/apk.py +7 -2
- pyinfra/facts/apt.py +15 -7
- pyinfra/facts/brew.py +28 -13
- pyinfra/facts/bsdinit.py +9 -6
- pyinfra/facts/cargo.py +6 -3
- pyinfra/facts/choco.py +8 -4
- pyinfra/facts/deb.py +21 -9
- pyinfra/facts/dnf.py +11 -6
- pyinfra/facts/docker.py +30 -5
- pyinfra/facts/files.py +49 -33
- pyinfra/facts/gem.py +7 -2
- pyinfra/facts/git.py +14 -21
- pyinfra/facts/gpg.py +4 -1
- pyinfra/facts/hardware.py +186 -138
- pyinfra/facts/launchd.py +7 -2
- pyinfra/facts/lxd.py +8 -2
- pyinfra/facts/mysql.py +19 -12
- pyinfra/facts/npm.py +3 -1
- pyinfra/facts/openrc.py +8 -2
- pyinfra/facts/pacman.py +13 -5
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +5 -1
- pyinfra/facts/pkgin.py +7 -2
- pyinfra/facts/postgres.py +170 -0
- pyinfra/facts/postgresql.py +5 -162
- pyinfra/facts/rpm.py +21 -15
- pyinfra/facts/runit.py +70 -0
- pyinfra/facts/selinux.py +12 -4
- pyinfra/facts/server.py +240 -82
- pyinfra/facts/snap.py +8 -2
- pyinfra/facts/systemd.py +37 -13
- pyinfra/facts/sysvinit.py +7 -4
- pyinfra/facts/upstart.py +7 -2
- pyinfra/facts/util/packaging.py +3 -2
- pyinfra/facts/vzctl.py +8 -4
- pyinfra/facts/xbps.py +7 -2
- pyinfra/facts/yum.py +10 -5
- pyinfra/facts/zypper.py +9 -4
- pyinfra/operations/apk.py +5 -3
- pyinfra/operations/apt.py +28 -25
- pyinfra/operations/brew.py +60 -29
- pyinfra/operations/bsdinit.py +6 -4
- pyinfra/operations/cargo.py +3 -1
- pyinfra/operations/choco.py +3 -1
- pyinfra/operations/dnf.py +16 -20
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +187 -168
- pyinfra/operations/gem.py +3 -1
- pyinfra/operations/git.py +23 -25
- pyinfra/operations/iptables.py +33 -25
- pyinfra/operations/launchd.py +5 -6
- pyinfra/operations/lxd.py +7 -4
- pyinfra/operations/mysql.py +59 -55
- pyinfra/operations/npm.py +8 -1
- pyinfra/operations/openrc.py +5 -3
- pyinfra/operations/pacman.py +6 -7
- pyinfra/operations/pip.py +19 -12
- pyinfra/operations/pkg.py +3 -1
- pyinfra/operations/pkgin.py +5 -3
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -335
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -29
- pyinfra/operations/server.py +138 -67
- pyinfra/operations/snap.py +3 -1
- pyinfra/operations/ssh.py +18 -16
- pyinfra/operations/systemd.py +18 -12
- pyinfra/operations/sysvinit.py +7 -5
- pyinfra/operations/upstart.py +7 -5
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +54 -38
- pyinfra/operations/util/service.py +39 -47
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +5 -3
- pyinfra/operations/yum.py +15 -19
- pyinfra/operations/zypper.py +9 -10
- pyinfra/version.py +5 -2
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/METADATA +51 -58
- pyinfra-3.0.1.dist-info/RECORD +168 -0
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/WHEEL +1 -1
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/entry_points.txt +0 -3
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +3 -2
- pyinfra_cli/exceptions.py +75 -43
- pyinfra_cli/inventory.py +52 -31
- pyinfra_cli/log.py +10 -2
- pyinfra_cli/main.py +88 -65
- pyinfra_cli/prints.py +37 -109
- pyinfra_cli/util.py +15 -10
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +9 -9
- tests/test_api/test_api_deploys.py +15 -19
- tests/test_api/test_api_facts.py +4 -5
- tests/test_api/test_api_operations.py +18 -20
- tests/test_api/test_api_util.py +41 -2
- tests/test_cli/test_cli.py +14 -50
- tests/test_cli/test_cli_deploy.py +10 -12
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/test_cli_inventory.py +66 -0
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_dockerssh.py +11 -8
- tests/test_connectors/test_ssh.py +88 -23
- tests/test_connectors/test_sshuserclient.py +1 -1
- tests/test_connectors/test_terraform.py +11 -8
- tests/test_connectors/test_vagrant.py +6 -6
- pyinfra/connectors/ansible.py +0 -175
- pyinfra/connectors/mech.py +0 -189
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -312
- 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 -538
- pyinfra-3.0.dev0.dist-info/RECORD +0 -170
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0.dev0.dist-info → pyinfra-3.0.1.dist-info}/top_level.txt +0 -0
pyinfra_cli/inventory.py
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import re
|
|
2
1
|
import socket
|
|
3
2
|
from collections import defaultdict
|
|
4
3
|
from os import listdir, path
|
|
@@ -37,32 +36,33 @@ def _is_inventory_group(key: str, value: Any):
|
|
|
37
36
|
return all(isinstance(item, ALLOWED_HOST_TYPES) for item in value)
|
|
38
37
|
|
|
39
38
|
|
|
40
|
-
def _get_group_data(
|
|
39
|
+
def _get_group_data(dirname_or_filename: str):
|
|
41
40
|
group_data = {}
|
|
42
|
-
group_data_directory = path.join(dirname, "group_data")
|
|
43
41
|
|
|
44
|
-
logger.debug("Checking possible group_data
|
|
42
|
+
logger.debug("Checking possible group_data at: %s", dirname_or_filename)
|
|
45
43
|
|
|
46
|
-
if path.exists(
|
|
47
|
-
|
|
44
|
+
if path.exists(dirname_or_filename):
|
|
45
|
+
if path.isfile(dirname_or_filename):
|
|
46
|
+
files = [dirname_or_filename]
|
|
47
|
+
else:
|
|
48
|
+
files = [path.join(dirname_or_filename, file) for file in listdir(dirname_or_filename)]
|
|
48
49
|
|
|
49
50
|
for file in files:
|
|
50
51
|
if not file.endswith(".py"):
|
|
51
52
|
continue
|
|
52
53
|
|
|
53
|
-
group_data_file = path.join(group_data_directory, file)
|
|
54
54
|
group_name = path.basename(file)[:-3]
|
|
55
55
|
|
|
56
|
-
logger.debug("Looking for group data in: %s",
|
|
56
|
+
logger.debug("Looking for group data in: %s", file)
|
|
57
57
|
|
|
58
58
|
# Read the files locals into a dict
|
|
59
|
-
attrs = exec_file(
|
|
59
|
+
attrs = exec_file(file, return_locals=True)
|
|
60
60
|
keys = attrs.get("__all__", attrs.keys())
|
|
61
61
|
|
|
62
62
|
group_data[group_name] = {
|
|
63
63
|
key: value
|
|
64
64
|
for key, value in attrs.items()
|
|
65
|
-
if key in keys and not key.startswith("
|
|
65
|
+
if key in keys and not key.startswith("__")
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
return group_data
|
|
@@ -81,32 +81,52 @@ def _get_any_tuple_first(item: Union[T, Tuple[T, Any]]) -> T:
|
|
|
81
81
|
return item[0] if isinstance(item, tuple) else item
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
def _resolves_to_host(maybe_host: str) -> bool:
|
|
85
|
+
"""Check if a string resolves to a valid IP address."""
|
|
86
|
+
try:
|
|
87
|
+
# Use getaddrinfo to support IPv6 hosts
|
|
88
|
+
socket.getaddrinfo(maybe_host, port=None)
|
|
89
|
+
return True
|
|
90
|
+
except socket.gaierror:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
84
94
|
def make_inventory(
|
|
85
95
|
inventory: str,
|
|
86
96
|
override_data=None,
|
|
87
97
|
cwd: Optional[str] = None,
|
|
88
98
|
group_data_directories=None,
|
|
89
99
|
):
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
#
|
|
108
|
-
|
|
100
|
+
# (Un)fortunately the CLI is pretty flexible for inventory inputs; we support inventory files, a
|
|
101
|
+
# single hostname, list of hosts, connectors, and python module.function or module:function
|
|
102
|
+
# imports.
|
|
103
|
+
#
|
|
104
|
+
# We check first for an inventory file, a list of hosts or anything with a connector, because
|
|
105
|
+
# (1) an inventory file is a common use case and (2) no other option can have a comma or an @
|
|
106
|
+
# symbol in them.
|
|
107
|
+
is_path_or_host_list_or_connector = (
|
|
108
|
+
path.exists(inventory) or "," in inventory or "@" in inventory
|
|
109
|
+
)
|
|
110
|
+
if not is_path_or_host_list_or_connector:
|
|
111
|
+
# Next, try loading the inventory from a python function. This happens before checking for a
|
|
112
|
+
# single-host inventory, so that your command does not stop working because somebody
|
|
113
|
+
# registered the domain `my.module.name`.
|
|
114
|
+
inventory_func = try_import_module_attribute(inventory, raise_for_none=False)
|
|
115
|
+
|
|
116
|
+
# If the inventory does not refer to a module, we finally check if it refers to a reachable
|
|
117
|
+
# host
|
|
118
|
+
if inventory_func is None and _resolves_to_host(inventory):
|
|
119
|
+
is_path_or_host_list_or_connector = True
|
|
120
|
+
|
|
121
|
+
if is_path_or_host_list_or_connector:
|
|
122
|
+
# The inventory is either an inventory file or a (list of) hosts
|
|
109
123
|
return make_inventory_from_files(inventory, override_data, cwd, group_data_directories)
|
|
124
|
+
elif inventory_func is None:
|
|
125
|
+
logger.warn(
|
|
126
|
+
f"{inventory} is neither an inventory file, a (list of) hosts or connectors "
|
|
127
|
+
"nor refers to a python module"
|
|
128
|
+
)
|
|
129
|
+
return Inventory.empty()
|
|
110
130
|
else:
|
|
111
131
|
return make_inventory_from_func(inventory_func, override_data)
|
|
112
132
|
|
|
@@ -236,10 +256,10 @@ def make_inventory_from_files(
|
|
|
236
256
|
|
|
237
257
|
possible_group_data_folders = []
|
|
238
258
|
if cwd:
|
|
239
|
-
possible_group_data_folders.append(cwd)
|
|
259
|
+
possible_group_data_folders.append(path.join(cwd, "group_data"))
|
|
240
260
|
inventory_dirname = path.abspath(path.dirname(inventory_filename))
|
|
241
261
|
if inventory_dirname != cwd:
|
|
242
|
-
possible_group_data_folders.append(inventory_dirname)
|
|
262
|
+
possible_group_data_folders.append(path.join(inventory_dirname, "group_data"))
|
|
243
263
|
|
|
244
264
|
if group_data_directories:
|
|
245
265
|
possible_group_data_folders.extend(group_data_directories)
|
|
@@ -250,6 +270,7 @@ def make_inventory_from_files(
|
|
|
250
270
|
for folder in possible_group_data_folders:
|
|
251
271
|
for group_name, data in _get_group_data(folder).items():
|
|
252
272
|
group_data[group_name].update(data)
|
|
273
|
+
logger.debug("Adding data to group %s: %r", group_name, data)
|
|
253
274
|
|
|
254
275
|
# For each group load up any data
|
|
255
276
|
for name, hosts in groups.items():
|
pyinfra_cli/log.py
CHANGED
|
@@ -2,7 +2,8 @@ import logging
|
|
|
2
2
|
|
|
3
3
|
import click
|
|
4
4
|
|
|
5
|
-
from pyinfra import logger
|
|
5
|
+
from pyinfra import logger, state
|
|
6
|
+
from pyinfra.context import ctx_state
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class LogHandler(logging.Handler):
|
|
@@ -41,6 +42,9 @@ class LogFormatter(logging.Formatter):
|
|
|
41
42
|
|
|
42
43
|
# We only handle strings here
|
|
43
44
|
if isinstance(message, str):
|
|
45
|
+
if ctx_state.isset() and record.levelno is logging.WARNING:
|
|
46
|
+
state.increment_warning_counter()
|
|
47
|
+
|
|
44
48
|
if "-->" in message:
|
|
45
49
|
if not self.previous_was_header:
|
|
46
50
|
click.echo(err=True)
|
|
@@ -57,9 +61,13 @@ class LogFormatter(logging.Formatter):
|
|
|
57
61
|
return super().format(record)
|
|
58
62
|
|
|
59
63
|
|
|
60
|
-
def setup_logging(log_level):
|
|
64
|
+
def setup_logging(log_level, other_log_level=None):
|
|
65
|
+
if other_log_level:
|
|
66
|
+
logging.basicConfig(level=other_log_level)
|
|
67
|
+
|
|
61
68
|
logger.setLevel(log_level)
|
|
62
69
|
handler = LogHandler()
|
|
63
70
|
formatter = LogFormatter()
|
|
64
71
|
handler.setFormatter(formatter)
|
|
65
72
|
logger.addHandler(handler)
|
|
73
|
+
logger.propagate = False
|
pyinfra_cli/main.py
CHANGED
|
@@ -13,12 +13,13 @@ from pyinfra.api.connect import connect_all, disconnect_all
|
|
|
13
13
|
from pyinfra.api.exceptions import NoGroupError, PyinfraError
|
|
14
14
|
from pyinfra.api.facts import get_facts
|
|
15
15
|
from pyinfra.api.operations import run_ops
|
|
16
|
+
from pyinfra.api.state import StateStage
|
|
16
17
|
from pyinfra.api.util import get_kwargs_str
|
|
17
18
|
from pyinfra.context import ctx_config, ctx_inventory, ctx_state
|
|
18
19
|
from pyinfra.operations import server
|
|
19
20
|
|
|
20
21
|
from .commands import get_facts_and_args, get_func_and_args
|
|
21
|
-
from .exceptions import CliError, UnexpectedExternalError, UnexpectedInternalError
|
|
22
|
+
from .exceptions import CliError, UnexpectedExternalError, UnexpectedInternalError, WrappedError
|
|
22
23
|
from .inventory import make_inventory
|
|
23
24
|
from .log import setup_logging
|
|
24
25
|
from .prints import (
|
|
@@ -69,6 +70,8 @@ def _print_support(ctx, param, value):
|
|
|
69
70
|
is_flag=True,
|
|
70
71
|
default=False,
|
|
71
72
|
help="Execute operations immediately on hosts without prompt or checking for changes.",
|
|
73
|
+
envvar="PYINFRA_YES",
|
|
74
|
+
show_envvar=True,
|
|
72
75
|
)
|
|
73
76
|
@click.option(
|
|
74
77
|
"--limit",
|
|
@@ -138,11 +141,6 @@ def _print_support(ctx, param, value):
|
|
|
138
141
|
help="SSH Private key password.",
|
|
139
142
|
)
|
|
140
143
|
@click.option("--ssh-password", "--password", "ssh_password", help="SSH password.")
|
|
141
|
-
# WinRM connector args
|
|
142
|
-
@click.option("--winrm-username", help="WINRM user to connect as.")
|
|
143
|
-
@click.option("--winrm-password", help="WINRM password.")
|
|
144
|
-
@click.option("--winrm-port", help="WINRM port to connect to.")
|
|
145
|
-
@click.option("--winrm-transport", help="WINRM transport for use.")
|
|
146
144
|
# Eager commands (pyinfra --support)
|
|
147
145
|
@click.option(
|
|
148
146
|
"--support",
|
|
@@ -153,16 +151,16 @@ def _print_support(ctx, param, value):
|
|
|
153
151
|
)
|
|
154
152
|
# Debug args
|
|
155
153
|
@click.option(
|
|
156
|
-
"--
|
|
154
|
+
"--debug",
|
|
157
155
|
is_flag=True,
|
|
158
156
|
default=False,
|
|
159
|
-
help="
|
|
157
|
+
help="Print debug logs from pyinfra.",
|
|
160
158
|
)
|
|
161
159
|
@click.option(
|
|
162
|
-
"--debug",
|
|
160
|
+
"--debug-all",
|
|
163
161
|
is_flag=True,
|
|
164
162
|
default=False,
|
|
165
|
-
help="Print debug
|
|
163
|
+
help="Print debug logs from all packages including pyinfra.",
|
|
166
164
|
)
|
|
167
165
|
@click.option(
|
|
168
166
|
"--debug-facts",
|
|
@@ -222,24 +220,14 @@ def cli(*args, **kwargs):
|
|
|
222
220
|
|
|
223
221
|
try:
|
|
224
222
|
_main(*args, **kwargs)
|
|
225
|
-
|
|
226
|
-
except PyinfraError as e:
|
|
227
|
-
# Re-raise any internal exceptions that aren't handled by click as
|
|
228
|
-
# CliErrors which are.
|
|
229
|
-
if not isinstance(e, click.ClickException):
|
|
230
|
-
message = getattr(e, "message", e.args[0])
|
|
231
|
-
raise CliError(message)
|
|
232
|
-
|
|
233
|
-
raise
|
|
234
|
-
|
|
235
|
-
except UnexpectedExternalError:
|
|
236
|
-
# Pass unexpected external exceptions through as-is
|
|
223
|
+
except (CliError, UnexpectedExternalError):
|
|
237
224
|
raise
|
|
238
|
-
|
|
225
|
+
except PyinfraError as e:
|
|
226
|
+
# Re-raise "expected" pyinfra exceptions with our click exception wrapper
|
|
227
|
+
raise WrappedError(e)
|
|
239
228
|
except Exception as e:
|
|
240
229
|
# Re-raise any unexpected internal exceptions as UnexpectedInternalError
|
|
241
230
|
raise UnexpectedInternalError(e)
|
|
242
|
-
|
|
243
231
|
finally:
|
|
244
232
|
if ctx_state.isset() and state.initialised:
|
|
245
233
|
logger.info("--> Disconnecting from hosts...")
|
|
@@ -265,10 +253,6 @@ def _main(
|
|
|
265
253
|
ssh_key,
|
|
266
254
|
ssh_key_password: str,
|
|
267
255
|
ssh_password: str,
|
|
268
|
-
winrm_username: str,
|
|
269
|
-
winrm_password: str,
|
|
270
|
-
winrm_port,
|
|
271
|
-
winrm_transport,
|
|
272
256
|
shell_executable,
|
|
273
257
|
sudo: bool,
|
|
274
258
|
sudo_user: str,
|
|
@@ -284,8 +268,8 @@ def _main(
|
|
|
284
268
|
limit: Iterable,
|
|
285
269
|
no_wait: bool,
|
|
286
270
|
serial: bool,
|
|
287
|
-
quiet: bool,
|
|
288
271
|
debug: bool,
|
|
272
|
+
debug_all: bool,
|
|
289
273
|
debug_facts: bool,
|
|
290
274
|
debug_operations: bool,
|
|
291
275
|
support: bool = False,
|
|
@@ -297,7 +281,7 @@ def _main(
|
|
|
297
281
|
|
|
298
282
|
# Setup logging & Bootstrap/Venv
|
|
299
283
|
#
|
|
300
|
-
_setup_log_level(debug)
|
|
284
|
+
_setup_log_level(debug, debug_all)
|
|
301
285
|
init_virtualenv()
|
|
302
286
|
|
|
303
287
|
# Check operations are valid and setup commands
|
|
@@ -306,7 +290,7 @@ def _main(
|
|
|
306
290
|
|
|
307
291
|
# Setup state, config & inventory
|
|
308
292
|
#
|
|
309
|
-
state = _setup_state(verbosity,
|
|
293
|
+
state = _setup_state(verbosity, yes)
|
|
310
294
|
config = Config()
|
|
311
295
|
ctx_config.set(config)
|
|
312
296
|
|
|
@@ -322,7 +306,7 @@ def _main(
|
|
|
322
306
|
parallel,
|
|
323
307
|
shell_executable,
|
|
324
308
|
fail_percent,
|
|
325
|
-
|
|
309
|
+
yes,
|
|
326
310
|
)
|
|
327
311
|
override_data = _set_override_data(
|
|
328
312
|
data,
|
|
@@ -331,12 +315,11 @@ def _main(
|
|
|
331
315
|
ssh_key_password,
|
|
332
316
|
ssh_port,
|
|
333
317
|
ssh_password,
|
|
334
|
-
winrm_username,
|
|
335
|
-
winrm_password,
|
|
336
|
-
winrm_port,
|
|
337
|
-
winrm_transport,
|
|
338
318
|
)
|
|
339
319
|
|
|
320
|
+
if yes is False:
|
|
321
|
+
_set_fail_prompts(state, config)
|
|
322
|
+
|
|
340
323
|
# Load up the inventory from the filesystem
|
|
341
324
|
#
|
|
342
325
|
logger.info("--> Loading inventory...")
|
|
@@ -361,7 +344,11 @@ def _main(
|
|
|
361
344
|
# Connect to the hosts & start handling the user commands
|
|
362
345
|
#
|
|
363
346
|
logger.info("--> Connecting to hosts...")
|
|
347
|
+
state.set_stage(StateStage.Connect)
|
|
364
348
|
connect_all(state)
|
|
349
|
+
|
|
350
|
+
logger.info("--> Preparing operations...")
|
|
351
|
+
state.set_stage(StateStage.Prepare)
|
|
365
352
|
can_diff, state, config = _handle_commands(
|
|
366
353
|
state, config, command, original_operations, operations
|
|
367
354
|
)
|
|
@@ -385,41 +372,72 @@ def _main(
|
|
|
385
372
|
if dry:
|
|
386
373
|
_exit()
|
|
387
374
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
)
|
|
375
|
+
click.echo(
|
|
376
|
+
"""
|
|
377
|
+
Detected changes may not include every change pyinfra will execute.
|
|
378
|
+
Hidden side effects of operations may alter behaviour of future operations,
|
|
379
|
+
this will be shown in the results. The remote state will always be updated
|
|
380
|
+
to reflect the state defined by the input operations.""",
|
|
381
|
+
err=True,
|
|
382
|
+
)
|
|
383
|
+
if (
|
|
384
|
+
can_diff
|
|
385
|
+
and not yes
|
|
386
|
+
and not _do_confirm("Detected changes displayed above, skip this step with -y")
|
|
387
|
+
):
|
|
388
|
+
_exit()
|
|
402
389
|
|
|
403
390
|
logger.info("--> Beginning operation run...")
|
|
404
|
-
|
|
391
|
+
state.set_stage(StateStage.Execute)
|
|
405
392
|
run_ops(state, serial=serial, no_wait=no_wait)
|
|
406
393
|
|
|
407
394
|
logger.info("--> Results:")
|
|
395
|
+
state.set_stage(StateStage.Disconnect)
|
|
408
396
|
print_results(state)
|
|
409
397
|
_exit()
|
|
410
398
|
|
|
411
399
|
|
|
400
|
+
def _do_confirm(msg: str) -> bool:
|
|
401
|
+
click.echo(err=True)
|
|
402
|
+
click.echo(f" {msg}", err=True)
|
|
403
|
+
warning_count = state.get_warning_counter()
|
|
404
|
+
if warning_count > 0:
|
|
405
|
+
click.secho(
|
|
406
|
+
f" {warning_count} warnings shown during change detection, see above",
|
|
407
|
+
fg="yellow",
|
|
408
|
+
err=True,
|
|
409
|
+
)
|
|
410
|
+
confirm_msg = " Press enter to execute..."
|
|
411
|
+
click.echo(confirm_msg, err=True, nl=False)
|
|
412
|
+
v = input()
|
|
413
|
+
if v:
|
|
414
|
+
click.echo(f" Unexpected user input: {v}", err=True)
|
|
415
|
+
return False
|
|
416
|
+
# Go up, clear the line, go up again - as if the confirmation statement was never here!
|
|
417
|
+
click.echo(
|
|
418
|
+
"\033[1A{0}\033[1A".format("".join(" " for _ in range(len(confirm_msg)))),
|
|
419
|
+
err=True,
|
|
420
|
+
nl=False,
|
|
421
|
+
)
|
|
422
|
+
click.echo(err=True)
|
|
423
|
+
return True
|
|
424
|
+
|
|
425
|
+
|
|
412
426
|
# Setup
|
|
413
427
|
#
|
|
414
|
-
def _setup_log_level(debug):
|
|
428
|
+
def _setup_log_level(debug, debug_all):
|
|
415
429
|
if not debug and not sys.warnoptions:
|
|
416
430
|
warnings.simplefilter("ignore")
|
|
417
431
|
|
|
418
432
|
log_level = logging.INFO
|
|
419
|
-
if debug:
|
|
433
|
+
if debug or debug_all:
|
|
420
434
|
log_level = logging.DEBUG
|
|
421
435
|
|
|
422
|
-
|
|
436
|
+
other_log_level = None
|
|
437
|
+
if debug_all:
|
|
438
|
+
other_log_level = logging.DEBUG
|
|
439
|
+
|
|
440
|
+
setup_logging(log_level, other_log_level)
|
|
423
441
|
|
|
424
442
|
|
|
425
443
|
def _validate_operations(operations, chdir):
|
|
@@ -503,12 +521,12 @@ def _set_verbosity(state, verbosity):
|
|
|
503
521
|
return state
|
|
504
522
|
|
|
505
523
|
|
|
506
|
-
def _setup_state(verbosity,
|
|
524
|
+
def _setup_state(verbosity, yes):
|
|
507
525
|
cwd = getcwd()
|
|
508
526
|
if cwd not in sys.path: # ensure cwd is present in sys.path
|
|
509
527
|
sys.path.append(cwd)
|
|
510
528
|
|
|
511
|
-
state = State(check_for_changes=
|
|
529
|
+
state = State(check_for_changes=not yes)
|
|
512
530
|
state.cwd = cwd
|
|
513
531
|
ctx_state.set(state)
|
|
514
532
|
|
|
@@ -526,7 +544,7 @@ def _set_config(
|
|
|
526
544
|
parallel,
|
|
527
545
|
shell_executable,
|
|
528
546
|
fail_percent,
|
|
529
|
-
|
|
547
|
+
yes,
|
|
530
548
|
):
|
|
531
549
|
logger.info("--> Loading config...")
|
|
532
550
|
|
|
@@ -571,10 +589,6 @@ def _set_override_data(
|
|
|
571
589
|
ssh_key_password,
|
|
572
590
|
ssh_port,
|
|
573
591
|
ssh_password,
|
|
574
|
-
winrm_username,
|
|
575
|
-
winrm_password,
|
|
576
|
-
winrm_port,
|
|
577
|
-
winrm_transport,
|
|
578
592
|
):
|
|
579
593
|
override_data = {}
|
|
580
594
|
|
|
@@ -590,10 +604,6 @@ def _set_override_data(
|
|
|
590
604
|
("ssh_key_password", ssh_key_password),
|
|
591
605
|
("ssh_port", ssh_port),
|
|
592
606
|
("ssh_password", ssh_password),
|
|
593
|
-
("winrm_username", winrm_username),
|
|
594
|
-
("winrm_password", winrm_password),
|
|
595
|
-
("winrm_port", winrm_port),
|
|
596
|
-
("winrm_transport", winrm_transport),
|
|
597
607
|
):
|
|
598
608
|
if value:
|
|
599
609
|
override_data[key] = value
|
|
@@ -601,6 +611,19 @@ def _set_override_data(
|
|
|
601
611
|
return override_data
|
|
602
612
|
|
|
603
613
|
|
|
614
|
+
def _set_fail_prompts(state: State, config: Config) -> None:
|
|
615
|
+
# Set fail percent to zero, meaning we'll raise an exception for any fail,
|
|
616
|
+
# and we can capture + prompt the user to continue/exit.
|
|
617
|
+
config.FAIL_PERCENT = 0
|
|
618
|
+
|
|
619
|
+
def should_raise_failed_hosts(state: State) -> bool:
|
|
620
|
+
if state.current_stage == StateStage.Connect:
|
|
621
|
+
return not _do_confirm("One of more hosts failed to connect, continue?")
|
|
622
|
+
return not _do_confirm("One of more hosts failed, continue?")
|
|
623
|
+
|
|
624
|
+
state.should_raise_failed_hosts = should_raise_failed_hosts
|
|
625
|
+
|
|
626
|
+
|
|
604
627
|
def _apply_inventory_limit(inventory, limit):
|
|
605
628
|
initial_limit = None
|
|
606
629
|
if limit:
|
pyinfra_cli/prints.py
CHANGED
|
@@ -217,28 +217,42 @@ def pretty_op_name(op_meta):
|
|
|
217
217
|
|
|
218
218
|
def print_meta(state: "State"):
|
|
219
219
|
rows: List[Tuple[Callable, Union[List[str], str]]] = [
|
|
220
|
-
(logger.info, ["Operation", "
|
|
220
|
+
(logger.info, ["Operation", "Change", "Conditional Change"]),
|
|
221
221
|
]
|
|
222
222
|
|
|
223
223
|
for op_hash in state.get_op_order():
|
|
224
|
-
hosts_in_op =
|
|
224
|
+
hosts_in_op = []
|
|
225
|
+
hosts_maybe_in_op = []
|
|
225
226
|
for host in state.inventory.iter_activated_hosts():
|
|
226
227
|
if op_hash in state.ops[host]:
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
op_data = state.get_op_data_for_host(host, op_hash)
|
|
229
|
+
if op_data.operation_meta._maybe_is_change:
|
|
230
|
+
if op_data.global_arguments["_if"]:
|
|
231
|
+
hosts_maybe_in_op.append(host.name)
|
|
232
|
+
else:
|
|
233
|
+
hosts_in_op.append(host.name)
|
|
229
234
|
|
|
230
235
|
rows.append(
|
|
231
236
|
(
|
|
232
237
|
logger.info,
|
|
233
238
|
[
|
|
234
239
|
pretty_op_name(state.op_meta[op_hash]),
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
len(
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
(
|
|
241
|
+
"-"
|
|
242
|
+
if len(hosts_in_op) == 0
|
|
243
|
+
else "{0} ({1})".format(
|
|
244
|
+
len(hosts_in_op),
|
|
245
|
+
truncate(", ".join(sorted(hosts_in_op)), 48),
|
|
246
|
+
)
|
|
247
|
+
),
|
|
248
|
+
(
|
|
249
|
+
"-"
|
|
250
|
+
if len(hosts_maybe_in_op) == 0
|
|
251
|
+
else "{0} ({1})".format(
|
|
252
|
+
len(hosts_maybe_in_op),
|
|
253
|
+
truncate(", ".join(sorted(hosts_maybe_in_op)), 48),
|
|
254
|
+
)
|
|
255
|
+
),
|
|
242
256
|
],
|
|
243
257
|
)
|
|
244
258
|
)
|
|
@@ -253,25 +267,23 @@ def print_results(state: "State"):
|
|
|
253
267
|
|
|
254
268
|
for op_hash in state.get_op_order():
|
|
255
269
|
hosts_in_op = 0
|
|
256
|
-
hosts_in_op_success =
|
|
257
|
-
hosts_in_op_error =
|
|
258
|
-
|
|
270
|
+
hosts_in_op_success: list[str] = []
|
|
271
|
+
hosts_in_op_error: list[str] = []
|
|
272
|
+
hosts_in_op_no_change: list[str] = []
|
|
259
273
|
for host in state.inventory.iter_activated_hosts():
|
|
260
274
|
if op_hash not in state.ops[host]:
|
|
261
275
|
continue
|
|
262
276
|
|
|
263
277
|
hosts_in_op += 1
|
|
264
278
|
|
|
265
|
-
|
|
266
|
-
if
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
279
|
+
op_meta = state.ops[host][op_hash].operation_meta
|
|
280
|
+
if op_meta.did_succeed(_raise_if_not_complete=False):
|
|
281
|
+
if op_meta._did_change():
|
|
282
|
+
hosts_in_op_success.append(host.name)
|
|
283
|
+
else:
|
|
284
|
+
hosts_in_op_no_change.append(host.name)
|
|
270
285
|
else:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
# if not hosts_in_op:
|
|
274
|
-
# continue
|
|
286
|
+
hosts_in_op_error.append(host.name)
|
|
275
287
|
|
|
276
288
|
row = [
|
|
277
289
|
pretty_op_name(state.op_meta[op_hash]),
|
|
@@ -286,95 +298,11 @@ def print_results(state: "State"):
|
|
|
286
298
|
row.append(f"{len(hosts_in_op_error)}")
|
|
287
299
|
else:
|
|
288
300
|
row.append("-")
|
|
289
|
-
if
|
|
290
|
-
row.append(f"{len(
|
|
301
|
+
if hosts_in_op_no_change:
|
|
302
|
+
row.append(f"{len(hosts_in_op_no_change)}")
|
|
291
303
|
else:
|
|
292
304
|
row.append("-")
|
|
293
305
|
|
|
294
306
|
rows.append((logger.info, row))
|
|
295
307
|
|
|
296
308
|
print_rows(rows)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
def get_fucked(state: "State"):
|
|
300
|
-
group_combinations = _get_group_combinations(state.inventory.iter_activated_hosts())
|
|
301
|
-
rows: List[Tuple[Callable, Union[List[str], str]]] = []
|
|
302
|
-
|
|
303
|
-
for i, (groups, hosts) in enumerate(group_combinations.items(), 1):
|
|
304
|
-
if not hosts:
|
|
305
|
-
continue
|
|
306
|
-
|
|
307
|
-
if groups:
|
|
308
|
-
rows.append(
|
|
309
|
-
(
|
|
310
|
-
logger.info,
|
|
311
|
-
"Groups: {0}".format(
|
|
312
|
-
click.style(" / ".join(groups), bold=True),
|
|
313
|
-
),
|
|
314
|
-
),
|
|
315
|
-
)
|
|
316
|
-
else:
|
|
317
|
-
rows.append((logger.info, "Ungrouped:"))
|
|
318
|
-
|
|
319
|
-
for host in hosts:
|
|
320
|
-
# Didn't connect to this host?
|
|
321
|
-
if host not in state.activated_hosts:
|
|
322
|
-
rows.append(
|
|
323
|
-
(
|
|
324
|
-
logger.info,
|
|
325
|
-
[
|
|
326
|
-
host.style_print_prefix("red", bold=True),
|
|
327
|
-
click.style("No connection", "red"),
|
|
328
|
-
],
|
|
329
|
-
),
|
|
330
|
-
)
|
|
331
|
-
continue
|
|
332
|
-
|
|
333
|
-
results = state.results[host]
|
|
334
|
-
|
|
335
|
-
meta = state.meta[host]
|
|
336
|
-
success_ops = results.success_ops
|
|
337
|
-
partial_ops = results.partial_ops
|
|
338
|
-
# TODO: type meta object
|
|
339
|
-
changed_ops = success_ops - meta.ops_no_change # type: ignore
|
|
340
|
-
error_ops = results.error_ops
|
|
341
|
-
ignored_error_ops = results.ignored_error_ops
|
|
342
|
-
|
|
343
|
-
host_args = ("green",)
|
|
344
|
-
host_kwargs = {}
|
|
345
|
-
|
|
346
|
-
# If all ops got complete
|
|
347
|
-
if results.ops == meta.ops:
|
|
348
|
-
# We had some errors - but we ignored them - so "warning" color
|
|
349
|
-
if error_ops != 0:
|
|
350
|
-
host_args = ("yellow",)
|
|
351
|
-
|
|
352
|
-
# Ops did not complete!
|
|
353
|
-
else:
|
|
354
|
-
host_args = ("red",)
|
|
355
|
-
host_kwargs["bold"] = True
|
|
356
|
-
|
|
357
|
-
changed_str = "Changed: {0}".format(click.style(f"{changed_ops}", bold=True))
|
|
358
|
-
if partial_ops:
|
|
359
|
-
changed_str = f"{changed_str} ({partial_ops} partial)"
|
|
360
|
-
|
|
361
|
-
error_str = "Errors: {0}".format(click.style(f"{error_ops}", bold=True))
|
|
362
|
-
if ignored_error_ops:
|
|
363
|
-
error_str = f"{error_str} ({ignored_error_ops} ignored)"
|
|
364
|
-
|
|
365
|
-
rows.append(
|
|
366
|
-
(
|
|
367
|
-
logger.info,
|
|
368
|
-
[
|
|
369
|
-
host.style_print_prefix(*host_args, **host_kwargs),
|
|
370
|
-
changed_str,
|
|
371
|
-
"No change: {0}".format(click.style(f"{meta.ops_no_change}", bold=True)),
|
|
372
|
-
error_str,
|
|
373
|
-
],
|
|
374
|
-
),
|
|
375
|
-
)
|
|
376
|
-
|
|
377
|
-
if i != len(group_combinations):
|
|
378
|
-
rows.append((lambda m: click.echo(m, err=True), []))
|
|
379
|
-
|
|
380
|
-
print_rows(rows)
|