diff options
-rw-r--r-- | contrib/python/podman/libs/__init__.py | 6 | ||||
-rw-r--r-- | contrib/python/podman/libs/_containers_attach.py | 111 | ||||
-rw-r--r-- | contrib/python/podman/libs/containers.py | 10 | ||||
-rw-r--r-- | contrib/python/test/podman_testcase.py | 3 | ||||
-rw-r--r-- | contrib/python/test/test_containers.py | 20 | ||||
-rwxr-xr-x | hack/release.sh | 80 |
6 files changed, 215 insertions, 15 deletions
diff --git a/contrib/python/podman/libs/__init__.py b/contrib/python/podman/libs/__init__.py index 9db5dacf5..3a8a35021 100644 --- a/contrib/python/podman/libs/__init__.py +++ b/contrib/python/podman/libs/__init__.py @@ -1,4 +1,5 @@ """Support files for podman API implementation.""" +import collections import datetime import functools @@ -12,12 +13,11 @@ __all__ = [ def cached_property(fn): - """Cache return to save future expense.""" + """Decorate property to cache return value.""" return property(functools.lru_cache(maxsize=8)(fn)) -# Cannot subclass collections.UserDict, breaks varlink -class Config(dict): +class Config(collections.UserDict): """Silently ignore None values, only take key once.""" def __init__(self, **kwargs): diff --git a/contrib/python/podman/libs/_containers_attach.py b/contrib/python/podman/libs/_containers_attach.py new file mode 100644 index 000000000..54e6a009e --- /dev/null +++ b/contrib/python/podman/libs/_containers_attach.py @@ -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) diff --git a/contrib/python/podman/libs/containers.py b/contrib/python/podman/libs/containers.py index 96ec6be37..a350a128a 100644 --- a/contrib/python/podman/libs/containers.py +++ b/contrib/python/podman/libs/containers.py @@ -6,8 +6,10 @@ import json import signal import time +from ._containers_attach import Mixin as AttachMixin -class Container(collections.UserDict): + +class Container(collections.UserDict, AttachMixin): """Model for a container.""" def __init__(self, client, id, data): @@ -46,12 +48,6 @@ class Container(collections.UserDict): with self._client() as 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): """Show processes running in container.""" with self._client() as podman: diff --git a/contrib/python/test/podman_testcase.py b/contrib/python/test/podman_testcase.py index fc99f26ce..f96a3a013 100644 --- a/contrib/python/test/podman_testcase.py +++ b/contrib/python/test/podman_testcase.py @@ -62,7 +62,8 @@ class PodmanTestCase(unittest.TestCase): cmd = ['podman'] 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( cmd, stdout=PodmanTestCase.alpine_log, diff --git a/contrib/python/test/test_containers.py b/contrib/python/test/test_containers.py index 5c7c9934b..87d43adb4 100644 --- a/contrib/python/test/test_containers.py +++ b/contrib/python/test/test_containers.py @@ -1,11 +1,9 @@ import os import signal -import time import unittest from test.podman_testcase import PodmanTestCase import podman -from podman import datetime_parse class TestContainers(PodmanTestCase): @@ -65,8 +63,22 @@ class TestContainers(PodmanTestCase): self.pclient.containers.get("bozo") def test_attach(self): - with self.assertRaisesNotImplemented(): - self.alpine_ctnr.attach() + # StringIO does not support fileno() so we had to go old school + 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): actual = list(self.alpine_ctnr.processes()) diff --git a/hack/release.sh b/hack/release.sh new file mode 100755 index 000000000..01d2e81a9 --- /dev/null +++ b/hack/release.sh @@ -0,0 +1,80 @@ +#!/bin/sh +# +# Cut a libpod release. Usage: +# +# $ hack/release.sh <version> <next-version> +# +# For example: +# +# $ hack/release.sh 1.2.3 1.3.0 +# +# for "I'm cutting 1.2.3, and want to use 1.3.0-dev for future work". + +VERSION="$1" +NEXT_VERSION="$2" +DATE=$(date '+%Y-%m-%d') +LAST_TAG=$(git describe --tags --abbrev=0) + +write_go_version() +{ + LOCAL_VERSION="$1" + sed -i "s/^\(const Version = \"\).*/\1${LOCAL_VERSION}\"/" version/version.go +} + +write_python_version() +{ + LOCAL_VERSION="$1" + sed -i "s/^\( *version='*\).*/\1${LOCAL_VERSION}'/" contrib/python/setup.py +} + +write_spec_version() +{ + LOCAL_VERSION="$1" + sed -i "s/^\(Version: *\).*/\1${LOCAL_VERSION}/" contrib/spec/podman.spec.in +} + +write_makefile_epoch() +{ + LOCAL_EPOCH="$1" + sed -i "s/^\(EPOCH_TEST_COMMIT ?= \).*/\1${LOCAL_EPOCH}/" Makefile +} + +write_changelog() +{ + echo "- Changelog for v${VERSION} (${DATE})" >.changelog.txt && + git log --no-merges --format=' * %s' "${LAST_TAG}..HEAD" >>.changelog.txt && + echo >>.changelog.txt && + cat changelog.txt >>.changelog.txt && + mv -f .changelog.txt changelog.txt +} + +release_commit() +{ + write_go_version "${VERSION}" && + write_python_version "${VERSION}" && + write_changelog && + git commit -asm "Bump to v${VERSION}" +} + +dev_version_commit() +{ + write_go_version "${NEXT_VERSION}-dev" && + write_python_version "${NEXT_VERSION}" && + write_spec_version "${VERSION}" && + git commit -asm "Bump to v${NEXT_VERSION}-dev" +} + +epoch_commit() +{ + LOCAL_EPOCH="$1" + write_makefile_epoch "${LOCAL_EPOCH}" && + git commit -asm 'Bump gitvalidation epoch' +} + +git fetch origin && +git checkout -b "bump-${VERSION}" origin/master && +EPOCH=$(git rev-parse HEAD) && +release_commit && +git tag -s -m "version ${VERSION}" "v${VERSION}" && +dev_version_commit && +epoch_commit "${EPOCH}" |