pyinfra 3.0b2__py2.py3-none-any.whl → 3.0b4__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.
pyinfra/connectors/ssh.py CHANGED
@@ -122,14 +122,18 @@ class SSHConnector(BaseConnector):
122
122
  .. code:: python
123
123
 
124
124
  hosts = (
125
- [
126
- "my-host-1.net",
127
- "my-host-2.net",
128
- ],
129
- {
130
- "ssh_username": "ssh-user",
131
- },
125
+ ["my-host-1.net", "my-host-2.net"],
126
+ {"ssh_username": "ssh-user"},
132
127
  )
128
+
129
+ Multiple hosts with different SSH usernames:
130
+
131
+ .. code:: python
132
+
133
+ hosts = [
134
+ ("my-host-1.net", {"ssh_username": "ssh-user"}),
135
+ ("my-host-2.net", {"ssh_username": "other-user"}),
136
+ ]
133
137
  """
134
138
 
135
139
  handles_execution = True
pyinfra/facts/docker.py CHANGED
@@ -86,3 +86,19 @@ class DockerNetwork(DockerSingleMixin):
86
86
  """
87
87
 
88
88
  docker_type = "network"
89
+
90
+
91
+ class DockerVolumes(DockerFactBase):
92
+ """
93
+ Returns ``docker inspect`` output for all Docker volumes.
94
+ """
95
+
96
+ command = "docker volume inspect `docker volume ls -q`"
97
+
98
+
99
+ class DockerVolume(DockerSingleMixin):
100
+ """
101
+ Returns ``docker inspect`` output for a single Docker container.
102
+ """
103
+
104
+ docker_type = "volume"
pyinfra/facts/runit.py ADDED
@@ -0,0 +1,68 @@
1
+ from pyinfra.api import FactBase
2
+
3
+
4
+ class RunitStatus(FactBase):
5
+ """
6
+ Returns a dict of name -> status for runit services.
7
+
8
+ + service: optionally check only for a single service
9
+ + svdir: alternative ``SVDIR``
10
+
11
+ .. code:: python
12
+
13
+ {
14
+ 'agetty-tty1': True, # service is running
15
+ 'dhcpcd': False, # service is down
16
+ 'wpa_supplicant': None, # service is managed, but not running or down,
17
+ # possibly in a fail state
18
+ }
19
+ """
20
+
21
+ requires_command = "sv"
22
+ default = dict
23
+
24
+ def command(self, service=None, svdir="/var/service"):
25
+ if service is None:
26
+ return (
27
+ 'export SVDIR="{0}" && '
28
+ 'cd "$SVDIR" && find * -maxdepth 0 -exec sv status {{}} + 2>/dev/null'
29
+ ).format(svdir)
30
+ else:
31
+ return 'SVDIR="{0}" sv status "{1}"'.format(svdir, service)
32
+
33
+ def process(self, output):
34
+ services = {}
35
+ for line in output:
36
+ statusstr, service, _ = line.split(sep=": ", maxsplit=2)
37
+ status = None
38
+
39
+ if statusstr == "run":
40
+ status = True
41
+ elif statusstr == "down":
42
+ status = False
43
+ # another observable state is "fail"
44
+ # report as ``None`` for now
45
+
46
+ services[service] = status
47
+
48
+ return services
49
+
50
+
51
+ class RunitManaged(FactBase):
52
+ """
53
+ Returns a set of all services managed by runit
54
+
55
+ + service: optionally check only for a single service
56
+ + svdir: alternative ``SVDIR``
57
+ """
58
+
59
+ default = set
60
+
61
+ def command(self, service=None, svdir="/var/service"):
62
+ if service is None:
63
+ return 'cd "{0}" && find -mindepth 1 -maxdepth 1 -type l -printf "%f\n"'.format(svdir)
64
+ else:
65
+ return 'cd "{0}" && test -h "{1}" && echo "{1}" || true'.format(svdir, service)
66
+
67
+ def process(self, output):
68
+ return set(output)
@@ -0,0 +1,339 @@
1
+ """
2
+ Manager Docker Containers, Volumes and Networks
3
+ """
4
+
5
+ from pyinfra import host
6
+ from pyinfra.api import operation
7
+ from pyinfra.facts.docker import DockerContainers, DockerNetworks, DockerVolumes
8
+
9
+ from .util.docker import handle_docker
10
+
11
+
12
+ @operation()
13
+ def container(
14
+ container,
15
+ image="",
16
+ ports=None,
17
+ networks=None,
18
+ volumes=None,
19
+ env_vars=None,
20
+ pull_always=False,
21
+ present=True,
22
+ force=False,
23
+ start=True,
24
+ ):
25
+ """
26
+ Manage Docker containers
27
+
28
+ + container: name to identify the container
29
+ + image: container image and tag ex: nginx:alpine
30
+ + networks: network list to attach on container
31
+ + ports: port list to expose
32
+ + volumes: volume list to map on container
33
+ + env_vars: environment varible list to inject on container
34
+ + pull_always: force image pull
35
+ + force: remove a contaner with same name and create a new one
36
+ + present: whether the container should be up and running
37
+ + start: start or stop the container
38
+
39
+ **Examples:**
40
+
41
+ .. code:: python
42
+
43
+ # Run a container
44
+ docker.container(
45
+ name="Deploy Nginx container",
46
+ container="nginx",
47
+ image="nginx:alpine",
48
+ ports=["80:80"],
49
+ present=True,
50
+ force=True,
51
+ networks=["proxy", "services"],
52
+ volumes=["nginx_data:/usr/share/nginx/html"],
53
+ pull_always=True,
54
+ )
55
+
56
+ # Stop a container
57
+ docker.container(
58
+ name="Stop Nginx container",
59
+ container="nginx",
60
+ start=False,
61
+ )
62
+
63
+ # Start a container
64
+ docker.container(
65
+ name="Start Nginx container",
66
+ container="nginx",
67
+ start=True,
68
+ )
69
+ """
70
+
71
+ existent_container = [c for c in host.get_fact(DockerContainers) if container in c["Name"]]
72
+
73
+ if force:
74
+ if existent_container:
75
+ yield handle_docker(
76
+ resource="container",
77
+ command="remove",
78
+ container=container,
79
+ )
80
+
81
+ if present:
82
+ if not existent_container or force:
83
+ yield handle_docker(
84
+ resource="container",
85
+ command="create",
86
+ container=container,
87
+ image=image,
88
+ ports=ports,
89
+ networks=networks,
90
+ volumes=volumes,
91
+ env_vars=env_vars,
92
+ pull_always=pull_always,
93
+ present=present,
94
+ force=force,
95
+ start=start,
96
+ )
97
+
98
+ if existent_container and start:
99
+ if existent_container[0]["State"]["Status"] != "running":
100
+ yield handle_docker(
101
+ resource="container",
102
+ command="start",
103
+ container=container,
104
+ )
105
+
106
+ if existent_container and not start:
107
+ if existent_container[0]["State"]["Status"] == "running":
108
+ yield handle_docker(
109
+ resource="container",
110
+ command="stop",
111
+ container=container,
112
+ )
113
+
114
+ if existent_container and not present:
115
+ yield handle_docker(
116
+ resource="container",
117
+ command="remove",
118
+ container=container,
119
+ )
120
+
121
+
122
+ @operation(is_idempotent=False)
123
+ def image(image, present=True):
124
+ """
125
+ Manage Docker images
126
+
127
+ + image: Image and tag ex: nginx:alpine
128
+ + present: whether the Docker image should be exist
129
+
130
+ **Examples:**
131
+
132
+ .. code:: python
133
+
134
+ # Pull a Docker image
135
+ docker.image(
136
+ name="Pull nginx image",
137
+ image="nginx:alpine",
138
+ present=True,
139
+ )
140
+
141
+ # Remove a Docker image
142
+ docker.image(
143
+ name="Remove nginx image",
144
+ image:"nginx:image",
145
+ present=False,
146
+ )
147
+ """
148
+
149
+ if present:
150
+ yield handle_docker(
151
+ resource="image",
152
+ command="pull",
153
+ image=image,
154
+ )
155
+
156
+ else:
157
+ yield handle_docker(
158
+ resource="image",
159
+ command="remove",
160
+ image=image,
161
+ )
162
+
163
+
164
+ @operation()
165
+ def volume(volume, driver="", labels=None, present=True):
166
+ """
167
+ Manage Docker volumes
168
+
169
+ + volume: Volume name
170
+ + driver: Docker volume storage driver
171
+ + labels: Label list to attach in the volume
172
+ + present: whether the Docker volume should exist
173
+
174
+ **Examples:**
175
+
176
+ .. code:: python
177
+
178
+ # Create a Docker volume
179
+ docker.volume(
180
+ name="Create nginx volume",
181
+ volume="nginx_data",
182
+ present=True
183
+ )
184
+ """
185
+
186
+ existent_volume = [v for v in host.get_fact(DockerVolumes) if v["Name"] == volume]
187
+
188
+ if present:
189
+
190
+ if existent_volume:
191
+ host.noop("Volume alredy exist!")
192
+ return
193
+
194
+ yield handle_docker(
195
+ resource="volume",
196
+ command="create",
197
+ volume=volume,
198
+ driver=driver,
199
+ labels=labels,
200
+ present=present,
201
+ )
202
+
203
+ else:
204
+ if existent_volume is None:
205
+ host.noop("There is no {0} volume!".format(volume))
206
+ return
207
+
208
+ yield handle_docker(
209
+ resource="volume",
210
+ command="remove",
211
+ volume=volume,
212
+ )
213
+
214
+
215
+ @operation()
216
+ def network(
217
+ network,
218
+ driver="",
219
+ gateway="",
220
+ ip_range="",
221
+ ipam_driver="",
222
+ subnet="",
223
+ scope="",
224
+ opts=None,
225
+ ipam_opts=None,
226
+ labels=None,
227
+ ingress=False,
228
+ attachable=False,
229
+ present=True,
230
+ ):
231
+ """
232
+ Manage docker networks
233
+
234
+ + network_name: Image name
235
+ + driver: Container image and tag ex: nginx:alpine
236
+ + gateway: IPv4 or IPv6 Gateway for the master subnet
237
+ + ip_range: Allocate container ip from a sub-range
238
+ + ipam_driver: IP Address Management Driver
239
+ + subnet: Subnet in CIDR format that represents a network segment
240
+ + scope: Control the network's scope
241
+ + opts: Set driver specific options
242
+ + ipam_opts: Set IPAM driver specific options
243
+ + labels: Label list to attach in the network
244
+ + ingress: Create swarm routing-mesh network
245
+ + attachable: Enable manual container attachment
246
+ + present: whether the Docker network should exist
247
+
248
+ **Examples:**
249
+
250
+ .. code:: python
251
+
252
+ # Create Docker network
253
+ docker.network(
254
+ name="Create nginx network",
255
+ network_name="nginx",
256
+ attachable=True,
257
+ present=True,
258
+ )
259
+ """
260
+ existent_network = [n for n in host.get_fact(DockerNetworks) if n["Name"] == network]
261
+
262
+ if present:
263
+ if existent_network:
264
+ host.noop("Alredy exist a network with {0} name!".format(network))
265
+ return
266
+
267
+ yield handle_docker(
268
+ resource="network",
269
+ command="create",
270
+ network=network,
271
+ driver=driver,
272
+ gateway=gateway,
273
+ ip_range=ip_range,
274
+ ipam_driver=ipam_driver,
275
+ subnet=subnet,
276
+ scope=scope,
277
+ opts=opts,
278
+ ipam_opts=ipam_opts,
279
+ labels=labels,
280
+ ingress=ingress,
281
+ attachable=attachable,
282
+ present=present,
283
+ )
284
+
285
+ else:
286
+ if existent_network is None:
287
+ host.noop("Ther is not network with {0} name!".format(network))
288
+ return
289
+
290
+ yield handle_docker(
291
+ resource="network",
292
+ command="create",
293
+ network=network,
294
+ )
295
+
296
+
297
+ @operation(is_idempotent=False)
298
+ def prune(
299
+ all=False,
300
+ volume=False,
301
+ filter="",
302
+ ):
303
+ """
304
+ Execute a docker system prune.
305
+
306
+ + all: Remove all unused images not just dangling ones
307
+ + volumes: Prune anonymous volumes
308
+ + filter: Provide filter values (e.g. "label=<key>=<value>" or "until=24h")
309
+
310
+ **Examples:**
311
+
312
+ .. code:: python
313
+
314
+ # Remove dangling images
315
+ docker.prune(
316
+ name="remove dangling images",
317
+ )
318
+
319
+ # Remove all images and volumes
320
+ docker.prune(
321
+ name="Remove all images and volumes",
322
+ all=True,
323
+ volumes=True,
324
+ )
325
+
326
+ # Remove images older than 90 days
327
+ docker.prune(
328
+ name="Remove unused older than 90 days",
329
+ filter="until=2160h"
330
+ )
331
+ """
332
+
333
+ yield handle_docker(
334
+ resource="system",
335
+ command="prune",
336
+ all=all,
337
+ volume=volume,
338
+ filter=filter,
339
+ )
@@ -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
+ )
@@ -41,6 +41,7 @@ from . import (
41
41
  openrc,
42
42
  pacman,
43
43
  pkg,
44
+ runit,
44
45
  systemd,
45
46
  sysvinit,
46
47
  upstart,
@@ -492,6 +493,9 @@ def service(
492
493
  elif host.get_fact(Which, command="initctl"):
493
494
  service_operation = upstart.service
494
495
 
496
+ elif host.get_fact(Which, command="sv"):
497
+ service_operation = runit.service
498
+
495
499
  elif (
496
500
  host.get_fact(Which, command="service")
497
501
  or host.get_fact(Link, path="/etc/init.d")
@@ -4,6 +4,8 @@ Manage systemd services.
4
4
 
5
5
  from __future__ import annotations
6
6
 
7
+ import shlex
8
+
7
9
  from pyinfra import host
8
10
  from pyinfra.api import StringCommand, operation
9
11
  from pyinfra.facts.systemd import SystemdEnabled, SystemdStatus, _make_systemctl_cmd
@@ -140,8 +142,8 @@ def service(
140
142
 
141
143
  # Isn't enabled and want enabled?
142
144
  if not is_enabled and enabled is True:
143
- yield "{0} enable {1}".format(systemctl_cmd, service)
145
+ yield "{0} enable {1}".format(systemctl_cmd, shlex.quote(service))
144
146
 
145
147
  # Is enabled and want disabled?
146
148
  elif is_enabled and enabled is False:
147
- yield "{0} disable {1}".format(systemctl_cmd, service)
149
+ yield "{0} disable {1}".format(systemctl_cmd, shlex.quote(service))
@@ -0,0 +1,177 @@
1
+ from pyinfra.api import OperationError
2
+
3
+
4
+ def _create_container(**kwargs):
5
+ command = []
6
+
7
+ networks = kwargs["networks"] if kwargs["networks"] else []
8
+ ports = kwargs["ports"] if kwargs["ports"] else []
9
+ volumes = kwargs["volumes"] if kwargs["volumes"] else []
10
+ env_vars = kwargs["env_vars"] if kwargs["env_vars"] else []
11
+
12
+ if kwargs["image"] == "":
13
+ raise OperationError("missing 1 required argument: 'image'")
14
+
15
+ command.append("docker container create --name {0}".format(kwargs["container"]))
16
+
17
+ for network in networks:
18
+ command.append("--network {0}".format(network))
19
+
20
+ for port in ports:
21
+ command.append("-p {0}".format(port))
22
+
23
+ for volume in volumes:
24
+ command.append("-v {0}".format(volume))
25
+
26
+ for env_var in env_vars:
27
+ command.append("-e {0}".format(env_var))
28
+
29
+ if kwargs["pull_always"]:
30
+ command.append("--pull always")
31
+
32
+ command.append(kwargs["image"])
33
+
34
+ if kwargs["start"]:
35
+ command.append("; {0}".format(_start_container(container=kwargs["container"])))
36
+
37
+ return " ".join(command)
38
+
39
+
40
+ def _remove_container(**kwargs):
41
+ return "docker container rm -f {0}".format(kwargs["container"])
42
+
43
+
44
+ def _start_container(**kwargs):
45
+ return "docker container start {0}".format(kwargs["container"])
46
+
47
+
48
+ def _stop_container(**kwargs):
49
+ return "docker container stop {0}".format(kwargs["container"])
50
+
51
+
52
+ def _pull_image(**kwargs):
53
+ return "docker image pull {0}".format(kwargs["image"])
54
+
55
+
56
+ def _remove_image(**kwargs):
57
+ return "docker image rm {0}".format(kwargs["image"])
58
+
59
+
60
+ def _prune_command(**kwargs):
61
+ command = ["docker system prune"]
62
+
63
+ if kwargs["all"]:
64
+ command.append("-a")
65
+
66
+ if kwargs["filter"] != "":
67
+ command.append("--filter={0}".format(kwargs["filter"]))
68
+
69
+ if kwargs["volumes"]:
70
+ command.append("--volumes")
71
+
72
+ command.append("-f")
73
+
74
+ return " ".join(command)
75
+
76
+
77
+ def _create_volume(**kwargs):
78
+ command = []
79
+ labels = kwargs["labels"] if kwargs["labels"] else []
80
+
81
+ command.append("docker volume create {0}".format(kwargs["volume"]))
82
+
83
+ if kwargs["driver"] != "":
84
+ command.append("-d {0}".format(kwargs["driver"]))
85
+
86
+ for label in labels:
87
+ command.append("--label {0}".format(label))
88
+
89
+ return " ".join(command)
90
+
91
+
92
+ def _remove_volume(**kwargs):
93
+ return "docker image rm {0}".format(kwargs["volume"])
94
+
95
+
96
+ def _create_network(**kwargs):
97
+ command = []
98
+ opts = kwargs["opts"] if kwargs["opts"] else []
99
+ ipam_opts = kwargs["ipam_opts"] if kwargs["ipam_opts"] else []
100
+ labels = kwargs["labels"] if kwargs["labels"] else []
101
+
102
+ command.append("docker network create {0}".format(kwargs["network"]))
103
+ if kwargs["driver"] != "":
104
+ command.append("-d {0}".format(kwargs["driver"]))
105
+
106
+ if kwargs["gateway"] != "":
107
+ command.append("--gateway {0}".format(kwargs["gateway"]))
108
+
109
+ if kwargs["ip_range"] != "":
110
+ command.append("--ip-range {0}".format(kwargs["ip_range"]))
111
+
112
+ if kwargs["ipam_driver"] != "":
113
+ command.append("--ipam-driver {0}".format(kwargs["ipam_driver"]))
114
+
115
+ if kwargs["subnet"] != "":
116
+ command.append("--subnet {0}".format(kwargs["subnet"]))
117
+
118
+ if kwargs["scope"] != "":
119
+ command.append("--scope {0}".format(kwargs["scope"]))
120
+
121
+ if kwargs["ingress"]:
122
+ command.append("--ingress")
123
+
124
+ if kwargs["attachable"]:
125
+ command.append("--attachable")
126
+
127
+ for opt in opts:
128
+ command.append("--opt {0}".format(opt))
129
+
130
+ for opt in ipam_opts:
131
+ command.append("--ipam-opt {0}".format(opt))
132
+
133
+ for label in labels:
134
+ command.append("--label {0}".format(label))
135
+ return " ".join(command)
136
+
137
+
138
+ def _remove_network(**kwargs):
139
+ return "docker network rm {0}".format(kwargs["network"])
140
+
141
+
142
+ def handle_docker(resource, command, **kwargs):
143
+ container_commands = {
144
+ "create": _create_container,
145
+ "remove": _remove_container,
146
+ "start": _start_container,
147
+ "stop": _stop_container,
148
+ }
149
+
150
+ image_commands = {
151
+ "pull": _pull_image,
152
+ "remove": _remove_image,
153
+ }
154
+
155
+ volume_commands = {
156
+ "create": _create_volume,
157
+ "remove": _remove_volume,
158
+ }
159
+
160
+ network_commands = {
161
+ "create": _create_network,
162
+ "remove": _remove_network,
163
+ }
164
+
165
+ system_commands = {
166
+ "prune": _prune_command,
167
+ }
168
+
169
+ docker_commands = {
170
+ "container": container_commands,
171
+ "image": image_commands,
172
+ "volume": volume_commands,
173
+ "network": network_commands,
174
+ "system": system_commands,
175
+ }
176
+
177
+ return docker_commands[resource][command](**kwargs)
@@ -1,5 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import shlex
4
+
3
5
  from pyinfra.api import Host
4
6
 
5
7
 
@@ -19,26 +21,26 @@ def handle_service_control(
19
21
  # Need down but running
20
22
  if running is False:
21
23
  if is_running:
22
- yield formatter.format(name, "stop")
24
+ yield formatter.format(shlex.quote(name), "stop")
23
25
  else:
24
26
  host.noop("service {0} is stopped".format(name))
25
27
 
26
28
  # Need running but down
27
29
  if running is True:
28
30
  if not is_running:
29
- yield formatter.format(name, "start")
31
+ yield formatter.format(shlex.quote(name), "start")
30
32
  else:
31
33
  host.noop("service {0} is running".format(name))
32
34
 
33
35
  # Only restart if the service is already running
34
36
  if restarted and is_running:
35
- yield formatter.format(name, "restart")
37
+ yield formatter.format(shlex.quote(name), "restart")
36
38
 
37
39
  # Only reload if the service is already reloaded
38
40
  if reloaded and is_running:
39
- yield formatter.format(name, "reload")
41
+ yield formatter.format(shlex.quote(name), "reload")
40
42
 
41
43
  # Always execute arbitrary commands as these may or may not rely on the service
42
44
  # being up or down
43
45
  if command:
44
- yield formatter.format(name, command)
46
+ yield formatter.format(shlex.quote(name), command)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pyinfra
3
- Version: 3.0b2
3
+ Version: 3.0b4
4
4
  Summary: pyinfra automates/provisions/manages/deploys infrastructure.
5
5
  Home-page: https://pyinfra.com
6
6
  Author: Nick / Fizzadar
@@ -56,7 +56,7 @@ Requires-Dist: types-paramiko ; extra == 'dev'
56
56
  Requires-Dist: types-python-dateutil ; extra == 'dev'
57
57
  Requires-Dist: types-PyYAML ; extra == 'dev'
58
58
  Requires-Dist: types-setuptools ; extra == 'dev'
59
- Requires-Dist: pyinfra-guzzle-sphinx-theme ==0.15 ; extra == 'dev'
59
+ Requires-Dist: pyinfra-guzzle-sphinx-theme ==0.16 ; extra == 'dev'
60
60
  Requires-Dist: myst-parser ==2.0.0 ; extra == 'dev'
61
61
  Requires-Dist: sphinx ==6.2.1 ; extra == 'dev'
62
62
  Requires-Dist: wheel ; extra == 'dev'
@@ -67,7 +67,7 @@ Requires-Dist: ipdbplugin ; extra == 'dev'
67
67
  Requires-Dist: flake8-spellcheck ==0.12.1 ; extra == 'dev'
68
68
  Requires-Dist: redbaron ; extra == 'dev'
69
69
  Provides-Extra: docs
70
- Requires-Dist: pyinfra-guzzle-sphinx-theme ==0.15 ; extra == 'docs'
70
+ Requires-Dist: pyinfra-guzzle-sphinx-theme ==0.16 ; extra == 'docs'
71
71
  Requires-Dist: myst-parser ==2.0.0 ; extra == 'docs'
72
72
  Requires-Dist: sphinx ==6.2.1 ; extra == 'docs'
73
73
  Provides-Extra: test
@@ -27,7 +27,7 @@ pyinfra/connectors/chroot.py,sha256=Xd72I8T58KIwKOoc0LXCw91AoEIaiHfRLDcDVTHGJ0o,
27
27
  pyinfra/connectors/docker.py,sha256=2UNHhXS4hpLo7I19ixDeSd7JR8SNo43VgqsaUIZQZJ4,8741
28
28
  pyinfra/connectors/dockerssh.py,sha256=VWHY--jqs3yf-RuPUZXav4vLeON9SzoVC9CUyOJo1rg,8919
29
29
  pyinfra/connectors/local.py,sha256=vYOBQS_5rf-dVaPeG4dJlLwBHqkxAzLjj3aDEgbAsx8,6900
30
- pyinfra/connectors/ssh.py,sha256=ESCZwehJ0Ve6gkjbqGTJrNEGZlu3ItjKLMkmCWq-Z3Y,20989
30
+ pyinfra/connectors/ssh.py,sha256=kUtp86GlIteM_bqXtoPtdQE1--AmxzAtBOXv5oK0IOI,21136
31
31
  pyinfra/connectors/ssh_util.py,sha256=CN_5AdTA3RpiWCnXTrRBjez1NsN59hITDzQmXIkZvoE,3683
32
32
  pyinfra/connectors/terraform.py,sha256=G7lK168Fz0jNFetc_7_bPT-RnoaRDksJat0R26fqkUk,3617
33
33
  pyinfra/connectors/util.py,sha256=0bvoMsGMD-Tbfaer8NUhWJjBnaNKdmE83PDg48BYjcU,11374
@@ -44,7 +44,7 @@ pyinfra/facts/cargo.py,sha256=mHtT2Yxoqx_g0akDq6jkHFN5VWNHQu70vKvNLZ10-b8,567
44
44
  pyinfra/facts/choco.py,sha256=Tf7_DuQY0eZ4SIxV0rlyulrBCg7A0qyPbtTcHgkVP_0,739
45
45
  pyinfra/facts/deb.py,sha256=dy5PKdQqy1oLdCUd6iTxCa6yG8agsaxZrPVsjvlDi74,1756
46
46
  pyinfra/facts/dnf.py,sha256=7YNicP8EbRvWJQKU0OcwcW3bnIxx9GJ1Fy4qdCFnI0Y,898
47
- pyinfra/facts/docker.py,sha256=uaY2ui98t3p0a6F6DQq5FiA6y8pOCwsrkFK4JTRlj3Y,1714
47
+ pyinfra/facts/docker.py,sha256=Vr0rIEXSpK33yc2dDGct2lKhPQamvKEztxIAfwN0sk0,2046
48
48
  pyinfra/facts/files.py,sha256=ki9-NQnYyt1VzuqKwEOYpX1nz5gJ_ph-ruq-4DS1VLg,11511
49
49
  pyinfra/facts/gem.py,sha256=U1oF32LeiBo3ruaFm6hfF3XwNmgyhqamFUAeGn4cD3k,509
50
50
  pyinfra/facts/git.py,sha256=rk4NS2SQJiosI6eY2eCy_p9kOP4O8UARRjFi16ObE2w,1294
@@ -63,6 +63,7 @@ pyinfra/facts/pkgin.py,sha256=Q1q7m4CCs_na92dDdQIjTuQnJ3axYv5qUdxnAy7e_XY,517
63
63
  pyinfra/facts/postgres.py,sha256=M7ppmVvUXy8HSNPqrc8zLPyt8JamAyGs2IO2ocbzSZ8,4187
64
64
  pyinfra/facts/postgresql.py,sha256=4nusMVvGhtku86KX4O4vjSxh_MamxZy_kmTQvvy0GhE,223
65
65
  pyinfra/facts/rpm.py,sha256=v9OCfTlTSqs4UQWDwnt2tVJkqHOmHZvj7Cd1g4mZ3WI,2091
66
+ pyinfra/facts/runit.py,sha256=uqwftOBmjWma09hNhIZ9ZF2QqqgdcA5NzrbBtP95lRM,1965
66
67
  pyinfra/facts/selinux.py,sha256=N0zbJrAtBeRBtxZFUHbYTLQ2L4mRV7_Oj3Cj3OA1Npw,4272
67
68
  pyinfra/facts/server.py,sha256=ocUPIIveHWfe4K4-7mt7Je7TswWbKmjdu1xAF2VxaD8,19798
68
69
  pyinfra/facts/snap.py,sha256=MnZDllRZ1JKLw0SKRFQ1tI6Wi05gvipQPo7m4gpL4fI,1946
@@ -85,6 +86,7 @@ pyinfra/operations/bsdinit.py,sha256=okQUQDr2H8Z-cAdfdbPJiuGujsHLuV5gpuMZ1UlICEM
85
86
  pyinfra/operations/cargo.py,sha256=mXWd6pb0IR6kzJMmPHwXZN-VJ-B_y8AdOFlrRzDQOZI,1104
86
87
  pyinfra/operations/choco.py,sha256=8nG0wc1tZEA0L0HTIjgR00IDiONARokyzHyKj-R3xmo,1515
87
88
  pyinfra/operations/dnf.py,sha256=3154Rer6dejVB1AK-CqyJhpMVn_djaSDJrVMs62GNcE,5599
89
+ pyinfra/operations/docker.py,sha256=Mra-m2iayXkc2LgCk2tuE6M7lZHhOGNJD3WQIPM9t2I,8396
88
90
  pyinfra/operations/files.py,sha256=9O_HKgmVD_z74jtSivY4pKBPrCDKKHDSy0jAB9QERHU,53639
89
91
  pyinfra/operations/gem.py,sha256=2C85sOwIRMHGvmPg4uAlUVf6MokhiA7LLPqzdJRHsBg,1132
90
92
  pyinfra/operations/git.py,sha256=b26tQF_4hykTy0FtxiuCkqPk9i8JdZaz-RBhH4X96yw,11789
@@ -102,11 +104,12 @@ pyinfra/operations/postgres.py,sha256=LRoedDevQqiM5eX5Lmzb5mr_E9Od0ROVC0j18ZqaR0
102
104
  pyinfra/operations/postgresql.py,sha256=agZjL2W4yxigk9ThIC0V_3wvmcWVdX308aJO24WkN6g,833
103
105
  pyinfra/operations/puppet.py,sha256=eDe8D9jQbHYQ4_r4-dmEZfMASKQvj36BR8z_h8aDfw8,861
104
106
  pyinfra/operations/python.py,sha256=u569cdPrPesrmzU09nwIPA3bk6TZ-Qv2QP0lJLcO_bw,2021
107
+ pyinfra/operations/runit.py,sha256=jRR5kt1OUCLbYktnu7yl3YvSiTW51VvEvOuB0yfd7Ww,5126
105
108
  pyinfra/operations/selinux.py,sha256=khqWJsr9MOTyZmxP9P4dQ_7KUNlAfo5fx3Nv7kWm49w,5961
106
- pyinfra/operations/server.py,sha256=TUJ_E61ar1sOYUykX35mYUuwtJRqOpiDZ22HYDP6Vys,36315
109
+ pyinfra/operations/server.py,sha256=wc5pNDQzEiwl9XEz1cG1m-51z1TV-5P39eMvSig90tY,36414
107
110
  pyinfra/operations/snap.py,sha256=a-QtNE4Dlsavqq425TUIwpEJu4oGw8UlLRkdTFyT1F8,3049
108
111
  pyinfra/operations/ssh.py,sha256=wocoaYDlOhhItItAVQCEfnVowTtkg3AP0hQ3mnpUnl0,5634
109
- pyinfra/operations/systemd.py,sha256=89bq9VMpQ-qnupTa6U8OsBFFoDWUNwWvrLP_LN8uaxE,3944
112
+ pyinfra/operations/systemd.py,sha256=hPHTjASj6N_fRAzLr3DNHnxxIbiiTIIT9UStSxKDkTk,3984
110
113
  pyinfra/operations/sysvinit.py,sha256=WzzthkmWL46MNNY6LsBZ90e37yPj0w2QyUtEAlGBwqY,4078
111
114
  pyinfra/operations/upstart.py,sha256=pHb9RGnVhT14A_y6OezfOH-lmniKpiyJqpeoOJl0beE,1978
112
115
  pyinfra/operations/vzctl.py,sha256=2u2CDkuDjzHBRQ54HfyfLpLrsbT8U7_05EEjbbhKUiU,3110
@@ -114,9 +117,10 @@ pyinfra/operations/xbps.py,sha256=ru3_srMBUyUXGzAsPo7WwoomfM0AeDglFv8CDqB33B0,15
114
117
  pyinfra/operations/yum.py,sha256=Ig7AzQy1C7I8XM37lWbw0nI5lzFGMoX30P8FV8-V5uA,5600
115
118
  pyinfra/operations/zypper.py,sha256=z1CWv2uwWBlCLIhHna7U5DojVoKZYoUYpezJ_FM_xK8,5555
116
119
  pyinfra/operations/util/__init__.py,sha256=ZAHjeCXtLo0TIOSfZ9h0Sh5IXXRCspfHs3RR1l8tQCE,366
120
+ pyinfra/operations/util/docker.py,sha256=6CvQgeFAXH_lDqKb7RxWpMvlCDwEAXlBaDZoJ8LxrYg,4596
117
121
  pyinfra/operations/util/files.py,sha256=Zcet3ydNVbdT9jss0BDm6RJFyR_s6XTr0isDR60Zubw,3622
118
122
  pyinfra/operations/util/packaging.py,sha256=xFtOlEX46ms7g3gDvOOInRVR1RVfgsmhLzFzsJAL_eU,9381
119
- pyinfra/operations/util/service.py,sha256=RQjnByy-x_KdOSjOLweS12x8pOXpLkTPLlaHsntpYQE,1230
123
+ pyinfra/operations/util/service.py,sha256=kJd1zj4-sAaGIp5Ts7yAJznogWaGr8oQTztwenLAr7Y,1309
120
124
  pyinfra_cli/__init__.py,sha256=G0X7tNdqT45uWuK3aHIKxMdDeCgJ7zHo6vbxoG6zy_8,284
121
125
  pyinfra_cli/__main__.py,sha256=8tjq8HUll8P8naFw7pGlygwSz7u9je_MQ-0pqcDlENY,881
122
126
  pyinfra_cli/commands.py,sha256=J-mCJYvDebJ8M7o3HreB2zToa871-xO6_KjVhPLeHho,1832
@@ -155,9 +159,9 @@ tests/test_connectors/test_sshuserclient.py,sha256=2PQNLPhNL6lBACc6tQuXmPoog-9L6
155
159
  tests/test_connectors/test_terraform.py,sha256=Z5MhgDeRDFumu-GlbjMD0ZRkecwBIPP8C8ZVg-mq7C8,3743
156
160
  tests/test_connectors/test_util.py,sha256=hQir0WyjH0LEF6xvIyHNyqdI5pkJX6qUR9287MgO2bY,4647
157
161
  tests/test_connectors/test_vagrant.py,sha256=27qRB7ftjEPaj4ejBNZ-rR4Ou1AD1VyVcf2XjwZPG3M,3640
158
- pyinfra-3.0b2.dist-info/LICENSE.md,sha256=gwC95tUll0gwB32tHNkTAasN7Sb6vjWzXa305NwClbI,1076
159
- pyinfra-3.0b2.dist-info/METADATA,sha256=lISn-ii_LETvqwvTJMl5HZ4oa1jNndtp6M-Q1Q8TSzk,8322
160
- pyinfra-3.0b2.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
161
- pyinfra-3.0b2.dist-info/entry_points.txt,sha256=BraEFyquy05M8ch33HZXOHoH_m2BTqejL3xX3NrpzOM,471
162
- pyinfra-3.0b2.dist-info/top_level.txt,sha256=2K6D1mK35JTSEBgOfEPV-N-uA2SDErxGiE0J-HUMMVI,26
163
- pyinfra-3.0b2.dist-info/RECORD,,
162
+ pyinfra-3.0b4.dist-info/LICENSE.md,sha256=gwC95tUll0gwB32tHNkTAasN7Sb6vjWzXa305NwClbI,1076
163
+ pyinfra-3.0b4.dist-info/METADATA,sha256=L9PyC6qdNCq30gCNvpPpl41jLlqacs9n-IlbHHO-lwc,8322
164
+ pyinfra-3.0b4.dist-info/WHEEL,sha256=DZajD4pwLWue70CAfc7YaxT1wLUciNBvN_TTcvXpltE,110
165
+ pyinfra-3.0b4.dist-info/entry_points.txt,sha256=BraEFyquy05M8ch33HZXOHoH_m2BTqejL3xX3NrpzOM,471
166
+ pyinfra-3.0b4.dist-info/top_level.txt,sha256=2K6D1mK35JTSEBgOfEPV-N-uA2SDErxGiE0J-HUMMVI,26
167
+ pyinfra-3.0b4.dist-info/RECORD,,