diff options
Diffstat (limited to 'contrib/python/pypodman')
18 files changed, 304 insertions, 102 deletions
diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile index cd0fcf1de..272145c5d 100644 --- a/contrib/python/pypodman/Makefile +++ b/contrib/python/pypodman/Makefile @@ -4,6 +4,7 @@ PODMAN_VERSION ?= '0.0.4' .PHONY: python-pypodman python-pypodman: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py sdist bdist .PHONY: lint @@ -16,6 +17,7 @@ integration: .PHONY: install install: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py install --root ${DESTDIR} .PHONY: upload diff --git a/contrib/python/pypodman/docs/man1/pypodman.1 b/contrib/python/pypodman/docs/man1/pypodman.1 index 09acb205b..45472dab0 100644 --- a/contrib/python/pypodman/docs/man1/pypodman.1 +++ b/contrib/python/pypodman/docs/man1/pypodman.1 @@ -85,7 +85,7 @@ overwriting earlier. Any missing items are ignored. .IP \[bu] 2 From \f[C]\-\-config\-home\f[] command line option + \f[C]pypodman/pypodman.conf\f[] .IP \[bu] 2 -From environment variable, for example: RUN_DIR +From environment variable prefixed with PODMAN_, for example: PODMAN_RUN_DIR .IP \[bu] 2 From command line option, for example: \[en]run\-dir .PP diff --git a/contrib/python/pypodman/pypodman/lib/__init__.py b/contrib/python/pypodman/pypodman/lib/__init__.py index 5525ddaef..be1b5f467 100644 --- a/contrib/python/pypodman/pypodman/lib/__init__.py +++ b/contrib/python/pypodman/pypodman/lib/__init__.py @@ -4,14 +4,15 @@ import sys import podman from pypodman.lib.action_base import AbstractActionBase from pypodman.lib.parser_actions import (BooleanAction, BooleanValidate, - PathAction, PositiveIntAction, - UnitAction) + ChangeAction, PathAction, + PositiveIntAction, UnitAction) from pypodman.lib.podman_parser import PodmanArgumentParser from pypodman.lib.report import Report, ReportColumn # Silence pylint overlording... assert BooleanAction assert BooleanValidate +assert ChangeAction assert PathAction assert PositiveIntAction assert UnitAction diff --git a/contrib/python/pypodman/pypodman/lib/actions/__init__.py b/contrib/python/pypodman/pypodman/lib/actions/__init__.py index 2668cd8ff..c0d77ddb1 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/__init__.py +++ b/contrib/python/pypodman/pypodman/lib/actions/__init__.py @@ -22,6 +22,8 @@ from pypodman.lib.actions.rm_action import Rm from pypodman.lib.actions.rmi_action import Rmi from pypodman.lib.actions.run_action import Run from pypodman.lib.actions.search_action import Search +from pypodman.lib.actions.start_action import Start +from pypodman.lib.actions.version_action import Version __all__ = [ 'Attach', @@ -47,4 +49,6 @@ __all__ = [ 'Rmi', 'Run', 'Search', + 'Start', + 'Version', ] diff --git a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py index 0da6a2078..21924e938 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase, BooleanAction +from pypodman.lib import AbstractActionBase, BooleanAction, ChangeAction class Commit(AbstractActionBase): @@ -12,7 +12,9 @@ class Commit(AbstractActionBase): def subparser(cls, parent): """Add Commit command to parent parser.""" parser = parent.add_parser( - 'commit', help='create image from container') + 'commit', + help='create image from container', + ) parser.add_argument( '--author', help='Set the author for the committed image', @@ -20,11 +22,7 @@ class Commit(AbstractActionBase): parser.add_argument( '--change', '-c', - choices=('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'), - action='append', - type=str.upper, - help='Apply the following possible changes to the created image', + action=ChangeAction, ) parser.add_argument( '--format', @@ -32,7 +30,8 @@ class Commit(AbstractActionBase): choices=('oci', 'docker'), default='oci', type=str.lower, - help='Set the format of the image manifest and metadata', + help='Set the format of the image manifest and metadata.' + ' (Ignored.)', ) parser.add_argument( '--iidfile', @@ -42,7 +41,8 @@ class Commit(AbstractActionBase): parser.add_argument( '--message', '-m', - help='Set commit message for committed image', + help='Set commit message for committed image' + ' (Only on docker images.)', ) parser.add_argument( '--pause', @@ -69,27 +69,11 @@ class Commit(AbstractActionBase): ) parser.set_defaults(class_=cls, method='commit') - def __init__(self, args): - """Construct Commit class.""" - if not args.container: - raise ValueError('You must supply one container id' - ' or name to be used as source.') - if not args.image: - raise ValueError('You must supply one image id' - ' or name to be created.') - super().__init__(args) - - # used only on client - del self.opts['image'] - del self.opts['container'] - def commit(self): """Create image from container.""" try: try: ctnr = self.client.containers.get(self._args.container[0]) - ident = ctnr.commit(**self.opts) - print(ident) except podman.ContainerNotFound as e: sys.stdout.flush() print( @@ -97,6 +81,17 @@ class Commit(AbstractActionBase): file=sys.stderr, flush=True) return 1 + else: + ident = ctnr.commit( + self.opts['image'][0], + change=self.opts.get('change', None), + message=self.opts.get('message', None), + pause=self.opts['pause'], + author=self.opts.get('author', None), + ) + + if not self.opts['quiet']: + print(ident) except podman.ErrorOccurred as e: sys.stdout.flush() print( @@ -104,3 +99,4 @@ class Commit(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/create_action.py b/contrib/python/pypodman/pypodman/lib/actions/create_action.py index d9631763a..26a312bb1 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/create_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/create_action.py @@ -21,7 +21,7 @@ class Create(AbstractActionBase): parser.add_argument('image', nargs=1, help='source image id') parser.add_argument( 'command', - nargs='*', + nargs=parent.REMAINDER, help='command and args to run.', ) parser.set_defaults(class_=cls, method='create') diff --git a/contrib/python/pypodman/pypodman/lib/actions/export_action.py b/contrib/python/pypodman/pypodman/lib/actions/export_action.py index f62cd3535..7ef178c4c 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/export_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/export_action.py @@ -12,13 +12,16 @@ class Export(AbstractActionBase): def subparser(cls, parent): """Add Export command to parent parser.""" parser = parent.add_parser( - 'export', help='export container to tarball') + 'export', + help='export container to tarball', + ) parser.add_argument( '--output', '-o', metavar='PATH', nargs=1, - help='Write to a file', + required=True, + help='Write to this file on host', ) parser.add_argument( 'container', @@ -27,23 +30,11 @@ class Export(AbstractActionBase): ) parser.set_defaults(class_=cls, method='export') - def __init__(self, args): - """Construct Export class.""" - if not args.container: - raise ValueError('You must supply one container id' - ' or name to be used as source.') - - if not args.output: - raise ValueError('You must supply one filename' - ' to be created as tarball using --output.') - super().__init__(args) - def export(self): """Create tarball from container filesystem.""" try: try: ctnr = self.client.containers.get(self._args.container[0]) - ctnr.export(self._args.output[0]) except podman.ContainerNotFound as e: sys.stdout.flush() print( @@ -51,6 +42,8 @@ class Export(AbstractActionBase): file=sys.stderr, flush=True) return 1 + else: + ctnr.export(self._args.output[0]) except podman.ErrorOccurred as e: sys.stdout.flush() print( @@ -58,3 +51,4 @@ class Export(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/history_action.py b/contrib/python/pypodman/pypodman/lib/actions/history_action.py index 3e3f539fc..f9aaa54f6 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/history_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/history_action.py @@ -60,7 +60,7 @@ class History(AbstractActionBase): if self._args.human: fields.update({ 'size': - humanize.naturalsize(details.size, binary=True), + humanize.naturalsize(details.size), 'created': humanize.naturaldate( podman.datetime_parse(details.created)), diff --git a/contrib/python/pypodman/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/pypodman/lib/actions/images_action.py index d28e32db9..29bf90dd2 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/images_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/images_action.py @@ -37,7 +37,7 @@ class Images(AbstractActionBase): self.columns = OrderedDict({ 'name': - ReportColumn('name', 'REPOSITORY', 40), + ReportColumn('name', 'REPOSITORY', 0), 'tag': ReportColumn('tag', 'TAG', 10), 'id': @@ -65,18 +65,18 @@ class Images(AbstractActionBase): 'created': humanize.naturaldate(podman.datetime_parse(image.created)), 'size': - humanize.naturalsize(int(image.size), binary=True), + humanize.naturalsize(int(image.size)), 'repoDigests': ' '.join(image.repoDigests), }) for r in image.repoTags: - name, tag = r.split(':', 1) + name, tag = r.rsplit(':', 1) fields.update({ 'name': name, 'tag': tag, }) - rows.append(fields) + rows.append(fields) if not self._args.digests: del self.columns['repoDigests'] diff --git a/contrib/python/pypodman/pypodman/lib/actions/import_action.py b/contrib/python/pypodman/pypodman/lib/actions/import_action.py index 49b8a5a57..43448144a 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/import_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/import_action.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase +from pypodman.lib import AbstractActionBase, ChangeAction class Import(AbstractActionBase): @@ -12,18 +12,19 @@ class Import(AbstractActionBase): def subparser(cls, parent): """Add Import command to parent parser.""" parser = parent.add_parser( - 'import', help='import tarball as image filesystem') + 'import', + help='import tarball as image filesystem', + ) parser.add_argument( '--change', '-c', - action='append', - choices=('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR'), - type=str.upper, - help='Apply the following possible instructions', + action=ChangeAction, ) parser.add_argument( - '--message', '-m', help='Set commit message for imported image.') + '--message', + '-m', + help='Set commit message for imported image.', + ) parser.add_argument( 'source', metavar='PATH', @@ -38,18 +39,25 @@ class Import(AbstractActionBase): ) parser.set_defaults(class_=cls, method='import_') - def __init__(self, args): - """Construct Import class.""" - super().__init__(args) - def import_(self): """Import tarball as image filesystem.""" + # ImportImage() validates it's parameters therefore we need to create + # pristine dict() for keywords + options = {} + if 'message' in self.opts: + options['message'] = self.opts['message'] + if 'change' in self.opts and self.opts['change']: + options['changes'] = self.opts['change'] + + reference = self.opts['reference'][0] if 'reference' in self.opts\ + else None + try: ident = self.client.images.import_image( - self.opts.source, - self.opts.reference, - message=self.opts.message, - changes=self.opts.change) + self.opts['source'][0], + reference, + **options, + ) print(ident) except podman.ErrorOccurred as e: sys.stdout.flush() @@ -58,3 +66,4 @@ class Import(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 diff --git a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py index 514b4702a..a581e7e4e 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py @@ -23,9 +23,9 @@ class Inspect(AbstractActionBase): help='Type of object to inspect', ) parser.add_argument( - 'size', + '--size', action='store_true', - default=True, + default=False, help='Display the total file size if the type is a container.' ' Always True.') parser.add_argument( @@ -59,7 +59,7 @@ class Inspect(AbstractActionBase): def inspect(self): """Inspect provided podman objects.""" - output = {} + output = [] try: for ident in self._args.objects: obj = None @@ -78,7 +78,13 @@ class Inspect(AbstractActionBase): msg = 'Object "{}" not found'.format(ident) print(msg, file=sys.stderr, flush=True) else: - output.update(obj._asdict()) + fields = obj._asdict() + if not self._args.size: + try: + del fields['sizerootfs'] + except KeyError: + pass + output.append(fields) except podman.ErrorOccurred as e: sys.stdout.flush() print( diff --git a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py index cd7a7947d..62ceb2e67 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py @@ -16,6 +16,7 @@ class Ps(AbstractActionBase): """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', @@ -32,9 +33,9 @@ class Ps(AbstractActionBase): self.columns = OrderedDict({ 'id': - ReportColumn('id', 'CONTAINER ID', 14), + ReportColumn('id', 'CONTAINER ID', 12), 'image': - ReportColumn('image', 'IMAGE', 30), + ReportColumn('image', 'IMAGE', 31), 'command': ReportColumn('column', 'COMMAND', 20), 'createdat': @@ -49,10 +50,15 @@ class Ps(AbstractActionBase): def list(self): """List containers.""" + if self._args.all: + ictnrs = self.client.containers.list() + else: + ictnrs = filter( + lambda c: podman.FoldedString(c['status']) == 'running', + self.client.containers.list()) + # TODO: Verify sorting on dates and size - ctnrs = sorted( - self.client.containers.list(), - key=operator.attrgetter(self._args.sort)) + ctnrs = sorted(ictnrs, key=operator.attrgetter(self._args.sort)) if not ctnrs: return @@ -65,9 +71,6 @@ class Ps(AbstractActionBase): 'createdat': humanize.naturaldate(podman.datetime_parse(ctnr.createdat)), }) - - if self._args.truncate: - fields.update({'image': ctnr.image[-30:]}) rows.append(fields) with Report(self.columns, heading=self._args.heading) as report: diff --git a/contrib/python/pypodman/pypodman/lib/actions/run_action.py b/contrib/python/pypodman/pypodman/lib/actions/run_action.py index a63eb7917..6a6b3cb2c 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/run_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/run_action.py @@ -21,7 +21,7 @@ class Run(AbstractActionBase): parser.add_argument('image', nargs=1, help='source image id.') parser.add_argument( 'command', - nargs='*', + nargs=parent.REMAINDER, help='command and args to run.', ) parser.set_defaults(class_=cls, method='run') diff --git a/contrib/python/pypodman/pypodman/lib/actions/start_action.py b/contrib/python/pypodman/pypodman/lib/actions/start_action.py new file mode 100644 index 000000000..f312fb3fa --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/start_action.py @@ -0,0 +1,76 @@ +"""Remote client command for starting containers.""" +import sys + +import podman +from pypodman.lib import AbstractActionBase, BooleanAction + + +class Start(AbstractActionBase): + """Class for starting container.""" + + @classmethod + def subparser(cls, parent): + """Add Start command to parent parser.""" + parser = parent.add_parser('start', help='start container') + parser.add_argument( + '--attach', + '-a', + action=BooleanAction, + default=False, + help="Attach container's STDOUT and STDERR (default: %(default)s)") + parser.add_argument( + '--detach-keys', + metavar='KEY(s)', + default=4, + help='Override the key sequence for detaching a container.' + ' (format: a single character [a-Z] or ctrl-<value> where' + ' <value> is one of: a-z, @, ^, [, , or _) (default: ^D)') + parser.add_argument( + '--interactive', + '-i', + action=BooleanAction, + default=False, + help="Attach container's STDIN (default: %(default)s)") + # TODO: Implement sig-proxy + parser.add_argument( + '--sig-proxy', + action=BooleanAction, + default=False, + help="Proxy received signals to the process (default: %(default)s)" + ) + parser.add_argument( + 'containers', + nargs='+', + help='containers to start', + ) + parser.set_defaults(class_=cls, method='start') + + def start(self): + """Start provided containers.""" + stdin = sys.stdin if self.opts['interactive'] else None + stdout = sys.stdout if self.opts['attach'] else None + + try: + for ident in self._args.containers: + try: + ctnr = self.client.containers.get(ident) + ctnr.attach( + eot=self.opts['detach_keys'], + stdin=stdin, + stdout=stdout) + ctnr.start() + except podman.ContainerNotFound as e: + sys.stdout.flush() + print( + 'Container "{}" not found'.format(e.name), + file=sys.stderr, + flush=True) + else: + print(ident) + except podman.ErrorOccurred as e: + sys.stdout.flush() + print( + '{}'.format(e.reason).capitalize(), + file=sys.stderr, + flush=True) + return 1 diff --git a/contrib/python/pypodman/pypodman/lib/actions/version_action.py b/contrib/python/pypodman/pypodman/lib/actions/version_action.py new file mode 100644 index 000000000..12b6dc576 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/version_action.py @@ -0,0 +1,41 @@ +"""Remote client command for reporting on Podman service.""" +import json +import sys + +import podman +import yaml +from pypodman.lib import AbstractActionBase + + +class Version(AbstractActionBase): + """Class for reporting on Podman Service.""" + + @classmethod + def subparser(cls, parent): + """Add Version command to parent parser.""" + parser = parent.add_parser( + 'version', help='report version on podman service') + parser.set_defaults(class_=cls, method='version') + + def __init__(self, args): + """Construct Version class.""" + super().__init__(args) + + def version(self): + """Report on Podman Service.""" + try: + info = self.client.system.info() + except podman.ErrorOccurred as e: + sys.stdout.flush() + print( + '{}'.format(e.reason).capitalize(), + file=sys.stderr, + flush=True) + return 1 + else: + version = info._asdict()['podman'] + host = info._asdict()['host'] + print("Version {}".format(version['podman_version'])) + print("Go Version {}".format(version['go_version'])) + print("Git Commit {}".format(version['git_commit'])) + print("OS/Arch {}/{}".format(host["os"], host["arch"])) diff --git a/contrib/python/pypodman/pypodman/lib/parser_actions.py b/contrib/python/pypodman/pypodman/lib/parser_actions.py index 2a5859e47..77ee14761 100644 --- a/contrib/python/pypodman/pypodman/lib/parser_actions.py +++ b/contrib/python/pypodman/pypodman/lib/parser_actions.py @@ -4,9 +4,10 @@ Supplimental argparse.Action converters and validaters. The constructors are very verbose but remain for IDE support. """ import argparse +import copy import os -# API defined by argparse.Action shut up pylint +# API defined by argparse.Action therefore shut up pylint # pragma pylint: disable=redefined-builtin # pragma pylint: disable=too-few-public-methods # pragma pylint: disable=too-many-arguments @@ -36,7 +37,7 @@ class BooleanAction(argparse.Action): const=None, default=None, type=None, - choices=('True', 'False'), + choices=None, required=False, help=None, metavar='{True,False}'): @@ -58,11 +59,58 @@ class BooleanAction(argparse.Action): try: val = BooleanValidate()(values) except ValueError: - parser.error('{} must be True or False.'.format(self.dest)) + parser.error('"{}" must be True or False.'.format(option_string)) else: setattr(namespace, self.dest, val) +class ChangeAction(argparse.Action): + """Convert and validate change argument.""" + + def __init__(self, + option_strings, + dest, + nargs=None, + const=None, + default=[], + type=None, + choices=None, + required=False, + help=None, + metavar='OPT=VALUE'): + """Create ChangeAction object.""" + help = (help or '') + ('Apply change(s) to the new image.' + ' May be given multiple times.') + + super().__init__( + option_strings=option_strings, + dest=dest, + nargs=nargs, + const=const, + default=default, + type=type, + choices=choices, + required=required, + help=help, + metavar=metavar) + + def __call__(self, parser, namespace, values, option_string=None): + """Convert and Validate input.""" + items = getattr(namespace, self.dest, None) or [] + items = copy.copy(items) + + choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', + 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR') + + opt, val = values.split('=', 1) + if opt not in choices: + parser.error('Option "{}" is not supported by argument "{}",' + ' valid options are: {}'.format( + opt, option_string, ', '.join(choices))) + items.append(values) + setattr(namespace, self.dest, items) + + class UnitAction(argparse.Action): """Validate number given is positive integer, with optional suffix.""" @@ -78,8 +126,8 @@ class UnitAction(argparse.Action): help=None, metavar='UNIT'): """Create UnitAction object.""" - help = (help or metavar or dest - ) + ' (format: <number>[<unit>], where unit = b, k, m or g)' + help = (help or metavar or dest)\ + + ' (format: <number>[<unit>], where unit = b, k, m or g)' super().__init__( option_strings=option_strings, dest=dest, @@ -99,15 +147,15 @@ class UnitAction(argparse.Action): except ValueError: if not values[:-1].isdigit(): msg = ('{} must be a positive integer,' - ' with optional suffix').format(self.dest) + ' with optional suffix').format(option_string) parser.error(msg) if not values[-1] in ('b', 'k', 'm', 'g'): msg = '{} only supports suffices of: b, k, m, g'.format( - self.dest) + option_string) parser.error(msg) else: if val <= 0: - msg = '{} must be a positive integer'.format(self.dest) + msg = '{} must be a positive integer'.format(option_string) parser.error(msg) setattr(namespace, self.dest, values) @@ -125,19 +173,16 @@ class PositiveIntAction(argparse.Action): type=int, choices=None, required=False, - help=None, + help='Must be a positive integer.', metavar=None): """Create PositiveIntAction object.""" - self.message = '{} must be a positive integer'.format(dest) - help = help or self.message - super().__init__( option_strings=option_strings, dest=dest, nargs=nargs, const=const, default=default, - type=int, + type=type, choices=choices, required=required, help=help, @@ -149,7 +194,8 @@ class PositiveIntAction(argparse.Action): setattr(namespace, self.dest, values) return - parser.error(self.message) + msg = '{} must be a positive integer'.format(option_string) + parser.error(msg) class PathAction(argparse.Action): diff --git a/contrib/python/pypodman/pypodman/lib/podman_parser.py b/contrib/python/pypodman/pypodman/lib/podman_parser.py index d3c84224f..412c8c8fd 100644 --- a/contrib/python/pypodman/pypodman/lib/podman_parser.py +++ b/contrib/python/pypodman/pypodman/lib/podman_parser.py @@ -97,6 +97,8 @@ class PodmanArgumentParser(argparse.ArgumentParser): actions_parser = self.add_subparsers( dest='subparser_name', help='commands') + # For create/exec/run: don't process options intended for subcommand + actions_parser.REMAINDER = argparse.REMAINDER # import buried here to prevent import loops import pypodman.lib.actions # pylint: disable=cyclic-import @@ -152,7 +154,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): reqattr( 'run_dir', getattr(args, 'run_dir') - or os.environ.get('RUN_DIR') + or os.environ.get('PODMAN_RUN_DIR') or config['default'].get('run_dir') or str(Path(args.xdg_runtime_dir, 'pypodman')) ) # yapf: disable @@ -161,23 +163,24 @@ class PodmanArgumentParser(argparse.ArgumentParser): args, 'host', getattr(args, 'host') - or os.environ.get('HOST') + or os.environ.get('PODMAN_HOST') or config['default'].get('host') ) # yapf:disable reqattr( 'username', getattr(args, 'username') + or os.environ.get('PODMAN_USER') + or config['default'].get('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 os.environ.get('PODMAN_PORT') or config['default'].get('port', None) or 22 ) # yapf:disable @@ -185,7 +188,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): reqattr( 'remote_socket_path', getattr(args, 'remote_socket_path') - or os.environ.get('REMOTE_SOCKET_PATH') + or os.environ.get('PODMAN_REMOTE_SOCKET_PATH') or config['default'].get('remote_socket_path') or '/run/podman/io.podman' ) # yapf:disable @@ -193,7 +196,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): reqattr( 'log_level', getattr(args, 'log_level') - or os.environ.get('LOG_LEVEL') + or os.environ.get('PODMAN_LOG_LEVEL') or config['default'].get('log_level') or logging.WARNING ) # yapf:disable @@ -202,7 +205,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): args, 'identity_file', getattr(args, 'identity_file') - or os.environ.get('IDENTITY_FILE') + or os.environ.get('PODMAN_IDENTITY_FILE') or config['default'].get('identity_file') or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.username)) ) # yapf:disable diff --git a/contrib/python/pypodman/pypodman/lib/report.py b/contrib/python/pypodman/pypodman/lib/report.py index 1db4268da..b689390fd 100644 --- a/contrib/python/pypodman/pypodman/lib/report.py +++ b/contrib/python/pypodman/pypodman/lib/report.py @@ -1,8 +1,23 @@ """Report Manager.""" +import string import sys from collections import namedtuple +class ReportFormatter(string.Formatter): + """Custom formatter to default missing keys to '<none>'.""" + + def get_value(self, key, args, kwargs): + """Map missing key to value '<none>'.""" + try: + if isinstance(key, int): + return args[key] + else: + return kwargs[key] + except KeyError: + return '<none>' + + class ReportColumn(namedtuple('ReportColumn', 'key display width default')): """Hold attributes of output column.""" @@ -26,18 +41,24 @@ class Report(): """ self._columns = columns self._file = file + self._format_string = None + self._formatter = ReportFormatter() 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) + print( + self._formatter.format(self._format_string, **hdrs), + flush=True, + file=self._file, + ) self._heading = False + fields = {k: str(v) for k, v in fields.items()} - print(self._format.format(**fields)) + print(self._formatter.format(self._format_string, **fields)) def __enter__(self): """Return `self` upon entering the runtime context.""" @@ -63,4 +84,4 @@ class Report(): display_len = info.width fmt.append('{{{0}:{1}.{1}}}'.format(key, display_len)) - self._format = ' '.join(fmt) + self._format_string = ' '.join(fmt) |