pyinfra 3.2__py2.py3-none-any.whl → 3.3__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.
Files changed (88) hide show
  1. pyinfra/api/arguments_typed.py +4 -5
  2. pyinfra/api/command.py +22 -3
  3. pyinfra/api/config.py +5 -2
  4. pyinfra/api/facts.py +3 -0
  5. pyinfra/api/host.py +10 -4
  6. pyinfra/api/operation.py +2 -1
  7. pyinfra/api/state.py +1 -1
  8. pyinfra/connectors/base.py +34 -8
  9. pyinfra/connectors/chroot.py +7 -2
  10. pyinfra/connectors/docker.py +7 -2
  11. pyinfra/connectors/dockerssh.py +7 -2
  12. pyinfra/connectors/local.py +7 -2
  13. pyinfra/connectors/ssh.py +9 -2
  14. pyinfra/connectors/sshuserclient/client.py +16 -0
  15. pyinfra/connectors/sshuserclient/config.py +2 -0
  16. pyinfra/connectors/terraform.py +1 -1
  17. pyinfra/connectors/util.py +13 -9
  18. pyinfra/context.py +9 -2
  19. pyinfra/facts/apk.py +5 -0
  20. pyinfra/facts/apt.py +9 -1
  21. pyinfra/facts/brew.py +13 -0
  22. pyinfra/facts/bsdinit.py +3 -0
  23. pyinfra/facts/cargo.py +5 -0
  24. pyinfra/facts/choco.py +6 -0
  25. pyinfra/facts/crontab.py +6 -1
  26. pyinfra/facts/deb.py +10 -0
  27. pyinfra/facts/dnf.py +5 -0
  28. pyinfra/facts/docker.py +10 -0
  29. pyinfra/facts/efibootmgr.py +5 -0
  30. pyinfra/facts/files.py +19 -1
  31. pyinfra/facts/flatpak.py +7 -0
  32. pyinfra/facts/freebsd.py +75 -0
  33. pyinfra/facts/gem.py +5 -0
  34. pyinfra/facts/git.py +9 -0
  35. pyinfra/facts/gpg.py +7 -0
  36. pyinfra/facts/hardware.py +13 -0
  37. pyinfra/facts/iptables.py +9 -1
  38. pyinfra/facts/launchd.py +5 -0
  39. pyinfra/facts/lxd.py +5 -0
  40. pyinfra/facts/mysql.py +8 -0
  41. pyinfra/facts/npm.py +5 -0
  42. pyinfra/facts/openrc.py +8 -0
  43. pyinfra/facts/opkg.py +12 -0
  44. pyinfra/facts/pacman.py +9 -1
  45. pyinfra/facts/pip.py +5 -0
  46. pyinfra/facts/pipx.py +8 -0
  47. pyinfra/facts/pkg.py +4 -0
  48. pyinfra/facts/pkgin.py +5 -0
  49. pyinfra/facts/podman.py +7 -0
  50. pyinfra/facts/postgres.py +8 -2
  51. pyinfra/facts/rpm.py +11 -0
  52. pyinfra/facts/runit.py +7 -0
  53. pyinfra/facts/selinux.py +16 -0
  54. pyinfra/facts/server.py +49 -3
  55. pyinfra/facts/snap.py +7 -0
  56. pyinfra/facts/systemd.py +5 -0
  57. pyinfra/facts/sysvinit.py +4 -0
  58. pyinfra/facts/upstart.py +5 -0
  59. pyinfra/facts/util/__init__.py +4 -1
  60. pyinfra/facts/vzctl.py +5 -0
  61. pyinfra/facts/xbps.py +6 -1
  62. pyinfra/facts/yum.py +5 -0
  63. pyinfra/facts/zfs.py +19 -2
  64. pyinfra/facts/zypper.py +5 -0
  65. pyinfra/operations/apt.py +10 -3
  66. pyinfra/operations/docker.py +48 -44
  67. pyinfra/operations/files.py +47 -1
  68. pyinfra/operations/freebsd/__init__.py +12 -0
  69. pyinfra/operations/freebsd/freebsd_update.py +70 -0
  70. pyinfra/operations/freebsd/pkg.py +219 -0
  71. pyinfra/operations/freebsd/service.py +116 -0
  72. pyinfra/operations/freebsd/sysrc.py +92 -0
  73. pyinfra/operations/opkg.py +5 -5
  74. pyinfra/operations/postgres.py +99 -16
  75. pyinfra/operations/server.py +6 -4
  76. pyinfra/operations/util/docker.py +44 -22
  77. {pyinfra-3.2.dist-info → pyinfra-3.3.dist-info}/LICENSE.md +1 -1
  78. {pyinfra-3.2.dist-info → pyinfra-3.3.dist-info}/METADATA +25 -24
  79. {pyinfra-3.2.dist-info → pyinfra-3.3.dist-info}/RECORD +88 -82
  80. pyinfra_cli/exceptions.py +5 -0
  81. pyinfra_cli/log.py +3 -0
  82. pyinfra_cli/main.py +9 -8
  83. pyinfra_cli/prints.py +1 -1
  84. pyinfra_cli/virtualenv.py +1 -1
  85. tests/test_connectors/test_ssh.py +302 -182
  86. {pyinfra-3.2.dist-info → pyinfra-3.3.dist-info}/WHEEL +0 -0
  87. {pyinfra-3.2.dist-info → pyinfra-3.3.dist-info}/entry_points.txt +0 -0
  88. {pyinfra-3.2.dist-info → pyinfra-3.3.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ from pyinfra import host
8
8
  from pyinfra.api import operation
9
9
  from pyinfra.facts.docker import DockerContainer, DockerNetwork, DockerVolume
10
10
 
11
- from .util.docker import handle_docker
11
+ from .util.docker import ContainerSpec, handle_docker
12
12
 
13
13
 
14
14
  @operation()
@@ -70,56 +70,60 @@ def container(
70
70
  )
71
71
  """
72
72
 
73
+ want_spec = ContainerSpec(
74
+ image,
75
+ ports or list(),
76
+ networks or list(),
77
+ volumes or list(),
78
+ env_vars or list(),
79
+ pull_always,
80
+ )
73
81
  existent_container = host.get_fact(DockerContainer, object_id=container)
74
82
 
75
- if force:
76
- if existent_container:
77
- yield handle_docker(
78
- resource="container",
79
- command="remove",
80
- container=container,
81
- )
83
+ container_spec_changes = want_spec.diff_from_inspect(existent_container)
82
84
 
83
- if present:
84
- if not existent_container or force:
85
- yield handle_docker(
86
- resource="container",
87
- command="create",
88
- container=container,
89
- image=image,
90
- ports=ports,
91
- networks=networks,
92
- volumes=volumes,
93
- env_vars=env_vars,
94
- pull_always=pull_always,
95
- present=present,
96
- force=force,
97
- start=start,
98
- )
99
-
100
- if existent_container and start:
101
- if existent_container[0]["State"]["Status"] != "running":
102
- yield handle_docker(
103
- resource="container",
104
- command="start",
105
- container=container,
106
- )
107
-
108
- if existent_container and not start:
109
- if existent_container[0]["State"]["Status"] == "running":
110
- yield handle_docker(
111
- resource="container",
112
- command="stop",
113
- container=container,
114
- )
115
-
116
- if existent_container and not present:
85
+ is_running = (
86
+ (existent_container[0]["State"]["Status"] == "running")
87
+ if existent_container and existent_container[0]
88
+ else False
89
+ )
90
+ recreating = existent_container and (force or container_spec_changes)
91
+ removing = existent_container and not present
92
+
93
+ do_remove = recreating or removing
94
+ do_create = (present and not existent_container) or recreating
95
+ do_start = start and (recreating or not is_running)
96
+ do_stop = not start and not removing and is_running
97
+
98
+ if do_remove:
117
99
  yield handle_docker(
118
100
  resource="container",
119
101
  command="remove",
120
102
  container=container,
121
103
  )
122
104
 
105
+ if do_create:
106
+ yield handle_docker(
107
+ resource="container",
108
+ command="create",
109
+ container=container,
110
+ spec=want_spec,
111
+ )
112
+
113
+ if do_start:
114
+ yield handle_docker(
115
+ resource="container",
116
+ command="start",
117
+ container=container,
118
+ )
119
+
120
+ if do_stop:
121
+ yield handle_docker(
122
+ resource="container",
123
+ command="stop",
124
+ container=container,
125
+ )
126
+
123
127
 
124
128
  @operation(is_idempotent=False)
125
129
  def image(image, present=True):
@@ -298,7 +302,7 @@ def network(
298
302
  @operation(is_idempotent=False)
299
303
  def prune(
300
304
  all=False,
301
- volume=False,
305
+ volumes=False,
302
306
  filter="",
303
307
  ):
304
308
  """
@@ -335,6 +339,6 @@ def prune(
335
339
  resource="system",
336
340
  command="prune",
337
341
  all=all,
338
- volume=volume,
342
+ volumes=volumes,
339
343
  filter=filter,
340
344
  )
@@ -665,7 +665,7 @@ def sync(
665
665
 
666
666
 
667
667
  @memoize
668
- def show_rsync_warning():
668
+ def show_rsync_warning() -> None:
669
669
  logger.warning("The `files.rsync` operation is in alpha!")
670
670
 
671
671
 
@@ -958,6 +958,9 @@ def template(
958
958
  a dict with arguments that will be passed as keyword args to the jinja2
959
959
  `Environment() <https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment>`_.
960
960
 
961
+ The ``host``, ``state``, and ``inventory`` objects will be automatically passed to the template
962
+ if not set explicitly.
963
+
961
964
  Notes:
962
965
  Common convention is to store templates in a "templates" directory and
963
966
  have a filename suffix with '.j2' (for jinja2).
@@ -1015,6 +1018,21 @@ def template(
1015
1018
  foo_dict=foo_dict,
1016
1019
  foo_list=foo_list
1017
1020
  )
1021
+
1022
+ # Example showing how to use host and inventory in a template file.
1023
+ template = StringIO("""
1024
+ name: "{{ host.name }}"
1025
+ list_contents:
1026
+ {% for entry in inventory.groups.my_servers %}
1027
+ - "{{ entry }}"
1028
+ {% endfor %}
1029
+ """)
1030
+
1031
+ files.template(
1032
+ name="Create a templated file",
1033
+ src=template,
1034
+ dest="/tmp/foo.yml"
1035
+ )
1018
1036
  '''
1019
1037
 
1020
1038
  if not hasattr(src, "read") and state.cwd:
@@ -1069,6 +1087,34 @@ def template(
1069
1087
  )
1070
1088
 
1071
1089
 
1090
+ @operation()
1091
+ def move(src: str, dest: str, overwrite=False):
1092
+ """
1093
+ Move remote file/directory/link into remote directory
1094
+
1095
+ + src: remote file/directory to move
1096
+ + dest: remote directory to move `src` into
1097
+ + overwrite: whether to overwrite dest, if present
1098
+ """
1099
+
1100
+ if host.get_fact(File, src) is None:
1101
+ raise OperationError("src {0} does not exist".format(src))
1102
+
1103
+ if not host.get_fact(Directory, dest):
1104
+ raise OperationError("dest {0} is not an existing directory".format(dest))
1105
+
1106
+ full_dest_path = os.path.join(dest, os.path.basename(src))
1107
+ if host.get_fact(File, full_dest_path) is not None:
1108
+ if overwrite:
1109
+ yield StringCommand("rm", "-rf", QuoteString(full_dest_path))
1110
+ else:
1111
+ raise OperationError(
1112
+ "dest {0} already exists and `overwrite` is unset".format(full_dest_path)
1113
+ )
1114
+
1115
+ yield StringCommand("mv", QuoteString(src), QuoteString(dest))
1116
+
1117
+
1072
1118
  def _validate_path(path):
1073
1119
  try:
1074
1120
  return os.fspath(path)
@@ -0,0 +1,12 @@
1
+ # This file only exists to support:
2
+ # from pyinfra.operations import freebsd
3
+ # freebsd.X.Y
4
+
5
+ from glob import glob
6
+ from os import path
7
+
8
+ module_filenames = glob(path.join(path.dirname(__file__), "*.py"))
9
+ module_names = [path.basename(name)[:-3] for name in module_filenames]
10
+ __all__ = [name for name in module_names if name != "__init__"]
11
+
12
+ from . import * # noqa
@@ -0,0 +1,70 @@
1
+ """
2
+ Fetch and install binary updates to FreeBSD.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing_extensions import List, Optional, Union
8
+
9
+ from pyinfra.api import QuoteString, StringCommand, operation
10
+
11
+
12
+ @operation()
13
+ def update(
14
+ force: bool = False,
15
+ basedir: Optional[str] = None,
16
+ workdir: Optional[str] = None,
17
+ conffile: Optional[str] = None,
18
+ jail: Optional[str] = None,
19
+ key: Optional[str] = None,
20
+ currently_running: Optional[str] = None,
21
+ server: Optional[str] = None,
22
+ ):
23
+ """
24
+ Based on the currently installed world and the configuration options set, fetch
25
+ all available binary updates and install them.
26
+
27
+ + force: See ``-F`` in ``freebsd-update(8)``.
28
+ + basedir: See ``-b`` in ``freebsd-update(8)``.
29
+ + workdir: See ``-d`` in ``freebsd-update(8)``.
30
+ + conffile: See ``-f`` in ``freebsd-update(8)``.
31
+ + jail: See ``-j`` in ``freebsd-update(8)``.
32
+ + key: See ``-k`` in ``freebsd-update(8)``.
33
+ + currently_running: See ``--currently-running`` in ``freebsd-update(8)``.
34
+ + server: See ``-s`` in ``freebsd-update(8)``.
35
+
36
+ **Example:**
37
+
38
+ .. code:: python
39
+
40
+ freebsd_update.update()
41
+ """
42
+
43
+ args: List[Union[str, "QuoteString"]] = []
44
+
45
+ args.extend(["PAGER=cat", "freebsd-update", "--not-running-from-cron"])
46
+
47
+ if force:
48
+ args.append("-F")
49
+
50
+ if basedir is not None:
51
+ args.extend(["-b", QuoteString(basedir)])
52
+
53
+ if workdir is not None:
54
+ args.extend(["-d", QuoteString(workdir)])
55
+
56
+ if conffile is not None:
57
+ args.extend(["-f", QuoteString(conffile)])
58
+
59
+ if jail is not None:
60
+ args.extend(["-j", QuoteString(jail)])
61
+
62
+ if key is not None:
63
+ args.extend(["-k", QuoteString(key)])
64
+
65
+ if server is not None:
66
+ args.extend(["-s", QuoteString(server)])
67
+
68
+ args.extend(["fetch", "install"])
69
+
70
+ yield StringCommand(*args)
@@ -0,0 +1,219 @@
1
+ """
2
+ Manage FreeBSD packages.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing_extensions import List, Optional, Union
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import QuoteString, StringCommand, operation
11
+ from pyinfra.facts.freebsd import PkgPackage
12
+
13
+
14
+ @operation()
15
+ def update(jail: Optional[str] = None, force: bool = False, reponame: Optional[str] = None):
16
+ """
17
+ Update the local catalogues of the enabled package repositories.
18
+
19
+ + jail: See ``-j`` in ``pkg(8)``.
20
+ + force: See ``-f`` in ``pkg-update(8)``.
21
+ + reponame: See ``-r`` in ``pkg-update(8)``
22
+
23
+ **Examples:**
24
+
25
+ .. code:: python
26
+
27
+ # host
28
+ pkg.update()
29
+
30
+ # jail
31
+ pkg.update(
32
+ jail="nginx"
33
+ )
34
+ """
35
+
36
+ args: List[Union[str, "QuoteString"]] = []
37
+
38
+ args.append("pkg")
39
+
40
+ if jail is not None:
41
+ args.extend(["-j", QuoteString(jail)])
42
+
43
+ args.extend(["update"])
44
+
45
+ if force:
46
+ args.append("-f")
47
+
48
+ if reponame is not None:
49
+ args.extend(["-r", QuoteString(reponame)])
50
+
51
+ yield StringCommand(*args)
52
+
53
+
54
+ @operation()
55
+ def upgrade(jail: Optional[str] = None, force: bool = False, reponame: Optional[str] = None):
56
+ """
57
+ Perform upgrades of package software distributions.
58
+
59
+ + jail: See ``-j`` in ``pkg(8)``.
60
+ + force: See ``-f`` in ``pkg-upgrade(8)``.
61
+ + reponame: See ``-r`` in ``pkg-upgrade(8)``.
62
+
63
+ **Examples:**
64
+
65
+ .. code:: python
66
+
67
+ # host
68
+ pkg.upgrade()
69
+
70
+ # jail
71
+ pkg.upgrade(
72
+ jail="nginx"
73
+ )
74
+ """
75
+
76
+ args: List[Union[str, "QuoteString"]] = []
77
+
78
+ args.append("pkg")
79
+
80
+ if jail is not None:
81
+ args.extend(["-j", QuoteString(jail)])
82
+
83
+ args.extend(["upgrade", "-y"])
84
+
85
+ if force:
86
+ args.append("-f")
87
+
88
+ if reponame is not None:
89
+ args.extend(["-r", QuoteString(reponame)])
90
+
91
+ yield StringCommand(*args)
92
+
93
+
94
+ @operation()
95
+ def install(package: str, jail: Optional[str] = None, reponame: Optional[str] = None):
96
+ """
97
+ Install packages from remote packages repositories or local archives.
98
+
99
+ + package: Package to install.
100
+ + jail: See ``-j`` in ``pkg(8)``.
101
+ + reponame: See ``-r`` in ``pkg-install(8)``.
102
+
103
+ **Example:**
104
+
105
+ .. code:: python
106
+
107
+ pkg.install("nginx")
108
+ """
109
+
110
+ if host.get_fact(PkgPackage, package=package, jail=jail):
111
+ host.noop(f"Package '{package}' already installed")
112
+ return
113
+
114
+ args: List[Union[str, "QuoteString"]] = []
115
+
116
+ args.append("pkg")
117
+
118
+ if jail is not None:
119
+ args.extend(["-j", QuoteString(jail)])
120
+
121
+ args.extend(["install", "-y"])
122
+
123
+ if reponame is not None:
124
+ args.extend(["-r", QuoteString(reponame)])
125
+
126
+ args.extend(["--", QuoteString(package)])
127
+
128
+ yield StringCommand(*args)
129
+
130
+
131
+ @operation()
132
+ def remove(package: str, jail: Optional[str] = None):
133
+ """
134
+ Deletes packages from the database and the system.
135
+
136
+ + package: Package to remove.
137
+ + jail: See ``-j`` in ``pkg(8)``.
138
+
139
+ **Example:**
140
+
141
+ .. code:: python
142
+
143
+ pkg.remove("nginx")
144
+ """
145
+
146
+ if not host.get_fact(PkgPackage, package=package, jail=jail):
147
+ host.noop(f"Package '{package}' cannot be found")
148
+ return
149
+
150
+ args: List[Union[str, "QuoteString"]] = []
151
+
152
+ args.append("pkg")
153
+
154
+ if jail is not None:
155
+ args.extend(["-j", QuoteString(jail)])
156
+
157
+ args.extend(["remove", "-y"])
158
+
159
+ args.extend(["--", QuoteString(package)])
160
+
161
+ yield StringCommand(*args)
162
+
163
+
164
+ @operation()
165
+ def autoremove(jail: Optional[str] = None):
166
+ """
167
+ Remove orphan packages.
168
+
169
+ + jail: See ``-j`` in ``pkg(8)``.
170
+
171
+ **Example:**
172
+
173
+ .. code:: python
174
+
175
+ pkg.autoremove()
176
+ """
177
+
178
+ args: List[Union[str, "QuoteString"]] = []
179
+
180
+ args.append("pkg")
181
+
182
+ if jail is not None:
183
+ args.extend(["-j", QuoteString(jail)])
184
+
185
+ args.extend(["autoremove", "-y"])
186
+
187
+ yield StringCommand(*args)
188
+
189
+
190
+ @operation()
191
+ def clean(all_pkg: bool = False, jail: Optional[str] = None):
192
+ """
193
+ Clean the local cache of fetched remote packages.
194
+
195
+ + all_pkg: See ``-a`` in ``pkg-clean(8)``.
196
+ + jail: See ``-j`` in ``pkg(8)``.
197
+
198
+ **Example:**
199
+
200
+ .. code:: python
201
+
202
+ pkg.clean(
203
+ all_pkg=True
204
+ )
205
+ """
206
+
207
+ args: List[Union[str, "QuoteString"]] = []
208
+
209
+ args.append("pkg")
210
+
211
+ if jail is not None:
212
+ args.extend(["-j", QuoteString(jail)])
213
+
214
+ args.extend(["clean", "-y"])
215
+
216
+ if all_pkg:
217
+ args.append("-a")
218
+
219
+ yield StringCommand(*args)
@@ -0,0 +1,116 @@
1
+ """
2
+ Manage FreeBSD services.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing_extensions import List, Optional, Union
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import QuoteString, StringCommand, operation
11
+ from pyinfra.api.exceptions import OperationValueError
12
+ from pyinfra.facts.freebsd import ServiceScript, ServiceStatus
13
+
14
+ SRV_STARTED: str = "started"
15
+ SRV_STOPPED: str = "stopped"
16
+ SRV_RESTARTED: str = "restarted"
17
+ SRV_RELOADED: str = "reloaded"
18
+ SRV_CUSTOM: str = "custom"
19
+
20
+
21
+ @operation()
22
+ def service(
23
+ srvname: str,
24
+ jail: Optional[str] = None,
25
+ srvstate: str = SRV_STARTED,
26
+ command: Optional[Union[str, List[str]]] = None,
27
+ environment: Optional[List[str]] = None,
28
+ verbose: bool = False,
29
+ ):
30
+ """
31
+ Control (start/stop/etc.) ``rc(8)`` scripts.
32
+
33
+ + srvname: Service.
34
+ + jail: See ``-j`` in ``service(8)``.
35
+ + srvstate: Desire state of the service.
36
+ + command: When ``srvstate`` is ``custom``, the command to execute.
37
+ + environment: See ``-E`` in ``service(8)``.
38
+ + verbose: See ``-v`` in ``service(8)``.
39
+
40
+ States:
41
+ There are a few states you can use to manipulate the service:
42
+
43
+ - started: The service must be started.
44
+ - stopped: The service must be stopped.
45
+ - restarted: The service must be restarted.
46
+ - reloaded: The service must be reloaded.
47
+ - custom: Run a custom command for this service.
48
+
49
+ **Examples:**
50
+
51
+ .. code:: python
52
+
53
+ # Start a service.
54
+ service.service(
55
+ "beanstalkd",
56
+ srvstate="started"
57
+ )
58
+
59
+ # Execute a custom command.
60
+ service.service(
61
+ "sopel",
62
+ srvstate="custom",
63
+ command="configure"
64
+ )
65
+ """
66
+
67
+ if not host.get_fact(ServiceScript, srvname=srvname, jail=jail):
68
+ host.noop(f"Cannot find rc(8) script '{srvname}'")
69
+ return
70
+
71
+ args: List[Union[str, "QuoteString"]] = []
72
+
73
+ args.append("service")
74
+
75
+ if verbose:
76
+ args.append("-v")
77
+
78
+ if jail is not None:
79
+ args.extend(["-j", QuoteString(jail)])
80
+
81
+ if environment is not None:
82
+ for env_var in environment:
83
+ args.extend(["-E", QuoteString(env_var)])
84
+
85
+ if srvstate == SRV_STARTED:
86
+ if host.get_fact(ServiceStatus, srvname=srvname, jail=jail):
87
+ host.noop(f"Service '{srvname}' already started")
88
+ return
89
+
90
+ args.extend([QuoteString(srvname), "start"])
91
+
92
+ elif srvstate == SRV_STOPPED:
93
+ if not host.get_fact(ServiceStatus, srvname=srvname, jail=jail):
94
+ host.noop(f"Service '{srvname}' already stopped")
95
+ return
96
+
97
+ args.extend([QuoteString(srvname), "stop"])
98
+ elif srvstate == SRV_RESTARTED:
99
+ args.extend([QuoteString(srvname), "restart"])
100
+
101
+ elif srvstate == SRV_RELOADED:
102
+ args.extend([QuoteString(srvname), "reload"])
103
+
104
+ elif srvstate == SRV_CUSTOM:
105
+ args.append(QuoteString(srvname))
106
+
107
+ if command is not None:
108
+ if isinstance(command, str):
109
+ command = [command]
110
+
111
+ args.extend([QuoteString(c) for c in command])
112
+
113
+ else:
114
+ raise OperationValueError(f"Invalid service command: {srvstate}")
115
+
116
+ yield StringCommand(*args)
@@ -0,0 +1,92 @@
1
+ """
2
+ Manipulate system rc files.
3
+ """
4
+
5
+ from __future__ import annotations
6
+
7
+ from typing_extensions import List, Optional, Union
8
+
9
+ from pyinfra import host
10
+ from pyinfra.api import QuoteString, StringCommand, operation
11
+ from pyinfra.api.exceptions import OperationValueError
12
+ from pyinfra.facts.freebsd import Sysrc
13
+
14
+ SYSRC_ADD: str = "add"
15
+ SYSRC_SUB: str = "sub"
16
+ SYSRC_SET: str = "set"
17
+ SYSRC_DEL: str = "del"
18
+
19
+
20
+ @operation()
21
+ def sysrc(
22
+ parameter: str,
23
+ value: str,
24
+ jail: Optional[str] = None,
25
+ command: str = SYSRC_SET,
26
+ overwrite: bool = False,
27
+ ):
28
+ """
29
+ Safely edit system rc files.
30
+
31
+ + parameter: Parameter to manipulate.
32
+ + value: Value, if the parameter requires it.
33
+ + jail: See ``-j`` in ``sysrc(8)``.
34
+ + command: Desire state of the parameter.
35
+ + overwrite: Overwrite the value of the parameter when ``command`` is set to ``set``.
36
+
37
+ Commands:
38
+ There are a few commands you can use to manipulate the rc file:
39
+
40
+ - add: Adds the value to the parameter.
41
+ - sub: Delete the parameter value.
42
+ - set: Change the parameter value. If the parameter already has a value
43
+ set, the changes will not be applied unless ``overwrite`` is set
44
+ to ``True``.
45
+ - del: Delete the parameter.
46
+
47
+ **Example:**
48
+
49
+ .. code:: python
50
+
51
+ sysrc.sysrc(
52
+ "beanstalkd_enable",
53
+ "YES",
54
+ command="set"
55
+ )
56
+ """
57
+
58
+ args: List[Union[str, "QuoteString"]] = []
59
+
60
+ args.extend(["sysrc", "-i"])
61
+
62
+ if command == SYSRC_DEL:
63
+ sign = "="
64
+
65
+ if not host.get_fact(Sysrc, parameter=parameter, jail=jail):
66
+ host.noop(f"Cannot find sysrc(8) parameter '{parameter}'")
67
+ return
68
+
69
+ args.append("-x")
70
+
71
+ elif command == SYSRC_SET:
72
+ sign = "="
73
+
74
+ if not overwrite and host.get_fact(Sysrc, parameter=parameter, jail=jail):
75
+ host.noop(f"sysrc(8) parameter '{parameter}' already set")
76
+ return
77
+
78
+ elif command == SYSRC_ADD:
79
+ sign = "+="
80
+
81
+ elif command == SYSRC_SUB:
82
+ sign = "-="
83
+
84
+ else:
85
+ raise OperationValueError(f"Invalid sysrc command: {command}")
86
+
87
+ if jail is not None:
88
+ args.extend(["-j", QuoteString(jail)])
89
+
90
+ args.extend(["--", QuoteString(f"{parameter}{sign}{value}")])
91
+
92
+ yield StringCommand(*args)