pyinfra 0.11.dev3__py3-none-any.whl → 3.6__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 (204) hide show
  1. pyinfra/__init__.py +9 -12
  2. pyinfra/__main__.py +4 -0
  3. pyinfra/api/__init__.py +19 -3
  4. pyinfra/api/arguments.py +413 -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 +73 -18
  12. pyinfra/api/facts.py +267 -200
  13. pyinfra/api/host.py +416 -50
  14. pyinfra/api/inventory.py +121 -160
  15. pyinfra/api/metadata.py +69 -0
  16. pyinfra/api/operation.py +432 -262
  17. pyinfra/api/operations.py +273 -260
  18. pyinfra/api/state.py +302 -248
  19. pyinfra/api/util.py +309 -369
  20. pyinfra/connectors/base.py +173 -0
  21. pyinfra/connectors/chroot.py +212 -0
  22. pyinfra/connectors/docker.py +405 -0
  23. pyinfra/connectors/dockerssh.py +297 -0
  24. pyinfra/connectors/local.py +238 -0
  25. pyinfra/connectors/scp/__init__.py +1 -0
  26. pyinfra/connectors/scp/client.py +204 -0
  27. pyinfra/connectors/ssh.py +727 -0
  28. pyinfra/connectors/ssh_util.py +114 -0
  29. pyinfra/connectors/sshuserclient/client.py +309 -0
  30. pyinfra/connectors/sshuserclient/config.py +102 -0
  31. pyinfra/connectors/terraform.py +135 -0
  32. pyinfra/connectors/util.py +417 -0
  33. pyinfra/connectors/vagrant.py +183 -0
  34. pyinfra/context.py +145 -0
  35. pyinfra/facts/__init__.py +7 -6
  36. pyinfra/facts/apk.py +22 -7
  37. pyinfra/facts/apt.py +117 -60
  38. pyinfra/facts/brew.py +100 -15
  39. pyinfra/facts/bsdinit.py +23 -0
  40. pyinfra/facts/cargo.py +37 -0
  41. pyinfra/facts/choco.py +47 -0
  42. pyinfra/facts/crontab.py +195 -0
  43. pyinfra/facts/deb.py +94 -0
  44. pyinfra/facts/dnf.py +48 -0
  45. pyinfra/facts/docker.py +96 -23
  46. pyinfra/facts/efibootmgr.py +113 -0
  47. pyinfra/facts/files.py +629 -58
  48. pyinfra/facts/flatpak.py +77 -0
  49. pyinfra/facts/freebsd.py +70 -0
  50. pyinfra/facts/gem.py +19 -6
  51. pyinfra/facts/git.py +59 -14
  52. pyinfra/facts/gpg.py +150 -0
  53. pyinfra/facts/hardware.py +313 -167
  54. pyinfra/facts/iptables.py +72 -62
  55. pyinfra/facts/launchd.py +44 -0
  56. pyinfra/facts/lxd.py +17 -4
  57. pyinfra/facts/mysql.py +122 -86
  58. pyinfra/facts/npm.py +17 -9
  59. pyinfra/facts/openrc.py +71 -0
  60. pyinfra/facts/opkg.py +246 -0
  61. pyinfra/facts/pacman.py +50 -7
  62. pyinfra/facts/pip.py +24 -7
  63. pyinfra/facts/pipx.py +82 -0
  64. pyinfra/facts/pkg.py +15 -6
  65. pyinfra/facts/pkgin.py +35 -0
  66. pyinfra/facts/podman.py +54 -0
  67. pyinfra/facts/postgres.py +178 -0
  68. pyinfra/facts/postgresql.py +6 -147
  69. pyinfra/facts/rpm.py +105 -0
  70. pyinfra/facts/runit.py +77 -0
  71. pyinfra/facts/selinux.py +161 -0
  72. pyinfra/facts/server.py +762 -285
  73. pyinfra/facts/snap.py +88 -0
  74. pyinfra/facts/systemd.py +139 -0
  75. pyinfra/facts/sysvinit.py +59 -0
  76. pyinfra/facts/upstart.py +35 -0
  77. pyinfra/facts/util/__init__.py +17 -0
  78. pyinfra/facts/util/databases.py +4 -6
  79. pyinfra/facts/util/packaging.py +37 -6
  80. pyinfra/facts/util/units.py +30 -0
  81. pyinfra/facts/util/win_files.py +99 -0
  82. pyinfra/facts/vzctl.py +20 -13
  83. pyinfra/facts/xbps.py +35 -0
  84. pyinfra/facts/yum.py +34 -40
  85. pyinfra/facts/zfs.py +77 -0
  86. pyinfra/facts/zypper.py +42 -0
  87. pyinfra/local.py +45 -83
  88. pyinfra/operations/__init__.py +12 -0
  89. pyinfra/operations/apk.py +99 -0
  90. pyinfra/operations/apt.py +496 -0
  91. pyinfra/operations/brew.py +232 -0
  92. pyinfra/operations/bsdinit.py +59 -0
  93. pyinfra/operations/cargo.py +45 -0
  94. pyinfra/operations/choco.py +61 -0
  95. pyinfra/operations/crontab.py +194 -0
  96. pyinfra/operations/dnf.py +213 -0
  97. pyinfra/operations/docker.py +492 -0
  98. pyinfra/operations/files.py +2014 -0
  99. pyinfra/operations/flatpak.py +95 -0
  100. pyinfra/operations/freebsd/__init__.py +12 -0
  101. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  102. pyinfra/operations/freebsd/pkg.py +219 -0
  103. pyinfra/operations/freebsd/service.py +116 -0
  104. pyinfra/operations/freebsd/sysrc.py +92 -0
  105. pyinfra/operations/gem.py +48 -0
  106. pyinfra/operations/git.py +420 -0
  107. pyinfra/operations/iptables.py +312 -0
  108. pyinfra/operations/launchd.py +45 -0
  109. pyinfra/operations/lxd.py +69 -0
  110. pyinfra/operations/mysql.py +610 -0
  111. pyinfra/operations/npm.py +57 -0
  112. pyinfra/operations/openrc.py +63 -0
  113. pyinfra/operations/opkg.py +89 -0
  114. pyinfra/operations/pacman.py +82 -0
  115. pyinfra/operations/pip.py +206 -0
  116. pyinfra/operations/pipx.py +103 -0
  117. pyinfra/operations/pkg.py +71 -0
  118. pyinfra/operations/pkgin.py +92 -0
  119. pyinfra/operations/postgres.py +437 -0
  120. pyinfra/operations/postgresql.py +30 -0
  121. pyinfra/operations/puppet.py +41 -0
  122. pyinfra/operations/python.py +73 -0
  123. pyinfra/operations/runit.py +184 -0
  124. pyinfra/operations/selinux.py +190 -0
  125. pyinfra/operations/server.py +1100 -0
  126. pyinfra/operations/snap.py +118 -0
  127. pyinfra/operations/ssh.py +217 -0
  128. pyinfra/operations/systemd.py +150 -0
  129. pyinfra/operations/sysvinit.py +142 -0
  130. pyinfra/operations/upstart.py +68 -0
  131. pyinfra/operations/util/__init__.py +12 -0
  132. pyinfra/operations/util/docker.py +407 -0
  133. pyinfra/operations/util/files.py +247 -0
  134. pyinfra/operations/util/packaging.py +338 -0
  135. pyinfra/operations/util/service.py +46 -0
  136. pyinfra/operations/vzctl.py +137 -0
  137. pyinfra/operations/xbps.py +78 -0
  138. pyinfra/operations/yum.py +213 -0
  139. pyinfra/operations/zfs.py +176 -0
  140. pyinfra/operations/zypper.py +193 -0
  141. pyinfra/progress.py +44 -32
  142. pyinfra/py.typed +0 -0
  143. pyinfra/version.py +9 -1
  144. pyinfra-3.6.dist-info/METADATA +142 -0
  145. pyinfra-3.6.dist-info/RECORD +160 -0
  146. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info}/WHEEL +1 -2
  147. pyinfra-3.6.dist-info/entry_points.txt +12 -0
  148. {pyinfra-0.11.dev3.dist-info → pyinfra-3.6.dist-info/licenses}/LICENSE.md +1 -1
  149. pyinfra_cli/__init__.py +1 -0
  150. pyinfra_cli/cli.py +793 -0
  151. pyinfra_cli/commands.py +66 -0
  152. pyinfra_cli/exceptions.py +155 -65
  153. pyinfra_cli/inventory.py +233 -89
  154. pyinfra_cli/log.py +39 -43
  155. pyinfra_cli/main.py +26 -495
  156. pyinfra_cli/prints.py +215 -156
  157. pyinfra_cli/util.py +172 -105
  158. pyinfra_cli/virtualenv.py +25 -20
  159. pyinfra/api/connectors/__init__.py +0 -21
  160. pyinfra/api/connectors/ansible.py +0 -99
  161. pyinfra/api/connectors/docker.py +0 -178
  162. pyinfra/api/connectors/local.py +0 -169
  163. pyinfra/api/connectors/ssh.py +0 -402
  164. pyinfra/api/connectors/sshuserclient/client.py +0 -105
  165. pyinfra/api/connectors/sshuserclient/config.py +0 -90
  166. pyinfra/api/connectors/util.py +0 -63
  167. pyinfra/api/connectors/vagrant.py +0 -155
  168. pyinfra/facts/init.py +0 -176
  169. pyinfra/facts/util/files.py +0 -102
  170. pyinfra/hook.py +0 -41
  171. pyinfra/modules/__init__.py +0 -11
  172. pyinfra/modules/apk.py +0 -64
  173. pyinfra/modules/apt.py +0 -272
  174. pyinfra/modules/brew.py +0 -122
  175. pyinfra/modules/files.py +0 -711
  176. pyinfra/modules/gem.py +0 -30
  177. pyinfra/modules/git.py +0 -115
  178. pyinfra/modules/init.py +0 -344
  179. pyinfra/modules/iptables.py +0 -271
  180. pyinfra/modules/lxd.py +0 -45
  181. pyinfra/modules/mysql.py +0 -347
  182. pyinfra/modules/npm.py +0 -47
  183. pyinfra/modules/pacman.py +0 -60
  184. pyinfra/modules/pip.py +0 -99
  185. pyinfra/modules/pkg.py +0 -43
  186. pyinfra/modules/postgresql.py +0 -245
  187. pyinfra/modules/puppet.py +0 -20
  188. pyinfra/modules/python.py +0 -37
  189. pyinfra/modules/server.py +0 -524
  190. pyinfra/modules/ssh.py +0 -150
  191. pyinfra/modules/util/files.py +0 -52
  192. pyinfra/modules/util/packaging.py +0 -118
  193. pyinfra/modules/vzctl.py +0 -133
  194. pyinfra/modules/yum.py +0 -171
  195. pyinfra/pseudo_modules.py +0 -64
  196. pyinfra-0.11.dev3.dist-info/.DS_Store +0 -0
  197. pyinfra-0.11.dev3.dist-info/METADATA +0 -135
  198. pyinfra-0.11.dev3.dist-info/RECORD +0 -95
  199. pyinfra-0.11.dev3.dist-info/entry_points.txt +0 -3
  200. pyinfra-0.11.dev3.dist-info/top_level.txt +0 -2
  201. pyinfra_cli/__main__.py +0 -40
  202. pyinfra_cli/config.py +0 -92
  203. /pyinfra/{modules/util → connectors}/__init__.py +0 -0
  204. /pyinfra/{api/connectors → connectors}/sshuserclient/__init__.py +0 -0
