Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to disable streaming to stdout file #937

Merged
merged 3 commits into from Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions ansible_runner/config/_base.py
Expand Up @@ -244,6 +244,7 @@ def _prepare_env(self, runner_mode='pexpect'):
self.ssh_key_path = os.path.join(self.artifact_dir, 'ssh_key_data')
open_fifo_write(self.ssh_key_path, self.ssh_key_data)

self.suppress_output_file = self.settings.get('suppress_output_file', False)
self.suppress_ansible_output = self.settings.get('suppress_ansible_output', self.quiet)

if 'fact_cache' in self.settings:
Expand Down
3 changes: 2 additions & 1 deletion ansible_runner/config/runner.py
Expand Up @@ -65,7 +65,7 @@ class RunnerConfig(BaseConfig):
def __init__(self,
private_data_dir, playbook=None, inventory=None, roles_path=None, limit=None,
module=None, module_args=None, verbosity=None, host_pattern=None, binary=None,
extravars=None, suppress_ansible_output=False, process_isolation_path=None,
extravars=None, suppress_output_file=False, suppress_ansible_output=False, process_isolation_path=None,
process_isolation_hide_paths=None, process_isolation_show_paths=None,
process_isolation_ro_paths=None, resource_profiling=False,
resource_profiling_base_cgroup='ansible-runner', resource_profiling_cpu_poll_interval=0.25,
Expand Down Expand Up @@ -101,6 +101,7 @@ def __init__(self,

self.directory_isolation_path = directory_isolation_base_path
self.verbosity = verbosity
self.suppress_output_file = suppress_output_file
self.suppress_ansible_output = suppress_ansible_output
self.tags = tags
self.skip_tags = skip_tags
Expand Down
19 changes: 13 additions & 6 deletions ansible_runner/runner.py
Expand Up @@ -10,6 +10,7 @@
import collections
import datetime
import logging
from io import StringIO

import six
import pexpect
Expand Down Expand Up @@ -117,9 +118,7 @@ def run(self):
password_values = []

self.status_callback('starting')
stdout_filename = os.path.join(self.config.artifact_dir, 'stdout')
command_filename = os.path.join(self.config.artifact_dir, 'command')
stderr_filename = os.path.join(self.config.artifact_dir, 'stderr')

try:
os.makedirs(self.config.artifact_dir, mode=0o700)
Expand All @@ -128,7 +127,6 @@ def run(self):
pass
else:
raise
os.close(os.open(stdout_filename, os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR))

job_events_path = os.path.join(self.config.artifact_dir, 'job_events')
if not os.path.exists(job_events_path):
Expand All @@ -151,9 +149,17 @@ def run(self):
else:
suppress_ansible_output = False

stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
if not self.config.suppress_output_file:
stdout_filename = os.path.join(self.config.artifact_dir, 'stdout')
stderr_filename = os.path.join(self.config.artifact_dir, 'stderr')
os.close(os.open(stdout_filename, os.O_CREAT, stat.S_IRUSR | stat.S_IWUSR))
stdout_handle = codecs.open(stdout_filename, 'w', encoding='utf-8')
stderr_handle = codecs.open(stderr_filename, 'w', encoding='utf-8')
else:
stdout_handle = StringIO()
stderr_handle = StringIO()

stdout_handle = OutputEventFilter(stdout_handle, self.event_callback, suppress_ansible_output, output_json=self.config.json_mode)
stderr_handle = codecs.open(stderr_filename, 'w', encoding='utf-8')
stderr_handle = OutputEventFilter(stderr_handle, self.event_callback, suppress_ansible_output, output_json=self.config.json_mode)

if self.runner_mode == 'pexpect' and not isinstance(self.config.expect_passwords, collections.OrderedDict):
Expand Down Expand Up @@ -307,7 +313,8 @@ def run(self):
echo=False,
use_poll=self.config.pexpect_use_poll,
)
child.logfile_read = stdout_handle
if not self.config.suppress_output_file:
child.logfile_read = stdout_handle
except pexpect.exceptions.ExceptionPexpect as e:
child = collections.namedtuple(
'MissingProcess', 'exitstatus isalive close'
Expand Down
3 changes: 2 additions & 1 deletion docs/intro.rst
Expand Up @@ -139,7 +139,8 @@ The **settings** file is a little different than the other files provided in thi
* ``pexpect_timeout``: ``10`` Number of seconds for the internal pexpect command to wait to block on input before continuing
* ``pexpect_use_poll``: ``True`` Use ``poll()`` function for communication with child processes instead of ``select()``. ``select()`` is used when the value is set to ``False``. ``select()`` has a known limitation of using only up to 1024 file descriptors.

* ``suppress_ansible_output``: ``False`` Allow output from ansible to not be printed to the screen
* ``suppress_output_file``: ``False`` Allow output from ansible to not be streamed to the ``stdout`` or ``stderr`` files inside of the artifacts directory.
* ``suppress_ansible_output``: ``False`` Allow output from ansible to not be printed to the screen.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a check to make these mutually exclusive? If they are both set ansible would "just run" with no output, maybe we don't care?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AWX will set them both to False by design. The output is processed by the callback receiver. If someone really wanted the standard out file (which is normally deleted at the end of the run anyway) then we can introduce a setting for them to get it back.

There's a feature in AWX to download the standard out file from a job run. It is illustrative that this works by a completely unrelated method. In the request-response cycle, it is written to disk from the database data and then delivered to the client. Then deleted.

* ``fact_cache``: ``'fact_cache'`` The directory relative to ``artifacts`` where ``jsonfile`` fact caching will be stored. Defaults to ``fact_cache``. This is ignored if ``fact_cache_type`` is different than ``jsonfile``.
* ``fact_cache_type``: ``'jsonfile'`` The type of fact cache to use. Defaults to ``jsonfile``.

Expand Down
23 changes: 23 additions & 0 deletions test/unit/test_runner.py
Expand Up @@ -4,6 +4,7 @@
import os

import json
from pathlib import Path
import pexpect
import pytest
import six
Expand Down Expand Up @@ -152,3 +153,25 @@ def test_status_callback_interface(rc, mocker):
assert runner.status_handler.call_count == 1
runner.status_handler.assert_called_with(dict(status='running', runner_ident=str(rc.ident)), runner_config=runner.config)
assert runner.status == 'running'


@pytest.mark.parametrize('runner_mode', ['pexpect', 'subprocess'])
def test_stdout_file_write(rc, runner_mode):
rc.command = ['echo', 'hello_world_marker']
rc.runner_mode = runner_mode
status, exitcode = Runner(config=rc).run()
assert status == 'successful'
stdout_path = Path(rc.artifact_dir) / 'stdout'
assert stdout_path.read_text().strip() == 'hello_world_marker'


@pytest.mark.parametrize('runner_mode', ['pexpect', 'subprocess'])
def test_stdout_file_no_write(rc, runner_mode):
rc.command = ['echo', 'hello_world_marker']
rc.runner_mode = runner_mode
rc.suppress_output_file = True
status, exitcode = Runner(config=rc).run()
assert status == 'successful'
for filename in ('stdout', 'stderr'):
stdout_path = Path(rc.artifact_dir) / filename
assert not stdout_path.exists()