pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyinfra/api/__init__.py +3 -0
- pyinfra/api/arguments.py +261 -255
- pyinfra/api/arguments_typed.py +77 -0
- pyinfra/api/command.py +66 -53
- pyinfra/api/config.py +27 -22
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +2 -24
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +77 -113
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +17 -25
- pyinfra/api/operation.py +232 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +55 -70
- pyinfra/connectors/base.py +150 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +227 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +195 -207
- pyinfra/connectors/ssh.py +528 -615
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +212 -137
- pyinfra/connectors/vagrant.py +55 -48
- pyinfra/context.py +3 -2
- pyinfra/facts/docker.py +1 -0
- pyinfra/facts/files.py +45 -32
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +1 -1
- pyinfra/facts/hardware.py +4 -2
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/mysql.py +1 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +5 -161
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +77 -30
- pyinfra/facts/systemd.py +29 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/util/packaging.py +4 -2
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +3 -3
- pyinfra/operations/apt.py +25 -47
- pyinfra/operations/brew.py +7 -14
- pyinfra/operations/bsdinit.py +4 -4
- pyinfra/operations/cargo.py +1 -1
- pyinfra/operations/choco.py +1 -1
- pyinfra/operations/dnf.py +4 -4
- pyinfra/operations/files.py +108 -321
- pyinfra/operations/gem.py +1 -1
- pyinfra/operations/git.py +6 -37
- pyinfra/operations/iptables.py +2 -10
- pyinfra/operations/launchd.py +1 -1
- pyinfra/operations/lxd.py +1 -9
- pyinfra/operations/mysql.py +5 -28
- pyinfra/operations/npm.py +1 -1
- pyinfra/operations/openrc.py +1 -1
- pyinfra/operations/pacman.py +3 -3
- pyinfra/operations/pip.py +14 -15
- pyinfra/operations/pkg.py +1 -1
- pyinfra/operations/pkgin.py +3 -3
- pyinfra/operations/postgres.py +347 -0
- pyinfra/operations/postgresql.py +17 -380
- pyinfra/operations/python.py +2 -17
- pyinfra/operations/selinux.py +5 -28
- pyinfra/operations/server.py +59 -84
- pyinfra/operations/snap.py +1 -3
- pyinfra/operations/ssh.py +8 -23
- pyinfra/operations/systemd.py +7 -7
- pyinfra/operations/sysvinit.py +3 -12
- pyinfra/operations/upstart.py +4 -4
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/files.py +2 -2
- pyinfra/operations/util/packaging.py +6 -24
- pyinfra/operations/util/service.py +18 -37
- pyinfra/operations/vzctl.py +2 -2
- pyinfra/operations/xbps.py +3 -3
- pyinfra/operations/yum.py +4 -4
- pyinfra/operations/zypper.py +4 -4
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
- pyinfra-3.0b1.dist-info/RECORD +163 -0
- pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +2 -0
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +83 -42
- pyinfra_cli/inventory.py +19 -4
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +93 -129
- pyinfra_cli/util.py +60 -29
- tests/test_api/test_api.py +2 -0
- tests/test_api/test_api_arguments.py +13 -13
- tests/test_api/test_api_deploys.py +28 -29
- tests/test_api/test_api_facts.py +60 -98
- tests/test_api/test_api_operations.py +100 -200
- tests/test_cli/test_cli.py +18 -49
- tests/test_cli/test_cli_deploy.py +11 -37
- tests/test_cli/test_cli_exceptions.py +50 -19
- tests/test_cli/util.py +1 -1
- tests/test_connectors/test_chroot.py +6 -6
- tests/test_connectors/test_docker.py +4 -4
- tests/test_connectors/test_dockerssh.py +38 -50
- tests/test_connectors/test_local.py +11 -12
- tests/test_connectors/test_ssh.py +66 -107
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +4 -4
- pyinfra/api/operation.pyi +0 -117
- pyinfra/connectors/ansible.py +0 -171
- pyinfra/connectors/mech.py +0 -186
- pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
- pyinfra/connectors/winrm.py +0 -320
- pyinfra/facts/windows.py +0 -366
- pyinfra/facts/windows_files.py +0 -90
- pyinfra/operations/windows.py +0 -59
- pyinfra/operations/windows_files.py +0 -551
- pyinfra-2.9.2.dist-info/RECORD +0 -170
- pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
- tests/test_connectors/test_ansible.py +0 -64
- tests/test_connectors/test_mech.py +0 -126
- tests/test_connectors/test_winrm.py +0 -76
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/top_level.txt +0 -0
pyinfra_cli/commands.py
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import json
|
|
2
4
|
|
|
5
|
+
from pyinfra.api.facts import FactBase
|
|
6
|
+
|
|
3
7
|
from .exceptions import CliError
|
|
4
8
|
from .util import parse_cli_arg, try_import_module_attribute
|
|
5
9
|
|
|
@@ -32,7 +36,7 @@ def get_func_and_args(commands):
|
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
def get_facts_and_args(commands):
|
|
35
|
-
facts = []
|
|
39
|
+
facts: list[tuple[FactBase, tuple, dict]] = []
|
|
36
40
|
|
|
37
41
|
current_fact = None
|
|
38
42
|
|
|
@@ -42,7 +46,7 @@ def get_facts_and_args(commands):
|
|
|
42
46
|
raise CliError("Invalid fact commands: `{0}`".format(commands))
|
|
43
47
|
|
|
44
48
|
key, value = command.split("=", 1)
|
|
45
|
-
current_fact[2][key] = value
|
|
49
|
+
current_fact[2][key] = parse_cli_arg(value)
|
|
46
50
|
continue
|
|
47
51
|
|
|
48
52
|
if current_fact:
|
|
@@ -53,6 +57,7 @@ def get_facts_and_args(commands):
|
|
|
53
57
|
raise CliError(f"Invalid fact: `{command}`, should be in the format `module.cls`")
|
|
54
58
|
|
|
55
59
|
fact_cls = try_import_module_attribute(command, prefix="pyinfra.facts")
|
|
60
|
+
assert fact_cls is not None
|
|
56
61
|
current_fact = (fact_cls, (), {})
|
|
57
62
|
|
|
58
63
|
if current_fact:
|
pyinfra_cli/exceptions.py
CHANGED
|
@@ -1,67 +1,108 @@
|
|
|
1
|
+
import abc
|
|
1
2
|
import sys
|
|
2
|
-
from
|
|
3
|
+
from inspect import getframeinfo
|
|
4
|
+
from os import path
|
|
5
|
+
from traceback import format_exception, format_tb, walk_tb
|
|
6
|
+
from types import TracebackType
|
|
3
7
|
|
|
4
8
|
import click
|
|
5
9
|
|
|
6
|
-
from pyinfra import
|
|
7
|
-
from pyinfra.api.exceptions import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
from pyinfra import logger
|
|
11
|
+
from pyinfra.api.exceptions import (
|
|
12
|
+
ArgumentTypeError,
|
|
13
|
+
ConnectorDataTypeError,
|
|
14
|
+
OperationError,
|
|
15
|
+
PyinfraError,
|
|
16
|
+
)
|
|
17
|
+
from pyinfra.api.util import PYINFRA_INSTALL_DIR
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_frame_line_from_tb(tb: TracebackType):
|
|
21
|
+
frame_lines = list(walk_tb(tb))
|
|
22
|
+
frame_lines.reverse()
|
|
23
|
+
for frame, line in frame_lines:
|
|
24
|
+
info = getframeinfo(frame)
|
|
25
|
+
if info.filename.startswith(PYINFRA_INSTALL_DIR):
|
|
26
|
+
continue
|
|
27
|
+
if info.filename.startswith("/"):
|
|
28
|
+
continue
|
|
29
|
+
if not path.exists(info.filename):
|
|
30
|
+
continue
|
|
31
|
+
return info
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class WrappedError(click.ClickException):
|
|
35
|
+
def __init__(self, e: Exception):
|
|
36
|
+
self.traceback = e.__traceback__
|
|
37
|
+
self.exception = e
|
|
38
|
+
|
|
39
|
+
# Pull message from the wrapped exception
|
|
40
|
+
message = getattr(e, "message", e.args[0])
|
|
41
|
+
if not isinstance(message, str):
|
|
42
|
+
message = repr(message)
|
|
43
|
+
self.message = message
|
|
44
|
+
|
|
45
|
+
def show(self, file=None):
|
|
13
46
|
name = "unknown error"
|
|
14
47
|
|
|
15
|
-
if isinstance(self,
|
|
48
|
+
if isinstance(self.exception, ConnectorDataTypeError):
|
|
49
|
+
name = "Connector data type error"
|
|
50
|
+
elif isinstance(self.exception, ArgumentTypeError):
|
|
51
|
+
name = "Argument type error"
|
|
52
|
+
elif isinstance(self.exception, OperationError):
|
|
53
|
+
name = "Operation error"
|
|
54
|
+
elif isinstance(self.exception, PyinfraError):
|
|
16
55
|
name = "pyinfra error"
|
|
56
|
+
elif isinstance(self.exception, IOError):
|
|
57
|
+
name = "Local IO error"
|
|
58
|
+
|
|
59
|
+
if self.traceback:
|
|
60
|
+
info = get_frame_line_from_tb(self.traceback)
|
|
61
|
+
if info:
|
|
62
|
+
name = f"{name} in {info.filename} line {info.lineno}"
|
|
63
|
+
|
|
64
|
+
logger.warning(
|
|
65
|
+
"--> {0}: {1}".format(
|
|
66
|
+
click.style(name, "red", bold=True),
|
|
67
|
+
self,
|
|
68
|
+
),
|
|
69
|
+
)
|
|
17
70
|
|
|
18
|
-
elif isinstance(self, IOError):
|
|
19
|
-
name = "local IO error"
|
|
20
|
-
|
|
21
|
-
if ctx_host.isset():
|
|
22
|
-
# Get any operation meta + name
|
|
23
|
-
op_name = None
|
|
24
|
-
current_op_hash = host.current_op_hash
|
|
25
|
-
current_op_meta = state.op_meta.get(current_op_hash)
|
|
26
|
-
if current_op_meta:
|
|
27
|
-
op_name = ", ".join(current_op_meta["names"])
|
|
28
|
-
|
|
29
|
-
sys.stderr.write(
|
|
30
|
-
"--> {0}{1}{2}: ".format(
|
|
31
|
-
host.print_prefix,
|
|
32
|
-
click.style(name, "red", bold=True),
|
|
33
|
-
" (operation={0})".format(op_name) if op_name else "",
|
|
34
|
-
),
|
|
35
|
-
)
|
|
36
|
-
else:
|
|
37
|
-
sys.stderr.write(
|
|
38
|
-
"--> {0}: ".format(click.style(name, "red", bold=True)),
|
|
39
|
-
)
|
|
40
71
|
|
|
41
|
-
|
|
72
|
+
class CliError(click.ClickException):
|
|
73
|
+
def show(self, file=None):
|
|
74
|
+
logger.warning(
|
|
75
|
+
"--> {0}: {1}".format(
|
|
76
|
+
click.style("pyinfra error", "red", bold=True),
|
|
77
|
+
self,
|
|
78
|
+
),
|
|
79
|
+
)
|
|
80
|
+
|
|
42
81
|
|
|
82
|
+
class UnexpectedMixin(abc.ABC):
|
|
83
|
+
exception: Exception
|
|
84
|
+
traceback: TracebackType
|
|
43
85
|
|
|
44
|
-
class UnexpectedMixin:
|
|
45
86
|
def get_traceback_lines(self):
|
|
46
|
-
traceback = getattr(self.
|
|
87
|
+
traceback = getattr(self.exception, "_traceback")
|
|
47
88
|
return format_tb(traceback)
|
|
48
89
|
|
|
49
90
|
def get_traceback(self):
|
|
50
91
|
return "".join(self.get_traceback_lines())
|
|
51
92
|
|
|
52
93
|
def get_exception(self):
|
|
53
|
-
return "".join(format_exception(self.
|
|
94
|
+
return "".join(format_exception(self.exception.__class__, self.exception, None))
|
|
54
95
|
|
|
55
96
|
|
|
56
97
|
class UnexpectedExternalError(click.ClickException, UnexpectedMixin):
|
|
57
98
|
def __init__(self, e, filename):
|
|
58
99
|
_, _, traceback = sys.exc_info()
|
|
59
100
|
e._traceback = traceback
|
|
60
|
-
self.
|
|
101
|
+
self.exception = e
|
|
61
102
|
self.filename = filename
|
|
62
103
|
|
|
63
|
-
def show(self):
|
|
64
|
-
|
|
104
|
+
def show(self, file=None):
|
|
105
|
+
logger.warning(
|
|
65
106
|
"--> {0}:\n".format(
|
|
66
107
|
click.style(
|
|
67
108
|
"An exception occurred in: {0}".format(self.filename),
|
|
@@ -69,9 +110,9 @@ class UnexpectedExternalError(click.ClickException, UnexpectedMixin):
|
|
|
69
110
|
bold=True,
|
|
70
111
|
),
|
|
71
112
|
),
|
|
72
|
-
err=True,
|
|
73
113
|
)
|
|
74
114
|
|
|
115
|
+
click.echo("Traceback (most recent call last):", err=True)
|
|
75
116
|
click.echo(self.get_traceback(), err=True, nl=False)
|
|
76
117
|
click.echo(self.get_exception(), err=True)
|
|
77
118
|
|
|
@@ -80,9 +121,9 @@ class UnexpectedInternalError(click.ClickException, UnexpectedMixin):
|
|
|
80
121
|
def __init__(self, e):
|
|
81
122
|
_, _, traceback = sys.exc_info()
|
|
82
123
|
e._traceback = traceback
|
|
83
|
-
self.
|
|
124
|
+
self.exception = e
|
|
84
125
|
|
|
85
|
-
def show(self):
|
|
126
|
+
def show(self, file=None):
|
|
86
127
|
click.echo(
|
|
87
128
|
"--> {0}:\n".format(
|
|
88
129
|
click.style(
|
|
@@ -99,7 +140,7 @@ class UnexpectedInternalError(click.ClickException, UnexpectedMixin):
|
|
|
99
140
|
|
|
100
141
|
# Syntax errors contain the filename/line/etc, but other exceptions
|
|
101
142
|
# don't, so print the *last* call to stderr.
|
|
102
|
-
if not isinstance(self.
|
|
143
|
+
if not isinstance(self.exception, SyntaxError):
|
|
103
144
|
sys.stderr.write(traceback_lines[-1])
|
|
104
145
|
|
|
105
146
|
exception = self.get_exception()
|
pyinfra_cli/inventory.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import socket
|
|
1
3
|
from collections import defaultdict
|
|
2
4
|
from os import listdir, path
|
|
3
5
|
from types import GeneratorType
|
|
@@ -85,10 +87,23 @@ def make_inventory(
|
|
|
85
87
|
cwd: Optional[str] = None,
|
|
86
88
|
group_data_directories=None,
|
|
87
89
|
):
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
inventory_func = None
|
|
91
|
+
|
|
92
|
+
# (Un)fortunately the CLI is pretty flexible for inventory inputs; we support a single hostname,
|
|
93
|
+
# a Python module.function import or a Python file path. All of these are kind of similar, and
|
|
94
|
+
# we want error handling to be a good user experience.
|
|
95
|
+
# Thus, we'll check for everything but also drop a warning to the console if the inventory looks
|
|
96
|
+
# like either an import or hostname but neither works.
|
|
97
|
+
if re.match("[a-zA-Z0-9\\._]+[\\.:][a-zA-Z0-9_]+", inventory):
|
|
98
|
+
# First, try loading the inventory as if it's a Python import function
|
|
99
|
+
inventory_func = try_import_module_attribute(inventory, raise_for_none=False)
|
|
100
|
+
if inventory_func is None:
|
|
101
|
+
try:
|
|
102
|
+
socket.gethostbyname(inventory)
|
|
103
|
+
except socket.gaierror:
|
|
104
|
+
logger.warning(f"{inventory} is neither a valid Python import or hostname.")
|
|
105
|
+
|
|
106
|
+
if inventory_func is None:
|
|
92
107
|
# If not an import, load as if from the filesystem *or* comma separated list, which also
|
|
93
108
|
# loads any all.py group data files (imported functions do not load group data).
|
|
94
109
|
return make_inventory_from_files(inventory, override_data, cwd, group_data_directories)
|
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):
|
|
@@ -15,6 +16,8 @@ class LogHandler(logging.Handler):
|
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class LogFormatter(logging.Formatter):
|
|
19
|
+
previous_was_header = True
|
|
20
|
+
|
|
18
21
|
level_to_format = {
|
|
19
22
|
logging.DEBUG: lambda s: click.style(s, "green"),
|
|
20
23
|
logging.WARNING: lambda s: click.style(s, "yellow"),
|
|
@@ -39,21 +42,32 @@ class LogFormatter(logging.Formatter):
|
|
|
39
42
|
|
|
40
43
|
# We only handle strings here
|
|
41
44
|
if isinstance(message, str):
|
|
42
|
-
if
|
|
45
|
+
if ctx_state.isset() and record.levelno is logging.WARNING:
|
|
46
|
+
state.increment_warning_counter()
|
|
47
|
+
|
|
48
|
+
if "-->" in message:
|
|
49
|
+
if not self.previous_was_header:
|
|
50
|
+
click.echo(err=True)
|
|
51
|
+
else:
|
|
43
52
|
message = " {0}".format(message)
|
|
44
53
|
|
|
45
54
|
if record.levelno in self.level_to_format:
|
|
46
55
|
message = self.level_to_format[record.levelno](message)
|
|
47
56
|
|
|
57
|
+
self.previous_was_header = "-->" in message
|
|
48
58
|
return message
|
|
49
59
|
|
|
50
60
|
# If not a string, pass to standard Formatter
|
|
51
61
|
return super().format(record)
|
|
52
62
|
|
|
53
63
|
|
|
54
|
-
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
|
+
|
|
55
68
|
logger.setLevel(log_level)
|
|
56
69
|
handler = LogHandler()
|
|
57
70
|
formatter = LogFormatter()
|
|
58
71
|
handler.setFormatter(formatter)
|
|
59
72
|
logger.addHandler(handler)
|
|
73
|
+
logger.propagate = False
|