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.
Files changed (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.2.dist-info/RECORD +0 -170
  151. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
@@ -1,391 +1,30 @@
1
- """
2
- The PostgreSQL modules manage PostgreSQL databases, users and privileges.
1
+ from __future__ import annotations
3
2
 
4
- Requires the ``psql`` CLI executable on the target host(s).
3
+ from pyinfra.api import operation
5
4
 
6
- All operations in this module take four optional arguments:
7
- + ``psql_user``: the username to connect to postgresql to
8
- + ``psql_password``: the password for the connecting user
9
- + ``psql_host``: the hostname of the server to connect to
10
- + ``psql_port``: the port of the server to connect to
5
+ from . import postgres
11
6
 
12
- See example/postgresql.py for detailed example
13
7
 
14
- """
8
+ @operation(is_idempotent=False, is_deprecated=True, deprecated_for="postgres.sql")
9
+ def sql(*args, **kwargs):
10
+ yield from postgres.sql._inner(*args, **kwargs)
15
11
 
16
- from pyinfra import host, logger
17
- from pyinfra.api import MaskString, StringCommand, operation
18
- from pyinfra.facts.postgresql import (
19
- PostgresqlDatabases,
20
- PostgresqlRoles,
21
- make_execute_psql_command,
22
- make_psql_command,
23
- )
24
12
 
25
- LEGACY_ARG_MAP = {
26
- "postgresql_user": "psql_user",
27
- "postgresql_password": "psql_password",
28
- "postgresql_host": "psql_host",
29
- "postgresql_port": "psql_port",
30
- }
13
+ @operation(is_idempotent=False, is_deprecated=True, deprecated_for="postgres.role")
14
+ def role(*args, **kwargs):
15
+ yield from postgres.role._inner(*args, **kwargs)
31
16
 
32
17
 
33
- def _translate_legacy_args(func):
34
- def decorated_func(*args, **kwargs):
35
- for legacy_key, key in LEGACY_ARG_MAP.items():
36
- if legacy_key in kwargs:
37
- kwargs[key] = kwargs.pop(legacy_key)
38
- logger.warning(
39
- (
40
- f"The `{legacy_key}` has been replaced "
41
- f"by `{key}` in `postgresql.*` operations."
42
- ),
43
- )
44
- return func(*args, **kwargs)
18
+ @operation(is_idempotent=False, is_deprecated=True)
19
+ def database(*args, **kwargs):
20
+ yield from postgres.database._inner(*args, **kwargs)
45
21
 
46
- decorated_func._pyinfra_op = func
47
- return decorated_func
48
22
 
23
+ @operation(is_idempotent=False, is_deprecated=True)
24
+ def dump(*args, **kwargs):
25
+ yield from postgres.dump._inner(*args, **kwargs)
49
26
 
50
- @operation(is_idempotent=False)
51
- @_translate_legacy_args
52
- def sql(
53
- sql,
54
- database=None,
55
- # Details for speaking to PostgreSQL via `psql` CLI
56
- psql_user=None,
57
- psql_password=None,
58
- psql_host=None,
59
- psql_port=None,
60
- ):
61
- """
62
- Execute arbitrary SQL against PostgreSQL.
63
27
 
64
- + sql: SQL command(s) to execute
65
- + database: optional database to execute against
66
- + psql_*: global module arguments, see above
67
- """
68
-
69
- yield make_execute_psql_command(
70
- sql,
71
- database=database,
72
- user=psql_user,
73
- password=psql_password,
74
- host=psql_host,
75
- port=psql_port,
76
- )
77
-
78
-
79
- @operation
80
- @_translate_legacy_args
81
- def role(
82
- role,
83
- present=True,
84
- password=None,
85
- login=True,
86
- superuser=False,
87
- inherit=False,
88
- createdb=False,
89
- createrole=False,
90
- replication=False,
91
- connection_limit=None,
92
- # Details for speaking to PostgreSQL via `psql` CLI
93
- psql_user=None,
94
- psql_password=None,
95
- psql_host=None,
96
- psql_port=None,
97
- ):
98
- """
99
- Add/remove PostgreSQL roles.
100
-
101
- + role: name of the role
102
- + present: whether the role should be present or absent
103
- + password: the password for the role
104
- + login: whether the role can login
105
- + superuser: whether role will be a superuser
106
- + inherit: whether the role inherits from other roles
107
- + createdb: whether the role is allowed to create databases
108
- + createrole: whether the role is allowed to create new roles
109
- + replication: whether this role is allowed to replicate
110
- + connection_limit: the connection limit for the role
111
- + psql_*: global module arguments, see above
112
-
113
- Updates:
114
- pyinfra will not attempt to change existing roles - it will either
115
- create or drop roles, but not alter them (if the role exists this
116
- operation will make no changes).
117
-
118
- **Example:**
119
-
120
- .. code:: python
121
-
122
- postgresql.role(
123
- name="Create the pyinfra PostgreSQL role",
124
- role="pyinfra",
125
- password="somepassword",
126
- superuser=True,
127
- login=True,
128
- sudo_user="postgres",
129
- )
130
-
131
- """
132
-
133
- roles = host.get_fact(
134
- PostgresqlRoles,
135
- psql_user=psql_user,
136
- psql_password=psql_password,
137
- psql_host=psql_host,
138
- psql_port=psql_port,
139
- )
140
-
141
- is_present = role in roles
142
-
143
- # User not wanted?
144
- if not present:
145
- if is_present:
146
- yield make_execute_psql_command(
147
- 'DROP ROLE "{0}"'.format(role),
148
- user=psql_user,
149
- password=psql_password,
150
- host=psql_host,
151
- port=psql_port,
152
- )
153
- roles.pop(role)
154
- else:
155
- host.noop("postgresql role {0} does not exist".format(role))
156
- return
157
-
158
- # If we want the user and they don't exist
159
- if not is_present:
160
- sql_bits = ['CREATE ROLE "{0}"'.format(role)]
161
-
162
- for key, value in (
163
- ("LOGIN", login),
164
- ("SUPERUSER", superuser),
165
- ("INHERIT", inherit),
166
- ("CREATEDB", createdb),
167
- ("CREATEROLE", createrole),
168
- ("REPLICATION", replication),
169
- ):
170
- if value:
171
- sql_bits.append(key)
172
-
173
- if connection_limit:
174
- sql_bits.append("CONNECTION LIMIT {0}".format(connection_limit))
175
-
176
- if password:
177
- sql_bits.append(MaskString("PASSWORD '{0}'".format(password)))
178
-
179
- yield make_execute_psql_command(
180
- StringCommand(*sql_bits),
181
- user=psql_user,
182
- password=psql_password,
183
- host=psql_host,
184
- port=psql_port,
185
- )
186
- roles[role] = {
187
- "super": superuser,
188
- "createdb": createdb,
189
- "createrole": createrole,
190
- }
191
- else:
192
- host.noop("postgresql role {0} exists".format(role))
193
-
194
-
195
- @operation
196
- @_translate_legacy_args
197
- def database(
198
- database,
199
- present=True,
200
- owner=None,
201
- template=None,
202
- encoding=None,
203
- lc_collate=None,
204
- lc_ctype=None,
205
- tablespace=None,
206
- connection_limit=None,
207
- # Details for speaking to PostgreSQL via `psql` CLI
208
- psql_user=None,
209
- psql_password=None,
210
- psql_host=None,
211
- psql_port=None,
212
- ):
213
- """
214
- Add/remove PostgreSQL databases.
215
-
216
- + name: name of the database
217
- + present: whether the database should exist or not
218
- + owner: the PostgreSQL role that owns the database
219
- + template: name of the PostgreSQL template to use
220
- + encoding: encoding of the database
221
- + lc_collate: lc_collate of the database
222
- + lc_ctype: lc_ctype of the database
223
- + tablespace: the tablespace to use for the template
224
- + connection_limit: the connection limit to apply to the database
225
- + psql_*: global module arguments, see above
226
-
227
- Updates:
228
- pyinfra will not attempt to change existing databases - it will either
229
- create or drop databases, but not alter them (if the db exists this
230
- operation will make no changes).
231
-
232
- **Example:**
233
-
234
- .. code:: python
235
-
236
- postgresql.database(
237
- name="Create the pyinfra_stuff database",
238
- database="pyinfra_stuff",
239
- owner="pyinfra",
240
- encoding="UTF8",
241
- sudo_user="postgres",
242
- )
243
-
244
- """
245
-
246
- current_databases = host.get_fact(
247
- PostgresqlDatabases,
248
- psql_user=psql_user,
249
- psql_password=psql_password,
250
- psql_host=psql_host,
251
- psql_port=psql_port,
252
- )
253
-
254
- is_present = database in current_databases
255
-
256
- if not present:
257
- if is_present:
258
- yield make_execute_psql_command(
259
- 'DROP DATABASE "{0}"'.format(database),
260
- user=psql_user,
261
- password=psql_password,
262
- host=psql_host,
263
- port=psql_port,
264
- )
265
- current_databases.pop(database)
266
- else:
267
- host.noop("postgresql database {0} does not exist".format(database))
268
- return
269
-
270
- # We want the database but it doesn't exist
271
- if present and not is_present:
272
- sql_bits = ['CREATE DATABASE "{0}"'.format(database)]
273
-
274
- for key, value in (
275
- ("OWNER", '"{0}"'.format(owner) if owner else owner),
276
- ("TEMPLATE", template),
277
- ("ENCODING", encoding),
278
- ("LC_COLLATE", lc_collate),
279
- ("LC_CTYPE", lc_ctype),
280
- ("TABLESPACE", tablespace),
281
- ("CONNECTION LIMIT", connection_limit),
282
- ):
283
- if value:
284
- sql_bits.append("{0} {1}".format(key, value))
285
-
286
- yield make_execute_psql_command(
287
- StringCommand(*sql_bits),
288
- user=psql_user,
289
- password=psql_password,
290
- host=psql_host,
291
- port=psql_port,
292
- )
293
- current_databases[database] = {
294
- "template": template,
295
- "encoding": encoding,
296
- "lc_collate": lc_collate,
297
- "lc_ctype": lc_ctype,
298
- "tablespace": tablespace,
299
- "connection_limit": connection_limit,
300
- }
301
- else:
302
- host.noop("postgresql database {0} exists".format(database))
303
-
304
-
305
- @operation(is_idempotent=False)
306
- @_translate_legacy_args
307
- def dump(
308
- dest,
309
- database=None,
310
- # Details for speaking to PostgreSQL via `psql` CLI
311
- psql_user=None,
312
- psql_password=None,
313
- psql_host=None,
314
- psql_port=None,
315
- ):
316
- """
317
- Dump a PostgreSQL database into a ``.sql`` file. Requires ``pg_dump``.
318
-
319
- + dest: name of the file to dump the SQL to
320
- + database: name of the database to dump
321
- + psql_*: global module arguments, see above
322
-
323
- **Example:**
324
-
325
- .. code:: python
326
-
327
- postgresql.dump(
328
- name="Dump the pyinfra_stuff database",
329
- dest="/tmp/pyinfra_stuff.dump",
330
- database="pyinfra_stuff",
331
- sudo_user="postgres",
332
- )
333
-
334
- """
335
-
336
- yield StringCommand(
337
- make_psql_command(
338
- executable="pg_dump",
339
- database=database,
340
- user=psql_user,
341
- password=psql_password,
342
- host=psql_host,
343
- port=psql_port,
344
- ),
345
- ">",
346
- dest,
347
- )
348
-
349
-
350
- @operation(is_idempotent=False)
351
- @_translate_legacy_args
352
- def load(
353
- src,
354
- database=None,
355
- # Details for speaking to PostgreSQL via `psql` CLI
356
- psql_user=None,
357
- psql_password=None,
358
- psql_host=None,
359
- psql_port=None,
360
- ):
361
- """
362
- Load ``.sql`` file into a database.
363
-
364
- + src: the filename to read from
365
- + database: name of the database to import into
366
- + psql_*: global module arguments, see above
367
-
368
- **Example:**
369
-
370
- .. code:: python
371
-
372
- postgresql.load(
373
- name="Import the pyinfra_stuff dump into pyinfra_stuff_copy",
374
- src="/tmp/pyinfra_stuff.dump",
375
- database="pyinfra_stuff_copy",
376
- sudo_user="postgres",
377
- )
378
-
379
- """
380
-
381
- yield StringCommand(
382
- make_psql_command(
383
- database=database,
384
- user=psql_user,
385
- password=psql_password,
386
- host=psql_host,
387
- port=psql_port,
388
- ),
389
- "<",
390
- src,
391
- )
28
+ @operation(is_idempotent=False, is_deprecated=True)
29
+ def load(*args, **kwargs):
30
+ yield from postgres.load._inner(*args, **kwargs)
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import operation
2
4
 
3
5
 
4
6
  @operation(is_idempotent=False)
5
- def agent(server=None, port=None):
7
+ def agent(server: str | None = None, port: int | None = None):
6
8
  """
7
9
  Run puppet agent
8
10
 
@@ -2,15 +2,15 @@
2
2
  The Python module allows you to execute Python code within the context of a deploy.
3
3
  """
4
4
 
5
- from inspect import getfullargspec
5
+ from __future__ import annotations
6
+
7
+ from typing import Callable
6
8
 
7
- from pyinfra import logger
8
9
  from pyinfra.api import FunctionCommand, operation
9
- from pyinfra.api.util import get_call_location
10
10
 
11
11
 
12
- @operation(is_idempotent=False)
13
- def call(function, *args, **kwargs):
12
+ @operation(is_idempotent=False, _set_in_op=False)
13
+ def call(function: Callable, *args, **kwargs):
14
14
  """
15
15
  Execute a Python function within a deploy.
16
16
 
@@ -43,20 +43,11 @@ def call(function, *args, **kwargs):
43
43
 
44
44
  """
45
45
 
46
- argspec = getfullargspec(function)
47
- if "state" in argspec.args and "host" in argspec.args:
48
- logger.warning(
49
- "Callback functions used in `python.call` operations no "
50
- f"longer take `state` and `host` arguments: {get_call_location(frame_offset=3)}",
51
- )
52
-
53
- kwargs.pop("state", None)
54
- kwargs.pop("host", None)
55
46
  yield FunctionCommand(function, args, kwargs)
56
47
 
57
48
 
58
- @operation(is_idempotent=False)
59
- def raise_exception(exception, *args, **kwargs):
49
+ @operation(is_idempotent=False, _set_in_op=False)
50
+ def raise_exception(exception: Exception, *args, **kwargs):
60
51
  """
61
52
  Raise a Python exception within a deploy.
62
53
 
@@ -76,8 +67,6 @@ def raise_exception(exception, *args, **kwargs):
76
67
  """
77
68
 
78
69
  def raise_exc(*args, **kwargs): # pragma: no cover
79
- raise exception(*args, **kwargs)
70
+ raise exception(*args, **kwargs) # type: ignore[operator]
80
71
 
81
- kwargs.pop("state", None)
82
- kwargs.pop("host", None)
83
72
  yield FunctionCommand(raise_exc, args, kwargs)
@@ -0,0 +1,182 @@
1
+ """
2
+ Manage runit services.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from pyinfra import host
8
+ from pyinfra.api import operation
9
+ from pyinfra.facts.files import File
10
+ from pyinfra.facts.runit import RunitManaged, RunitStatus
11
+
12
+ from .files import file, link
13
+ from .util.service import handle_service_control
14
+
15
+
16
+ @operation()
17
+ def service(
18
+ service: str,
19
+ running: bool = True,
20
+ restarted: bool = False,
21
+ reloaded: bool = False,
22
+ command: Optional[str] = None,
23
+ enabled: Optional[bool] = None,
24
+ managed: bool = True,
25
+ svdir: str = "/var/service",
26
+ sourcedir: str = "/etc/sv",
27
+ ):
28
+ """
29
+ Manage the state of runit services.
30
+
31
+ + service: name of the service to manage
32
+ + running: whether the service should be running
33
+ + restarted: whether the service should be restarted
34
+ + reloaded: whether the service should be reloaded
35
+ + command: custom command to pass like: ``sv <command> <service>``
36
+ + enabled: whether this service should be enabled/disabled on boot
37
+ + managed: whether runit should manage this service
38
+
39
+ For services to be controlled, they first need to be managed by runit by
40
+ adding a symlink to the service in ``SVDIR``.
41
+ By setting ``managed=False`` the symlink will be removed.
42
+ Other options won't have any effect after that.
43
+ Although the ``<service>/down`` file can still be controlled with the
44
+ ``enabled`` option.
45
+
46
+ + svdir: alternative ``SVDIR``
47
+
48
+ An alternative ``SVDIR`` can be specified. This can be used for user services.
49
+
50
+ + sourcedir: where to search for available services
51
+
52
+ An alternative directory for available services can be specified.
53
+ Example: ``sourcedir=/etc/sv.local`` for services managed by the administrator.
54
+ """
55
+
56
+ was_managed = service in host.get_fact(RunitManaged, service=service, svdir=svdir)
57
+ was_auto = not host.get_fact(File, path="{0}/{1}/down".format(sourcedir, service))
58
+
59
+ # Disable autostart for previously unmanaged services.
60
+ #
61
+ # Where ``running=False`` is requested, this prevents one case of briefly
62
+ # starting and stopping the service.
63
+ if not was_managed and managed and was_auto:
64
+ yield from auto._inner(
65
+ service=service,
66
+ auto=False,
67
+ sourcedir=sourcedir,
68
+ )
69
+
70
+ yield from manage._inner(
71
+ service=service,
72
+ managed=managed,
73
+ svdir=svdir,
74
+ sourcedir=sourcedir,
75
+ )
76
+
77
+ # Service wasn't managed before, so wait for ``runsv`` to start.
78
+ # ``runsvdir`` will check at least every 5 seconds for new services.
79
+ # Wait for at most 10 seconds for the service to be managed, otherwise fail.
80
+ if not was_managed and managed:
81
+ yield from wait_runsv._inner(
82
+ service=service,
83
+ svdir=svdir,
84
+ )
85
+
86
+ if isinstance(enabled, bool):
87
+ yield from auto._inner(
88
+ service=service,
89
+ auto=enabled,
90
+ sourcedir=sourcedir,
91
+ )
92
+ else:
93
+ # restore previous state of ``<service>/down``
94
+ yield from auto._inner(
95
+ service=service,
96
+ auto=was_auto,
97
+ sourcedir=sourcedir,
98
+ )
99
+
100
+ # Services need to be managed by ``runit`` for the other options to make sense.
101
+ if not managed:
102
+ return
103
+
104
+ yield from handle_service_control(
105
+ host,
106
+ service,
107
+ host.get_fact(RunitStatus, service=service, svdir=svdir),
108
+ "SVDIR={0} sv {{1}} {{0}}".format(svdir),
109
+ running,
110
+ restarted,
111
+ reloaded,
112
+ command,
113
+ )
114
+
115
+
116
+ @operation()
117
+ def manage(
118
+ service: str,
119
+ managed: bool = True,
120
+ svdir: str = "/var/service",
121
+ sourcedir: str = "/etc/sv",
122
+ ):
123
+ """
124
+ Manage runit svdir links.
125
+
126
+ + service: name of the service to manage
127
+ + managed: whether the link should exist
128
+ + svdir: alternative ``SVDIR``
129
+ + sourcedir: where to search for available services
130
+ """
131
+
132
+ yield from link._inner(
133
+ path="{0}/{1}".format(svdir, service),
134
+ target="{0}/{1}".format(sourcedir, service),
135
+ present=managed,
136
+ create_remote_dir=False,
137
+ )
138
+
139
+
140
+ @operation(is_idempotent=False)
141
+ def wait_runsv(
142
+ service: str,
143
+ svdir: str = "/var/service",
144
+ timeout: int = 10,
145
+ ):
146
+ """
147
+ Wait for runsv for ``service`` to be available.
148
+
149
+ + service: name of the service to manage
150
+ + svdir: alternative ``SVDIR``
151
+ + timeout: time in seconds to wait
152
+ """
153
+
154
+ yield (
155
+ "export SVDIR={0}\n"
156
+ "for i in $(seq {1}); do\n"
157
+ " sv status {2} > /dev/null && exit 0\n"
158
+ " sleep 1;\n"
159
+ "done\n"
160
+ "exit 1"
161
+ ).format(svdir, timeout, service)
162
+
163
+
164
+ @operation()
165
+ def auto(
166
+ service: str,
167
+ auto: bool = True,
168
+ sourcedir: str = "/etc/sv",
169
+ ):
170
+ """
171
+ Start service automatically by managing the ``service/down`` file.
172
+
173
+ + service: name of the service to manage
174
+ + auto: whether the service should start automatically
175
+ + sourcedir: where to search for available services
176
+ """
177
+
178
+ yield from file._inner(
179
+ path="{0}/{1}/down".format(sourcedir, service),
180
+ present=not auto,
181
+ create_remote_dir=False,
182
+ )