diff options
author | Jhon Honce <jhonce@redhat.com> | 2018-07-16 17:29:50 -0700 |
---|---|---|
committer | Atomic Bot <atomic-devel@projectatomic.io> | 2018-07-23 18:53:44 +0000 |
commit | 9a18681ba62d1a297809c243607a7b3763131c36 (patch) | |
tree | 8333f8727fd7d32f81cb1f54754ccd138a7e1063 /contrib/python/pypodman/lib | |
parent | 8569ed03056ce39e0dc163747089ed4b60b1b9b1 (diff) | |
download | podman-9a18681ba62d1a297809c243607a7b3763131c36.tar.gz podman-9a18681ba62d1a297809c243607a7b3763131c36.tar.bz2 podman-9a18681ba62d1a297809c243607a7b3763131c36.zip |
[WIP] Refactor and simplify python builds
* pypodman namespaced in site-packages
* version numbers pulled from requirements.txt
* add python-podman spec file to install eggs
Signed-off-by: Jhon Honce <jhonce@redhat.com>
Closes: #1106
Approved by: rhatdan
Diffstat (limited to 'contrib/python/pypodman/lib')
-rw-r--r-- | contrib/python/pypodman/lib/__init__.py | 11 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/action_base.py | 84 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/__init__.py | 7 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/images_action.py | 88 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/ps_action.py | 76 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/rm_action.py | 51 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/rmi_action.py | 50 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/config.py | 212 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/future_abstract.py | 29 | ||||
-rwxr-xr-x | contrib/python/pypodman/lib/pypodman.py | 76 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/report.py | 67 |
11 files changed, 0 insertions, 751 deletions
diff --git a/contrib/python/pypodman/lib/__init__.py b/contrib/python/pypodman/lib/__init__.py deleted file mode 100644 index 5a8303668..000000000 --- a/contrib/python/pypodman/lib/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Remote podman client support library.""" -from .action_base import AbstractActionBase -from .config import PodmanArgumentParser -from .report import Report, ReportColumn - -__all__ = [ - 'AbstractActionBase', - 'PodmanArgumentParser', - 'Report', - 'ReportColumn', -] diff --git a/contrib/python/pypodman/lib/action_base.py b/contrib/python/pypodman/lib/action_base.py deleted file mode 100644 index ff2922262..000000000 --- a/contrib/python/pypodman/lib/action_base.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Base class for all actions of remote client.""" -import abc -from functools import lru_cache - -import podman - - -class AbstractActionBase(abc.ABC): - """Base class for all actions of remote client.""" - - @classmethod - @abc.abstractmethod - def subparser(cls, parser): - """Define parser for this action. Subclasses must implement. - - API: - Use set_defaults() to set attributes "class_" and "method". These will - be invoked as class_(parsed_args).method() - """ - parser.add_argument( - '--all', - action='store_true', - help=('list all items.' - ' (default: no-op, included for compatibility.)')) - parser.add_argument( - '--no-trunc', - '--notruncate', - action='store_false', - dest='truncate', - default=True, - help='Display extended information. (default: False)') - parser.add_argument( - '--noheading', - action='store_false', - dest='heading', - default=True, - help=('Omit the table headings from the output.' - ' (default: False)')) - parser.add_argument( - '--quiet', - action='store_true', - help='List only the IDs. (default: %(default)s)') - - def __init__(self, args): - """Construct class.""" - self._args = args - - @property - def remote_uri(self): - """URI for remote side of connection.""" - return self._args.remote_uri - - @property - def local_uri(self): - """URI for local side of connection.""" - return self._args.local_uri - - @property - def identity_file(self): - """Key for authenication.""" - return self._args.identity_file - - @property - @lru_cache(maxsize=1) - def client(self): - """Podman remote client for communicating.""" - if self._args.host is None: - return podman.Client( - uri=self.local_uri) - else: - return podman.Client( - uri=self.local_uri, - remote_uri=self.remote_uri, - identity_file=self.identity_file) - - def __repr__(self): - """Compute the “official” string representation of object.""" - return ("{}(local_uri='{}', remote_uri='{}'," - " identity_file='{}')").format( - self.__class__, - self.local_uri, - self.remote_uri, - self.identity_file, - ) diff --git a/contrib/python/pypodman/lib/actions/__init__.py b/contrib/python/pypodman/lib/actions/__init__.py deleted file mode 100644 index cdc58b6ab..000000000 --- a/contrib/python/pypodman/lib/actions/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Module to export all the podman subcommands.""" -from .images_action import Images -from .ps_action import Ps -from .rm_action import Rm -from .rmi_action import Rmi - -__all__ = ['Images', 'Ps', 'Rm', 'Rmi'] diff --git a/contrib/python/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/lib/actions/images_action.py deleted file mode 100644 index f6a7497e5..000000000 --- a/contrib/python/pypodman/lib/actions/images_action.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Remote client commands dealing with images.""" -import operator -from collections import OrderedDict - -import humanize -import podman - -from .. import AbstractActionBase, Report, ReportColumn - - -class Images(AbstractActionBase): - """Class for Image manipulation.""" - - @classmethod - def subparser(cls, parent): - """Add Images commands to parent parser.""" - parser = parent.add_parser('images', help='list images') - super().subparser(parser) - parser.add_argument( - '--sort', - choices=['created', 'id', 'repository', 'size', 'tag'], - default='created', - type=str.lower, - help=('Change sort ordered of displayed images.' - ' (default: %(default)s)')) - - group = parser.add_mutually_exclusive_group() - group.add_argument( - '--digests', - action='store_true', - help='Include digests with images. (default: %(default)s)') - parser.set_defaults(class_=cls, method='list') - - def __init__(self, args): - """Construct Images class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'name': - ReportColumn('name', 'REPOSITORY', 40), - 'tag': - ReportColumn('tag', 'TAG', 10), - 'id': - ReportColumn('id', 'IMAGE ID', 12), - 'created': - ReportColumn('created', 'CREATED', 12), - 'size': - ReportColumn('size', 'SIZE', 8), - 'repoDigests': - ReportColumn('repoDigests', 'DIGESTS', 35), - }) - - def list(self): - """List images.""" - images = sorted( - self.client.images.list(), - key=operator.attrgetter(self._args.sort)) - if len(images) == 0: - return 0 - - rows = list() - for image in images: - fields = dict(image) - fields.update({ - 'created': - humanize.naturaldate(podman.datetime_parse(image.created)), - 'size': - humanize.naturalsize(int(image.size)), - 'repoDigests': - ' '.join(image.repoDigests), - }) - - for r in image.repoTags: - name, tag = r.split(':', 1) - fields.update({ - 'name': name, - 'tag': tag, - }) - rows.append(fields) - - if not self._args.digests: - del self.columns['repoDigests'] - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/lib/actions/ps_action.py b/contrib/python/pypodman/lib/actions/ps_action.py deleted file mode 100644 index 4bbec5578..000000000 --- a/contrib/python/pypodman/lib/actions/ps_action.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Remote client commands dealing with containers.""" -import operator -from collections import OrderedDict - -import humanize -import podman - -from .. import AbstractActionBase, Report, ReportColumn - - -class Ps(AbstractActionBase): - """Class for Container manipulation.""" - - @classmethod - def subparser(cls, parent): - """Add Images command to parent parser.""" - parser = parent.add_parser('ps', help='list containers') - super().subparser(parser) - parser.add_argument( - '--sort', - choices=[ - 'createdat', 'id', 'image', 'names', 'runningfor', 'size', - 'status' - ], - default='createdat', - type=str.lower, - help=('Change sort ordered of displayed containers.' - ' (default: %(default)s)')) - parser.set_defaults(class_=cls, method='list') - - def __init__(self, args): - """Construct Ps class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'id': - ReportColumn('id', 'CONTAINER ID', 14), - 'image': - ReportColumn('image', 'IMAGE', 30), - 'command': - ReportColumn('column', 'COMMAND', 20), - 'createdat': - ReportColumn('createdat', 'CREATED', 12), - 'status': - ReportColumn('status', 'STATUS', 10), - 'ports': - ReportColumn('ports', 'PORTS', 28), - 'names': - ReportColumn('names', 'NAMES', 18) - }) - - def list(self): - """List containers.""" - # TODO: Verify sorting on dates and size - ctnrs = sorted( - self.client.containers.list(), - key=operator.attrgetter(self._args.sort)) - if len(ctnrs) == 0: - return 0 - - rows = list() - for ctnr in ctnrs: - fields = dict(ctnr) - fields.update({ - 'command': - ' '.join(ctnr.command), - 'createdat': - humanize.naturaldate(podman.datetime_parse(ctnr.createdat)), - }) - rows.append(fields) - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/lib/actions/rm_action.py b/contrib/python/pypodman/lib/actions/rm_action.py deleted file mode 100644 index bd8950bd6..000000000 --- a/contrib/python/pypodman/lib/actions/rm_action.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Remote client command for deleting containers.""" -import sys - -import podman - -from .. import AbstractActionBase - - -class Rm(AbstractActionBase): - """Class for removing containers from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Rm command to parent parser.""" - parser = parent.add_parser('rm', help='delete container(s)') - parser.add_argument( - '-f', - '--force', - action='store_true', - help=('force delete of running container(s).' - ' (default: %(default)s)')) - parser.add_argument( - 'targets', nargs='*', help='container id(s) to delete') - parser.set_defaults(class_=cls, method='remove') - - def __init__(self, args): - """Construct Rm class.""" - super().__init__(args) - if len(args.targets) < 1: - raise ValueError('You must supply at least one container id' - ' or name to be deleted.') - - def remove(self): - """Remove container(s).""" - for id in self._args.targets: - try: - ctnr = self.client.containers.get(id) - ctnr.remove(self._args.force) - print(id) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/lib/actions/rmi_action.py b/contrib/python/pypodman/lib/actions/rmi_action.py deleted file mode 100644 index 91f0deeaf..000000000 --- a/contrib/python/pypodman/lib/actions/rmi_action.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Remote client command for deleting images.""" -import sys - -import podman - -from .. import AbstractActionBase - - -class Rmi(AbstractActionBase): - """Clas for removing images from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Rmi command to parent parser.""" - parser = parent.add_parser('rmi', help='delete image(s)') - parser.add_argument( - '-f', - '--force', - action='store_true', - help=('force delete of image(s) and associated containers.' - ' (default: %(default)s)')) - parser.add_argument('targets', nargs='*', help='image id(s) to delete') - parser.set_defaults(class_=cls, method='remove') - - def __init__(self, args): - """Construct Rmi class.""" - super().__init__(args) - if len(args.targets) < 1: - raise ValueError('You must supply at least one image id' - ' or name to be deleted.') - - def remove(self): - """Remove image(s).""" - for id in self._args.targets: - try: - img = self.client.images.get(id) - img.remove(self._args.force) - print(id) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/lib/config.py b/contrib/python/pypodman/lib/config.py deleted file mode 100644 index e687697ef..000000000 --- a/contrib/python/pypodman/lib/config.py +++ /dev/null @@ -1,212 +0,0 @@ -import argparse -import curses -import getpass -import inspect -import logging -import os -import sys - -import pkg_resources - -import pytoml - -# TODO: setup.py and obtain __version__ from rpm.spec -try: - __version__ = pkg_resources.get_distribution('pypodman').version -except Exception: - __version__ = '0.0.0' - - -class HelpFormatter(argparse.RawDescriptionHelpFormatter): - """Set help width to screen size.""" - - def __init__(self, *args, **kwargs): - """Construct HelpFormatter using screen width.""" - if 'width' not in kwargs: - kwargs['width'] = 80 - try: - height, width = curses.initscr().getmaxyx() - kwargs['width'] = width - finally: - curses.endwin() - super().__init__(*args, **kwargs) - - -class PodmanArgumentParser(argparse.ArgumentParser): - """Default remote podman configuration.""" - - def __init__(self, **kwargs): - """Construct the parser.""" - kwargs['add_help'] = True - kwargs['allow_abbrev'] = True - kwargs['description'] = __doc__ - kwargs['formatter_class'] = HelpFormatter - - super().__init__(**kwargs) - - def initialize_parser(self): - """Initialize parser without causing recursion meltdown.""" - self.add_argument( - '--version', - action='version', - version='%(prog)s v. ' + __version__) - self.add_argument( - '--log-level', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - default='WARNING', - type=str.upper, - help='set logging level for events. (default: %(default)s)', - ) - self.add_argument( - '--run-dir', - metavar='DIRECTORY', - help=('directory to place local socket bindings.' - ' (default: XDG_RUNTIME_DIR/pypodman')) - self.add_argument( - '--user', - default=getpass.getuser(), - help='Authenicating user on remote host. (default: %(default)s)') - self.add_argument( - '--host', help='name of remote host. (default: None)') - self.add_argument( - '--remote-socket-path', - metavar='PATH', - help=('path of podman socket on remote host' - ' (default: /run/podman/io.projectatomic.podman)')) - self.add_argument( - '--identity-file', - metavar='PATH', - help=('path to ssh identity file. (default: ~user/.ssh/id_dsa)')) - self.add_argument( - '--config-home', - metavar='DIRECTORY', - help=('home of configuration "pypodman.conf".' - ' (default: XDG_CONFIG_HOME/pypodman')) - - actions_parser = self.add_subparsers( - dest='subparser_name', help='actions') - - # pull in plugin(s) code for each subcommand - for name, obj in inspect.getmembers( - sys.modules['lib.actions'], - lambda member: inspect.isclass(member)): - if hasattr(obj, 'subparser'): - try: - obj.subparser(actions_parser) - except NameError as e: - logging.critical(e) - logging.warning( - 'See subparser configuration for Class "{}"'.format( - name)) - sys.exit(3) - - def parse_args(self, args=None, namespace=None): - """Parse command line arguments, backed by env var and config_file.""" - self.initialize_parser() - cooked = super().parse_args(args, namespace) - return self.resolve_configuration(cooked) - - def resolve_configuration(self, args): - """Find and fill in any arguments not passed on command line.""" - args.xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp') - args.xdg_config_home = os.environ.get('XDG_CONFIG_HOME', - os.path.expanduser('~/.config')) - args.xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg') - - # Configuration file(s) are optional, - # required arguments may be provided elsewhere - config = {'default': {}} - dirs = args.xdg_config_dirs.split(':') - dirs.extend((args.xdg_config_home, args.config_home)) - for dir_ in dirs: - if dir_ is None: - continue - try: - with open(os.path.join(dir_, 'pypodman/pypodman.conf'), - 'r') as stream: - config.update(pytoml.load(stream)) - except OSError: - pass - - def reqattr(name, value): - if value: - setattr(args, name, value) - return value - self.error('Required argument "%s" is not configured.' % name) - - reqattr( - 'run_dir', - getattr(args, 'run_dir') - or os.environ.get('RUN_DIR') - or config['default'].get('run_dir') - or os.path.join(args.xdg_runtime_dir, 'pypodman') - ) # yapf: disable - - setattr( - args, - 'host', - getattr(args, 'host') - or os.environ.get('HOST') - or config['default'].get('host') - ) # yapf:disable - - reqattr( - 'user', - getattr(args, 'user') - or os.environ.get('USER') - or config['default'].get('user') - or getpass.getuser() - ) # yapf:disable - - reqattr( - 'remote_socket_path', - getattr(args, 'remote_socket_path') - or os.environ.get('REMOTE_SOCKET_PATH') - or config['default'].get('remote_socket_path') - or '/run/podman/io.projectatomic.podman' - ) # yapf:disable - - reqattr( - 'log_level', - getattr(args, 'log_level') - or os.environ.get('LOG_LEVEL') - or config['default'].get('log_level') - or logging.WARNING - ) # yapf:disable - - setattr( - args, - 'identity_file', - getattr(args, 'identity_file') - or os.environ.get('IDENTITY_FILE') - or config['default'].get('identity_file') - or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.user)) - ) # yapf:disable - - if not os.path.isfile(args.identity_file): - args.identity_file = None - - if args.host: - args.local_socket_path = os.path.join(args.run_dir, - "podman.socket") - else: - args.local_socket_path = args.remote_socket_path - - args.local_uri = "unix:{}".format(args.local_socket_path) - args.remote_uri = "ssh://{}@{}{}".format(args.user, args.host, - args.remote_socket_path) - return args - - def exit(self, status=0, message=None): - """Capture message and route to logger.""" - if message: - log = logging.info if status == 0 else logging.error - log(message) - super().exit(status) - - def error(self, message): - """Capture message and route to logger.""" - logging.error('{}: {}'.format(self.prog, message)) - logging.error("Try '{} --help' for more information.".format( - self.prog)) - super().exit(2) diff --git a/contrib/python/pypodman/lib/future_abstract.py b/contrib/python/pypodman/lib/future_abstract.py deleted file mode 100644 index 75a1d42db..000000000 --- a/contrib/python/pypodman/lib/future_abstract.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Utilities for with-statement contexts. See PEP 343.""" - -import abc - -import _collections_abc - -try: - from contextlib import AbstractContextManager -except ImportError: - # Copied from python3.7 library as "backport" - class AbstractContextManager(abc.ABC): - """An abstract base class for context managers.""" - - def __enter__(self): - """Return `self` upon entering the runtime context.""" - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - """Raise any exception triggered within the runtime context.""" - return None - - @classmethod - def __subclasshook__(cls, C): - """Check whether subclass is considered a subclass of this ABC.""" - if cls is AbstractContextManager: - return _collections_abc._check_methods(C, "__enter__", - "__exit__") - return NotImplemented diff --git a/contrib/python/pypodman/lib/pypodman.py b/contrib/python/pypodman/lib/pypodman.py deleted file mode 100755 index 4bc71a9cc..000000000 --- a/contrib/python/pypodman/lib/pypodman.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -"""Remote podman client.""" - -import logging -import os -import sys - -import lib.actions -from lib import PodmanArgumentParser - -assert lib.actions # silence pyflakes - - -def main(): - """Entry point.""" - # Setup logging so we use stderr and can change logging level later - # Do it now before there is any chance of a default setup hardcoding crap. - log = logging.getLogger() - fmt = logging.Formatter('%(asctime)s | %(levelname)-8s | %(message)s', - '%Y-%m-%d %H:%M:%S %Z') - stderr = logging.StreamHandler(stream=sys.stderr) - stderr.setFormatter(fmt) - log.addHandler(stderr) - log.setLevel(logging.WARNING) - - parser = PodmanArgumentParser() - args = parser.parse_args() - - log.setLevel(args.log_level) - logging.debug('Logging initialized at level {}'.format( - logging.getLevelName(logging.getLogger().getEffectiveLevel()))) - - def want_tb(): - """Add traceback when logging events.""" - return log.getEffectiveLevel() == logging.DEBUG - - try: - if not os.path.exists(args.run_dir): - os.makedirs(args.run_dir) - except PermissionError as e: - logging.critical(e, exc_info=want_tb()) - sys.exit(6) - - # class_(args).method() are set by the sub-command's parser - returncode = None - try: - obj = args.class_(args) - except Exception as e: - logging.critical(repr(e), exc_info=want_tb()) - logging.warning('See subparser "{}" configuration.'.format( - args.subparser_name)) - sys.exit(5) - - try: - returncode = getattr(obj, args.method)() - except AttributeError as e: - logging.critical(e, exc_info=want_tb()) - logging.warning('See subparser "{}" configuration.'.format( - args.subparser_name)) - returncode = 3 - except KeyboardInterrupt: - pass - except ( - ConnectionRefusedError, - ConnectionResetError, - TimeoutError, - ) as e: - logging.critical(e, exc_info=want_tb()) - logging.info('Review connection arguments for correctness.') - returncode = 4 - - return 0 if returncode is None else returncode - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/contrib/python/pypodman/lib/report.py b/contrib/python/pypodman/lib/report.py deleted file mode 100644 index 25fe2ae0d..000000000 --- a/contrib/python/pypodman/lib/report.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Report Manager.""" -import sys -from collections import namedtuple - -from .future_abstract import AbstractContextManager - - -class ReportColumn(namedtuple('ReportColumn', 'key display width default')): - """Hold attributes of output column.""" - - __slots__ = () - - def __new__(cls, key, display, width, default=None): - """Add defaults for attributes.""" - return super(ReportColumn, cls).__new__(cls, key, display, width, - default) - - -class Report(AbstractContextManager): - """Report Manager.""" - - def __init__(self, columns, heading=True, epilog=None, file=sys.stdout): - """Construct Report. - - columns is a mapping for named fields to column headings. - headers True prints headers on table. - epilog will be printed when the report context is closed. - """ - self._columns = columns - self._file = file - self._heading = heading - self.epilog = epilog - self._format = None - - def row(self, **fields): - """Print row for report.""" - if self._heading: - hdrs = {k: v.display for (k, v) in self._columns.items()} - print(self._format.format(**hdrs), flush=True, file=self._file) - self._heading = False - fields = {k: str(v) for k, v in fields.items()} - print(self._format.format(**fields)) - - def __exit__(self, exc_type, exc_value, traceback): - """Leave Report context and print epilog if provided.""" - if self.epilog: - print(self.epilog, flush=True, file=self._file) - - def layout(self, iterable, keys, truncate=True): - """Use data and headings build format for table to fit.""" - format = [] - - for key in keys: - value = max(map(lambda x: len(str(x.get(key, ''))), iterable)) - # print('key', key, 'value', value) - - if truncate: - row = self._columns.get( - key, ReportColumn(key, key.upper(), len(key))) - if value < row.width: - step = row.width if value == 0 else value - value = max(len(key), step) - elif value > row.width: - value = row.width if row.width != 0 else value - - format.append('{{{0}:{1}.{1}}}'.format(key, value)) - self._format = ' '.join(format) |