setns: support changing user.
To match existing third party plugin.
This commit is contained in:
parent
947d35649c
commit
7c5bbc5168
|
@ -112,6 +112,26 @@ def _connect_lxc(spec):
|
|||
}
|
||||
|
||||
|
||||
def _connect_machinectl(spec):
|
||||
return _connect_setns(dict(spec, mitogen_kind='machinectl'))
|
||||
|
||||
|
||||
def _connect_setns(spec):
|
||||
print 'ULTRAFLEEN', spec['remote_addr'], spec['remote_user']
|
||||
return {
|
||||
'method': 'setns',
|
||||
'kwargs': {
|
||||
'container': spec['remote_addr'],
|
||||
'username': spec['remote_user'],
|
||||
'python_path': spec['python_path'],
|
||||
'kind': spec['mitogen_kind'],
|
||||
'docker_path': spec['mitogen_docker_path'],
|
||||
'lxc_info_path': spec['mitogen_lxc_info_path'],
|
||||
'machinectl_path': spec['mitogen_machinectl_path'],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def _connect_sudo(spec):
|
||||
return {
|
||||
'method': 'sudo',
|
||||
|
@ -132,6 +152,8 @@ CONNECTION_METHOD = {
|
|||
'local': _connect_local,
|
||||
'lxc': _connect_lxc,
|
||||
'lxd': _connect_lxc,
|
||||
'machinectl': _connect_machinectl,
|
||||
'setns': _connect_setns,
|
||||
'ssh': _connect_ssh,
|
||||
'sudo': _connect_sudo,
|
||||
}
|
||||
|
@ -178,6 +200,10 @@ def config_from_play_context(transport, inventory_name, connection):
|
|||
for term in shlex.split(s or '')
|
||||
],
|
||||
'mitogen_via': connection.mitogen_via,
|
||||
'mitogen_kind': connection.mitogen_kind,
|
||||
'mitogen_docker_path': connection.mitogen_docker_path,
|
||||
'mitogen_lxc_info_path': connection.mitogen_lxc_info_path,
|
||||
'mitogen_machinectl_path': connection.mitogen_machinectl_path,
|
||||
}
|
||||
|
||||
|
||||
|
@ -202,6 +228,10 @@ def config_from_hostvars(transport, inventory_name, connection,
|
|||
'private_key_file': (hostvars.get('ansible_ssh_private_key_file') or
|
||||
hostvars.get('ansible_private_key_file')),
|
||||
'mitogen_via': hostvars.get('mitogen_via'),
|
||||
'mitogen_kind': hostvars.get('mitogen_kind'),
|
||||
'mitogen_docker_path': hostvars.get('mitogen_docker_path'),
|
||||
'mitogen_lxc_info_path': hostvars.get('mitogen_lxc_info_path'),
|
||||
'mitogen_machinectl_path': hostvars.get('mitogen_machinctl_path'),
|
||||
})
|
||||
|
||||
|
||||
|
@ -232,6 +262,18 @@ class Connection(ansible.plugins.connection.ConnectionBase):
|
|||
#: Set to 'mitogen_via' by on_action_run().
|
||||
mitogen_via = None
|
||||
|
||||
#: Set to 'mitogen_kind' by on_action_run().
|
||||
mitogen_kind = None
|
||||
|
||||
#: Set to 'mitogen_docker_path' by on_action_run().
|
||||
mitogen_docker_path = None
|
||||
|
||||
#: Set to 'mitogen_lxc_info_path' by on_action_run().
|
||||
mitogen_lxc_info_path = None
|
||||
|
||||
#: Set to 'mitogen_lxc_info_path' by on_action_run().
|
||||
mitogen_machinectl_path = None
|
||||
|
||||
#: Set to 'inventory_hostname' by on_action_run().
|
||||
inventory_hostname = None
|
||||
|
||||
|
@ -267,6 +309,10 @@ class Connection(ansible.plugins.connection.ConnectionBase):
|
|||
self.python_path = task_vars.get('ansible_python_interpreter',
|
||||
'/usr/bin/python')
|
||||
self.mitogen_via = task_vars.get('mitogen_via')
|
||||
self.mitogen_kind = task_vars.get('mitogen_kind')
|
||||
self.mitogen_docker_path = task_vars.get('mitogen_docker_path')
|
||||
self.mitogen_lxc_info_path = task_vars.get('mitogen_lxc_info_path')
|
||||
self.mitogen_machinectl_path = task_vars.get('mitogen_machinectl_path')
|
||||
self.inventory_hostname = task_vars['inventory_hostname']
|
||||
self.host_vars = task_vars['hostvars']
|
||||
self.close(new_task=True)
|
||||
|
|
|
@ -63,12 +63,11 @@ def wrap_action_loader__get(name, *args, **kwargs):
|
|||
|
||||
def wrap_connection_loader__get(name, play_context, new_stdin, **kwargs):
|
||||
"""
|
||||
While the mitogen strategy is active, rewrite connection_loader.get() calls
|
||||
for the 'ssh' and 'local' transports into corresponding requests for the
|
||||
'mitogen' connection type, passing the original transport name into it as
|
||||
an argument, so that it can emulate the original type.
|
||||
While the strategy is active, rewrite connection_loader.get() calls for
|
||||
some transports into requests for a compatible Mitogen transport.
|
||||
"""
|
||||
if name in ('ssh', 'local', 'docker', 'lxc', 'lxd', 'jail'):
|
||||
if name in ('docker', 'jail', 'local', 'lxc',
|
||||
'lxd', 'machinectl', 'setns', 'ssh'):
|
||||
name = 'mitogen_' + name
|
||||
return connection_loader__get(name, play_context, new_stdin, **kwargs)
|
||||
|
||||
|
|
|
@ -159,7 +159,7 @@ def transfer_file(context, in_path, out_path, sync=False, set_owner=False):
|
|||
prefix='.ansible_mitogen_transfer-',
|
||||
dir=os.path.dirname(out_path))
|
||||
fp = os.fdopen(fd, 'wb', mitogen.core.CHUNK_SIZE)
|
||||
LOG.debug('transfer_file(%r) tempory file: %s', out_path, tmp_path)
|
||||
LOG.debug('transfer_file(%r) temporary file: %s', out_path, tmp_path)
|
||||
|
||||
try:
|
||||
try:
|
||||
|
|
|
@ -455,6 +455,20 @@ connection delegation is supported.
|
|||
* ``ansible_user``: Name of user within the container to execute as.
|
||||
|
||||
|
||||
Machinectl
|
||||
~~~~~~~~~~
|
||||
|
||||
Behaves like `machinectl
|
||||
<https://github.com/BaxterStockman/ansible-connection-machinectl>`_ except
|
||||
connection delegation is supported. This is a lightweight wrapper around the
|
||||
``setns`` method below.
|
||||
|
||||
* ``ansible_host``: Name of Docker container (default: inventory hostname).
|
||||
* ``ansible_user``: Name of user within the container to execute as.
|
||||
* ``mitogen_machinectl_path``: path to ``machinectl`` command if not available
|
||||
as ``/bin/machinectl``.
|
||||
|
||||
|
||||
Sudo
|
||||
~~~~
|
||||
|
||||
|
@ -478,9 +492,10 @@ supported.
|
|||
Utility programs must still be installed to discover the PID of the container's
|
||||
root process.
|
||||
|
||||
* ``mitogen_container_kind``: one of ``docker``, ``lxc`` or ``machinectl``.
|
||||
* ``mitogen_kind``: one of ``docker``, ``lxc`` or ``machinectl``.
|
||||
* ``ansible_host``: Name of container as it is known to the corresponding tool
|
||||
(default: inventory hostname).
|
||||
* ``ansible_user``: Name of user within the container to execute as.
|
||||
* ``mitogen_docker_path``: path to Docker if not available on the system path.
|
||||
* ``mitogen_lxc_info_path``: path to ``lxc-info`` command if not available as
|
||||
``/usr/bin/lxc-info``.
|
||||
|
@ -537,6 +552,7 @@ rather than the LXC Python bindings, as is usual with the ``lxc`` method.
|
|||
|
||||
The ``lxc-attach`` command must be available on the host machine.
|
||||
|
||||
* ``ansible_python_interpreter``
|
||||
* ``ansible_host``: Name of LXC container (default: inventory hostname).
|
||||
|
||||
|
||||
|
|
|
@ -484,9 +484,9 @@ def _proxy_connect(name, method_name, kwargs, econtext):
|
|||
return {
|
||||
'id': None,
|
||||
'name': None,
|
||||
'msg': '%s (error occurred on host %s)' % (
|
||||
sys.exc_info()[1],
|
||||
'msg': 'error occurred on host %s: %s' % (
|
||||
socket.gethostname(),
|
||||
sys.exc_info()[1],
|
||||
),
|
||||
}
|
||||
|
||||
|
|
|
@ -27,9 +27,12 @@
|
|||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import ctypes
|
||||
import grp
|
||||
import logging
|
||||
import os
|
||||
import pwd
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import mitogen.core
|
||||
import mitogen.parent
|
||||
|
@ -53,11 +56,16 @@ def setns(kind, fd):
|
|||
|
||||
|
||||
def _run_command(args):
|
||||
proc = subprocess.Popen(
|
||||
args=args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT
|
||||
)
|
||||
argv = mitogen.parent.Argv(args)
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
args=args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT
|
||||
)
|
||||
except OSError:
|
||||
e = sys.exc_info()[1]
|
||||
raise Error('could not execute %s: %s', argv, e)
|
||||
|
||||
output, _ = proc.communicate()
|
||||
if not proc.returncode:
|
||||
|
@ -97,6 +105,7 @@ def get_machinectl_pid(path, name):
|
|||
|
||||
class Stream(mitogen.parent.Stream):
|
||||
container = None
|
||||
username = None
|
||||
kind = None
|
||||
docker_path = 'docker'
|
||||
lxc_info_path = 'lxc-info'
|
||||
|
@ -108,7 +117,7 @@ class Stream(mitogen.parent.Stream):
|
|||
'machinectl': ('machinectl_path', get_machinectl_pid),
|
||||
}
|
||||
|
||||
def construct(self, container, kind, docker_path=None,
|
||||
def construct(self, container, kind, username=None, docker_path=None,
|
||||
lxc_info_path=None, machinectl_path=None, **kwargs):
|
||||
super(Stream, self).construct(**kwargs)
|
||||
if kind not in self.GET_LEADER_BY_KIND:
|
||||
|
@ -116,6 +125,8 @@ class Stream(mitogen.parent.Stream):
|
|||
|
||||
self.container = container
|
||||
self.kind = kind
|
||||
if username:
|
||||
self.username = username
|
||||
if docker_path:
|
||||
self.docker_path = docker_path
|
||||
if lxc_info_path:
|
||||
|
@ -140,12 +151,43 @@ class Stream(mitogen.parent.Stream):
|
|||
except Exception, e:
|
||||
raise Error(str(e))
|
||||
|
||||
os.chroot('/proc/%s/root' % (self.leader_pid,))
|
||||
os.chdir('/proc/%s/root' % (self.leader_pid,))
|
||||
os.chroot('.')
|
||||
os.chdir('/')
|
||||
for fp in ns_fps:
|
||||
setns(fp.name, fp.fileno())
|
||||
fp.close()
|
||||
|
||||
for sym in 'endpwent', 'endgrent', 'endspent', 'endsgent':
|
||||
try:
|
||||
getattr(LIBC, sym)()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if self.username:
|
||||
try:
|
||||
os.setgroups([grent.gr_gid
|
||||
for grent in grp.getgrall()
|
||||
if self.username in grent.gr_mem])
|
||||
pwent = pwd.getpwnam(self.username)
|
||||
os.setreuid(pwent.pw_uid, pwent.pw_uid)
|
||||
# shadow-4.4/libmisc/setupenv.c. Not done: MAIL, PATH
|
||||
os.environ.update({
|
||||
'HOME': pwent.pw_dir,
|
||||
'SHELL': pwent.pw_shell or '/bin/sh',
|
||||
'LOGNAME': self.username,
|
||||
'USER': self.username,
|
||||
})
|
||||
if ((os.path.exists(pwent.pw_dir) and
|
||||
os.access(pwent.pw_dir, os.X_OK))):
|
||||
os.chdir(pwent.pw_dir)
|
||||
except Exception:
|
||||
e = sys.exc_info()[1]
|
||||
raise Error(self.username_msg, self.username, self.container,
|
||||
type(e).__name__, e)
|
||||
|
||||
username_msg = 'while transitioning to user %r in container %r: %s: %s'
|
||||
|
||||
def get_boot_command(self):
|
||||
# With setns(CLONE_NEWPID), new children of the caller receive a new
|
||||
# PID namespace, however the caller's namespace won't change. That
|
||||
|
|
|
@ -68,7 +68,7 @@ class StreamErrorTest(testlib.RouterMixin, testlib.TestCase):
|
|||
connect_timeout=3,
|
||||
)
|
||||
)
|
||||
self.assertEquals(e.args[0], "EOF on stream; last 300 bytes received: ''")
|
||||
self.assertTrue("EOF on stream; last 300 bytes received: ''" in e.args[0])
|
||||
|
||||
def test_direct_enoent(self):
|
||||
e = self.assertRaises(mitogen.core.StreamError,
|
||||
|
@ -89,8 +89,8 @@ class StreamErrorTest(testlib.RouterMixin, testlib.TestCase):
|
|||
connect_timeout=3,
|
||||
)
|
||||
)
|
||||
prefix = 'Child start failed: [Errno 2] No such file or directory.'
|
||||
self.assertTrue(e.args[0].startswith(prefix))
|
||||
s = 'Child start failed: [Errno 2] No such file or directory.'
|
||||
self.assertTrue(s in e.args[0])
|
||||
|
||||
|
||||
class ContextTest(testlib.RouterMixin, unittest2.TestCase):
|
||||
|
|
Loading…
Reference in New Issue