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
pyinfra/api/__init__.py
CHANGED
|
@@ -11,6 +11,9 @@ from .config import Config # noqa: F401 # pragma: no cover
|
|
|
11
11
|
from .deploy import deploy # noqa: F401 # pragma: no cover
|
|
12
12
|
from .exceptions import DeployError # noqa: F401 # pragma: no cover
|
|
13
13
|
from .exceptions import ( # noqa: F401
|
|
14
|
+
FactError,
|
|
15
|
+
FactTypeError,
|
|
16
|
+
FactValueError,
|
|
14
17
|
InventoryError,
|
|
15
18
|
OperationError,
|
|
16
19
|
OperationTypeError,
|
pyinfra/api/arguments.py
CHANGED
|
@@ -1,261 +1,295 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from typing import (
|
|
2
4
|
TYPE_CHECKING,
|
|
3
5
|
Any,
|
|
4
6
|
Callable,
|
|
5
|
-
|
|
7
|
+
Generic,
|
|
6
8
|
Iterable,
|
|
7
9
|
List,
|
|
8
10
|
Mapping,
|
|
9
11
|
Optional,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
Type,
|
|
13
|
+
TypeVar,
|
|
12
14
|
Union,
|
|
15
|
+
cast,
|
|
16
|
+
get_type_hints,
|
|
13
17
|
)
|
|
14
18
|
|
|
15
|
-
import
|
|
16
|
-
from pyinfra import context, logger
|
|
17
|
-
from pyinfra.api.state import State
|
|
19
|
+
from typing_extensions import TypedDict
|
|
18
20
|
|
|
19
|
-
from
|
|
21
|
+
from pyinfra import context
|
|
22
|
+
from pyinfra.api.exceptions import ArgumentTypeError
|
|
23
|
+
from pyinfra.api.state import State
|
|
24
|
+
from pyinfra.api.util import raise_if_bad_type
|
|
20
25
|
|
|
21
26
|
if TYPE_CHECKING:
|
|
22
27
|
from pyinfra.api.config import Config
|
|
23
28
|
from pyinfra.api.host import Host
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"description": "Execute/apply any changes with ``sudo``.",
|
|
28
|
-
"default": lambda config: config.SUDO,
|
|
29
|
-
"type": bool,
|
|
30
|
-
},
|
|
31
|
-
"_sudo_user": {
|
|
32
|
-
"description": "Execute/apply any changes with ``sudo`` as a non-root user.",
|
|
33
|
-
"default": lambda config: config.SUDO_USER,
|
|
34
|
-
"type": str,
|
|
35
|
-
},
|
|
36
|
-
"_use_sudo_login": {
|
|
37
|
-
"description": "Execute ``sudo`` with a login shell.",
|
|
38
|
-
"default": lambda config: config.USE_SUDO_LOGIN,
|
|
39
|
-
"type": bool,
|
|
40
|
-
},
|
|
41
|
-
"_use_sudo_password": {
|
|
42
|
-
"description": "Whether to use a password with ``sudo`` (will ask).",
|
|
43
|
-
"default": lambda config: config.USE_SUDO_PASSWORD,
|
|
44
|
-
"type": bool,
|
|
45
|
-
},
|
|
46
|
-
"_preserve_sudo_env": {
|
|
47
|
-
"description": "Preserve the shell environment when using ``sudo``.",
|
|
48
|
-
"default": lambda config: config.PRESERVE_SUDO_ENV,
|
|
49
|
-
"type": bool,
|
|
50
|
-
},
|
|
51
|
-
"_su_user": {
|
|
52
|
-
"description": "Execute/apply any changes with this user using ``su``.",
|
|
53
|
-
"default": lambda config: config.SU_USER,
|
|
54
|
-
"type": str,
|
|
55
|
-
},
|
|
56
|
-
"_use_su_login": {
|
|
57
|
-
"description": "Execute ``su`` with a login shell.",
|
|
58
|
-
"default": lambda config: config.USE_SU_LOGIN,
|
|
59
|
-
"type": bool,
|
|
60
|
-
},
|
|
61
|
-
"_preserve_su_env": {
|
|
62
|
-
"description": "Preserve the shell environment when using ``su``.",
|
|
63
|
-
"default": lambda config: config.PRESERVE_SU_ENV,
|
|
64
|
-
"type": bool,
|
|
65
|
-
},
|
|
66
|
-
"_su_shell": {
|
|
67
|
-
"description": (
|
|
68
|
-
"Use this shell (instead of user login shell) when using ``su``). "
|
|
69
|
-
"Only available under Linux, for use when using `su` with a user that "
|
|
70
|
-
"has nologin/similar as their login shell."
|
|
71
|
-
),
|
|
72
|
-
"default": lambda config: config.SU_SHELL,
|
|
73
|
-
"type": str,
|
|
74
|
-
},
|
|
75
|
-
"_doas": {
|
|
76
|
-
"description": "Execute/apply any changes with ``doas``.",
|
|
77
|
-
"default": lambda config: config.DOAS,
|
|
78
|
-
"type": bool,
|
|
79
|
-
},
|
|
80
|
-
"_doas_user": {
|
|
81
|
-
"description": "Execute/apply any changes with ``doas`` as a non-root user.",
|
|
82
|
-
"default": lambda config: config.DOAS_USER,
|
|
83
|
-
"type": str,
|
|
84
|
-
},
|
|
85
|
-
}
|
|
30
|
+
T = TypeVar("T")
|
|
31
|
+
default_sentinel = object()
|
|
86
32
|
|
|
87
33
|
|
|
88
|
-
|
|
89
|
-
|
|
34
|
+
class ArgumentMeta(Generic[T]):
|
|
35
|
+
description: str
|
|
36
|
+
default: Callable[["Config"], T]
|
|
37
|
+
handler: Optional[Callable[["Config", T], T]]
|
|
38
|
+
|
|
39
|
+
def __init__(self, description, default, handler=None) -> None:
|
|
40
|
+
self.description = description
|
|
41
|
+
self.default = default
|
|
42
|
+
self.handler = handler
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Connector arguments
|
|
46
|
+
# These are arguments passed to the various connectors that provide the underlying
|
|
47
|
+
# API to read/write external systems.
|
|
90
48
|
|
|
91
|
-
# TODO: this is to protect against host.data.env being a string or similar,
|
|
92
|
-
# the introduction of using host.data.X for operation kwargs combined with
|
|
93
|
-
# `env` being a commonly defined data variable causes issues.
|
|
94
|
-
# The real fix here is the prefixed `_env` argument.
|
|
95
|
-
if value and isinstance(value, dict):
|
|
96
|
-
env.update(value)
|
|
97
49
|
|
|
50
|
+
# Note: ConnectorArguments is specifically not total as it's used to type many
|
|
51
|
+
# functions via Unpack and we don't want to specify every kwarg.
|
|
52
|
+
class ConnectorArguments(TypedDict, total=False):
|
|
53
|
+
# Auth arguments
|
|
54
|
+
_sudo: bool
|
|
55
|
+
_sudo_user: str
|
|
56
|
+
_use_sudo_login: bool
|
|
57
|
+
_sudo_password: str
|
|
58
|
+
_preserve_sudo_env: bool
|
|
59
|
+
_su_user: str
|
|
60
|
+
_use_su_login: bool
|
|
61
|
+
_preserve_su_env: bool
|
|
62
|
+
_su_shell: str
|
|
63
|
+
_doas: bool
|
|
64
|
+
_doas_user: str
|
|
65
|
+
|
|
66
|
+
# Shell arguments
|
|
67
|
+
_shell_executable: str
|
|
68
|
+
_chdir: str
|
|
69
|
+
_env: Mapping[str, str]
|
|
70
|
+
|
|
71
|
+
# Connector control (outside of command generation)
|
|
72
|
+
_success_exit_codes: Iterable[int]
|
|
73
|
+
_timeout: int
|
|
74
|
+
_get_pty: bool
|
|
75
|
+
_stdin: Union[str, Iterable[str]]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def generate_env(config: "Config", value: dict) -> dict:
|
|
79
|
+
env = config.ENV.copy()
|
|
80
|
+
env.update(value)
|
|
98
81
|
return env
|
|
99
82
|
|
|
100
83
|
|
|
101
|
-
|
|
102
|
-
"
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
"
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
84
|
+
auth_argument_meta: dict[str, ArgumentMeta] = {
|
|
85
|
+
"_sudo": ArgumentMeta(
|
|
86
|
+
"Execute/apply any changes with sudo.",
|
|
87
|
+
default=lambda config: config.SUDO,
|
|
88
|
+
),
|
|
89
|
+
"_sudo_user": ArgumentMeta(
|
|
90
|
+
"Execute/apply any changes with sudo as a non-root user.",
|
|
91
|
+
default=lambda config: config.SUDO_USER,
|
|
92
|
+
),
|
|
93
|
+
"_use_sudo_login": ArgumentMeta(
|
|
94
|
+
"Execute sudo with a login shell.",
|
|
95
|
+
default=lambda config: config.USE_SUDO_LOGIN,
|
|
96
|
+
),
|
|
97
|
+
"_sudo_password": ArgumentMeta(
|
|
98
|
+
"Password to sudo with. If needed and not specified pyinfra will prompt for it.",
|
|
99
|
+
default=lambda config: config.SUDO_PASSWORD,
|
|
100
|
+
),
|
|
101
|
+
"_preserve_sudo_env": ArgumentMeta(
|
|
102
|
+
"Preserve the shell environment of the connecting user when using sudo.",
|
|
103
|
+
default=lambda config: config.PRESERVE_SUDO_ENV,
|
|
104
|
+
),
|
|
105
|
+
"_su_user": ArgumentMeta(
|
|
106
|
+
"Execute/apply any changes with this user using su.",
|
|
107
|
+
default=lambda config: config.SU_USER,
|
|
108
|
+
),
|
|
109
|
+
"_use_su_login": ArgumentMeta(
|
|
110
|
+
"Execute su with a login shell.",
|
|
111
|
+
default=lambda config: config.USE_SU_LOGIN,
|
|
112
|
+
),
|
|
113
|
+
"_preserve_su_env": ArgumentMeta(
|
|
114
|
+
"Preserve the shell environment of the connecting user when using su.",
|
|
115
|
+
default=lambda config: config.PRESERVE_SU_ENV,
|
|
116
|
+
),
|
|
117
|
+
"_su_shell": ArgumentMeta(
|
|
118
|
+
"Use this shell (instead of user login shell) when using ``_su``). "
|
|
119
|
+
+ "Only available under Linux, for use when using `su` with a user that "
|
|
120
|
+
+ "has nologin/similar as their login shell.",
|
|
121
|
+
default=lambda config: config.SU_SHELL,
|
|
122
|
+
),
|
|
123
|
+
"_doas": ArgumentMeta(
|
|
124
|
+
"Execute/apply any changes with doas.",
|
|
125
|
+
default=lambda config: config.DOAS,
|
|
126
|
+
),
|
|
127
|
+
"_doas_user": ArgumentMeta(
|
|
128
|
+
"Execute/apply any changes with doas as a non-root user.",
|
|
129
|
+
default=lambda config: config.DOAS_USER,
|
|
130
|
+
),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
shell_argument_meta: dict[str, ArgumentMeta] = {
|
|
134
|
+
"_shell_executable": ArgumentMeta(
|
|
135
|
+
"The shell executable to use for executing commands.",
|
|
136
|
+
default=lambda config: config.SHELL,
|
|
137
|
+
),
|
|
138
|
+
"_chdir": ArgumentMeta(
|
|
139
|
+
"Directory to switch to before executing the command.",
|
|
140
|
+
default=lambda _: "",
|
|
141
|
+
),
|
|
142
|
+
"_env": ArgumentMeta(
|
|
143
|
+
"Dictionary of environment variables to set.",
|
|
144
|
+
default=lambda _: {},
|
|
145
|
+
handler=generate_env,
|
|
146
|
+
),
|
|
147
|
+
"_success_exit_codes": ArgumentMeta(
|
|
148
|
+
"List of exit codes to consider a success.",
|
|
149
|
+
default=lambda _: [0],
|
|
150
|
+
),
|
|
151
|
+
"_timeout": ArgumentMeta(
|
|
152
|
+
"Timeout for *each* command executed during the operation.",
|
|
153
|
+
default=lambda _: None,
|
|
154
|
+
),
|
|
155
|
+
"_get_pty": ArgumentMeta(
|
|
156
|
+
"Whether to get a pseudoTTY when executing any commands.",
|
|
157
|
+
default=lambda _: False,
|
|
158
|
+
),
|
|
159
|
+
"_stdin": ArgumentMeta(
|
|
160
|
+
"String or buffer to send to the stdin of any commands.",
|
|
161
|
+
default=lambda _: None,
|
|
162
|
+
),
|
|
133
163
|
}
|
|
134
164
|
|
|
135
|
-
|
|
165
|
+
|
|
166
|
+
# Meta arguments
|
|
167
|
+
# These provide/extend additional operation metadata
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class MetaArguments(TypedDict):
|
|
171
|
+
name: str
|
|
172
|
+
_ignore_errors: bool
|
|
173
|
+
_continue_on_error: bool
|
|
174
|
+
_if: Union[List[Callable[[], bool]], Callable[[], bool], None]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
meta_argument_meta: dict[str, ArgumentMeta] = {
|
|
136
178
|
# NOTE: name is the only non-_-prefixed argument
|
|
137
|
-
"name":
|
|
138
|
-
"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
"_ignore_errors":
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
"description": (
|
|
179
|
+
"name": ArgumentMeta(
|
|
180
|
+
"Name of the operation.",
|
|
181
|
+
default=lambda _: None,
|
|
182
|
+
),
|
|
183
|
+
"_ignore_errors": ArgumentMeta(
|
|
184
|
+
"Ignore errors when executing the operation.",
|
|
185
|
+
default=lambda config: config.IGNORE_ERRORS,
|
|
186
|
+
),
|
|
187
|
+
"_continue_on_error": ArgumentMeta(
|
|
188
|
+
(
|
|
148
189
|
"Continue executing operation commands after error. "
|
|
149
190
|
"Only applies when ``_ignore_errors`` is true."
|
|
150
191
|
),
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
},
|
|
158
|
-
"_postcondition": {
|
|
159
|
-
"description": "Command to execute & check after the operation commands complete.",
|
|
160
|
-
"type": str,
|
|
161
|
-
},
|
|
162
|
-
# Lambda on the next two are to workaround a circular import
|
|
163
|
-
"_on_success": {
|
|
164
|
-
"description": "Callback function to execute on success.",
|
|
165
|
-
"type": lambda: Callable[[State, pyinfra.api.Host, str], None],
|
|
166
|
-
},
|
|
167
|
-
"_on_error": {
|
|
168
|
-
"description": "Callback function to execute on error.",
|
|
169
|
-
"type": lambda: Callable[[State, pyinfra.api.Host, str], None],
|
|
170
|
-
},
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
# Execution kwargs are global - ie must be identical for every host
|
|
174
|
-
execution_kwargs = {
|
|
175
|
-
"_parallel": {
|
|
176
|
-
"description": "Run this operation in batches of hosts.",
|
|
177
|
-
"default": lambda config: config.PARALLEL,
|
|
178
|
-
"type": int,
|
|
179
|
-
},
|
|
180
|
-
"_run_once": {
|
|
181
|
-
"description": "Only execute this operation once, on the first host to see it.",
|
|
182
|
-
"default": lambda config: False,
|
|
183
|
-
"type": bool,
|
|
184
|
-
},
|
|
185
|
-
"_serial": {
|
|
186
|
-
"description": "Run this operation host by host, rather than in parallel.",
|
|
187
|
-
"default": lambda config: False,
|
|
188
|
-
"type": bool,
|
|
189
|
-
},
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
# TODO: refactor these into classes so they can be typed properly, remove Any
|
|
193
|
-
ALL_ARGUMENTS: Dict[str, Dict[str, Any]] = {
|
|
194
|
-
**auth_kwargs,
|
|
195
|
-
**shell_kwargs,
|
|
196
|
-
**meta_kwargs,
|
|
197
|
-
**execution_kwargs,
|
|
192
|
+
default=lambda _: False,
|
|
193
|
+
),
|
|
194
|
+
"_if": ArgumentMeta(
|
|
195
|
+
"Only run this operation if these functions return True",
|
|
196
|
+
default=lambda _: [],
|
|
197
|
+
),
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
OPERATION_KWARG_DOC: List[Tuple[str, Optional[str], Dict[str, Dict[str, Any]]]] = [
|
|
201
|
-
("Privilege & user escalation", None, auth_kwargs),
|
|
202
|
-
("Shell control & features", None, shell_kwargs),
|
|
203
|
-
("Operation meta & callbacks", "Not available in facts.", meta_kwargs),
|
|
204
|
-
(
|
|
205
|
-
"Execution strategy",
|
|
206
|
-
"Not available in facts, value must be the same for all hosts.",
|
|
207
|
-
execution_kwargs,
|
|
208
|
-
),
|
|
209
|
-
]
|
|
210
200
|
|
|
201
|
+
# Execution arguments
|
|
202
|
+
# These alter how pyinfra is to execute an operation. Notably these must all have the same value
|
|
203
|
+
# over every target host for the same operation.
|
|
211
204
|
|
|
212
|
-
def _get_internal_key(key: str) -> str:
|
|
213
|
-
if key.startswith("_"):
|
|
214
|
-
return key[1:]
|
|
215
|
-
return key
|
|
216
205
|
|
|
206
|
+
class ExecutionArguments(TypedDict):
|
|
207
|
+
_parallel: int
|
|
208
|
+
_run_once: bool
|
|
209
|
+
_serial: bool
|
|
217
210
|
|
|
218
|
-
@memoize
|
|
219
|
-
def get_execution_kwarg_keys() -> List[Any]:
|
|
220
|
-
return [_get_internal_key(key) for key in execution_kwargs.keys()]
|
|
221
211
|
|
|
212
|
+
execution_argument_meta: dict[str, ArgumentMeta] = {
|
|
213
|
+
"_parallel": ArgumentMeta(
|
|
214
|
+
"Run this operation in batches of hosts.",
|
|
215
|
+
default=lambda config: config.PARALLEL,
|
|
216
|
+
),
|
|
217
|
+
"_run_once": ArgumentMeta(
|
|
218
|
+
"Only execute this operation once, on the first host to see it.",
|
|
219
|
+
default=lambda _: False,
|
|
220
|
+
),
|
|
221
|
+
"_serial": ArgumentMeta(
|
|
222
|
+
"Run this operation host by host, rather than in parallel.",
|
|
223
|
+
default=lambda _: False,
|
|
224
|
+
),
|
|
225
|
+
}
|
|
222
226
|
|
|
223
|
-
@memoize
|
|
224
|
-
def get_executor_kwarg_keys() -> List[Any]:
|
|
225
|
-
keys: Set[str] = set()
|
|
226
|
-
keys.update(auth_kwargs.keys(), shell_kwargs.keys())
|
|
227
|
-
return [_get_internal_key(key) for key in keys]
|
|
228
227
|
|
|
228
|
+
class AllArguments(ConnectorArguments, MetaArguments, ExecutionArguments):
|
|
229
|
+
pass
|
|
229
230
|
|
|
230
|
-
@memoize
|
|
231
|
-
def show_legacy_argument_warning(key, call_location):
|
|
232
|
-
logger.warning(
|
|
233
|
-
(
|
|
234
|
-
'{0}:\n\tGlobal arguments should be prefixed "_", '
|
|
235
|
-
"please us the `{1}` keyword argument in place of `{2}`."
|
|
236
|
-
).format(call_location, "_{0}".format(key), key),
|
|
237
|
-
)
|
|
238
231
|
|
|
232
|
+
def all_global_arguments() -> List[tuple[str, Type]]:
|
|
233
|
+
"""Return all global arguments and their types."""
|
|
234
|
+
return list(get_type_hints(AllArguments).items())
|
|
239
235
|
|
|
240
|
-
@memoize
|
|
241
|
-
def show_legacy_argument_host_data_warning(key):
|
|
242
|
-
logger.warning(
|
|
243
|
-
(
|
|
244
|
-
'Global arguments should be prefixed "_", '
|
|
245
|
-
"please us the `host.data._{0}` keyword argument in place of `host.data.{0}`."
|
|
246
|
-
).format(key),
|
|
247
|
-
)
|
|
248
236
|
|
|
237
|
+
all_argument_meta: dict[str, ArgumentMeta] = {
|
|
238
|
+
**auth_argument_meta,
|
|
239
|
+
**shell_argument_meta,
|
|
240
|
+
**meta_argument_meta,
|
|
241
|
+
**execution_argument_meta,
|
|
242
|
+
}
|
|
249
243
|
|
|
250
|
-
|
|
244
|
+
EXECUTION_KWARG_KEYS = list(ExecutionArguments.__annotations__.keys())
|
|
245
|
+
CONNECTOR_ARGUMENT_KEYS = list(ConnectorArguments.__annotations__.keys())
|
|
246
|
+
|
|
247
|
+
__argument_docs__ = {
|
|
248
|
+
"Privilege & user escalation": (
|
|
249
|
+
auth_argument_meta,
|
|
250
|
+
"""
|
|
251
|
+
.. code:: python
|
|
252
|
+
|
|
253
|
+
# Execute a command with sudo
|
|
254
|
+
server.user(
|
|
255
|
+
name="Create pyinfra user using sudo",
|
|
256
|
+
user="pyinfra",
|
|
257
|
+
_sudo=True,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Execute a command with a specific sudo password
|
|
261
|
+
server.user(
|
|
262
|
+
name="Create pyinfra user using sudo",
|
|
263
|
+
user="pyinfra",
|
|
264
|
+
_sudo=True,
|
|
265
|
+
_sudo_password="my-secret-password",
|
|
266
|
+
)
|
|
267
|
+
""",
|
|
268
|
+
),
|
|
269
|
+
"Shell control & features": (
|
|
270
|
+
shell_argument_meta,
|
|
271
|
+
"""
|
|
272
|
+
.. code:: python
|
|
273
|
+
|
|
274
|
+
# Execute from a specific directory
|
|
275
|
+
server.shell(
|
|
276
|
+
name="Bootstrap nginx params",
|
|
277
|
+
commands=["openssl dhparam -out dhparam.pem 4096"],
|
|
278
|
+
_chdir="/etc/ssl/certs",
|
|
279
|
+
)
|
|
280
|
+
""",
|
|
281
|
+
),
|
|
282
|
+
"Operation meta & callbacks": (meta_argument_meta, ""),
|
|
283
|
+
"Execution strategy": (execution_argument_meta, ""),
|
|
284
|
+
}
|
|
251
285
|
|
|
252
286
|
|
|
253
287
|
def pop_global_arguments(
|
|
254
|
-
kwargs:
|
|
288
|
+
kwargs: dict[str, Any],
|
|
255
289
|
state: Optional["State"] = None,
|
|
256
290
|
host: Optional["Host"] = None,
|
|
257
291
|
keys_to_check=None,
|
|
258
|
-
):
|
|
292
|
+
) -> tuple[AllArguments, list[str]]:
|
|
259
293
|
"""
|
|
260
294
|
Pop and return operation global keyword arguments, in preferred order:
|
|
261
295
|
|
|
@@ -263,16 +297,6 @@ def pop_global_arguments(
|
|
|
263
297
|
+ From any current @deploy context (deploy kwargs)
|
|
264
298
|
+ From the host data variables
|
|
265
299
|
+ From the config variables
|
|
266
|
-
|
|
267
|
-
Note this function is only called directly in the @operation & @deploy decorator
|
|
268
|
-
wrappers which the user should pass global arguments prefixed "_". This is to
|
|
269
|
-
avoid any clashes with operation and deploy functions both internal and third
|
|
270
|
-
party.
|
|
271
|
-
|
|
272
|
-
This is a bit strange because internally pyinfra uses non-_-prefixed arguments,
|
|
273
|
-
and this function is responsible for the translation between the two.
|
|
274
|
-
|
|
275
|
-
TODO: is this weird-ness acceptable? Is it worth updating internal use to _prefix?
|
|
276
300
|
"""
|
|
277
301
|
|
|
278
302
|
state = state or context.state
|
|
@@ -282,52 +306,40 @@ def pop_global_arguments(
|
|
|
282
306
|
if context.ctx_config.isset():
|
|
283
307
|
config = context.config
|
|
284
308
|
|
|
285
|
-
meta_kwargs = host.current_deploy_kwargs or {}
|
|
309
|
+
meta_kwargs: dict[str, Any] = host.current_deploy_kwargs or {} # type: ignore[assignment]
|
|
286
310
|
|
|
287
|
-
|
|
288
|
-
found_keys = []
|
|
311
|
+
arguments: dict[str, Any] = {}
|
|
312
|
+
found_keys: list[str] = []
|
|
289
313
|
|
|
290
|
-
for key,
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
if keys_to_check and internal_key not in keys_to_check:
|
|
314
|
+
for key, type_ in all_global_arguments():
|
|
315
|
+
if keys_to_check and key not in keys_to_check:
|
|
294
316
|
continue
|
|
295
317
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if isinstance(argument, dict):
|
|
300
|
-
handler = argument.get("handler")
|
|
301
|
-
default = argument.get("default")
|
|
302
|
-
if default:
|
|
303
|
-
default = default(config)
|
|
318
|
+
argument_meta = all_argument_meta[key]
|
|
319
|
+
handler = argument_meta.handler
|
|
320
|
+
default: Any = argument_meta.default(config)
|
|
304
321
|
|
|
305
|
-
host_default
|
|
306
|
-
|
|
307
|
-
# TODO: remove this additional check in v3
|
|
308
|
-
if host_default is sentinel and internal_key != key:
|
|
309
|
-
host_default = getattr(host.data, internal_key, sentinel)
|
|
310
|
-
if host_default is not sentinel:
|
|
311
|
-
show_legacy_argument_host_data_warning(internal_key)
|
|
312
|
-
|
|
313
|
-
if host_default is not sentinel:
|
|
322
|
+
host_default = getattr(host.data, key, default_sentinel)
|
|
323
|
+
if host_default is not default_sentinel:
|
|
314
324
|
default = host_default
|
|
315
325
|
|
|
316
326
|
if key in kwargs:
|
|
317
|
-
found_keys.append(
|
|
327
|
+
found_keys.append(key)
|
|
318
328
|
value = kwargs.pop(key)
|
|
319
|
-
|
|
320
|
-
# TODO: remove this additional check in v3
|
|
321
|
-
elif internal_key in kwargs:
|
|
322
|
-
show_legacy_argument_warning(internal_key, get_call_location(frame_offset=2))
|
|
323
|
-
found_keys.append(internal_key)
|
|
324
|
-
value = kwargs.pop(internal_key)
|
|
325
|
-
|
|
326
329
|
else:
|
|
327
|
-
value = meta_kwargs.get(
|
|
330
|
+
value = meta_kwargs.get(key, default)
|
|
328
331
|
|
|
329
332
|
if handler:
|
|
330
333
|
value = handler(config, value)
|
|
331
334
|
|
|
332
|
-
|
|
333
|
-
|
|
335
|
+
if value != default:
|
|
336
|
+
raise_if_bad_type(
|
|
337
|
+
value,
|
|
338
|
+
type_,
|
|
339
|
+
ArgumentTypeError,
|
|
340
|
+
f"Invalid argument `{key}`:",
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# TODO: why is type failing here?
|
|
344
|
+
arguments[key] = value # type: ignore
|
|
345
|
+
return cast(AllArguments, arguments), found_keys
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import (
|
|
4
|
+
TYPE_CHECKING,
|
|
5
|
+
Callable,
|
|
6
|
+
Generator,
|
|
7
|
+
Generic,
|
|
8
|
+
Iterable,
|
|
9
|
+
List,
|
|
10
|
+
Mapping,
|
|
11
|
+
Optional,
|
|
12
|
+
Union,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from typing_extensions import ParamSpec, Protocol
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from pyinfra.api.operation import OperationMeta
|
|
19
|
+
|
|
20
|
+
P = ParamSpec("P")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Unfortunately we have to re-type out all of the global arguments here because
|
|
24
|
+
# Python typing doesn't (yet) support merging kwargs. This acts as the operation
|
|
25
|
+
# decorator output function which merges actual operation args/kwargs in paramspec
|
|
26
|
+
# with the global arguments available in all operations.
|
|
27
|
+
# The nature of "global arguments" is somewhat opposed to static typing as it's
|
|
28
|
+
# indirect and somewhat magic, but we are where we are.
|
|
29
|
+
class PyinfraOperation(Generic[P], Protocol):
|
|
30
|
+
_inner: Callable[P, Generator]
|
|
31
|
+
|
|
32
|
+
def __call__(
|
|
33
|
+
self,
|
|
34
|
+
#
|
|
35
|
+
# op args
|
|
36
|
+
# needs to be first
|
|
37
|
+
#
|
|
38
|
+
*args: P.args,
|
|
39
|
+
#
|
|
40
|
+
# ConnectorArguments
|
|
41
|
+
#
|
|
42
|
+
# Auth
|
|
43
|
+
_sudo: bool = False,
|
|
44
|
+
_sudo_user: Optional[str] = None,
|
|
45
|
+
_use_sudo_login: bool = False,
|
|
46
|
+
_sudo_password: Optional[str] = None,
|
|
47
|
+
_preserve_sudo_env: bool = False,
|
|
48
|
+
_su_user: Optional[str] = None,
|
|
49
|
+
_use_su_login: bool = False,
|
|
50
|
+
_preserve_su_env: bool = False,
|
|
51
|
+
_su_shell: Optional[str] = None,
|
|
52
|
+
_doas: bool = False,
|
|
53
|
+
_doas_user: Optional[str] = None,
|
|
54
|
+
# Shell arguments
|
|
55
|
+
_shell_executable: Optional[str] = None,
|
|
56
|
+
_chdir: Optional[str] = None,
|
|
57
|
+
_env: Optional[Mapping[str, str]] = None,
|
|
58
|
+
# Connector control
|
|
59
|
+
_success_exit_codes: Iterable[int] = (0,),
|
|
60
|
+
_timeout: Optional[int] = None,
|
|
61
|
+
_get_pty: bool = False,
|
|
62
|
+
_stdin: Union[None, str, list[str], tuple[str, ...]] = None,
|
|
63
|
+
#
|
|
64
|
+
# MetaArguments
|
|
65
|
+
#
|
|
66
|
+
name: Optional[str] = None,
|
|
67
|
+
_ignore_errors: bool = False,
|
|
68
|
+
_continue_on_error: bool = False,
|
|
69
|
+
_if: Union[List[Callable[[], bool]], Callable[[], bool], None] = None,
|
|
70
|
+
#
|
|
71
|
+
# ExecutionArguments
|
|
72
|
+
#
|
|
73
|
+
_parallel: Optional[int] = None,
|
|
74
|
+
_run_once: bool = False,
|
|
75
|
+
_serial: bool = False,
|
|
76
|
+
#
|
|
77
|
+
# op kwargs
|
|
78
|
+
#
|
|
79
|
+
**kwargs: P.kwargs,
|
|
80
|
+
) -> "OperationMeta": ...
|