You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
221 lines
6.9 KiB
221 lines
6.9 KiB
"""A client for communicating with a Podman varlink service.""" |
|
|
|
from __future__ import absolute_import |
|
|
|
import errno |
|
import logging |
|
import os |
|
from urllib.parse import urlparse |
|
|
|
from varlink import Client as VarlinkClient |
|
from varlink import VarlinkError |
|
|
|
from .libs import cached_property |
|
from .libs.containers import Containers |
|
from .libs.errors import error_factory |
|
from .libs.images import Images |
|
from .libs.system import System |
|
from .libs.tunnel import Context, Portal, Tunnel |
|
from .libs.pods import Pods |
|
|
|
|
|
class BaseClient(): |
|
"""Context manager for API workers to access varlink.""" |
|
|
|
def __init__(self, context): |
|
"""Construct Client.""" |
|
self._client = None |
|
self._iface = None |
|
self._context = context |
|
|
|
def __call__(self): |
|
"""Support being called for old API.""" |
|
return self |
|
|
|
@classmethod |
|
def factory(cls, |
|
uri=None, |
|
interface='io.podman', |
|
*_args, |
|
**kwargs): |
|
"""Construct a Client based on input.""" |
|
# pylint: disable=keyword-arg-before-vararg |
|
log_level = os.environ.get('LOG_LEVEL') |
|
if log_level is not None: |
|
logging.basicConfig(level=logging.getLevelName(log_level.upper())) |
|
|
|
if uri is None: |
|
raise ValueError('uri is required and cannot be None') |
|
if interface is None: |
|
raise ValueError('interface is required and cannot be None') |
|
|
|
unsupported = set(kwargs.keys()).difference( |
|
('uri', 'interface', 'remote_uri', 'identity_file', |
|
'ignore_hosts', 'known_hosts')) |
|
if unsupported: |
|
raise ValueError('Unknown keyword arguments: {}'.format( |
|
', '.join(unsupported))) |
|
|
|
local_path = urlparse(uri).path |
|
if local_path == '': |
|
raise ValueError('path is required for uri,' |
|
' expected format "unix://path_to_socket"') |
|
|
|
if kwargs.get('remote_uri') is None: |
|
return LocalClient(Context(uri, interface)) |
|
|
|
required = ('{} is required, expected format' |
|
' "ssh://user@hostname[:port]/path_to_socket".') |
|
|
|
# Remote access requires the full tuple of information |
|
if kwargs.get('remote_uri') is None: |
|
raise ValueError(required.format('remote_uri')) |
|
|
|
remote = urlparse(kwargs['remote_uri']) |
|
if remote.username is None: |
|
raise ValueError(required.format('username')) |
|
if remote.path == '': |
|
raise ValueError(required.format('path')) |
|
if remote.hostname is None: |
|
raise ValueError(required.format('hostname')) |
|
|
|
return RemoteClient( |
|
Context( |
|
uri, |
|
interface, |
|
local_path, |
|
remote.path, |
|
remote.username, |
|
remote.hostname, |
|
remote.port, |
|
kwargs.get('identity_file'), |
|
kwargs.get('ignore_hosts'), |
|
kwargs.get('known_hosts'), |
|
)) |
|
|
|
|
|
class LocalClient(BaseClient): |
|
"""Context manager for API workers to access varlink.""" |
|
|
|
def __enter__(self): |
|
"""Enter context for LocalClient.""" |
|
self._client = VarlinkClient(address=self._context.uri) |
|
self._iface = self._client.open(self._context.interface) |
|
return self._iface |
|
|
|
def __exit__(self, e_type, e, e_traceback): |
|
"""Cleanup context for LocalClient.""" |
|
if hasattr(self._client, 'close'): |
|
# pylint: disable=no-member |
|
self._client.close() |
|
self._iface.close() |
|
|
|
if isinstance(e, VarlinkError): |
|
raise error_factory(e) |
|
|
|
|
|
class RemoteClient(BaseClient): |
|
"""Context manager for API workers to access remote varlink.""" |
|
|
|
def __init__(self, context): |
|
"""Construct RemoteCLient.""" |
|
super().__init__(context) |
|
self._portal = Portal() |
|
|
|
def __enter__(self): |
|
"""Context manager for API workers to access varlink.""" |
|
tunnel = self._portal.get(self._context.uri) |
|
if tunnel is None: |
|
tunnel = Tunnel(self._context).bore() |
|
self._portal[self._context.uri] = tunnel |
|
|
|
try: |
|
self._client = VarlinkClient(address=self._context.uri) |
|
self._iface = self._client.open(self._context.interface) |
|
return self._iface |
|
except Exception: |
|
tunnel.close() |
|
raise |
|
|
|
def __exit__(self, e_type, e, e_traceback): |
|
"""Cleanup context for RemoteClient.""" |
|
if hasattr(self._client, 'close'): |
|
# pylint: disable=no-member |
|
self._client.close() |
|
self._iface.close() |
|
|
|
# set timer to shutdown ssh tunnel |
|
# self._portal.get(self._context.uri).close() |
|
if isinstance(e, VarlinkError): |
|
raise error_factory(e) |
|
|
|
|
|
class Client(): |
|
"""A client for communicating with a Podman varlink service. |
|
|
|
Example: |
|
|
|
>>> import podman |
|
>>> c = podman.Client() |
|
>>> c.system.versions |
|
|
|
Example remote podman: |
|
|
|
>>> import podman |
|
>>> c = podman.Client(uri='unix:/tmp/podman.sock', |
|
remote_uri='ssh://user@host/run/podman/io.podman', |
|
identity_file='~/.ssh/id_rsa') |
|
""" |
|
|
|
def __init__(self, |
|
uri='unix:/run/podman/io.podman', |
|
interface='io.podman', |
|
**kwargs): |
|
"""Construct a podman varlink Client. |
|
|
|
uri from default systemd unit file. |
|
interface from io.podman.varlink, do not change unless |
|
you are a varlink guru. |
|
""" |
|
self._client = BaseClient.factory(uri, interface, **kwargs) |
|
|
|
address = "{}-{}".format(uri, interface) |
|
# Quick validation of connection data provided |
|
try: |
|
if not System(self._client).ping(): |
|
raise ConnectionRefusedError( |
|
errno.ECONNREFUSED, |
|
('Failed varlink connection "{}"').format(address)) |
|
except FileNotFoundError as ex: |
|
raise ConnectionError( |
|
errno.ECONNREFUSED, |
|
('Failed varlink connection "{}".' |
|
' Is podman socket or service running?' |
|
).format(address)) from ex |
|
|
|
def __enter__(self): |
|
"""Return `self` upon entering the runtime context.""" |
|
return self |
|
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
"""Raise any exception triggered within the runtime context.""" |
|
|
|
@cached_property |
|
def system(self): |
|
"""Manage system model for podman.""" |
|
return System(self._client) |
|
|
|
@cached_property |
|
def images(self): |
|
"""Manage images model for libpod.""" |
|
return Images(self._client) |
|
|
|
@cached_property |
|
def containers(self): |
|
"""Manage containers model for libpod.""" |
|
return Containers(self._client) |
|
|
|
@cached_property |
|
def pods(self): |
|
"""Manage pods model for libpod.""" |
|
return Pods(self._client)
|
|
|