pyinfra 2.9.1__py2.py3-none-any.whl → 3.0__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 (156) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +265 -253
  3. pyinfra/api/arguments_typed.py +80 -0
  4. pyinfra/api/command.py +68 -53
  5. pyinfra/api/config.py +139 -32
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +7 -26
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +102 -137
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +21 -25
  13. pyinfra/api/operation.py +240 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +79 -86
  17. pyinfra/connectors/base.py +147 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +220 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +196 -208
  22. pyinfra/connectors/ssh.py +530 -613
  23. pyinfra/connectors/ssh_util.py +114 -0
  24. pyinfra/connectors/sshuserclient/client.py +5 -3
  25. pyinfra/connectors/terraform.py +86 -65
  26. pyinfra/connectors/util.py +211 -137
  27. pyinfra/connectors/vagrant.py +60 -53
  28. pyinfra/context.py +4 -2
  29. pyinfra/facts/apk.py +2 -0
  30. pyinfra/facts/apt.py +2 -0
  31. pyinfra/facts/brew.py +2 -0
  32. pyinfra/facts/bsdinit.py +2 -0
  33. pyinfra/facts/cargo.py +2 -0
  34. pyinfra/facts/choco.py +2 -0
  35. pyinfra/facts/deb.py +7 -2
  36. pyinfra/facts/dnf.py +2 -0
  37. pyinfra/facts/docker.py +19 -0
  38. pyinfra/facts/files.py +47 -32
  39. pyinfra/facts/gem.py +2 -0
  40. pyinfra/facts/git.py +3 -1
  41. pyinfra/facts/gpg.py +3 -1
  42. pyinfra/facts/hardware.py +34 -24
  43. pyinfra/facts/iptables.py +5 -3
  44. pyinfra/facts/launchd.py +2 -0
  45. pyinfra/facts/lxd.py +2 -0
  46. pyinfra/facts/mysql.py +13 -6
  47. pyinfra/facts/npm.py +1 -0
  48. pyinfra/facts/openrc.py +2 -0
  49. pyinfra/facts/pacman.py +6 -2
  50. pyinfra/facts/pip.py +2 -0
  51. pyinfra/facts/pkg.py +2 -0
  52. pyinfra/facts/pkgin.py +2 -0
  53. pyinfra/facts/postgres.py +168 -0
  54. pyinfra/facts/postgresql.py +6 -160
  55. pyinfra/facts/rpm.py +12 -9
  56. pyinfra/facts/runit.py +68 -0
  57. pyinfra/facts/selinux.py +3 -1
  58. pyinfra/facts/server.py +80 -36
  59. pyinfra/facts/snap.py +2 -0
  60. pyinfra/facts/systemd.py +31 -12
  61. pyinfra/facts/sysvinit.py +10 -10
  62. pyinfra/facts/upstart.py +2 -0
  63. pyinfra/facts/util/packaging.py +7 -4
  64. pyinfra/facts/vzctl.py +2 -0
  65. pyinfra/facts/xbps.py +2 -0
  66. pyinfra/facts/yum.py +2 -0
  67. pyinfra/facts/zypper.py +2 -0
  68. pyinfra/local.py +4 -5
  69. pyinfra/operations/apk.py +6 -4
  70. pyinfra/operations/apt.py +46 -65
  71. pyinfra/operations/brew.py +17 -22
  72. pyinfra/operations/bsdinit.py +9 -7
  73. pyinfra/operations/cargo.py +4 -2
  74. pyinfra/operations/choco.py +4 -2
  75. pyinfra/operations/dnf.py +19 -23
  76. pyinfra/operations/docker.py +339 -0
  77. pyinfra/operations/files.py +188 -386
  78. pyinfra/operations/gem.py +4 -2
  79. pyinfra/operations/git.py +24 -53
  80. pyinfra/operations/iptables.py +29 -35
  81. pyinfra/operations/launchd.py +6 -7
  82. pyinfra/operations/lxd.py +8 -13
  83. pyinfra/operations/mysql.py +62 -81
  84. pyinfra/operations/npm.py +9 -2
  85. pyinfra/operations/openrc.py +6 -4
  86. pyinfra/operations/pacman.py +7 -8
  87. pyinfra/operations/pip.py +25 -24
  88. pyinfra/operations/pkg.py +4 -2
  89. pyinfra/operations/pkgin.py +6 -4
  90. pyinfra/operations/postgres.py +349 -0
  91. pyinfra/operations/postgresql.py +18 -379
  92. pyinfra/operations/puppet.py +3 -1
  93. pyinfra/operations/python.py +8 -19
  94. pyinfra/operations/runit.py +182 -0
  95. pyinfra/operations/selinux.py +47 -44
  96. pyinfra/operations/server.py +111 -127
  97. pyinfra/operations/snap.py +4 -4
  98. pyinfra/operations/ssh.py +20 -33
  99. pyinfra/operations/systemd.py +19 -15
  100. pyinfra/operations/sysvinit.py +9 -16
  101. pyinfra/operations/upstart.py +9 -7
  102. pyinfra/operations/util/__init__.py +12 -0
  103. pyinfra/operations/util/docker.py +177 -0
  104. pyinfra/operations/util/files.py +24 -16
  105. pyinfra/operations/util/packaging.py +55 -57
  106. pyinfra/operations/util/service.py +39 -51
  107. pyinfra/operations/vzctl.py +12 -10
  108. pyinfra/operations/xbps.py +6 -4
  109. pyinfra/operations/yum.py +18 -22
  110. pyinfra/operations/zypper.py +12 -13
  111. pyinfra/version.py +5 -2
  112. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/WHEEL +1 -1
  115. pyinfra-3.0.dist-info/entry_points.txt +11 -0
  116. pyinfra_cli/__main__.py +4 -3
  117. pyinfra_cli/commands.py +7 -2
  118. pyinfra_cli/exceptions.py +78 -42
  119. pyinfra_cli/inventory.py +40 -6
  120. pyinfra_cli/log.py +17 -3
  121. pyinfra_cli/main.py +133 -90
  122. pyinfra_cli/prints.py +95 -127
  123. pyinfra_cli/util.py +62 -29
  124. tests/test_api/test_api.py +2 -0
  125. tests/test_api/test_api_arguments.py +13 -13
  126. tests/test_api/test_api_deploys.py +28 -29
  127. tests/test_api/test_api_facts.py +60 -98
  128. tests/test_api/test_api_operations.py +101 -201
  129. tests/test_cli/test_cli.py +18 -49
  130. tests/test_cli/test_cli_deploy.py +11 -37
  131. tests/test_cli/test_cli_exceptions.py +50 -19
  132. tests/test_cli/util.py +1 -1
  133. tests/test_connectors/test_chroot.py +6 -6
  134. tests/test_connectors/test_docker.py +4 -4
  135. tests/test_connectors/test_dockerssh.py +38 -50
  136. tests/test_connectors/test_local.py +11 -12
  137. tests/test_connectors/test_ssh.py +105 -93
  138. tests/test_connectors/test_terraform.py +9 -15
  139. tests/test_connectors/test_util.py +24 -46
  140. tests/test_connectors/test_vagrant.py +7 -7
  141. pyinfra/api/operation.pyi +0 -117
  142. pyinfra/connectors/ansible.py +0 -171
  143. pyinfra/connectors/mech.py +0 -186
  144. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  145. pyinfra/connectors/winrm.py +0 -320
  146. pyinfra/facts/windows.py +0 -366
  147. pyinfra/facts/windows_files.py +0 -90
  148. pyinfra/operations/windows.py +0 -59
  149. pyinfra/operations/windows_files.py +0 -551
  150. pyinfra-2.9.1.dist-info/RECORD +0 -170
  151. pyinfra-2.9.1.dist-info/entry_points.txt +0 -14
  152. tests/test_connectors/test_ansible.py +0 -64
  153. tests/test_connectors/test_mech.py +0 -126
  154. tests/test_connectors/test_winrm.py +0 -76
  155. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.1.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
