Files
Jhon Honce 40cb756801 Add support for pod commands
* Add support for pod -- create, inspect, kill, pause, ps, rm,
  restart, start, stop, top, unpause
* Update pylintrc to better reflect pep8 code standards
* Fix various pylint reported errors
* Refactor code that determines screen width to no longer
  require initializing curses.  Improved start up time and
  pushing data blob down ssh tunnel.
* Correct pod-create man page, cgroupparent not boolean
* Abort integration tests if podman service fails to start

Signed-off-by: Jhon Honce <jhonce@redhat.com>
2018-10-12 09:34:23 -07:00

241 lines
8.1 KiB
Python

"""Parse configuration while building subcommands."""
import argparse
import getpass
import inspect
import logging
import os
import shutil
import sys
from contextlib import suppress
from pathlib import Path
import pkg_resources
import pytoml
from .parser_actions import PathAction, PositiveIntAction
# TODO: setup.py and obtain __version__ from rpm.spec
try:
__version__ = pkg_resources.get_distribution('pypodman').version
except Exception: # pylint: disable=broad-except
__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:
try:
size = shutil.get_terminal_size()
kwargs['width'] = size.columns
except Exception: # pylint: disable=broad-except
kwargs['width'] = 80
super().__init__(*args, **kwargs)
class PodmanArgumentParser(argparse.ArgumentParser):
"""Default remote podman configuration."""
def __init__(self, **kwargs):
"""Construct the parser."""
kwargs['add_help'] = True
kwargs['description'] = ('Portable and simple management'
' tool for containers and images')
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(
'--username',
'-l',
help='Authenicating user on remote host. (default: {})'.format(
getpass.getuser()))
self.add_argument(
'--host', help='name of remote host. (default: None)')
self.add_argument(
'--port',
'-p',
action=PositiveIntAction,
help='port for ssh tunnel to remote host. (default: 22)')
self.add_argument(
'--remote-socket-path',
metavar='PATH',
help=('path of podman socket on remote host'
' (default: /run/podman/io.podman)'))
self.add_argument(
'--identity-file',
'-i',
action=PathAction,
help='path to ssh identity file. (default: ~user/.ssh/id_dsa)')
self.add_argument(
'--config-home',
metavar='DIRECTORY',
action=PathAction,
help=('home of configuration "pypodman.conf".'
' (default: XDG_CONFIG_HOME/pypodman)'))
actions_parser = self.add_subparsers(
dest='subparser_name', help='commands')
# import buried here to prevent import loops
import pypodman.lib.actions # pylint: disable=cyclic-import
assert pypodman.lib.actions
# pull in plugin(s) code for each subcommand
for name, obj in inspect.getmembers(
sys.modules['pypodman.lib.actions'],
predicate=inspect.isclass):
if hasattr(obj, 'subparser'):
try:
obj.subparser(actions_parser)
except NameError as e:
logging.critical(e)
logging.warning(
'See subparser configuration for Class "%s"', 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
with suppress(OSError):
cnf = Path(dir_, 'pypodman', 'pypodman.conf')
with cnf.open() as stream:
config.update(pytoml.load(stream))
def reqattr(name, value):
"""Raise an error if value is unset."""
if value:
setattr(args, name, value)
return value
return self.error(
'Required argument "{}" is not configured.'.format(name))
reqattr(
'run_dir',
getattr(args, 'run_dir')
or os.environ.get('RUN_DIR')
or config['default'].get('run_dir')
or Path(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(
'username',
getattr(args, 'username')
or os.environ.get('USER')
or os.environ.get('LOGNAME')
or config['default'].get('username')
or getpass.getuser()
) # yapf:disable
reqattr(
'port',
getattr(args, 'port')
or os.environ.get('PORT')
or config['default'].get('port', None)
or 22
) # 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.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.username))
) # yapf:disable
if not os.path.isfile(args.identity_file):
args.identity_file = None
if args.host:
args.local_socket_path = Path(args.run_dir, 'podman.socket')
else:
args.local_socket_path = args.remote_socket_path
args.local_uri = 'unix:{}'.format(args.local_socket_path)
if args.host:
components = ['ssh://', args.username, '@', args.host]
if args.port:
components.extend((':', str(args.port)))
components.append(args.remote_socket_path)
args.remote_uri = ''.join(components)
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('%s: %s', self.prog, message)
logging.error("Try '%s --help' for more information.", self.prog)
super().exit(2)