From c926df92e4c5fdfd652c62a068e55e9a3ecad161 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Fri, 18 Aug 2017 16:52:16 +0200 Subject: [PATCH 1/2] Add NSenter tunnel that allows to enter in any linux namespace container --- README.rst | 3 ++- chopsticks/tunnel.py | 59 ++++++++++++++++++++++++++++++++++++++++++++ doc/tunnels.rst | 6 +++++ 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index ecbec08..e19d9d3 100644 --- a/README.rst +++ b/README.rst @@ -11,7 +11,8 @@ remote hosts over SSH. Naturally this is agentless and nothing needs to be installed on the remote host except Python and an SSH agent. -It also has support for executing code in Docker containers. +It also has support for executing code in Docker or any linux namespaces based +containers. It's perhaps best compared to Ansible or Fabric, but has some clever transport magic which means it's very easy to develop with: you just write Python diff --git a/chopsticks/tunnel.py b/chopsticks/tunnel.py index 626c831..1379594 100644 --- a/chopsticks/tunnel.py +++ b/chopsticks/tunnel.py @@ -628,6 +628,65 @@ def close(self): self.proc.wait() +class NSEnter(SubprocessTunnel): + """A tunnel to a process on the same host, launched with nsenter. + + This is a generic tunnel for all containers using linux namespaces + such as docker, systemd-nspawn, rkt, ... that can be spawned as a + subprocess of any given process id. This tunnel uses sudo hence it + requires the same passworless setup than the Sudo tunnel plus the + ``nsenter`` command line utility found in ``linux-utils`` package. + + + Args: + pid (int): the process id that will be used as parent process for the + python interpreter + + Kwargs: + keep_environ (bool): specify if the caller's environment should be kept + + namespaces (list): optional list of namespaces to enter, valid + namespaces are: 'mount', 'uts', 'ipc', 'net', 'pid' + Defautls to all namespaces. + """ + + def __init__(self, pid, keep_environ=False, namespaces=None): + self.pid = int(pid) + self.host = 'PID#%s@localhost' % self.pid + self.keep_environ = keep_environ + valid_namespaces = set(('mount', 'uts', 'ipc', 'net', 'pid')) + if namespaces and not set(namespaces).issubset(valid_namespaces): + raise ValueError("Invalid namespace %s" % + (namespaces - valid_namespaces)) + self.namespaces = namespaces or valid_namespaces + super(NSEnter, self).__init__() + + def cmd_args(self): + uid = os.getuid() + gid = os.getegid() + args = [] + if uid != 0: + args += ['sudo', '--non-interactive'] + args += ['nsenter', '-t', str(self.pid)] + args += ['--%s' % name for name in self.namespaces] + args += ['-S', str(uid), '-G', str(gid)] + if not self.keep_environ: + env = self.get_environ() + args += ['--', 'env', '-i', '-'] + env + else: + args.append('--') + args += super(NSEnter, self).cmd_args() + return args + + def get_environ(self): + with open('/proc/%d/environ' % self.pid, 'r') as f: + return [var for var in f.read().split('\x00') if var.strip()] + + def close(self): + self.wpipe.close() + self.proc.wait() + + class Docker(SubprocessTunnel): """A tunnel connected to a throwaway Docker container. diff --git a/doc/tunnels.rst b/doc/tunnels.rst index 0fa6654..becef7c 100644 --- a/doc/tunnels.rst +++ b/doc/tunnels.rst @@ -31,6 +31,12 @@ Docker .. autoclass:: Docker +NSenter +'''''' + +.. autoclass:: NSenter + + Subprocess '''''''''' From 76ee3156ef9d4f8f4a87918253bda0c3c3ae1b55 Mon Sep 17 00:00:00 2001 From: Denis Vermylen Date: Mon, 20 Feb 2023 11:45:28 +0100 Subject: [PATCH 2/2] [FIX] set running=False when IOLoop stops Steps to reproduce: 1) Make a tunnel then close it - this ends the IOLoop as self.{read,write} are empty, but without calling `stop()` 2) Make another tunnel - stderr ioloop never starts running as it's still considered running https://github.com/amigrave/chopsticks/blob/master/chopsticks/tunnel.py#L51 -> we never get the stderr This error is hidden if the second tunnel is created before the first one is closed. --- chopsticks/ioloop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/chopsticks/ioloop.py b/chopsticks/ioloop.py index 68f38f2..0d70ebb 100644 --- a/chopsticks/ioloop.py +++ b/chopsticks/ioloop.py @@ -302,4 +302,5 @@ def run(self): self.running = True while self.running and (self.read or self.write): self.step() + self.stop(self.result) return self.result