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/connectors/base.py
CHANGED
|
@@ -1,42 +1,90 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import abc
|
|
4
|
-
from dataclasses import dataclass
|
|
5
4
|
from io import IOBase
|
|
6
|
-
from typing import
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
from typing import (
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Any,
|
|
8
|
+
Iterable,
|
|
9
|
+
Iterator,
|
|
10
|
+
Optional,
|
|
11
|
+
Type,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
cast,
|
|
15
|
+
get_type_hints,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
from typing_extensions import TypedDict, Unpack
|
|
19
|
+
|
|
20
|
+
from pyinfra.api.exceptions import ConnectorDataTypeError
|
|
21
|
+
from pyinfra.api.util import raise_if_bad_type
|
|
9
22
|
|
|
10
23
|
if TYPE_CHECKING:
|
|
11
24
|
from pyinfra.api.arguments import ConnectorArguments
|
|
12
25
|
from pyinfra.api.command import StringCommand
|
|
13
|
-
from pyinfra.api.host import Host
|
|
26
|
+
from pyinfra.api.host import Host, HostData
|
|
14
27
|
from pyinfra.api.state import State
|
|
15
28
|
|
|
16
29
|
from .util import CommandOutput
|
|
17
30
|
|
|
18
31
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
T = TypeVar("T")
|
|
33
|
+
default_sentinel = object()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def host_to_connector_data(
|
|
37
|
+
connector_data: Type[T],
|
|
38
|
+
connector_data_meta: dict[str, DataMeta],
|
|
39
|
+
host_data: "HostData",
|
|
40
|
+
) -> T:
|
|
41
|
+
data: T = cast(T, {})
|
|
42
|
+
for key, type_ in get_type_hints(connector_data).items():
|
|
43
|
+
value = host_data.get(key, default_sentinel)
|
|
44
|
+
if value is default_sentinel:
|
|
45
|
+
value = connector_data_meta[key].default
|
|
46
|
+
else:
|
|
47
|
+
raise_if_bad_type(
|
|
48
|
+
value,
|
|
49
|
+
type_,
|
|
50
|
+
ConnectorDataTypeError,
|
|
51
|
+
f"Invalid connector data `{key}`:",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
data[key] = value # type: ignore
|
|
55
|
+
return data
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DataMeta:
|
|
59
|
+
description: str
|
|
60
|
+
default: Any
|
|
61
|
+
|
|
62
|
+
def __init__(self, description, default=None) -> None:
|
|
63
|
+
self.description = description
|
|
64
|
+
self.default = default
|
|
22
65
|
|
|
23
|
-
for key in cls.__dict__:
|
|
24
|
-
if not key.startswith("_"):
|
|
25
|
-
setattr(Keys, key, f"{prefix}_{key}")
|
|
26
66
|
|
|
27
|
-
|
|
67
|
+
class ConnectorData(TypedDict, total=False):
|
|
68
|
+
pass
|
|
28
69
|
|
|
29
70
|
|
|
30
|
-
@dataclass
|
|
31
71
|
class BaseConnector(abc.ABC):
|
|
32
72
|
state: "State"
|
|
33
73
|
host: "Host"
|
|
34
74
|
|
|
35
75
|
handles_execution = False
|
|
36
76
|
|
|
77
|
+
data_cls: Type = ConnectorData
|
|
78
|
+
data_meta: dict[str, DataMeta] = {}
|
|
79
|
+
|
|
80
|
+
def __init__(self, state: "State", host: "Host"):
|
|
81
|
+
self.state = state
|
|
82
|
+
self.host = host
|
|
83
|
+
self.data = host_to_connector_data(self.data_cls, self.data_meta, host.data)
|
|
84
|
+
|
|
37
85
|
@staticmethod
|
|
38
86
|
@abc.abstractmethod
|
|
39
|
-
def make_names_data(
|
|
87
|
+
def make_names_data(name: str) -> Iterator[tuple[str, dict, list[str]]]:
|
|
40
88
|
"""
|
|
41
89
|
Generates hosts/data/groups information for inventory. This allows a
|
|
42
90
|
single connector reference to generate multiple target hosts.
|
|
@@ -60,8 +108,7 @@ class BaseConnector(abc.ABC):
|
|
|
60
108
|
print_output: bool,
|
|
61
109
|
print_input: bool,
|
|
62
110
|
**arguments: Unpack["ConnectorArguments"],
|
|
63
|
-
) -> tuple[bool, "CommandOutput"]:
|
|
64
|
-
...
|
|
111
|
+
) -> tuple[bool, "CommandOutput"]: ...
|
|
65
112
|
|
|
66
113
|
@abc.abstractmethod
|
|
67
114
|
def put_file(
|
|
@@ -72,8 +119,7 @@ class BaseConnector(abc.ABC):
|
|
|
72
119
|
print_output: bool = False,
|
|
73
120
|
print_input: bool = False,
|
|
74
121
|
**arguments: Unpack["ConnectorArguments"],
|
|
75
|
-
) -> bool:
|
|
76
|
-
...
|
|
122
|
+
) -> bool: ...
|
|
77
123
|
|
|
78
124
|
@abc.abstractmethod
|
|
79
125
|
def get_file(
|
|
@@ -84,8 +130,7 @@ class BaseConnector(abc.ABC):
|
|
|
84
130
|
print_output: bool = False,
|
|
85
131
|
print_input: bool = False,
|
|
86
132
|
**arguments: Unpack["ConnectorArguments"],
|
|
87
|
-
) -> bool:
|
|
88
|
-
...
|
|
133
|
+
) -> bool: ...
|
|
89
134
|
|
|
90
135
|
def check_can_rsync(self):
|
|
91
136
|
raise NotImplementedError("This connector does not support rsync")
|
pyinfra/connectors/chroot.py
CHANGED
|
@@ -27,6 +27,10 @@ def show_warning():
|
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
class ChrootConnector(BaseConnector):
|
|
30
|
+
"""
|
|
31
|
+
The chroot connector allows you to execute operations within another root.
|
|
32
|
+
"""
|
|
33
|
+
|
|
30
34
|
handles_execution = True
|
|
31
35
|
|
|
32
36
|
local: LocalConnector
|
|
@@ -36,17 +40,17 @@ class ChrootConnector(BaseConnector):
|
|
|
36
40
|
self.local = LocalConnector(state, host)
|
|
37
41
|
|
|
38
42
|
@staticmethod
|
|
39
|
-
def make_names_data(
|
|
40
|
-
if not
|
|
43
|
+
def make_names_data(name: Optional[str] = None):
|
|
44
|
+
if not name:
|
|
41
45
|
raise InventoryError("No directory provided!")
|
|
42
46
|
|
|
43
47
|
show_warning()
|
|
44
48
|
|
|
45
|
-
yield "@chroot/{0}".format(
|
|
46
|
-
"chroot_directory": "/{0}".format(
|
|
49
|
+
yield "@chroot/{0}".format(name), {
|
|
50
|
+
"chroot_directory": "/{0}".format(name.lstrip("/")),
|
|
47
51
|
}, ["@chroot"]
|
|
48
52
|
|
|
49
|
-
def connect(self):
|
|
53
|
+
def connect(self) -> None:
|
|
50
54
|
self.local.connect()
|
|
51
55
|
|
|
52
56
|
chroot_directory = self.host.data.chroot_directory
|
|
@@ -61,7 +65,6 @@ class ChrootConnector(BaseConnector):
|
|
|
61
65
|
raise ConnectError(e.args[0])
|
|
62
66
|
|
|
63
67
|
self.host.connector_data["chroot_directory"] = chroot_directory
|
|
64
|
-
return True
|
|
65
68
|
|
|
66
69
|
def run_shell_command(
|
|
67
70
|
self,
|
|
@@ -117,11 +120,10 @@ class ChrootConnector(BaseConnector):
|
|
|
117
120
|
temp_f.write(data)
|
|
118
121
|
|
|
119
122
|
chroot_directory = self.host.connector_data["chroot_directory"]
|
|
120
|
-
|
|
121
|
-
|
|
123
|
+
chroot_command = StringCommand(
|
|
124
|
+
"cp",
|
|
122
125
|
temp_filename,
|
|
123
|
-
chroot_directory,
|
|
124
|
-
remote_filename,
|
|
126
|
+
f"{chroot_directory}/{remote_filename}",
|
|
125
127
|
)
|
|
126
128
|
|
|
127
129
|
status, output = self.local.run_shell_command(
|
|
@@ -159,9 +161,9 @@ class ChrootConnector(BaseConnector):
|
|
|
159
161
|
|
|
160
162
|
try:
|
|
161
163
|
chroot_directory = self.host.connector_data["chroot_directory"]
|
|
162
|
-
chroot_command =
|
|
163
|
-
|
|
164
|
-
remote_filename,
|
|
164
|
+
chroot_command = StringCommand(
|
|
165
|
+
"cp",
|
|
166
|
+
f"{chroot_directory}/{remote_filename}",
|
|
165
167
|
temp_filename,
|
|
166
168
|
)
|
|
167
169
|
|
pyinfra/connectors/docker.py
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The ``@docker`` connector allows you to build Docker images, or modify running
|
|
3
|
-
Docker containers, using ``pyinfra``. You can pass either an image name or
|
|
4
|
-
existing container ID:
|
|
5
|
-
|
|
6
|
-
+ Image - will create a container from the image, execute operations and save
|
|
7
|
-
into a new image
|
|
8
|
-
+ Existing container ID - will simply execute operations against the container,
|
|
9
|
-
leaving it up afterwards
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
.. code:: shell
|
|
13
|
-
|
|
14
|
-
# A Docker base image must be provided
|
|
15
|
-
pyinfra @docker/alpine:3.8 ...
|
|
16
|
-
|
|
17
|
-
# pyinfra can run on multiple Docker images in parallel
|
|
18
|
-
pyinfra @docker/alpine:3.8,@docker/ubuntu:bionic ...
|
|
19
|
-
|
|
20
|
-
# Execute against a running container
|
|
21
|
-
pyinfra @docker/2beb8c15a1b1 ...
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
1
|
from __future__ import annotations
|
|
25
2
|
|
|
26
3
|
import json
|
|
@@ -29,7 +6,7 @@ from tempfile import mkstemp
|
|
|
29
6
|
from typing import TYPE_CHECKING
|
|
30
7
|
|
|
31
8
|
import click
|
|
32
|
-
from typing_extensions import Unpack
|
|
9
|
+
from typing_extensions import TypedDict, Unpack
|
|
33
10
|
|
|
34
11
|
from pyinfra import local, logger
|
|
35
12
|
from pyinfra.api import QuoteString, StringCommand
|
|
@@ -37,7 +14,7 @@ from pyinfra.api.exceptions import ConnectError, InventoryError, PyinfraError
|
|
|
37
14
|
from pyinfra.api.util import get_file_io
|
|
38
15
|
from pyinfra.progress import progress_spinner
|
|
39
16
|
|
|
40
|
-
from .base import BaseConnector,
|
|
17
|
+
from .base import BaseConnector, DataMeta
|
|
41
18
|
from .local import LocalConnector
|
|
42
19
|
from .util import CommandOutput, extract_control_arguments, make_unix_command_for_host
|
|
43
20
|
|
|
@@ -47,20 +24,24 @@ if TYPE_CHECKING:
|
|
|
47
24
|
from pyinfra.api.state import State
|
|
48
25
|
|
|
49
26
|
|
|
50
|
-
class
|
|
51
|
-
|
|
52
|
-
container_id = "ID of container to target, overrides ``docker_identifier``"
|
|
27
|
+
class ConnectorData(TypedDict):
|
|
28
|
+
docker_identifier: str
|
|
53
29
|
|
|
54
30
|
|
|
55
|
-
|
|
31
|
+
connector_data_meta: dict[str, DataMeta] = {
|
|
32
|
+
"docker_identifier": DataMeta("ID of container or image to start from"),
|
|
33
|
+
}
|
|
56
34
|
|
|
57
35
|
|
|
58
|
-
def _find_start_docker_container(container_id):
|
|
36
|
+
def _find_start_docker_container(container_id) -> tuple[str, bool]:
|
|
59
37
|
docker_info = local.shell("docker container inspect {0}".format(container_id))
|
|
38
|
+
assert isinstance(docker_info, str)
|
|
60
39
|
docker_info = json.loads(docker_info)[0]
|
|
61
40
|
if docker_info["State"]["Running"] is False:
|
|
62
41
|
logger.info("Starting stopped container: {0}".format(container_id))
|
|
63
42
|
local.shell("docker container start {0}".format(container_id))
|
|
43
|
+
return container_id, False
|
|
44
|
+
return container_id, True
|
|
64
45
|
|
|
65
46
|
|
|
66
47
|
def _start_docker_image(image_name):
|
|
@@ -76,53 +57,69 @@ def _start_docker_image(image_name):
|
|
|
76
57
|
|
|
77
58
|
|
|
78
59
|
class DockerConnector(BaseConnector):
|
|
60
|
+
"""
|
|
61
|
+
The docker connector allows you to build Docker images or modify running
|
|
62
|
+
Docker containers. You can pass either an image name or existing container ID:
|
|
63
|
+
|
|
64
|
+
+ Image - will create a new container from the image, execute operations \
|
|
65
|
+
against it, save into a new Docker image and remove the container
|
|
66
|
+
+ Existing container ID - will execute operations against the running \
|
|
67
|
+
container, leaving it running
|
|
68
|
+
|
|
69
|
+
.. code:: shell
|
|
70
|
+
|
|
71
|
+
# A Docker base image must be provided
|
|
72
|
+
pyinfra @docker/alpine:3.8 ...
|
|
73
|
+
|
|
74
|
+
# pyinfra can run on multiple Docker images in parallel
|
|
75
|
+
pyinfra @docker/alpine:3.8,@docker/ubuntu:bionic ...
|
|
76
|
+
|
|
77
|
+
# Execute against a running container
|
|
78
|
+
pyinfra @docker/2beb8c15a1b1 ...
|
|
79
|
+
"""
|
|
80
|
+
|
|
79
81
|
handles_execution = True
|
|
80
|
-
|
|
82
|
+
|
|
83
|
+
data_cls = ConnectorData
|
|
84
|
+
data_meta = connector_data_meta
|
|
85
|
+
data: ConnectorData
|
|
81
86
|
|
|
82
87
|
local: LocalConnector
|
|
83
88
|
|
|
89
|
+
container_id: str
|
|
90
|
+
no_stop: bool = False
|
|
91
|
+
|
|
84
92
|
def __init__(self, state: "State", host: "Host"):
|
|
85
93
|
super().__init__(state, host)
|
|
86
94
|
self.local = LocalConnector(state, host)
|
|
87
95
|
|
|
88
96
|
@staticmethod
|
|
89
|
-
def make_names_data(
|
|
90
|
-
if not
|
|
97
|
+
def make_names_data(name=None):
|
|
98
|
+
if not name:
|
|
91
99
|
raise InventoryError("No docker base ID provided!")
|
|
92
100
|
|
|
93
101
|
yield (
|
|
94
|
-
"@docker/{0}".format(
|
|
95
|
-
{
|
|
102
|
+
"@docker/{0}".format(name),
|
|
103
|
+
{"docker_identifier": name},
|
|
96
104
|
["@docker"],
|
|
97
105
|
)
|
|
98
106
|
|
|
99
|
-
def connect(self):
|
|
107
|
+
def connect(self) -> None:
|
|
100
108
|
self.local.connect()
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
if docker_container_id: # user can provide a docker_container_id
|
|
104
|
-
self.host.connector_data["docker_container_no_disconnect"] = True
|
|
105
|
-
self.host.connector_data["docker_container_id"] = docker_container_id
|
|
106
|
-
return True
|
|
107
|
-
|
|
108
|
-
docker_identifier = getattr(self.host.data, DATA_KEYS.identifier)
|
|
110
|
+
docker_identifier = self.data["docker_identifier"]
|
|
109
111
|
with progress_spinner({"prepare docker container"}):
|
|
110
112
|
try:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
self.container_id, was_running = _find_start_docker_container(docker_identifier)
|
|
114
|
+
if was_running:
|
|
115
|
+
self.no_stop = True
|
|
113
116
|
except PyinfraError:
|
|
114
|
-
container_id = _start_docker_image(docker_identifier)
|
|
115
|
-
else:
|
|
116
|
-
container_id = docker_identifier
|
|
117
|
-
self.host.connector_data["docker_container_no_disconnect"] = True
|
|
118
|
-
|
|
119
|
-
self.host.connector_data["docker_container_id"] = container_id
|
|
120
|
-
return True
|
|
117
|
+
self.container_id = _start_docker_image(docker_identifier)
|
|
121
118
|
|
|
122
119
|
def disconnect(self):
|
|
123
|
-
container_id = self.
|
|
120
|
+
container_id = self.container_id
|
|
124
121
|
|
|
125
|
-
if self.
|
|
122
|
+
if self.no_stop:
|
|
126
123
|
logger.info(
|
|
127
124
|
"{0}docker build complete, container left running: {1}".format(
|
|
128
125
|
self.host.print_prefix,
|
|
@@ -157,7 +154,7 @@ class DockerConnector(BaseConnector):
|
|
|
157
154
|
) -> tuple[bool, CommandOutput]:
|
|
158
155
|
local_arguments = extract_control_arguments(arguments)
|
|
159
156
|
|
|
160
|
-
container_id = self.
|
|
157
|
+
container_id = self.container_id
|
|
161
158
|
|
|
162
159
|
command = make_unix_command_for_host(self.state, self.host, command, **arguments)
|
|
163
160
|
command = StringCommand(QuoteString(command))
|
|
@@ -207,11 +204,11 @@ class DockerConnector(BaseConnector):
|
|
|
207
204
|
|
|
208
205
|
temp_f.write(data)
|
|
209
206
|
|
|
210
|
-
|
|
211
|
-
|
|
207
|
+
docker_command = StringCommand(
|
|
208
|
+
"docker",
|
|
209
|
+
"cp",
|
|
212
210
|
temp_filename,
|
|
213
|
-
|
|
214
|
-
remote_filename,
|
|
211
|
+
f"{self.container_id}:{remote_filename}",
|
|
215
212
|
)
|
|
216
213
|
|
|
217
214
|
status, output = self.local.run_shell_command(
|
|
@@ -254,10 +251,10 @@ class DockerConnector(BaseConnector):
|
|
|
254
251
|
fd, temp_filename = mkstemp()
|
|
255
252
|
|
|
256
253
|
try:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
remote_filename,
|
|
254
|
+
docker_command = StringCommand(
|
|
255
|
+
"docker",
|
|
256
|
+
"cp",
|
|
257
|
+
f"{self.container_id}:{remote_filename}",
|
|
261
258
|
temp_filename,
|
|
262
259
|
)
|
|
263
260
|
|
|
@@ -268,17 +265,10 @@ class DockerConnector(BaseConnector):
|
|
|
268
265
|
)
|
|
269
266
|
|
|
270
267
|
# Load the temporary file and write it to our file or IO object
|
|
271
|
-
with open(temp_filename,
|
|
268
|
+
with open(temp_filename, "rb") as temp_f:
|
|
272
269
|
with get_file_io(filename_or_io, "wb") as file_io:
|
|
273
270
|
data = temp_f.read()
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
if isinstance(data, str):
|
|
277
|
-
data_bytes = data.encode()
|
|
278
|
-
else:
|
|
279
|
-
data_bytes = data
|
|
280
|
-
|
|
281
|
-
file_io.write(data_bytes)
|
|
271
|
+
file_io.write(data)
|
|
282
272
|
finally:
|
|
283
273
|
os.close(fd)
|
|
284
274
|
os.remove(temp_filename)
|
pyinfra/connectors/dockerssh.py
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
**Note**: this connector is in beta!
|
|
3
|
-
|
|
4
|
-
The ``@dockerssh`` connector allows you to run commands on Docker containers on a remote machine.
|
|
5
|
-
|
|
6
|
-
.. code:: shell
|
|
7
|
-
|
|
8
|
-
# A Docker base image must be provided
|
|
9
|
-
pyinfra @dockerssh/remotehost:alpine:3.8 ...
|
|
10
|
-
|
|
11
|
-
# pyinfra can run on multiple Docker images in parallel
|
|
12
|
-
pyinfra @dockerssh/remotehost:alpine:3.8,@dockerssh/remotehost:ubuntu:bionic ...
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
1
|
import os
|
|
16
2
|
from tempfile import mkstemp
|
|
17
3
|
from typing import TYPE_CHECKING
|
|
@@ -41,6 +27,21 @@ def show_warning():
|
|
|
41
27
|
|
|
42
28
|
|
|
43
29
|
class DockerSSHConnector(BaseConnector):
|
|
30
|
+
"""
|
|
31
|
+
**Note**: this connector is in beta!
|
|
32
|
+
|
|
33
|
+
The ``@dockerssh`` connector allows you to run commands on Docker containers \
|
|
34
|
+
on a remote machine.
|
|
35
|
+
|
|
36
|
+
.. code:: shell
|
|
37
|
+
|
|
38
|
+
# A Docker base image must be provided
|
|
39
|
+
pyinfra @dockerssh/remotehost:alpine:3.8 ...
|
|
40
|
+
|
|
41
|
+
# pyinfra can run on multiple Docker images in parallel
|
|
42
|
+
pyinfra @dockerssh/remotehost:alpine:3.8,@dockerssh/remotehost:ubuntu:bionic ...
|
|
43
|
+
"""
|
|
44
|
+
|
|
44
45
|
handles_execution = True
|
|
45
46
|
|
|
46
47
|
ssh: SSHConnector
|
|
@@ -50,10 +51,10 @@ class DockerSSHConnector(BaseConnector):
|
|
|
50
51
|
self.ssh = SSHConnector(state, host)
|
|
51
52
|
|
|
52
53
|
@staticmethod
|
|
53
|
-
def make_names_data(
|
|
54
|
+
def make_names_data(name):
|
|
54
55
|
try:
|
|
55
|
-
hostname, image =
|
|
56
|
-
except (AttributeError, ValueError): # failure to parse the
|
|
56
|
+
hostname, image = name.split(":", 1)
|
|
57
|
+
except (AttributeError, ValueError): # failure to parse the name
|
|
57
58
|
raise InventoryError("No ssh host or docker base image provided!")
|
|
58
59
|
|
|
59
60
|
if not image:
|
|
@@ -164,7 +165,7 @@ class DockerSSHConnector(BaseConnector):
|
|
|
164
165
|
"""
|
|
165
166
|
|
|
166
167
|
fd, local_temp_filename = mkstemp()
|
|
167
|
-
remote_temp_filename = remote_temp_filename or self.
|
|
168
|
+
remote_temp_filename = remote_temp_filename or self.host.get_temp_filename(
|
|
168
169
|
local_temp_filename
|
|
169
170
|
)
|
|
170
171
|
|
|
@@ -234,7 +235,7 @@ class DockerSSHConnector(BaseConnector):
|
|
|
234
235
|
location and then reading that into our final file/IO object.
|
|
235
236
|
"""
|
|
236
237
|
|
|
237
|
-
remote_temp_filename = remote_temp_filename or self.
|
|
238
|
+
remote_temp_filename = remote_temp_filename or self.host.get_temp_filename(remote_filename)
|
|
238
239
|
|
|
239
240
|
try:
|
|
240
241
|
docker_id = self.host.host_data["docker_container_id"]
|
pyinfra/connectors/local.py
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The ``@local`` connector executes changes on the local machine using subprocesses.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
1
|
import os
|
|
6
|
-
from
|
|
2
|
+
from shutil import which
|
|
7
3
|
from tempfile import mkstemp
|
|
8
4
|
from typing import TYPE_CHECKING, Tuple
|
|
9
5
|
|
|
@@ -28,18 +24,30 @@ if TYPE_CHECKING:
|
|
|
28
24
|
|
|
29
25
|
|
|
30
26
|
class LocalConnector(BaseConnector):
|
|
27
|
+
"""
|
|
28
|
+
The ``@local`` connector executes changes on the local machine using
|
|
29
|
+
subprocesses. **This connector is only compatible with MacOS & Linux hosts**.
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
|
|
33
|
+
.. code::
|
|
34
|
+
|
|
35
|
+
# Install nginx
|
|
36
|
+
pyinfra inventory.py apt.packages nginx update=true _sudo=true
|
|
37
|
+
"""
|
|
38
|
+
|
|
31
39
|
handles_execution = True
|
|
32
40
|
|
|
33
41
|
@staticmethod
|
|
34
|
-
def make_names_data(
|
|
35
|
-
if
|
|
42
|
+
def make_names_data(name=None):
|
|
43
|
+
if name is not None:
|
|
36
44
|
raise InventoryError("Cannot have more than one @local")
|
|
37
45
|
|
|
38
46
|
yield "@local", {}, ["@local"]
|
|
39
47
|
|
|
40
48
|
def run_shell_command(
|
|
41
49
|
self,
|
|
42
|
-
command,
|
|
50
|
+
command: StringCommand,
|
|
43
51
|
print_output: bool = False,
|
|
44
52
|
print_input: bool = False,
|
|
45
53
|
**arguments: Unpack["ConnectorArguments"],
|
|
@@ -48,17 +56,14 @@ class LocalConnector(BaseConnector):
|
|
|
48
56
|
Execute a command on the local machine.
|
|
49
57
|
|
|
50
58
|
Args:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
sudo_user (string): user to sudo to
|
|
56
|
-
env (dict): environment variables to set
|
|
57
|
-
timeout (int): timeout for this command to complete before erroring
|
|
59
|
+
command (StringCommand): actual command to execute
|
|
60
|
+
print_output (bool): whether to print command output
|
|
61
|
+
print_input (bool): whether to print command input
|
|
62
|
+
arguments: (ConnectorArguments): connector global arguments
|
|
58
63
|
|
|
59
64
|
Returns:
|
|
60
|
-
tuple: (
|
|
61
|
-
|
|
65
|
+
tuple: (bool, CommandOutput)
|
|
66
|
+
Bool indicating success and CommandOutput with stdout/stderr lines.
|
|
62
67
|
"""
|
|
63
68
|
|
|
64
69
|
arguments.pop("_get_pty", False)
|
|
@@ -108,6 +113,9 @@ class LocalConnector(BaseConnector):
|
|
|
108
113
|
"""
|
|
109
114
|
Upload a local file or IO object by copying it to a temporary directory
|
|
110
115
|
and then writing it to the upload location.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
bool: Indicating success or failure
|
|
111
119
|
"""
|
|
112
120
|
|
|
113
121
|
_, temp_filename = mkstemp()
|
|
@@ -156,6 +164,9 @@ class LocalConnector(BaseConnector):
|
|
|
156
164
|
"""
|
|
157
165
|
Download a local file by copying it to a temporary location and then writing
|
|
158
166
|
it to our filename or IO object.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
bool: Indicating success or failure
|
|
159
170
|
"""
|
|
160
171
|
|
|
161
172
|
_, temp_filename = mkstemp()
|
|
@@ -163,7 +174,7 @@ class LocalConnector(BaseConnector):
|
|
|
163
174
|
try:
|
|
164
175
|
# Copy the file using `cp` such that we support sudo/su
|
|
165
176
|
status, output = self.run_shell_command(
|
|
166
|
-
"cp
|
|
177
|
+
StringCommand("cp", remote_filename, temp_filename),
|
|
167
178
|
print_output=print_output,
|
|
168
179
|
print_input=print_input,
|
|
169
180
|
**arguments,
|
|
@@ -195,9 +206,8 @@ class LocalConnector(BaseConnector):
|
|
|
195
206
|
|
|
196
207
|
return True
|
|
197
208
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if not find_executable("rsync"):
|
|
209
|
+
def check_can_rsync(self):
|
|
210
|
+
if not which("rsync"):
|
|
201
211
|
raise NotImplementedError("The `rsync` binary is not available on this system.")
|
|
202
212
|
|
|
203
213
|
def rsync(
|
|
@@ -210,7 +220,7 @@ class LocalConnector(BaseConnector):
|
|
|
210
220
|
**arguments,
|
|
211
221
|
) -> bool:
|
|
212
222
|
status, output = self.run_shell_command(
|
|
213
|
-
"rsync
|
|
223
|
+
StringCommand("rsync", " ".join(flags), src, dest),
|
|
214
224
|
print_output=print_output,
|
|
215
225
|
print_input=print_input,
|
|
216
226
|
**arguments,
|