pyinfra 2.9.2__py2.py3-none-any.whl → 3.0b1__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 (126) hide show
  1. pyinfra/api/__init__.py +3 -0
  2. pyinfra/api/arguments.py +261 -255
  3. pyinfra/api/arguments_typed.py +77 -0
  4. pyinfra/api/command.py +66 -53
  5. pyinfra/api/config.py +27 -22
  6. pyinfra/api/connect.py +1 -1
  7. pyinfra/api/connectors.py +2 -24
  8. pyinfra/api/deploy.py +21 -52
  9. pyinfra/api/exceptions.py +33 -8
  10. pyinfra/api/facts.py +77 -113
  11. pyinfra/api/host.py +150 -82
  12. pyinfra/api/inventory.py +17 -25
  13. pyinfra/api/operation.py +232 -198
  14. pyinfra/api/operations.py +102 -148
  15. pyinfra/api/state.py +137 -79
  16. pyinfra/api/util.py +55 -70
  17. pyinfra/connectors/base.py +150 -0
  18. pyinfra/connectors/chroot.py +160 -169
  19. pyinfra/connectors/docker.py +227 -237
  20. pyinfra/connectors/dockerssh.py +231 -253
  21. pyinfra/connectors/local.py +195 -207
  22. pyinfra/connectors/ssh.py +528 -615
  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 +212 -137
  27. pyinfra/connectors/vagrant.py +55 -48
  28. pyinfra/context.py +3 -2
  29. pyinfra/facts/docker.py +1 -0
  30. pyinfra/facts/files.py +45 -32
  31. pyinfra/facts/git.py +3 -1
  32. pyinfra/facts/gpg.py +1 -1
  33. pyinfra/facts/hardware.py +4 -2
  34. pyinfra/facts/iptables.py +5 -3
  35. pyinfra/facts/mysql.py +1 -0
  36. pyinfra/facts/postgres.py +168 -0
  37. pyinfra/facts/postgresql.py +5 -161
  38. pyinfra/facts/selinux.py +3 -1
  39. pyinfra/facts/server.py +77 -30
  40. pyinfra/facts/systemd.py +29 -12
  41. pyinfra/facts/sysvinit.py +10 -10
  42. pyinfra/facts/util/packaging.py +4 -2
  43. pyinfra/local.py +4 -5
  44. pyinfra/operations/apk.py +3 -3
  45. pyinfra/operations/apt.py +25 -47
  46. pyinfra/operations/brew.py +7 -14
  47. pyinfra/operations/bsdinit.py +4 -4
  48. pyinfra/operations/cargo.py +1 -1
  49. pyinfra/operations/choco.py +1 -1
  50. pyinfra/operations/dnf.py +4 -4
  51. pyinfra/operations/files.py +108 -321
  52. pyinfra/operations/gem.py +1 -1
  53. pyinfra/operations/git.py +6 -37
  54. pyinfra/operations/iptables.py +2 -10
  55. pyinfra/operations/launchd.py +1 -1
  56. pyinfra/operations/lxd.py +1 -9
  57. pyinfra/operations/mysql.py +5 -28
  58. pyinfra/operations/npm.py +1 -1
  59. pyinfra/operations/openrc.py +1 -1
  60. pyinfra/operations/pacman.py +3 -3
  61. pyinfra/operations/pip.py +14 -15
  62. pyinfra/operations/pkg.py +1 -1
  63. pyinfra/operations/pkgin.py +3 -3
  64. pyinfra/operations/postgres.py +347 -0
  65. pyinfra/operations/postgresql.py +17 -380
  66. pyinfra/operations/python.py +2 -17
  67. pyinfra/operations/selinux.py +5 -28
  68. pyinfra/operations/server.py +59 -84
  69. pyinfra/operations/snap.py +1 -3
  70. pyinfra/operations/ssh.py +8 -23
  71. pyinfra/operations/systemd.py +7 -7
  72. pyinfra/operations/sysvinit.py +3 -12
  73. pyinfra/operations/upstart.py +4 -4
  74. pyinfra/operations/util/__init__.py +12 -0
  75. pyinfra/operations/util/files.py +2 -2
  76. pyinfra/operations/util/packaging.py +6 -24
  77. pyinfra/operations/util/service.py +18 -37
  78. pyinfra/operations/vzctl.py +2 -2
  79. pyinfra/operations/xbps.py +3 -3
  80. pyinfra/operations/yum.py +4 -4
  81. pyinfra/operations/zypper.py +4 -4
  82. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/METADATA +19 -22
  83. pyinfra-3.0b1.dist-info/RECORD +163 -0
  84. pyinfra-3.0b1.dist-info/entry_points.txt +11 -0
  85. pyinfra_cli/__main__.py +2 -0
  86. pyinfra_cli/commands.py +7 -2
  87. pyinfra_cli/exceptions.py +83 -42
  88. pyinfra_cli/inventory.py +19 -4
  89. pyinfra_cli/log.py +17 -3
  90. pyinfra_cli/main.py +133 -90
  91. pyinfra_cli/prints.py +93 -129
  92. pyinfra_cli/util.py +60 -29
  93. tests/test_api/test_api.py +2 -0
  94. tests/test_api/test_api_arguments.py +13 -13
  95. tests/test_api/test_api_deploys.py +28 -29
  96. tests/test_api/test_api_facts.py +60 -98
  97. tests/test_api/test_api_operations.py +100 -200
  98. tests/test_cli/test_cli.py +18 -49
  99. tests/test_cli/test_cli_deploy.py +11 -37
  100. tests/test_cli/test_cli_exceptions.py +50 -19
  101. tests/test_cli/util.py +1 -1
  102. tests/test_connectors/test_chroot.py +6 -6
  103. tests/test_connectors/test_docker.py +4 -4
  104. tests/test_connectors/test_dockerssh.py +38 -50
  105. tests/test_connectors/test_local.py +11 -12
  106. tests/test_connectors/test_ssh.py +66 -107
  107. tests/test_connectors/test_terraform.py +9 -15
  108. tests/test_connectors/test_util.py +24 -46
  109. tests/test_connectors/test_vagrant.py +4 -4
  110. pyinfra/api/operation.pyi +0 -117
  111. pyinfra/connectors/ansible.py +0 -171
  112. pyinfra/connectors/mech.py +0 -186
  113. pyinfra/connectors/pyinfrawinrmsession/__init__.py +0 -28
  114. pyinfra/connectors/winrm.py +0 -320
  115. pyinfra/facts/windows.py +0 -366
  116. pyinfra/facts/windows_files.py +0 -90
  117. pyinfra/operations/windows.py +0 -59
  118. pyinfra/operations/windows_files.py +0 -551
  119. pyinfra-2.9.2.dist-info/RECORD +0 -170
  120. pyinfra-2.9.2.dist-info/entry_points.txt +0 -14
  121. tests/test_connectors/test_ansible.py +0 -64
  122. tests/test_connectors/test_mech.py +0 -126
  123. tests/test_connectors/test_winrm.py +0 -76
  124. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/LICENSE.md +0 -0
  125. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.dist-info}/WHEEL +0 -0
  126. {pyinfra-2.9.2.dist-info → pyinfra-3.0b1.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,107 @@ 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
212
-
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
-
225
- for host in hosts:
226
- meta = state.meta[host]
227
-
228
- # Didn't connect to this host?
229
- if host not in state.activated_hosts:
230
- rows.append(
231
- (
232
- logger.info,
233
- [
234
- host.style_print_prefix("red", bold=True),
235
- click.style("No connection", "red"),
236
- ],
237
- ),
238
- )
239
- continue
205
+ text = text[: max_length - 3]
206
+ return f"{text}..."
240
207
 
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
- ),
251
- )
252
208
 