pyinfra/api/operations.py CHANGED
@@ -1,26 +1,27 @@
1
+ from __future__ import annotations
2
+
1
3
  import traceback
2
4
  from itertools import product
3
5
  from socket import error as socket_error, timeout as timeout_error
4
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, Optional, cast
5
7
 
6
8
  import click
7
9
  import gevent
8
10
  from paramiko import SSHException
9
11
 
10
- import pyinfra
11
12
  from pyinfra import logger
12
- from pyinfra.context import ctx_host
13
+ from pyinfra.connectors.util import CommandOutput, OutputLine
14
+ from pyinfra.context import ctx_host, ctx_state
13
15
  from pyinfra.progress import progress_spinner
14
16
 
15
- from .arguments import get_executor_kwarg_keys
17
+ from .arguments import CONNECTOR_ARGUMENT_KEYS, ConnectorArguments
16
18
  from .command import FunctionCommand, PyinfraCommand, StringCommand
17
- from .exceptions import NoMoreHostsError, PyinfraError
19
+ from .exceptions import PyinfraError
18
20
  from .util import (
19
21
  format_exception,
20
22
  log_error_or_warning,
21
23
  log_host_command_error,
22
24
  log_operation_start,
23
- memoize,
24
25
  print_host_combined_output,
25
26
  )
