Matt Martz 2 weeks ago committed by GitHub
commit d971eca621
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -17,19 +17,25 @@
from __future__ import annotations
import contextlib
import fcntl
import os
import sys
import termios
import traceback
from jinja2.exceptions import TemplateNotFound
from multiprocessing.queues import Queue
from ansible import context
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.executor.task_executor import TaskExecutor
from ansible.module_utils.common.collections import is_sequence
from ansible.module_utils.common.text.converters import to_text
from ansible.plugins.loader import init_plugin_loader
from ansible.utils.display import Display
from ansible.utils.multiprocessing import context as multiprocessing_context
from jinja2.exceptions import TemplateNotFound
__all__ = ['WorkerProcess']
display = Display()
@ -53,7 +59,7 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
for reading later.
'''
def __init__(self, final_q, task_vars, host, task, play_context, loader, variable_manager, shared_loader_obj, worker_id):
def __init__(self, final_q, task_vars, host, task, play_context, loader, variable_manager, shared_loader_obj, worker_id, cliargs):
super(WorkerProcess, self).__init__()
# takes a task queue manager as the sole param:
@ -73,22 +79,12 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
self.worker_queue = WorkerQueue(ctx=multiprocessing_context)
self.worker_id = worker_id
def _save_stdin(self):
self._new_stdin = None
try:
if sys.stdin.isatty() and sys.stdin.fileno() is not None:
try:
self._new_stdin = os.fdopen(os.dup(sys.stdin.fileno()))
except OSError:
# couldn't dupe stdin, most likely because it's
# not a valid file descriptor
pass
except (AttributeError, ValueError):
# couldn't get stdin's fileno
pass
self._cliargs = cliargs
if self._new_stdin is None:
self._new_stdin = open(os.devnull)
self._detached = False
self._detach_error = None
self._master = None
self._slave = None
def start(self):
'''
@ -99,13 +95,20 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
make sure it is closed in the parent when start() completes.
'''
self._save_stdin()
# FUTURE: this lock can be removed once a more generalized pre-fork thread pause is in place
with display._lock:
try:
return super(WorkerProcess, self).start()
finally:
self._new_stdin.close()
if multiprocessing_context.get_start_method() == 'fork':
self._master, self._slave = os.openpty()
cm = contextlib.ExitStack()
cm.callback(os.close, self._slave)
else:
cm = contextlib.nullcontext()
with display._lock, cm:
super(WorkerProcess, self).start()
def close(self):
if self._master:
os.close(self._master)
super().close()
def _hard_exit(self, e):
'''
@ -125,6 +128,36 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
os._exit(1)
@contextlib.contextmanager
def _detach(self):
'''
The intent here is to detach the child process from the inherited stdio fds,
including /dev/tty. Children should use Display, instead of direct interactions
with stdio fds.
'''
try:
if multiprocessing_context.get_start_method() != 'fork':
# If we aren't forking, we don't have inherited pty
_io = open(os.devnull, 'w')
else:
os.close(self._master)
os.setsid()
fcntl.ioctl(self._slave, termios.TIOCSCTTY)
_io = os.fdopen(self._slave, 'w')
sys.stdin = sys.__stdin__ = _io # type: ignore[misc]
sys.stdout = sys.__stdout__ = sys.stdin # type: ignore[misc]
sys.stderr = sys.__stderr__ = sys.stdin # type: ignore[misc]
except Exception as e:
# We aren't in a place we can reliably notify from, just pass here, evaluate self._detached later
self._detach_error = (e, traceback.format_exc())
else:
self._detached = True
yield
if self._slave:
os.close(self._slave)
def run(self):
'''
Wrap _run() to ensure no possibility an errant exception can cause
@ -136,23 +169,11 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
to suddenly assume the role and prior state of its parent.
'''
try:
return self._run()
except BaseException as e:
self._hard_exit(e)
finally:
# This is a hack, pure and simple, to work around a potential deadlock
# in ``multiprocessing.Process`` when flushing stdout/stderr during process
# shutdown.
#
# We should no longer have a problem with ``Display``, as it now proxies over
# the queue from a fork. However, to avoid any issues with plugins that may
# be doing their own printing, this has been kept.
#
# This happens at the very end to avoid that deadlock, by simply side
# stepping it. This should not be treated as a long term fix.
#
# TODO: Evaluate migrating away from the ``fork`` multiprocessing start method.
sys.stdout = sys.stderr = open(os.devnull, 'w')
display.set_queue(self._final_q)
with self._detach():
return self._run()
except BaseException:
self._hard_exit(traceback.format_exc())
def _run(self):
'''
@ -168,9 +189,23 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
# Set the queue on Display so calls to Display.display are proxied over the queue
display.set_queue(self._final_q)
if not self._detached:
display.debug(f'Could not detach from stdio: {self._detach_error[1]}')
display.error(f'Could not detach from stdio: {self._detach_error[0]}')
os._exit(1)
global current_worker
current_worker = self
if multiprocessing_context.get_start_method() != 'fork':
context.CLIARGS = self._cliargs
# Initialize plugin loader after parse, so that the init code can utilize parsed arguments
cli_collections_path = context.CLIARGS.get('collections_path') or []
if not is_sequence(cli_collections_path):
# In some contexts ``collections_path`` is singular
cli_collections_path = [cli_collections_path]
init_plugin_loader(cli_collections_path)
try:
# execute the task and build a TaskResult from the result
display.debug("running TaskExecutor() for %s/%s" % (self._host, self._task))
@ -179,7 +214,6 @@ class WorkerProcess(multiprocessing_context.Process): # type: ignore[name-defin
self._task,
self._task_vars,
self._play_context,
self._new_stdin,
self._loader,
self._shared_loader_obj,
self._final_q,

@ -81,12 +81,11 @@ class TaskExecutor:
class.
'''
def __init__(self, host, task, job_vars, play_context, new_stdin, loader, shared_loader_obj, final_q, variable_manager):
def __init__(self, host, task, job_vars, play_context, loader, shared_loader_obj, final_q, variable_manager):
self._host = host
self._task = task
self._job_vars = job_vars
self._play_context = play_context
self._new_stdin = new_stdin
self._loader = loader
self._shared_loader_obj = shared_loader_obj
self._connection = None
@ -958,7 +957,7 @@ class TaskExecutor:
connection, plugin_load_context = self._shared_loader_obj.connection_loader.get_with_context(
conn_type,
self._play_context,
self._new_stdin,
new_stdin=None, # No longer used, kept for backwards compat for plugins that explicitly accept this as an arg
task_uuid=self._task._uuid,
ansible_playbook_pid=to_text(os.getppid())
)

