pyinfra 2.9.2__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.2.dist-info → pyinfra-3.0.dist-info}/METADATA +40 -41
  113. pyinfra-3.0.dist-info/RECORD +167 -0
  114. {pyinfra-2.9.2.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.2.dist-info/RECORD +0 -170
  151. pyinfra-2.9.2.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.2.dist-info → pyinfra-3.0.dist-info}/LICENSE.md +0 -0
  156. {pyinfra-2.9.2.dist-info → pyinfra-3.0.dist-info}/top_level.txt +0 -0
pyinfra_cli/prints.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  import platform
3
5
  import re
@@ -50,12 +52,6 @@ def jsonify(data, *args, **kwargs):
50
52
  return json.dumps(data, *args, **kwargs)
51
53
 
52
54
 
53
- def print_state_facts(state: "State"):
54
- click.echo(err=True)
55
- click.echo("--> Facts:", err=True)
56
- click.echo(jsonify(state.facts, indent=4, default=json_encode), err=True)
57
-
58
-
59
55
  def print_state_operations(state: "State"):
60
56
  state_ops = {host: ops for host, ops in state.ops.items() if state.is_host_in_limit(host)}
61
57
 
@@ -76,7 +72,7 @@ def print_state_operations(state: "State"):
76
72
  click.echo(
77
73
  " {0} (names={1}, hosts={2})".format(
78
74
  op_hash,
79
- meta["names"],
75
+ meta.names,
80
76
  hosts,
81
77
  ),
82
78
  err=True,
@@ -161,22 +157,22 @@ def print_support_info():
161
157
 
162
158
  def print_rows(rows):
163
159
  # Go through the rows and work out all the widths in each column
164
- column_widths = []
160
+ row_column_widths: list[list[int]] = []
165
161
 
166
162
  for _, columns in rows:
167
163
  if isinstance(columns, str):
168
164
  continue
169
165
 
170
166
  for i, column in enumerate(columns):
171
- if i >= len(column_widths):
172
- column_widths.append([])
167
+ if i >= len(row_column_widths):
168
+ row_column_widths.append([])
173
169
 
174
170
  # Length of the column (with ansi codes removed)
175
171
  width = len(_strip_ansi(column.strip()))
176
- column_widths[i].append(width)
172
+ row_column_widths[i].append(width)
177
173
 
178
174
  # Get the max width of each column and add 4 padding spaces
179
- column_widths = [max(widths) + 4 for widths in column_widths]
175
+ column_widths = [max(widths) + 4 for widths in row_column_widths]
180
176
 
181
177
  # Now print each column, keeping text justified to the widths above
182
178
  for func, columns in rows:
@@ -202,139 +198,111 @@ def print_rows(rows):
202
198
  func(line)
203
199
 
204
200
 
205
- def print_meta(state: "State"):
206
- group_combinations = _get_group_combinations(state.inventory.iter_activated_hosts())
207
- rows: List[Tuple[Callable, Union[List[str], str]]] = []
201
+ def truncate(text, max_length):
202
+ if len(text) <= max_length:
203
+ return text
208
204
 
209
- for i, (groups, hosts) in enumerate(group_combinations.items(), 1):
210
- if not hosts:
211
- continue
205
+ text = text[: max_length - 3]
206
+ return f"{text}..."
212
207
 
213
- if groups:
214
- rows.append(
215
- (
216
- logger.info,
217
- "Groups: {0}".format(
218
- click.style(" / ".join(groups), bold=True),
219
- ),
220
- ),
221
- )
222
- else:
223
- rows.append((logger.info, "Ungrouped:"))
224
208
 
225
- for host in hosts:
226
- meta = state.meta[host]
209
+ def pretty_op_name(op_meta):
210
+ name = list(op_meta.names)[0]
211
+
212
+ if op_meta.args:
213
+ name = "{0} ({1})".format(name, ", ".join(str(arg) for arg in op_meta.args))
227
214
 
228
- # Didn't connect to this host?
229
- if host not in state.activated_hosts:
230
- rows.append(
215
+ return name
216
+
217
+
218
+ def print_meta(state: "State"):
219
+ rows: List[Tuple[Callable, Union[List[str], str]]] = [
220
+ (logger.info, ["Operation", "Change", "Conditional Change"]),
221
+ ]
222
+
223
+ for op_hash in state.get_op_order():
224
+ hosts_in_op = []
225
+ hosts_maybe_in_op = []
226
+ for host in state.inventory.iter_activated_hosts():
227
+ if op_hash in state.ops[host]:
228
+ op_data = state.get_op_data_for_host(host, op_hash)
229
+ if op_data.operation_meta._maybe_is_change:
230
+ if op_data.global_arguments["_if"]:
231
+ hosts_maybe_in_op.append(host.name)
232
+ else:
233
+ hosts_in_op.append(host.name)
234
+
235
+ rows.append(
236
+ (
237
+ logger.info,
238
+ [
239
+ pretty_op_name(state.op_meta[op_hash]),
231
240
  (
232
- logger.info,
233
- [
234
- host.style_print_prefix("red", bold=True),
235
- click.style("No connection", "red"),
236
- ],
241
+ "-"
242
+ if len(hosts_in_op) == 0
243
+ else "{0} ({1})".format(
244
+ len(hosts_in_op),
245
+ truncate(", ".join(sorted(hosts_in_op)), 48),
246
+ )
237
247
  ),
238
- )
239
- continue
240
-
241
- rows.append(
242
- (
243
- logger.info,
244
- [
245
- host.print_prefix,
246
- "Operations: {0}".format(meta["ops"]),
247
- "Change: {0}".format(meta["ops_change"]),
248
- "No change: {0}".format(meta["ops_no_change"]),
249
- ],
250
- ),
248
+ (
249
+ "-"
250
+ if len(hosts_maybe_in_op) == 0
251
+ else "{0} ({1})".format(
252
+ len(hosts_maybe_in_op),
253
+ truncate(", ".join(sorted(hosts_maybe_in_op)), 48),
254
+ )
255
+ ),
256
+ ],
251
257
  )
252
-
253
- if i != len(group_combinations):
254
- rows.append((lambda m: click.echo(m, err=True), []))
258
+ )
255
259
 
256
260
  print_rows(rows)
257
261
 
258
262
 
259
263
  def print_results(state: "State"):
260
- group_combinations = _get_group_combinations(state.inventory.iter_activated_hosts())
261
- rows: List[Tuple[Callable, Union[List[str], str]]] = []
262
-
263
- for i, (groups, hosts) in enumerate(group_combinations.items(), 1):
264
- if not hosts:
265
- continue
266
-
267
- if groups:
268
- rows.append(
269
- (
270
- logger.info,
271
- "Groups: {0}".format(
272
- click.style(" / ".join(groups), bold=True),
273
- ),
274
- ),
275
- )
276
- else:
277
- rows.append((logger.info, "Ungrouped:"))
264
+ rows: List[Tuple[Callable, Union[List[str], str]]] = [
265
+ (logger.info, ["Operation", "Hosts", "Success", "Error", "No Change"]),
266
+ ]
278
267
 
279
- for host in hosts:
280
- # Didn't connect to this host?
281
- if host not in state.activated_hosts:
282
- rows.append(
283
- (
284
- logger.info,
285
- [
286
- host.style_print_prefix("red", bold=True),
287
- click.style("No connection", "red"),
288
- ],
289
- ),
290
- )
268
+ for op_hash in state.get_op_order():
269
+ hosts_in_op = 0
270
+ hosts_in_op_success: list[str] = []
271
+ hosts_in_op_error: list[str] = []
272
+ hosts_in_op_no_change: list[str] = []
273
+ for host in state.inventory.iter_activated_hosts():
274
+ if op_hash not in state.ops[host]:
291
275
  continue
292
276
 
293
- results = state.results[host]
294
-
295
- meta = state.meta[host]
296
- success_ops = results["success_ops"]
297
- partial_ops = results["partial_ops"]
298
- # TODO: type meta object
299
- changed_ops = success_ops - meta["ops_no_change"] # type: ignore
300
- error_ops = results["error_ops"]
301
- ignored_error_ops = results["ignored_error_ops"]
277
+ hosts_in_op += 1
302
278
 
303
- host_args = ("green",)
304
- host_kwargs = {}
279
+ op_meta = state.ops[host][op_hash].operation_meta
280
+ if op_meta.did_succeed(_raise_if_not_complete=False):
281
+ if op_meta._did_change():
282
+ hosts_in_op_success.append(host.name)
283
+ else:
284
+ hosts_in_op_no_change.append(host.name)
285
+ else:
286
+ hosts_in_op_error.append(host.name)
305
287
 
306
- # If all ops got complete
307
- if results["ops"] == meta["ops"]:
308
- # We had some errors - but we ignored them - so "warning" color
309
- if error_ops != 0:
310
- host_args = ("yellow",)
288
+ row = [
289
+ pretty_op_name(state.op_meta[op_hash]),
290
+ str(hosts_in_op),
291
+ ]
311
292
 
312
- # Ops did not complete!
313
- else:
314
- host_args = ("red",)
315
- host_kwargs["bold"] = True
316
-
317
- changed_str = "Changed: {0}".format(click.style(f"{changed_ops}", bold=True))
318
- if partial_ops:
319
- changed_str = f"{changed_str} ({partial_ops} partial)"
320
-
321
- error_str = "Errors: {0}".format(click.style(f"{error_ops}", bold=True))
322
- if ignored_error_ops:
323
- error_str = f"{error_str} ({ignored_error_ops} ignored)"
324
-
325
- rows.append(
326
- (
327
- logger.info,
328
- [
329
- host.style_print_prefix(*host_args, **host_kwargs),
330
- changed_str,
331
- "No change: {0}".format(click.style(f"{meta['ops_no_change']}", bold=True)),
332
- error_str,
333
- ],
334
- ),
335
- )
293
+ if hosts_in_op_success:
294
+ row.append(f"{len(hosts_in_op_success)}")
295
+ else:
296
+ row.append("-")
297
+ if hosts_in_op_error:
298
+ row.append(f"{len(hosts_in_op_error)}")
299
+ else:
300
+ row.append("-")
301
+ if hosts_in_op_no_change:
302
+ row.append(f"{len(hosts_in_op_no_change)}")
303
+ else:
304
+ row.append("-")
336
305
 
337
- if i != len(group_combinations):
338
- rows.append((lambda m: click.echo(m, err=True), []))
306
+ rows.append((logger.info, row))
339
307
 
340
308
  print_rows(rows)
pyinfra_cli/util.py CHANGED
@@ -1,12 +1,15 @@
1
+ from __future__ import annotations
2
+
1
3
  import json
2
4
  import os
3
5
  from datetime import datetime
4
6
  from importlib import import_module
7
+ from importlib.util import find_spec
5
8
  from io import IOBase
6
9
  from os import path
7
10
  from pathlib import Path
8
- from types import FunctionType, ModuleType
9
- from typing import TYPE_CHECKING, Callable
11
+ from types import CodeType, FunctionType, ModuleType
12
+ from typing import Callable
10
13
 
11
14
  import click
12
15
  import gevent
@@ -16,16 +19,20 @@ from pyinfra.api.command import PyinfraCommand
16
19
  from pyinfra.api.exceptions import PyinfraError
17
20
  from pyinfra.api.host import HostData
18
21
  from pyinfra.api.operation import OperationMeta
22
+ from pyinfra.api.state import (
23
+ State,
24
+ StateHostMeta,
25
+ StateHostResults,
26
+ StateOperationHostData,
27
+ StateOperationMeta,
28
+ )
19
29
  from pyinfra.context import ctx_config, ctx_host
20
30
  from pyinfra.progress import progress_spinner
21
31
 
22
32
  from .exceptions import CliError, UnexpectedExternalError
23
33
 
24
- if TYPE_CHECKING:
25
- from pyinfra.api.state import State
26
-
27
34
  # Cache for compiled Python deploy code
28
- PYTHON_CODES = {}
35
+ PYTHON_CODES: dict[str, CodeType] = {}
29
36
 
30
37
 
31
38
  def is_subdir(child, parent):
@@ -45,25 +52,26 @@ def exec_file(filename, return_locals: bool = False, is_deploy_code: bool = Fals
45
52
 
46
53
  if filename not in PYTHON_CODES:
47
54
  with open(filename, "r", encoding="utf-8") as f:
48
- code = f.read()
55
+ code_str = f.read()
49
56
 
50
- code = compile(code, filename, "exec")
57
+ code = compile(code_str, filename, "exec")
51
58
  PYTHON_CODES[filename] = code
52
59
 
53
60
  # Create some base attributes for our "module"
54
- data = {
55
- "__file__": filename,
56
- }
61
+ data = {"__file__": filename}
57
62
 
58
63
  # Execute the code with locals/globals going into the dict above
59
64
  try:
60
65
  exec(PYTHON_CODES[filename], data)
66
+ except PyinfraError:
67
+ # Raise pyinfra errors as-is
68
+ raise
61
69
  except Exception as e:
62
- if isinstance(e, (PyinfraError, UnexpectedExternalError)):
63
- raise
70
+ # Wrap & re-raise errors in user code so we highlight filename/etc
64
71
  raise UnexpectedExternalError(e, filename)
72
+ finally:
73
+ state.current_exec_filename = old_current_exec_filename
65
74
 
66
- state.current_exec_filename = old_current_exec_filename
67
75
  return data
68
76
 
69
77
 
@@ -75,7 +83,16 @@ def json_encode(obj):
75
83
  if isinstance(obj, PyinfraCommand):
76
84
  return repr(obj)
77
85
 
78
- if isinstance(obj, OperationMeta):
86
+ if isinstance(
87
+ obj,
88
+ (
89
+ OperationMeta,
90
+ StateOperationMeta,
91
+ StateOperationHostData,
92
+ StateHostMeta,
93
+ StateHostResults,
94
+ ),
95
+ ):
79
96
  return repr(obj)
80
97
 
81
98
  # Python types
@@ -133,28 +150,44 @@ def parse_cli_arg(arg):
133
150
  return arg
134
151
 
135
152
 
136
- def try_import_module_attribute(path, prefix=None):
137
- mod_path, attr_name = path.rsplit(".", 1)
138
- module = None
153
+ def try_import_module_attribute(path, prefix=None, raise_for_none=True):
154
+ if ":" in path:
155
+ # Allow a.module.name:function syntax
156
+ mod_path, attr_name = path.rsplit(":", 1)
157
+ elif "." in path:
158
+ # And also a.module.name.function
159
+ mod_path, attr_name = path.rsplit(".", 1)
160
+ else:
161
+ return None
139
162
 
163
+ possible_modules = [mod_path]
140
164
  if prefix:
141
- full_path = f"{prefix}.{mod_path}"
165
+ possible_modules.append(f"{prefix}.{mod_path}")
166
+
167
+ module = None
168
+
169
+ for possible in possible_modules:
142
170
  try:
143
- module = import_module(full_path)
144
- except (ModuleNotFoundError, ImportError):
145
- pass
146
- else:
147
- full_path = mod_path
171
+ # First use find_spec which checks if the possible module exists *without* importing
172
+ # it, thus any import errors it contains still get properly raised to the user.
173
+ spec = find_spec(possible)
174
+ except ModuleNotFoundError:
175
+ continue
176
+ else:
177
+ if spec is not None:
178
+ module = import_module(possible)
179
+ break
148
180
 
149
181
  if module is None:
150
- try:
151
- module = import_module(mod_path)
152
- except (ModuleNotFoundError, ImportError):
153
- raise CliError(f"No such module: {full_path}")
182
+ if raise_for_none:
183
+ raise CliError(f"No such module: {possible_modules[-1]}")
184
+ return
154
185
 
155
186
  attr = getattr(module, attr_name, None)
156
187
  if attr is None:
157
- raise CliError(f"No such attribute in module {full_path}: {attr_name}")
188
+ if raise_for_none:
189
+ raise CliError(f"No such attribute in module {possible_modules[-1]}: {attr_name}")
190
+ return
158
191
 
159
192
  return attr
160
193
 
@@ -1,4 +1,5 @@
1
1
  from unittest import TestCase
2
+ from unittest.mock import patch
2
3
 
3
4
  from paramiko import SSHException
4
5
 
@@ -60,6 +61,7 @@ class TestInventoryApi(TestCase):
60
61
 
61
62
 
62
63
  class TestStateApi(PatchSSHTestCase):
64
+ @patch("pyinfra.connectors.base.raise_if_bad_type", lambda *args, **kwargs: None)
63
65
  def test_fail_percent(self):
64
66
  inventory = make_inventory(
65
67
  (
@@ -12,44 +12,44 @@ class TestOperationKwargs(TestCase):
12
12
  state = State(config=config, inventory=inventory)
13
13
 
14
14
  kwargs, keys = pop_global_arguments({}, state=state, host=inventory.get_host("somehost"))
15
- assert kwargs["sudo"] == "config-value"
15
+ assert kwargs["_sudo"] == "config-value"
16
16
 
17
17
  def test_get_from_host(self):
18
18
  config = Config(SUDO="config-value")
19
- inventory = Inventory(([("somehost", {"sudo": "host-value"})], {}))
19
+ inventory = Inventory(([("somehost", {"_sudo": True})], {}))
20
20
 
21
21
  state = State(config=config, inventory=inventory)
22
22
 
23
23
  kwargs, keys = pop_global_arguments({}, state=state, host=inventory.get_host("somehost"))
24
- assert kwargs["sudo"] == "host-value"
24
+ assert kwargs["_sudo"] is True
25
25
 
26
26
  def test_get_from_state_deploy_kwargs(self):
27
27
  config = Config(SUDO="config-value")
28
- inventory = Inventory(([("somehost", {"sudo": "host-value"})], {}))
28
+ inventory = Inventory(([("somehost", {"_sudo": False})], {}))
29
29
  somehost = inventory.get_host("somehost")
30
30
 
31
31
  state = State(config=config, inventory=inventory)
32
- somehost.current_deploy_kwargs = {"sudo": "deploy-kwarg-value"}
32
+ somehost.current_deploy_kwargs = {"_sudo": True}
33
33
 
34
34
  kwargs, keys = pop_global_arguments({}, state=state, host=somehost)
35
- assert kwargs["sudo"] == "deploy-kwarg-value"
35
+ assert kwargs["_sudo"] is True
36
36
 
37
37
  def test_get_from_kwargs(self):
38
38
  config = Config(SUDO="config-value")
39
- inventory = Inventory(([("somehost", {"sudo": "host-value"})], {}))
39
+ inventory = Inventory(([("somehost", {"_sudo": False})], {}))
40
40
  somehost = inventory.get_host("somehost")
41
41
 
42
42
  state = State(config=config, inventory=inventory)
43
43
  somehost.current_deploy_kwargs = {
44
- "sudo": "deploy-kwarg-value",
45
- "sudo_user": "deploy-kwarg-user",
44
+ "_sudo": False,
45
+ "_sudo_user": "deploy-kwarg-user",
46
46
  }
47
47
 
48
48
  kwargs, keys = pop_global_arguments(
49
- {"sudo": "kwarg-value"},
49
+ {"_sudo": True},
50
50
  state=state,
51
51
  host=somehost,
52
52
  )
53
- assert kwargs["sudo"] == "kwarg-value"
54
- assert kwargs["sudo_user"] == "deploy-kwarg-user"
55
- assert "sudo" in keys
53
+ assert kwargs["_sudo"] is True
54
+ assert kwargs["_sudo_user"] == "deploy-kwarg-user"
55
+ assert "_sudo" in keys
@@ -24,7 +24,7 @@ class TestDeploysApi(PatchSSHTestCase):
24
24
 
25
25
  connect_all(state)
26
26
 
27
- @deploy
27
+ @deploy()
28
28
  def test_deploy(state=None, host=None):
29
29
  server.shell(commands=["echo first command"])
30
30
  server.shell(commands=["echo second command"])
@@ -36,40 +36,36 @@ class TestDeploysApi(PatchSSHTestCase):
36
36
  # Ensure we have an op
37
37
  assert len(op_order) == 2
38
38
 
39
+ # Ensure run ops works
40
+ run_ops(state)
41
+
39
42
  first_op_hash = op_order[0]
40
- assert state.op_meta[first_op_hash]["names"] == {"test_deploy | Server/Shell"}
41
- assert state.ops[somehost][first_op_hash]["commands"] == [
43
+ assert state.op_meta[first_op_hash].names == {"test_deploy | server.shell"}
44
+ assert state.ops[somehost][first_op_hash].operation_meta._commands == [
42
45
  StringCommand("echo first command"),
43
46
  ]
44
- assert state.ops[anotherhost][first_op_hash]["commands"] == [
47
+ assert state.ops[anotherhost][first_op_hash].operation_meta._commands == [
45
48
  StringCommand("echo first command"),
46
49
  ]
47
50
 
48
51
  second_op_hash = op_order[1]
49
- assert state.op_meta[second_op_hash]["names"] == {"test_deploy | Server/Shell"}
50
- assert state.ops[somehost][second_op_hash]["commands"] == [
52
+ assert state.op_meta[second_op_hash].names == {"test_deploy | server.shell"}
53
+ assert state.ops[somehost][second_op_hash].operation_meta._commands == [
51
54
  StringCommand("echo second command"),
52
55
  ]
53
- assert state.ops[anotherhost][second_op_hash]["commands"] == [
56
+ assert state.ops[anotherhost][second_op_hash].operation_meta._commands == [
54
57
  StringCommand("echo second command"),
55
58
  ]
56
59
 
57
- # Ensure run ops works
58
- run_ops(state)
59
-
60
60
  # Ensure ops completed OK
61
- assert state.results[somehost]["success_ops"] == 2
62
- assert state.results[somehost]["ops"] == 2
63
- assert state.results[anotherhost]["success_ops"] == 2
64
- assert state.results[anotherhost]["ops"] == 2
61
+ assert state.results[somehost].success_ops == 2
62
+ assert state.results[somehost].ops == 2
63
+ assert state.results[anotherhost].success_ops == 2
64
+ assert state.results[anotherhost].ops == 2
65
65
 
66
66
  # And w/o errors
67
- assert state.results[somehost]["error_ops"] == 0
68
- assert state.results[anotherhost]["error_ops"] == 0
69
-
70
- # And with the different modes
71
- run_ops(state, serial=True)
72
- run_ops(state, no_wait=True)
67
+ assert state.results[somehost].error_ops == 0
68
+ assert state.results[anotherhost].error_ops == 0
73
69
 
74
70
  disconnect_all(state)
75
71
 
@@ -87,11 +83,11 @@ class TestDeploysApi(PatchSSHTestCase):
87
83
 
88
84
  connect_all(state)
89
85
 
90
- @deploy
86
+ @deploy()
91
87
  def test_nested_deploy():
92
88
  server.shell(commands=["echo nested command"])
93
89
 
94
- @deploy
90
+ @deploy()
95
91
  def test_deploy():
96
92
  server.shell(commands=["echo first command"])
97
93
  test_nested_deploy()
@@ -104,22 +100,25 @@ class TestDeploysApi(PatchSSHTestCase):
104
100
  # Ensure we have an op
105
101
  assert len(op_order) == 3
106
102
 
103
+ # Ensure run ops works
104
+ run_ops(state)
105
+
107
106
  first_op_hash = op_order[0]
108
- assert state.op_meta[first_op_hash]["names"] == {"test_deploy | Server/Shell"}
109
- assert state.ops[somehost][first_op_hash]["commands"] == [
107
+ assert state.op_meta[first_op_hash].names == {"test_deploy | server.shell"}
108
+ assert state.ops[somehost][first_op_hash].operation_meta._commands == [
110
109
  StringCommand("echo first command"),
111
110
  ]
112
111
 
113
112
  second_op_hash = op_order[1]
114
- assert state.op_meta[second_op_hash]["names"] == {
115
- "test_deploy | test_nested_deploy | Server/Shell",
113
+ assert state.op_meta[second_op_hash].names == {
114
+ "test_deploy | test_nested_deploy | server.shell",
116
115
  }
117
- assert state.ops[somehost][second_op_hash]["commands"] == [
116
+ assert state.ops[somehost][second_op_hash].operation_meta._commands == [
118
117
  StringCommand("echo nested command"),
119
118
  ]
120
119
 
121
120
  third_op_hash = op_order[2]
122
- assert state.op_meta[third_op_hash]["names"] == {"test_deploy | Server/Shell"}
123
- assert state.ops[somehost][third_op_hash]["commands"] == [
121
+ assert state.op_meta[third_op_hash].names == {"test_deploy | server.shell"}
122
+ assert state.ops[somehost][third_op_hash].operation_meta._commands == [
124
123
  StringCommand("echo second command"),
125
124
  ]