26
27
 
@@ -29,192 +30,153 @@ if TYPE_CHECKING:
29
30
  from .state import State
30
31
 
31
32
 
32
- @memoize
33
- def show_pre_or_post_condition_warning(condition_name):
34
- logger.warning("The `{0}` argument is in beta!".format(condition_name))
33
+ # Run a single host operation
34
+ #
35
35
 
36
36
 
37
- def run_host_op(state: "State", host: "Host", op_hash):
37
+ def run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
38
38
  state.trigger_callbacks("operation_host_start", host, op_hash)
39
39
 
40
40
  if op_hash not in state.ops[host]:
41
41
  logger.info("{0}{1}".format(host.print_prefix, click.style("Skipped", "blue")))
42
42
  return True
43
43
 
44
- op_data = state.get_op_data(host, op_hash)
45
- global_kwargs = op_data["global_kwargs"]
46
-
47
44
  op_meta = state.get_op_meta(op_hash)
45
+ logger.debug("Starting operation %r on %s", op_meta.names, host)
48
46
 
49
- ignore_errors = global_kwargs["ignore_errors"]
50
- continue_on_error = global_kwargs["continue_on_error"]
51
-
52
- logger.debug("Starting operation %r on %s", op_meta["names"], host)
53
-
54
- executor_kwarg_keys = get_executor_kwarg_keys()
55
- base_executor_kwargs = {
56
- key: global_kwargs[key] for key in executor_kwarg_keys if key in global_kwargs
57
- }
58
-
59
- def _run_shell_command(command, executor_kwargs):
60
- status = False
61
- combined_output_lines = []
62
-
63
- try:
64
- status, combined_output_lines = command.execute(state, host, executor_kwargs)
65
- except (timeout_error, socket_error, SSHException) as e:
66
- log_host_command_error(
67
- host,
68
- e,
69
- timeout=global_kwargs["timeout"],
70
- )
71
-
72
- # If we failed and have no already printed the stderr, print it
73
- if status is False and not state.print_output:
74
- print_host_combined_output(host, combined_output_lines)
75
-
76
- return status, combined_output_lines
77
-
78
- def run_condition(condition_name: str) -> bool:
79
- condition_value = global_kwargs[condition_name]
80
- if not condition_value:
81
- return True
82
-
83
- show_pre_or_post_condition_warning(condition_name)
47
+ if host.executing_op_hash is None:
48
+ host.executing_op_hash = op_hash
49
+ else:
50
+ host.nested_executing_op_hash = op_hash
84
51
 
