pyinfra 3.0b1__py2.py3-none-any.whl → 3.0b3__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/arguments.py +9 -3
- pyinfra/api/arguments_typed.py +8 -5
- pyinfra/api/command.py +5 -3
- pyinfra/api/config.py +115 -13
- pyinfra/api/connectors.py +5 -2
- pyinfra/api/facts.py +33 -32
- pyinfra/api/host.py +5 -5
- pyinfra/api/inventory.py +4 -0
- pyinfra/api/operation.py +22 -14
- pyinfra/api/util.py +24 -16
- pyinfra/connectors/base.py +3 -6
- pyinfra/connectors/docker.py +2 -9
- pyinfra/connectors/local.py +2 -2
- pyinfra/connectors/ssh.py +2 -2
- pyinfra/connectors/util.py +6 -7
- pyinfra/connectors/vagrant.py +5 -5
- pyinfra/context.py +1 -0
- 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 +18 -0
- pyinfra/facts/files.py +2 -0
- pyinfra/facts/gem.py +2 -0
- pyinfra/facts/gpg.py +2 -0
- pyinfra/facts/hardware.py +30 -22
- pyinfra/facts/launchd.py +2 -0
- pyinfra/facts/lxd.py +2 -0
- pyinfra/facts/mysql.py +12 -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 +6 -6
- pyinfra/facts/postgresql.py +2 -0
- pyinfra/facts/rpm.py +12 -9
- pyinfra/facts/runit.py +68 -0
- pyinfra/facts/server.py +10 -13
- pyinfra/facts/snap.py +2 -0
- pyinfra/facts/systemd.py +2 -0
- pyinfra/facts/upstart.py +2 -0
- pyinfra/facts/util/packaging.py +3 -2
- 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/operations/apk.py +3 -1
- pyinfra/operations/apt.py +16 -18
- pyinfra/operations/brew.py +10 -8
- pyinfra/operations/bsdinit.py +5 -3
- pyinfra/operations/cargo.py +3 -1
- pyinfra/operations/choco.py +3 -1
- pyinfra/operations/dnf.py +15 -19
- pyinfra/operations/docker.py +339 -0
- pyinfra/operations/files.py +81 -66
- pyinfra/operations/gem.py +3 -1
- pyinfra/operations/git.py +18 -16
- pyinfra/operations/iptables.py +27 -25
- pyinfra/operations/launchd.py +5 -6
- pyinfra/operations/lxd.py +7 -4
- pyinfra/operations/mysql.py +57 -53
- pyinfra/operations/npm.py +8 -1
- pyinfra/operations/openrc.py +5 -3
- pyinfra/operations/pacman.py +4 -5
- pyinfra/operations/pip.py +11 -9
- pyinfra/operations/pkg.py +3 -1
- pyinfra/operations/pkgin.py +3 -1
- pyinfra/operations/postgres.py +39 -37
- pyinfra/operations/postgresql.py +2 -0
- pyinfra/operations/puppet.py +3 -1
- pyinfra/operations/python.py +7 -3
- pyinfra/operations/runit.py +182 -0
- pyinfra/operations/selinux.py +42 -16
- pyinfra/operations/server.py +52 -43
- pyinfra/operations/snap.py +3 -1
- pyinfra/operations/ssh.py +12 -10
- pyinfra/operations/systemd.py +12 -8
- pyinfra/operations/sysvinit.py +6 -4
- pyinfra/operations/upstart.py +5 -3
- pyinfra/operations/util/docker.py +177 -0
- pyinfra/operations/util/files.py +24 -16
- pyinfra/operations/util/packaging.py +53 -37
- pyinfra/operations/util/service.py +25 -18
- pyinfra/operations/vzctl.py +12 -10
- pyinfra/operations/xbps.py +3 -1
- pyinfra/operations/yum.py +14 -18
- pyinfra/operations/zypper.py +8 -9
- pyinfra/version.py +5 -2
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/METADATA +30 -28
- pyinfra-3.0b3.dist-info/RECORD +167 -0
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/WHEEL +1 -1
- pyinfra_cli/exceptions.py +0 -5
- pyinfra_cli/inventory.py +38 -19
- pyinfra_cli/prints.py +15 -11
- pyinfra_cli/util.py +3 -1
- tests/test_api/test_api_operations.py +1 -1
- tests/test_connectors/test_ssh.py +66 -13
- tests/test_connectors/test_vagrant.py +3 -3
- pyinfra-3.0b1.dist-info/RECORD +0 -163
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/LICENSE.md +0 -0
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/entry_points.txt +0 -0
- {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/top_level.txt +0 -0
pyinfra/operations/systemd.py
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Manage systemd services.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import shlex
|
|
8
|
+
|
|
5
9
|
from pyinfra import host
|
|
6
10
|
from pyinfra.api import StringCommand, operation
|
|
7
11
|
from pyinfra.facts.systemd import SystemdEnabled, SystemdStatus, _make_systemctl_cmd
|
|
@@ -10,7 +14,7 @@ from .util.service import handle_service_control
|
|
|
10
14
|
|
|
11
15
|
|
|
12
16
|
@operation(is_idempotent=False)
|
|
13
|
-
def daemon_reload(user_mode=False, machine=None, user_name=None):
|
|
17
|
+
def daemon_reload(user_mode=False, machine: str | None = None, user_name: str | None = None):
|
|
14
18
|
"""
|
|
15
19
|
Reload the systemd daemon to read unit file changes.
|
|
16
20
|
|
|
@@ -33,16 +37,16 @@ _daemon_reload = daemon_reload._inner # noqa: E305
|
|
|
33
37
|
|
|
34
38
|
@operation()
|
|
35
39
|
def service(
|
|
36
|
-
service,
|
|
40
|
+
service: str,
|
|
37
41
|
running=True,
|
|
38
42
|
restarted=False,
|
|
39
43
|
reloaded=False,
|
|
40
|
-
command=None,
|
|
41
|
-
enabled=None,
|
|
44
|
+
command: str | None = None,
|
|
45
|
+
enabled: bool | None = None,
|
|
42
46
|
daemon_reload=False,
|
|
43
47
|
user_mode=False,
|
|
44
|
-
machine=None,
|
|
45
|
-
user_name=None,
|
|
48
|
+
machine: str | None = None,
|
|
49
|
+
user_name: str | None = None,
|
|
46
50
|
):
|
|
47
51
|
"""
|
|
48
52
|
Manage the state of systemd managed units.
|
|
@@ -138,8 +142,8 @@ def service(
|
|
|
138
142
|
|
|
139
143
|
# Isn't enabled and want enabled?
|
|
140
144
|
if not is_enabled and enabled is True:
|
|
141
|
-
yield "{0} enable {1}".format(systemctl_cmd, service)
|
|
145
|
+
yield "{0} enable {1}".format(systemctl_cmd, shlex.quote(service))
|
|
142
146
|
|
|
143
147
|
# Is enabled and want disabled?
|
|
144
148
|
elif is_enabled and enabled is False:
|
|
145
|
-
yield "{0} disable {1}".format(systemctl_cmd, service)
|
|
149
|
+
yield "{0} disable {1}".format(systemctl_cmd, shlex.quote(service))
|
pyinfra/operations/sysvinit.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Manage sysvinit services (``/etc/init.d``).
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
from pyinfra import host
|
|
6
8
|
from pyinfra.api import operation
|
|
7
9
|
from pyinfra.facts.files import FindLinks
|
|
@@ -14,12 +16,12 @@ from .util.service import handle_service_control
|
|
|
14
16
|
|
|
15
17
|
@operation()
|
|
16
18
|
def service(
|
|
17
|
-
service,
|
|
19
|
+
service: str,
|
|
18
20
|
running=True,
|
|
19
21
|
restarted=False,
|
|
20
22
|
reloaded=False,
|
|
21
|
-
enabled=None,
|
|
22
|
-
command=None,
|
|
23
|
+
enabled: bool | None = None,
|
|
24
|
+
command: str | None = None,
|
|
23
25
|
):
|
|
24
26
|
"""
|
|
25
27
|
Manage the state of SysV Init (/etc/init.d) services.
|
|
@@ -95,7 +97,7 @@ def service(
|
|
|
95
97
|
|
|
96
98
|
@operation()
|
|
97
99
|
def enable(
|
|
98
|
-
service,
|
|
100
|
+
service: str,
|
|
99
101
|
start_priority=20,
|
|
100
102
|
stop_priority=80,
|
|
101
103
|
start_levels=(2, 3, 4, 5),
|
pyinfra/operations/upstart.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Manage upstart services.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
from io import StringIO
|
|
6
8
|
|
|
7
9
|
from pyinfra import host
|
|
@@ -14,12 +16,12 @@ from .util.service import handle_service_control
|
|
|
14
16
|
|
|
15
17
|
@operation()
|
|
16
18
|
def service(
|
|
17
|
-
service,
|
|
19
|
+
service: str,
|
|
18
20
|
running=True,
|
|
19
21
|
restarted=False,
|
|
20
22
|
reloaded=False,
|
|
21
|
-
command=None,
|
|
22
|
-
enabled=None,
|
|
23
|
+
command: str | None = None,
|
|
24
|
+
enabled: bool | None = None,
|
|
23
25
|
):
|
|
24
26
|
"""
|
|
25
27
|
Manage the state of upstart managed services.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
from pyinfra.api import OperationError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _create_container(**kwargs):
|
|
5
|
+
command = []
|
|
6
|
+
|
|
7
|
+
networks = kwargs["networks"] if kwargs["networks"] else []
|
|
8
|
+
ports = kwargs["ports"] if kwargs["ports"] else []
|
|
9
|
+
volumes = kwargs["volumes"] if kwargs["volumes"] else []
|
|
10
|
+
env_vars = kwargs["env_vars"] if kwargs["env_vars"] else []
|
|
11
|
+
|
|
12
|
+
if kwargs["image"] == "":
|
|
13
|
+
raise OperationError("missing 1 required argument: 'image'")
|
|
14
|
+
|
|
15
|
+
command.append("docker container create --name {0}".format(kwargs["container"]))
|
|
16
|
+
|
|
17
|
+
for network in networks:
|
|
18
|
+
command.append("--network {0}".format(network))
|
|
19
|
+
|
|
20
|
+
for port in ports:
|
|
21
|
+
command.append("-p {0}".format(port))
|
|
22
|
+
|
|
23
|
+
for volume in volumes:
|
|
24
|
+
command.append("-v {0}".format(volume))
|
|
25
|
+
|
|
26
|
+
for env_var in env_vars:
|
|
27
|
+
command.append("-e {0}".format(env_var))
|
|
28
|
+
|
|
29
|
+
if kwargs["pull_always"]:
|
|
30
|
+
command.append("--pull always")
|
|
31
|
+
|
|
32
|
+
command.append(kwargs["image"])
|
|
33
|
+
|
|
34
|
+
if kwargs["start"]:
|
|
35
|
+
command.append("; {0}".format(_start_container(container=kwargs["container"])))
|
|
36
|
+
|
|
37
|
+
return " ".join(command)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _remove_container(**kwargs):
|
|
41
|
+
return "docker container rm -f {0}".format(kwargs["container"])
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _start_container(**kwargs):
|
|
45
|
+
return "docker container start {0}".format(kwargs["container"])
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _stop_container(**kwargs):
|
|
49
|
+
return "docker container stop {0}".format(kwargs["container"])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _pull_image(**kwargs):
|
|
53
|
+
return "docker image pull {0}".format(kwargs["image"])
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _remove_image(**kwargs):
|
|
57
|
+
return "docker image rm {0}".format(kwargs["image"])
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _prune_command(**kwargs):
|
|
61
|
+
command = ["docker system prune"]
|
|
62
|
+
|
|
63
|
+
if kwargs["all"]:
|
|
64
|
+
command.append("-a")
|
|
65
|
+
|
|
66
|
+
if kwargs["filter"] != "":
|
|
67
|
+
command.append("--filter={0}".format(kwargs["filter"]))
|
|
68
|
+
|
|
69
|
+
if kwargs["volumes"]:
|
|
70
|
+
command.append("--volumes")
|
|
71
|
+
|
|
72
|
+
command.append("-f")
|
|
73
|
+
|
|
74
|
+
return " ".join(command)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _create_volume(**kwargs):
|
|
78
|
+
command = []
|
|
79
|
+
labels = kwargs["labels"] if kwargs["labels"] else []
|
|
80
|
+
|
|
81
|
+
command.append("docker volume create {0}".format(kwargs["volume"]))
|
|
82
|
+
|
|
83
|
+
if kwargs["driver"] != "":
|
|
84
|
+
command.append("-d {0}".format(kwargs["driver"]))
|
|
85
|
+
|
|
86
|
+
for label in labels:
|
|
87
|
+
command.append("--label {0}".format(label))
|
|
88
|
+
|
|
89
|
+
return " ".join(command)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _remove_volume(**kwargs):
|
|
93
|
+
return "docker image rm {0}".format(kwargs["volume"])
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _create_network(**kwargs):
|
|
97
|
+
command = []
|
|
98
|
+
opts = kwargs["opts"] if kwargs["opts"] else []
|
|
99
|
+
ipam_opts = kwargs["ipam_opts"] if kwargs["ipam_opts"] else []
|
|
100
|
+
labels = kwargs["labels"] if kwargs["labels"] else []
|
|
101
|
+
|
|
102
|
+
command.append("docker network create {0}".format(kwargs["network"]))
|
|
103
|
+
if kwargs["driver"] != "":
|
|
104
|
+
command.append("-d {0}".format(kwargs["driver"]))
|
|
105
|
+
|
|
106
|
+
if kwargs["gateway"] != "":
|
|
107
|
+
command.append("--gateway {0}".format(kwargs["gateway"]))
|
|
108
|
+
|
|
109
|
+
if kwargs["ip_range"] != "":
|
|
110
|
+
command.append("--ip-range {0}".format(kwargs["ip_range"]))
|
|
111
|
+
|
|
112
|
+
if kwargs["ipam_driver"] != "":
|
|
113
|
+
command.append("--ipam-driver {0}".format(kwargs["ipam_driver"]))
|
|
114
|
+
|
|
115
|
+
if kwargs["subnet"] != "":
|
|
116
|
+
command.append("--subnet {0}".format(kwargs["subnet"]))
|
|
117
|
+
|
|
118
|
+
if kwargs["scope"] != "":
|
|
119
|
+
command.append("--scope {0}".format(kwargs["scope"]))
|
|
120
|
+
|
|
121
|
+
if kwargs["ingress"]:
|
|
122
|
+
command.append("--ingress")
|
|
123
|
+
|
|
124
|
+
if kwargs["attachable"]:
|
|
125
|
+
command.append("--attachable")
|
|
126
|
+
|
|
127
|
+
for opt in opts:
|
|
128
|
+
command.append("--opt {0}".format(opt))
|
|
129
|
+
|
|
130
|
+
for opt in ipam_opts:
|
|
131
|
+
command.append("--ipam-opt {0}".format(opt))
|
|
132
|
+
|
|
133
|
+
for label in labels:
|
|
134
|
+
command.append("--label {0}".format(label))
|
|
135
|
+
return " ".join(command)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _remove_network(**kwargs):
|
|
139
|
+
return "docker network rm {0}".format(kwargs["network"])
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def handle_docker(resource, command, **kwargs):
|
|
143
|
+
container_commands = {
|
|
144
|
+
"create": _create_container,
|
|
145
|
+
"remove": _remove_container,
|
|
146
|
+
"start": _start_container,
|
|
147
|
+
"stop": _stop_container,
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
image_commands = {
|
|
151
|
+
"pull": _pull_image,
|
|
152
|
+
"remove": _remove_image,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
volume_commands = {
|
|
156
|
+
"create": _create_volume,
|
|
157
|
+
"remove": _remove_volume,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
network_commands = {
|
|
161
|
+
"create": _create_network,
|
|
162
|
+
"remove": _remove_network,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
system_commands = {
|
|
166
|
+
"prune": _prune_command,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
docker_commands = {
|
|
170
|
+
"container": container_commands,
|
|
171
|
+
"image": image_commands,
|
|
172
|
+
"volume": volume_commands,
|
|
173
|
+
"network": network_commands,
|
|
174
|
+
"system": system_commands,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return docker_commands[resource][command](**kwargs)
|
pyinfra/operations/util/files.py
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
from datetime import datetime
|
|
3
5
|
|
|
4
6
|
from pyinfra.api import QuoteString, StringCommand
|
|
5
7
|
|
|
6
8
|
|
|
7
|
-
def unix_path_join(*
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
return "/".join(
|
|
9
|
+
def unix_path_join(*parts) -> str:
|
|
10
|
+
part_list = list(parts)
|
|
11
|
+
part_list[0:-1] = [part.rstrip("/") for part in part_list[0:-1]]
|
|
12
|
+
return "/".join(part_list)
|
|
11
13
|
|
|
12
14
|
|
|
13
|
-
def ensure_mode_int(mode):
|
|
15
|
+
def ensure_mode_int(mode: str | int | None) -> int | str | None:
|
|
14
16
|
# Already an int (/None)?
|
|
15
17
|
if isinstance(mode, int) or mode is None:
|
|
16
18
|
return mode
|
|
@@ -26,19 +28,19 @@ def ensure_mode_int(mode):
|
|
|
26
28
|
return mode
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
def get_timestamp():
|
|
31
|
+
def get_timestamp() -> str:
|
|
30
32
|
return datetime.now().strftime("%y%m%d%H%M")
|
|
31
33
|
|
|
32
34
|
|
|
33
35
|
def sed_replace(
|
|
34
|
-
filename,
|
|
35
|
-
line,
|
|
36
|
-
replace,
|
|
37
|
-
flags=None,
|
|
36
|
+
filename: str,
|
|
37
|
+
line: str,
|
|
38
|
+
replace: str,
|
|
39
|
+
flags: list[str] | None = None,
|
|
38
40
|
backup=False,
|
|
39
41
|
interpolate_variables=False,
|
|
40
|
-
):
|
|
41
|
-
|
|
42
|
+
) -> StringCommand:
|
|
43
|
+
flags_str = "".join(flags) if flags else ""
|
|
42
44
|
|
|
43
45
|
line = line.replace("/", r"\/")
|
|
44
46
|
replace = str(replace)
|
|
@@ -57,7 +59,7 @@ def sed_replace(
|
|
|
57
59
|
replace = replace.replace("'", "'\"'\"'")
|
|
58
60
|
sed_script_formatter = "'s/{0}/{1}/{2}'"
|
|
59
61
|
|
|
60
|
-
sed_script = sed_script_formatter.format(line, replace,
|
|
62
|
+
sed_script = sed_script_formatter.format(line, replace, flags_str)
|
|
61
63
|
|
|
62
64
|
sed_command = StringCommand(
|
|
63
65
|
"sed",
|
|
@@ -73,7 +75,7 @@ def sed_replace(
|
|
|
73
75
|
return sed_command
|
|
74
76
|
|
|
75
77
|
|
|
76
|
-
def chmod(target, mode, recursive=False):
|
|
78
|
+
def chmod(target: str, mode: str | int, recursive=False) -> StringCommand:
|
|
77
79
|
args = ["chmod"]
|
|
78
80
|
if recursive:
|
|
79
81
|
args.append("-R")
|
|
@@ -83,7 +85,13 @@ def chmod(target, mode, recursive=False):
|
|
|
83
85
|
return StringCommand(" ".join(args), QuoteString(target))
|
|
84
86
|
|
|
85
87
|
|
|
86
|
-
def chown(
|
|
88
|
+
def chown(
|
|
89
|
+
target: str,
|
|
90
|
+
user: str | None = None,
|
|
91
|
+
group: str | None = None,
|
|
92
|
+
recursive=False,
|
|
93
|
+
dereference=True,
|
|
94
|
+
) -> StringCommand:
|
|
87
95
|
command = "chown"
|
|
88
96
|
user_group = None
|
|
89
97
|
|
|
@@ -107,7 +115,7 @@ def chown(target, user, group=None, recursive=False, dereference=True):
|
|
|
107
115
|
return StringCommand(" ".join(args), user_group, QuoteString(target))
|
|
108
116
|
|
|
109
117
|
|
|
110
|
-
def adjust_regex(line, escape_regex_characters):
|
|
118
|
+
def adjust_regex(line: str, escape_regex_characters: bool) -> str:
|
|
111
119
|
"""
|
|
112
120
|
Ensure the regex starts with '^' and ends with '$' and escape regex characters if requested
|
|
113
121
|
"""
|
|
@@ -1,19 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import shlex
|
|
2
4
|
from collections import defaultdict
|
|
3
5
|
from io import StringIO
|
|
6
|
+
from typing import Callable
|
|
4
7
|
from urllib.parse import urlparse
|
|
5
8
|
|
|
9
|
+
from pyinfra.api import Host, State
|
|
6
10
|
from pyinfra.facts.files import File
|
|
7
11
|
from pyinfra.facts.rpm import RpmPackage
|
|
12
|
+
from pyinfra.operations import files
|
|
8
13
|
|
|
9
14
|
|
|
10
|
-
def _package_name(package):
|
|
15
|
+
def _package_name(package: list[str] | str) -> str:
|
|
11
16
|
if isinstance(package, list):
|
|
12
17
|
return package[0]
|
|
13
18
|
return package
|
|
14
19
|
|
|
15
20
|
|
|
16
|
-
def _has_package(
|
|
21
|
+
def _has_package(
|
|
22
|
+
package: str | list[str],
|
|
23
|
+
packages: dict[str, set[str]],
|
|
24
|
+
expand_package_fact: Callable[[str], list[str | list[str]]] | None = None,
|
|
25
|
+
match_any=False,
|
|
26
|
+
) -> tuple[bool, dict]:
|
|
17
27
|
def in_packages(pkg_name, pkg_versions):
|
|
18
28
|
if not pkg_versions:
|
|
19
29
|
return pkg_name in packages
|
|
@@ -21,9 +31,12 @@ def _has_package(package, packages, expand_package_fact=None, match_any=False):
|
|
|
21
31
|
version in packages[pkg_name] for version in pkg_versions
|
|
22
32
|
)
|
|
23
33
|
|
|
24
|
-
packages_to_check = [package]
|
|
34
|
+
packages_to_check: list[str | list[str]] = [package]
|
|
25
35
|
if expand_package_fact:
|
|
26
|
-
|
|
36
|
+
if isinstance(package, list):
|
|
37
|
+
packages_to_check = expand_package_fact(package[0]) or packages_to_check
|
|
38
|
+
else:
|
|
39
|
+
packages_to_check = expand_package_fact(package) or packages_to_check
|
|
27
40
|
|
|
28
41
|
package_name_to_versions = defaultdict(set)
|
|
29
42
|
for pkg in packages_to_check:
|
|
@@ -43,16 +56,16 @@ def _has_package(package, packages, expand_package_fact=None, match_any=False):
|
|
|
43
56
|
|
|
44
57
|
|
|
45
58
|
def ensure_packages(
|
|
46
|
-
host,
|
|
47
|
-
|
|
48
|
-
current_packages,
|
|
49
|
-
present,
|
|
50
|
-
install_command,
|
|
51
|
-
uninstall_command,
|
|
59
|
+
host: Host,
|
|
60
|
+
packages_to_ensure: str | list[str] | None,
|
|
61
|
+
current_packages: dict[str, set[str]],
|
|
62
|
+
present: bool,
|
|
63
|
+
install_command: str,
|
|
64
|
+
uninstall_command: str,
|
|
52
65
|
latest=False,
|
|
53
|
-
upgrade_command=None,
|
|
54
|
-
version_join=None,
|
|
55
|
-
expand_package_fact=None,
|
|
66
|
+
upgrade_command: str | None = None,
|
|
67
|
+
version_join: str | None = None,
|
|
68
|
+
expand_package_fact: Callable[[str], list[str | list[str]]] | None = None,
|
|
56
69
|
):
|
|
57
70
|
"""
|
|
58
71
|
Handles this common scenario:
|
|
@@ -64,7 +77,7 @@ def ensure_packages(
|
|
|
64
77
|
+ Optionally upgrades packages w/o specified version when present
|
|
65
78
|
|
|
66
79
|
Args:
|
|
67
|
-
|
|
80
|
+
packages_to_ensure (list): list of packages or package/versions
|
|
68
81
|
current_packages (fact): fact returning dict of package names -> version
|
|
69
82
|
present (bool): whether packages should exist or not
|
|
70
83
|
install_command (str): command to prefix to list of packages to install
|
|
@@ -73,18 +86,21 @@ def ensure_packages(
|
|
|
73
86
|
upgrade_command (str): as above for upgrading
|
|
74
87
|
version_join (str): the package manager specific "joiner", ie ``=`` for \
|
|
75
88
|
``<apt_pkg>=<version>``
|
|
89
|
+
expand_package_fact: fact returning packages providing a capability \
|
|
90
|
+
(ie ``yum whatprovides``)
|
|
76
91
|
"""
|
|
77
92
|
|
|
78
|
-
if
|
|
93
|
+
if packages_to_ensure is None:
|
|
79
94
|
return
|
|
95
|
+
if isinstance(packages_to_ensure, str):
|
|
96
|
+
packages_to_ensure = [packages_to_ensure]
|
|
80
97
|
|
|
81
|
-
|
|
82
|
-
packages = [packages]
|
|
98
|
+
packages: list[str | list[str]] = packages_to_ensure # type: ignore[assignment]
|
|
83
99
|
|
|
84
100
|
if version_join:
|
|
85
101
|
packages = [
|
|
86
102
|
package[0] if len(package) == 1 else package
|
|
87
|
-
for package in [package.rsplit(version_join, 1) for package in packages]
|
|
103
|
+
for package in [package.rsplit(version_join, 1) for package in packages] # type: ignore[union-attr] # noqa
|
|
88
104
|
]
|
|
89
105
|
|
|
90
106
|
diff_packages = []
|
|
@@ -140,7 +156,7 @@ def ensure_packages(
|
|
|
140
156
|
command = install_command if present else uninstall_command
|
|
141
157
|
|
|
142
158
|
joined_packages = [
|
|
143
|
-
version_join.join(package) if isinstance(package, list) else package
|
|
159
|
+
version_join.join(package) if isinstance(package, list) else package # type: ignore[union-attr] # noqa
|
|
144
160
|
for package in diff_packages
|
|
145
161
|
]
|
|
146
162
|
|
|
@@ -156,7 +172,7 @@ def ensure_packages(
|
|
|
156
172
|
)
|
|
157
173
|
|
|
158
174
|
|
|
159
|
-
def ensure_rpm(state, host
|
|
175
|
+
def ensure_rpm(state: State, host: Host, source: str, present: bool, package_manager_command: str):
|
|
160
176
|
original_source = source
|
|
161
177
|
|
|
162
178
|
# If source is a url
|
|
@@ -165,18 +181,18 @@ def ensure_rpm(state, host, files, source, present, package_manager_command):
|
|
|
165
181
|
temp_filename = "{0}.rpm".format(host.get_temp_filename(source))
|
|
166
182
|
|
|
167
183
|
# Ensure it's downloaded
|
|
168
|
-
yield from files.download._inner(source, temp_filename)
|
|
184
|
+
yield from files.download._inner(src=source, dest=temp_filename)
|
|
169
185
|
|
|
170
186
|
# Override the source with the downloaded file
|
|
171
187
|
source = temp_filename
|
|
172
188
|
|
|
173
189
|
# Check for file .rpm information
|
|
174
|
-
info = host.get_fact(RpmPackage,
|
|
190
|
+
info = host.get_fact(RpmPackage, package=source)
|
|
175
191
|
exists = False
|
|
176
192
|
|
|
177
193
|
# We have info!
|
|
178
194
|
if info:
|
|
179
|
-
current_package = host.get_fact(RpmPackage,
|
|
195
|
+
current_package = host.get_fact(RpmPackage, package=info["name"])
|
|
180
196
|
if current_package and current_package["version"] == info["version"]:
|
|
181
197
|
exists = True
|
|
182
198
|
|
|
@@ -204,18 +220,16 @@ def ensure_rpm(state, host, files, source, present, package_manager_command):
|
|
|
204
220
|
|
|
205
221
|
|
|
206
222
|
def ensure_yum_repo(
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
gpgcheck,
|
|
216
|
-
gpgkey,
|
|
223
|
+
host: Host,
|
|
224
|
+
name_or_url: str,
|
|
225
|
+
baseurl: str | None,
|
|
226
|
+
present: bool,
|
|
227
|
+
description: str | None,
|
|
228
|
+
enabled: bool,
|
|
229
|
+
gpgcheck: bool,
|
|
230
|
+
gpgkey: str | None,
|
|
217
231
|
repo_directory="/etc/yum.repos.d/",
|
|
218
|
-
type_=None,
|
|
232
|
+
type_: str | None = None,
|
|
219
233
|
):
|
|
220
234
|
url = None
|
|
221
235
|
url_parts = urlparse(name_or_url)
|
|
@@ -229,15 +243,17 @@ def ensure_yum_repo(
|
|
|
229
243
|
|
|
230
244
|
# If we don't want the repo, just remove any existing file
|
|
231
245
|
if not present:
|
|
232
|
-
yield from files.file._inner(filename, present=False)
|
|
246
|
+
yield from files.file._inner(path=filename, present=False)
|
|
233
247
|
return
|
|
234
248
|
|
|
235
249
|
# If we're a URL, download the repo if it doesn't exist
|
|
236
250
|
if url:
|
|
237
251
|
if not host.get_fact(File, path=filename):
|
|
238
|
-
yield from files.download._inner(url, filename)
|
|
252
|
+
yield from files.download._inner(src=url, dest=filename)
|
|
239
253
|
return
|
|
240
254
|
|
|
255
|
+
assert isinstance(baseurl, str)
|
|
256
|
+
|
|
241
257
|
# Description defaults to name
|
|
242
258
|
description = description or name_or_url
|
|
243
259
|
|
|
@@ -261,4 +277,4 @@ def ensure_yum_repo(
|
|
|
261
277
|
repo_file = StringIO(repo)
|
|
262
278
|
|
|
263
279
|
# Ensure this is the file on the server
|
|
264
|
-
yield from files.put._inner(repo_file, filename)
|
|
280
|
+
yield from files.put._inner(src=repo_file, dest=filename)
|
|
@@ -1,39 +1,46 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import shlex
|
|
4
|
+
|
|
5
|
+
from pyinfra.api import Host
|
|
6
|
+
|
|
7
|
+
|
|
1
8
|
def handle_service_control(
|
|
2
|
-
host,
|
|
3
|
-
name,
|
|
4
|
-
statuses,
|
|
5
|
-
formatter,
|
|
6
|
-
running,
|
|
7
|
-
restarted,
|
|
8
|
-
reloaded,
|
|
9
|
-
command,
|
|
9
|
+
host: Host,
|
|
10
|
+
name: str,
|
|
11
|
+
statuses: dict[str, bool],
|
|
12
|
+
formatter: str,
|
|
13
|
+
running: bool | None = None,
|
|
14
|
+
restarted: bool | None = None,
|
|
15
|
+
reloaded: bool | None = None,
|
|
16
|
+
command: str | None = None,
|
|
10
17
|
status_argument="status",
|
|
11
18
|
):
|
|
12
|
-
|
|
19
|
+
is_running = statuses.get(name, None)
|
|
13
20
|
|
|
14
21
|
# Need down but running
|
|
15
22
|
if running is False:
|
|
16
|
-
if
|
|
17
|
-
yield formatter.format(name, "stop")
|
|
23
|
+
if is_running:
|
|
24
|
+
yield formatter.format(shlex.quote(name), "stop")
|
|
18
25
|
else:
|
|
19
26
|
host.noop("service {0} is stopped".format(name))
|
|
20
27
|
|
|
21
28
|
# Need running but down
|
|
22
29
|
if running is True:
|
|
23
|
-
if not
|
|
24
|
-
yield formatter.format(name, "start")
|
|
30
|
+
if not is_running:
|
|
31
|
+
yield formatter.format(shlex.quote(name), "start")
|
|
25
32
|
else:
|
|
26
33
|
host.noop("service {0} is running".format(name))
|
|
27
34
|
|
|
28
35
|
# Only restart if the service is already running
|
|
29
|
-
if restarted and
|
|
30
|
-
yield formatter.format(name, "restart")
|
|
36
|
+
if restarted and is_running:
|
|
37
|
+
yield formatter.format(shlex.quote(name), "restart")
|
|
31
38
|
|
|
32
39
|
# Only reload if the service is already reloaded
|
|
33
|
-
if reloaded and
|
|
34
|
-
yield formatter.format(name, "reload")
|
|
40
|
+
if reloaded and is_running:
|
|
41
|
+
yield formatter.format(shlex.quote(name), "reload")
|
|
35
42
|
|
|
36
43
|
# Always execute arbitrary commands as these may or may not rely on the service
|
|
37
44
|
# being up or down
|
|
38
45
|
if command:
|
|
39
|
-
yield formatter.format(name, command)
|
|
46
|
+
yield formatter.format(shlex.quote(name), command)
|