@ -71,9 +71,8 @@ class ConnectionBase(AnsiblePlugin):
def __init__(
self,
play_context: PlayContext,
new_stdin: io.TextIOWrapper | None = None,
shell: ShellBase | None = None,
*args: t.Any,
shell: ShellBase | None = None,
**kwargs: t.Any,
) -> None:
@ -83,9 +82,6 @@ class ConnectionBase(AnsiblePlugin):
if not hasattr(self, '_play_context'):
# Backwards compat: self._play_context isn't really needed, using set_options/get_option
self._play_context = play_context
# Delete once the deprecation period is over for WorkerProcess._new_stdin
if not hasattr(self, '__new_stdin'):
self.__new_stdin = new_stdin
if not hasattr(self, '_display'):
# Backwards compat: self._display isn't really needed, just import the global display and use that.
self._display = display
@ -105,15 +101,6 @@ class ConnectionBase(AnsiblePlugin):
self.become: BecomeBase | None = None
@property
def _new_stdin(self) -> io.TextIOWrapper | None:
display.deprecated(
"The connection's stdin object is deprecated. "
"Call display.prompt_until(msg) instead.",
version='2.19',
)
return self.__new_stdin
def set_become_plugin(self, plugin: BecomeBase) -> None:
self.become = plugin
@ -298,11 +285,10 @@ class NetworkConnectionBase(ConnectionBase):
def __init__(
self,
play_context: PlayContext,
new_stdin: io.TextIOWrapper | None = None,
*args: t.Any,
**kwargs: t.Any,
) -> None:
super(NetworkConnectionBase, self).__init__(play_context, new_stdin, *args, **kwargs)
super(NetworkConnectionBase, self).__init__(play_context, *args, **kwargs)
self._messages: list[tuple[str, str]] = []
self._conn_closed = False