85
- _shell_command_status, _ = _run_shell_command(
86
- StringCommand(condition_value),
87
- base_executor_kwargs,
88
- )
52
+ try:
53
+ return _run_host_op(state, host, op_hash)
54
+ finally:
55
+ if host.nested_executing_op_hash:
56
+ host.nested_executing_op_hash = None
57
+ else:
58
+ host.executing_op_hash = None
89
59
 
90
- if _shell_command_status:
91
- return True
92
60
 
93
- _log_msg = f"{condition_name} failed: {condition_value}"
94
- log_error_or_warning(host, ignore_errors, description=_log_msg)
61
+ def _run_host_op(state: "State", host: "Host", op_hash: str) -> Optional[bool]:
62
+ op_data = state.get_op_data_for_host(host, op_hash)
63
+ global_arguments = op_data.global_arguments
95
64
 
96
- if ignore_errors:
97
- return True
65
+ ignore_errors = global_arguments["_ignore_errors"]
66
+ continue_on_error = global_arguments["_continue_on_error"]
67
+ timeout = global_arguments.get("_timeout", 0)
98
68
 
99
- state.trigger_callbacks("operation_host_error", host, op_hash)
100
- return False
69
+ executor_kwarg_keys = CONNECTOR_ARGUMENT_KEYS
70
+ # See: https://github.com/python/mypy/issues/10371
71
+ base_connector_arguments: ConnectorArguments = cast(
72
+ ConnectorArguments,
73
+ {key: global_arguments[key] for key in executor_kwarg_keys if key in global_arguments}, # type: ignore[literal-required] # noqa
74
+ )
101
75
 
102
- if not run_condition("precondition"):
103
- return False
104
-
105
- state.ops_run.add(op_hash)
106
-
107
- if host.executing_op_hash is None:
108
- host.executing_op_hash = op_hash
109
- else:
110
- host.nested_executing_op_hash = op_hash
111
-
112
- return_status = False
113
76
  did_error = False
114
77
  executed_commands = 0
115
- all_combined_output_lines = []
78
+ commands = []
79
+ all_output_lines: list[OutputLine] = []
116
80
 
117
- for i, command in enumerate(op_data["commands"]):
118
- status = False
81
+ for command in op_data.command_generator():
82
+ commands.append(command)
119
83
 
120
- executor_kwargs = base_executor_kwargs.copy()
121
- executor_kwargs.update(command.executor_kwargs)
84
+ status = False
122
85
 
123
- # Now we attempt to execute the command
124
- #
86
+ connector_arguments = base_connector_arguments.copy()
87
+ connector_arguments.update(command.connector_arguments)
125
88
 
126
89
  if not isinstance(command, PyinfraCommand):
127
90
  raise TypeError("{0} is an invalid pyinfra command!".format(command))
128
91
 
129
92
  if isinstance(command, FunctionCommand):
130
93
  try:
131
- status = command.execute(state, host, executor_kwargs)
132
- except NoMoreHostsError:
133
- status = False
134
- except Exception as e: # Custom functions could do anything, so expect anything!
135
- _formatted_exc = format_exception(e)
136
- _error_msg = "Unexpected error in Python callback: {0}".format(_formatted_exc)
137
- _error_msg_styled = click.style(_error_msg, "red")
138
- _error_log = "{0}{1}".format(host.print_prefix, _error_msg_styled)
94
+ status = command.execute(state, host, connector_arguments)
95
+ except Exception as e:
96
+ # Custom functions could do anything, so expect anything!
139
97
  logger.warning(traceback.format_exc())
140
- logger.error(_error_log)
98
+ host.log_styled(
99
+ f"Unexpected error in Python callback: {format_exception(e)}",
100
+ fg="red",
101
+ log_func=logger.warning,
102
+ )
141
103
 
