pyinfra 2.9.2__py2.py3-none-any.whl → 3.0__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 +265 -253
- pyinfra/api/arguments_typed.py +80 -0
- pyinfra/api/command.py +68 -53
- pyinfra/api/config.py +139 -32
- pyinfra/api/connect.py +1 -1
- pyinfra/api/connectors.py +7 -26
- pyinfra/api/deploy.py +21 -52
- pyinfra/api/exceptions.py +33 -8
- pyinfra/api/facts.py +102 -137
- pyinfra/api/host.py +150 -82
- pyinfra/api/inventory.py +21 -25
- pyinfra/api/operation.py +240 -198
- pyinfra/api/operations.py +102 -148
- pyinfra/api/state.py +137 -79
- pyinfra/api/util.py +79 -86
- pyinfra/connectors/base.py +147 -0
- pyinfra/connectors/chroot.py +160 -169
- pyinfra/connectors/docker.py +220 -237
- pyinfra/connectors/dockerssh.py +231 -253
- pyinfra/connectors/local.py +196 -208
- pyinfra/connectors/ssh.py +530 -613
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +5 -3
- pyinfra/connectors/terraform.py +86 -65
- pyinfra/connectors/util.py +211 -137
- pyinfra/connectors/vagrant.py +60 -53
- pyinfra/context.py +4 -2
- pyinfra/facts/apk.py +2 -0
- pyinfra/facts/apt.py +2 -0
- pyinfra/facts/brew.py +2 -0
- pyinfra/facts/bsdinit.py +2 -0
- pyinfra/facts/cargo.py +2 -0
- pyinfra/facts/choco.py +2 -0
- pyinfra/facts/deb.py +7 -2
- pyinfra/facts/dnf.py +2 -0
- pyinfra/facts/docker.py +19 -0
- pyinfra/facts/files.py +47 -32
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/git.py +3 -1
- pyinfra/facts/gpg.py +3 -1
- pyinfra/facts/hardware.py +34 -24
- pyinfra/facts/iptables.py +5 -3
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +13 -6
- pyinfra/facts/npm.py +1 -0
- pyinfra/facts/openrc.py +2 -0
- pyinfra/facts/pacman.py +6 -2
- pyinfra/facts/pip.py +2 -0
- pyinfra/facts/pkg.py +2 -0
- pyinfra/facts/pkgin.py +2 -0
- pyinfra/facts/postgres.py +168 -0
- pyinfra/facts/postgresql.py +6 -160
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/selinux.py +3 -1
- pyinfra/facts/server.py +80 -36
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +31 -12
- pyinfra/facts/sysvinit.py +10 -10
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +7 -4
- pyinfra/facts/vzctl.py +2 -0
- pyinfra/facts/xbps.py +2 -0
- pyinfra/facts/yum.py +2 -0
- pyinfra/facts/zypper.py +2 -0
- pyinfra/local.py +4 -5
- pyinfra/operations/apk.py +6 -4
- pyinfra/operations/apt.py +46 -65
- pyinfra/operations/brew.py +17 -22
- pyinfra/operations/bsdinit.py +9 -7
- pyinfra/operations/cargo.py +4 -2
- pyinfra/operations/choco.py +4 -2
- pyinfra/operations/dnf.py +19 -23
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +188 -386
- pyinfra/operations/gem.py +4 -2
- pyinfra/operations/git.py +24 -53
- pyinfra/operations/iptables.py +29 -35
- pyinfra/operations/launchd.py +6 -7
- pyinfra/operations/lxd.py +8 -13
- pyinfra/operations/mysql.py +62 -81
- pyinfra/operations/npm.py +9 -2
- pyinfra/operations/openrc.py +6 -4
- pyinfra/operations/pacman.py +7 -8
- pyinfra/operations/pip.py +25 -24
- pyinfra/operations/pkg.py +4 -2
- pyinfra/operations/pkgin.py +6 -4
- pyinfra/operations/postgres.py +349 -0
- pyinfra/operations/postgresql.py +18 -379
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +8 -19
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +47 -44
- pyinfra/operations/server.py +111 -127
- pyinfra/operations/snap.py +4 -4
- pyinfra/operations/ssh.py +20 -33
- pyinfra/operations/systemd.py +19 -15
- pyinfra/operations/sysvinit.py +9 -16
- pyinfra/operations/upstart.py +9 -7
- 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 +55 -57
- pyinfra/operations/util/service.py +39 -51
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +6 -4
- pyinfra/operations/yum.py +18 -22
- pyinfra/operations/zypper.py +12 -13
- pyinfra/version.py +5 -2
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
- pyinfra-3.0.dist-info/RECORD +167 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
- pyinfra-3.0.dist-info/entry_points.txt +11 -0
- pyinfra_cli/__main__.py +4 -3
- pyinfra_cli/commands.py +7 -2
- pyinfra_cli/exceptions.py +78 -42
- pyinfra_cli/inventory.py +40 -6
- pyinfra_cli/log.py +17 -3
- pyinfra_cli/main.py +133 -90
- pyinfra_cli/prints.py +95 -127
- pyinfra_cli/util.py +62 -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 +101 -201
- 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 +105 -93
- tests/test_connectors/test_terraform.py +9 -15
- tests/test_connectors/test_util.py +24 -46
- tests/test_connectors/test_vagrant.py +7 -7
- 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.0.dist-info}/LICENSE.md +0 -0
- {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from getpass import getpass
|
|
2
|
+
from os import path
|
|
3
|
+
from typing import TYPE_CHECKING, Type, Union
|
|
4
|
+
|
|
5
|
+
from paramiko import (
|
|
6
|
+
DSSKey,
|
|
7
|
+
ECDSAKey,
|
|
8
|
+
Ed25519Key,
|
|
9
|
+
PasswordRequiredException,
|
|
10
|
+
PKey,
|
|
11
|
+
RSAKey,
|
|
12
|
+
SSHException,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
import pyinfra
|
|
16
|
+
from pyinfra.api.exceptions import ConnectError, PyinfraError
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from pyinfra.api.host import Host
|
|
20
|
+
from pyinfra.api.state import State
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def raise_connect_error(host: "Host", message, data):
|
|
24
|
+
message = "{0} ({1})".format(message, data)
|
|
25
|
+
raise ConnectError(message)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _load_private_key_file(filename: str, key_filename: str, key_password: str):
|
|
29
|
+
exception: Union[PyinfraError, SSHException] = PyinfraError("Invalid key: {0}".format(filename))
|
|
30
|
+
|
|
31
|
+
key_cls: Union[Type[RSAKey], Type[DSSKey], Type[ECDSAKey], Type[Ed25519Key]]
|
|
32
|
+
|
|
33
|
+
for key_cls in (RSAKey, DSSKey, ECDSAKey, Ed25519Key):
|
|
34
|
+
try:
|
|
35
|
+
return key_cls.from_private_key_file(
|
|
36
|
+
filename=filename,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
except PasswordRequiredException:
|
|
40
|
+
if not key_password:
|
|
41
|
+
# If password is not provided, but we're in CLI mode, ask for it. I'm not a
|
|
42
|
+
# huge fan of having CLI specific code in here, but it doesn't really fit
|
|
43
|
+
# anywhere else without duplicating lots of key related code into cli.py.
|
|
44
|
+
if pyinfra.is_cli:
|
|
45
|
+
key_password = getpass(
|
|
46
|
+
"Enter password for private key: {0}: ".format(
|
|
47
|
+
key_filename,
|
|
48
|
+
),
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# API mode and no password? We can't continue!
|
|
52
|
+
else:
|
|
53
|
+
raise PyinfraError(
|
|
54
|
+
"Private key file ({0}) is encrypted, set ssh_key_password to "
|
|
55
|
+
"use this key".format(key_filename),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
return key_cls.from_private_key_file(
|
|
60
|
+
filename=filename,
|
|
61
|
+
password=key_password,
|
|
62
|
+
)
|
|
63
|
+
except SSHException as e: # key does not match key_cls type
|
|
64
|
+
exception = e
|
|
65
|
+
except SSHException as e: # key does not match key_cls type
|
|
66
|
+
exception = e
|
|
67
|
+
raise exception
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def get_private_key(state: "State", key_filename: str, key_password: str) -> PKey:
|
|
71
|
+
if key_filename in state.private_keys:
|
|
72
|
+
return state.private_keys[key_filename]
|
|
73
|
+
|
|
74
|
+
ssh_key_filenames = [
|
|
75
|
+
# Global from executed directory
|
|
76
|
+
path.expanduser(key_filename),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
if state.cwd:
|
|
80
|
+
# Relative to the CWD
|
|
81
|
+
path.join(state.cwd, key_filename)
|
|
82
|
+
|
|
83
|
+
key = None
|
|
84
|
+
key_file_exists = False
|
|
85
|
+
|
|
86
|
+
for filename in ssh_key_filenames:
|
|
87
|
+
if not path.isfile(filename):
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
key_file_exists = True
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
key = _load_private_key_file(filename, key_filename, key_password)
|
|
94
|
+
break
|
|
95
|
+
except SSHException:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
# No break, so no key found
|
|
99
|
+
if not key:
|
|
100
|
+
if not key_file_exists:
|
|
101
|
+
raise PyinfraError("No such private key file: {0}".format(key_filename))
|
|
102
|
+
raise PyinfraError("Invalid private key file: {0}".format(key_filename))
|
|
103
|
+
|
|
104
|
+
# Load any certificate, names from OpenSSH:
|
|
105
|
+
# https://github.com/openssh/openssh-portable/blob/049297de975b92adcc2db77e3fb7046c0e3c695d/ssh-keygen.c#L2453 # noqa: E501
|
|
106
|
+
for certificate_filename in (
|
|
107
|
+
"{0}-cert.pub".format(key_filename),
|
|
108
|
+
"{0}.pub".format(key_filename),
|
|
109
|
+
):
|
|
110
|
+
if path.isfile(certificate_filename):
|
|
111
|
+
key.load_certificate(certificate_filename)
|
|
112
|
+
|
|
113
|
+
state.private_keys[key_filename] = key
|
|
114
|
+
return key
|
|
@@ -166,12 +166,14 @@ class SSHClient(ParamikoClient):
|
|
|
166
166
|
forward_agent = _pyinfra_ssh_forward_agent
|
|
167
167
|
|
|
168
168
|
if forward_agent:
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
transport = self.get_transport()
|
|
170
|
+
assert transport is not None, "No transport"
|
|
171
|
+
session = transport.open_session()
|
|
171
172
|
AgentRequestHandler(session)
|
|
172
173
|
|
|
173
174
|
def gateway(self, hostname, host_port, target, target_port):
|
|
174
175
|
transport = self.get_transport()
|
|
176
|
+
assert transport is not None, "No transport"
|
|
175
177
|
return transport.open_channel(
|
|
176
178
|
"direct-tcpip",
|
|
177
179
|
(target, target_port),
|
|
@@ -185,7 +187,7 @@ class SSHClient(ParamikoClient):
|
|
|
185
187
|
ssh_config_file=None,
|
|
186
188
|
strict_host_key_checking=None,
|
|
187
189
|
):
|
|
188
|
-
cfg = {"port": 22}
|
|
190
|
+
cfg: dict = {"port": 22}
|
|
189
191
|
cfg.update(initial_cfg or {})
|
|
190
192
|
|
|
191
193
|
forward_agent = False
|
pyinfra/connectors/terraform.py
CHANGED
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
.. warning::
|
|
3
|
-
This connector is in alpha and may change in future releases.
|
|
4
|
-
|
|
5
|
-
Generate one or more SSH hosts from a Terraform output variable. The variable
|
|
6
|
-
must be a list of hostnames or IP addresses that ``pyinfra`` can connect to
|
|
7
|
-
over SSH. Currently there is no support for specifying SSH user/pass/port/key
|
|
8
|
-
from Terraform, these must be provided via ``pyinfra`` group data or ``--data``
|
|
9
|
-
CLI flags.
|
|
10
|
-
|
|
11
|
-
Output is fetched from a flattened JSON dictionary output from ``terraform output
|
|
12
|
-
-json``. For example the following object:
|
|
13
|
-
|
|
14
|
-
.. code:: json
|
|
15
|
-
|
|
16
|
-
{
|
|
17
|
-
"server_group": {
|
|
18
|
-
"value": {
|
|
19
|
-
"server_group_node_ips": [
|
|
20
|
-
"1.2.3.4",
|
|
21
|
-
"1.2.3.5",
|
|
22
|
-
"1.2.3.6"
|
|
23
|
-
]
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
The IP list ``server_group_node_ips`` would be used like so:
|
|
29
|
-
|
|
30
|
-
.. code:: python
|
|
31
|
-
|
|
32
|
-
pyinfra @terraform/server_group.value.server_group_node_ips ...
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
1
|
import json
|
|
36
2
|
|
|
37
3
|
from pyinfra import local, logger
|
|
@@ -39,6 +5,8 @@ from pyinfra.api.exceptions import InventoryError
|
|
|
39
5
|
from pyinfra.api.util import memoize
|
|
40
6
|
from pyinfra.progress import progress_spinner
|
|
41
7
|
|
|
8
|
+
from .base import BaseConnector
|
|
9
|
+
|
|
42
10
|
|
|
43
11
|
@memoize
|
|
44
12
|
def show_warning():
|
|
@@ -58,44 +26,97 @@ def _flatten_dict(d: dict, parent_key: str = "", sep: str = "."):
|
|
|
58
26
|
return dict(_flatten_dict_gen(d, parent_key, sep))
|
|
59
27
|
|
|
60
28
|
|
|
61
|
-
|
|
62
|
-
|
|
29
|
+
class TerraformInventoryConnector(BaseConnector):
|
|
30
|
+
"""
|
|
31
|
+
Generate one or more SSH hosts from a Terraform output variable. The variable
|
|
32
|
+
must be a list of hostnames or dictionaries.
|
|
63
33
|
|
|
64
|
-
|
|
65
|
-
|
|
34
|
+
Output is fetched from a flattened JSON dictionary output from ``terraform output
|
|
35
|
+
-json``. For example the following object:
|
|
66
36
|
|
|
67
|
-
|
|
68
|
-
tf_output_raw = local.shell("terraform output -json")
|
|
37
|
+
.. code:: json
|
|
69
38
|
|
|
70
|
-
|
|
71
|
-
|
|
39
|
+
{
|
|
40
|
+
"server_group": {
|
|
41
|
+
"value": {
|
|
42
|
+
"server_group_node_ips": [
|
|
43
|
+
"1.2.3.4",
|
|
44
|
+
"1.2.3.5",
|
|
45
|
+
"1.2.3.6"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
if tf_output_value is None:
|
|
75
|
-
keys = "\n".join(f" - {k}" for k in tf_output.keys())
|
|
76
|
-
raise InventoryError(f"No Terraform output with key: `{output_key}`, valid keys:\n{keys}")
|
|
51
|
+
The IP list ``server_group_node_ips`` would be used like so:
|
|
77
52
|
|
|
78
|
-
|
|
79
|
-
raise InventoryError(
|
|
80
|
-
"Invalid Terraform output type, should be `list`, got "
|
|
81
|
-
f"`{type(tf_output_value).__name__}`",
|
|
82
|
-
)
|
|
53
|
+
.. code:: sh
|
|
83
54
|
|
|
84
|
-
|
|
85
|
-
if isinstance(ssh_target, dict):
|
|
86
|
-
name = ssh_target.pop("name", ssh_target.get("ssh_hostname"))
|
|
87
|
-
if name is None:
|
|
88
|
-
raise InventoryError(
|
|
89
|
-
"Invalid Terraform list item, missing `name` or `ssh_hostname` keys",
|
|
90
|
-
)
|
|
91
|
-
yield f"@terraform/{name}", ssh_target, ["@terraform"]
|
|
55
|
+
pyinfra @terraform/server_group.value.server_group_node_ips ...
|
|
92
56
|
|
|
93
|
-
|
|
94
|
-
data = {"ssh_hostname": ssh_target}
|
|
95
|
-
yield f"@terraform/{ssh_target}", data, ["@terraform"]
|
|
57
|
+
You can also specify dictionaries to include extra data with hosts:
|
|
96
58
|
|
|
97
|
-
|
|
59
|
+
.. code:: json
|
|
60
|
+
|
|
61
|
+
{
|
|
62
|
+
"server_group": {
|
|
63
|
+
"value": {
|
|
64
|
+
"server_group_node_ips": [
|
|
65
|
+
{
|
|
66
|
+
"ssh_hostname": "1.2.3.4",
|
|
67
|
+
"ssh_user": "ssh-user"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"ssh_hostname": "1.2.3.5",
|
|
71
|
+
"ssh_user": "ssh-user"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def make_names_data(name=None):
|
|
82
|
+
show_warning()
|
|
83
|
+
|
|
84
|
+
if not name:
|
|
85
|
+
name = ""
|
|
86
|
+
|
|
87
|
+
with progress_spinner({"fetch terraform output"}):
|
|
88
|
+
tf_output_raw = local.shell("terraform output -json")
|
|
89
|
+
|
|
90
|
+
assert isinstance(tf_output_raw, str)
|
|
91
|
+
tf_output = json.loads(tf_output_raw)
|
|
92
|
+
tf_output = _flatten_dict(tf_output)
|
|
93
|
+
|
|
94
|
+
tf_output_value = tf_output.get(name)
|
|
95
|
+
if tf_output_value is None:
|
|
96
|
+
keys = "\n".join(f" - {k}" for k in tf_output.keys())
|
|
97
|
+
raise InventoryError(f"No Terraform output with key: `{name}`, valid keys:\n{keys}")
|
|
98
|
+
|
|
99
|
+
if not isinstance(tf_output_value, list):
|
|
98
100
|
raise InventoryError(
|
|
99
|
-
"Invalid Terraform
|
|
100
|
-
f"`{type(
|
|
101
|
+
"Invalid Terraform output type, should be `list`, got "
|
|
102
|
+
f"`{type(tf_output_value).__name__}`",
|
|
101
103
|
)
|
|
104
|
+
|
|
105
|
+
for ssh_target in tf_output_value:
|
|
106
|
+
if isinstance(ssh_target, dict):
|
|
107
|
+
name = ssh_target.pop("name", ssh_target.get("ssh_hostname"))
|
|
108
|
+
if name is None:
|
|
109
|
+
raise InventoryError(
|
|
110
|
+
"Invalid Terraform list item, missing `name` or `ssh_hostname` keys",
|
|
111
|
+
)
|
|
112
|
+
yield f"@terraform/{name}", ssh_target, ["@terraform"]
|
|
113
|
+
|
|
114
|
+
elif isinstance(ssh_target, str):
|
|
115
|
+
data = {"ssh_hostname": ssh_target}
|
|
116
|
+
yield f"@terraform/{ssh_target}", data, ["@terraform"]
|
|
117
|
+
|
|
118
|
+
else:
|
|
119
|
+
raise InventoryError(
|
|
120
|
+
"Invalid Terraform list item, should be `dict` or `str` got "
|
|
121
|
+
f"`{type(ssh_target).__name__}`",
|
|
122
|
+
)
|