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.
Files changed (203) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +18 -3
  4. pyinfra/api/arguments.py +406 -0
  5. pyinfra/api/arguments_typed.py +79 -0
  6. pyinfra/api/command.py +274 -0
  7. pyinfra/api/config.py +222 -28
  8. pyinfra/api/connect.py +33 -13
  9. pyinfra/api/connectors.py +27 -0
  10. pyinfra/api/deploy.py +65 -66
  11. pyinfra/api/exceptions.py +67 -18
  12. pyinfra/api/facts.py +253 -202
  13. pyinfra/api/host.py +413 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/operation.py +432 -262
  16. pyinfra/api/operations.py +273 -260
  17. pyinfra/api/state.py +302 -248
  18. pyinfra/api/util.py +291 -368
  19. pyinfra/connectors/base.py +173 -0
  20. pyinfra/connectors/chroot.py +212 -0
  21. pyinfra/connectors/docker.py +381 -0
  22. pyinfra/connectors/dockerssh.py +297 -0
  23. pyinfra/connectors/local.py +238 -0
  24. pyinfra/connectors/scp/__init__.py +1 -0
  25. pyinfra/connectors/scp/client.py +204 -0
  26. pyinfra/connectors/ssh.py +670 -0
  27. pyinfra/connectors/ssh_util.py +114 -0
  28. pyinfra/connectors/sshuserclient/client.py +309 -0
  29. pyinfra/connectors/sshuserclient/config.py +102 -0
  30. pyinfra/connectors/terraform.py +135 -0
  31. pyinfra/connectors/util.py +410 -0
  32. pyinfra/connectors/vagrant.py +183 -0
  33. pyinfra/context.py +145 -0
  34. pyinfra/facts/__init__.py +7 -6
  35. pyinfra/facts/apk.py +22 -7
  36. pyinfra/facts/apt.py +117 -60
  37. pyinfra/facts/brew.py +100 -15
  38. pyinfra/facts/bsdinit.py +23 -0
  39. pyinfra/facts/cargo.py +37 -0
  40. pyinfra/facts/choco.py +47 -0
  41. pyinfra/facts/crontab.py +195 -0
  42. pyinfra/facts/deb.py +94 -0
  43. pyinfra/facts/dnf.py +48 -0
  44. pyinfra/facts/docker.py +96 -23
  45. pyinfra/facts/efibootmgr.py +113 -0
  46. pyinfra/facts/files.py +630 -58
  47. pyinfra/facts/flatpak.py +77 -0
  48. pyinfra/facts/freebsd.py +70 -0
  49. pyinfra/facts/gem.py +19 -6
  50. pyinfra/facts/git.py +59 -14
  51. pyinfra/facts/gpg.py +150 -0
  52. pyinfra/facts/hardware.py +313 -167
  53. pyinfra/facts/iptables.py +72 -62
  54. pyinfra/facts/launchd.py +44 -0
  55. pyinfra/facts/lxd.py +17 -4
  56. pyinfra/facts/mysql.py +122 -86
  57. pyinfra/facts/npm.py +17 -9
  58. pyinfra/facts/openrc.py +71 -0
  59. pyinfra/facts/opkg.py +246 -0
  60. pyinfra/facts/pacman.py +50 -7
  61. pyinfra/facts/pip.py +24 -7
  62. pyinfra/facts/pipx.py +82 -0
  63. pyinfra/facts/pkg.py +15 -6
  64. pyinfra/facts/pkgin.py +35 -0
  65. pyinfra/facts/podman.py +54 -0
  66. pyinfra/facts/postgres.py +178 -0
  67. pyinfra/facts/postgresql.py +6 -147
  68. pyinfra/facts/rpm.py +105 -0
  69. pyinfra/facts/runit.py +77 -0
  70. pyinfra/facts/selinux.py +161 -0
  71. pyinfra/facts/server.py +746 -285
  72. pyinfra/facts/snap.py +88 -0
  73. pyinfra/facts/systemd.py +139 -0
  74. pyinfra/facts/sysvinit.py +59 -0
  75. pyinfra/facts/upstart.py +35 -0
  76. pyinfra/facts/util/__init__.py +17 -0
  77. pyinfra/facts/util/databases.py +4 -6
  78. pyinfra/facts/util/packaging.py +37 -6
  79. pyinfra/facts/util/units.py +30 -0
  80. pyinfra/facts/util/win_files.py +99 -0
  81. pyinfra/facts/vzctl.py +20 -13
  82. pyinfra/facts/xbps.py +35 -0
  83. pyinfra/facts/yum.py +34 -40
  84. pyinfra/facts/zfs.py +77 -0
  85. pyinfra/facts/zypper.py +42 -0
  86. pyinfra/local.py +45 -83
  87. pyinfra/operations/__init__.py +12 -0
  88. pyinfra/operations/apk.py +98 -0
  89. pyinfra/operations/apt.py +488 -0
  90. pyinfra/operations/brew.py +231 -0
  91. pyinfra/operations/bsdinit.py +59 -0
  92. pyinfra/operations/cargo.py +45 -0
  93. pyinfra/operations/choco.py +61 -0
  94. pyinfra/operations/crontab.py +191 -0
  95. pyinfra/operations/dnf.py +210 -0
  96. pyinfra/operations/docker.py +446 -0
  97. pyinfra/operations/files.py +1939 -0
  98. pyinfra/operations/flatpak.py +94 -0
  99. pyinfra/operations/freebsd/__init__.py +12 -0
  100. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  101. pyinfra/operations/freebsd/pkg.py +219 -0
  102. pyinfra/operations/freebsd/service.py +116 -0
  103. pyinfra/operations/freebsd/sysrc.py +92 -0
  104. pyinfra/operations/gem.py +47 -0
  105. pyinfra/operations/git.py +419 -0
  106. pyinfra/operations/iptables.py +311 -0
  107. pyinfra/operations/launchd.py +45 -0
  108. pyinfra/operations/lxd.py +68 -0
  109. pyinfra/operations/mysql.py +609 -0
  110. pyinfra/operations/npm.py +57 -0
  111. pyinfra/operations/openrc.py +63 -0
  112. pyinfra/operations/opkg.py +88 -0
  113. pyinfra/operations/pacman.py +81 -0
  114. pyinfra/operations/pip.py +205 -0
  115. pyinfra/operations/pipx.py +102 -0
  116. pyinfra/operations/pkg.py +70 -0
  117. pyinfra/operations/pkgin.py +91 -0
  118. pyinfra/operations/postgres.py +436 -0
  119. pyinfra/operations/postgresql.py +30 -0
  120. pyinfra/operations/puppet.py +40 -0
  121. pyinfra/operations/python.py +72 -0
  122. pyinfra/operations/runit.py +184 -0
  123. pyinfra/operations/selinux.py +189 -0
  124. pyinfra/operations/server.py +1099 -0
  125. pyinfra/operations/snap.py +117 -0
  126. pyinfra/operations/ssh.py +216 -0
  127. pyinfra/operations/systemd.py +149 -0
  128. pyinfra/operations/sysvinit.py +141 -0
  129. pyinfra/operations/upstart.py +68 -0
  130. pyinfra/operations/util/__init__.py +12 -0
  131. pyinfra/operations/util/docker.py +251 -0
  132. pyinfra/operations/util/files.py +247 -0
  133. pyinfra/operations/util/packaging.py +336 -0
  134. pyinfra/operations/util/service.py +46 -0
  135. pyinfra/operations/vzctl.py +137 -0
  136. pyinfra/operations/xbps.py +77 -0
  137. pyinfra/operations/yum.py +210 -0
  138. pyinfra/operations/zfs.py +175 -0
  139. pyinfra/operations/zypper.py +192 -0
  140. pyinfra/progress.py +44 -32
  141. pyinfra/py.typed +0 -0
  142. pyinfra/version.py +9 -1
  143. pyinfra-3.5.1.dist-info/METADATA +141 -0
  144. pyinfra-3.5.1.dist-info/RECORD +159 -0
  145. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info}/WHEEL +1 -2
  146. pyinfra-3.5.1.dist-info/entry_points.txt +12 -0
  147. {pyinfra-0.11.dev3.dist-info → pyinfra-3.5.1.dist-info/licenses}/LICENSE.md +1 -1
  148. pyinfra_cli/__init__.py +1 -0
  149. pyinfra_cli/cli.py +780 -0
  150. pyinfra_cli/commands.py +66 -0
  151. pyinfra_cli/exceptions.py +155 -65
  152. pyinfra_cli/inventory.py +233 -89
  153. pyinfra_cli/log.py +39 -43
  154. pyinfra_cli/main.py +26 -495
  155. pyinfra_cli/prints.py +215 -156
  156. pyinfra_cli/util.py +172 -105
  157. pyinfra_cli/virtualenv.py +25 -20
  158. pyinfra/api/connectors/__init__.py +0 -21
  159. pyinfra/api/connectors/ansible.py +0 -99
  160. pyinfra/api/connectors/docker.py +0 -178
  161. pyinfra/api/connectors/local.py +0 -169
  162. pyinfra/api/connectors/ssh.py +0 -402
  163. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  164. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  165. pyinfra/api/connectors/util.py +0 -63
  166. pyinfra/api/connectors/vagrant.py +0 -155
  167. pyinfra/facts/init.py +0 -176
  168. pyinfra/facts/util/files.py +0 -102
  169. pyinfra/hook.py +0 -41
  170. pyinfra/modules/__init__.py +0 -11
  171. pyinfra/modules/apk.py +0 -64
  172. pyinfra/modules/apt.py +0 -272
  173. pyinfra/modules/brew.py +0 -122
  174. pyinfra/modules/files.py +0 -711
  175. pyinfra/modules/gem.py +0 -30
  176. pyinfra/modules/git.py +0 -115
  177. pyinfra/modules/init.py +0 -344
  178. pyinfra/modules/iptables.py +0 -271
  179. pyinfra/modules/lxd.py +0 -45
  180. pyinfra/modules/mysql.py +0 -347
  181. pyinfra/modules/npm.py +0 -47
  182. pyinfra/modules/pacman.py +0 -60
  183. pyinfra/modules/pip.py +0 -99
  184. pyinfra/modules/pkg.py +0 -43
  185. pyinfra/modules/postgresql.py +0 -245
  186. pyinfra/modules/puppet.py +0 -20
  187. pyinfra/modules/python.py +0 -37
  188. pyinfra/modules/server.py +0 -524
  189. pyinfra/modules/ssh.py +0 -150
  190. pyinfra/modules/util/files.py +0 -52
  191. pyinfra/modules/util/packaging.py +0 -118
  192. pyinfra/modules/vzctl.py +0 -133
  193. pyinfra/modules/yum.py +0 -171
  194. pyinfra/pseudo_modules.py +0 -64
  195. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  196. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  197. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  198. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  199. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  200. pyinfra_cli/__main__.py +0 -40
  201. pyinfra_cli/config.py +0 -92
  202. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  203. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
