mirror of
https://github.com/containers/podman.git
synced 2025-06-19 16:33:24 +08:00
Merge pull request #869 from jwhonce/wip/attach
Implement container attach
This commit is contained in:
@ -1,4 +1,5 @@
|
|||||||
"""Support files for podman API implementation."""
|
"""Support files for podman API implementation."""
|
||||||
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
|
|
||||||
@ -12,12 +13,11 @@ __all__ = [
|
|||||||
|
|
||||||
|
|
||||||
def cached_property(fn):
|
def cached_property(fn):
|
||||||
"""Cache return to save future expense."""
|
"""Decorate property to cache return value."""
|
||||||
return property(functools.lru_cache(maxsize=8)(fn))
|
return property(functools.lru_cache(maxsize=8)(fn))
|
||||||
|
|
||||||
|
|
||||||
# Cannot subclass collections.UserDict, breaks varlink
|
class Config(collections.UserDict):
|
||||||
class Config(dict):
|
|
||||||
"""Silently ignore None values, only take key once."""
|
"""Silently ignore None values, only take key once."""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
111
contrib/python/podman/libs/_containers_attach.py
Normal file
111
contrib/python/podman/libs/_containers_attach.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""Exported method Container.attach()."""
|
||||||
|
|
||||||
|
import fcntl
|
||||||
|
import os
|
||||||
|
import select
|
||||||
|
import signal
|
||||||
|
import socket
|
||||||
|
import struct
|
||||||
|
import sys
|
||||||
|
import termios
|
||||||
|
import tty
|
||||||
|
|
||||||
|
CONMON_BUFSZ = 8192
|
||||||
|
|
||||||
|
|
||||||
|
class Mixin:
|
||||||
|
"""Publish attach() for inclusion in Container class."""
|
||||||
|
|
||||||
|
def attach(self, eot=4, stdin=None, stdout=None):
|
||||||
|
"""Attach to container's PID1 stdin and stdout.
|
||||||
|
|
||||||
|
stderr is ignored.
|
||||||
|
"""
|
||||||
|
if stdin is None:
|
||||||
|
stdin = sys.stdin.fileno()
|
||||||
|
|
||||||
|
if stdout is None:
|
||||||
|
stdout = sys.stdout.fileno()
|
||||||
|
|
||||||
|
with self._client() as podman:
|
||||||
|
attach = podman.GetAttachSockets(self._id)
|
||||||
|
|
||||||
|
# This is the UDS where all the IO goes
|
||||||
|
io_socket = attach['sockets']['io_socket']
|
||||||
|
assert len(io_socket) <= 107,\
|
||||||
|
'Path length for sockets too long. {} > 107'.format(
|
||||||
|
len(io_socket)
|
||||||
|
)
|
||||||
|
|
||||||
|
# This is the control socket where resizing events are sent to conmon
|
||||||
|
ctl_socket = attach['sockets']['control_socket']
|
||||||
|
|
||||||
|
def resize_handler(signum, frame):
|
||||||
|
"""Send the new window size to conmon.
|
||||||
|
|
||||||
|
The method arguments are not used.
|
||||||
|
"""
|
||||||
|
packed = fcntl.ioctl(stdout, termios.TIOCGWINSZ,
|
||||||
|
struct.pack('HHHH', 0, 0, 0, 0))
|
||||||
|
rows, cols, _, _ = struct.unpack('HHHH', packed)
|
||||||
|
|
||||||
|
with open(ctl_socket, 'w') as skt:
|
||||||
|
# send conmon window resize message
|
||||||
|
skt.write('1 {} {}\n'.format(rows, cols))
|
||||||
|
|
||||||
|
def log_handler(signum, frame):
|
||||||
|
"""Send command to reopen log to conmon.
|
||||||
|
|
||||||
|
The method arguments are not used.
|
||||||
|
"""
|
||||||
|
with open(ctl_socket, 'w') as skt:
|
||||||
|
# send conmon reopen log message
|
||||||
|
skt.write('2\n')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# save off the old settings for terminal
|
||||||
|
original_attr = termios.tcgetattr(stdout)
|
||||||
|
tty.setraw(stdin)
|
||||||
|
|
||||||
|
# initialize containers window size
|
||||||
|
resize_handler(None, sys._getframe(0))
|
||||||
|
|
||||||
|
# catch any resizing events and send the resize info
|
||||||
|
# to the control fifo "socket"
|
||||||
|
signal.signal(signal.SIGWINCH, resize_handler)
|
||||||
|
except termios.error:
|
||||||
|
original_attr = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Prepare socket for communicating with conmon/container
|
||||||
|
with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as skt:
|
||||||
|
skt.connect(io_socket)
|
||||||
|
skt.sendall(b'\n')
|
||||||
|
|
||||||
|
sources = [skt, stdin]
|
||||||
|
while sources:
|
||||||
|
readable, _, _ = select.select(sources, [], [])
|
||||||
|
for r in readable:
|
||||||
|
if r is skt:
|
||||||
|
data = r.recv(CONMON_BUFSZ)
|
||||||
|
if not data:
|
||||||
|
sources.remove(skt)
|
||||||
|
|
||||||
|
# Remove source marker when writing
|
||||||
|
os.write(stdout, data[1:])
|
||||||
|
elif r is stdin:
|
||||||
|
data = os.read(stdin, CONMON_BUFSZ)
|
||||||
|
if not data:
|
||||||
|
sources.remove(stdin)
|
||||||
|
|
||||||
|
skt.sendall(data)
|
||||||
|
|
||||||
|
if eot in data:
|
||||||
|
sources.clear()
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError('Unknown source in select')
|
||||||
|
finally:
|
||||||
|
if original_attr:
|
||||||
|
termios.tcsetattr(stdout, termios.TCSADRAIN, original_attr)
|
||||||
|
signal.signal(signal.SIGWINCH, signal.SIG_DFL)
|
@ -6,8 +6,10 @@ import json
|
|||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
from ._containers_attach import Mixin as AttachMixin
|
||||||
|
|
||||||
class Container(collections.UserDict):
|
|
||||||
|
class Container(collections.UserDict, AttachMixin):
|
||||||
"""Model for a container."""
|
"""Model for a container."""
|
||||||
|
|
||||||
def __init__(self, client, id, data):
|
def __init__(self, client, id, data):
|
||||||
@ -46,12 +48,6 @@ class Container(collections.UserDict):
|
|||||||
with self._client() as podman:
|
with self._client() as podman:
|
||||||
return self._refresh(podman)
|
return self._refresh(podman)
|
||||||
|
|
||||||
def attach(self, detach_key=None, no_stdin=False, sig_proxy=True):
|
|
||||||
"""Attach to running container."""
|
|
||||||
with self._client() as podman:
|
|
||||||
# TODO: streaming and port magic occur, need arguments
|
|
||||||
podman.AttachToContainer()
|
|
||||||
|
|
||||||
def processes(self):
|
def processes(self):
|
||||||
"""Show processes running in container."""
|
"""Show processes running in container."""
|
||||||
with self._client() as podman:
|
with self._client() as podman:
|
||||||
|
@ -62,7 +62,8 @@ class PodmanTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
cmd = ['podman']
|
cmd = ['podman']
|
||||||
cmd.extend(podman_args)
|
cmd.extend(podman_args)
|
||||||
cmd.extend(['run', '-d', 'alpine', 'sleep', '500'])
|
# cmd.extend(['run', '-d', 'alpine', 'sleep', '500'])
|
||||||
|
cmd.extend(['run', '-dt', 'alpine', '/bin/sh'])
|
||||||
PodmanTestCase.alpine_process = subprocess.Popen(
|
PodmanTestCase.alpine_process = subprocess.Popen(
|
||||||
cmd,
|
cmd,
|
||||||
stdout=PodmanTestCase.alpine_log,
|
stdout=PodmanTestCase.alpine_log,
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import time
|
|
||||||
import unittest
|
import unittest
|
||||||
from test.podman_testcase import PodmanTestCase
|
from test.podman_testcase import PodmanTestCase
|
||||||
|
|
||||||
import podman
|
import podman
|
||||||
from podman import datetime_parse
|
|
||||||
|
|
||||||
|
|
||||||
class TestContainers(PodmanTestCase):
|
class TestContainers(PodmanTestCase):
|
||||||
@ -65,8 +63,22 @@ class TestContainers(PodmanTestCase):
|
|||||||
self.pclient.containers.get("bozo")
|
self.pclient.containers.get("bozo")
|
||||||
|
|
||||||
def test_attach(self):
|
def test_attach(self):
|
||||||
with self.assertRaisesNotImplemented():
|
# StringIO does not support fileno() so we had to go old school
|
||||||
self.alpine_ctnr.attach()
|
input = os.path.join(self.tmpdir, 'test_attach.stdin')
|
||||||
|
output = os.path.join(self.tmpdir, 'test_attach.stdout')
|
||||||
|
|
||||||
|
with open(input, 'w+') as mock_in, open(output, 'w+') as mock_out:
|
||||||
|
# double quote is indeed in the expected place
|
||||||
|
mock_in.write('echo H"ello, World"; exit\n')
|
||||||
|
mock_in.seek(0, 0)
|
||||||
|
|
||||||
|
self.alpine_ctnr.attach(
|
||||||
|
stdin=mock_in.fileno(), stdout=mock_out.fileno())
|
||||||
|
|
||||||
|
mock_out.flush()
|
||||||
|
mock_out.seek(0, 0)
|
||||||
|
output = mock_out.read()
|
||||||
|
self.assertIn('Hello', output)
|
||||||
|
|
||||||
def test_processes(self):
|
def test_processes(self):
|
||||||
actual = list(self.alpine_ctnr.processes())
|
actual = list(self.alpine_ctnr.processes())
|
||||||
|
Reference in New Issue
Block a user