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
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage MySQL databases, users and privileges.
|
|
3
|
+
|
|
4
|
+
Requires the ``mysql`` CLI executable on the target host(s).
|
|
5
|
+
|
|
6
|
+
All operations in this module take four optional arguments:
|
|
7
|
+
+ ``mysql_user``: the username to connect to mysql to
|
|
8
|
+
+ ``mysql_password``: the password for the connecting user
|
|
9
|
+
+ ``mysql_host``: the hostname of the server to connect to
|
|
10
|
+
+ ``mysql_port``: the port of the server to connect to
|
|
11
|
+
|
|
12
|
+
See the example/mysql.py
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pyinfra import host
|
|
19
|
+
from pyinfra.api import MaskString, OperationError, QuoteString, StringCommand, operation
|
|
20
|
+
from pyinfra.facts.mysql import (
|
|
21
|
+
MysqlDatabases,
|
|
22
|
+
MysqlUserGrants,
|
|
23
|
+
MysqlUsers,
|
|
24
|
+
make_execute_mysql_command,
|
|
25
|
+
make_mysql_command,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@operation(is_idempotent=False)
|
|
30
|
+
def sql(
|
|
31
|
+
sql: str,
|
|
32
|
+
database: str | None = None,
|
|
33
|
+
# Details for speaking to MySQL via `mysql` CLI
|
|
34
|
+
mysql_user: str | None = None,
|
|
35
|
+
mysql_password: str | None = None,
|
|
36
|
+
mysql_host: str | None = None,
|
|
37
|
+
mysql_port: int | None = None,
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Execute arbitrary SQL against MySQL.
|
|
41
|
+
|
|
42
|
+
+ sql: SQL command(s) to execute
|
|
43
|
+
+ database: optional database to open the connection with
|
|
44
|
+
+ mysql_*: global module arguments, see above
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
yield make_execute_mysql_command(
|
|
48
|
+
sql,
|
|
49
|
+
database=database,
|
|
50
|
+
user=mysql_user,
|
|
51
|
+
password=mysql_password,
|
|
52
|
+
host=mysql_host,
|
|
53
|
+
port=mysql_port,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@operation()
|
|
58
|
+
def user(
|
|
59
|
+
user: str,
|
|
60
|
+
present: bool = True,
|
|
61
|
+
user_hostname: str = "localhost",
|
|
62
|
+
password: str | None = None,
|
|
63
|
+
privileges: str | list[str] | None = None,
|
|
64
|
+
# MySQL REQUIRE SSL/TLS options
|
|
65
|
+
require: str | None = None, # SSL or X509
|
|
66
|
+
require_cipher: str | None = None,
|
|
67
|
+
require_issuer: str | None = None,
|
|
68
|
+
require_subject: str | None = None,
|
|
69
|
+
# MySQL WITH resource limit options
|
|
70
|
+
max_connections: int | None = None,
|
|
71
|
+
max_queries_per_hour: int | None = None,
|
|
72
|
+
max_updates_per_hour: int | None = None,
|
|
73
|
+
max_connections_per_hour: int | None = None,
|
|
74
|
+
# Details for speaking to MySQL via `mysql` CLI via `mysql` CLI
|
|
75
|
+
mysql_user: str | None = None,
|
|
76
|
+
mysql_password: str | None = None,
|
|
77
|
+
mysql_host: str | None = None,
|
|
78
|
+
mysql_port: int | None = None,
|
|
79
|
+
):
|
|
80
|
+
...
|
|
81
|
+
"""
|
|
82
|
+
Add/remove/update MySQL users.
|
|
83
|
+
|
|
84
|
+
+ user: the name of the user
|
|
85
|
+
+ present: whether the user should exist or not
|
|
86
|
+
+ user_hostname: the hostname of the user
|
|
87
|
+
+ password: the password of the user (if created)
|
|
88
|
+
+ privileges: the global privileges for this user
|
|
89
|
+
+ mysql_*: global module arguments, see above
|
|
90
|
+
|
|
91
|
+
Hostname:
|
|
92
|
+
this + ``name`` makes the username - so changing this will create a new
|
|
93
|
+
user, rather than update users with the same ``name``.
|
|
94
|
+
|
|
95
|
+
Password:
|
|
96
|
+
will only be applied if the user does not exist - ie pyinfra cannot
|
|
97
|
+
detect if the current password doesn't match the one provided, so won't
|
|
98
|
+
attempt to change it.
|
|
99
|
+
|
|
100
|
+
**Example:**
|
|
101
|
+
|
|
102
|
+
.. code:: python
|
|
103
|
+
|
|
104
|
+
mysql.user(
|
|
105
|
+
name="Create the pyinfra@localhost MySQL user",
|
|
106
|
+
user="pyinfra",
|
|
107
|
+
password="somepassword",
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Create a user with resource limits
|
|
111
|
+
mysql.user(
|
|
112
|
+
name="Create the pyinfra@localhost MySQL user",
|
|
113
|
+
user="pyinfra",
|
|
114
|
+
max_connections=50,
|
|
115
|
+
max_updates_per_hour=10,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Create a user that requires SSL for connections
|
|
119
|
+
mysql.user(
|
|
120
|
+
name="Create the pyinfra@localhost MySQL user",
|
|
121
|
+
user="pyinfra",
|
|
122
|
+
password="somepassword",
|
|
123
|
+
require="SSL",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# Create a user that requires a specific certificate
|
|
127
|
+
mysql.user(
|
|
128
|
+
name="Create the pyinfra@localhost MySQL user",
|
|
129
|
+
user="pyinfra",
|
|
130
|
+
password="somepassword",
|
|
131
|
+
require="X509",
|
|
132
|
+
require_issuer="/C=SE/ST=Stockholm...",
|
|
133
|
+
require_cipher="EDH-RSA-DES-CBC3-SHA",
|
|
134
|
+
)
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
if require and require not in ("SSL", "X509"):
|
|
138
|
+
raise OperationError('Invalid `require` value, must be: "SSL" or "X509"')
|
|
139
|
+
|
|
140
|
+
if require != "X509":
|
|
141
|
+
if require_cipher:
|
|
142
|
+
raise OperationError('Cannot set `require_cipher` if `require` is not "X509"')
|
|
143
|
+
if require_issuer:
|
|
144
|
+
raise OperationError('Cannot set `require_issuer` if `require` is not "X509"')
|
|
145
|
+
if require_subject:
|
|
146
|
+
raise OperationError('Cannot set `require_subject` if `require` is not "X509"')
|
|
147
|
+
|
|
148
|
+
current_users = host.get_fact(
|
|
149
|
+
MysqlUsers,
|
|
150
|
+
mysql_user=mysql_user,
|
|
151
|
+
mysql_password=mysql_password,
|
|
152
|
+
mysql_host=mysql_host,
|
|
153
|
+
mysql_port=mysql_port,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
user_host = "{0}@{1}".format(user, user_hostname)
|
|
157
|
+
is_present = user_host in current_users
|
|
158
|
+
|
|
159
|
+
if not present:
|
|
160
|
+
if is_present:
|
|
161
|
+
yield make_execute_mysql_command(
|
|
162
|
+
'DROP USER "{0}"@"{1}"'.format(user, user_hostname),
|
|
163
|
+
user=mysql_user,
|
|
164
|
+
password=mysql_password,
|
|
165
|
+
host=mysql_host,
|
|
166
|
+
port=mysql_port,
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
host.noop("mysql user {0}@{1} does not exist".format(user, user_hostname))
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
if present and not is_present:
|
|
173
|
+
sql_bits = ['CREATE USER "{0}"@"{1}"'.format(user, user_hostname)]
|
|
174
|
+
if password:
|
|
175
|
+
sql_bits.append(MaskString('IDENTIFIED BY "{0}"'.format(password)))
|
|
176
|
+
|
|
177
|
+
if require == "SSL":
|
|
178
|
+
sql_bits.append("REQUIRE SSL")
|
|
179
|
+
|
|
180
|
+
if require == "X509":
|
|
181
|
+
sql_bits.append("REQUIRE")
|
|
182
|
+
require_bits = []
|
|
183
|
+
|
|
184
|
+
if require_cipher:
|
|
185
|
+
require_bits.append('CIPHER "{0}"'.format(require_cipher))
|
|
186
|
+
if require_issuer:
|
|
187
|
+
require_bits.append('ISSUER "{0}"'.format(require_issuer))
|
|
188
|
+
if require_subject:
|
|
189
|
+
require_bits.append('SUBJECT "{0}"'.format(require_subject))
|
|
190
|
+
|
|
191
|
+
if not require_bits:
|
|
192
|
+
require_bits.append("X509")
|
|
193
|
+
|
|
194
|
+
sql_bits.extend(require_bits)
|
|
195
|
+
|
|
196
|
+
resource_bits = []
|
|
197
|
+
if max_connections:
|
|
198
|
+
resource_bits.append("MAX_USER_CONNECTIONS {0}".format(max_connections))
|
|
199
|
+
if max_queries_per_hour:
|
|
200
|
+
resource_bits.append("MAX_QUERIES_PER_HOUR {0}".format(max_queries_per_hour))
|
|
201
|
+
if max_updates_per_hour:
|
|
202
|
+
resource_bits.append("MAX_UPDATES_PER_HOUR {0}".format(max_updates_per_hour))
|
|
203
|
+
if max_connections_per_hour:
|
|
204
|
+
resource_bits.append("MAX_CONNECTIONS_PER_HOUR {0}".format(max_connections_per_hour))
|
|
205
|
+
|
|
206
|
+
if resource_bits:
|
|
207
|
+
sql_bits.append("WITH")
|
|
208
|
+
sql_bits.append(" ".join(resource_bits))
|
|
209
|
+
|
|
210
|
+
yield make_execute_mysql_command(
|
|
211
|
+
StringCommand(*sql_bits),
|
|
212
|
+
user=mysql_user,
|
|
213
|
+
password=mysql_password,
|
|
214
|
+
host=mysql_host,
|
|
215
|
+
port=mysql_port,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if present and is_present:
|
|
219
|
+
current_user = current_users.get(user_host)
|
|
220
|
+
|
|
221
|
+
alter_bits = []
|
|
222
|
+
|
|
223
|
+
if require == "SSL":
|
|
224
|
+
if current_user["ssl_type"] != "ANY":
|
|
225
|
+
alter_bits.append("REQUIRE SSL")
|
|
226
|
+
|
|
227
|
+
if require == "X509":
|
|
228
|
+
require_bits = []
|
|
229
|
+
|
|
230
|
+
if require_cipher and current_user["ssl_cipher"] != require_cipher:
|
|
231
|
+
require_bits.append('CIPHER "{0}"'.format(require_cipher))
|
|
232
|
+
if require_issuer and current_user["x509_issuer"] != require_issuer:
|
|
233
|
+
require_bits.append('ISSUER "{0}"'.format(require_issuer))
|
|
234
|
+
if require_subject and current_user["x509_subject"] != require_subject:
|
|
235
|
+
require_bits.append('SUBJECT "{0}"'.format(require_subject))
|
|
236
|
+
|
|
237
|
+
if not require_bits:
|
|
238
|
+
if current_user["ssl_type"] != "X509":
|
|
239
|
+
require_bits.append("X509")
|
|
240
|
+
|
|
241
|
+
if require_bits:
|
|
242
|
+
alter_bits.append("REQUIRE")
|
|
243
|
+
alter_bits.extend(require_bits)
|
|
244
|
+
|
|
245
|
+
resource_bits = []
|
|
246
|
+
if max_connections and current_user["max_user_connections"] != max_connections:
|
|
247
|
+
resource_bits.append("MAX_USER_CONNECTIONS {0}".format(max_connections))
|
|
248
|
+
if max_queries_per_hour and current_user["max_questions"] != max_queries_per_hour:
|
|
249
|
+
resource_bits.append("MAX_QUERIES_PER_HOUR {0}".format(max_queries_per_hour))
|
|
250
|
+
if max_updates_per_hour and current_user["max_updates"] != max_updates_per_hour:
|
|
251
|
+
resource_bits.append("MAX_UPDATES_PER_HOUR {0}".format(max_updates_per_hour))
|
|
252
|
+
if max_connections_per_hour and current_user["max_connections"] != max_connections_per_hour:
|
|
253
|
+
resource_bits.append("MAX_CONNECTIONS_PER_HOUR {0}".format(max_connections_per_hour))
|
|
254
|
+
|
|
255
|
+
if resource_bits:
|
|
256
|
+
alter_bits.append("WITH")
|
|
257
|
+
alter_bits.append(" ".join(resource_bits))
|
|
258
|
+
|
|
259
|
+
if alter_bits:
|
|
260
|
+
sql_bits = ['ALTER USER "{0}"@"{1}"'.format(user, user_hostname)]
|
|
261
|
+
sql_bits.extend(alter_bits)
|
|
262
|
+
yield make_execute_mysql_command(
|
|
263
|
+
StringCommand(*sql_bits),
|
|
264
|
+
user=mysql_user,
|
|
265
|
+
password=mysql_password,
|
|
266
|
+
host=mysql_host,
|
|
267
|
+
port=mysql_port,
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
host.noop("mysql user {0}@{1} exists".format(user, user_hostname))
|
|
271
|
+
|
|
272
|
+
# If we're here either the user exists or we just created them; either way
|
|
273
|
+
# now we can check any privileges are set.
|
|
274
|
+
if privileges:
|
|
275
|
+
yield from _privileges._inner(
|
|
276
|
+
user,
|
|
277
|
+
privileges,
|
|
278
|
+
user_hostname=user_hostname,
|
|
279
|
+
mysql_user=mysql_user,
|
|
280
|
+
mysql_password=mysql_password,
|
|
281
|
+
mysql_host=mysql_host,
|
|
282
|
+
mysql_port=mysql_port,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
@operation()
|
|
287
|
+
def database(
|
|
288
|
+
database: str,
|
|
289
|
+
# Desired database settings
|
|
290
|
+
present: bool = True,
|
|
291
|
+
collate: str | None = None,
|
|
292
|
+
charset: str | None = None,
|
|
293
|
+
user: str | None = None,
|
|
294
|
+
user_hostname: str = "localhost",
|
|
295
|
+
user_privileges: str | list[str] = "ALL",
|
|
296
|
+
# Details for speaking to MySQL via `mysql` CLI
|
|
297
|
+
mysql_user: str | None = None,
|
|
298
|
+
mysql_password: str | None = None,
|
|
299
|
+
mysql_host: str | None = None,
|
|
300
|
+
mysql_port: int | None = None,
|
|
301
|
+
):
|
|
302
|
+
...
|
|
303
|
+
"""
|
|
304
|
+
Add/remove MySQL databases.
|
|
305
|
+
|
|
306
|
+
+ database: the name of the database
|
|
307
|
+
+ present: whether the database should exist or not
|
|
308
|
+
+ collate: the collate to use when creating the database
|
|
309
|
+
+ charset: the charset to use when creating the database
|
|
310
|
+
+ user: MySQL user to grant privileges on this database to
|
|
311
|
+
+ user_hostname: the hostname of the MySQL user to grant
|
|
312
|
+
+ user_privileges: privileges to grant to any specified user
|
|
313
|
+
+ mysql_*: global module arguments, see above
|
|
314
|
+
|
|
315
|
+
Collate/charset:
|
|
316
|
+
these will only be applied if the database does not exist - ie pyinfra
|
|
317
|
+
will not attempt to alter the existing databases collate/character sets.
|
|
318
|
+
|
|
319
|
+
**Example:**
|
|
320
|
+
|
|
321
|
+
.. code:: python
|
|
322
|
+
|
|
323
|
+
mysql.database(
|
|
324
|
+
name="Create the pyinfra_stuff database",
|
|
325
|
+
database="pyinfra_stuff",
|
|
326
|
+
user="pyinfra",
|
|
327
|
+
user_privileges=["SELECT", "INSERT"],
|
|
328
|
+
charset="utf8",
|
|
329
|
+
)
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
current_databases = host.get_fact(
|
|
333
|
+
MysqlDatabases,
|
|
334
|
+
mysql_user=mysql_user,
|
|
335
|
+
mysql_password=mysql_password,
|
|
336
|
+
mysql_host=mysql_host,
|
|
337
|
+
mysql_port=mysql_port,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
is_present = database in current_databases
|
|
341
|
+
|
|
342
|
+
if not present:
|
|
343
|
+
if is_present:
|
|
344
|
+
yield make_execute_mysql_command(
|
|
345
|
+
"DROP DATABASE `{0}`".format(database),
|
|
346
|
+
user=mysql_user,
|
|
347
|
+
password=mysql_password,
|
|
348
|
+
host=mysql_host,
|
|
349
|
+
port=mysql_port,
|
|
350
|
+
)
|
|
351
|
+
else:
|
|
352
|
+
host.noop("mysql database {0} does not exist".format(database))
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
# We want the database but it doesn't exist
|
|
356
|
+
if present and not is_present:
|
|
357
|
+
sql_bits = ["CREATE DATABASE `{0}`".format(database)]
|
|
358
|
+
|
|
359
|
+
if collate:
|
|
360
|
+
sql_bits.append("COLLATE {0}".format(collate))
|
|
361
|
+
|
|
362
|
+
if charset:
|
|
363
|
+
sql_bits.append("CHARSET {0}".format(charset))
|
|
364
|
+
|
|
365
|
+
yield make_execute_mysql_command(
|
|
366
|
+
" ".join(sql_bits),
|
|
367
|
+
user=mysql_user,
|
|
368
|
+
password=mysql_password,
|
|
369
|
+
host=mysql_host,
|
|
370
|
+
port=mysql_port,
|
|
371
|
+
)
|
|
372
|
+
else:
|
|
373
|
+
host.noop("mysql database {0} exists".format(database))
|
|
374
|
+
|
|
375
|
+
# Ensure any user privileges for this database
|
|
376
|
+
if user and user_privileges:
|
|
377
|
+
yield from privileges._inner(
|
|
378
|
+
user,
|
|
379
|
+
user_hostname=user_hostname,
|
|
380
|
+
privileges=user_privileges,
|
|
381
|
+
database=database,
|
|
382
|
+
mysql_user=mysql_user,
|
|
383
|
+
mysql_password=mysql_password,
|
|
384
|
+
mysql_host=mysql_host,
|
|
385
|
+
mysql_port=mysql_port,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@operation()
|
|
390
|
+
def privileges(
|
|
391
|
+
user: str,
|
|
392
|
+
privileges: str | list[str] | set[str],
|
|
393
|
+
user_hostname="localhost",
|
|
394
|
+
database="*",
|
|
395
|
+
table="*",
|
|
396
|
+
flush=True,
|
|
397
|
+
with_grant_option=False,
|
|
398
|
+
# Details for speaking to MySQL via `mysql` CLI
|
|
399
|
+
mysql_user: str | None = None,
|
|
400
|
+
mysql_password: str | None = None,
|
|
401
|
+
mysql_host: str | None = None,
|
|
402
|
+
mysql_port: int | None = None,
|
|
403
|
+
):
|
|
404
|
+
"""
|
|
405
|
+
Add/remove MySQL privileges for a user, either global, database or table specific.
|
|
406
|
+
|
|
407
|
+
+ user: name of the user to manage privileges for
|
|
408
|
+
+ privileges: list of privileges the user should have (see also: ``with_grant_option`` argument)
|
|
409
|
+
+ user_hostname: the hostname of the user
|
|
410
|
+
+ database: name of the database to grant privileges to (defaults to all)
|
|
411
|
+
+ table: name of the table to grant privileges to (defaults to all)
|
|
412
|
+
+ flush: whether to flush (and update) the privileges table after any changes
|
|
413
|
+
+ with_grant_option: whether the grant option privilege should be set
|
|
414
|
+
+ mysql_*: global module arguments, see above
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
# Ensure we have a list
|
|
418
|
+
if isinstance(privileges, str):
|
|
419
|
+
privileges = {privileges}
|
|
420
|
+
|
|
421
|
+
if isinstance(privileges, list):
|
|
422
|
+
privileges = set(privileges)
|
|
423
|
+
|
|
424
|
+
if with_grant_option:
|
|
425
|
+
privileges.add("GRANT OPTION")
|
|
426
|
+
|
|
427
|
+
if database != "*":
|
|
428
|
+
database = "`{0}`".format(database)
|
|
429
|
+
|
|
430
|
+
if table != "*":
|
|
431
|
+
table = "`{0}`".format(table)
|
|
432
|
+
|
|
433
|
+
# We can't set privileges on *.tablename as MySQL won't allow it
|
|
434
|
+
if database == "*":
|
|
435
|
+
raise OperationError(
|
|
436
|
+
("Cannot apply MySQL privileges on {0}.{1}, no database provided").format(
|
|
437
|
+
database,
|
|
438
|
+
table,
|
|
439
|
+
),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
database_table = "{0}.{1}".format(database, table)
|
|
443
|
+
user_grants = host.get_fact(
|
|
444
|
+
MysqlUserGrants,
|
|
445
|
+
user=user,
|
|
446
|
+
hostname=user_hostname,
|
|
447
|
+
mysql_user=mysql_user,
|
|
448
|
+
mysql_password=mysql_password,
|
|
449
|
+
mysql_host=mysql_host,
|
|
450
|
+
mysql_port=mysql_port,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
existing_privileges = set()
|
|
454
|
+
if database_table in user_grants:
|
|
455
|
+
existing_privileges = {
|
|
456
|
+
"ALL" if privilege == "ALL PRIVILEGES" else privilege
|
|
457
|
+
for privilege in user_grants[database_table]
|
|
458
|
+
}
|
|
459
|
+
else:
|
|
460
|
+
user_grants[database_table] = set()
|
|
461
|
+
|
|
462
|
+
def handle_privileges(action, target, privileges_to_apply, with_statement=None):
|
|
463
|
+
command = (
|
|
464
|
+
'{action} {privileges} ON {database}.{table} {target} "{user}"@"{user_hostname}"'
|
|
465
|
+
).format(
|
|
466
|
+
privileges=", ".join(sorted(privileges_to_apply)),
|
|
467
|
+
action=action,
|
|
468
|
+
target=target,
|
|
469
|
+
database=database,
|
|
470
|
+
table=table,
|
|
471
|
+
user=user,
|
|
472
|
+
user_hostname=user_hostname,
|
|
473
|
+
)
|
|
474
|
+
|
|
475
|
+
if with_statement:
|
|
476
|
+
command += " WITH {with_statement}".format(with_statement=with_statement)
|
|
477
|
+
|
|
478
|
+
yield make_execute_mysql_command(
|
|
479
|
+
command,
|
|
480
|
+
user=mysql_user,
|
|
481
|
+
password=mysql_password,
|
|
482
|
+
host=mysql_host,
|
|
483
|
+
port=mysql_port,
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
# Find / revoke any privileges that exist that do not match the desired state
|
|
487
|
+
privileges_to_revoke = existing_privileges.difference(privileges)
|
|
488
|
+
# Find / grant any privileges that we want but do not exist
|
|
489
|
+
privileges_to_grant = privileges - existing_privileges
|
|
490
|
+
|
|
491
|
+
if privileges_to_grant:
|
|
492
|
+
# We will grant something on this table, no need to revoke "USAGE" as it will be overridden
|
|
493
|
+
privileges_to_revoke.discard("USAGE")
|
|
494
|
+
|
|
495
|
+
if privileges_to_revoke:
|
|
496
|
+
if {"ALL", "GRANT OPTION"} == privileges_to_revoke:
|
|
497
|
+
# This specific case is identified as the alternative syntax for revoke (without ON).
|
|
498
|
+
# To avoid this problem, revoke in 2 steps.
|
|
499
|
+
yield from handle_privileges("REVOKE", "FROM", {"GRANT OPTION"})
|
|
500
|
+
yield from handle_privileges("REVOKE", "FROM", {"ALL PRIVILEGES"})
|
|
501
|
+
else:
|
|
502
|
+
yield from handle_privileges("REVOKE", "FROM", privileges_to_revoke)
|
|
503
|
+
|
|
504
|
+
if privileges_to_grant:
|
|
505
|
+
if {"ALL", "GRANT OPTION"} == privileges_to_grant:
|
|
506
|
+
# ALL must be named by itself and cannot be specified along with other privileges
|
|
507
|
+
# The only exception for us is GRANT OPTION but as a WITH clause
|
|
508
|
+
# So handle this case and let the other ones fail
|
|
509
|
+
yield from handle_privileges("GRANT", "TO", {"ALL PRIVILEGES"}, "GRANT OPTION")
|
|
510
|
+
else:
|
|
511
|
+
yield from handle_privileges("GRANT", "TO", privileges_to_grant)
|
|
512
|
+
|
|
513
|
+
if privileges_to_grant or privileges_to_revoke:
|
|
514
|
+
if flush:
|
|
515
|
+
yield make_execute_mysql_command(
|
|
516
|
+
"FLUSH PRIVILEGES",
|
|
517
|
+
user=mysql_user,
|
|
518
|
+
password=mysql_password,
|
|
519
|
+
host=mysql_host,
|
|
520
|
+
port=mysql_port,
|
|
521
|
+
)
|
|
522
|
+
else:
|
|
523
|
+
host.noop("mysql privileges are already correct")
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
_privileges = privileges # noqa: E305 (for use where kwarg is the same)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
@operation(is_idempotent=False)
|
|
530
|
+
def dump(
|
|
531
|
+
dest: str,
|
|
532
|
+
database: str | None = None,
|
|
533
|
+
# Details for speaking to MySQL via `mysql` CLI
|
|
534
|
+
mysql_user: str | None = None,
|
|
535
|
+
mysql_password: str | None = None,
|
|
536
|
+
mysql_host: str | None = None,
|
|
537
|
+
mysql_port: int | None = None,
|
|
538
|
+
):
|
|
539
|
+
"""
|
|
540
|
+
Dump a MySQL database into a ``.sql`` file. Requires ``mysqldump``.
|
|
541
|
+
|
|
542
|
+
+ dest: name of the file to dump the SQL to
|
|
543
|
+
+ database: name of the database to dump
|
|
544
|
+
+ mysql_*: global module arguments, see above
|
|
545
|
+
|
|
546
|
+
**Example:**
|
|
547
|
+
|
|
548
|
+
.. code:: python
|
|
549
|
+
|
|
550
|
+
mysql.dump(
|
|
551
|
+
name="Dump the pyinfra_stuff database",
|
|
552
|
+
dest="/tmp/pyinfra_stuff.dump",
|
|
553
|
+
database="pyinfra_stuff",
|
|
554
|
+
)
|
|
555
|
+
"""
|
|
556
|
+
|
|
557
|
+
yield StringCommand(
|
|
558
|
+
make_mysql_command(
|
|
559
|
+
executable="mysqldump",
|
|
560
|
+
database=database,
|
|
561
|
+
user=mysql_user,
|
|
562
|
+
password=mysql_password,
|
|
563
|
+
host=mysql_host,
|
|
564
|
+
port=mysql_port,
|
|
565
|
+
),
|
|
566
|
+
">",
|
|
567
|
+
QuoteString(dest),
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
@operation(is_idempotent=False)
|
|
572
|
+
def load(
|
|
573
|
+
src: str,
|
|
574
|
+
database: str | None = None,
|
|
575
|
+
# Details for speaking to MySQL via `mysql` CLI
|
|
576
|
+
mysql_user: str | None = None,
|
|
577
|
+
mysql_password: str | None = None,
|
|
578
|
+
mysql_host: str | None = None,
|
|
579
|
+
mysql_port: int | None = None,
|
|
580
|
+
):
|
|
581
|
+
"""
|
|
582
|
+
Load ``.sql`` file into a database.
|
|
583
|
+
|
|
584
|
+
+ src: the filename to read from
|
|
585
|
+
+ database: name of the database to import into
|
|
586
|
+
+ mysql_*: global module arguments, see above
|
|
587
|
+
|
|
588
|
+
**Example:**
|
|
589
|
+
|
|
590
|
+
.. code:: python
|
|
591
|
+
|
|
592
|
+
mysql.load(
|
|
593
|
+
name="Import the pyinfra_stuff dump into pyinfra_stuff_copy",
|
|
594
|
+
src="/tmp/pyinfra_stuff.dump",
|
|
595
|
+
database="pyinfra_stuff_copy",
|
|
596
|
+
)
|
|
597
|
+
"""
|
|
598
|
+
|
|
599
|
+
yield StringCommand(
|
|
600
|
+
make_mysql_command(
|
|
601
|
+
database=database,
|
|
602
|
+
user=mysql_user,
|
|
603
|
+
password=mysql_password,
|
|
604
|
+
host=mysql_host,
|
|
605
|
+
port=mysql_port,
|
|
606
|
+
),
|
|
607
|
+
"<",
|
|
608
|
+
QuoteString(src),
|
|
609
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Manage npm (aka node aka Node.js) packages.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from pyinfra import host
|
|
8
|
+
from pyinfra.api import operation
|
|
9
|
+
from pyinfra.facts.npm import NpmPackages
|
|
10
|
+
|
|
11
|
+
from .util.packaging import ensure_packages
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@operation()
|
|
15
|
+
def packages(
|
|
16
|
+
packages: str | list[str] | None = None,
|
|
17
|
+
present=True,
|
|
18
|
+
latest=False,
|
|
19
|
+
directory: str | None = None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Install/remove/update npm packages.
|
|
23
|
+
|
|
24
|
+
+ packages: list of packages to ensure
|
|
25
|
+
+ present: whether the packages should be present
|
|
26
|
+
+ latest: whether to upgrade packages without a specified version
|
|
27
|
+
+ directory: directory to manage packages for, defaults to global
|
|
28
|
+
|
|
29
|
+
Versions:
|
|
30
|
+
Package versions can be pinned like npm: ``<pkg>@<version>``.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
current_packages = host.get_fact(NpmPackages, directory=directory)
|
|
34
|
+
|
|
35
|
+
install_command = (
|
|
36
|
+
"npm install -g" if directory is None else "cd {0} && npm install".format(directory)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
uninstall_command = (
|
|
40
|
+
"npm uninstall -g" if directory is None else "cd {0} && npm uninstall".format(directory)
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
upgrade_command = (
|
|
44
|
+
"npm update -g" if directory is None else "cd {0} && npm update".format(directory)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
yield from ensure_packages(
|
|
48
|
+
host,
|
|
49
|
+
packages,
|
|
50
|
+
current_packages,
|
|
51
|
+
present,
|
|
52
|
+
install_command=install_command,
|
|
53
|
+
uninstall_command=uninstall_command,
|
|
54
|
+
upgrade_command=upgrade_command,
|
|
55
|
+
version_join="@",
|
|
56
|
+
latest=latest,
|
|
57
|
+
)
|