@@ -0,0 +1,446 @@
1
+ """
2
+ Manager Docker containers, volumes and networks. These operations allow you to manage Docker from
3
+ the view of the current inventory host. See the :doc:`../connectors/docker` to use Docker containers
4
+ as inventory directly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import operation
11
+ from pyinfra.facts.docker import DockerContainer, DockerNetwork, DockerPlugin, DockerVolume
12
+
13
+ from .util.docker import ContainerSpec, handle_docker
14
+
15
+
16
+ @operation()
17
+ def container(
18
+ container: str,
19
+ image: str = "",
20
+ ports: list[str] | None = None,
21
+ networks: list[str] | None = None,
22
+ volumes: list[str] | None = None,
23
+ env_vars: list[str] | None = None,
24
+ pull_always: bool = False,
25
+ present: bool = True,
26
+ force: bool = False,
27
+ start: bool = True,
28
+ ):
29
+ """
30
+ Manage Docker containers
31
+
32
+ + container: name to identify the container
33
+ + image: container image and tag ex: nginx:alpine
34
+ + networks: network list to attach on container
35
+ + ports: port list to expose
36
+ + volumes: volume list to map on container
37
+ + env_vars: environment variable list to inject on container
38
+ + pull_always: force image pull
39
+ + force: remove a container with same name and create a new one
40
+ + present: whether the container should be up and running
41
+ + start: start or stop the container
42
+
43
+ **Examples:**
44
+
45
+ .. code:: python
46
+
47
+ # Run a container
48
+ docker.container(
49
+ name="Deploy Nginx container",
50
+ container="nginx",
51
+ image="nginx:alpine",
52
+ ports=["80:80"],
53
+ present=True,
54
+ force=True,
55
+ networks=["proxy", "services"],
56
+ volumes=["nginx_data:/usr/share/nginx/html"],
57
+ pull_always=True,
58
+ )
59
+
60
+ # Stop a container
61
+ docker.container(
62
+ name="Stop Nginx container",
63
+ container="nginx",
64
+ start=False,
65
+ )
66
+
67
+ # Start a container
68
+ docker.container(
69
+ name="Start Nginx container",
70
+ container="nginx",
71
+ start=True,
72
+ )
73
+ """
74
+
75
+ want_spec = ContainerSpec(
76
+ image,
77
+ ports or list(),
78
+ networks or list(),
79
+ volumes or list(),
80
+ env_vars or list(),
81
+ pull_always,
82
+ )
83
+ existent_container = host.get_fact(DockerContainer, object_id=container)
84
+
85
+ container_spec_changes = want_spec.diff_from_inspect(existent_container)
86
+
87
+ is_running = (
88
+ (existent_container[0]["State"]["Status"] == "running")
89
+ if existent_container and existent_container[0]
90
+ else False
91
+ )
92
+ recreating = existent_container and (force or container_spec_changes)
93
+ removing = existent_container and not present
94
+
95
+ do_remove = recreating or removing
96
+ do_create = (present and not existent_container) or recreating
97
+ do_start = start and (recreating or not is_running)
98
+ do_stop = not start and not removing and is_running
99
+
100
+ if do_remove:
101
+ yield handle_docker(
102
+ resource="container",
103
+ command="remove",
104
+ container=container,
105
+ )
106
+
107
+ if do_create:
108
+ yield handle_docker(
109
+ resource="container",
110
+ command="create",
111
+ container=container,
112
+ spec=want_spec,
113
+ )
114
+
115
+ if do_start:
116
+ yield handle_docker(
117
+ resource="container",
118
+ command="start",
119
+ container=container,
120
+ )
121
+
122
+ if do_stop:
123
+ yield handle_docker(
124
+ resource="container",
125
+ command="stop",
126
+ container=container,
127
+ )
128
+
129
+
130
+ @operation(is_idempotent=False)
131
+ def image(image, present=True):
132
+ """
133
+ Manage Docker images
134
+
135
+ + image: Image and tag ex: nginx:alpine
136
+ + present: whether the Docker image should exist
137
+
138
+ **Examples:**
139
+
140
+ .. code:: python
141
+
142
+ # Pull a Docker image
143
+ docker.image(
144
+ name="Pull nginx image",
145
+ image="nginx:alpine",
146
+ present=True,
147
+ )
148
+
149
+ # Remove a Docker image
150
+ docker.image(
151
+ name="Remove nginx image",
152
+ image:"nginx:image",
153
+ present=False,
154
+ )
155
+ """
156
+
157
+ if present:
158
+ yield handle_docker(
159
+ resource="image",
160
+ command="pull",
161
+ image=image,
162
+ )
163
+
164
+ else:
165
+ yield handle_docker(
166
+ resource="image",
167
+ command="remove",
168
+ image=image,
169
+ )
170
+
171
+
172
+ @operation()
173
+ def volume(volume: str, driver: str = "", labels: list[str] | None = None, present: bool = True):
174
+ """
175
+ Manage Docker volumes
176
+
177
+ + volume: Volume name
178
+ + driver: Docker volume storage driver
179
+ + labels: Label list to attach in the volume
180
+ + present: whether the Docker volume should exist
181
+
182
+ **Examples:**
183
+
184
+ .. code:: python
185
+
186
+ # Create a Docker volume
187
+ docker.volume(
188
+ name="Create nginx volume",
189
+ volume="nginx_data",
190
+ present=True
191
+ )
192
+ """
193
+
194
+ existent_volume = host.get_fact(DockerVolume, object_id=volume)
195
+
196
+ if present:
197
+ if existent_volume:
198
+ host.noop("Volume already exists!")
199
+ return
200
+
201
+ yield handle_docker(
202
+ resource="volume",
203
+ command="create",
204
+ volume=volume,
205
+ driver=driver,
206
+ labels=labels,
207
+ present=present,
208
+ )
209
+
210
+ else:
211
+ if existent_volume is None:
212
+ host.noop("There is no {0} volume!".format(volume))
213
+ return
214
+
215
+ yield handle_docker(
216
+ resource="volume",
217
+ command="remove",
218
+ volume=volume,
219
+ )
220
+
221
+
222
+ @operation()
223
+ def network(
224
+ network: str,
225
+ driver: str = "",
226
+ gateway: str = "",
227
+ ip_range: str = "",
228
+ ipam_driver: str = "",
229
+ subnet: str = "",
230
+ scope: str = "",
231
+ aux_addresses: dict[str, str] | None = None,
232
+ opts: list[str] | None = None,
233
+ ipam_opts: list[str] | None = None,
234
+ labels: list[str] | None = None,
235
+ ingress: bool = False,
236
+ attachable: bool = False,
237
+ present: bool = True,
238
+ ):
239
+ """
240
+ Manage docker networks
241
+
242
+ + network: Network name
243
+ + driver: Network driver ex: bridge or overlay
244
+ + gateway: IPv4 or IPv6 Gateway for the master subnet
245
+ + ip_range: Allocate container ip from a sub-range
246
+ + ipam_driver: IP Address Management Driver
247
+ + subnet: Subnet in CIDR format that represents a network segment
248
+ + scope: Control the network's scope
249
+ + aux_addresses: named aux addresses for the network
250
+ + opts: Set driver specific options
251
+ + ipam_opts: Set IPAM driver specific options
252
+ + labels: Label list to attach in the network
253
+ + ingress: Create swarm routing-mesh network
254
+ + attachable: Enable manual container attachment
255
+ + present: whether the Docker network should exist
256
+
257
+ **Examples:**
258
+
259
+ .. code:: python
260
+
261
+ # Create Docker network
262
+ docker.network(
263
+ network="nginx",
264
+ attachable=True,
265
+ present=True,
266
+ )
267
+ """
268
+ existent_network = host.get_fact(DockerNetwork, object_id=network)
269
+
270
+ if present:
271
+ if existent_network:
272
+ host.noop("Network {0} already exists!".format(network))
273
+ return
274
+
275
+ yield handle_docker(
276
+ resource="network",
277
+ command="create",
278
+ network=network,
279
+ driver=driver,
280
+ gateway=gateway,
281
+ ip_range=ip_range,
282
+ ipam_driver=ipam_driver,
283
+ subnet=subnet,
284
+ scope=scope,
285
+ aux_addresses=aux_addresses,
286
+ opts=opts,
287
+ ipam_opts=ipam_opts,
288
+ labels=labels,
289
+ ingress=ingress,
290
+ attachable=attachable,
291
+ present=present,
292
+ )
293
+
294
+ else:
295
+ if existent_network is None:
296
+ host.noop("Network {0} does not exist!".format(network))
297
+ return
298
+
299
+ yield handle_docker(
300
+ resource="network",
301
+ command="remove",
302
+ network=network,
303
+ )
304
+
305
+
306
+ @operation(is_idempotent=False)
307
+ def prune(
308
+ all: bool = False,
309
+ volumes: bool = False,
310
+ filter: str = "",
311
+ ):
312
+ """
313
+ Execute a docker system prune.
314
+
315
+ + all: Remove all unused images not just dangling ones
316
+ + volumes: Prune anonymous volumes
317
+ + filter: Provide filter values (e.g. "label=<key>=<value>" or "until=24h")
318
+
319
+ **Examples:**
320
+
321
+ .. code:: python
322
+
323
+ # Remove dangling images
324
+ docker.prune(
325
+ name="remove dangling images",
326
+ )
327
+
328
+ # Remove all images and volumes
329
+ docker.prune(
330
+ name="Remove all images and volumes",
331
+ all=True,
332
+ volumes=True,
333
+ )
334
+
335
+ # Remove images older than 90 days
336
+ docker.prune(
337
+ name="Remove unused older than 90 days",
338
+ filter="until=2160h"
339
+ )
340
+ """
341
+
342
+ yield handle_docker(
343
+ resource="system",
344
+ command="prune",
345
+ all=all,
346
+ volumes=volumes,
347
+ filter=filter,
348
+ )
349
+
350
+
351
+ @operation()
352
+ def plugin(
353
+ plugin: str,
354
+ alias: str | None = None,
355
+ present: bool = True,
356
+ enabled: bool = True,
357
+ plugin_options: dict[str, str] | None = None,
358
+ ):
359
+ """
360
+ Manage Docker plugins
361
+
362
+ + plugin: Plugin name
363
+ + alias: Alias for the plugin (optional)
364
+ + present: Whether the plugin should be installed
365
+ + enabled: Whether the plugin should be enabled
366
+ + plugin_options: Options to pass to the plugin
367
+
368
+ **Examples:**
369
+
370
+ .. code:: python
371
+
372
+ # Install and enable a Docker plugin
373
+ docker.plugin(
374
+ name="Install and enable a Docker plugin",
375
+ plugin="username/my-awesome-plugin:latest",
376
+ alias="my-plugin",
377
+ present=True,
378
+ enabled=True,
379
+ plugin_options={"option1": "value1", "option2": "value2"},
380
+ )
381
+ """
382
+ plugin_name = alias if alias else plugin
383
+ existent_plugin = host.get_fact(DockerPlugin, object_id=plugin_name)
384
+ if existent_plugin:
385
+ existent_plugin = existent_plugin[0]
386
+
387
+ if present:
388
+ if existent_plugin:
389
+ plugin_options_different = (
390
+ plugin_options and existent_plugin["Settings"]["Env"] != plugin_options
391
+ )
392
+ if plugin_options_different:
393
+ # Update options on existing plugin
394
+ if existent_plugin["Enabled"]:
395
+ yield handle_docker(
396
+ resource="plugin",
397
+ command="disable",
398
+ plugin=plugin_name,
399
+ )
400
+ yield handle_docker(
401
+ resource="plugin",
402
+ command="set",
403
+ plugin=plugin_name,
404
+ enabled=enabled,
405
+ existent_options=existent_plugin["Settings"]["Env"],
406
+ required_options=plugin_options,
407
+ )
408
+ if enabled:
409
+ yield handle_docker(
410
+ resource="plugin",
411
+ command="enable",
412
+ plugin=plugin_name,
413
+ )
414
+ else:
415
+ # Options are the same, check if enabled state is different
416
+ if existent_plugin["Enabled"] == enabled:
417
+ host.noop(
418
+ f"Plugin '{plugin_name}' is already installed with the same options "
419
+ f"and {'enabled' if enabled else 'disabled'}."
420
+ )
421
+ return
422
+ else:
423
+ command = "enable" if enabled else "disable"
424
+ yield handle_docker(
425
+ resource="plugin",
426
+ command=command,
427
+ plugin=plugin_name,
428
+ )
429
+ else:
430
+ yield handle_docker(
431
+ resource="plugin",
432
+ command="install",
433
+ plugin=plugin,
434
+ alias=alias,
435
+ enabled=enabled,
436
+ plugin_options=plugin_options,
437
+ )
438
+ else:
439
+ if not existent_plugin:
440
+ host.noop(f"Plugin '{plugin_name}' is not installed.")
441
+ return
442
+ yield handle_docker(
443
+ resource="plugin",
444
+ command="remove",
445
+ plugin=plugin_name,
446
+ )