222 lines
6.9 KiB
Python
222 lines
6.9 KiB
Python
"""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)
|