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_cli/log.py CHANGED
@@ -2,7 +2,8 @@ import logging
2
2
 
3
3
  import click
4
4
 
5
- from pyinfra import logger
5
+ from pyinfra import logger, state
6
+ from pyinfra.context import ctx_state
6
7
 
7
8
 
8
9
  class LogHandler(logging.Handler):
@@ -15,6 +16,8 @@ class LogHandler(logging.Handler):
15
16
 
16
17
 
17
18
  class LogFormatter(logging.Formatter):
19
+ previous_was_header = True
20
+
18
21
  level_to_format = {
19
22
  logging.DEBUG: lambda s: click.style(s, "green"),
20
23
  logging.WARNING: lambda s: click.style(s, "yellow"),
@@ -39,21 +42,32 @@ class LogFormatter(logging.Formatter):
39
42
 
40
43
  # We only handle strings here
41
44
  if isinstance(message, str):
42
- if "-->" not in message:
45
+ if ctx_state.isset() and record.levelno is logging.WARNING:
46
+ state.increment_warning_counter()
47
+
48
+ if "-->" in message:
49
+ if not self.previous_was_header:
50
+ click.echo(err=True)
51
+ else:
43
52
  message = " {0}".format(message)
44
53
 
45
54
  if record.levelno in self.level_to_format:
46
55
  message = self.level_to_format[record.levelno](message)
47
56
 
57
+ self.previous_was_header = "-->" in message
48
58
  return message
49
59
 
50
60
  # If not a string, pass to standard Formatter
51
61
  return super().format(record)
52
62
 
53
63
 
54
- def setup_logging(log_level):
64
+ def setup_logging(log_level, other_log_level=None):
65
+ if other_log_level:
66
+ logging.basicConfig(level=other_log_level)
67
+
55
68
  logger.setLevel(log_level)
56
69
  handler = LogHandler()
57
70
  formatter = LogFormatter()
58
71
  handler.setFormatter(formatter)
59
72
  logger.addHandler(handler)
73
+ logger.propagate = False
pyinfra_cli/main.py CHANGED
@@ -13,12 +13,13 @@ from pyinfra.api.connect import connect_all, disconnect_all
13
13
  from pyinfra.api.exceptions import NoGroupError, PyinfraError
14
14
  from pyinfra.api.facts import get_facts
15
15
  from pyinfra.api.operations import run_ops
16
+ from pyinfra.api.state import StateStage
16
17
  from pyinfra.api.util import get_kwargs_str
17
18
  from pyinfra.context import ctx_config, ctx_inventory, ctx_state
18
19
  from pyinfra.operations import server
19
20
 
20
21
  from .commands import get_facts_and_args, get_func_and_args
21
- from .exceptions import CliError, UnexpectedExternalError, UnexpectedInternalError
22
+ from .exceptions import CliError, UnexpectedExternalError, UnexpectedInternalError, WrappedError
22
23
  from .inventory import make_inventory
23
24
  from .log import setup_logging
24
25
  from .prints import (
@@ -26,7 +27,6 @@ from .prints import (
26
27
  print_inventory,
27
28
  print_meta,
28
29
  print_results,
29
- print_state_facts,
30
30
  print_state_operations,
31
31
  print_support_info,
32
32
  )
@@ -44,7 +44,7 @@ def _print_support(ctx, param, value):
44
44
  if not value:
45
45
  return
46
46
 
47
- click.echo("--> Support information:", err=True)
47
+ logger.info("--> Support information:")
48
48
  print_support_info()
49
49
  ctx.exit()
50
50
 
@@ -64,6 +64,15 @@ def _print_support(ctx, param, value):
64
64
  default=False,
65
65
  help="Don't execute operations on the target hosts.",
66
66
  )
67
+ @click.option(
68
+ "-y",
69
+ "--yes",
70
+ is_flag=True,
71
+ default=False,
72
+ help="Execute operations immediately on hosts without prompt or checking for changes.",
73
+ envvar="PYINFRA_YES",
74
+ show_envvar=True,
75
+ )
67
76
  @click.option(
68
77
  "--limit",
69
78
  help="Restrict the target hosts by name and group name.",
@@ -132,11 +141,6 @@ def _print_support(ctx, param, value):
132
141
  help="SSH Private key password.",
133
142
  )
134
143
  @click.option("--ssh-password", "--password", "ssh_password", help="SSH password.")
135
- # WinRM connector args
136
- @click.option("--winrm-username", help="WINRM user to connect as.")
137
- @click.option("--winrm-password", help="WINRM password.")
138
- @click.option("--winrm-port", help="WINRM port to connect to.")
139
- @click.option("--winrm-transport", help="WINRM transport for use.")
140
144
  # Eager commands (pyinfra --support)
141
145
  @click.option(
142
146
  "--support",
@@ -156,7 +160,13 @@ def _print_support(ctx, param, value):
156
160
  "--debug",
157
161
  is_flag=True,
158
162
  default=False,
159
- help="Print debug info.",
163
+ help="Print debug logs from pyinfra.",
164
+ )
165
+ @click.option(
166
+ "--debug-all",
167
+ is_flag=True,
168
+ default=False,
169
+ help="Print debug logs from all packages including pyinfra.",
160
170
  )
161
171
  @click.option(
162
172
  "--debug-facts",
@@ -216,26 +226,17 @@ def cli(*args, **kwargs):
216
226
 
217
227
  try:
218
228
  _main(*args, **kwargs)
219
-
220
- except PyinfraError as e:
221
- # Re-raise any internal exceptions that aren't handled by click as
222
- # CliErrors which are.
223
- if not isinstance(e, click.ClickException):
224
- message = getattr(e, "message", e.args[0])
225
- raise CliError(message)
226
-
227
- raise
228
-
229
- except UnexpectedExternalError:
230
- # Pass unexpected external exceptions through as-is
229
+ except (CliError, UnexpectedExternalError):
231
230
  raise
232
-
231
+ except PyinfraError as e:
232
+ # Re-raise "expected" pyinfra exceptions with our click exception wrapper
233
+ raise WrappedError(e)
233
234
  except Exception as e:
234
235
  # Re-raise any unexpected internal exceptions as UnexpectedInternalError
235
236
  raise UnexpectedInternalError(e)
236
-
237
237
  finally:
238
238
  if ctx_state.isset() and state.initialised:
239
+ logger.info("--> Disconnecting from hosts...")
239
240
  # Triggers any executor disconnect requirements
240
241
  disconnect_all(state)
241
242
 
@@ -258,10 +259,6 @@ def _main(
258
259
  ssh_key,
259
260
  ssh_key_password: str,
260
261
  ssh_password: str,
261
- winrm_username: str,
262
- winrm_password: str,
263
- winrm_port,
264
- winrm_transport,
265
262
  shell_executable,
266
263
  sudo: bool,
267
264
  sudo_user: str,
@@ -273,14 +270,16 @@ def _main(
273
270
  group_data,
274
271
  config_filename: str,
275
272
  dry: bool,
273
+ yes: bool,
276
274
  limit: Iterable,
277
275
  no_wait: bool,
278
276
  serial: bool,
279
277
  quiet: bool,
280
278
  debug: bool,
279
+ debug_all: bool,
281
280
  debug_facts: bool,
282
281
  debug_operations: bool,
283
- support: bool = None,
282
+ support: bool = False,
284
283
  ):
285
284
  # Setup working directory
286
285
  #
@@ -289,7 +288,7 @@ def _main(
289
288
 
290
289
  # Setup logging & Bootstrap/Venv
291
290
  #
292
- _setup_log_level(debug, quiet)
291
+ _setup_log_level(debug, debug_all)
293
292
  init_virtualenv()
294
293
 
295
294
  # Check operations are valid and setup commands
@@ -298,7 +297,7 @@ def _main(
298
297
 
299
298
  # Setup state, config & inventory
300
299
  #
301
- state = _setup_state(verbosity, quiet)
300
+ state = _setup_state(verbosity, quiet, yes)
302
301
  config = Config()
303
302
  ctx_config.set(config)
304
303
 
@@ -314,7 +313,7 @@ def _main(
314
313
  parallel,
315
314
  shell_executable,
316
315
  fail_percent,
317
- quiet,
316
+ yes,
318
317
  )
319
318
  override_data = _set_override_data(
320
319
  data,
@@ -323,15 +322,14 @@ def _main(
323
322
  ssh_key_password,
324
323
  ssh_port,
325
324
  ssh_password,
326
- winrm_username,
327
- winrm_password,
328
- winrm_port,
329
- winrm_transport,
330
325
  )
331
326
 
327
+ if yes is False:
328
+ _set_fail_prompts(state, config)
329
+
332
330
  # Load up the inventory from the filesystem
333
331
  #
334
- echo_msg("--> Loading inventory...", quiet)
332
+ logger.info("--> Loading inventory...")
335
333
  inventory = make_inventory(
336
334
  inventory,
337
335
  cwd=state.cwd,
@@ -352,20 +350,27 @@ def _main(
352
350
 
353
351
  # Connect to the hosts & start handling the user commands
354
352
  #
355
- echo_msg("--> Connecting to hosts...", quiet)
353
+ logger.info("--> Connecting to hosts...")
354
+ state.set_stage(StateStage.Connect)
356
355
  connect_all(state)
357
- state, config = _handle_commands(state, config, command, original_operations, operations, quiet)
356
+
357
+ logger.info("--> Preparing operations...")
358
+ state.set_stage(StateStage.Prepare)
359
+ can_diff, state, config = _handle_commands(
360
+ state, config, command, original_operations, operations
361
+ )
358
362
 
359
363
  # Print proposed changes, execute unless --dry, and exit
360
364
  #
361
- echo_msg("--> Proposed changes:", quiet)
362
- print_meta(state)
365
+ if can_diff:
366
+ if yes:
367
+ logger.info("--> Skipping change detection")
368
+ else:
369
+ logger.info("--> Detected changes:")
370
+ print_meta(state)
363
371
 
364
372
  # If --debug-facts or --debug-operations, print and exit
365
373
  if debug_facts or debug_operations:
366
- if debug_facts:
367
- print_state_facts(state)
368
-
369
374
  if debug_operations:
370
375
  print_state_operations(state)
371
376
 
@@ -374,33 +379,67 @@ def _main(
374
379
  if dry:
375
380
  _exit()
376
381
 
377
- echo_msg(quiet=quiet)
378
- echo_msg("--> Beginning operation run...", quiet)
382
+ if (
383
+ can_diff
384
+ and not yes
385
+ and not _do_confirm("Detected changes displayed above, skip this step with -y")
386
+ ):
387
+ _exit()
379
388
 
389
+ logger.info("--> Beginning operation run...")
390
+ state.set_stage(StateStage.Execute)
380
391
  run_ops(state, serial=serial, no_wait=no_wait)
381
392
 
382
- echo_msg("--> Results:", quiet)
393
+ logger.info("--> Results:")
394
+ state.set_stage(StateStage.Disconnect)
383
395
  print_results(state)
384
396
  _exit()
385
397
 
386
398
 
399
+ def _do_confirm(msg: str) -> bool:
400
+ click.echo(err=True)
401
+ click.echo(f" {msg}", err=True)
402
+ warning_count = state.get_warning_counter()
403
+ if warning_count > 0:
404
+ click.secho(
405
+ f" {warning_count} warnings shown during change detection, see above",
406
+ fg="yellow",
407
+ err=True,
408
+ )
409
+ confirm_msg = " Press enter to execute..."
410
+ click.echo(confirm_msg, err=True, nl=False)
411
+ v = input()
412
+ if v:
413
+ click.echo(f" Unexpected user input: {v}", err=True)
414
+ return False
415
+ # Go up, clear the line, go up again - as if the confirmation statement was never here!
416
+ click.echo(
417
+ "\033[1A{0}\033[1A".format("".join(" " for _ in range(len(confirm_msg)))),
418
+ err=True,
419
+ nl=False,
420
+ )
421
+ click.echo(err=True)
422
+ return True
423
+
424
+
387
425
  # Setup
388
426
  #
389
- def _setup_log_level(debug, quiet):
427
+ def _setup_log_level(debug, debug_all):
390
428
  if not debug and not sys.warnoptions:
391
429
  warnings.simplefilter("ignore")
392
430
 
393
431
  log_level = logging.INFO
394
- if debug:
432
+ if debug or debug_all:
395
433
  log_level = logging.DEBUG
396
- elif quiet:
397
- log_level = logging.WARNING
398
434
 
399
- setup_logging(log_level)
435
+ other_log_level = None
436
+ if debug_all:
437
+ other_log_level = logging.DEBUG
400
438
 
439
+ setup_logging(log_level, other_log_level)
401
440
 
402
- def _validate_operations(operations, chdir):
403
441
 
442
+ def _validate_operations(operations, chdir):
404
443
  # Make a copy before we overwrite
405
444
  original_operations = operations
406
445
 
@@ -467,7 +506,7 @@ def _validate_operations(operations, chdir):
467
506
  return original_operations, operations, command, chdir
468
507
 
469
508
 
470
- def _set_verbosity(state, verbosity, quiet):
509
+ def _set_verbosity(state, verbosity):
471
510
  if verbosity > 0:
472
511
  state.print_fact_info = True
473
512
  state.print_noop_info = True
@@ -481,16 +520,16 @@ def _set_verbosity(state, verbosity, quiet):
481
520
  return state
482
521
 
483
522
 
484
- def _setup_state(verbosity, quiet):
523
+ def _setup_state(verbosity, quiet, yes):
485
524
  cwd = getcwd()
486
525
  if cwd not in sys.path: # ensure cwd is present in sys.path
487
526
  sys.path.append(cwd)
488
527
 
489
- state = State()
528
+ state = State(check_for_changes=not yes)
490
529
  state.cwd = cwd
491
530
  ctx_state.set(state)
492
531
 
493
- state = _set_verbosity(state, verbosity, quiet)
532
+ state = _set_verbosity(state, verbosity)
494
533
  return state
495
534
 
496
535
 
@@ -504,12 +543,13 @@ def _set_config(
504
543
  parallel,
505
544
  shell_executable,
506
545
  fail_percent,
507
- quiet,
546
+ yes,
508
547
  ):
509
- echo_msg("--> Loading config...", quiet)
548
+ logger.info("--> Loading config...")
510
549
 
511
550
  # Load up any config.py from the filesystem
512
- config_filename = path.join(state.cwd, config_filename)
551
+ if state.cwd:
552
+ config_filename = path.join(state.cwd, config_filename)
513
553
  if path.exists(config_filename):
514
554
  exec_file(config_filename)
515
555
 
@@ -548,10 +588,6 @@ def _set_override_data(
548
588
  ssh_key_password,
549
589
  ssh_port,
550
590
  ssh_password,
551
- winrm_username,
552
- winrm_password,
553
- winrm_port,
554
- winrm_transport,
555
591
  ):
556
592
  override_data = {}
557
593
 
@@ -567,10 +603,6 @@ def _set_override_data(
567
603
  ("ssh_key_password", ssh_key_password),
568
604
  ("ssh_port", ssh_port),
569
605
  ("ssh_password", ssh_password),
570
- ("winrm_username", winrm_username),
571
- ("winrm_password", winrm_password),
572
- ("winrm_port", winrm_port),
573
- ("winrm_transport", winrm_transport),
574
606
  ):
575
607
  if value:
576
608
  override_data[key] = value
@@ -578,6 +610,19 @@ def _set_override_data(
578
610
  return override_data
579
611
 
580
612
 
613
+ def _set_fail_prompts(state: State, config: Config) -> None:
614
+ # Set fail percent to zero, meaning we'll raise an exception for any fail,
615
+ # and we can capture + prompt the user to continue/exit.
616
+ config.FAIL_PERCENT = 0
617
+
618
+ def should_raise_failed_hosts(state: State) -> bool:
619
+ if state.current_stage == StateStage.Connect:
620
+ return not _do_confirm("One of more hosts failed to connect, continue?")
621
+ return not _do_confirm("One of more hosts failed, continue?")
622
+
623
+ state.should_raise_failed_hosts = should_raise_failed_hosts
624
+
625
+
581
626
  def _apply_inventory_limit(inventory, limit):
582
627
  initial_limit = None
583
628
  if limit:
@@ -600,27 +645,34 @@ def _apply_inventory_limit(inventory, limit):
600
645
 
601
646
  # Operations Execution
602
647
  #
603
- def _handle_commands(state, config, command, original_operations, operations, quiet):
604
-
648
+ def _handle_commands(state, config, command, original_operations, operations):
605
649
  if command is CliCommands.FACT:
606
- state, fact_data = _run_fact_operations(state, config, operations, quiet)
650
+ state, fact_data = _run_fact_operations(state, config, operations)
607
651
  print_facts(fact_data)
608
652
  _exit()
609
653
 
654
+ can_diff = True
655
+
610
656
  if command == CliCommands.SHELL:
611
- state = _run_exec_operations(state, config, operations, quiet)
657
+ state = _prepare_exec_operations(state, config, operations)
658
+ can_diff = False
612
659
 
613
660
  elif command == CliCommands.DEPLOY_FILES:
614
- state, config, operations = _run_deploy_operations(state, config, operations, quiet)
661
+ state, config, operations = _prepare_deploy_operations(state, config, operations)
615
662
 
616
663
  elif command == CliCommands.FUNC:
617
- state, kwargs = _run_func_operations(state, config, operations, original_operations, quiet)
664
+ state, kwargs = _prepare_func_operations(
665
+ state,
666
+ config,
667
+ operations,
668
+ original_operations,
669
+ )
618
670
 
619
- return state, config
671
+ return can_diff, state, config
620
672
 
621
673
 
622
- def _run_fact_operations(state, config, operations, quiet):
623
- echo_msg("--> Gathering facts...", quiet)
674
+ def _run_fact_operations(state, config, operations):
675
+ logger.info("--> Gathering facts...")
624
676
 
625
677
  state.print_fact_info = True
626
678
  fact_data = {}
@@ -648,7 +700,7 @@ def _run_fact_operations(state, config, operations, quiet):
648
700
  return state, fact_data
649
701
 
650
702
 
651
- def _run_exec_operations(state, config, operations, quiet):
703
+ def _prepare_exec_operations(state, config, operations):
652
704
  state.print_output = True
653
705
  load_func(
654
706
  state,
@@ -658,8 +710,8 @@ def _run_exec_operations(state, config, operations, quiet):
658
710
  return state
659
711
 
660
712
 
661
- def _run_deploy_operations(state, config, operations, quiet):
662
- echo_msg("--> Preparing Operations...", quiet)
713
+ def _prepare_deploy_operations(state, config, operations):
714
+ logger.info("--> Preparing Operations...")
663
715
 
664
716
  # Number of "steps" to make = number of files * number of hosts
665
717
  for i, filename in enumerate(operations):
@@ -675,8 +727,8 @@ def _run_deploy_operations(state, config, operations, quiet):
675
727
  return state, config, operations
676
728
 
677
729
 
678
- def _run_func_operations(state, config, operations, original_operations, quiet):
679
- echo_msg("--> Preparing operation...", quiet)
730
+ def _prepare_func_operations(state, config, operations, original_operations):
731
+ logger.info("--> Preparing operation...")
680
732
 
681
733
  op, args = operations
682
734
  args, kwargs = args
@@ -684,12 +736,3 @@ def _run_func_operations(state, config, operations, original_operations, quiet):
684
736
  load_func(state, op, *args, **kwargs)
685
737
 
686
738
  return state, kwargs
687
-
688
-
689
- # Utils
690
- #
691
- def echo_msg(msg=None, quiet=None):
692
- if not quiet:
693
- click.echo(err=True)
694
- if msg:
695
- click.echo(msg, err=True)