Merge remote-tracking branch 'origin/dmw'
* origin/dmw: issue #404: add to Changelog. issue #251: readd to Changelog. tests: add exact test for issue 251; closes #251. issue #412: pad out debugging docs, add get_stack to changelog. issue #412: force-verbose output for mitogen_get_stack. issue #412: promote "mitogen_get_stack" to the main extension. issue #412: add docstrings/boilerplate to transport_config.py. issue #251, #412, #434: fix connection configuration brainwrong issue #434: tests: set a default remote_user in ansible.cfg. tests: CI should symlink all contents of ansible/hosts/ ansible: fix test failure during process exit. tests: use assert_equal in more places. tests: make assert_equal work on newer Ansibles. tests: convert stack_construction.yml to assert_equal. tests: make fork_histogram optional tests: use assert_equal in delegate_to_template.yml. tests: import assert_equal action. tests: rename 'delegation/' to 'connection_delegation/' core: replace ancient YOLO loop in fire(). tests: some more utility function tests + flake8. tests: clean up / deduplicate Ansible inventory. tests: add some more helper function tests.issue510
commit
2e63586483
@ -0,0 +1,54 @@
|
|||||||
|
# Copyright 2017, David Wilson
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
"""
|
||||||
|
Fetch the connection configuration stack that would be used to connect to a
|
||||||
|
target, without actually connecting to it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ansible_mitogen.connection
|
||||||
|
|
||||||
|
from ansible.plugins.action import ActionBase
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(ActionBase):
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
if not isinstance(self._connection,
|
||||||
|
ansible_mitogen.connection.Connection):
|
||||||
|
return {
|
||||||
|
'skipped': True,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'changed': True,
|
||||||
|
'result': self._connection._build_stack(),
|
||||||
|
'_ansible_verbose_always': True,
|
||||||
|
}
|
@ -0,0 +1,567 @@
|
|||||||
|
# Copyright 2017, David Wilson
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
#
|
||||||
|
# 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# 3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software without
|
||||||
|
# specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||||
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||||
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||||
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||||
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
|
# POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
"""
|
||||||
|
Mitogen extends Ansible's target configuration mechanism in several ways that
|
||||||
|
require some care:
|
||||||
|
|
||||||
|
* Per-task configurables in Ansible like ansible_python_interpreter are
|
||||||
|
connection-layer configurables in Mitogen. They must be extracted during each
|
||||||
|
task execution to form the complete connection-layer configuration.
|
||||||
|
|
||||||
|
* Mitogen has extra configurables not supported by Ansible at all, such as
|
||||||
|
mitogen_ssh_debug_level. These are extracted the same way as
|
||||||
|
ansible_python_interpreter.
|
||||||
|
|
||||||
|
* Mitogen allows connections to be delegated to other machines. Ansible has no
|
||||||
|
internal framework for this, and so Mitogen must figure out a delegated
|
||||||
|
connection configuration all on its own. It cannot reuse much of the Ansible
|
||||||
|
machinery for building a connection configuration, as that machinery is
|
||||||
|
deeply spread out and hard-wired to expect Ansible's usual mode of operation.
|
||||||
|
|
||||||
|
For normal and delegate_to connections, Ansible's PlayContext is reused where
|
||||||
|
possible to maximize compatibility, but for proxy hops, configurations are
|
||||||
|
built up using the HostVars magic class to call VariableManager.get_vars()
|
||||||
|
behind the scenes on our behalf. Where Ansible has multiple sources of a
|
||||||
|
configuration item, for example, ansible_ssh_extra_args, Mitogen must (ideally
|
||||||
|
perfectly) reproduce how Ansible arrives at its value, without using mechanisms
|
||||||
|
that are hard-wired or change across Ansible versions.
|
||||||
|
|
||||||
|
That is what this file is for. It exports two spec classes, one that takes all
|
||||||
|
information from PlayContext, and another that takes (almost) all information
|
||||||
|
from HostVars.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import abc
|
||||||
|
import os
|
||||||
|
import ansible.utils.shlex
|
||||||
|
import ansible.constants as C
|
||||||
|
|
||||||
|
from ansible.module_utils.six import with_metaclass
|
||||||
|
|
||||||
|
|
||||||
|
import mitogen.core
|
||||||
|
|
||||||
|
|
||||||
|
def parse_python_path(s):
|
||||||
|
"""
|
||||||
|
Given the string set for ansible_python_interpeter, parse it using shell
|
||||||
|
syntax and return an appropriate argument vector.
|
||||||
|
"""
|
||||||
|
if s:
|
||||||
|
return ansible.utils.shlex.shlex_split(s)
|
||||||
|
|
||||||
|
|
||||||
|
def optional_secret(value):
|
||||||
|
"""
|
||||||
|
Wrap `value` in :class:`mitogen.core.Secret` if it is not :data:`None`,
|
||||||
|
otherwise return :data:`None`.
|
||||||
|
"""
|
||||||
|
if value is not None:
|
||||||
|
return mitogen.core.Secret(value)
|
||||||
|
|
||||||
|
|
||||||
|
class Spec(with_metaclass(abc.ABCMeta, object)):
|
||||||
|
"""
|
||||||
|
A source for variables that comprise a connection configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def transport(self):
|
||||||
|
"""
|
||||||
|
The name of the Ansible plug-in implementing the connection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def inventory_name(self):
|
||||||
|
"""
|
||||||
|
The name of the target being connected to as it appears in Ansible's
|
||||||
|
inventory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def remote_addr(self):
|
||||||
|
"""
|
||||||
|
The network address of the target, or for container and other special
|
||||||
|
targets, some other unique identifier.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def remote_user(self):
|
||||||
|
"""
|
||||||
|
The username of the login account on the target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def password(self):
|
||||||
|
"""
|
||||||
|
The password of the login account on the target.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def become(self):
|
||||||
|
"""
|
||||||
|
:data:`True` if privilege escalation should be active.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def become_method(self):
|
||||||
|
"""
|
||||||
|
The name of the Ansible become method to use.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def become_user(self):
|
||||||
|
"""
|
||||||
|
The username of the target account for become.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def become_pass(self):
|
||||||
|
"""
|
||||||
|
The password of the target account for become.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def port(self):
|
||||||
|
"""
|
||||||
|
The port of the login service on the target machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def python_path(self):
|
||||||
|
"""
|
||||||
|
Path to the Python interpreter on the target machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def private_key_file(self):
|
||||||
|
"""
|
||||||
|
Path to the SSH private key file to use to login.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def ssh_executable(self):
|
||||||
|
"""
|
||||||
|
Path to the SSH executable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def timeout(self):
|
||||||
|
"""
|
||||||
|
The generic timeout for all connections.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def ansible_ssh_timeout(self):
|
||||||
|
"""
|
||||||
|
The SSH-specific timeout for a connection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def ssh_args(self):
|
||||||
|
"""
|
||||||
|
The list of additional arguments that should be included in an SSH
|
||||||
|
invocation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def become_exe(self):
|
||||||
|
"""
|
||||||
|
The path to the executable implementing the become method on the remote
|
||||||
|
machine.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def sudo_args(self):
|
||||||
|
"""
|
||||||
|
The list of additional arguments that should be included in a become
|
||||||
|
invocation.
|
||||||
|
"""
|
||||||
|
# TODO: split out into sudo_args/become_args.
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_via(self):
|
||||||
|
"""
|
||||||
|
The value of the mitogen_via= variable for this connection. Indicates
|
||||||
|
the connection should be established via an intermediary.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_kind(self):
|
||||||
|
"""
|
||||||
|
The type of container to use with the "setns" transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_docker_path(self):
|
||||||
|
"""
|
||||||
|
The path to the "docker" program for the 'docker' transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_kubectl_path(self):
|
||||||
|
"""
|
||||||
|
The path to the "kubectl" program for the 'docker' transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_lxc_path(self):
|
||||||
|
"""
|
||||||
|
The path to the "lxc" program for the 'lxd' transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_lxc_attach_path(self):
|
||||||
|
"""
|
||||||
|
The path to the "lxc-attach" program for the 'lxc' transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_lxc_info_path(self):
|
||||||
|
"""
|
||||||
|
The path to the "lxc-info" program for the 'lxc' transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_machinectl_path(self):
|
||||||
|
"""
|
||||||
|
The path to the "machinectl" program for the 'setns' transport.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def mitogen_ssh_debug_level(self):
|
||||||
|
"""
|
||||||
|
The SSH debug level.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def extra_args(self):
|
||||||
|
"""
|
||||||
|
Connection-specific arguments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class PlayContextSpec(Spec):
|
||||||
|
"""
|
||||||
|
PlayContextSpec takes almost all its information as-is from Ansible's
|
||||||
|
PlayContext. It is used for normal connections and delegate_to connections,
|
||||||
|
and should always be accurate.
|
||||||
|
"""
|
||||||
|
def __init__(self, connection, play_context, transport, inventory_name):
|
||||||
|
self._connection = connection
|
||||||
|
self._play_context = play_context
|
||||||
|
self._transport = transport
|
||||||
|
self._inventory_name = inventory_name
|
||||||
|
|
||||||
|
def transport(self):
|
||||||
|
return self._transport
|
||||||
|
|
||||||
|
def inventory_name(self):
|
||||||
|
return self._inventory_name
|
||||||
|
|
||||||
|
def remote_addr(self):
|
||||||
|
return self._play_context.remote_addr
|
||||||
|
|
||||||
|
def remote_user(self):
|
||||||
|
return self._play_context.remote_user
|
||||||
|
|
||||||
|
def become(self):
|
||||||
|
return self._play_context.become
|
||||||
|
|
||||||
|
def become_method(self):
|
||||||
|
return self._play_context.become_method
|
||||||
|
|
||||||
|
def become_user(self):
|
||||||
|
return self._play_context.become_user
|
||||||
|
|
||||||
|
def become_pass(self):
|
||||||
|
return optional_secret(self._play_context.become_pass)
|
||||||
|
|
||||||
|
def password(self):
|
||||||
|
return optional_secret(self._play_context.password)
|
||||||
|
|
||||||
|
def port(self):
|
||||||
|
return self._play_context.port
|
||||||
|
|
||||||
|
def python_path(self):
|
||||||
|
return parse_python_path(
|
||||||
|
self._connection.get_task_var('ansible_python_interpreter')
|
||||||
|
)
|
||||||
|
|
||||||
|
def private_key_file(self):
|
||||||
|
return self._play_context.private_key_file
|
||||||
|
|
||||||
|
def ssh_executable(self):
|
||||||
|
return self._play_context.ssh_executable
|
||||||
|
|
||||||
|
def timeout(self):
|
||||||
|
return self._play_context.timeout
|
||||||
|
|
||||||
|
def ansible_ssh_timeout(self):
|
||||||
|
return (
|
||||||
|
self._connection.get_task_var('ansible_timeout') or
|
||||||
|
self._connection.get_task_var('ansible_ssh_timeout') or
|
||||||
|
self.timeout()
|
||||||
|
)
|
||||||
|
|
||||||
|
def ssh_args(self):
|
||||||
|
return [
|
||||||
|
mitogen.core.to_text(term)
|
||||||
|
for s in (
|
||||||
|
getattr(self._play_context, 'ssh_args', ''),
|
||||||
|
getattr(self._play_context, 'ssh_common_args', ''),
|
||||||
|
getattr(self._play_context, 'ssh_extra_args', '')
|
||||||
|
)
|
||||||
|
for term in ansible.utils.shlex.shlex_split(s or '')
|
||||||
|
]
|
||||||
|
|
||||||
|
def become_exe(self):
|
||||||
|
return self._play_context.become_exe
|
||||||
|
|
||||||
|
def sudo_args(self):
|
||||||
|
return [
|
||||||
|
mitogen.core.to_text(term)
|
||||||
|
for s in (
|
||||||
|
self._play_context.sudo_flags,
|
||||||
|
self._play_context.become_flags
|
||||||
|
)
|
||||||
|
for term in ansible.utils.shlex.shlex_split(s or '')
|
||||||
|
]
|
||||||
|
|
||||||
|
def mitogen_via(self):
|
||||||
|
return self._connection.get_task_var('mitogen_via')
|
||||||
|
|
||||||
|
def mitogen_kind(self):
|
||||||
|
return self._connection.get_task_var('mitogen_kind')
|
||||||
|
|
||||||
|
def mitogen_docker_path(self):
|
||||||
|
return self._connection.get_task_var('mitogen_docker_path')
|
||||||
|
|
||||||
|
def mitogen_kubectl_path(self):
|
||||||
|
return self._connection.get_task_var('mitogen_kubectl_path')
|
||||||
|
|
||||||
|
def mitogen_lxc_path(self):
|
||||||
|
return self._connection.get_task_var('mitogen_lxc_path')
|
||||||
|
|
||||||
|
def mitogen_lxc_attach_path(self):
|
||||||
|
return self._connection.get_task_var('mitogen_lxc_attach_path')
|
||||||
|
|
||||||
|
def mitogen_lxc_info_path(self):
|
||||||
|
return self._connection.get_task_var('mitogen_lxc_info_path')
|
||||||
|
|
||||||
|
def mitogen_machinectl_path(self):
|
||||||
|
return self._connection.get_task_var('mitogen_machinectl_path')
|
||||||
|
|
||||||
|
def mitogen_ssh_debug_level(self):
|
||||||
|
return self._connection.get_task_var('mitogen_ssh_debug_level')
|
||||||
|
|
||||||
|
def extra_args(self):
|
||||||
|
return self._connection.get_extra_args()
|
||||||
|
|
||||||
|
|
||||||
|
class MitogenViaSpec(Spec):
|
||||||
|
"""
|
||||||
|
MitogenViaSpec takes most of its information from the HostVars of the
|
||||||
|
running task. HostVars is a lightweight wrapper around VariableManager, so
|
||||||
|
it is better to say that VariableManager.get_vars() is the ultimate source
|
||||||
|
of MitogenViaSpec's information.
|
||||||
|
|
||||||
|
Due to this, mitogen_via= hosts must have all their configuration
|
||||||
|
information represented as host and group variables. We cannot use any
|
||||||
|
per-task configuration, as all that data belongs to the real target host.
|
||||||
|
|
||||||
|
Ansible uses all kinds of strange historical logic for calculating
|
||||||
|
variables, including making their precedence configurable. MitogenViaSpec
|
||||||
|
must ultimately reimplement all of that logic. It is likely that if you are
|
||||||
|
having a configruation problem with connection delegation, the answer to
|
||||||
|
your problem lies in the method implementations below!
|
||||||
|
"""
|
||||||
|
def __init__(self, inventory_name, host_vars,
|
||||||
|
become_method, become_user):
|
||||||
|
self._inventory_name = inventory_name
|
||||||
|
self._host_vars = host_vars
|
||||||
|
self._become_method = become_method
|
||||||
|
self._become_user = become_user
|
||||||
|
|
||||||
|
def transport(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_connection') or
|
||||||
|
C.DEFAULT_TRANSPORT
|
||||||
|
)
|
||||||
|
|
||||||
|
def inventory_name(self):
|
||||||
|
return self._inventory_name
|
||||||
|
|
||||||
|
def remote_addr(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_host') or
|
||||||
|
self._inventory_name
|
||||||
|
)
|
||||||
|
|
||||||
|
def remote_user(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_user') or
|
||||||
|
self._host_vars.get('ansible_ssh_user') or
|
||||||
|
C.DEFAULT_REMOTE_USER
|
||||||
|
)
|
||||||
|
|
||||||
|
def become(self):
|
||||||
|
return bool(self._become_user)
|
||||||
|
|
||||||
|
def become_method(self):
|
||||||
|
return self._become_method or C.DEFAULT_BECOME_METHOD
|
||||||
|
|
||||||
|
def become_user(self):
|
||||||
|
return self._become_user
|
||||||
|
|
||||||
|
def become_pass(self):
|
||||||
|
return optional_secret(
|
||||||
|
# TODO: Might have to come from PlayContext.
|
||||||
|
self._host_vars.get('ansible_become_password') or
|
||||||
|
self._host_vars.get('ansible_become_pass')
|
||||||
|
)
|
||||||
|
|
||||||
|
def password(self):
|
||||||
|
return optional_secret(
|
||||||
|
# TODO: Might have to come from PlayContext.
|
||||||
|
self._host_vars.get('ansible_ssh_pass') or
|
||||||
|
self._host_vars.get('ansible_password')
|
||||||
|
)
|
||||||
|
|
||||||
|
def port(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_port') or
|
||||||
|
C.DEFAULT_REMOTE_PORT
|
||||||
|
)
|
||||||
|
|
||||||
|
def python_path(self):
|
||||||
|
return parse_python_path(
|
||||||
|
self._host_vars.get('ansible_python_interpreter')
|
||||||
|
# This variable has no default for remote hosts. For local hosts it
|
||||||
|
# is sys.executable.
|
||||||
|
)
|
||||||
|
|
||||||
|
def private_key_file(self):
|
||||||
|
# TODO: must come from PlayContext too.
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_ssh_private_key_file') or
|
||||||
|
self._host_vars.get('ansible_private_key_file') or
|
||||||
|
C.DEFAULT_PRIVATE_KEY_FILE
|
||||||
|
)
|
||||||
|
|
||||||
|
def ssh_executable(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_ssh_executable') or
|
||||||
|
C.ANSIBLE_SSH_EXECUTABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
def timeout(self):
|
||||||
|
# TODO: must come from PlayContext too.
|
||||||
|
return C.DEFAULT_TIMEOUT
|
||||||
|
|
||||||
|
def ansible_ssh_timeout(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_timeout') or
|
||||||
|
self._host_vars.get('ansible_ssh_timeout') or
|
||||||
|
self.timeout()
|
||||||
|
)
|
||||||
|
|
||||||
|
def ssh_args(self):
|
||||||
|
return [
|
||||||
|
mitogen.core.to_text(term)
|
||||||
|
for s in (
|
||||||
|
(
|
||||||
|
self._host_vars.get('ansible_ssh_args') or
|
||||||
|
getattr(C, 'ANSIBLE_SSH_ARGS', None) or
|
||||||
|
os.environ.get('ANSIBLE_SSH_ARGS')
|
||||||
|
# TODO: ini entry. older versions.
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self._host_vars.get('ansible_ssh_common_args') or
|
||||||
|
os.environ.get('ANSIBLE_SSH_COMMON_ARGS')
|
||||||
|
# TODO: ini entry.
|
||||||
|
),
|
||||||
|
(
|
||||||
|
self._host_vars.get('ansible_ssh_extra_args') or
|
||||||
|
os.environ.get('ANSIBLE_SSH_EXTRA_ARGS')
|
||||||
|
# TODO: ini entry.
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for term in ansible.utils.shlex.shlex_split(s)
|
||||||
|
if s
|
||||||
|
]
|
||||||
|
|
||||||
|
def become_exe(self):
|
||||||
|
return (
|
||||||
|
self._host_vars.get('ansible_become_exe') or
|
||||||
|
C.DEFAULT_BECOME_EXE
|
||||||
|
)
|
||||||
|
|
||||||
|
def sudo_args(self):
|
||||||
|
return [
|
||||||
|
mitogen.core.to_text(term)
|
||||||
|
for s in (
|
||||||
|
self._host_vars.get('ansible_sudo_flags') or '',
|
||||||
|
self._host_vars.get('ansible_become_flags') or '',
|
||||||
|
)
|
||||||
|
for term in ansible.utils.shlex.shlex_split(s)
|
||||||
|
]
|
||||||
|
|
||||||
|
def mitogen_via(self):
|
||||||
|
return self._host_vars.get('mitogen_via')
|
||||||
|
|
||||||
|
def mitogen_kind(self):
|
||||||
|
return self._host_vars.get('mitogen_kind')
|
||||||
|
|
||||||
|
def mitogen_docker_path(self):
|
||||||
|
return self._host_vars.get('mitogen_docker_path')
|
||||||
|
|
||||||
|
def mitogen_kubectl_path(self):
|
||||||
|
return self._host_vars.get('mitogen_kubectl_path')
|
||||||
|
|
||||||
|
def mitogen_lxc_path(self):
|
||||||
|
return self.host_vars.get('mitogen_lxc_path')
|
||||||
|
|
||||||
|
def mitogen_lxc_attach_path(self):
|
||||||
|
return self._host_vars.get('mitogen_lxc_attach_path')
|
||||||
|
|
||||||
|
def mitogen_lxc_info_path(self):
|
||||||
|
return self._host_vars.get('mitogen_lxc_info_path')
|
||||||
|
|
||||||
|
def mitogen_machinectl_path(self):
|
||||||
|
return self._host_vars.get('mitogen_machinectl_path')
|
||||||
|
|
||||||
|
def mitogen_ssh_debug_level(self):
|
||||||
|
return self._host_vars.get('mitogen_ssh_debug_level')
|
||||||
|
|
||||||
|
def extra_args(self):
|
||||||
|
return [] # TODO
|
@ -1,12 +0,0 @@
|
|||||||
[connection-delegation-test]
|
|
||||||
cd-bastion
|
|
||||||
cd-rack11 mitogen_via=ssh-user@cd-bastion
|
|
||||||
cd-rack11a mitogen_via=root@cd-rack11
|
|
||||||
cd-rack11a-docker mitogen_via=docker-admin@cd-rack11a ansible_connection=docker
|
|
||||||
|
|
||||||
[connection-delegation-cycle]
|
|
||||||
# Create cycle with Docker container.
|
|
||||||
cdc-bastion mitogen_via=cdc-rack11a-docker
|
|
||||||
cdc-rack11 mitogen_via=ssh-user@cdc-bastion
|
|
||||||
cdc-rack11a mitogen_via=root@cdc-rack11
|
|
||||||
cdc-rack11a-docker mitogen_via=docker-admin@cdc-rack11a ansible_connection=docker
|
|
@ -0,0 +1,8 @@
|
|||||||
|
# vim: syntax=dosini
|
||||||
|
|
||||||
|
# When running the tests outside CI, make a single 'target' host which is the
|
||||||
|
# local machine.
|
||||||
|
target ansible_host=localhost
|
||||||
|
|
||||||
|
[test-targets]
|
||||||
|
target
|
@ -1,3 +1,6 @@
|
|||||||
|
# vim: syntax=dosini
|
||||||
|
|
||||||
|
# Used for manual testing.
|
||||||
k3
|
k3
|
||||||
|
|
||||||
[k3-x10]
|
[k3-x10]
|
@ -1,8 +0,0 @@
|
|||||||
localhost
|
|
||||||
target ansible_host=localhost
|
|
||||||
|
|
||||||
[test-targets]
|
|
||||||
target
|
|
||||||
|
|
||||||
[localhost-x10]
|
|
||||||
localhost-[01:10]
|
|
@ -0,0 +1,10 @@
|
|||||||
|
# vim: syntax=dosini
|
||||||
|
|
||||||
|
# This must be defined explicitly, otherwise _create_implicit_localhost()
|
||||||
|
# generates its own copy, which includes an ansible_python_interpreter that
|
||||||
|
# varies according to host machine.
|
||||||
|
localhost
|
||||||
|
|
||||||
|
# This is only used for manual testing.
|
||||||
|
[localhost-x10]
|
||||||
|
localhost-[01:10]
|
@ -1,10 +0,0 @@
|
|||||||
nessy
|
|
||||||
|
|
||||||
[nessy-x10]
|
|
||||||
nessy-[00:10]
|
|
||||||
|
|
||||||
[nessy-x20]
|
|
||||||
nessy-[00:20]
|
|
||||||
|
|
||||||
[nessy-x50]
|
|
||||||
nessy-[00:50]
|
|
@ -1,25 +0,0 @@
|
|||||||
z
|
|
||||||
|
|
||||||
[z-x10]
|
|
||||||
z-[01:10]
|
|
||||||
|
|
||||||
[z-x20]
|
|
||||||
z-[01:20]
|
|
||||||
|
|
||||||
[z-x50]
|
|
||||||
z-[01:50]
|
|
||||||
|
|
||||||
[z-x100]
|
|
||||||
z-[001:100]
|
|
||||||
|
|
||||||
[z-x200]
|
|
||||||
z-[001:200]
|
|
||||||
|
|
||||||
[z-x300]
|
|
||||||
z-[001:300]
|
|
||||||
|
|
||||||
[z-x400]
|
|
||||||
z-[001:400]
|
|
||||||
|
|
||||||
[z-x500]
|
|
||||||
z-[001:500]
|
|
@ -1,4 +1,5 @@
|
|||||||
- import_playbook: delegate_to_template.yml
|
- import_playbook: delegate_to_template.yml
|
||||||
|
- import_playbook: local_action.yml
|
||||||
- import_playbook: osa_container_standalone.yml
|
- import_playbook: osa_container_standalone.yml
|
||||||
- import_playbook: osa_delegate_to_self.yml
|
- import_playbook: osa_delegate_to_self.yml
|
||||||
- import_playbook: stack_construction.yml
|
- import_playbook: stack_construction.yml
|
@ -0,0 +1,77 @@
|
|||||||
|
# issue #340: Ensure templated delegate_to field works.
|
||||||
|
#
|
||||||
|
# Here we delegate from "test-targets" group to a templated "{{physical_host}}"
|
||||||
|
# variable, which contains "cd-normal-alias", which has a
|
||||||
|
# "mitogen_via=cd-alias", which in turn has an "ansible_host="alias-host".
|
||||||
|
#
|
||||||
|
# So the full stack should be:
|
||||||
|
# - First hop: hostname "alias-host", username "alias-user"
|
||||||
|
# - Second hop: hostname "cd-normal-alias"
|
||||||
|
|
||||||
|
- name: integration/connection_delegation/delegate_to_template.yml
|
||||||
|
vars:
|
||||||
|
physical_host: "cd-normal-alias"
|
||||||
|
physical_hosts: ["cd-normal-alias", "cd-normal-normal"]
|
||||||
|
hosts: test-targets
|
||||||
|
gather_facts: no
|
||||||
|
any_errors_fatal: true
|
||||||
|
tasks:
|
||||||
|
- meta: end_play
|
||||||
|
when: not is_mitogen
|
||||||
|
|
||||||
|
- mitogen_get_stack:
|
||||||
|
delegate_to: "{{ physical_host }}"
|
||||||
|
register: out
|
||||||
|
|
||||||
|
- assert_equal:
|
||||||
|
left: out.result
|
||||||
|
right: [
|
||||||
|
{
|
||||||
|
'kwargs': {
|
||||||
|
'check_host_keys': 'ignore',
|
||||||
|
'connect_timeout': 10,
|
||||||
|
'hostname': 'alias-host',
|
||||||
|
'identities_only': False,
|
||||||
|
'identity_file': null,
|
||||||
|
'password': null,
|
||||||
|
'port': null,
|
||||||
|
'python_path': null,
|
||||||
|
'ssh_args': [
|
||||||
|
'-o',
|
||||||
|
'ForwardAgent=yes',
|
||||||
|
'-o',
|
||||||
|
'ControlMaster=auto',
|
||||||
|
'-o',
|
||||||
|
'ControlPersist=60s',
|
||||||
|
],
|
||||||
|
'ssh_debug_level': null,
|
||||||
|
'ssh_path': 'ssh',
|
||||||
|
'username': 'alias-user',
|
||||||
|
},
|
||||||
|
'method': 'ssh',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'kwargs': {
|
||||||
|
'check_host_keys': 'ignore',
|
||||||
|
'connect_timeout': 10,
|
||||||
|
'hostname': 'cd-normal-alias',
|
||||||
|
'identities_only': False,
|
||||||
|
'identity_file': null,
|
||||||
|
'password': null,
|
||||||
|
'port': null,
|
||||||
|
'python_path': null,
|
||||||
|
'ssh_args': [
|
||||||
|
'-o',
|
||||||
|
'ForwardAgent=yes',
|
||||||
|
'-o',
|
||||||
|
'ControlMaster=auto',
|
||||||
|
'-o',
|
||||||
|
'ControlPersist=60s',
|
||||||
|
],
|
||||||
|
'ssh_debug_level': null,
|
||||||
|
'ssh_path': 'ssh',
|
||||||
|
'username': 'ansible-cfg-remote-user',
|
||||||
|
},
|
||||||
|
'method': 'ssh',
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
# issue #251: local_action with mitogen_via= builds wrong stack.
|
||||||
|
|
||||||
|
- hosts: cd-newuser-normal-normal
|
||||||
|
tasks:
|
||||||
|
- meta: end_play
|
||||||
|
when: not is_mitogen
|
||||||
|
|
||||||
|
- local_action: mitogen_get_stack
|
||||||
|
become: true
|
||||||
|
register: out
|
||||||
|
|
||||||
|
- assert_equal:
|
||||||
|
left: out.result
|
||||||
|
right: [
|
||||||
|
{
|
||||||
|
'kwargs': {
|
||||||
|
'python_path': null
|
||||||
|
},
|
||||||
|
'method': 'local',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'enable_lru': true,
|
||||||
|
'kwargs': {
|
||||||
|
'connect_timeout': 10,
|
||||||
|
'python_path': null,
|
||||||
|
'password': null,
|
||||||
|
'username': 'root',
|
||||||
|
'sudo_path': null,
|
||||||
|
'sudo_args': ['-H', '-S', '-n'],
|
||||||
|
},
|
||||||
|
'method': 'sudo',
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,28 @@
|
|||||||
|
# Verify one OSA-style container has the correct config.
|
||||||
|
|
||||||
|
- name: integration/connection_delegation/osa_container_standalone.yml
|
||||||
|
hosts: dtc-container-1
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- meta: end_play
|
||||||
|
when: not is_mitogen
|
||||||
|
|
||||||
|
- mitogen_get_stack:
|
||||||
|
register: out
|
||||||
|
|
||||||
|
- assert_equal:
|
||||||
|
left: out.result
|
||||||
|
right: [
|
||||||
|
{
|
||||||
|
'kwargs': {
|
||||||
|
'container': 'dtc-container-1',
|
||||||
|
'docker_path': null,
|
||||||
|
'kind': 'lxc',
|
||||||
|
'lxc_info_path': null,
|
||||||
|
'machinectl_path': null,
|
||||||
|
'python_path': ['/usr/bin/python'],
|
||||||
|
'username': null,
|
||||||
|
},
|
||||||
|
'method': 'setns',
|
||||||
|
},
|
||||||
|
]
|
@ -0,0 +1,32 @@
|
|||||||
|
# OSA: Verify delegating the connection back to the container succeeds.
|
||||||
|
|
||||||
|
- name: integration/connection_delegation/osa_delegate_to_self.yml
|
||||||
|
hosts: osa-container-1
|
||||||
|
vars:
|
||||||
|
target: osa-container-1
|
||||||
|
gather_facts: false
|
||||||
|
tasks:
|
||||||
|
- meta: end_play
|
||||||
|
when: not is_mitogen
|
||||||
|
|
||||||
|
- mitogen_get_stack:
|
||||||
|
delegate_to: "{{target}}"
|
||||||
|
register: out
|
||||||
|
|
||||||
|
- assert_equal:
|
||||||
|
left: out.result
|
||||||
|
right: [
|
||||||
|
{
|
||||||
|
'kwargs': {
|
||||||
|
'container': 'osa-container-1',
|
||||||
|
'docker_path': null,
|
||||||
|
'kind': 'lxc',
|
||||||
|
'lxc_info_path': null,
|
||||||
|
'lxc_path': null,
|
||||||
|
'machinectl_path': null,
|
||||||
|
'python_path': null,
|
||||||
|
'username': 'ansible-cfg-remote-user',
|
||||||
|
},
|
||||||
|
'method': 'setns',
|
||||||
|
},
|
||||||
|
]
|
@ -1,69 +0,0 @@
|
|||||||
# Ensure templated delegate_to field works.
|
|
||||||
|
|
||||||
- name: integration/delegation/delegate_to_template.yml
|
|
||||||
vars:
|
|
||||||
physical_host: "cd-normal-alias"
|
|
||||||
physical_hosts: ["cd-normal-alias", "cd-normal-normal"]
|
|
||||||
hosts: test-targets
|
|
||||||
gather_facts: no
|
|
||||||
any_errors_fatal: true
|
|
||||||
tasks:
|
|
||||||
- meta: end_play
|
|
||||||
when: not is_mitogen
|
|
||||||
|
|
||||||
- mitogen_get_stack:
|
|
||||||
delegate_to: "{{ physical_host }}"
|
|
||||||
register: out
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that: |
|
|
||||||
out.result == [
|
|
||||||
{
|
|
||||||
'kwargs': {
|
|
||||||
'check_host_keys': 'ignore',
|
|
||||||
'connect_timeout': 10,
|
|
||||||
'hostname': 'alias-host',
|
|
||||||
'identities_only': False,
|
|
||||||
'identity_file': None,
|
|
||||||
'password': None,
|
|
||||||
'port': None,
|
|
||||||
'python_path': None,
|
|
||||||
'ssh_args': [
|
|
||||||
'-o',
|
|
||||||
'ForwardAgent=yes',
|
|
||||||
'-o',
|
|
||||||
'ControlMaster=auto',
|
|
||||||
'-o',
|
|
||||||
'ControlPersist=60s',
|
|
||||||
],
|
|
||||||
'ssh_debug_level': None,
|
|
||||||
'ssh_path': 'ssh',
|
|
||||||
'username': 'alias-user',
|
|
||||||
},
|
|
||||||
'method': 'ssh',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'kwargs': {
|
|
||||||
'check_host_keys': 'ignore',
|
|
||||||
'connect_timeout': 10,
|
|
||||||
'hostname': 'cd-normal-alias',
|
|
||||||
'identities_only': False,
|
|
||||||
'identity_file': None,
|
|
||||||
'password': None,
|
|
||||||
'port': None,
|
|
||||||
'python_path': None,
|
|
||||||
'ssh_args': [
|
|
||||||
'-o',
|
|
||||||
'ForwardAgent=yes',
|
|
||||||
'-o',
|
|
||||||
'ControlMaster=auto',
|
|
||||||
'-o',
|
|
||||||
'ControlPersist=60s',
|
|
||||||
],
|
|
||||||
'ssh_debug_level': None,
|
|
||||||
'ssh_path': 'ssh',
|
|
||||||
'username': None,
|
|
||||||
},
|
|
||||||
'method': 'ssh',
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,28 +0,0 @@
|
|||||||
# Verify one OSA-style container has the correct config.
|
|
||||||
|
|
||||||
- name: integration/delegation/container_standalone.yml
|
|
||||||
hosts: dtc-container-1
|
|
||||||
gather_facts: false
|
|
||||||
tasks:
|
|
||||||
- meta: end_play
|
|
||||||
when: not is_mitogen
|
|
||||||
|
|
||||||
- mitogen_get_stack:
|
|
||||||
register: out
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that: |
|
|
||||||
out.result == [
|
|
||||||
{
|
|
||||||
'kwargs': {
|
|
||||||
'container': 'dtc-container-1',
|
|
||||||
'docker_path': None,
|
|
||||||
'kind': 'lxc',
|
|
||||||
'lxc_info_path': None,
|
|
||||||
'machinectl_path': None,
|
|
||||||
'python_path': ['/usr/bin/python'],
|
|
||||||
'username': None,
|
|
||||||
},
|
|
||||||
'method': 'setns',
|
|
||||||
},
|
|
||||||
]
|
|
@ -1,32 +0,0 @@
|
|||||||
# OSA: Verify delegating the connection back to the container succeeds.
|
|
||||||
|
|
||||||
- name: integration/delegation/osa_delegate_to_self.yml
|
|
||||||
hosts: osa-container-1
|
|
||||||
vars:
|
|
||||||
target: osa-container-1
|
|
||||||
gather_facts: false
|
|
||||||
tasks:
|
|
||||||
- meta: end_play
|
|
||||||
when: not is_mitogen
|
|
||||||
|
|
||||||
- mitogen_get_stack:
|
|
||||||
delegate_to: "{{target}}"
|
|
||||||
register: out
|
|
||||||
|
|
||||||
- assert:
|
|
||||||
that: |
|
|
||||||
out.result == [
|
|
||||||
{
|
|
||||||
'kwargs': {
|
|
||||||
'container': 'osa-container-1',
|
|
||||||
'docker_path': None,
|
|
||||||
'kind': 'lxc',
|
|
||||||
'lxc_info_path': None,
|
|
||||||
'lxc_path': None,
|
|
||||||
'machinectl_path': None,
|
|
||||||
'python_path': None,
|
|
||||||
'username': None,
|
|
||||||
},
|
|
||||||
'method': 'setns',
|
|
||||||
},
|
|
||||||
]
|
|
@ -0,0 +1,70 @@
|
|||||||
|
#
|
||||||
|
# Print data structure diff on assertion failure.
|
||||||
|
#
|
||||||
|
# assert_equal: left=some.result right={1:2}
|
||||||
|
#
|
||||||
|
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
import inspect
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import ansible.template
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.plugins.action import ActionBase
|
||||||
|
from ansible.module_utils.six import string_types
|
||||||
|
|
||||||
|
|
||||||
|
TEMPLATE_KWARGS = {}
|
||||||
|
|
||||||
|
_argspec = inspect.getargspec(ansible.template.Templar.template)
|
||||||
|
if 'bare_deprecated' in _argspec.args:
|
||||||
|
TEMPLATE_KWARGS['bare_deprecated'] = False
|
||||||
|
|
||||||
|
|
||||||
|
class TestCase(unittest2.TestCase):
|
||||||
|
def runTest(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def text_diff(a, b):
|
||||||
|
tc = TestCase()
|
||||||
|
tc.maxDiff = None
|
||||||
|
try:
|
||||||
|
tc.assertEqual(a, b)
|
||||||
|
return None
|
||||||
|
except AssertionError as e:
|
||||||
|
return str(e)
|
||||||
|
|
||||||
|
|
||||||
|
class ActionModule(ActionBase):
|
||||||
|
''' Fail with custom message '''
|
||||||
|
|
||||||
|
TRANSFERS_FILES = False
|
||||||
|
_VALID_ARGS = frozenset(('left', 'right'))
|
||||||
|
|
||||||
|
def template(self, obj):
|
||||||
|
return self._templar.template(
|
||||||
|
obj,
|
||||||
|
convert_bare=True,
|
||||||
|
**TEMPLATE_KWARGS
|
||||||
|
)
|
||||||
|
|
||||||
|
def run(self, tmp=None, task_vars=None):
|
||||||
|
result = super(ActionModule, self).run(tmp, task_vars or {})
|
||||||
|
left = self.template(self._task.args['left'])
|
||||||
|
right = self.template(self._task.args['right'])
|
||||||
|
|
||||||
|
diff = text_diff(left, right)
|
||||||
|
if diff is None:
|
||||||
|
return {
|
||||||
|
'changed': False
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'changed': False,
|
||||||
|
'failed': True,
|
||||||
|
'msg': diff,
|
||||||
|
'_ansible_verbose_always': True,
|
||||||
|
}
|
@ -1,22 +0,0 @@
|
|||||||
"""
|
|
||||||
Fetch the connection configuration stack that would be used to connect to a
|
|
||||||
target, without actually connecting to it.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import ansible_mitogen.connection
|
|
||||||
|
|
||||||
from ansible.plugins.action import ActionBase
|
|
||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionBase):
|
|
||||||
def run(self, tmp=None, task_vars=None):
|
|
||||||
if not isinstance(self._connection,
|
|
||||||
ansible_mitogen.connection.Connection):
|
|
||||||
return {
|
|
||||||
'skipped': True,
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'changed': True,
|
|
||||||
'result': self._connection._build_stack(),
|
|
||||||
}
|
|
@ -0,0 +1 @@
|
|||||||
|
../../../../ansible_mitogen/plugins/action/mitogen_get_stack.py
|
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import testlib
|
||||||
|
import mitogen.core
|
||||||
|
|
||||||
|
|
||||||
|
class ConstructorTest(testlib.TestCase):
|
||||||
|
klass = mitogen.core.Error
|
||||||
|
|
||||||
|
def test_literal_no_format(self):
|
||||||
|
e = self.klass('error')
|
||||||
|
self.assertEquals(e.args[0], 'error')
|
||||||
|
self.assertTrue(isinstance(e.args[0], mitogen.core.UnicodeType))
|
||||||
|
|
||||||
|
def test_literal_format_chars_present(self):
|
||||||
|
e = self.klass('error%s')
|
||||||
|
self.assertEquals(e.args[0], 'error%s')
|
||||||
|
self.assertTrue(isinstance(e.args[0], mitogen.core.UnicodeType))
|
||||||
|
|
||||||
|
def test_format(self):
|
||||||
|
e = self.klass('error%s', 123)
|
||||||
|
self.assertEquals(e.args[0], 'error123')
|
||||||
|
self.assertTrue(isinstance(e.args[0], mitogen.core.UnicodeType))
|
||||||
|
|
||||||
|
def test_bytes_to_unicode(self):
|
||||||
|
e = self.klass(mitogen.core.b('error'))
|
||||||
|
self.assertEquals(e.args[0], 'error')
|
||||||
|
self.assertTrue(isinstance(e.args[0], mitogen.core.UnicodeType))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest2.main()
|
@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import mock
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import mitogen.core
|
||||||
|
import mitogen.parent
|
||||||
|
|
||||||
|
import testlib
|
||||||
|
|
||||||
|
|
||||||
|
class HasParentAuthorityTest(testlib.TestCase):
|
||||||
|
func = staticmethod(mitogen.core.has_parent_authority)
|
||||||
|
|
||||||
|
def call(self, auth_id):
|
||||||
|
msg = mitogen.core.Message(auth_id=auth_id)
|
||||||
|
return self.func(msg)
|
||||||
|
|
||||||
|
@mock.patch('mitogen.context_id', 5555)
|
||||||
|
@mock.patch('mitogen.parent_ids', [111, 222])
|
||||||
|
def test_okay(self):
|
||||||
|
self.assertFalse(self.call(0))
|
||||||
|
self.assertTrue(self.call(5555))
|
||||||
|
self.assertTrue(self.call(111))
|
||||||
|
|
||||||
|
|
||||||
|
class IsImmediateChildTest(testlib.TestCase):
|
||||||
|
func = staticmethod(mitogen.core.has_parent_authority)
|
||||||
|
|
||||||
|
def call(self, auth_id, remote_id):
|
||||||
|
msg = mitogen.core.Message(auth_id=auth_id)
|
||||||
|
stream = mock.Mock(remote_id=remote_id)
|
||||||
|
return self.func(msg, stream)
|
||||||
|
|
||||||
|
def test_okay(self):
|
||||||
|
self.assertFalse(0, 1)
|
||||||
|
self.assertTrue(1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest2.main()
|
@ -0,0 +1,45 @@
|
|||||||
|
|
||||||
|
import unittest2
|
||||||
|
|
||||||
|
import testlib
|
||||||
|
import mitogen.core
|
||||||
|
|
||||||
|
|
||||||
|
class Thing():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ListenFireTest(testlib.TestCase):
|
||||||
|
def test_no_args(self):
|
||||||
|
thing = Thing()
|
||||||
|
latch = mitogen.core.Latch()
|
||||||
|
mitogen.core.listen(thing, 'event',
|
||||||
|
lambda: latch.put('event fired'))
|
||||||
|
|
||||||
|
mitogen.core.fire(thing, 'event')
|
||||||
|
self.assertEquals('event fired', latch.get())
|
||||||
|
self.assertTrue(latch.empty())
|
||||||
|
|
||||||
|
def test_with_args(self):
|
||||||
|
thing = Thing()
|
||||||
|
latch = mitogen.core.Latch()
|
||||||
|
mitogen.core.listen(thing, 'event', latch.put)
|
||||||
|
mitogen.core.fire(thing, 'event', 'event fired')
|
||||||
|
self.assertEquals('event fired', latch.get())
|
||||||
|
self.assertTrue(latch.empty())
|
||||||
|
|
||||||
|
def test_two_listeners(self):
|
||||||
|
thing = Thing()
|
||||||
|
latch = mitogen.core.Latch()
|
||||||
|
latch2 = mitogen.core.Latch()
|
||||||
|
mitogen.core.listen(thing, 'event', latch.put)
|
||||||
|
mitogen.core.listen(thing, 'event', latch2.put)
|
||||||
|
mitogen.core.fire(thing, 'event', 'event fired')
|
||||||
|
self.assertEquals('event fired', latch.get())
|
||||||
|
self.assertEquals('event fired', latch2.get())
|
||||||
|
self.assertTrue(latch.empty())
|
||||||
|
self.assertTrue(latch2.empty())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest2.main()
|
Loading…
Reference in New Issue