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,311 @@
1
+ """
2
+ The iptables modules handles iptables rules
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from pyinfra import host
8
+ from pyinfra.api import operation
9
+ from pyinfra.api.exceptions import OperationError
10
+ from pyinfra.facts.iptables import Ip6tablesChains, Ip6tablesRules, IptablesChains, IptablesRules
11
+
12
+
13
+ @operation()
14
+ def chain(
15
+ chain: str,
16
+ present=True,
17
+ table="filter",
18
+ policy: str | None = None,
19
+ version=4,
20
+ ):
21
+ """
22
+ Add/remove/update iptables chains.
23
+
24
+ + chain: the name of the chain
25
+ + present: whether the chain should exist
26
+ + table: the iptables table this chain should belong to
27
+ + policy: the policy this table should have
28
+ + version: whether to target iptables or ip6tables
29
+
30
+ Policy:
31
+ These can only be applied to system chains (FORWARD, INPUT, OUTPUT, etc).
32
+ """
33
+
34
+ chains = (
35
+ host.get_fact(IptablesChains, table=table)
36
+ if version == 4
37
+ else host.get_fact(Ip6tablesChains, table=table)
38
+ )
39
+
40
+ command = "iptables" if version == 4 else "ip6tables"
41
+ command = "{0} -t {1}".format(command, table)
42
+
43
+ if not present:
44
+ if chain in chains:
45
+ yield "{0} -X {1}".format(command, chain)
46
+ else:
47
+ host.noop("iptables chain {0} does not exist".format(chain))
48
+ return
49
+
50
+ if present:
51
+ if chain not in chains:
52
+ yield "{0} -N {1}".format(command, chain)
53
+ else:
54
+ host.noop("iptables chain {0} exists".format(chain))
55
+
56
+ if policy:
57
+ if chain not in chains or chains[chain] != policy:
58
+ yield "{0} -P {1} {2}".format(command, chain, policy)
59
+
60
+
61
+ @operation()
62
+ def rule(
63
+ chain: str,
64
+ jump: str,
65
+ present: bool = True,
66
+ table: str = "filter",
67
+ append: bool = True,
68
+ version: int = 4,
69
+ # Core iptables filter arguments
70
+ protocol: str | None = None,
71
+ not_protocol: str | None = None,
72
+ source: str | None = None,
73
+ not_source: str | None = None,
74
+ destination: str | None = None,
75
+ not_destination: str | None = None,
76
+ in_interface: str | None = None,
77
+ not_in_interface: str | None = None,
78
+ out_interface: str | None = None,
79
+ not_out_interface: str | None = None,
80
+ # After-rule arguments
81
+ to_destination: str | None = None,
82
+ to_source: str | None = None,
83
+ to_ports: int | str | None = None,
84
+ log_prefix: str | None = None,
85
+ # Extras and extra shortcuts
86
+ destination_port: int | None = None,
87
+ source_port: int | None = None,
88
+ extras: str = "",
89
+ ):
90
+ """
91
+ Add/remove iptables rules.
92
+
93
+ + chain: the chain this rule should live in
94
+ + jump: the target of the rule
95
+ + table: the iptables table this rule should belong to
96
+ + append: whether to append or insert the rule (if not present)
97
+ + version: whether to target iptables or ip6tables
98
+
99
+ Iptables args:
100
+
101
+ + protocol/not_protocol: filter by protocol (tcp or udp)
102
+ + source/not_source: filter by source IPs
103
+ + destination/not_destination: filter by destination IPs
104
+ + in_interface/not_in_interface: filter by incoming interface
105
+ + out_interface/not_out_interface: filter by outgoing interface
106
+ + to_destination: where to route to when jump=DNAT
107
+ + to_source: where to route to when jump=SNAT
108
+ + to_ports: where to route to when jump=REDIRECT
109
+ + log_prefix: prefix for the log of this rule when jump=LOG
110
+
111
+ Extras:
112
+
113
+ + extras: a place to define iptables extension arguments (eg --limit, --physdev)
114
+ + destination_port: destination port (requires protocol)
115
+ + source_port: source port (requires protocol)
116
+
117
+ **Examples:**
118
+
119
+ .. code:: python
120
+
121
+ iptables.rule(
122
+ name="Block SSH traffic",
123
+ chain="INPUT",
124
+ jump="DROP",
125
+ destination_port=22,
126
+ )
127
+
128
+ iptables.rule(
129
+ name="NAT traffic on from 8.8.8.8:53 to 8.8.4.4:8080",
130
+ chain="PREROUTING",
131
+ jump="DNAT",
132
+ table="nat",
133
+ source="8.8.8.8",
134
+ destination_port=53,
135
+ to_destination="8.8.4.4:8080",
136
+ )
137
+ """
138
+
139
+ if isinstance(to_ports, int):
140
+ to_ports = "{0}".format(to_ports)
141
+
142
+ # These are only shortcuts for extras
143
+ if destination_port:
144
+ extras = "{0} --dport {1}".format(extras, destination_port)
145
+
146
+ if source_port:
147
+ extras = "{0} --sport {1}".format(extras, source_port)
148
+
149
+ # Convert the extras string into a set to enable comparison with the fact
150
+ extras_set = set(extras.split())
151
+
152
+ # When protocol is set, the extension is automagically added by iptables (which shows
153
+ # in iptables-save): http://ipset.netfilter.org/iptables-extensions.man.html
154
+ if protocol:
155
+ protocol = protocol.lower()
156
+ extras_set.add("-m")
157
+ extras_set.add(protocol)
158
+
159
+ # --dport and --sport do not work without a protocol (because they need -m [tcp|udp]
160
+ elif destination_port or source_port:
161
+ raise OperationError(
162
+ "iptables cannot filter by destination_port/source_port without a protocol",
163
+ )
164
+
165
+ # Verify NAT arguments, --to-destination only w/table=nat, jump=DNAT
166
+ if to_destination and (table != "nat" or jump != "DNAT"):
167
+ raise OperationError(
168
+ "iptables only supports to_destination on the nat table and the DNAT jump "
169
+ "(table={0}, jump={1})".format(table, jump),
170
+ )
171
+
172
+ # As above, --to-source only w/table=nat, jump=SNAT
173
+ if to_source and (table != "nat" or jump != "SNAT"):
174
+ raise OperationError(
175
+ "iptables only supports to_source on the nat table and the SNAT jump "
176
+ "(table={0}, jump={1})".format(table, jump),
177
+ )
178
+
179
+ # As above, --to-ports only w/table=nat, jump=REDIRECT
180
+ if to_ports and (table != "nat" or jump != "REDIRECT"):
181
+ raise OperationError(
182
+ "iptables only supports to_ports on the nat table and the REDIRECT jump "
183
+ "(table={0}, jump={1})".format(table, jump),
184
+ )
185
+
186
+ # --log-prefix is only supported with jump=LOG
187
+ if log_prefix and jump != "LOG":
188
+ raise OperationError(
189
+ "iptables only supports log_prefix with the LOG jump (jump={0})".format(jump),
190
+ )
191
+
192
+ definition = {
193
+ "chain": chain,
194
+ "jump": jump,
195
+ "protocol": protocol,
196
+ "source": source,
197
+ "destination": destination,
198
+ "in_interface": in_interface,
199
+ "out_interface": out_interface,
200
+ "not_protocol": not_protocol,
201
+ "not_source": not_source,
202
+ "not_destination": not_destination,
203
+ "not_in_interface": not_in_interface,
204
+ "not_out_interface": not_out_interface,
205
+ # These go *after* the jump argument
206
+ "log_prefix": log_prefix,
207
+ "to_destination": to_destination,
208
+ "to_source": to_source,
209
+ "to_ports": to_ports,
210
+ "extras": extras_set,
211
+ }
212
+
213
+ definition = {
214
+ key: (
215
+ "{0}/32".format(value)
216
+ if (
217
+ key in ("source", "not_source", "destination", "not_destination")
218
+ and "/" not in value
219
+ )
220
+ else value
221
+ )
222
+ for key, value in definition.items()
223
+ if value
224
+ }
225
+
226
+ rules = (
227
+ host.get_fact(IptablesRules, table=table)
228
+ if version == 4
229
+ else host.get_fact(Ip6tablesRules, table=table)
230
+ )
231
+
232
+ action = None
233
+
234
+ # Definition doesn't exist and we want it
235
+ if present:
236
+ if definition not in rules:
237
+ action = "-A" if append else "-I"
238
+ else:
239
+ host.noop("iptables {0} rule exists".format(chain))
240
+ return
241
+
242
+ # Definition exists and we don't want it
243
+ if not present:
244
+ if definition in rules:
245
+ action = "-D"
246
+ else:
247
+ host.noop("iptables {0} rule does not exists".format(chain))
248
+ return
249
+
250
+ # Are we adding/removing a rule? Lets build it
251
+ if action:
252
+ args = [
253
+ "iptables" if version == 4 else "ip6tables",
254
+ # Add the table
255
+ "-t",
256
+ table,
257
+ # Add the action and target chain
258
+ action,
259
+ chain,
260
+ ]
261
+
262
+ if protocol:
263
+ args.extend(("-p", protocol))
264
+
265
+ if source:
266
+ args.extend(("-s", source))
267
+
268
+ if destination:
269
+ args.extend(("-d", destination))
270
+
271
+ if in_interface:
272
+ args.extend(("-i", in_interface))
273
+
274
+ if out_interface:
275
+ args.extend(("-o", out_interface))
276
+
277
+ if not_protocol:
278
+ args.extend(("!", "-p", not_protocol.lower()))
279
+
280
+ if not_source:
281
+ args.extend(("!", "-s", not_source))
282
+
283
+ if not_destination:
284
+ args.extend(("!", "-d", not_destination))
285
+
286
+ if not_in_interface:
287
+ args.extend(("!", "-i", not_in_interface))
288
+
289
+ if not_out_interface:
290
+ args.extend(("!", "-o", not_out_interface))
291
+
292
+ if extras:
293
+ args.append(extras.strip())
294
+
295
+ # Add the jump
296
+ args.extend(("-j", jump))
297
+
298
+ if log_prefix:
299
+ args.extend(("--log-prefix", log_prefix))
300
+
301
+ if to_destination:
302
+ args.extend(("--to-destination", to_destination))
303
+
304
+ if to_source:
305
+ args.extend(("--to-source", to_source))
306
+
307
+ if to_ports:
308
+ args.extend(("--to-ports", to_ports))
309
+
310
+ # Build the final iptables command
311
+ yield " ".join(args)
@@ -0,0 +1,45 @@
1
+ """
2
+ Manage launchd services.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from pyinfra import host
8
+ from pyinfra.api import operation
9
+ from pyinfra.facts.launchd import LaunchdStatus
10
+
11
+ from .util.service import handle_service_control
12
+
13
+
14
+ @operation()
15
+ def service(
16
+ service: str,
17
+ running=True,
18
+ restarted=False,
19
+ command: str | None = None,
20
+ ):
21
+ """
22
+ Manage the state of systemd managed services.
23
+
24
+ + service: name of the service to manage
25
+ + running: whether the service should be running
26
+ + restarted: whether the service should be restarted
27
+ + command: custom command to pass like: ``launchctl <command> <service>``
28
+ + enabled: whether this service should be enabled/disabled on boot
29
+ """
30
+
31
+ was_running = host.get_fact(LaunchdStatus).get(service, None)
32
+
33
+ yield from handle_service_control(
34
+ host,
35
+ service,
36
+ host.get_fact(LaunchdStatus),
37
+ "launchctl {1} {0}",
38
+ running,
39
+ # No support for restart/reload/command
40
+ )
41
+
42
+ # No restart command, so just stop/start
43
+ if restarted and was_running:
44
+ yield "launchctl stop {0}".format(service)
45
+ yield "launchctl start {0}".format(service)
@@ -0,0 +1,68 @@
1
+ """
2
+ The LXD modules manage LXD containers
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing import Any
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import operation
11
+ from pyinfra.facts.lxd import LxdContainers
12
+
13
+
14
+ def get_container_named(name: str, containers: list[dict[str, Any]]) -> dict[str, Any] | None:
15
+ for container in containers:
16
+ if container["name"] == name:
17
+ return container
18
+ return None
19
+
20
+
21
+ @operation()
22
+ def container(
23
+ id: str,
24
+ present=True,
25
+ image="ubuntu:16.04",
26
+ ):
27
+ """
28
+ Add/remove LXD containers.
29
+
30
+ Note: does not check if an existing container is based on the specified
31
+ image.
32
+
33
+ + id: name/identifier for the container
34
+ + image: image to base the container on
35
+ + present: whether the container should be present or absent
36
+
37
+ **Example:**
38
+
39
+ .. code:: python
40
+
41
+ lxd.container(
42
+ name="Add an ubuntu container",
43
+ id="ubuntu19",
44
+ image="ubuntu:19.10",
45
+ )
46
+ """
47
+
48
+ current_containers = host.get_fact(LxdContainers)
49
+ container = get_container_named(id, current_containers)
50
+
51
+ # Container exists and we don't want it
52
+ if not present:
53
+ if container:
54
+ if container["status"] == "Running":
55
+ yield "lxc stop {0}".format(id)
56
+
57
+ # Command to remove the container:
58
+ yield "lxc delete {0}".format(id)
59
+ else:
60
+ host.noop("container {0} does not exist".format(id))
61
+
62
+ # Container doesn't exist and we want it
63
+ if present:
64
+ if not container:
65
+ # Command to create the container:
66
+ yield "lxc launch {image} {id} < /dev/null".format(id=id, image=image)
67
+ else:
68
+ host.noop("container {0} exists".format(id))