142
104
  elif isinstance(command, StringCommand):
143
- status, combined_output_lines = _run_shell_command(command, executor_kwargs)
144
- all_combined_output_lines.extend(combined_output_lines)
105
+ output_lines = CommandOutput([])
106
+ try:
107
+ status, output_lines = command.execute(
108
+ state,
109
+ host,
110
+ connector_arguments,
111
+ )
112
+ except (timeout_error, socket_error, SSHException) as e:
113
+ log_host_command_error(host, e, timeout=timeout)
114
+ all_output_lines.extend(output_lines)
115
+ # If we failed and have not already printed the stderr, print it
116
+ if status is False and not state.print_output:
117
+ print_host_combined_output(host, output_lines)
145
118
 
146
119
  else:
147
120
  try:
148
- status = command.execute(state, host, executor_kwargs)
121
+ status = command.execute(state, host, connector_arguments)
149
122
  except (timeout_error, socket_error, SSHException, IOError) as e:
150
- _timeout = global_kwargs["timeout"]
151
- log_host_command_error(host, e, timeout=_timeout)
123
+ log_host_command_error(host, e, timeout=timeout)
152
124
 
153
125
  # Break the loop to trigger a failure
154
126
  if status is False:
127
+ did_error = True
155
128
  if continue_on_error is True:
156
- did_error = True
157
129
  continue
158
130
  break
159
131
 
160
132
  executed_commands += 1
161
- state.results[host]["commands"] += 1
162
133
 
163
- # Commands didn't break, so count our successes & return True!
164
- else:
165
- if not run_condition("postcondition"):
166
- return False
134
+ # Handle results
135
+ #
167
136
 
168
- if not did_error:
169
- return_status = True
137
+ op_success = return_status = not did_error
138
+ host_results = state.get_results_for_host(host)
170
139
 
171
- if return_status is True:
172
- state.results[host]["ops"] += 1
173
- state.results[host]["success_ops"] += 1
140
+ if did_error is False:
141
+ host_results.ops += 1
142
+ host_results.success_ops += 1
174
143
 
175
- _status_log = "Success" if len(op_data["commands"]) > 0 else "No changes"
144
+ _status_log = "Success" if executed_commands > 0 else "No changes"
176
145
  _click_log_status = click.style(_status_log, "green")
177
146
  logger.info("{0}{1}".format(host.print_prefix, _click_log_status))
178
147
 
179
- # Trigger any success handler
180
- if global_kwargs["on_success"]:
181
- global_kwargs["on_success"](state, host, op_hash)
182
-
183
148
  state.trigger_callbacks("operation_host_success", host, op_hash)
184
149
  else:
185
150
  if ignore_errors:
186
- state.results[host]["ignored_error_ops"] += 1
151
+ host_results.ignored_error_ops += 1
187
152
  else:
188
- state.results[host]["error_ops"] += 1
153
+ host_results.error_ops += 1
189
154
 
190
155
  if executed_commands:
191
- state.results[host]["partial_ops"] += 1
156
+ host_results.partial_ops += 1
192
157
 
193
- _command_description = f"executed {executed_commands}/{len(op_data['commands'])} commands"
158
+ _command_description = f"executed {executed_commands} commands"
194
159
  log_error_or_warning(host, ignore_errors, _command_description, continue_on_error)
195
160
 
196
- # Always trigger any error handler
197
- if global_kwargs["on_error"]:
198
- global_kwargs["on_error"](state, host, op_hash)
199
-
200
161
  # Ignored, op "completes" w/ ignored error
201
162
  if ignore_errors:
202
- state.results[host]["ops"] += 1
163
+ host_results.ops += 1
164
+ return_status = True
203
165
 
204
166
  # Unignored error -> False
205
167
  state.trigger_callbacks("operation_host_error", host, op_hash)
