dockerpty-0.3.4/0000755000076600000240000000000012535400421015154 5ustar aanandcloakproxy00000000000000dockerpty-0.3.4/dockerpty/0000755000076600000240000000000012535400421017160 5ustar aanandcloakproxy00000000000000dockerpty-0.3.4/dockerpty/__init__.py0000644000076600000240000000176512535327375021322 0ustar aanandcloakproxy00000000000000# dockerpty. # # Copyright 2014 Chris Corbyn # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from dockerpty.pty import PseudoTerminal def start(client, container, interactive=True, stdout=None, stderr=None, stdin=None): """ Present the PTY of the container inside the current process. This is just a wrapper for PseudoTerminal(client, container).start() """ PseudoTerminal(client, container, interactive=interactive, stdout=stdout, stderr=stderr, stdin=stdin).start() dockerpty-0.3.4/dockerpty/io.py0000644000076600000240000002544212535400176020157 0ustar aanandcloakproxy00000000000000# dockerpty: io.py # # Copyright 2014 Chris Corbyn # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import fcntl import errno import struct import select as builtin_select import six def set_blocking(fd, blocking=True): """ Set the given file-descriptor blocking or non-blocking. Returns the original blocking status. """ old_flag = fcntl.fcntl(fd, fcntl.F_GETFL) if blocking: new_flag = old_flag & ~ os.O_NONBLOCK else: new_flag = old_flag | os.O_NONBLOCK fcntl.fcntl(fd, fcntl.F_SETFL, new_flag) return not bool(old_flag & os.O_NONBLOCK) def select(read_streams, write_streams, timeout=0): """ Select the streams from `read_streams` that are ready for reading, and streams from `write_streams` ready for writing. Uses `select.select()` internally but only returns two lists of ready streams. """ exception_streams = [] try: return builtin_select.select( read_streams, write_streams, exception_streams, timeout, )[0:2] except builtin_select.error as e: # POSIX signals interrupt select() no = e.errno if six.PY3 else e[0] if no == errno.EINTR: return ([], []) else: raise e class Stream(object): """ Generic Stream class. This is a file-like abstraction on top of os.read() and os.write(), which add consistency to the reading of sockets and files alike. """ """ Recoverable IO/OS Errors. """ ERRNO_RECOVERABLE = [ errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK, ] def __init__(self, fd): """ Initialize the Stream for the file descriptor `fd`. The `fd` object must have a `fileno()` method. """ self.fd = fd self.buffer = b'' self.close_requested = False self.closed = False def fileno(self): """ Return the fileno() of the file descriptor. """ return self.fd.fileno() def set_blocking(self, value): if hasattr(self.fd, 'setblocking'): self.fd.setblocking(value) return True else: return set_blocking(self.fd, value) def read(self, n=4096): """ Return `n` bytes of data from the Stream, or None at end of stream. """ while True: try: if hasattr(self.fd, 'recv'): return self.fd.recv(n) return os.read(self.fd.fileno(), n) except EnvironmentError as e: if e.errno not in Stream.ERRNO_RECOVERABLE: raise e def write(self, data): """ Write `data` to the Stream. Not all data may be written right away. Use select to find when the stream is writeable, and call do_write() to flush the internal buffer. """ if not data: return None self.buffer += data self.do_write() return len(data) def do_write(self): """ Flushes as much pending data from the internal write buffer as possible. """ while True: try: written = 0 if hasattr(self.fd, 'send'): written = self.fd.send(self.buffer) else: written = os.write(self.fd.fileno(), self.buffer) self.buffer = self.buffer[written:] # try to close after writes if a close was requested if self.close_requested and len(self.buffer) == 0: self.close() return written except EnvironmentError as e: if e.errno not in Stream.ERRNO_RECOVERABLE: raise e def needs_write(self): """ Returns True if the stream has data waiting to be written. """ return len(self.buffer) > 0 def close(self): self.close_requested = True # We don't close the fd immediately, as there may still be data pending # to write. if not self.closed and len(self.buffer) == 0: self.closed = True if hasattr(self.fd, 'close'): self.fd.close() else: os.close(self.fd.fileno()) def __repr__(self): return "{cls}({fd})".format(cls=type(self).__name__, fd=self.fd) class Demuxer(object): """ Wraps a multiplexed Stream to read in data demultiplexed. Docker multiplexes streams together when there is no PTY attached, by sending an 8-byte header, followed by a chunk of data. The first 4 bytes of the header denote the stream from which the data came (i.e. 0x01 = stdout, 0x02 = stderr). Only the first byte of these initial 4 bytes is used. The next 4 bytes indicate the length of the following chunk of data as an integer in big endian format. This much data must be consumed before the next 8-byte header is read. """ def __init__(self, stream): """ Initialize a new Demuxer reading from `stream`. """ self.stream = stream self.remain = 0 def fileno(self): """ Returns the fileno() of the underlying Stream. This is useful for select() to work. """ return self.stream.fileno() def set_blocking(self, value): return self.stream.set_blocking(value) def read(self, n=4096): """ Read up to `n` bytes of data from the Stream, after demuxing. Less than `n` bytes of data may be returned depending on the available payload, but the number of bytes returned will never exceed `n`. Because demuxing involves scanning 8-byte headers, the actual amount of data read from the underlying stream may be greater than `n`. """ size = self._next_packet_size(n) if size <= 0: return else: data = six.binary_type() while len(data) < size: nxt = self.stream.read(size - len(data)) if not nxt: # the stream has closed, return what data we got return data data = data + nxt return data def write(self, data): """ Delegates the the underlying Stream. """ return self.stream.write(data) def needs_write(self): """ Delegates to underlying Stream. """ if hasattr(self.stream, 'needs_write'): return self.stream.needs_write() return False def do_write(self): """ Delegates to underlying Stream. """ if hasattr(self.stream, 'do_write'): return self.stream.do_write() return False def close(self): """ Delegates to underlying Stream. """ return self.stream.close() def _next_packet_size(self, n=0): size = 0 if self.remain > 0: size = min(n, self.remain) self.remain -= size else: data = six.binary_type() while len(data) < 8: nxt = self.stream.read(8 - len(data)) if not nxt: # The stream has closed, there's nothing more to read return 0 data = data + nxt if data is None: return 0 if len(data) == 8: __, actual = struct.unpack('>BxxxL', data) size = min(n, actual) self.remain = actual - size return size def __repr__(self): return "{cls}({stream})".format(cls=type(self).__name__, stream=self.stream) class Pump(object): """ Stream pump class. A Pump wraps two Streams, reading from one and and writing its data into the other, much like a pipe but manually managed. This abstraction is used to facilitate piping data between the file descriptors associated with the tty and those associated with a container's allocated pty. Pumps are selectable based on the 'read' end of the pipe. """ def __init__(self, from_stream, to_stream, wait_for_output=True, propagate_close=True): """ Initialize a Pump with a Stream to read from and another to write to. `wait_for_output` is a flag that says that we need to wait for EOF on the from_stream in order to consider this pump as "done". """ self.from_stream = from_stream self.to_stream = to_stream self.eof = False self.wait_for_output = wait_for_output self.propagate_close = propagate_close def fileno(self): """ Returns the `fileno()` of the reader end of the Pump. This is useful to allow Pumps to function with `select()`. """ return self.from_stream.fileno() def set_blocking(self, value): return self.from_stream.set_blocking(value) def flush(self, n=4096): """ Flush `n` bytes of data from the reader Stream to the writer Stream. Returns the number of bytes that were actually flushed. A return value of zero is not an error. If EOF has been reached, `None` is returned. """ try: read = self.from_stream.read(n) if read is None or len(read) == 0: self.eof = True if self.propagate_close: self.to_stream.close() return None return self.to_stream.write(read) except OSError as e: if e.errno != errno.EPIPE: raise e def is_done(self): """ Returns True if the read stream is done (either it's returned EOF or the pump doesn't have wait_for_output set), and the write side does not have pending bytes to send. """ return (not self.wait_for_output or self.eof) and \ not (hasattr(self.to_stream, 'needs_write') and self.to_stream.needs_write()) def __repr__(self): return "{cls}(from={from_stream}, to={to_stream})".format( cls=type(self).__name__, from_stream=self.from_stream, to_stream=self.to_stream) dockerpty-0.3.4/dockerpty/pty.py0000644000076600000240000001575412535400176020371 0ustar aanandcloakproxy00000000000000# dockerpty: pty.py # # Copyright 2014 Chris Corbyn # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import sys import signal from ssl import SSLError import dockerpty.io as io import dockerpty.tty as tty class WINCHHandler(object): """ WINCH Signal handler to keep the PTY correctly sized. """ def __init__(self, pty): """ Initialize a new WINCH handler for the given PTY. Initializing a handler has no immediate side-effects. The `start()` method must be invoked for the signals to be trapped. """ self.pty = pty self.original_handler = None def __enter__(self): """ Invoked on entering a `with` block. """ self.start() return self def __exit__(self, *_): """ Invoked on exiting a `with` block. """ self.stop() def start(self): """ Start trapping WINCH signals and resizing the PTY. This method saves the previous WINCH handler so it can be restored on `stop()`. """ def handle(signum, frame): if signum == signal.SIGWINCH: self.pty.resize() self.original_handler = signal.signal(signal.SIGWINCH, handle) def stop(self): """ Stop trapping WINCH signals and restore the previous WINCH handler. """ if self.original_handler is not None: signal.signal(signal.SIGWINCH, self.original_handler) class PseudoTerminal(object): """ Wraps the pseudo-TTY (PTY) allocated to a docker container. The PTY is managed via the current process' TTY until it is closed. Example: import docker from dockerpty import PseudoTerminal client = docker.Client() container = client.create_container( image='busybox:latest', stdin_open=True, tty=True, command='/bin/sh', ) # hijacks the current tty until the pty is closed PseudoTerminal(client, container).start() Care is taken to ensure all file descriptors are restored on exit. For example, you can attach to a running container from within a Python REPL and when the container exits, the user will be returned to the Python REPL without adverse effects. """ def __init__(self, client, container, interactive=True, stdout=None, stderr=None, stdin=None): """ Initialize the PTY using the docker.Client instance and container dict. """ self.client = client self.container = container self.raw = None self.interactive = interactive self.stdout = stdout or sys.stdout self.stderr = stderr or sys.stderr self.stdin = stdin or sys.stdin def start(self, **kwargs): """ Present the PTY of the container inside the current process. This will take over the current process' TTY until the container's PTY is closed. """ pty_stdin, pty_stdout, pty_stderr = self.sockets() pumps = [] if pty_stdin and self.interactive: pumps.append(io.Pump(io.Stream(self.stdin), pty_stdin, wait_for_output=False)) if pty_stdout: pumps.append(io.Pump(pty_stdout, io.Stream(self.stdout), propagate_close=False)) if pty_stderr: pumps.append(io.Pump(pty_stderr, io.Stream(self.stderr), propagate_close=False)) if not self.container_info()['State']['Running']: self.client.start(self.container, **kwargs) flags = [p.set_blocking(False) for p in pumps] try: with WINCHHandler(self): self._hijack_tty(pumps) finally: if flags: for (pump, flag) in zip(pumps, flags): io.set_blocking(pump, flag) def israw(self): """ Returns True if the PTY should operate in raw mode. If the container was not started with tty=True, this will return False. """ if self.raw is None: info = self.container_info() self.raw = self.stdout.isatty() and info['Config']['Tty'] return self.raw def sockets(self): """ Returns a tuple of sockets connected to the pty (stdin,stdout,stderr). If any of the sockets are not attached in the container, `None` is returned in the tuple. """ info = self.container_info() def attach_socket(key): if info['Config']['Attach{0}'.format(key.capitalize())]: socket = self.client.attach_socket( self.container, {key: 1, 'stream': 1, 'logs': 1}, ) stream = io.Stream(socket) if info['Config']['Tty']: return stream else: return io.Demuxer(stream) else: return None return map(attach_socket, ('stdin', 'stdout', 'stderr')) def resize(self, size=None): """ Resize the container's PTY. If `size` is not None, it must be a tuple of (height,width), otherwise it will be determined by the size of the current TTY. """ if not self.israw(): return size = size or tty.size(self.stdout) if size is not None: rows, cols = size try: self.client.resize(self.container, height=rows, width=cols) except IOError: # Container already exited pass def container_info(self): """ Thin wrapper around client.inspect_container(). """ return self.client.inspect_container(self.container) def _hijack_tty(self, pumps): with tty.Terminal(self.stdin, raw=self.israw()): self.resize() while True: read_pumps = [p for p in pumps if not p.eof] write_streams = [p.to_stream for p in pumps if p.to_stream.needs_write()] read_ready, write_ready = io.select(read_pumps, write_streams, timeout=60) try: for write_stream in write_ready: write_stream.do_write() for pump in read_ready: pump.flush() if all([p.is_done() for p in pumps]): break except SSLError as e: if 'The operation did not complete' not in e.strerror: raise e dockerpty-0.3.4/dockerpty/tty.py0000644000076600000240000000620012515707354020365 0ustar aanandcloakproxy00000000000000# dockerpty: tty.py # # Copyright 2014 Chris Corbyn # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import absolute_import import os import termios import tty import fcntl import struct def size(fd): """ Return a tuple (rows,cols) representing the size of the TTY `fd`. The provided file descriptor should be the stdout stream of the TTY. If the TTY size cannot be determined, returns None. """ if not os.isatty(fd.fileno()): return None try: dims = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, 'hhhh')) except: try: dims = (os.environ['LINES'], os.environ['COLUMNS']) except: return None return dims class Terminal(object): """ Terminal provides wrapper functionality to temporarily make the tty raw. This is useful when streaming data from a pseudo-terminal into the tty. Example: with Terminal(sys.stdin, raw=True): do_things_in_raw_mode() """ def __init__(self, fd, raw=True): """ Initialize a terminal for the tty with stdin attached to `fd`. Initializing the Terminal has no immediate side effects. The `start()` method must be invoked, or `with raw_terminal:` used before the terminal is affected. """ self.fd = fd self.raw = raw self.original_attributes = None def __enter__(self): """ Invoked when a `with` block is first entered. """ self.start() return self def __exit__(self, *_): """ Invoked when a `with` block is finished. """ self.stop() def israw(self): """ Returns True if the TTY should operate in raw mode. """ return self.raw def start(self): """ Saves the current terminal attributes and makes the tty raw. This method returns None immediately. """ if os.isatty(self.fd.fileno()) and self.israw(): self.original_attributes = termios.tcgetattr(self.fd) tty.setraw(self.fd) def stop(self): """ Restores the terminal attributes back to before setting raw mode. If the raw terminal was not started, does nothing. """ if self.original_attributes is not None: termios.tcsetattr( self.fd, termios.TCSADRAIN, self.original_attributes, ) def __repr__(self): return "{cls}({fd}, raw={raw})".format( cls=type(self).__name__, fd=self.fd, raw=self.raw) dockerpty-0.3.4/dockerpty.egg-info/0000755000076600000240000000000012535400421020652 5ustar aanandcloakproxy00000000000000dockerpty-0.3.4/dockerpty.egg-info/dependency_links.txt0000644000076600000240000000000112535400420024717 0ustar aanandcloakproxy00000000000000 dockerpty-0.3.4/dockerpty.egg-info/PKG-INFO0000644000076600000240000001413112535400420021746 0ustar aanandcloakproxy00000000000000Metadata-Version: 1.1 Name: dockerpty Version: 0.3.4 Summary: Python library to use the pseudo-tty of a docker container Home-page: https://github.com/d11wtq/dockerpty Author: Chris Corbyn Author-email: chris@w3style.co.uk License: Apache 2.0 Description: # Docker PTY Provides the functionality needed to operate the pseudo-tty (PTY) allocated to a docker container, using the Python client. [![Build Status](https://travis-ci.org/d11wtq/dockerpty.svg?branch=master)] (https://travis-ci.org/d11wtq/dockerpty) ## Installation Via pip: ``` pip install dockerpty ``` Dependencies: * docker-py>=0.3.2 However, this library does not explicitly declare this dependency in PyPi for a number of reasons. It is assumed you have it installed. ## Usage The following example will run busybox in a docker container and place the user at the shell prompt via Python. This obviously only works when run in a terminal. ``` python import docker import dockerpty client = docker.Client() container = client.create_container( image='busybox:latest', stdin_open=True, tty=True, command='/bin/sh', ) dockerpty.start(client, container) ``` Keyword arguments passed to `start()` will be forwarded onto the client to start the container. When the dockerpty is started, control is yielded to the container's PTY until the container exits, or the container's PTY is closed. This is a safe operation and all resources are restored back to their original states. > **Note:** dockerpty does support attaching to non-tty containers to stream container output, though it is obviously not possible to 'control' the container if you do not allocate a pseudo-tty. If you press `C-p C-q`, the container's PTY will be closed, but the container will keep running. In other words, you will have detached from the container and can re-attach with another `dockerpty.start()` call. ## Tests If you want to hack on dockerpty and send a PR, you'll need to run the tests. In the features/ directory, are features/user stories for how dockerpty is supposed to work. To run them: ``` -bash$ pip install -r requirements-dev.txt -bash$ behave features/ ``` You'll need to have docker installed and running locally. The tests use busybox container as a test fixture, so are not too heavy. Step definitions are defined in features/steps/. There are also unit tests for the parts of the code that are not inherently dependent on controlling a TTY. To run those: ``` -bash$ pip install -r requirements-dev.txt -bash$ py.test tests/ ``` Travis CI runs this build inside a UML kernel that is new enough to run docker. Your PR will need to pass the build before I can merge it. - Travis CI build: https://travis-ci.org/d11wtq/dockerpty ## How it works In a terminal, the three file descriptors stdin, stdout and stderr are all connected to the controlling terminal (TTY). When you pass the `tty=True` flag to docker's `create_container()`, docker allocates a fake TTY inside the container (a PTY) to which the container's stdin, stdout and stderr are all connected. The docker API provides a way to access the three sockets connected to the PTY. If with access to the host system's TTY file descriptors and the container's PTY file descriptors, it is trivial to simply 'pipe' data written to these file descriptors between the host and the container. Doing this makes the user's terminal effectively become the pseudo-terminal from inside the container. In reality it's a bit more complicated than this, since care must be taken to put the host terminal into raw mode (where keys such as enter are not interpreted with any special meaning) and restore it on exit. Additionally, the container's stdout and stderr streams along with `sys.stdin` must be made non-blocking so that they can be used with `select()` without blocking the main process. These attributes are restored on exit. The size of a terminal cannot be controlled by sending data to stdin and can only be controlled by the terminal program itself. Since the pseudo-terminal is running inside a real terminal, it is import that the size of the PTY be kept the same as that of the presenting TTY. For this reason, docker provides an API call to resize the allocated PTY. A SIGWINCH handler is used to detect window size changes and resize the pseudo-terminal as needed. ## Contributors - Primary author: [Chris Corbyn](https://github.com/d11wtq) - Collaborator: [Daniel Nephin](https://github.com/dnephin) - Contributor: [Stephen Moore](https://github.com/delfick) - Contributor: [Ben Firshman](https://github.com/bfirsh) ## Copyright & Licensing Copyright © 2014 Chris Corbyn. See the LICENSE.txt file for details. Keywords: docker,tty,pty,terminal Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Topic :: Terminals Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules dockerpty-0.3.4/dockerpty.egg-info/requires.txt0000644000076600000240000000001512535400420023245 0ustar aanandcloakproxy00000000000000six >= 1.3.0 dockerpty-0.3.4/dockerpty.egg-info/SOURCES.txt0000644000076600000240000000042612535400421022540 0ustar aanandcloakproxy00000000000000LICENSE.txt MANIFEST.in README.md setup.py dockerpty/__init__.py dockerpty/io.py dockerpty/pty.py dockerpty/tty.py dockerpty.egg-info/PKG-INFO dockerpty.egg-info/SOURCES.txt dockerpty.egg-info/dependency_links.txt dockerpty.egg-info/requires.txt dockerpty.egg-info/top_level.txtdockerpty-0.3.4/dockerpty.egg-info/top_level.txt0000644000076600000240000000001212535400420023374 0ustar aanandcloakproxy00000000000000dockerpty dockerpty-0.3.4/LICENSE.txt0000644000076600000240000002613612417251622017015 0ustar aanandcloakproxy00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. dockerpty-0.3.4/MANIFEST.in0000644000076600000240000000004612417251622016720 0ustar aanandcloakproxy00000000000000include README.md include LICENSE.txt dockerpty-0.3.4/PKG-INFO0000644000076600000240000001413112535400421016251 0ustar aanandcloakproxy00000000000000Metadata-Version: 1.1 Name: dockerpty Version: 0.3.4 Summary: Python library to use the pseudo-tty of a docker container Home-page: https://github.com/d11wtq/dockerpty Author: Chris Corbyn Author-email: chris@w3style.co.uk License: Apache 2.0 Description: # Docker PTY Provides the functionality needed to operate the pseudo-tty (PTY) allocated to a docker container, using the Python client. [![Build Status](https://travis-ci.org/d11wtq/dockerpty.svg?branch=master)] (https://travis-ci.org/d11wtq/dockerpty) ## Installation Via pip: ``` pip install dockerpty ``` Dependencies: * docker-py>=0.3.2 However, this library does not explicitly declare this dependency in PyPi for a number of reasons. It is assumed you have it installed. ## Usage The following example will run busybox in a docker container and place the user at the shell prompt via Python. This obviously only works when run in a terminal. ``` python import docker import dockerpty client = docker.Client() container = client.create_container( image='busybox:latest', stdin_open=True, tty=True, command='/bin/sh', ) dockerpty.start(client, container) ``` Keyword arguments passed to `start()` will be forwarded onto the client to start the container. When the dockerpty is started, control is yielded to the container's PTY until the container exits, or the container's PTY is closed. This is a safe operation and all resources are restored back to their original states. > **Note:** dockerpty does support attaching to non-tty containers to stream container output, though it is obviously not possible to 'control' the container if you do not allocate a pseudo-tty. If you press `C-p C-q`, the container's PTY will be closed, but the container will keep running. In other words, you will have detached from the container and can re-attach with another `dockerpty.start()` call. ## Tests If you want to hack on dockerpty and send a PR, you'll need to run the tests. In the features/ directory, are features/user stories for how dockerpty is supposed to work. To run them: ``` -bash$ pip install -r requirements-dev.txt -bash$ behave features/ ``` You'll need to have docker installed and running locally. The tests use busybox container as a test fixture, so are not too heavy. Step definitions are defined in features/steps/. There are also unit tests for the parts of the code that are not inherently dependent on controlling a TTY. To run those: ``` -bash$ pip install -r requirements-dev.txt -bash$ py.test tests/ ``` Travis CI runs this build inside a UML kernel that is new enough to run docker. Your PR will need to pass the build before I can merge it. - Travis CI build: https://travis-ci.org/d11wtq/dockerpty ## How it works In a terminal, the three file descriptors stdin, stdout and stderr are all connected to the controlling terminal (TTY). When you pass the `tty=True` flag to docker's `create_container()`, docker allocates a fake TTY inside the container (a PTY) to which the container's stdin, stdout and stderr are all connected. The docker API provides a way to access the three sockets connected to the PTY. If with access to the host system's TTY file descriptors and the container's PTY file descriptors, it is trivial to simply 'pipe' data written to these file descriptors between the host and the container. Doing this makes the user's terminal effectively become the pseudo-terminal from inside the container. In reality it's a bit more complicated than this, since care must be taken to put the host terminal into raw mode (where keys such as enter are not interpreted with any special meaning) and restore it on exit. Additionally, the container's stdout and stderr streams along with `sys.stdin` must be made non-blocking so that they can be used with `select()` without blocking the main process. These attributes are restored on exit. The size of a terminal cannot be controlled by sending data to stdin and can only be controlled by the terminal program itself. Since the pseudo-terminal is running inside a real terminal, it is import that the size of the PTY be kept the same as that of the presenting TTY. For this reason, docker provides an API call to resize the allocated PTY. A SIGWINCH handler is used to detect window size changes and resize the pseudo-terminal as needed. ## Contributors - Primary author: [Chris Corbyn](https://github.com/d11wtq) - Collaborator: [Daniel Nephin](https://github.com/dnephin) - Contributor: [Stephen Moore](https://github.com/delfick) - Contributor: [Ben Firshman](https://github.com/bfirsh) ## Copyright & Licensing Copyright © 2014 Chris Corbyn. See the LICENSE.txt file for details. Keywords: docker,tty,pty,terminal Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: Topic :: Terminals Classifier: Topic :: Terminals :: Terminal Emulators/X Terminals Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules dockerpty-0.3.4/README.md0000644000076600000240000001053012417251622016440 0ustar aanandcloakproxy00000000000000# Docker PTY Provides the functionality needed to operate the pseudo-tty (PTY) allocated to a docker container, using the Python client. [![Build Status](https://travis-ci.org/d11wtq/dockerpty.svg?branch=master)] (https://travis-ci.org/d11wtq/dockerpty) ## Installation Via pip: ``` pip install dockerpty ``` Dependencies: * docker-py>=0.3.2 However, this library does not explicitly declare this dependency in PyPi for a number of reasons. It is assumed you have it installed. ## Usage The following example will run busybox in a docker container and place the user at the shell prompt via Python. This obviously only works when run in a terminal. ``` python import docker import dockerpty client = docker.Client() container = client.create_container( image='busybox:latest', stdin_open=True, tty=True, command='/bin/sh', ) dockerpty.start(client, container) ``` Keyword arguments passed to `start()` will be forwarded onto the client to start the container. When the dockerpty is started, control is yielded to the container's PTY until the container exits, or the container's PTY is closed. This is a safe operation and all resources are restored back to their original states. > **Note:** dockerpty does support attaching to non-tty containers to stream container output, though it is obviously not possible to 'control' the container if you do not allocate a pseudo-tty. If you press `C-p C-q`, the container's PTY will be closed, but the container will keep running. In other words, you will have detached from the container and can re-attach with another `dockerpty.start()` call. ## Tests If you want to hack on dockerpty and send a PR, you'll need to run the tests. In the features/ directory, are features/user stories for how dockerpty is supposed to work. To run them: ``` -bash$ pip install -r requirements-dev.txt -bash$ behave features/ ``` You'll need to have docker installed and running locally. The tests use busybox container as a test fixture, so are not too heavy. Step definitions are defined in features/steps/. There are also unit tests for the parts of the code that are not inherently dependent on controlling a TTY. To run those: ``` -bash$ pip install -r requirements-dev.txt -bash$ py.test tests/ ``` Travis CI runs this build inside a UML kernel that is new enough to run docker. Your PR will need to pass the build before I can merge it. - Travis CI build: https://travis-ci.org/d11wtq/dockerpty ## How it works In a terminal, the three file descriptors stdin, stdout and stderr are all connected to the controlling terminal (TTY). When you pass the `tty=True` flag to docker's `create_container()`, docker allocates a fake TTY inside the container (a PTY) to which the container's stdin, stdout and stderr are all connected. The docker API provides a way to access the three sockets connected to the PTY. If with access to the host system's TTY file descriptors and the container's PTY file descriptors, it is trivial to simply 'pipe' data written to these file descriptors between the host and the container. Doing this makes the user's terminal effectively become the pseudo-terminal from inside the container. In reality it's a bit more complicated than this, since care must be taken to put the host terminal into raw mode (where keys such as enter are not interpreted with any special meaning) and restore it on exit. Additionally, the container's stdout and stderr streams along with `sys.stdin` must be made non-blocking so that they can be used with `select()` without blocking the main process. These attributes are restored on exit. The size of a terminal cannot be controlled by sending data to stdin and can only be controlled by the terminal program itself. Since the pseudo-terminal is running inside a real terminal, it is import that the size of the PTY be kept the same as that of the presenting TTY. For this reason, docker provides an API call to resize the allocated PTY. A SIGWINCH handler is used to detect window size changes and resize the pseudo-terminal as needed. ## Contributors - Primary author: [Chris Corbyn](https://github.com/d11wtq) - Collaborator: [Daniel Nephin](https://github.com/dnephin) - Contributor: [Stephen Moore](https://github.com/delfick) - Contributor: [Ben Firshman](https://github.com/bfirsh) ## Copyright & Licensing Copyright © 2014 Chris Corbyn. See the LICENSE.txt file for details. dockerpty-0.3.4/setup.cfg0000644000076600000240000000007312535400421016775 0ustar aanandcloakproxy00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 dockerpty-0.3.4/setup.py0000644000076600000240000000323012535400234016666 0ustar aanandcloakproxy00000000000000# dockerpty. # # Copyright 2014 Chris Corbyn # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from setuptools import setup import os def fopen(filename): return open(os.path.join(os.path.dirname(__file__), filename)) def read(filename): return fopen(filename).read() setup( name='dockerpty', version='0.3.4', description='Python library to use the pseudo-tty of a docker container', long_description=read('README.md'), url='https://github.com/d11wtq/dockerpty', author='Chris Corbyn', author_email='chris@w3style.co.uk', install_requires=['six >= 1.3.0'], license='Apache 2.0', keywords='docker, tty, pty, terminal', packages=['dockerpty'], classifiers=[ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', 'Environment :: Console', 'Intended Audience :: Developers', 'Topic :: Terminals', 'Topic :: Terminals :: Terminal Emulators/X Terminals', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Libraries :: Python Modules', ], )