summaryrefslogtreecommitdiff
path: root/contrib/python/podman/libs
diff options
context:
space:
mode:
authorJhon Honce <jhonce@redhat.com>2018-07-12 19:26:14 -0700
committerJhon Honce <jhonce@redhat.com>2018-07-13 12:50:12 -0700
commit74ccd9ce5f29a1df4ffe70b4d8bd00c29d5d9d15 (patch)
tree75ba256d70545d79aa61d7c57c20df886be1555f /contrib/python/podman/libs
parent44b523c946c88e540b50d7ba59f441b5f8e0bad0 (diff)
downloadpodman-74ccd9ce5f29a1df4ffe70b4d8bd00c29d5d9d15.tar.gz
podman-74ccd9ce5f29a1df4ffe70b4d8bd00c29d5d9d15.tar.bz2
podman-74ccd9ce5f29a1df4ffe70b4d8bd00c29d5d9d15.zip
Update python directories to better support setup.py
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Diffstat (limited to 'contrib/python/podman/libs')
-rw-r--r--contrib/python/podman/libs/__init__.py54
-rw-r--r--contrib/python/podman/libs/_containers_attach.py75
-rw-r--r--contrib/python/podman/libs/_containers_start.py82
-rw-r--r--contrib/python/podman/libs/containers.py245
-rw-r--r--contrib/python/podman/libs/errors.py65
-rw-r--r--contrib/python/podman/libs/images.py172
-rw-r--r--contrib/python/podman/libs/system.py40
-rw-r--r--contrib/python/podman/libs/tunnel.py138
8 files changed, 0 insertions, 871 deletions
diff --git a/contrib/python/podman/libs/__init__.py b/contrib/python/podman/libs/__init__.py
deleted file mode 100644
index 3a8a35021..000000000
--- a/contrib/python/podman/libs/__init__.py
+++ /dev/null
@@ -1,54 +0,0 @@
-"""Support files for podman API implementation."""
-import collections
-import datetime
-import functools
-
-from dateutil.parser import parse as dateutil_parse
-
-__all__ = [
- 'cached_property',
- 'datetime_parse',
- 'datetime_format',
-]
-
-
-def cached_property(fn):
- """Decorate property to cache return value."""
- return property(functools.lru_cache(maxsize=8)(fn))
-
-
-class Config(collections.UserDict):
- """Silently ignore None values, only take key once."""
-
- def __init__(self, **kwargs):
- """Construct dictionary."""
- super(Config, self).__init__(kwargs)
-
- def __setitem__(self, key, value):
- """Store unique, not None values."""
- if value is None:
- return
-
- if super().__contains__(key):
- return
-
- super().__setitem__(key, value)
-
-
-def datetime_parse(string):
- """Convert timestamps to datetime.
-
- tzinfo aware, if provided.
- """
- return dateutil_parse(string.upper(), fuzzy=True)
-
-
-def datetime_format(dt):
- """Format datetime in consistent style."""
- if isinstance(dt, str):
- return datetime_parse(dt).isoformat()
- elif isinstance(dt, datetime.datetime):
- return dt.isoformat()
- else:
- raise ValueError('Unable to format {}. Type {} not supported.'.format(
- dt, type(dt)))
diff --git a/contrib/python/podman/libs/_containers_attach.py b/contrib/python/podman/libs/_containers_attach.py
deleted file mode 100644
index df12fa998..000000000
--- a/contrib/python/podman/libs/_containers_attach.py
+++ /dev/null
@@ -1,75 +0,0 @@
-"""Exported method Container.attach()."""
-
-import collections
-import fcntl
-import logging
-import struct
-import sys
-import termios
-
-
-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.
- PseudoTTY work is done in start().
- """
- 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
- # attach['sockets']['control_socket']
- self.pseudo_tty = collections.namedtuple(
- 'PseudoTTY',
- ['stdin', 'stdout', 'io_socket', 'control_socket', 'eot'])(
- stdin,
- stdout,
- attach['sockets']['io_socket'],
- attach['sockets']['control_socket'],
- eot,
- )
-
- @property
- def resize_handler(self):
- """Send the new window size to conmon."""
-
- def wrapped(signum, frame):
- packed = fcntl.ioctl(self.pseudo_tty.stdout, termios.TIOCGWINSZ,
- struct.pack('HHHH', 0, 0, 0, 0))
- rows, cols, _, _ = struct.unpack('HHHH', packed)
- logging.debug('Resize window({}x{}) using {}'.format(
- rows, cols, self.pseudo_tty.control_socket))
-
- # TODO: Need some kind of timeout in case pipe is blocked
- with open(self.pseudo_tty.control_socket, 'w') as skt:
- # send conmon window resize message
- skt.write('1 {} {}\n'.format(rows, cols))
-
- return wrapped
-
- @property
- def log_handler(self):
- """Send command to reopen log to conmon."""
-
- def wrapped(signum, frame):
- with open(self.pseudo_tty.control_socket, 'w') as skt:
- # send conmon reopen log message
- skt.write('2\n')
-
- return wrapped
diff --git a/contrib/python/podman/libs/_containers_start.py b/contrib/python/podman/libs/_containers_start.py
deleted file mode 100644
index ad9f32eab..000000000
--- a/contrib/python/podman/libs/_containers_start.py
+++ /dev/null
@@ -1,82 +0,0 @@
-"""Exported method Container.start()."""
-import logging
-import os
-import select
-import signal
-import socket
-import sys
-import termios
-import tty
-
-CONMON_BUFSZ = 8192
-
-
-class Mixin:
- """Publish start() for inclusion in Container class."""
-
- def start(self):
- """Start container, return container on success.
-
- Will block if container has been detached.
- """
- with self._client() as podman:
- results = podman.StartContainer(self.id)
- logging.debug('Started Container "{}"'.format(
- results['container']))
-
- if not hasattr(self, 'pseudo_tty') or self.pseudo_tty is None:
- return self._refresh(podman)
-
- logging.debug('Setting up PseudoTTY for Container "{}"'.format(
- results['container']))
-
- try:
- # save off the old settings for terminal
- tcoldattr = termios.tcgetattr(self.pseudo_tty.stdin)
- tty.setraw(self.pseudo_tty.stdin)
-
- # initialize container's window size
- self.resize_handler(None, sys._getframe(0))
-
- # catch any resizing events and send the resize info
- # to the control fifo "socket"
- signal.signal(signal.SIGWINCH, self.resize_handler)
-
- except termios.error:
- tcoldattr = None
-
- try:
- # TODO: Is socket.SOCK_SEQPACKET supported in Windows?
- with socket.socket(socket.AF_UNIX,
- socket.SOCK_SEQPACKET) as skt:
- # Prepare socket for use with conmon/container
- skt.connect(self.pseudo_tty.io_socket)
-
- sources = [skt, self.pseudo_tty.stdin]
- while sources:
- logging.debug('Waiting on sources: {}'.format(sources))
- readable, _, _ = select.select(sources, [], [])
-
- if skt in readable:
- data = skt.recv(CONMON_BUFSZ)
- if data:
- # Remove source marker when writing
- os.write(self.pseudo_tty.stdout, data[1:])
- else:
- sources.remove(skt)
-
- if self.pseudo_tty.stdin in readable:
- data = os.read(self.pseudo_tty.stdin, CONMON_BUFSZ)
- if data:
- skt.sendall(data)
-
- if self.pseudo_tty.eot in data:
- sources.clear()
- else:
- sources.remove(self.pseudo_tty.stdin)
- finally:
- if tcoldattr:
- termios.tcsetattr(self.pseudo_tty.stdin, termios.TCSADRAIN,
- tcoldattr)
- signal.signal(signal.SIGWINCH, signal.SIG_DFL)
- return self._refresh(podman)
diff --git a/contrib/python/podman/libs/containers.py b/contrib/python/podman/libs/containers.py
deleted file mode 100644
index 6dc2c141e..000000000
--- a/contrib/python/podman/libs/containers.py
+++ /dev/null
@@ -1,245 +0,0 @@
-"""Models for manipulating containers and storage."""
-import collections
-import functools
-import getpass
-import json
-import signal
-import time
-
-from ._containers_attach import Mixin as AttachMixin
-from ._containers_start import Mixin as StartMixin
-
-
-class Container(AttachMixin, StartMixin, collections.UserDict):
- """Model for a container."""
-
- def __init__(self, client, id, data):
- """Construct Container Model."""
- super(Container, self).__init__(data)
-
- self._client = client
- self._id = id
-
- with client() as podman:
- self._refresh(podman)
-
- assert self._id == self.data['id'],\
- 'Requested container id({}) does not match store id({})'.format(
- self._id, self.id
- )
-
- def __getitem__(self, key):
- """Get items from parent dict."""
- return super().__getitem__(key)
-
- def _refresh(self, podman):
- ctnr = podman.GetContainer(self._id)
- super().update(ctnr['container'])
-
- for k, v in self.data.items():
- setattr(self, k, v)
- if 'containerrunning' in self.data:
- setattr(self, 'running', self.data['containerrunning'])
- self.data['running'] = self.data['containerrunning']
-
- return self
-
- def refresh(self):
- """Refresh status fields for this container."""
- with self._client() as podman:
- return self._refresh(podman)
-
- def processes(self):
- """Show processes running in container."""
- with self._client() as podman:
- results = podman.ListContainerProcesses(self.id)
- yield from results['container']
-
- def changes(self):
- """Retrieve container changes."""
- with self._client() as podman:
- results = podman.ListContainerChanges(self.id)
- return results['container']
-
- def kill(self, signal=signal.SIGTERM, wait=25):
- """Send signal to container.
-
- default signal is signal.SIGTERM.
- wait n of seconds, 0 waits forever.
- """
- with self._client() as podman:
- podman.KillContainer(self.id, signal)
- timeout = time.time() + wait
- while True:
- self._refresh(podman)
- if self.status != 'running':
- return self
-
- if wait and timeout < time.time():
- raise TimeoutError()
-
- time.sleep(0.5)
-
- def _lower_hook(self):
- """Convert all keys to lowercase."""
-
- @functools.wraps(self._lower_hook)
- def wrapped(input):
- return {k.lower(): v for (k, v) in input.items()}
-
- return wrapped
-
- def inspect(self):
- """Retrieve details about containers."""
- with self._client() as podman:
- results = podman.InspectContainer(self.id)
- obj = json.loads(results['container'], object_hook=self._lower_hook())
- return collections.namedtuple('ContainerInspect', obj.keys())(**obj)
-
- def export(self, target):
- """Export container from store to tarball.
-
- TODO: should there be a compress option, like images?
- """
- with self._client() as podman:
- results = podman.ExportContainer(self.id, target)
- return results['tarfile']
-
- def commit(self,
- image_name,
- *args,
- changes=[],
- message='',
- pause=True,
- **kwargs):
- """Create image from container.
-
- All changes overwrite existing values.
- See inspect() to obtain current settings.
-
- Changes:
- CMD=/usr/bin/zsh
- ENTRYPOINT=/bin/sh date
- ENV=TEST=test_containers.TestContainers.test_commit
- EXPOSE=8888/tcp
- LABEL=unittest=test_commit
- USER=bozo:circus
- VOLUME=/data
- WORKDIR=/data/application
- """
- # TODO: Clean up *args, **kwargs after Commit() is complete
- try:
- author = kwargs.get('author', getpass.getuser())
- except Exception:
- author = ''
-
- for c in changes:
- if c.startswith('LABEL=') and c.count('=') < 2:
- raise ValueError(
- 'LABEL should have the format: LABEL=label=value, not {}'.
- format(c))
-
- with self._client() as podman:
- results = podman.Commit(self.id, image_name, changes, author,
- message, pause)
- return results['image']
-
- def stop(self, timeout=25):
- """Stop container, return id on success."""
- with self._client() as podman:
- podman.StopContainer(self.id, timeout)
- return self._refresh(podman)
-
- def remove(self, force=False):
- """Remove container, return id on success.
-
- force=True, stop running container.
- """
- with self._client() as podman:
- results = podman.RemoveContainer(self.id, force)
- return results['container']
-
- def restart(self, timeout=25):
- """Restart container with timeout, return id on success."""
- with self._client() as podman:
- podman.RestartContainer(self.id, timeout)
- return self._refresh(podman)
-
- def rename(self, target):
- """Rename container, return id on success."""
- with self._client() as podman:
- # TODO: Need arguments
- results = podman.RenameContainer()
- # TODO: fixup objects cached information
- return results['container']
-
- def resize_tty(self, width, height):
- """Resize container tty."""
- with self._client() as podman:
- # TODO: magic re: attach(), arguments
- podman.ResizeContainerTty()
-
- def pause(self):
- """Pause container, return id on success."""
- with self._client() as podman:
- podman.PauseContainer(self.id)
- return self._refresh(podman)
-
- def unpause(self):
- """Unpause container, return id on success."""
- with self._client() as podman:
- podman.UnpauseContainer(self.id)
- return self._refresh(podman)
-
- def update_container(self, *args, **kwargs):
- """TODO: Update container..., return id on success."""
- with self._client() as podman:
- podman.UpdateContainer()
- return self._refresh(podman)
-
- def wait(self):
- """Wait for container to finish, return 'returncode'."""
- with self._client() as podman:
- results = podman.WaitContainer(self.id)
- return int(results['exitcode'])
-
- def stats(self):
- """Retrieve resource stats from the container."""
- with self._client() as podman:
- results = podman.GetContainerStats(self.id)
- obj = results['container']
- return collections.namedtuple('StatDetail', obj.keys())(**obj)
-
- def logs(self, *args, **kwargs):
- """Retrieve container logs."""
- with self._client() as podman:
- results = podman.GetContainerLogs(self.id)
- yield from results
-
-
-class Containers(object):
- """Model for Containers collection."""
-
- def __init__(self, client):
- """Construct model for Containers collection."""
- self._client = client
-
- def list(self):
- """List of containers in the container store."""
- with self._client() as podman:
- results = podman.ListContainers()
- for cntr in results['containers']:
- yield Container(self._client, cntr['id'], cntr)
-
- def delete_stopped(self):
- """Delete all stopped containers."""
- with self._client() as podman:
- results = podman.DeleteStoppedContainers()
- return results['containers']
-
- def get(self, id):
- """Retrieve container details from store."""
- with self._client() as podman:
- cntr = podman.GetContainer(id)
- return Container(self._client, cntr['container']['id'],
- cntr['container'])
diff --git a/contrib/python/podman/libs/errors.py b/contrib/python/podman/libs/errors.py
deleted file mode 100644
index b98210481..000000000
--- a/contrib/python/podman/libs/errors.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""Error classes and wrappers for VarlinkError."""
-from varlink import VarlinkError
-
-
-class VarlinkErrorProxy(VarlinkError):
- """Class to Proxy VarlinkError methods."""
-
- def __init__(self, message, namespaced=False):
- """Construct proxy from Exception."""
- super().__init__(message.as_dict(), namespaced)
- self._message = message
- self.__module__ = 'libpod'
-
- def __getattr__(self, method):
- """Return attribute from proxied Exception."""
- if hasattr(self._message, method):
- return getattr(self._message, method)
-
- try:
- return self._message.parameters()[method]
- except KeyError:
- raise AttributeError('No such attribute: {}'.format(method))
-
-
-class ContainerNotFound(VarlinkErrorProxy):
- """Raised when Client can not find requested container."""
-
- pass
-
-
-class ImageNotFound(VarlinkErrorProxy):
- """Raised when Client can not find requested image."""
-
- pass
-
-
-class ErrorOccurred(VarlinkErrorProxy):
- """Raised when an error occurs during the execution.
-
- See error() to see actual error text.
- """
-
- pass
-
-
-class RuntimeError(VarlinkErrorProxy):
- """Raised when Client fails to connect to runtime."""
-
- pass
-
-
-error_map = {
- 'io.projectatomic.podman.ContainerNotFound': ContainerNotFound,
- 'io.projectatomic.podman.ErrorOccurred': ErrorOccurred,
- 'io.projectatomic.podman.ImageNotFound': ImageNotFound,
- 'io.projectatomic.podman.RuntimeError': RuntimeError,
-}
-
-
-def error_factory(exception):
- """Map Exceptions to a discrete type."""
- try:
- return error_map[exception.error()](exception)
- except KeyError:
- return exception
diff --git a/contrib/python/podman/libs/images.py b/contrib/python/podman/libs/images.py
deleted file mode 100644
index 334ff873c..000000000
--- a/contrib/python/podman/libs/images.py
+++ /dev/null
@@ -1,172 +0,0 @@
-"""Models for manipulating images in/to/from storage."""
-import collections
-import copy
-import functools
-import json
-import logging
-
-from . import Config
-from .containers import Container
-
-
-class Image(collections.UserDict):
- """Model for an Image."""
-
- def __init__(self, client, id, data):
- """Construct Image Model."""
- super(Image, self).__init__(data)
- for k, v in data.items():
- setattr(self, k, v)
-
- self._id = id
- self._client = client
-
- assert self._id == self.id,\
- 'Requested image id({}) does not match store id({})'.format(
- self._id, self.id
- )
-
- def __getitem__(self, key):
- """Get items from parent dict."""
- return super().__getitem__(key)
-
- def _split_token(self, values=None, sep='='):
- return dict([v.split(sep, 1) for v in values if values])
-
- def create(self, *args, **kwargs):
- """Create container from image.
-
- Pulls defaults from image.inspect()
- """
- details = self.inspect()
-
- config = Config(image_id=self.id, **kwargs)
- config['command'] = details.containerconfig['cmd']
- config['env'] = self._split_token(details.containerconfig['env'])
- config['image'] = copy.deepcopy(details.repotags[0])
- config['labels'] = copy.deepcopy(details.labels)
- config['net_mode'] = 'bridge'
- config['network'] = 'bridge'
-
- logging.debug('Image {}: create config: {}'.format(self.id, config))
- with self._client() as podman:
- id = podman.CreateContainer(config)['container']
- cntr = podman.GetContainer(id)
- return Container(self._client, id, cntr['container'])
-
- container = create
-
- def export(self, dest, compressed=False):
- """Write image to dest, return id on success."""
- with self._client() as podman:
- results = podman.ExportImage(self.id, dest, compressed)
- return results['image']
-
- def history(self):
- """Retrieve image history."""
- with self._client() as podman:
- for r in podman.HistoryImage(self.id)['history']:
- yield collections.namedtuple('HistoryDetail', r.keys())(**r)
-
- # Convert all keys to lowercase.
- def _lower_hook(self):
- @functools.wraps(self._lower_hook)
- def wrapped(input):
- return {k.lower(): v for (k, v) in input.items()}
-
- return wrapped
-
- def inspect(self):
- """Retrieve details about image."""
- with self._client() as podman:
- results = podman.InspectImage(self.id)
- obj = json.loads(results['image'], object_hook=self._lower_hook())
- return collections.namedtuple('ImageInspect', obj.keys())(**obj)
-
- def push(self, target, tlsverify=False):
- """Copy image to target, return id on success."""
- with self._client() as podman:
- results = podman.PushImage(self.id, target, tlsverify)
- return results['image']
-
- def remove(self, force=False):
- """Delete image, return id on success.
-
- force=True, stop any running containers using image.
- """
- with self._client() as podman:
- results = podman.RemoveImage(self.id, force)
- return results['image']
-
- def tag(self, tag):
- """Tag image."""
- with self._client() as podman:
- results = podman.TagImage(self.id, tag)
- return results['image']
-
-
-class Images(object):
- """Model for Images collection."""
-
- def __init__(self, client):
- """Construct model for Images collection."""
- self._client = client
-
- def list(self):
- """List all images in the libpod image store."""
- with self._client() as podman:
- results = podman.ListImages()
- for img in results['images']:
- yield Image(self._client, img['id'], img)
-
- def build(self, dockerfile=None, tags=None, **kwargs):
- """Build container from image.
-
- See podman-build.1.md for kwargs details.
- """
- if dockerfile is None:
- raise ValueError('"dockerfile" is a required argument.')
- elif not hasattr(dockerfile, '__iter__'):
- raise ValueError('"dockerfile" is required to be an iter.')
-
- if tags is None:
- raise ValueError('"tags" is a required argument.')
- elif not hasattr(tags, '__iter__'):
- raise ValueError('"tags" is required to be an iter.')
-
- config = Config(dockerfile=dockerfile, tags=tags, **kwargs)
- with self._client() as podman:
- result = podman.BuildImage(config)
- return self.get(result['image']['id']), \
- (line for line in result['image']['logs'])
-
- def delete_unused(self):
- """Delete Images not associated with a container."""
- with self._client() as podman:
- results = podman.DeleteUnusedImages()
- return results['images']
-
- def import_image(self, source, reference, message=None, changes=None):
- """Read image tarball from source and save in image store."""
- with self._client() as podman:
- results = podman.ImportImage(source, reference, message, changes)
- return results['image']
-
- def pull(self, source):
- """Copy image from registry to image store."""
- with self._client() as podman:
- results = podman.PullImage(source)
- return results['id']
-
- def search(self, id, limit=25):
- """Search registries for id."""
- with self._client() as podman:
- results = podman.SearchImage(id, limit)
- for img in results['images']:
- yield collections.namedtuple('ImageSearch', img.keys())(**img)
-
- def get(self, id):
- """Get Image from id."""
- with self._client() as podman:
- result = podman.GetImage(id)
- return Image(self._client, result['image']['id'], result['image'])
diff --git a/contrib/python/podman/libs/system.py b/contrib/python/podman/libs/system.py
deleted file mode 100644
index c59867760..000000000
--- a/contrib/python/podman/libs/system.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""Models for accessing details from varlink server."""
-import collections
-
-import pkg_resources
-
-from . import cached_property
-
-
-class System(object):
- """Model for accessing system resources."""
-
- def __init__(self, client):
- """Construct system model."""
- self._client = client
-
- @cached_property
- def versions(self):
- """Access versions."""
- with self._client() as podman:
- vers = podman.GetVersion()['version']
-
- client = '0.0.0'
- try:
- client = pkg_resources.get_distribution('podman').version
- except Exception:
- pass
- vers['client_version'] = client
- return collections.namedtuple('Version', vers.keys())(**vers)
-
- def info(self):
- """Return podman info."""
- with self._client() as podman:
- info = podman.GetInfo()['info']
- return collections.namedtuple('Info', info.keys())(**info)
-
- def ping(self):
- """Return True if server awake."""
- with self._client() as podman:
- response = podman.Ping()
- return 'OK' == response['ping']['message']
diff --git a/contrib/python/podman/libs/tunnel.py b/contrib/python/podman/libs/tunnel.py
deleted file mode 100644
index 9effdff6c..000000000
--- a/contrib/python/podman/libs/tunnel.py
+++ /dev/null
@@ -1,138 +0,0 @@
-"""Cache for SSH tunnels."""
-import collections
-import logging
-import os
-import subprocess
-import threading
-import time
-import weakref
-
-Context = collections.namedtuple('Context', (
- 'uri',
- 'interface',
- 'local_socket',
- 'remote_socket',
- 'username',
- 'hostname',
- 'identity_file',
-))
-
-
-class Portal(collections.MutableMapping):
- """Expiring container for tunnels."""
-
- def __init__(self, sweap=25):
- """Construct portal, reap tunnels every sweap seconds."""
- self.data = collections.OrderedDict()
- self.sweap = sweap
- self.ttl = sweap * 2
- self.lock = threading.RLock()
- self._schedule_reaper()
-
- def __getitem__(self, key):
- """Given uri return tunnel and update TTL."""
- with self.lock:
- value, _ = self.data[key]
- self.data[key] = (value, time.time() + self.ttl)
- self.data.move_to_end(key)
- return value
-
- def __setitem__(self, key, value):
- """Store given tunnel keyed with uri."""
- if not isinstance(value, Tunnel):
- raise ValueError('Portals only support Tunnels.')
-
- with self.lock:
- self.data[key] = (value, time.time() + self.ttl)
- self.data.move_to_end(key)
-
- def __delitem__(self, key):
- """Remove and close tunnel from portal."""
- with self.lock:
- value, _ = self.data[key]
- del self.data[key]
- value.close(key)
- del value
-
- def __iter__(self):
- """Iterate tunnels."""
- with self.lock:
- values = self.data.values()
-
- for tunnel, _ in values:
- yield tunnel
-
- def __len__(self):
- """Return number of tunnels in portal."""
- with self.lock:
- return len(self.data)
-
- def _schedule_reaper(self):
- timer = threading.Timer(interval=self.sweap, function=self.reap)
- timer.setName('PortalReaper')
- timer.setDaemon(True)
- timer.start()
-
- def reap(self):
- """Remove tunnels who's TTL has expired."""
- now = time.time()
- with self.lock:
- reaped_data = self.data.copy()
- for entry in reaped_data.items():
- if entry[1][1] < now:
- del self.data[entry[0]]
- else:
- # StopIteration as soon as possible
- break
- self._schedule_reaper()
-
-
-class Tunnel(object):
- """SSH tunnel."""
-
- def __init__(self, context):
- """Construct Tunnel."""
- self.context = context
- self._tunnel = None
-
- def bore(self, id):
- """Create SSH tunnel from given context."""
- ssh_opts = '-nNT'
- if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
- ssh_opts += 'v'
- else:
- ssh_opts += 'q'
-
- cmd = [
- 'ssh',
- ssh_opts,
- '-L',
- '{}:{}'.format(self.context.local_socket,
- self.context.remote_socket),
- '-i',
- self.context.identity_file,
- 'ssh://{}@{}'.format(self.context.username, self.context.hostname),
- ]
- logging.debug('Tunnel cmd "{}"'.format(' '.join(cmd)))
-
- self._tunnel = subprocess.Popen(cmd, close_fds=True)
- for i in range(10):
- # TODO: Make timeout configurable
- if os.path.exists(self.context.local_socket):
- break
- time.sleep(0.5)
- else:
- raise TimeoutError('Failed to create tunnel using: {}'.format(
- ' '.join(cmd)))
- weakref.finalize(self, self.close, id)
- return self
-
- def close(self, id):
- """Close SSH tunnel."""
- if self._tunnel is None:
- return
-
- self._tunnel.kill()
- self._tunnel.wait(300)
- os.remove(self.context.local_socket)
- self._tunnel = None