Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ insert_final_newline = true
trim_trailing_whitespace = true

[*.{py,ini}]
max_line_length = 79
max_line_length = 120

[*.{yml,rst}]
indent_size = 2
Expand Down
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ logging-modules=logging
[FORMAT]

# Maximum number of characters on a single line.
max-line-length=80
max-line-length=120

# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
Expand Down
12 changes: 8 additions & 4 deletions doc/source/SSHClient.rst
Original file line number Diff line number Diff line change
Expand Up @@ -101,24 +101,28 @@ API: SSHClient and SSHAuth.
:param enforce: Enforce sudo enabled or disabled. By default: None
:type enforce: ``typing.Optional[bool]``

.. py:method:: execute_async(command, get_pty=False, open_stdout=True, open_stderr=True, stdin=None, **kwargs)
.. py:method:: execute_async(command, stdin=None, open_stdout=True, open_stderr=True, verbose=False, log_mask_re=None, **kwargs)

Execute command in async mode and return channel with IO objects.

:param command: Command for execution
:type command: ``str``
:param get_pty: open PTY on remote machine
:type get_pty: ``bool``
:param stdin: pass STDIN text to the process
:type stdin: ``typing.Union[six.text_type, six.binary_type, None]``
:type stdin: ``typing.Union[six.text_type, six.binary_type, bytearray, None]``
:param open_stdout: open STDOUT stream for read
:type open_stdout: bool
:param open_stderr: open STDERR stream for read
:type open_stderr: bool
:param verbose: produce verbose log record on command call
:type verbose: bool
:param log_mask_re: regex lookup rule to mask command for logger.
all MATCHED groups will be replaced by '<*masked*>'
:type log_mask_re: typing.Optional[str]
:rtype: ``typing.Tuple[paramiko.Channel, paramiko.ChannelFile, paramiko.ChannelFile, paramiko.ChannelFile]``

.. versionchanged:: 1.2.0 open_stdout and open_stderr flags
.. versionchanged:: 1.2.0 stdin data
.. versionchanged:: 1.2.0 get_pty moved to `**kwargs`

.. py:method:: execute(command, verbose=False, timeout=1*60*60, **kwargs)

Expand Down
21 changes: 21 additions & 0 deletions doc/source/Subprocess.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,27 @@ API: Subprocess

.. versionchanged:: 1.1.0 release lock on exit

.. py:method:: execute_async(command, stdin=None, open_stdout=True, open_stderr=True, verbose=False, log_mask_re=None, **kwargs)

Execute command in async mode and return Popen with IO objects.

:param command: Command for execution
:type command: str
:param stdin: pass STDIN text to the process
:type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None]
:param open_stdout: open STDOUT stream for read
:type open_stdout: bool
:param open_stderr: open STDERR stream for read
:type open_stderr: bool
:param verbose: produce verbose log record on command call
:type verbose: bool
:param log_mask_re: regex lookup rule to mask command for logger.
all MATCHED groups will be replaced by '<*masked*>'
:type log_mask_re: typing.Optional[str]
:rtype: ``typing.Tuple[subprocess.Popen, None, typing.Optional[io.TextIOWrapper], typing.Optional[io.TextIOWrapper], ]``

.. versionadded:: 1.2.0

.. py:method:: execute(command, verbose=False, timeout=1*60*60, **kwargs)

Execute command and wait for return code.
Expand Down
107 changes: 105 additions & 2 deletions exec_helpers/_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,16 @@
from __future__ import division
from __future__ import unicode_literals

import logging
import re
import threading
import typing # noqa # pylint: disable=unused-import

import six # noqa # pylint: disable=unused-import

from exec_helpers import constants
from exec_helpers import exceptions
from exec_helpers import exec_result # noqa # pylint: disable=unused-import
from exec_helpers import proc_enums
from exec_helpers import _log_templates

Expand All @@ -44,7 +49,7 @@ def __init__(
self,
logger, # type: logging.Logger
log_mask_re=None, # type: typing.Optional[str]
):
): # type: (...) -> None
"""ExecHelper global API.

:param log_mask_re: regex lookup rule to mask command for logger.
Expand Down Expand Up @@ -126,6 +131,78 @@ def mask(text, rules): # type: (str, str) -> str

return cmd

def execute_async(
self,
command, # type: str
stdin=None, # type: typing.Union[six.text_type, six.binary_type, bytearray, None]
open_stdout=True, # type: bool
open_stderr=True, # type: bool
verbose=False, # type: bool
log_mask_re=None, # type: typing.Optional[str]
**kwargs
):
"""Execute command in async mode and return remote interface with IO objects.

:param command: Command for execution
:type command: str
:param stdin: pass STDIN text to the process
:type stdin: typing.Union[six.text_type, six.binary_type, bytearray, None]
:param open_stdout: open STDOUT stream for read
:type open_stdout: bool
:param open_stderr: open STDERR stream for read
:type open_stderr: bool
:param verbose: produce verbose log record on command call
:type verbose: bool
:param log_mask_re: regex lookup rule to mask command for logger.
all MATCHED groups will be replaced by '<*masked*>'
:type log_mask_re: typing.Optional[str]
:rtype: typing.Tuple[
typing.Any,
typing.Any,
typing.Any,
typing.Any,
]

.. versionchanged:: 1.2.0 open_stdout and open_stderr flags
.. versionchanged:: 1.2.0 stdin data
"""
raise NotImplementedError # pragma: no cover

def _exec_command(
self,
command, # type: str
interface, # type: typing.Any,
stdout, # type: typing.Any,
stderr, # type: typing.Any,
timeout, # type: int
verbose=False, # type: bool
log_mask_re=None, # type: typing.Optional[str]
**kwargs
): # type: (...) -> exec_result.ExecResult
"""Get exit status from channel with timeout.

:param command: Command for execution
:type command: str
:param interface: Control interface
:type interface: typing.Any
:param stdout: STDOUT pipe or file-like object
:type stdout: typing.Any
:param stderr: STDERR pipe or file-like object
:type stderr: typing.Any
:param timeout: Timeout for command execution
:type timeout: int
:param verbose: produce verbose log record on command call
:type verbose: bool
:param log_mask_re: regex lookup rule to mask command for logger.
all MATCHED groups will be replaced by '<*masked*>'
:type log_mask_re: typing.Optional[str]
:rtype: ExecResult
:raises ExecHelperTimeoutError: Timeout exceeded

.. versionchanged:: 1.2.0 log_mask_re regex rule for masking cmd
"""
raise NotImplementedError # pragma: no cover

def execute(
self,
command, # type: str
Expand All @@ -148,7 +225,33 @@ def execute(

.. versionchanged:: 1.2.0 default timeout 1 hour
"""
raise NotImplementedError() # pragma: no cover
with self.lock:
(
iface, # type: typing.Any
_,
stderr, # type: typing.Any
stdout, # type: typing.Any
) = self.execute_async(
command,
verbose=verbose,
**kwargs
)

result = self._exec_command(
command=command,
interface=iface,
stdout=stdout,
stderr=stderr,
timeout=timeout,
verbose=verbose,
**kwargs
)
message = _log_templates.CMD_RESULT.format(result=result)
self.logger.log(
level=logging.INFO if verbose else logging.DEBUG,
msg=message
)
return result

def check_call(
self,
Expand Down
Loading