@ -6,11 +6,13 @@
from __future__ import annotations
import functools
import glob
import os
import os.path
import pkgutil
import sys
import types
import warnings
from collections import defaultdict, namedtuple
@ -52,10 +54,19 @@ display = Display()
get_with_context_result = namedtuple('get_with_context_result', ['object', 'plugin_load_context'])
@functools.cache
def get_all_plugin_loaders():
return [(name, obj) for (name, obj) in globals().items() if isinstance(obj, PluginLoader)]
@functools.cache
def get_plugin_loader_namespace():
ns = types.SimpleNamespace()
for name, obj in get_all_plugin_loaders():
setattr(ns, name, obj)
return ns
def add_all_plugin_dirs(path):
''' add any existing plugin dirs in the path provided '''
b_path = os.path.expanduser(to_bytes(path, errors='surrogate_or_strict'))

@ -399,6 +399,8 @@ class StrategyBase:
worker_prc = self._workers[self._cur_worker]
if worker_prc is None or not worker_prc.is_alive():
if worker_prc:
worker_prc.close()
self._queued_task_cache[(host.name, task._uuid)] = {
'host': host,
'task': task,
@ -408,7 +410,8 @@ class StrategyBase:
# Pass WorkerProcess its strategy worker number so it can send an identifier along with intra-task requests
worker_prc = WorkerProcess(
self._final_q, task_vars, host, task, play_context, self._loader, self._variable_manager, plugin_loader, self._cur_worker,
self._final_q, task_vars, host, task, play_context, self._loader, self._variable_manager,
plugin_loader.get_plugin_loader_namespace(), self._cur_worker, context.CLIARGS,
)
self._workers[self._cur_worker] = worker_prc
self._tqm.send_callback('v2_runner_on_start', host, task)

@ -42,7 +42,6 @@ class TestTaskExecutor(unittest.TestCase):
mock_task = MagicMock()
mock_play_context = MagicMock()
mock_shared_loader = MagicMock()
new_stdin = None
job_vars = dict()
mock_queue = MagicMock()
te = TaskExecutor(
@ -50,7 +49,6 @@ class TestTaskExecutor(unittest.TestCase):
task=mock_task,
job_vars=job_vars,
play_context=mock_play_context,
new_stdin=new_stdin,
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
@ -70,7 +68,6 @@ class TestTaskExecutor(unittest.TestCase):
mock_shared_loader = MagicMock()
mock_queue = MagicMock()
new_stdin = None
job_vars = dict()
te = TaskExecutor(
@ -78,7 +75,6 @@ class TestTaskExecutor(unittest.TestCase):
task=mock_task,
job_vars=job_vars,
play_context=mock_play_context,
new_stdin=new_stdin,
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
@ -101,7 +97,7 @@ class TestTaskExecutor(unittest.TestCase):
self.assertIn("failed", res)
def test_task_executor_run_clean_res(self):
te = TaskExecutor(None, MagicMock(), None, None, None, None, None, None, None)
te = TaskExecutor(None, MagicMock(), None, None, None, None, None, None)
te._get_loop_items = MagicMock(return_value=[1])
te._run_loop = MagicMock(
return_value=[
@ -136,7 +132,6 @@ class TestTaskExecutor(unittest.TestCase):
mock_shared_loader = MagicMock()
mock_shared_loader.lookup_loader = lookup_loader
new_stdin = None
job_vars = dict()
mock_queue = MagicMock()
@ -145,7 +140,6 @@ class TestTaskExecutor(unittest.TestCase):
task=mock_task,
job_vars=job_vars,
play_context=mock_play_context,
new_stdin=new_stdin,
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
@ -174,7 +168,6 @@ class TestTaskExecutor(unittest.TestCase):
mock_shared_loader = MagicMock()
mock_queue = MagicMock()
new_stdin = None
job_vars = dict()
te = TaskExecutor(
@ -182,7 +175,6 @@ class TestTaskExecutor(unittest.TestCase):
task=mock_task,
job_vars=job_vars,
play_context=mock_play_context,
new_stdin=new_stdin,
loader=fake_loader,
shared_loader_obj=mock_shared_loader,
final_q=mock_queue,
@ -203,7 +195,6 @@ class TestTaskExecutor(unittest.TestCase):
task=MagicMock(),
job_vars={},
play_context=MagicMock(),
new_stdin=None,
loader=DictDataLoader({}),
shared_loader_obj=MagicMock(),
final_q=MagicMock(),
@ -240,7 +231,6 @@ class TestTaskExecutor(unittest.TestCase):
task=MagicMock(),
job_vars={},
play_context=MagicMock(),
new_stdin=None,
loader=DictDataLoader({}),
shared_loader_obj=MagicMock(),
final_q=MagicMock(),
@ -279,7 +269,6 @@ class TestTaskExecutor(unittest.TestCase):
task=MagicMock(),
job_vars={},
play_context=MagicMock(),
new_stdin=None,
loader=DictDataLoader({}),
shared_loader_obj=MagicMock(),
final_q=MagicMock(),
@ -356,7 +345,6 @@ class TestTaskExecutor(unittest.TestCase):
mock_vm.get_delegated_vars_and_hostname.return_value = {}, None
shared_loader = MagicMock()
new_stdin = None
job_vars = dict(omit="XXXXXXXXXXXXXXXXXXX")
te = TaskExecutor(
@ -364,7 +352,6 @@ class TestTaskExecutor(unittest.TestCase):
task=mock_task,
job_vars=job_vars,
play_context=mock_play_context,
new_stdin=new_stdin,
loader=fake_loader,
shared_loader_obj=shared_loader,
final_q=mock_queue,
@ -413,7 +400,6 @@ class TestTaskExecutor(unittest.TestCase):
shared_loader = MagicMock()
shared_loader.action_loader = action_loader
new_stdin = None
job_vars = dict(omit="XXXXXXXXXXXXXXXXXXX")
te = TaskExecutor(
@ -421,7 +407,6 @@ class TestTaskExecutor(unittest.TestCase):
task=mock_task,
job_vars=job_vars,
play_context=mock_play_context,
new_stdin=new_stdin,
loader=fake_loader,
shared_loader_obj=shared_loader,
final_q=mock_queue,

@ -17,8 +17,6 @@
from __future__ import annotations
import os
import unittest
from unittest.mock import MagicMock, Mock
from ansible.plugins.action.raw import ActionModule
@ -31,7 +29,7 @@ class TestCopyResultExclude(unittest.TestCase):
def setUp(self):
self.play_context = Mock()
self.play_context.shell = 'sh'
self.connection = connection_loader.get('local', self.play_context, os.devnull)
self.connection = connection_loader.get('local', self.play_context)
def tearDown(self):
pass

@ -7,7 +7,6 @@ from __future__ import annotations
import pytest
import sys
from io import StringIO
from unittest.mock import MagicMock
from ansible.playbook.play_context import PlayContext
@ -204,9 +203,8 @@ class TestConnectionPSRP(object):
((o, e) for o, e in OPTIONS_DATA))
def test_set_options(self, options, expected):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('psrp', pc, new_stdin)
conn = connection_loader.get('psrp', pc)
conn.set_options(var_options=options)
conn._build_kwargs()
@ -218,9 +216,8 @@ class TestConnectionPSRP(object):
def test_set_invalid_extras_options(self, monkeypatch):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('psrp', pc, new_stdin)
conn = connection_loader.get('psrp', pc)
conn.set_options(var_options={'_extras': {'ansible_psrp_mock_test3': True}})
mock_display = MagicMock()

@ -75,16 +75,14 @@ class TestConnectionBaseClass(unittest.TestCase):
def test_plugins_connection_ssh__build_command(self):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin)
conn = connection_loader.get('ssh', pc)
conn.get_option = MagicMock()
conn.get_option.return_value = ""
conn._build_command('ssh', 'ssh')
def test_plugins_connection_ssh_exec_command(self):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin)
conn = connection_loader.get('ssh', pc)
conn._build_command = MagicMock()
conn._build_command.return_value = 'ssh something something'
@ -98,10 +96,9 @@ class TestConnectionBaseClass(unittest.TestCase):
def test_plugins_connection_ssh__examine_output(self):
pc = PlayContext()
new_stdin = StringIO()
become_success_token = b'BECOME-SUCCESS-abcdefghijklmnopqrstuvxyz'
conn = connection_loader.get('ssh', pc, new_stdin)
conn = connection_loader.get('ssh', pc)
conn.set_become_plugin(become_loader.get('sudo'))
conn.become.check_password_prompt = MagicMock()
@ -230,8 +227,7 @@ class TestConnectionBaseClass(unittest.TestCase):
@patch('os.path.exists')
def test_plugins_connection_ssh_put_file(self, mock_ospe, mock_sleep):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin)
conn = connection_loader.get('ssh', pc)
conn._build_command = MagicMock()
conn._bare_run = MagicMock()
@ -282,8 +278,7 @@ class TestConnectionBaseClass(unittest.TestCase):
@patch('time.sleep')
def test_plugins_connection_ssh_fetch_file(self, mock_sleep):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin)
conn = connection_loader.get('ssh', pc)
conn._build_command = MagicMock()
conn._bare_run = MagicMock()
conn._load_name = 'ssh'
@ -348,9 +343,8 @@ class MockSelector(object):
@pytest.fixture
def mock_run_env(request, mocker):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('ssh', pc, new_stdin)
conn = connection_loader.get('ssh', pc)
conn.set_become_plugin(become_loader.get('sudo'))
conn._send_initial_data = MagicMock()
conn._examine_output = MagicMock()

@ -8,8 +8,6 @@ import os
import pytest
from io import StringIO
from unittest.mock import MagicMock
from ansible.errors import AnsibleConnectionFailure, AnsibleError
from ansible.module_utils.common.text.converters import to_bytes
@ -207,9 +205,8 @@ class TestConnectionWinRM(object):
winrm.HAVE_KERBEROS = kerb
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options=options, direct=direct)
conn._build_winrm_kwargs()
@ -245,8 +242,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = False
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options=options)
conn._build_winrm_kwargs()
@ -279,8 +275,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = True
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options=options)
conn._build_winrm_kwargs()
@ -308,8 +303,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = False
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
options = {"_extras": {}, "ansible_winrm_kinit_cmd": "/fake/kinit"}
conn.set_options(var_options=options)
conn._build_winrm_kwargs()
@ -331,8 +325,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = True
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
options = {"_extras": {}, "ansible_winrm_kinit_cmd": "/fake/kinit"}
conn.set_options(var_options=options)
conn._build_winrm_kwargs()
@ -356,8 +349,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = False
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"_extras": {}})
conn._build_winrm_kwargs()
@ -381,8 +373,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = True
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"_extras": {}})
conn._build_winrm_kwargs()
@ -404,8 +395,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = False
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"_extras": {}})
conn._build_winrm_kwargs()
@ -429,8 +419,7 @@ class TestWinRMKerbAuth(object):
winrm.HAS_PEXPECT = True
pc = PlayContext()
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"_extras": {}})
conn._build_winrm_kwargs()
@ -444,8 +433,7 @@ class TestWinRMKerbAuth(object):
requests_exc = pytest.importorskip("requests.exceptions")
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
mock_proto = MagicMock()
mock_proto.run_command.side_effect = requests_exc.Timeout("msg")
@ -464,8 +452,7 @@ class TestWinRMKerbAuth(object):
requests_exc = pytest.importorskip("requests.exceptions")
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
mock_proto = MagicMock()
mock_proto.run_command.return_value = "command_id"
@ -483,8 +470,7 @@ class TestWinRMKerbAuth(object):
def test_connect_failure_auth_401(self, monkeypatch):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"ansible_winrm_transport": "basic", "_extras": {}})
mock_proto = MagicMock()
@ -499,8 +485,7 @@ class TestWinRMKerbAuth(object):
def test_connect_failure_other_exception(self, monkeypatch):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"ansible_winrm_transport": "basic", "_extras": {}})
mock_proto = MagicMock()
@ -515,8 +500,7 @@ class TestWinRMKerbAuth(object):
def test_connect_failure_operation_timed_out(self, monkeypatch):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"ansible_winrm_transport": "basic", "_extras": {}})
mock_proto = MagicMock()
@ -531,8 +515,7 @@ class TestWinRMKerbAuth(object):
def test_connect_no_transport(self):
pc = PlayContext()
new_stdin = StringIO()
conn = connection_loader.get('winrm', pc, new_stdin)
conn = connection_loader.get('winrm', pc)
conn.set_options(var_options={"_extras": {}})
conn._build_winrm_kwargs()
conn._winrm_transport = []

Loading…
Cancel
Save