253
- if i != len(group_combinations):
254
- rows.append((lambda m: click.echo(m, err=True), []))
209
+ def pretty_op_name(op_meta):
210
+ name = list(op_meta.names)[0]
255
211
 
256
- print_rows(rows)
212
+ if op_meta.args:
213
+ name = "{0} ({1})".format(name, ", ".join(str(arg) for arg in op_meta.args))
257
214
 
215
+ return name
258
216
 
259
- 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
217
 
263
- for i, (groups, hosts) in enumerate(group_combinations.items(), 1):
264
- if not hosts:
265
- continue
218
+ def print_meta(state: "State"):
219
+ rows: List[Tuple[Callable, Union[List[str], str]]] = [
220
+ (logger.info, ["Operation", "Change", "Conditional Change"]),
221
+ ]
266
222
 
267
- if groups:
268
- rows.append(
269
- (
270
- logger.info,
271
- "Groups: {0}".format(
272
- click.style(" / ".join(groups), bold=True),
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]),
240
+ "-"
241
+ if len(hosts_in_op) == 0
242
+ else "{0} ({1})".format(
243
+ len(hosts_in_op),
244
+ truncate(", ".join(sorted(hosts_in_op)), 48),
273
245
  ),
274
- ),
275
- )
276
- else:
277
- rows.append((logger.info, "Ungrouped:"))
278
-
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
- ],
246
+ "-"
247
+ if len(hosts_maybe_in_op) == 0
248
+ else "{0} ({1})".format(
249
+ len(hosts_maybe_in_op),
250
+ truncate(", ".join(sorted(hosts_maybe_in_op)), 48),
289
251
  ),
