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,312 @@
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
+ from pyinfra.operations import iptables
122
+ iptables.rule(
123
+ name="Block SSH traffic",
124
+ chain="INPUT",
125
+ jump="DROP",
126
+ destination_port=22,
127
+ )
128
+
129
+ iptables.rule(
130
+ name="NAT traffic on from 8.8.8.8:53 to 8.8.4.4:8080",
131
+ chain="PREROUTING",
132
+ jump="DNAT",
133
+ table="nat",
134
+ source="8.8.8.8",
135
+ destination_port=53,
136
+ to_destination="8.8.4.4:8080",
137
+ )
138
+ """
139
+
140
+ if isinstance(to_ports, int):
141
+ to_ports = "{0}".format(to_ports)
142
+
143
+ # These are only shortcuts for extras
144
+ if destination_port:
145
+ extras = "{0} --dport {1}".format(extras, destination_port)
146
+
147
+ if source_port:
148
+ extras = "{0} --sport {1}".format(extras, source_port)
149
+
150
+ # Convert the extras string into a set to enable comparison with the fact
151
+ extras_set = set(extras.split())
152
+
153
+ # When protocol is set, the extension is automagically added by iptables (which shows
154
+ # in iptables-save): http://ipset.netfilter.org/iptables-extensions.man.html
155
+ if protocol:
156
+ protocol = protocol.lower()
157
+ extras_set.add("-m")
158
+ extras_set.add(protocol)
159
+
160
+ # --dport and --sport do not work without a protocol (because they need -m [tcp|udp]
161
+ elif destination_port or source_port:
162
+ raise OperationError(
163
+ "iptables cannot filter by destination_port/source_port without a protocol",
164
+ )
165
+
166
+ # Verify NAT arguments, --to-destination only w/table=nat, jump=DNAT
167
+ if to_destination and (table != "nat" or jump != "DNAT"):
168
+ raise OperationError(
169
+ "iptables only supports to_destination on the nat table and the DNAT jump "
170
+ "(table={0}, jump={1})".format(table, jump),
171
+ )
172
+
173
+ # As above, --to-source only w/table=nat, jump=SNAT
174
+ if to_source and (table != "nat" or jump != "SNAT"):
175
+ raise OperationError(
176
+ "iptables only supports to_source on the nat table and the SNAT jump "
177
+ "(table={0}, jump={1})".format(table, jump),
178
+ )
179
+
180
+ # As above, --to-ports only w/table=nat, jump=REDIRECT
181
+ if to_ports and (table != "nat" or jump != "REDIRECT"):
182
+ raise OperationError(
183
+ "iptables only supports to_ports on the nat table and the REDIRECT jump "
184
+ "(table={0}, jump={1})".format(table, jump),
185
+ )
186
+
187
+ # --log-prefix is only supported with jump=LOG
188
+ if log_prefix and jump != "LOG":
189
+ raise OperationError(
190
+ "iptables only supports log_prefix with the LOG jump (jump={0})".format(jump),
191
+ )
192
+
193
+ definition = {
194
+ "chain": chain,
195
+ "jump": jump,
196
+ "protocol": protocol,
197
+ "source": source,
198
+ "destination": destination,
199
+ "in_interface": in_interface,
200
+ "out_interface": out_interface,
201
+ "not_protocol": not_protocol,
202
+ "not_source": not_source,
203
+ "not_destination": not_destination,
204
+ "not_in_interface": not_in_interface,
205
+ "not_out_interface": not_out_interface,
206
+ # These go *after* the jump argument
207
+ "log_prefix": log_prefix,
208
+ "to_destination": to_destination,
209
+ "to_source": to_source,
210
+ "to_ports": to_ports,
211
+ "extras": extras_set,
212
+ }
213
+
214
+ definition = {
215
+ key: (
216
+ "{0}/32".format(value)
217
+ if (
218
+ key in ("source", "not_source", "destination", "not_destination")
219
+ and "/" not in value
220
+ )
221
+ else value
222
+ )
223
+ for key, value in definition.items()
224
+ if value
225
+ }
226
+
227
+ rules = (
228
+ host.get_fact(IptablesRules, table=table)
229
+ if version == 4
230
+ else host.get_fact(Ip6tablesRules, table=table)
231
+ )
232
+
233
+ action = None
234
+
235
+ # Definition doesn't exist and we want it
236
+ if present:
237
+ if definition not in rules:
238
+ action = "-A" if append else "-I"
239
+ else:
240
+ host.noop("iptables {0} rule exists".format(chain))
241
+ return
242
+
243
+ # Definition exists and we don't want it
244
+ if not present:
245
+ if definition in rules:
246
+ action = "-D"
247
+ else:
248
+ host.noop("iptables {0} rule does not exists".format(chain))
249
+ return
250
+
251
+ # Are we adding/removing a rule? Lets build it
252
+ if action:
253
+ args = [
254
+ "iptables" if version == 4 else "ip6tables",
255
+ # Add the table
256
+ "-t",
257
+ table,
258
+ # Add the action and target chain
259
+ action,
260
+ chain,
261
+ ]
262
+
263
+ if protocol:
264
+ args.extend(("-p", protocol))
265
+
266
+ if source:
267
+ args.extend(("-s", source))
268
+
269
+ if destination:
270
+ args.extend(("-d", destination))
271
+
272
+ if in_interface:
273
+ args.extend(("-i", in_interface))
274
+
275
+ if out_interface:
276
+ args.extend(("-o", out_interface))
277
+
278
+ if not_protocol:
279
+ args.extend(("!", "-p", not_protocol.lower()))
280
+
281
+ if not_source:
282
+ args.extend(("!", "-s", not_source))
283
+
284
+ if not_destination:
285
+ args.extend(("!", "-d", not_destination))
286
+
287
+ if not_in_interface:
288
+ args.extend(("!", "-i", not_in_interface))
289
+
290
+ if not_out_interface:
291
+ args.extend(("!", "-o", not_out_interface))
292
+
293
+ if extras:
294
+ args.append(extras.strip())
295
+
296
+ # Add the jump
297
+ args.extend(("-j", jump))
298
+
299
+ if log_prefix:
300
+ args.extend(("--log-prefix", log_prefix))
301
+
302
+ if to_destination:
303
+ args.extend(("--to-destination", to_destination))
304
+
305
+ if to_source:
306
+ args.extend(("--to-source", to_source))
307
+
308
+ if to_ports:
309
+ args.extend(("--to-ports", to_ports))
310
+
311
+ # Build the final iptables command
312
+ 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,69 @@
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
+ from pyinfra.operations import lxd
42
+ lxd.container(
43
+ name="Add an ubuntu container",
44
+ id="ubuntu19",
45
+ image="ubuntu:19.10",
46
+ )
47
+ """
48
+
49
+ current_containers = host.get_fact(LxdContainers)
50
+ container = get_container_named(id, current_containers)
51
+
52
+ # Container exists and we don't want it
53
+ if not present:
54
+ if container:
55
+ if container["status"] == "Running":
56
+ yield "lxc stop {0}".format(id)
57
+
58
+ # Command to remove the container:
59
+ yield "lxc delete {0}".format(id)
60
+ else:
61
+ host.noop("container {0} does not exist".format(id))
62
+
63
+ # Container doesn't exist and we want it
64
+ if present:
65
+ if not container:
66
+ # Command to create the container:
67
+ yield "lxc launch {image} {id} < /dev/null".format(id=id, image=image)
68
+ else:
69
+ host.noop("container {0} exists".format(id))