pyinfra 0.11.dev3__py3-none-any.whl → 3.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyinfra/__init__.py +9 -12
- pyinfra/__main__.py +4 -0
- pyinfra/api/__init__.py +18 -3
- pyinfra/api/arguments.py +406 -0
- pyinfra/api/arguments_typed.py +79 -0
- pyinfra/api/command.py +274 -0
- pyinfra/api/config.py +222 -28
- pyinfra/api/connect.py +33 -13
- pyinfra/api/connectors.py +27 -0
- pyinfra/api/deploy.py +65 -66
- pyinfra/api/exceptions.py +67 -18
- pyinfra/api/facts.py +253 -202
- pyinfra/api/host.py +413 -50
- pyinfra/api/inventory.py +121 -160
- pyinfra/api/operation.py +432 -262
- pyinfra/api/operations.py +273 -260
- pyinfra/api/state.py +302 -248
- pyinfra/api/util.py +291 -368
- pyinfra/connectors/base.py +173 -0
- pyinfra/connectors/chroot.py +212 -0
- pyinfra/connectors/docker.py +381 -0
- pyinfra/connectors/dockerssh.py +297 -0
- pyinfra/connectors/local.py +238 -0
- pyinfra/connectors/scp/__init__.py +1 -0
- pyinfra/connectors/scp/client.py +204 -0
- pyinfra/connectors/ssh.py +670 -0
- pyinfra/connectors/ssh_util.py +114 -0
- pyinfra/connectors/sshuserclient/client.py +309 -0
- pyinfra/connectors/sshuserclient/config.py +102 -0
- pyinfra/connectors/terraform.py +135 -0
- pyinfra/connectors/util.py +410 -0
- pyinfra/connectors/vagrant.py +183 -0
- pyinfra/context.py +145 -0
- pyinfra/facts/__init__.py +7 -6
- pyinfra/facts/apk.py +22 -7
- pyinfra/facts/apt.py +117 -60
- pyinfra/facts/brew.py +100 -15
- pyinfra/facts/bsdinit.py +23 -0
- pyinfra/facts/cargo.py +37 -0
- pyinfra/facts/choco.py +47 -0
- pyinfra/facts/crontab.py +195 -0
- pyinfra/facts/deb.py +94 -0
- pyinfra/facts/dnf.py +48 -0
- pyinfra/facts/docker.py +96 -23
- pyinfra/facts/efibootmgr.py +113 -0
- pyinfra/facts/files.py +630 -58
- pyinfra/facts/flatpak.py +77 -0
- pyinfra/facts/freebsd.py +70 -0
- pyinfra/facts/gem.py +19 -6
- pyinfra/facts/git.py +59 -14
- pyinfra/facts/gpg.py +150 -0
- pyinfra/facts/hardware.py +313 -167
- pyinfra/facts/iptables.py +72 -62
- pyinfra/facts/launchd.py +44 -0
- pyinfra/facts/lxd.py +17 -4
- pyinfra/facts/mysql.py +122 -86
- pyinfra/facts/npm.py +17 -9
- pyinfra/facts/openrc.py +71 -0
- pyinfra/facts/opkg.py +246 -0
- pyinfra/facts/pacman.py +50 -7
- pyinfra/facts/pip.py +24 -7
- pyinfra/facts/pipx.py +82 -0
- pyinfra/facts/pkg.py +15 -6
- pyinfra/facts/pkgin.py +35 -0
- pyinfra/facts/podman.py +54 -0
- pyinfra/facts/postgres.py +178 -0
- pyinfra/facts/postgresql.py +6 -147
- pyinfra/facts/rpm.py +105 -0
- pyinfra/facts/runit.py +77 -0
- pyinfra/facts/selinux.py +161 -0
- pyinfra/facts/server.py +746 -285
- pyinfra/facts/snap.py +88 -0
- pyinfra/facts/systemd.py +139 -0
- pyinfra/facts/sysvinit.py +59 -0
- pyinfra/facts/upstart.py +35 -0
- pyinfra/facts/util/__init__.py +17 -0
- pyinfra/facts/util/databases.py +4 -6
- pyinfra/facts/util/packaging.py +37 -6
- pyinfra/facts/util/units.py +30 -0
- pyinfra/facts/util/win_files.py +99 -0
- pyinfra/facts/vzctl.py +20 -13
- pyinfra/facts/xbps.py +35 -0
- pyinfra/facts/yum.py +34 -40
- pyinfra/facts/zfs.py +77 -0
- pyinfra/facts/zypper.py +42 -0
- pyinfra/local.py +45 -83
- pyinfra/operations/__init__.py +12 -0
- pyinfra/operations/apk.py +98 -0
- pyinfra/operations/apt.py +488 -0
- pyinfra/operations/brew.py +231 -0
- pyinfra/operations/bsdinit.py +59 -0
- pyinfra/operations/cargo.py +45 -0
- pyinfra/operations/choco.py +61 -0
- pyinfra/operations/crontab.py +191 -0
- pyinfra/operations/dnf.py +210 -0
- pyinfra/operations/docker.py +446 -0
- pyinfra/operations/files.py +1939 -0
- pyinfra/operations/flatpak.py +94 -0
- pyinfra/operations/freebsd/__init__.py +12 -0
- pyinfra/operations/freebsd/freebsd_update.py +70 -0
- pyinfra/operations/freebsd/pkg.py +219 -0
- pyinfra/operations/freebsd/service.py +116 -0
- pyinfra/operations/freebsd/sysrc.py +92 -0
- pyinfra/operations/gem.py +47 -0
- pyinfra/operations/git.py +419 -0
- pyinfra/operations/iptables.py +311 -0
- pyinfra/operations/launchd.py +45 -0
- pyinfra/operations/lxd.py +68 -0
- pyinfra/operations/mysql.py +609 -0
- pyinfra/operations/npm.py +57 -0
- pyinfra/operations/openrc.py +63 -0
- pyinfra/operations/opkg.py +88 -0
- pyinfra/operations/pacman.py +81 -0
- pyinfra/operations/pip.py +205 -0
- pyinfra/operations/pipx.py +102 -0
- pyinfra/operations/pkg.py +70 -0
- pyinfra/operations/pkgin.py +91 -0
- pyinfra/operations/postgres.py +436 -0
- pyinfra/operations/postgresql.py +30 -0
- pyinfra/operations/puppet.py +40 -0
- pyinfra/operations/python.py +72 -0
- pyinfra/operations/runit.py +184 -0
- pyinfra/operations/selinux.py +189 -0
- pyinfra/operations/server.py +1099 -0
- pyinfra/operations/snap.py +117 -0
- pyinfra/operations/ssh.py +216 -0
- pyinfra/operations/systemd.py +149 -0
- pyinfra/operations/sysvinit.py +141 -0
- pyinfra/operations/upstart.py +68 -0
- pyinfra/operations/util/__init__.py +12 -0
- pyinfra/operations/util/docker.py +251 -0
- pyinfra/operations/util/files.py +247 -0
- pyinfra/operations/util/packaging.py +336 -0
- pyinfra/operations/util/service.py +46 -0
- pyinfra/operations/vzctl.py +137 -0
- pyinfra/operations/xbps.py +77 -0
- pyinfra/operations/yum.py +210 -0
- pyinfra/operations/zfs.py +175 -0
- pyinfra/operations/zypper.py +192 -0
- pyinfra/progress.py +44 -32
- pyinfra/py.typed +0 -0
- pyinfra/version.py +9 -1
- pyinfra-3.5.1.dist-info/METADATA +141 -0
- pyinfra-3.5.1.dist-info/RECORD +159 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
- pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
- {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
- pyinfra_cli/__init__.py +1 -0
- pyinfra_cli/cli.py +780 -0
- pyinfra_cli/commands.py +66 -0
- pyinfra_cli/exceptions.py +155 -65
- pyinfra_cli/inventory.py +233 -89
- pyinfra_cli/log.py +39 -43
- pyinfra_cli/main.py +26 -495
- pyinfra_cli/prints.py +215 -156
- pyinfra_cli/util.py +172 -105
- pyinfra_cli/virtualenv.py +25 -20
- pyinfra/api/connectors/__init__.py +0 -21
- pyinfra/api/connectors/ansible.py +0 -99
- pyinfra/api/connectors/docker.py +0 -178
- pyinfra/api/connectors/local.py +0 -169
- pyinfra/api/connectors/ssh.py +0 -402
- pyinfra/api/connectors/sshuserclient/client.py +0 -105
- pyinfra/api/connectors/sshuserclient/config.py +0 -90
- pyinfra/api/connectors/util.py +0 -63
- pyinfra/api/connectors/vagrant.py +0 -155
- pyinfra/facts/init.py +0 -176
- pyinfra/facts/util/files.py +0 -102
- pyinfra/hook.py +0 -41
- pyinfra/modules/__init__.py +0 -11
- pyinfra/modules/apk.py +0 -64
- pyinfra/modules/apt.py +0 -272
- pyinfra/modules/brew.py +0 -122
- pyinfra/modules/files.py +0 -711
- pyinfra/modules/gem.py +0 -30
- pyinfra/modules/git.py +0 -115
- pyinfra/modules/init.py +0 -344
- pyinfra/modules/iptables.py +0 -271
- pyinfra/modules/lxd.py +0 -45
- pyinfra/modules/mysql.py +0 -347
- pyinfra/modules/npm.py +0 -47
- pyinfra/modules/pacman.py +0 -60
- pyinfra/modules/pip.py +0 -99
- pyinfra/modules/pkg.py +0 -43
- pyinfra/modules/postgresql.py +0 -245
- pyinfra/modules/puppet.py +0 -20
- pyinfra/modules/python.py +0 -37
- pyinfra/modules/server.py +0 -524
- pyinfra/modules/ssh.py +0 -150
- pyinfra/modules/util/files.py +0 -52
- pyinfra/modules/util/packaging.py +0 -118
- pyinfra/modules/vzctl.py +0 -133
- pyinfra/modules/yum.py +0 -171
- pyinfra/pseudo_modules.py +0 -64
- pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
- pyinfra-0.11.dev3.dist-info/METADATA +0 -135
- pyinfra-0.11.dev3.dist-info/RECORD +0 -95
- pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
- pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
- pyinfra_cli/__main__.py +0 -40
- pyinfra_cli/config.py +0 -92
- /pyinfra/{modules/util → connectors}/__init__.py +0 -0
- /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
pyinfra/api/host.py
CHANGED
|
@@ -1,55 +1,197 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from copy import copy
|
|
5
|
+
from typing import (
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Any,
|
|
8
|
+
Callable,
|
|
9
|
+
Generator,
|
|
10
|
+
Optional,
|
|
11
|
+
Type,
|
|
12
|
+
TypeVar,
|
|
13
|
+
Union,
|
|
14
|
+
cast,
|
|
15
|
+
overload,
|
|
16
|
+
)
|
|
17
|
+
from uuid import uuid4
|
|
18
|
+
|
|
1
19
|
import click
|
|
20
|
+
from typing_extensions import Unpack, override
|
|
2
21
|
|
|
3
|
-
from
|
|
4
|
-
from .
|
|
5
|
-
from .
|
|
22
|
+
from pyinfra import logger
|
|
23
|
+
from pyinfra.connectors.base import BaseConnector
|
|
24
|
+
from pyinfra.connectors.util import CommandOutput, remove_any_sudo_askpass_file
|
|
6
25
|
|
|
26
|
+
from .connectors import get_execution_connector
|
|
27
|
+
from .exceptions import ConnectError
|
|
28
|
+
from .facts import FactBase, ShortFactBase, get_fact
|
|
29
|
+
from .util import memoize, sha1_hash
|
|
7
30
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from pyinfra.api.arguments import AllArguments
|
|
33
|
+
from pyinfra.api.inventory import Inventory
|
|
34
|
+
from pyinfra.api.state import State
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def extract_callable_datas(
|
|
38
|
+
datas: list[Union[Callable[..., Any], Any]],
|
|
39
|
+
) -> Generator[Any, Any, Any]:
|
|
40
|
+
for data in datas:
|
|
41
|
+
# Support for dynamic data, ie @deploy wrapped data defaults where
|
|
42
|
+
# the data is stored on the state temporarily.
|
|
43
|
+
if callable(data):
|
|
44
|
+
data = data()
|
|
45
|
+
yield data
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class HostData:
|
|
49
|
+
"""
|
|
50
|
+
Combines multiple AttrData's to search for attributes.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
override_datas: dict[str, Any]
|
|
54
|
+
|
|
55
|
+
def __init__(self, host: "Host", *datas):
|
|
56
|
+
self.__dict__["host"] = host
|
|
57
|
+
|
|
58
|
+
parsed_datas = list(datas)
|
|
12
59
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
60
|
+
# Inject an empty override data so we can assign during deploy
|
|
61
|
+
self.__dict__["override_datas"] = {}
|
|
62
|
+
parsed_datas.insert(0, self.override_datas)
|
|
16
63
|
|
|
17
|
-
|
|
18
|
-
connection = self.host.connect(self.inventory.state, for_fact=key)
|
|
64
|
+
self.__dict__["datas"] = tuple(parsed_datas)
|
|
19
65
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
66
|
+
def __getattr__(self, key: str):
|
|
67
|
+
for data in extract_callable_datas(self.datas):
|
|
68
|
+
try:
|
|
69
|
+
# Take a shallow copy of the object here, we don't want modifications
|
|
70
|
+
# to host.data.<X> to stick, instead setting host.data.<Y> = is the
|
|
71
|
+
# correct way to achieve this (see __setattr__).
|
|
72
|
+
return copy(data[key])
|
|
73
|
+
except KeyError:
|
|
74
|
+
pass
|
|
26
75
|
|
|
27
|
-
|
|
76
|
+
raise AttributeError(f"Host `{self.host}` has no data `{key}`")
|
|
28
77
|
|
|
78
|
+
@override
|
|
79
|
+
def __setattr__(self, key: str, value: Any):
|
|
80
|
+
self.override_datas[key] = value
|
|
29
81
|
|
|
30
|
-
|
|
31
|
-
|
|
82
|
+
@override
|
|
83
|
+
def __str__(self):
|
|
84
|
+
return str(self.datas)
|
|
85
|
+
|
|
86
|
+
def get(self, key: str, default=None):
|
|
87
|
+
return getattr(self, key, default)
|
|
88
|
+
|
|
89
|
+
def dict(self):
|
|
90
|
+
out = {}
|
|
91
|
+
|
|
92
|
+
# Copy and reverse data objects (such that the first items override
|
|
93
|
+
# the last, matching __getattr__ output).
|
|
94
|
+
datas = list(self.datas)
|
|
95
|
+
datas.reverse()
|
|
96
|
+
|
|
97
|
+
for data in extract_callable_datas(datas):
|
|
98
|
+
out.update(data)
|
|
99
|
+
|
|
100
|
+
return out
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Host:
|
|
104
|
+
"""
|
|
32
105
|
Represents a target host. Thin class that links up to facts and host/group
|
|
33
106
|
data.
|
|
34
|
-
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
state: "State"
|
|
110
|
+
connector_cls: type[BaseConnector]
|
|
111
|
+
connector: BaseConnector
|
|
112
|
+
connected: bool = False
|
|
113
|
+
|
|
114
|
+
# Current context inside an @operation function (op gen stage)
|
|
115
|
+
in_op: bool = False
|
|
116
|
+
in_callback_op: bool = False
|
|
117
|
+
current_op_hash: Optional[str] = None
|
|
118
|
+
current_op_global_arguments: Optional["AllArguments"] = None
|
|
119
|
+
|
|
120
|
+
# Current context inside a @deploy function which become part of the op data
|
|
121
|
+
in_deploy: bool = False
|
|
122
|
+
current_deploy_name: Optional[str] = None
|
|
123
|
+
current_deploy_kwargs = None
|
|
35
124
|
|
|
36
|
-
|
|
125
|
+
# @deploy decorator data is a bit different - we need to handle the case
|
|
126
|
+
# where we're evaluating an operation at runtime (current_op_) but also
|
|
127
|
+
# when ordering operations (current_) outside of an operation context.
|
|
128
|
+
current_op_deploy_data: Optional[dict[str, Any]] = None
|
|
129
|
+
current_deploy_data: Optional[dict[str, Any]] = None
|
|
130
|
+
|
|
131
|
+
# Current context during operation execution
|
|
132
|
+
executing_op_hash: Optional[str] = None
|
|
133
|
+
nested_executing_op_hash: Optional[str] = None
|
|
134
|
+
|
|
135
|
+
loop_position: list[int]
|
|
136
|
+
|
|
137
|
+
# Arbitrary data dictionary connectors may use
|
|
138
|
+
connector_data: dict[str, Any]
|
|
139
|
+
|
|
140
|
+
def loop(self, iterable):
|
|
141
|
+
self.loop_position.append(0)
|
|
142
|
+
for i, item in enumerate(iterable):
|
|
143
|
+
self.loop_position[-1] = i
|
|
144
|
+
yield item
|
|
145
|
+
self.loop_position.pop()
|
|
37
146
|
|
|
38
147
|
def __init__(
|
|
39
|
-
self,
|
|
40
|
-
|
|
148
|
+
self,
|
|
149
|
+
name: str,
|
|
150
|
+
inventory: "Inventory",
|
|
151
|
+
groups,
|
|
152
|
+
connector_cls=None,
|
|
41
153
|
):
|
|
154
|
+
if connector_cls is None:
|
|
155
|
+
connector_cls = get_execution_connector("ssh")
|
|
42
156
|
self.inventory = inventory
|
|
43
157
|
self.groups = groups
|
|
44
|
-
self.
|
|
45
|
-
self.executor = executor
|
|
158
|
+
self.connector_cls = connector_cls
|
|
46
159
|
self.name = name
|
|
47
160
|
|
|
48
|
-
|
|
49
|
-
|
|
161
|
+
self.loop_position = []
|
|
162
|
+
|
|
163
|
+
self.connector_data = {}
|
|
164
|
+
|
|
165
|
+
# Append only list of operation hashes as called on this host, used to
|
|
166
|
+
# generate a DAG to create the final operation order.
|
|
167
|
+
self.op_hash_order: list[str] = []
|
|
168
|
+
|
|
169
|
+
# Create the (waterfall data: override, host, group, global)
|
|
170
|
+
self.data = HostData(
|
|
171
|
+
self,
|
|
172
|
+
lambda: inventory.get_override_data(),
|
|
173
|
+
lambda: inventory.get_host_data(name),
|
|
174
|
+
lambda: inventory.get_groups_data(groups),
|
|
175
|
+
lambda: inventory.get_data(),
|
|
176
|
+
# @deploy function data are default values, so come last
|
|
177
|
+
self.get_deploy_data,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def init(self, state: "State") -> None:
|
|
181
|
+
self.state = state
|
|
182
|
+
self.connector = self.connector_cls(state, self)
|
|
183
|
+
|
|
184
|
+
longest_name_len = max([len(host.name) for host in self.inventory])
|
|
185
|
+
padding_diff = longest_name_len - len(self.name)
|
|
186
|
+
self.print_prefix_padding = "".join(" " for _ in range(0, padding_diff))
|
|
50
187
|
|
|
188
|
+
@override
|
|
189
|
+
def __str__(self):
|
|
190
|
+
return "{0}".format(self.name)
|
|
191
|
+
|
|
192
|
+
@override
|
|
51
193
|
def __repr__(self):
|
|
52
|
-
return self.name
|
|
194
|
+
return "Host({0})".format(self.name)
|
|
53
195
|
|
|
54
196
|
@property
|
|
55
197
|
def host_data(self):
|
|
@@ -60,38 +202,259 @@ class Host(object):
|
|
|
60
202
|
return self.inventory.get_groups_data(self.groups)
|
|
61
203
|
|
|
62
204
|
@property
|
|
63
|
-
def print_prefix(self):
|
|
64
|
-
|
|
65
|
-
|
|
205
|
+
def print_prefix(self) -> str:
|
|
206
|
+
if self.nested_executing_op_hash:
|
|
207
|
+
return "{0}[{1}] {2}{3} ".format(
|
|
208
|
+
click.style(""), # reset
|
|
209
|
+
click.style(self.name, bold=True),
|
|
210
|
+
click.style("nested", "blue"),
|
|
211
|
+
self.print_prefix_padding,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return "{0}[{1}]{2} ".format(
|
|
215
|
+
click.style(""), # reset
|
|
66
216
|
click.style(self.name, bold=True),
|
|
217
|
+
self.print_prefix_padding,
|
|
67
218
|
)
|
|
68
219
|
|
|
69
|
-
def style_print_prefix(self, *args, **kwargs):
|
|
70
|
-
return
|
|
71
|
-
click.style(
|
|
220
|
+
def style_print_prefix(self, *args, **kwargs) -> str:
|
|
221
|
+
return "{0}[{1}]{2} ".format(
|
|
222
|
+
click.style(""), # reset
|
|
72
223
|
click.style(self.name, *args, **kwargs),
|
|
224
|
+
self.print_prefix_padding,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
def log(self, message: str, log_func: Callable[[str], Any] = logger.info) -> None:
|
|
228
|
+
log_func(f"{self.print_prefix}{message}")
|
|
229
|
+
|
|
230
|
+
def log_styled(
|
|
231
|
+
self, message: str, log_func: Callable[[str], Any] = logger.info, **kwargs
|
|
232
|
+
) -> None:
|
|
233
|
+
message_styled = click.style(message, **kwargs)
|
|
234
|
+
self.log(message_styled, log_func=log_func)
|
|
235
|
+
|
|
236
|
+
def get_deploy_data(self):
|
|
237
|
+
return self.current_op_deploy_data or self.current_deploy_data or {}
|
|
238
|
+
|
|
239
|
+
def noop(self, description: str) -> None:
|
|
240
|
+
"""
|
|
241
|
+
Log a description for a noop operation.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
handler = logger.info if self.state.print_noop_info else logger.debug
|
|
245
|
+
handler("{0}noop: {1}".format(self.print_prefix, description))
|
|
246
|
+
|
|
247
|
+
def when(self, condition: Callable[[], bool]):
|
|
248
|
+
return self.deploy(
|
|
249
|
+
"",
|
|
250
|
+
cast("AllArguments", {"_if": [condition]}),
|
|
251
|
+
{},
|
|
252
|
+
in_deploy=False,
|
|
73
253
|
)
|
|
74
254
|
|
|
255
|
+
def arguments(self, **arguments: Unpack["AllArguments"]):
|
|
256
|
+
return self.deploy("", arguments, {}, in_deploy=False)
|
|
257
|
+
|
|
258
|
+
@contextmanager
|
|
259
|
+
def deploy(
|
|
260
|
+
self,
|
|
261
|
+
name: str,
|
|
262
|
+
kwargs: Optional["AllArguments"],
|
|
263
|
+
data: Optional[dict],
|
|
264
|
+
in_deploy: bool = True,
|
|
265
|
+
):
|
|
266
|
+
"""
|
|
267
|
+
Wraps a group of operations as a deploy, this should not be used
|
|
268
|
+
directly, instead use ``pyinfra.api.deploy.deploy``.
|
|
269
|
+
"""
|
|
270
|
+
|
|
271
|
+
# Handle nested deploy names
|
|
272
|
+
if self.current_deploy_name:
|
|
273
|
+
name = "{0} | {1}".format(self.current_deploy_name, name)
|
|
274
|
+
|
|
275
|
+
# Store the previous values
|
|
276
|
+
old_in_deploy = self.in_deploy
|
|
277
|
+
old_deploy_name = self.current_deploy_name
|
|
278
|
+
old_deploy_kwargs = self.current_deploy_kwargs
|
|
279
|
+
old_deploy_data = self.current_deploy_data
|
|
280
|
+
self.in_deploy = in_deploy
|
|
281
|
+
|
|
282
|
+
# Combine any old _ifs with the new ones
|
|
283
|
+
if old_deploy_kwargs and kwargs:
|
|
284
|
+
old_ifs = old_deploy_kwargs["_if"]
|
|
285
|
+
new_ifs = kwargs["_if"]
|
|
286
|
+
if old_ifs and new_ifs:
|
|
287
|
+
kwargs["_if"] = old_ifs + new_ifs
|
|
288
|
+
|
|
289
|
+
# Set the new values
|
|
290
|
+
self.current_deploy_name = name
|
|
291
|
+
self.current_deploy_kwargs = kwargs
|
|
292
|
+
self.current_deploy_data = data
|
|
293
|
+
logger.debug(
|
|
294
|
+
"Starting deploy %s (args=%r, data=%r)",
|
|
295
|
+
name,
|
|
296
|
+
kwargs,
|
|
297
|
+
data,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
yield
|
|
301
|
+
|
|
302
|
+
# Restore the previous values
|
|
303
|
+
self.in_deploy = old_in_deploy
|
|
304
|
+
self.current_deploy_name = old_deploy_name
|
|
305
|
+
self.current_deploy_kwargs = old_deploy_kwargs
|
|
306
|
+
self.current_deploy_data = old_deploy_data
|
|
307
|
+
|
|
308
|
+
logger.debug(
|
|
309
|
+
"Reset deploy to %s (args=%r, data=%r)",
|
|
310
|
+
old_deploy_name,
|
|
311
|
+
old_deploy_kwargs,
|
|
312
|
+
old_deploy_data,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
@memoize
|
|
316
|
+
def _get_temp_directory(self):
|
|
317
|
+
temp_directory = self.state.config.TEMP_DIR
|
|
318
|
+
|
|
319
|
+
if temp_directory is None:
|
|
320
|
+
# Unfortunate, but very hard to avoid, circular dependency, this method is memoized so
|
|
321
|
+
# performance isn't a concern.
|
|
322
|
+
from pyinfra.facts.server import TmpDir
|
|
323
|
+
|
|
324
|
+
temp_directory = self.get_fact(TmpDir)
|
|
325
|
+
|
|
326
|
+
if not temp_directory:
|
|
327
|
+
temp_directory = self.state.config.DEFAULT_TEMP_DIR
|
|
328
|
+
|
|
329
|
+
return temp_directory
|
|
330
|
+
|
|
331
|
+
def get_temp_filename(
|
|
332
|
+
self,
|
|
333
|
+
hash_key: Optional[str] = None,
|
|
334
|
+
hash_filename: bool = True,
|
|
335
|
+
*,
|
|
336
|
+
temp_directory: Optional[str] = None,
|
|
337
|
+
):
|
|
338
|
+
"""
|
|
339
|
+
Generate a temporary filename for this deploy.
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
temp_directory = temp_directory or self._get_temp_directory()
|
|
343
|
+
|
|
344
|
+
if not hash_key:
|
|
345
|
+
hash_key = str(uuid4())
|
|
346
|
+
|
|
347
|
+
if hash_filename:
|
|
348
|
+
hash_key = sha1_hash(hash_key)
|
|
349
|
+
|
|
350
|
+
return "{0}/pyinfra-{1}".format(temp_directory, hash_key)
|
|
351
|
+
|
|
352
|
+
# Host facts
|
|
353
|
+
#
|
|
354
|
+
|
|
355
|
+
T = TypeVar("T")
|
|
356
|
+
|
|
357
|
+
@overload
|
|
358
|
+
def get_fact(self, name_or_cls: Type[FactBase[T]], *args, **kwargs) -> T: ...
|
|
359
|
+
|
|
360
|
+
@overload
|
|
361
|
+
def get_fact(self, name_or_cls: Type[ShortFactBase[T]], *args, **kwargs) -> T: ...
|
|
362
|
+
|
|
363
|
+
def get_fact(self, name_or_cls, *args, **kwargs):
|
|
364
|
+
"""
|
|
365
|
+
Get a fact for this host, reading from the cache if present.
|
|
366
|
+
"""
|
|
367
|
+
return get_fact(self.state, self, name_or_cls, args=args, kwargs=kwargs)
|
|
368
|
+
|
|
75
369
|
# Connector proxy
|
|
76
370
|
#
|
|
77
371
|
|
|
78
|
-
def
|
|
79
|
-
if not self.
|
|
80
|
-
|
|
372
|
+
def _check_state(self) -> None:
|
|
373
|
+
if not self.state:
|
|
374
|
+
raise TypeError("Cannot call this function with no state!")
|
|
375
|
+
|
|
376
|
+
def connect(self, reason=None, show_errors: bool = True, raise_exceptions: bool = False):
|
|
377
|
+
"""
|
|
378
|
+
Connect to the host using it's configured connector.
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
self._check_state()
|
|
382
|
+
if not self.connected:
|
|
383
|
+
self.state.trigger_callbacks("host_before_connect", self)
|
|
81
384
|
|
|
82
|
-
|
|
385
|
+
try:
|
|
386
|
+
self.connector.connect()
|
|
387
|
+
except ConnectError as e:
|
|
388
|
+
if show_errors:
|
|
389
|
+
log_message = "{0}{1}".format(
|
|
390
|
+
self.print_prefix,
|
|
391
|
+
click.style(e.args[0], "red"),
|
|
392
|
+
)
|
|
393
|
+
logger.error(log_message)
|
|
83
394
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
395
|
+
self.state.trigger_callbacks("host_connect_error", self, e)
|
|
396
|
+
|
|
397
|
+
if raise_exceptions:
|
|
398
|
+
raise
|
|
399
|
+
else:
|
|
400
|
+
log_message = "{0}{1}".format(
|
|
401
|
+
self.print_prefix,
|
|
402
|
+
click.style("Connected", "green"),
|
|
403
|
+
)
|
|
404
|
+
if reason:
|
|
405
|
+
log_message = "{0}{1}".format(
|
|
406
|
+
log_message,
|
|
407
|
+
" ({0})".format(reason),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
logger.info(log_message)
|
|
411
|
+
self.state.trigger_callbacks("host_connect", self)
|
|
412
|
+
self.connected = True
|
|
413
|
+
|
|
414
|
+
def disconnect(self) -> None:
|
|
415
|
+
"""
|
|
416
|
+
Disconnect from the host using it's configured connector.
|
|
417
|
+
"""
|
|
418
|
+
self._check_state()
|
|
419
|
+
|
|
420
|
+
# Disconnect is an optional function for connectors if needed
|
|
421
|
+
disconnect_func = getattr(self.connector, "disconnect", None)
|
|
87
422
|
if disconnect_func:
|
|
88
|
-
|
|
423
|
+
disconnect_func()
|
|
424
|
+
|
|
425
|
+
# TODO: consider whether this should be here!
|
|
426
|
+
remove_any_sudo_askpass_file(self)
|
|
427
|
+
|
|
428
|
+
self.state.trigger_callbacks("host_disconnect", self)
|
|
429
|
+
self.connected = False
|
|
430
|
+
|
|
431
|
+
def run_shell_command(self, *args, **kwargs) -> tuple[bool, CommandOutput]:
|
|
432
|
+
"""
|
|
433
|
+
Low level method to execute a shell command on the host via it's configured connector.
|
|
434
|
+
"""
|
|
435
|
+
self._check_state()
|
|
436
|
+
return self.connector.run_shell_command(*args, **kwargs)
|
|
437
|
+
|
|
438
|
+
def put_file(self, *args, **kwargs) -> bool:
|
|
439
|
+
"""
|
|
440
|
+
Low level method to upload a file to the host via it's configured connector.
|
|
441
|
+
"""
|
|
442
|
+
self._check_state()
|
|
443
|
+
return self.connector.put_file(*args, **kwargs)
|
|
444
|
+
|
|
445
|
+
def get_file(self, *args, **kwargs) -> bool:
|
|
446
|
+
"""
|
|
447
|
+
Low level method to download a file from the host via it's configured connector.
|
|
448
|
+
"""
|
|
449
|
+
self._check_state()
|
|
450
|
+
return self.connector.get_file(*args, **kwargs)
|
|
89
451
|
|
|
90
|
-
|
|
91
|
-
return self.executor.run_shell_command(state, self, *args, **kwargs)
|
|
452
|
+
# Rsync - optional connector specific ability
|
|
92
453
|
|
|
93
|
-
def
|
|
94
|
-
|
|
454
|
+
def check_can_rsync(self) -> None:
|
|
455
|
+
self._check_state()
|
|
456
|
+
return self.connector.check_can_rsync()
|
|
95
457
|
|
|
96
|
-
def
|
|
97
|
-
|
|
458
|
+
def rsync(self, *args, **kwargs) -> bool:
|
|
459
|
+
self._check_state()
|
|
460
|
+
return self.connector.rsync(*args, **kwargs)
|