290
- )
291
- continue
252
+ ],
253
+ )
254
+ )
255
+
256
+ print_rows(rows)
292
257
 
293
- results = state.results[host]
294
258
 
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"]
259
+ def print_results(state: "State"):
260
+ rows: List[Tuple[Callable, Union[List[str], str]]] = [
261
+ (logger.info, ["Operation", "Hosts", "Success", "Error", "No Change"]),
262
+ ]
302
263
 
303
- host_args = ("green",)
304
- host_kwargs = {}
264
+ for op_hash in state.get_op_order():
265
+ hosts_in_op = 0
266
+ hosts_in_op_success: list[str] = []
267
+ hosts_in_op_error: list[str] = []
268
+ hosts_in_op_no_change: list[str] = []
269
+ for host in state.inventory.iter_activated_hosts():
270
+ if op_hash not in state.ops[host]:
271
+ continue
305
272
 
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",)
273
+ hosts_in_op += 1
311
274
 
312
- # Ops did not complete!
275
+ op_meta = state.ops[host][op_hash].operation_meta
276
+ if op_meta.did_succeed():
277
+ if op_meta._did_change():
278
+ hosts_in_op_success.append(host.name)
279
+ else:
280
+ hosts_in_op_no_change.append(host.name)
313
281
  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
- )
282
+ hosts_in_op_error.append(host.name)
283
+
284
+ row = [
285
+ pretty_op_name(state.op_meta[op_hash]),
286
+ str(hosts_in_op),
287
+ ]
288
+
289
+ if hosts_in_op_success:
290
+ row.append(f"{len(hosts_in_op_success)}")
291
+ else:
292
+ row.append("-")
293
+ if hosts_in_op_error:
294
+ row.append(f"{len(hosts_in_op_error)}")
295
+ else:
296
+ row.append("-")
297
+ if hosts_in_op_no_change:
298
+ row.append(f"{len(hosts_in_op_no_change)}")
299
+ else:
300
+ row.append("-")
336
301
 
337
- if i != len(group_combinations):
338
- rows.append((lambda m: click.echo(m, err=True), []))
302
+ rows.append((logger.info, row))
339
303
 
340
304
  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,42 @@ 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
+ else:
158
+ # And also a.module.name.function
159
+ mod_path, attr_name = path.rsplit(".", 1)
139
160
 
161
+ possible_modules = [mod_path]
140
162
  if prefix:
141
- full_path = f"{prefix}.{mod_path}"
163
+ possible_modules.append(f"{prefix}.{mod_path}")
164
+
165
+ module = None
166
+
167
+ for possible in possible_modules:
142
168
  try:
143
- module = import_module(full_path)
144
- except (ModuleNotFoundError, ImportError):
145
- pass
146
- else:
147
- full_path = mod_path
169
+ # First use find_spec which checks if the possible module exists *without* importing
170
+ # it, thus any import errors it contains still get properly raised to the user.
171
+ spec = find_spec(possible)
172
+ except ModuleNotFoundError:
173
+ continue
174
+ else:
175
+ if spec is not None:
176
+ module = import_module(possible)
177
+ break
148
178
 
149
179
  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}")
180
+ if raise_for_none:
181
+ raise CliError(f"No such module: {possible_modules[-1]}")
182
+ return
154
183
 
155
184
  attr = getattr(module, attr_name, None)
156
185
  if attr is None:
157
- raise CliError(f"No such attribute in module {full_path}: {attr_name}")
186
+ if raise_for_none:
187
+ raise CliError(f"No such attribute in module {possible_modules[-1]}: {attr_name}")
188
+ return
158
189
 
159
190
  return attr
160
191
 
@@ -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
  ]