206
168
 
207
- if ignore_errors:
208
- return_status = True
169
+ op_data.operation_meta.set_complete(
170
+ op_success,
171
+ commands,
172
+ CommandOutput(all_output_lines),
173
+ )
209
174
 
210
- op_data["operation_meta"].set_combined_output_lines(all_combined_output_lines)
175
+ return return_status
211
176
 
212
- if host.nested_executing_op_hash:
213
- host.nested_executing_op_hash = None
214
- else:
215
- host.executing_op_hash = None
216
177
 
217
- return return_status
178
+ # Run all operations strategies
179
+ #
218
180
 
219
181
 
220
182
  def _run_host_op_with_context(state: "State", host: "Host", op_hash: str):
@@ -242,14 +204,11 @@ def _run_host_ops(state: "State", host: "Host", progress=None):
242
204
  if result is False:
243
205
  raise PyinfraError(
244
206
  "Error in operation {0} on {1}".format(
245
- ", ".join(op_meta["names"]),
207
+ ", ".join(op_meta.names),
246
208
  host,
247
209
  ),
248
210
  )
249
211
 
250
- if pyinfra.is_cli:
251
- click.echo(err=True)
252
-
253
212
 
254
213
  def _run_serial_ops(state: "State"):
255
214
  """
@@ -303,7 +262,7 @@ def _run_single_op(state: "State", op_hash: str):
303
262
 
304
263
  failed_hosts = set()
305
264
 
306
- if op_meta["serial"]:
265
+ if op_meta.global_arguments["_serial"]:
307
266
  with progress_spinner(state.inventory.iter_active_hosts()) as progress:
308
267
  # For each host, run the op
309
268
  for host in state.inventory.iter_active_hosts():
@@ -318,10 +277,9 @@ def _run_single_op(state: "State", op_hash: str):
318
277
  batches = [list(state.inventory.iter_active_hosts())]
319
278
 
320
279
  # If parallel set break up the inventory into a series of batches
321
- if op_meta["parallel"]:
322
- parallel = op_meta["parallel"]
280
+ parallel = op_meta.global_arguments["_parallel"]
281
+ if parallel:
323
282
  hosts = list(state.inventory.iter_active_hosts())
324
-
325
283
  batches = [hosts[i : i + parallel] for i in range(0, len(hosts), parallel)]
326
284
 
327
285
  for batch in batches:
@@ -347,9 +305,6 @@ def _run_single_op(state: "State", op_hash: str):
347
305
  # Now all the batches/hosts are complete, fail any failures
348
306
  state.fail_hosts(failed_hosts)
349
307
 
350
- if pyinfra.is_cli:
351
- click.echo(err=True)
352
-
353
308
  state.trigger_callbacks("operation_end", op_hash)
354
309
 
355
310
 
@@ -366,15 +321,14 @@ def run_ops(state: "State", serial: bool = False, no_wait: bool = False):
366
321
  # Flag state as deploy in process
367
322
  state.is_executing = True
368
323
 
369
- # Run all ops, but server by server
370
- if serial:
371
- _run_serial_ops(state)
372
-
373
- # Run all the ops on each server in parallel (not waiting at each operation)
374
- elif no_wait:
375
- _run_no_wait_ops(state)
376
-
377
- # Default: run all ops in order, waiting at each for all servers to complete
378
- else:
379
- for op_hash in state.get_op_order():
380
- _run_single_op(state, op_hash)
324
+ with ctx_state.use(state):
325
+ # Run all ops, but server by server
326
+ if serial:
327
+ _run_serial_ops(state)
328
+ # Run all the ops on each server in parallel (not waiting at each operation)
329
+ elif no_wait:
330
+ _run_no_wait_ops(state)
331
+ # Default: run all ops in order, waiting at each for all servers to complete
332
+ else:
333
+ for op_hash in state.get_op_order():
334
+ _run_single_op(state, op_hash)