pyinfra 3.0b1__py2.py3-none-any.whl → 3.0b3__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 (108) hide show
  1. pyinfra/api/arguments.py +9 -3
  2. pyinfra/api/arguments_typed.py +8 -5
  3. pyinfra/api/command.py +5 -3
  4. pyinfra/api/config.py +115 -13
  5. pyinfra/api/connectors.py +5 -2
  6. pyinfra/api/facts.py +33 -32
  7. pyinfra/api/host.py +5 -5
  8. pyinfra/api/inventory.py +4 -0
  9. pyinfra/api/operation.py +22 -14
  10. pyinfra/api/util.py +24 -16
  11. pyinfra/connectors/base.py +3 -6
  12. pyinfra/connectors/docker.py +2 -9
  13. pyinfra/connectors/local.py +2 -2
  14. pyinfra/connectors/ssh.py +2 -2
  15. pyinfra/connectors/util.py +6 -7
  16. pyinfra/connectors/vagrant.py +5 -5
  17. pyinfra/context.py +1 -0
  18. pyinfra/facts/apk.py +2 -0
  19. pyinfra/facts/apt.py +2 -0
  20. pyinfra/facts/brew.py +2 -0
  21. pyinfra/facts/bsdinit.py +2 -0
  22. pyinfra/facts/cargo.py +2 -0
  23. pyinfra/facts/choco.py +2 -0
  24. pyinfra/facts/deb.py +7 -2
  25. pyinfra/facts/dnf.py +2 -0
  26. pyinfra/facts/docker.py +18 -0
  27. pyinfra/facts/files.py +2 -0
  28. pyinfra/facts/gem.py +2 -0
  29. pyinfra/facts/gpg.py +2 -0
  30. pyinfra/facts/hardware.py +30 -22
  31. pyinfra/facts/launchd.py +2 -0
  32. pyinfra/facts/lxd.py +2 -0
  33. pyinfra/facts/mysql.py +12 -6
  34. pyinfra/facts/npm.py +1 -0
  35. pyinfra/facts/openrc.py +2 -0
  36. pyinfra/facts/pacman.py +6 -2
  37. pyinfra/facts/pip.py +2 -0
  38. pyinfra/facts/pkg.py +2 -0
  39. pyinfra/facts/pkgin.py +2 -0
  40. pyinfra/facts/postgres.py +6 -6
  41. pyinfra/facts/postgresql.py +2 -0
  42. pyinfra/facts/rpm.py +12 -9
  43. pyinfra/facts/runit.py +68 -0
  44. pyinfra/facts/server.py +10 -13
  45. pyinfra/facts/snap.py +2 -0
  46. pyinfra/facts/systemd.py +2 -0
  47. pyinfra/facts/upstart.py +2 -0
  48. pyinfra/facts/util/packaging.py +3 -2
  49. pyinfra/facts/vzctl.py +2 -0
  50. pyinfra/facts/xbps.py +2 -0
  51. pyinfra/facts/yum.py +2 -0
  52. pyinfra/facts/zypper.py +2 -0
  53. pyinfra/operations/apk.py +3 -1
  54. pyinfra/operations/apt.py +16 -18
  55. pyinfra/operations/brew.py +10 -8
  56. pyinfra/operations/bsdinit.py +5 -3
  57. pyinfra/operations/cargo.py +3 -1
  58. pyinfra/operations/choco.py +3 -1
  59. pyinfra/operations/dnf.py +15 -19
  60. pyinfra/operations/docker.py +339 -0
  61. pyinfra/operations/files.py +81 -66
  62. pyinfra/operations/gem.py +3 -1
  63. pyinfra/operations/git.py +18 -16
  64. pyinfra/operations/iptables.py +27 -25
  65. pyinfra/operations/launchd.py +5 -6
  66. pyinfra/operations/lxd.py +7 -4
  67. pyinfra/operations/mysql.py +57 -53
  68. pyinfra/operations/npm.py +8 -1
  69. pyinfra/operations/openrc.py +5 -3
  70. pyinfra/operations/pacman.py +4 -5
  71. pyinfra/operations/pip.py +11 -9
  72. pyinfra/operations/pkg.py +3 -1
  73. pyinfra/operations/pkgin.py +3 -1
  74. pyinfra/operations/postgres.py +39 -37
  75. pyinfra/operations/postgresql.py +2 -0
  76. pyinfra/operations/puppet.py +3 -1
  77. pyinfra/operations/python.py +7 -3
  78. pyinfra/operations/runit.py +182 -0
  79. pyinfra/operations/selinux.py +42 -16
  80. pyinfra/operations/server.py +52 -43
  81. pyinfra/operations/snap.py +3 -1
  82. pyinfra/operations/ssh.py +12 -10
  83. pyinfra/operations/systemd.py +12 -8
  84. pyinfra/operations/sysvinit.py +6 -4
  85. pyinfra/operations/upstart.py +5 -3
  86. pyinfra/operations/util/docker.py +177 -0
  87. pyinfra/operations/util/files.py +24 -16
  88. pyinfra/operations/util/packaging.py +53 -37
  89. pyinfra/operations/util/service.py +25 -18
  90. pyinfra/operations/vzctl.py +12 -10
  91. pyinfra/operations/xbps.py +3 -1
  92. pyinfra/operations/yum.py +14 -18
  93. pyinfra/operations/zypper.py +8 -9
  94. pyinfra/version.py +5 -2
  95. {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/METADATA +30 -28
  96. pyinfra-3.0b3.dist-info/RECORD +167 -0
  97. {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/WHEEL +1 -1
  98. pyinfra_cli/exceptions.py +0 -5
  99. pyinfra_cli/inventory.py +38 -19
  100. pyinfra_cli/prints.py +15 -11
  101. pyinfra_cli/util.py +3 -1
  102. tests/test_api/test_api_operations.py +1 -1
  103. tests/test_connectors/test_ssh.py +66 -13
  104. tests/test_connectors/test_vagrant.py +3 -3
  105. pyinfra-3.0b1.dist-info/RECORD +0 -163
  106. {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/LICENSE.md +0 -0
  107. {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/entry_points.txt +0 -0
  108. {pyinfra-3.0b1.dist-info → pyinfra-3.0b3.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import operation
2
4
 
3
5
  from . import postgres
@@ -1,8 +1,10 @@
1
+ from __future__ import annotations
2
+
1
3
  from pyinfra.api import operation
2
4
 
3
5
 
4
6
  @operation(is_idempotent=False)
5
- def agent(server=None, port=None):
7
+ def agent(server: str | None = None, port: int | None = None):
6
8
  """
7
9
  Run puppet agent
8
10
 
@@ -2,11 +2,15 @@
2
2
  The Python module allows you to execute Python code within the context of a deploy.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
7
+ from typing import Callable
8
+
5
9
  from pyinfra.api import FunctionCommand, operation
6
10
 
7
11
 
8
12
  @operation(is_idempotent=False, _set_in_op=False)
9
- def call(function, *args, **kwargs):
13
+ def call(function: Callable, *args, **kwargs):
10
14
  """
11
15
  Execute a Python function within a deploy.
12
16
 
@@ -43,7 +47,7 @@ def call(function, *args, **kwargs):
43
47
 
44
48
 
45
49
  @operation(is_idempotent=False, _set_in_op=False)
46
- def raise_exception(exception, *args, **kwargs):
50
+ def raise_exception(exception: Exception, *args, **kwargs):
47
51
  """
48
52
  Raise a Python exception within a deploy.
49
53
 
@@ -63,6 +67,6 @@ def raise_exception(exception, *args, **kwargs):
63
67
  """
64
68
 
65
69
  def raise_exc(*args, **kwargs): # pragma: no cover
66
- raise exception(*args, **kwargs)
70
+ raise exception(*args, **kwargs) # type: ignore[operator]
67
71
 
68
72
  yield FunctionCommand(raise_exc, args, kwargs)
@@ -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
+ )
@@ -1,19 +1,36 @@
1
1
  """
2
2
  Provides operations to set SELinux file contexts, booleans and port types.
3
3
  """
4
+
5
+ from __future__ import annotations
6
+
7
+ from enum import Enum
8
+
4
9
  from pyinfra import host
5
- from pyinfra.api import QuoteString, StringCommand, operation
10
+ from pyinfra.api import OperationValueError, QuoteString, StringCommand, operation
6
11
  from pyinfra.facts.selinux import FileContext, FileContextMapping, SEBoolean, SEPort, SEPorts
7
12
  from pyinfra.facts.server import Which
8
13
 
9
14
 
15
+ class Boolean(Enum):
16
+ ON = "on"
17
+ OFF = "off"
18
+
19
+
20
+ class Protocol(Enum):
21
+ UDP = "udp"
22
+ TCP = "tcp"
23
+ SCTP = "sctp"
24
+ DCCP = "dccp"
25
+
26
+
10
27
  @operation()
11
- def boolean(bool_name, value, persistent=False):
28
+ def boolean(bool_name: str, value: Boolean, persistent=False):
12
29
  """
13
30
  Set the specified SELinux boolean to the desired state.
14
31
 
15
32
  + boolean: name of an SELinux boolean
16
- + state: 'on' or 'off'
33
+ + value: desired state of the boolean
17
34
  + persistent: whether to write updated policy or not
18
35
 
19
36
  Note: This operation requires root privileges.
@@ -25,26 +42,31 @@ def boolean(bool_name, value, persistent=False):
25
42
  selinux.boolean(
26
43
  name='Allow Apache to connect to LDAP server',
27
44
  'httpd_can_network_connect',
28
- 'on',
45
+ Boolean.ON,
29
46
  persistent=True
30
47
  )
31
48
  """
32
- _valid_states = ["on", "off"]
33
49
 
34
- if value not in _valid_states:
35
- raise ValueError(
36
- f'\'value\' must be one of \'{",".join(_valid_states)}\' but found \'{value}\'',
37
- )
50
+ value_str: str
51
+ if value in ["on", "off"]: # compatibility with the old version
52
+ assert isinstance(value, str)
53
+ value_str = value
54
+ elif value is Boolean.ON:
55
+ value_str = "on"
56
+ elif value is Boolean.OFF:
57
+ value_str = "off"
58
+ else:
59
+ raise OperationValueError(f"Invalid value '{value}' for boolean operation")
38
60
 
39
- if host.get_fact(SEBoolean, boolean=bool_name) != value:
61
+ if host.get_fact(SEBoolean, boolean=bool_name) != value_str:
40
62
  persist = "-P " if persistent else ""
41
- yield StringCommand("setsebool", f"{persist}{bool_name}", value)
63
+ yield StringCommand("setsebool", f"{persist}{bool_name}", value_str)
42
64
  else:
43
- host.noop(f"boolean '{bool_name}' already had the value '{value}'")
65
+ host.noop(f"boolean '{bool_name}' already had the value '{value_str}'")
44
66
 
45
67
 
46
68
  @operation()
47
- def file_context(path, se_type):
69
+ def file_context(path: str, se_type: str):
48
70
  """
49
71
  Set the SELinux type for the specified path to the specified value.
50
72
 
@@ -70,7 +92,7 @@ def file_context(path, se_type):
70
92
 
71
93
 
72
94
  @operation()
73
- def file_context_mapping(target, se_type=None, present=True):
95
+ def file_context_mapping(target: str, se_type: str | None = None, present=True):
74
96
  """
75
97
  Set the SELinux file context mapping for paths matching the target.
76
98
 
@@ -110,7 +132,7 @@ def file_context_mapping(target, se_type=None, present=True):
110
132
 
111
133
 
112
134
  @operation()
113
- def port(protocol, port_num, se_type=None, present=True):
135
+ def port(protocol: Protocol | str, port_num: int, se_type: str | None = None, present=True):
114
136
  """
115
137
  Set the SELinux type for the specified protocol and port.
116
138
 
@@ -127,12 +149,16 @@ def port(protocol, port_num, se_type=None, present=True):
127
149
 
128
150
  selinux.port(
129
151
  name='Allow Apache to provide service on port 2222',
130
- 'tcp',
152
+ Protocol.TCP,
131
153
  2222,
132
154
  'http_port_t',
133
155
  )
134
156
  """
135
157
 
158
+ if protocol is Protocol:
159
+ assert isinstance(protocol, Protocol)
160
+ protocol = protocol.value
161
+
136
162
  if present and (se_type is None):
137
163
  raise ValueError("se_type must have a valid value if present is set")
138
164
 
@@ -3,6 +3,8 @@ The server module takes care of os-level state. Targets POSIX compatibility, tes
3
3
  Linux/BSD.
4
4
  """
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import shlex
7
9
  from io import StringIO
8
10
  from itertools import filterfalse, tee
@@ -18,6 +20,7 @@ from pyinfra.facts.files import Directory, FindInFile, Link
18
20
  from pyinfra.facts.server import (
19
21
  Crontab,
20
22
  Groups,
23
+ Home,
21
24
  Hostname,
22
25
  KernelModules,
23
26
  Locales,
@@ -38,6 +41,7 @@ from . import (
38
41
  openrc,
39
42
  pacman,
40
43
  pkg,
44
+ runit,
41
45
  systemd,
42
46
  sysvinit,
43
47
  upstart,
@@ -139,7 +143,7 @@ def wait(port: int):
139
143
 
140
144
 
141
145
  @operation(is_idempotent=False)
142
- def shell(commands):
146
+ def shell(commands: str | list[str]):
143
147
  """
144
148
  Run raw shell code on server during a deploy. If the command would
145
149
  modify data that would be in a fact, the fact would not be updated
@@ -166,7 +170,7 @@ def shell(commands):
166
170
 
167
171
 
168
172
  @operation(is_idempotent=False)
169
- def script(src, args=()):
173
+ def script(src: str, args=()):
170
174
  """
171
175
  Upload and execute a local script on the remote host.
172
176
 
@@ -199,7 +203,7 @@ def script(src, args=()):
199
203
 
200
204
 
201
205
  @operation(is_idempotent=False)
202
- def script_template(src, args=(), **data):
206
+ def script_template(src: str, args=(), **data):
203
207
  """
204
208
  Generate, upload and execute a local script template on the remote host.
205
209
 
@@ -229,7 +233,7 @@ def script_template(src, args=(), **data):
229
233
 
230
234
 
231
235
  @operation()
232
- def modprobe(module, present=True, force=False):
236
+ def modprobe(module: str, present=True, force=False):
233
237
  """
234
238
  Load/unload kernel modules.
235
239
 
@@ -281,11 +285,11 @@ def modprobe(module, present=True, force=False):
281
285
 
282
286
  @operation()
283
287
  def mount(
284
- path,
288
+ path: str,
285
289
  mounted=True,
286
- options=None,
287
- device=None,
288
- fs_type=None,
290
+ options: list[str] | None = None,
291
+ device: str | None = None,
292
+ fs_type: str | None = None,
289
293
  # TODO: do we want to manage fstab here?
290
294
  # update_fstab=False,
291
295
  ):
@@ -344,7 +348,7 @@ def mount(
344
348
 
345
349
 
346
350
  @operation()
347
- def hostname(hostname, hostname_file=None):
351
+ def hostname(hostname: str, hostname_file: str | None = None):
348
352
  """
349
353
  Set the system hostname using ``hostnamectl`` or ``hostname`` on older systems.
350
354
 
@@ -402,8 +406,8 @@ def hostname(hostname, hostname_file=None):
402
406
 
403
407
  @operation()
404
408
  def sysctl(
405
- key,
406
- value,
409
+ key: str,
410
+ value: str | int | list[str | int],
407
411
  persist=False,
408
412
  persist_file="/etc/sysctl.conf",
409
413
  ):
@@ -449,12 +453,12 @@ def sysctl(
449
453
 
450
454
  @operation()
451
455
  def service(
452
- service,
456
+ service: str,
453
457
  running=True,
454
458
  restarted=False,
455
459
  reloaded=False,
456
- command=None,
457
- enabled=None,
460
+ command: str | None = None,
461
+ enabled: bool | None = None,
458
462
  ):
459
463
  """
460
464
  Manage the state of services. This command checks for the presence of all the
@@ -489,6 +493,9 @@ def service(
489
493
  elif host.get_fact(Which, command="initctl"):
490
494
  service_operation = upstart.service
491
495
 
496
+ elif host.get_fact(Which, command="sv"):
497
+ service_operation = runit.service
498
+
492
499
  elif (
493
500
  host.get_fact(Which, command="service")
494
501
  or host.get_fact(Link, path="/etc/init.d")
@@ -518,7 +525,7 @@ def service(
518
525
 
519
526
  @operation()
520
527
  def packages(
521
- packages,
528
+ packages: str | list[str],
522
529
  present=True,
523
530
  ):
524
531
  """
@@ -583,16 +590,16 @@ def packages(
583
590
 
584
591
  @operation()
585
592
  def crontab(
586
- command,
593
+ command: str,
587
594
  present=True,
588
- user=None,
589
- cron_name=None,
595
+ user: str | None = None,
596
+ cron_name: str | None = None,
590
597
  minute="*",
591
598
  hour="*",
592
599
  month="*",
593
600
  day_of_week="*",
594
601
  day_of_month="*",
595
- special_time=None,
602
+ special_time: str | None = None,
596
603
  interpolate_variables=False,
597
604
  ):
598
605
  """
@@ -663,7 +670,7 @@ def crontab(
663
670
 
664
671
  exists = existing_crontab is not None
665
672
 
666
- edit_commands = []
673
+ edit_commands: list[str | StringCommand] = []
667
674
  temp_filename = host.get_temp_filename()
668
675
 
669
676
  if special_time:
@@ -758,7 +765,7 @@ def crontab(
758
765
 
759
766
 
760
767
  @operation()
761
- def group(group, present=True, system=False, gid=None):
768
+ def group(group: str, present=True, system=False, gid: int | str | None = None):
762
769
  """
763
770
  Add/remove system groups.
764
771
 
@@ -827,12 +834,12 @@ def group(group, present=True, system=False, gid=None):
827
834
 
828
835
  @operation()
829
836
  def user_authorized_keys(
830
- user,
831
- public_keys,
832
- group=None,
837
+ user: str,
838
+ public_keys: str | list[str],
839
+ group: str | None = None,
833
840
  delete_keys=False,
834
- authorized_key_directory=None,
835
- authorized_key_filename=None,
841
+ authorized_key_directory: str | None = None,
842
+ authorized_key_filename: str | None = None,
836
843
  ):
837
844
  """
838
845
  Manage `authorized_keys` of system users.
@@ -858,7 +865,9 @@ def user_authorized_keys(
858
865
  """
859
866
 
860
867
  if not authorized_key_directory:
861
- authorized_key_directory = f"/home/{user}/.ssh/"
868
+ home = host.get_fact(Home, user=user)
869
+ authorized_key_directory = f"{home}/.ssh"
870
+
862
871
  if not authorized_key_filename:
863
872
  authorized_key_filename = "authorized_keys"
864
873
 
@@ -923,22 +932,22 @@ def user_authorized_keys(
923
932
 
924
933
  @operation()
925
934
  def user(
926
- user,
935
+ user: str,
927
936
  present=True,
928
- home=None,
929
- shell=None,
930
- group=None,
931
- groups=None,
932
- public_keys=None,
937
+ home: str | None = None,
938
+ shell: str | None = None,
939
+ group: str | None = None,
940
+ groups: list[str] | None = None,
941
+ public_keys: str | list[str] | None = None,
933
942
  delete_keys=False,
934
943
  ensure_home=True,
935
944
  create_home=False,
936
945
  system=False,
937
- uid=None,
938
- comment=None,
946
+ uid: int | None = None,
947
+ comment: str | None = None,
939
948
  add_deploy_dir=True,
940
949
  unique=True,
941
- password=None,
950
+ password: str | None = None,
942
951
  ):
943
952
  """
944
953
  Add/remove/update system users & their ssh `authorized_keys`.
@@ -1124,7 +1133,7 @@ def user(
1124
1133
  existing_user["password"] = password
1125
1134
 
1126
1135
  # Ensure home directory ownership
1127
- if ensure_home:
1136
+ if ensure_home and home:
1128
1137
  yield from files.directory._inner(
1129
1138
  path=home,
1130
1139
  user=user,
@@ -1147,7 +1156,7 @@ def user(
1147
1156
 
1148
1157
  @operation()
1149
1158
  def locale(
1150
- locale,
1159
+ locale: str,
1151
1160
  present=True,
1152
1161
  ):
1153
1162
  """
@@ -1217,10 +1226,10 @@ def locale(
1217
1226
 
1218
1227
  @operation()
1219
1228
  def security_limit(
1220
- domain,
1221
- limit_type,
1222
- item,
1223
- value,
1229
+ domain: str,
1230
+ limit_type: str,
1231
+ item: str,
1232
+ value: int,
1224
1233
  ):
1225
1234
  """
1226
1235
  Edit /etc/security/limits.conf configuration.
@@ -1239,7 +1248,7 @@ def security_limit(
1239
1248
  domain='*',
1240
1249
  limit_type='soft',
1241
1250
  item='nofile',
1242
- value='1024',
1251
+ value=1024,
1243
1252
  )
1244
1253
  """
1245
1254
 
@@ -2,6 +2,8 @@
2
2
  Manage snap packages. See https://snapcraft.io/
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  from pyinfra import host
6
8
  from pyinfra.api import operation
7
9
  from pyinfra.facts.snap import SnapPackage, SnapPackages
@@ -9,7 +11,7 @@ from pyinfra.facts.snap import SnapPackage, SnapPackages
9
11
 
10
12
  @operation()
11
13
  def package(
12
- packages=None,
14
+ packages: str | list[str] | None = None,
13
15
  channel="latest/stable",
14
16
  classic=False,
15
17
  present=True,
pyinfra/operations/ssh.py CHANGED
@@ -4,6 +4,8 @@ Execute commands and up/download files *from* the remote host.
4
4
  Eg: ``pyinfra -> inventory-host.net <-> another-host.net``
5
5
  """
6
6
 
7
+ from __future__ import annotations
8
+
7
9
  import shlex
8
10
 
9
11
  from pyinfra import host
@@ -15,7 +17,7 @@ from . import files
15
17
 
16
18
 
17
19
  @operation()
18
- def keyscan(hostname, force=False, port=22):
20
+ def keyscan(hostname: str, force=False, port=22):
19
21
  """
20
22
  Check/add hosts to the ``~/.ssh/known_hosts`` file.
21
23
 
@@ -63,7 +65,7 @@ def keyscan(hostname, force=False, port=22):
63
65
 
64
66
 
65
67
  @operation(is_idempotent=False)
66
- def command(hostname, command, user=None, port=22):
68
+ def command(hostname: str, command: str, user: str | None = None, port=22):
67
69
  """
68
70
  Execute commands on other servers over SSH.
69
71
 
@@ -95,11 +97,11 @@ def command(hostname, command, user=None, port=22):
95
97
 
96
98
  @operation(is_idempotent=False)
97
99
  def upload(
98
- hostname,
99
- filename,
100
- remote_filename=None,
100
+ hostname: str,
101
+ filename: str,
102
+ remote_filename: str | None = None,
101
103
  port=22,
102
- user=None,
104
+ user: str | None = None,
103
105
  use_remote_sudo=False,
104
106
  ssh_keyscan=False,
105
107
  ):
@@ -159,12 +161,12 @@ def upload(
159
161
 
160
162
  @operation()
161
163
  def download(
162
- hostname,
163
- filename,
164
- local_filename=None,
164
+ hostname: str,
165
+ filename: str,
166
+ local_filename: str | None = None,
165
167
  force=False,
166
168
  port=22,
167
- user=None,
169
+ user: str | None = None,
168
170
  ssh_keyscan=False,
169
171
  ):
170
172
  """