@@ -0,0 +1,213 @@
1
+ """
2
+ Manage dnf packages and repositories. Note that dnf package names are case-sensitive.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from pyinfra import host, state
8
+ from pyinfra.api import operation
9
+ from pyinfra.facts.rpm import RpmPackageProvides, RpmPackages
10
+
11
+ from .util.packaging import ensure_packages, ensure_rpm, ensure_yum_repo
12
+
13
+
14
+ @operation(is_idempotent=False)
15
+ def key(src: str):
16
+ """
17
+ Add dnf gpg keys with ``rpm``.
18
+
19
+ + key: filename or URL
20
+
21
+ Note:
22
+ always returns one command, not idempotent
23
+
24
+ **Example:**
25
+
26
+ .. code:: python
27
+
28
+ from pyinfra import host
29
+ from pyinfra.operations import dnf
30
+ from pyinfra.facts.server import LinuxDistribution
31
+ linux_id = host.get_fact(LinuxDistribution)["release_meta"].get("ID")
32
+ dnf.key(
33
+ name="Add the Docker gpg key",
34
+ src=f"https://download.docker.com/linux/{linux_id}/gpg",
35
+ )
36
+
37
+ """
38
+
39
+ yield "rpm --import {0}".format(src)
40
+
41
+
42
+ @operation()
43
+ def repo(
44
+ src: str,
45
+ present=True,
46
+ baseurl: str | None = None,
47
+ description: str | None = None,
48
+ enabled=True,
49
+ gpgcheck=True,
50
+ gpgkey: str | None = None,
51
+ ):
52
+ # NOTE: if updating this docstring also update `yum.repo`
53
+ """
54
+ Add/remove/update dnf repositories.
55
+
56
+ + src: URL or name for the ``.repo`` file
57
+ + present: whether the ``.repo`` file should be present
58
+ + baseurl: the baseurl of the repo (if ``name`` is not a URL)
59
+ + description: optional verbose description
60
+ + enabled: whether this repo is enabled
61
+ + gpgcheck: whether set ``gpgcheck=1``
62
+ + gpgkey: the URL to the gpg key for this repo
63
+
64
+ ``Baseurl``/``description``/``gpgcheck``/``gpgkey``:
65
+ These are only valid when ``name`` is a filename (ie not a URL). This is
66
+ for manual construction of repository files. Use a URL to download and
67
+ install remote repository files.
68
+
69
+ **Examples:**
70
+
71
+ .. code:: python
72
+
73
+ # Download a repository file
74
+ dnf.rpm(
75
+ name="Install Docker-CE repo via URL",
76
+ src="https://download.docker.com/linux/centos/docker-ce.repo",
77
+ )
78
+
79
+ # Create the repository file from baseurl/etc
80
+ dnf.repo(
81
+ name="Add the Docker CentOS repo",
82
+ src="DockerCE",
83
+ baseurl="https://download.docker.com/linux/centos/7/$basearch/stable",
84
+ )
85
+ """
86
+
87
+ yield from ensure_yum_repo(
88
+ host,
89
+ src,
90
+ baseurl,
91
+ present,
92
+ description,
93
+ enabled,
94
+ gpgcheck,
95
+ gpgkey,
96
+ )
97
+
98
+
99
+ @operation()
100
+ def rpm(src: str, present=True):
101
+ # NOTE: if updating this docstring also update `yum.rpm`
102
+ """
103
+ Add/remove ``.rpm`` file packages.
104
+
105
+ + src: filename or URL of the ``.rpm`` package
106
+ + present: whether ore not the package should exist on the system
107
+
108
+ URL sources with ``present=False``:
109
+ If the ``.rpm`` file isn't downloaded, pyinfra can't remove any existing
110
+ package as the file won't exist until mid-deploy.
111
+
112
+ **Example:**
113
+
114
+ .. code:: python
115
+
116
+ major_centos_version = host.get_fact(LinuxDistribution)["major"]
117
+ dnf.rpm(
118
+ name="Install EPEL rpm to enable EPEL repo",
119
+ src=f"https://dl.fedoraproject.org/pub/epel/epel-release-latest-{major_centos_version}.noarch.rpm",
120
+ )
121
+ """
122
+
123
+ yield from ensure_rpm(state, host, src, present, "dnf")
124
+
125
+
126
+ @operation(is_idempotent=False)
127
+ def update():
128
+ """
129
+ Updates all dnf packages.
130
+ """
131
+
132
+ yield "dnf update -y"
133
+
134
+
135
+ _update = update._inner # noqa: E305 (for use below where update is a kwarg)
136
+
137
+
138
+ @operation()
139
+ def packages(
140
+ packages: str | list[str] | None = None,
141
+ present=True,
142
+ latest=False,
143
+ update=False,
144
+ clean=False,
145
+ nobest=False,
146
+ extra_install_args: str | None = None,
147
+ extra_uninstall_args: str | None = None,
148
+ ):
149
+ """
150
+ Install/remove/update dnf packages & updates.
151
+
152
+ + packages: packages to ensure
153
+ + present: whether the packages should be installed
154
+ + latest: whether to upgrade packages without a specified version
155
+ + update: run ``dnf update`` before installing packages
156
+ + clean: run ``dnf clean`` before installing packages
157
+ + nobest: add the no best option to install
158
+ + extra_install_args: additional arguments to the dnf install command
159
+ + extra_uninstall_args: additional arguments to the dnf uninstall command
160
+
161
+ Versions:
162
+ Package versions can be pinned as follows: ``<pkg>=<version>``
163
+
164
+ **Examples:**
165
+
166
+ .. code:: python
167
+
168
+ # Update package list and install packages
169
+ dnf.packages(
170
+ name='Install Vim and Vim enhanced',
171
+ packages=["vim-enhanced", "vim"],
172
+ update=True,
173
+ )
174
+
175
+ # Install the latest versions of packages (always check)
176
+ dnf.packages(
177
+ name="Install latest Vim",
178
+ packages=["vim"],
179
+ latest=True,
180
+ )
181
+ """
182
+
183
+ if clean:
184
+ yield "dnf clean all"
185
+
186
+ if update:
187
+ yield from _update()
188
+
189
+ install_command = ["dnf", "install", "-y"]
190
+
191
+ if nobest:
192
+ install_command.append("--nobest")
193
+
194
+ if extra_install_args:
195
+ install_command.append(extra_install_args)
196
+
197
+ uninstall_command = ["dnf", "remove", "-y"]
198
+
199
+ if extra_uninstall_args:
200
+ uninstall_command.append(extra_uninstall_args)
201
+
202
+ yield from ensure_packages(
203
+ host,
204
+ packages,
205
+ host.get_fact(RpmPackages),
206
+ present,
207
+ install_command=" ".join(install_command),
208
+ uninstall_command=" ".join(uninstall_command),
209
+ upgrade_command="dnf update -y",
210
+ version_join="=",
211
+ latest=latest,
212
+ expand_package_fact=lambda package: host.get_fact(RpmPackageProvides, package=package),
213
+ )
@@ -0,0 +1,492 @@
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 (
12
+ DockerContainer,
13
+ DockerImage,
14
+ DockerNetwork,
15
+ DockerPlugin,
16
+ DockerVolume,
17
+ )
18
+
19
+ from .util.docker import ContainerSpec, handle_docker, parse_image_reference
20
+
21
+
22
+ @operation()
23
+ def container(
24
+ container: str,
25
+ image: str = "",
26
+ ports: list[str] | None = None,
27
+ networks: list[str] | None = None,
28
+ volumes: list[str] | None = None,
29
+ env_vars: list[str] | None = None,
30
+ labels: list[str] | None = None,
31
+ pull_always: bool = False,
32
+ present: bool = True,
33
+ force: bool = False,
34
+ start: bool = True,
35
+ ):
36
+ """
37
+ Manage Docker containers
38
+
39
+ + container: name to identify the container
40
+ + image: container image and tag ex: nginx:alpine
41
+ + networks: network list to attach on container
42
+ + ports: port list to expose
43
+ + volumes: volume list to map on container
44
+ + env_vars: environment variable list to inject on container
45
+ + labels: Label list to attach to the container
46
+ + pull_always: force image pull
47
+ + force: remove a container with same name and create a new one
48
+ + present: whether the container should be up and running
49
+ + start: start or stop the container
50
+
51
+ **Examples:**
52
+
53
+ .. code:: python
54
+
55
+ from pyinfra.operations import docker
56
+ # Run a container
57
+ docker.container(
58
+ name="Deploy Nginx container",
59
+ container="nginx",
60
+ image="nginx:alpine",
61
+ ports=["80:80"],
62
+ present=True,
63
+ force=True,
64
+ networks=["proxy", "services"],
65
+ volumes=["nginx_data:/usr/share/nginx/html"],
66
+ pull_always=True,
67
+ )
68
+
69
+ # Stop a container
70
+ docker.container(
71
+ name="Stop Nginx container",
72
+ container="nginx",
73
+ start=False,
74
+ )
75
+
76
+ # Start a container
77
+ docker.container(
78
+ name="Start Nginx container",
79
+ container="nginx",
80
+ start=True,
81
+ )
82
+ """
83
+
84
+ want_spec = ContainerSpec(
85
+ image,
86
+ ports or list(),
87
+ networks or list(),
88
+ volumes or list(),
89
+ env_vars or list(),
90
+ labels or list(),
91
+ pull_always,
92
+ )
93
+ existent_container = host.get_fact(DockerContainer, object_id=container)
94
+
95
+ container_spec_changes = want_spec.diff_from_inspect(existent_container)
96
+
97
+ is_running = (
98
+ (existent_container[0]["State"]["Status"] == "running")
99
+ if existent_container and existent_container[0]
100
+ else False
101
+ )
102
+ recreating = existent_container and (force or container_spec_changes)
103
+ removing = existent_container and not present
104
+
105
+ do_remove = recreating or removing
106
+ do_create = (present and not existent_container) or recreating
107
+ do_start = start and (recreating or not is_running)
108
+ do_stop = not start and not removing and is_running
109
+
110
+ if do_remove:
111
+ yield handle_docker(
112
+ resource="container",
113
+ command="remove",
114
+ container=container,
115
+ )
116
+
117
+ if do_create:
118
+ yield handle_docker(
119
+ resource="container",
120
+ command="create",
121
+ container=container,
122
+ spec=want_spec,
123
+ )
124
+
125
+ if do_start:
126
+ yield handle_docker(
127
+ resource="container",
128
+ command="start",
129
+ container=container,
130
+ )
131
+
132
+ if do_stop:
133
+ yield handle_docker(
134
+ resource="container",
135
+ command="stop",
136
+ container=container,
137
+ )
138
+
139
+
140
+ @operation()
141
+ def image(image: str, present: bool = True, force: bool = False):
142
+ """
143
+ Manage Docker images
144
+
145
+ + image: Image and tag ex: nginx:alpine
146
+ + present: whether the Docker image should exist
147
+ + force: always pull the image if present is True
148
+
149
+ **Examples:**
150
+
151
+ .. code:: python
152
+
153
+ # Pull a Docker image
154
+ docker.image(
155
+ name="Pull nginx image",
156
+ image="nginx:alpine",
157
+ present=True,
158
+ )
159
+
160
+ # Remove a Docker image
161
+ docker.image(
162
+ name="Remove nginx image",
163
+ image:"nginx:image",
164
+ present=False,
165
+ )
166
+ """
167
+ image_info = parse_image_reference(image)
168
+ if present:
169
+ if force:
170
+ # always pull the image if force is True
171
+ yield handle_docker(
172
+ resource="image",
173
+ command="pull",
174
+ image=image,
175
+ )
176
+ return
177
+ else:
178
+ existent_image = host.get_fact(DockerImage, object_id=image)
179
+ if image_info.digest:
180
+ # If a digest is specified, we must ensure the exact image is present
181
+ if existent_image:
182
+ host.noop(f"Image with digest {image_info.digest} already exists!")
183
+ else:
184
+ yield handle_docker(
185
+ resource="image",
186
+ command="pull",
187
+ image=image,
188
+ )
189
+ elif image_info.tag == "latest" or not image_info.tag:
190
+ # If the tag is 'latest' or not specified, always pull to ensure freshness
191
+ yield handle_docker(
192
+ resource="image",
193
+ command="pull",
194
+ image=image,
195
+ )
196
+ else:
197
+ # For other tags, check if the image exists
198
+ if existent_image:
199
+ host.noop(f"Image with tag {image_info.tag} already exists!")
200
+ else:
201
+ yield handle_docker(
202
+ resource="image",
203
+ command="pull",
204
+ image=image,
205
+ )
206
+ else:
207
+ existent_image = host.get_fact(DockerImage, object_id=image)
208
+ if existent_image:
209
+ yield handle_docker(
210
+ resource="image",
211
+ command="remove",
212
+ image=image,
213
+ )
214
+ else:
215
+ host.noop("There is no {0} image!".format(image))
216
+
217
+
218
+ @operation()
219
+ def volume(volume: str, driver: str = "", labels: list[str] | None = None, present: bool = True):
220
+ """
221
+ Manage Docker volumes
222
+
223
+ + volume: Volume name
224
+ + driver: Docker volume storage driver
225
+ + labels: Label list to attach in the volume
226
+ + present: whether the Docker volume should exist
227
+
228
+ **Examples:**
229
+
230
+ .. code:: python
231
+
232
+ # Create a Docker volume
233
+ docker.volume(
234
+ name="Create nginx volume",
235
+ volume="nginx_data",
236
+ present=True
237
+ )
238
+ """
239
+
240
+ existent_volume = host.get_fact(DockerVolume, object_id=volume)
241
+
242
+ if present:
243
+ if existent_volume:
244
+ host.noop("Volume already exists!")
245
+ return
246
+
247
+ yield handle_docker(
248
+ resource="volume",
249
+ command="create",
250
+ volume=volume,
251
+ driver=driver,
252
+ labels=labels,
253
+ present=present,
254
+ )
255
+
256
+ else:
257
+ if existent_volume is None:
258
+ host.noop("There is no {0} volume!".format(volume))
259
+ return
260
+
261
+ yield handle_docker(
262
+ resource="volume",
263
+ command="remove",
264
+ volume=volume,
265
+ )
266
+
267
+
268
+ @operation()
269
+ def network(
270
+ network: str,
271
+ driver: str = "",
272
+ gateway: str = "",
273
+ ip_range: str = "",
274
+ ipam_driver: str = "",
275
+ subnet: str = "",
276
+ scope: str = "",
277
+ aux_addresses: dict[str, str] | None = None,
278
+ opts: list[str] | None = None,
279
+ ipam_opts: list[str] | None = None,
280
+ labels: list[str] | None = None,
281
+ ingress: bool = False,
282
+ attachable: bool = False,
283
+ present: bool = True,
284
+ ):
285
+ """
286
+ Manage docker networks
287
+
288
+ + network: Network name
289
+ + driver: Network driver ex: bridge or overlay
290
+ + gateway: IPv4 or IPv6 Gateway for the master subnet
291
+ + ip_range: Allocate container ip from a sub-range
292
+ + ipam_driver: IP Address Management Driver
293
+ + subnet: Subnet in CIDR format that represents a network segment
294
+ + scope: Control the network's scope
295
+ + aux_addresses: named aux addresses for the network
296
+ + opts: Set driver specific options
297
+ + ipam_opts: Set IPAM driver specific options
298
+ + labels: Label list to attach in the network
299
+ + ingress: Create swarm routing-mesh network
300
+ + attachable: Enable manual container attachment
301
+ + present: whether the Docker network should exist
302
+
303
+ **Examples:**
304
+
305
+ .. code:: python
306
+
307
+ # Create Docker network
308
+ docker.network(
309
+ network="nginx",
310
+ attachable=True,
311
+ present=True,
312
+ )
313
+ """
314
+ existent_network = host.get_fact(DockerNetwork, object_id=network)
315
+
316
+ if present:
317
+ if existent_network:
318
+ host.noop("Network {0} already exists!".format(network))
319
+ return
320
+
321
+ yield handle_docker(
322
+ resource="network",
323
+ command="create",
324
+ network=network,
325
+ driver=driver,
326
+ gateway=gateway,
327
+ ip_range=ip_range,
328
+ ipam_driver=ipam_driver,
329
+ subnet=subnet,
330
+ scope=scope,
331
+ aux_addresses=aux_addresses,
332
+ opts=opts,
333
+ ipam_opts=ipam_opts,
334
+ labels=labels,
335
+ ingress=ingress,
336
+ attachable=attachable,
337
+ present=present,
338
+ )
339
+
340
+ else:
341
+ if existent_network is None:
342
+ host.noop("Network {0} does not exist!".format(network))
343
+ return
344
+
345
+ yield handle_docker(
346
+ resource="network",
347
+ command="remove",
348
+ network=network,
349
+ )
350
+
351
+
352
+ @operation(is_idempotent=False)
353
+ def prune(
354
+ all: bool = False,
355
+ volumes: bool = False,
356
+ filter: str = "",
357
+ ):
358
+ """
359
+ Execute a docker system prune.
360
+
361
+ + all: Remove all unused images not just dangling ones
362
+ + volumes: Prune anonymous volumes
363
+ + filter: Provide filter values (e.g. "label=<key>=<value>" or "until=24h")
364
+
365
+ **Examples:**
366
+
367
+ .. code:: python
368
+
369
+ # Remove dangling images
370
+ docker.prune(
371
+ name="remove dangling images",
372
+ )
373
+
374
+ # Remove all images and volumes
375
+ docker.prune(
376
+ name="Remove all images and volumes",
377
+ all=True,
378
+ volumes=True,
379
+ )
380
+
381
+ # Remove images older than 90 days
382
+ docker.prune(
383
+ name="Remove unused older than 90 days",
384
+ filter="until=2160h"
385
+ )
386
+ """
387
+
388
+ yield handle_docker(
389
+ resource="system",
390
+ command="prune",
391
+ all=all,
392
+ volumes=volumes,
393
+ filter=filter,
394
+ )
395
+
396
+
397
+ @operation()
398
+ def plugin(
399
+ plugin: str,
400
+ alias: str | None = None,
401
+ present: bool = True,
402
+ enabled: bool = True,
403
+ plugin_options: dict[str, str] | None = None,
404
+ ):
405
+ """
406
+ Manage Docker plugins
407
+
408
+ + plugin: Plugin name
409
+ + alias: Alias for the plugin (optional)
410
+ + present: Whether the plugin should be installed
411
+ + enabled: Whether the plugin should be enabled
412
+ + plugin_options: Options to pass to the plugin
413
+
414
+ **Examples:**
415
+
416
+ .. code:: python
417
+
418
+ # Install and enable a Docker plugin
419
+ docker.plugin(
420
+ name="Install and enable a Docker plugin",
421
+ plugin="username/my-awesome-plugin:latest",
422
+ alias="my-plugin",
423
+ present=True,
424
+ enabled=True,
425
+ plugin_options={"option1": "value1", "option2": "value2"},
426
+ )
427
+ """
428
+ plugin_name = alias if alias else plugin
429
+ existent_plugin = host.get_fact(DockerPlugin, object_id=plugin_name)
430
+ if existent_plugin:
431
+ existent_plugin = existent_plugin[0]
432
+
433
+ if present:
434
+ if existent_plugin:
435
+ plugin_options_different = (
436
+ plugin_options and existent_plugin["Settings"]["Env"] != plugin_options
437
+ )
438
+ if plugin_options_different:
439
+ # Update options on existing plugin
440
+ if existent_plugin["Enabled"]:
441
+ yield handle_docker(
442
+ resource="plugin",
443
+ command="disable",
444
+ plugin=plugin_name,
445
+ )
446
+ yield handle_docker(
447
+ resource="plugin",
448
+ command="set",
449
+ plugin=plugin_name,
450
+ enabled=enabled,
451
+ existent_options=existent_plugin["Settings"]["Env"],
452
+ required_options=plugin_options,
453
+ )
454
+ if enabled:
455
+ yield handle_docker(
456
+ resource="plugin",
457
+ command="enable",
458
+ plugin=plugin_name,
459
+ )
460
+ else:
461
+ # Options are the same, check if enabled state is different
462
+ if existent_plugin["Enabled"] == enabled:
463
+ host.noop(
464
+ f"Plugin '{plugin_name}' is already installed with the same options "
465
+ f"and {'enabled' if enabled else 'disabled'}."
466
+ )
467
+ return
468
+ else:
469
+ command = "enable" if enabled else "disable"
470
+ yield handle_docker(
471
+ resource="plugin",
472
+ command=command,
473
+ plugin=plugin_name,
474
+ )
475
+ else:
476
+ yield handle_docker(
477
+ resource="plugin",
478
+ command="install",
479
+ plugin=plugin,
480
+ alias=alias,
481
+ enabled=enabled,
482
+ plugin_options=plugin_options,
483
+ )
484
+ else:
485
+ if not existent_plugin:
486
+ host.noop(f"Plugin '{plugin_name}' is not installed.")
487
+ return
488
+ yield handle_docker(
489
+ resource="plugin",
490
+ command="remove",
491
+ plugin=plugin_name,
492
+ )