diff options
Diffstat (limited to 'contrib/python/pypodman')
36 files changed, 359 insertions, 273 deletions
diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile index cd0fcf1de..230eee44d 100644 --- a/contrib/python/pypodman/Makefile +++ b/contrib/python/pypodman/Makefile @@ -1,9 +1,10 @@ PYTHON ?= $(shell command -v python3 2>/dev/null || command -v python) DESTDIR := / -PODMAN_VERSION ?= '0.0.4' +PODMAN_VERSION ?= '0.11.1.1' .PHONY: python-pypodman python-pypodman: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py sdist bdist .PHONY: lint @@ -16,11 +17,12 @@ integration: .PHONY: install install: + PODMAN_VERSION=$(PODMAN_VERSION) \ $(PYTHON) setup.py install --root ${DESTDIR} .PHONY: upload upload: - $(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel + PODMAN_VERSION=$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel twine upload --repository-url https://test.pypi.org/legacy/ dist/* .PHONY: clobber 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 be1b5f467..d9a434254 100644 --- a/contrib/python/pypodman/pypodman/lib/__init__.py +++ b/contrib/python/pypodman/pypodman/lib/__init__.py @@ -3,18 +3,17 @@ import sys import podman from pypodman.lib.action_base import AbstractActionBase -from pypodman.lib.parser_actions import (BooleanAction, BooleanValidate, - ChangeAction, PathAction, - PositiveIntAction, UnitAction) +from pypodman.lib.parser_actions import (ChangeAction, PathAction, + PositiveIntAction, SignalAction, + 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 SignalAction assert UnitAction __all__ = [ diff --git a/contrib/python/pypodman/pypodman/lib/action_base.py b/contrib/python/pypodman/pypodman/lib/action_base.py index a950c362b..5cba7ac5c 100644 --- a/contrib/python/pypodman/pypodman/lib/action_base.py +++ b/contrib/python/pypodman/pypodman/lib/action_base.py @@ -17,29 +17,21 @@ class AbstractActionBase(abc.ABC): Use set_defaults() to set attributes "class_" and "method". These will be invoked as class_(parsed_args).method() """ - parent.add_argument( + parent.add_flag( '--all', - action='store_true', - help=('list all items.' - ' (default: no-op, included for compatibility.)')) - parent.add_argument( - '--no-trunc', - '--notruncate', - action='store_false', - dest='truncate', + help='list all items.') + parent.add_flag( + '--truncate', + '--trunc', default=True, - help='Display extended information. (default: False)') - parent.add_argument( - '--noheading', - action='store_false', - dest='heading', + help="Truncate id's and other long fields.") + parent.add_flag( + '--heading', default=True, - help=('Omit the table headings from the output.' - ' (default: False)')) - parent.add_argument( + help='Include table headings in the output.') + parent.add_flag( '--quiet', - action='store_true', - help='List only the IDs. (default: %(default)s)') + help='List only the IDs.') def __init__(self, args): """Construct class.""" 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/_create_args.py b/contrib/python/pypodman/pypodman/lib/actions/_create_args.py index 207f52796..8ab4292e8 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/_create_args.py +++ b/contrib/python/pypodman/pypodman/lib/actions/_create_args.py @@ -1,6 +1,6 @@ """Implement common create container arguments together.""" -from pypodman.lib import BooleanAction, UnitAction +from pypodman.lib import SignalAction, UnitAction class CreateArguments(): @@ -108,11 +108,9 @@ class CreateArguments(): metavar='NODES', help=('Memory nodes (MEMs) in which to allow execution (0-3, 0,1).' ' Only effective on NUMA systems')) - parser.add_argument( + parser.add_flag( '--detach', '-d', - action=BooleanAction, - default=False, help='Detached mode: run the container in the background and' ' print the new container ID. (default: False)') parser.add_argument( @@ -218,7 +216,7 @@ class CreateArguments(): # only way for argparse to handle these options. vol_args = { - 'choices': ['bind', 'tmpfs', 'ignore'], + 'choices': ('bind', 'tmpfs', 'ignore'), 'metavar': 'MODE', 'type': str.lower, 'help': 'Tells podman how to handle the builtin image volumes', @@ -228,12 +226,10 @@ class CreateArguments(): volume_group.add_argument('--image-volume', **vol_args) volume_group.add_argument('--builtin-volume', **vol_args) - parser.add_argument( + parser.add_flag( '--interactive', '-i', - action=BooleanAction, - default=False, - help='Keep STDIN open even if not attached. (default: False)') + help='Keep STDIN open even if not attached.') parser.add_argument('--ipc', help='Create namespace') parser.add_argument( '--kernel-memory', action=UnitAction, help='Kernel memory limit') @@ -278,10 +274,9 @@ class CreateArguments(): metavar='BRIDGE', help='Set the Network mode for the container.' ' (format: bridge, host, container:UUID, ns:PATH, none)') - parser.add_argument( + parser.add_flag( '--oom-kill-disable', - action=BooleanAction, - help='Whether to disable OOM Killer for the container or not') + help='Whether to disable OOM Killer for the container or not.') parser.add_argument( '--oom-score-adj', choices=range(-1000, 1000), @@ -298,41 +293,33 @@ class CreateArguments(): help=("Tune the container's pids limit." " Set -1 to have unlimited pids for the container.")) parser.add_argument('--pod', help='Run container in an existing pod') - parser.add_argument( + parser.add_flag( '--privileged', - action=BooleanAction, help='Give extended privileges to this container.') parser.add_argument( '--publish', '-p', metavar='RANGE', help="Publish a container's port, or range of ports, to the host") - parser.add_argument( + parser.add_flag( '--publish-all', '-P', - action=BooleanAction, help='Publish all exposed ports to random' - ' ports on the host interfaces' - '(default: False)') - parser.add_argument( + ' ports on the host interfaces.') + parser.add_flag( '--quiet', '-q', - action='store_true', help='Suppress output information when pulling images') - parser.add_argument( + parser.add_flag( '--read-only', - action=BooleanAction, help="Mount the container's root filesystem as read only.") - parser.add_argument( + parser.add_flag( '--rm', - action=BooleanAction, - default=False, help='Automatically remove the container when it exits.') parser.add_argument( '--rootfs', - action='store_true', - help=('If specified, the first argument refers to an' - ' exploded container on the file system of remote host.')) + help='If specified, the first argument refers to an' + ' exploded container on the file system of remote host.') parser.add_argument( '--security-opt', action='append', @@ -340,15 +327,14 @@ class CreateArguments(): help='Set security options.') parser.add_argument( '--shm-size', action=UnitAction, help='Size of /dev/shm') - parser.add_argument( + parser.add_flag( '--sig-proxy', - action=BooleanAction, - default=True, help='Proxy signals sent to the podman run' ' command to the container process') parser.add_argument( '--stop-signal', - metavar='SIGTERM', + action=SignalAction, + default='TERM', help='Signal to stop a container') parser.add_argument( '--stop-timeout', @@ -374,11 +360,9 @@ class CreateArguments(): metavar='MOUNT', help='Create a tmpfs mount.' ' (default: rw,noexec,nosuid,nodev,size=65536k.)') - parser.add_argument( + parser.add_flag( '--tty', '-t', - action=BooleanAction, - default=False, help='Allocate a pseudo-TTY for standard input of container.') parser.add_argument( '--uidmap', @@ -394,15 +378,16 @@ class CreateArguments(): parser.add_argument( '--user', '-u', - help=('Sets the username or UID used and optionally' - ' the groupname or GID for the specified command.')) + help='Sets the username or UID used and optionally' + ' the groupname or GID for the specified command.') parser.add_argument( '--userns', metavar='NAMESPACE', help='Set the user namespace mode for the container') parser.add_argument( '--uts', - choices=['host', 'ns'], + choices=('host', 'ns'), + type=str.lower, help='Set the UTS mode for the container') parser.add_argument('--volume', '-v', help='Create a bind mount.') parser.add_argument( diff --git a/contrib/python/pypodman/pypodman/lib/actions/commit_action.py b/contrib/python/pypodman/pypodman/lib/actions/commit_action.py index 21665ad0b..c166e1aff 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, ChangeAction +from pypodman.lib import AbstractActionBase, ChangeAction class Commit(AbstractActionBase): @@ -30,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', @@ -40,19 +41,17 @@ 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( + parser.add_flag( '--pause', '-p', - action=BooleanAction, - default=True, help='Pause the container when creating an image', ) - parser.add_argument( + parser.add_flag( '--quiet', '-q', - action='store_true', help='Suppress output', ) parser.add_argument( @@ -80,8 +79,16 @@ class Commit(AbstractActionBase): flush=True) return 1 else: - ident = ctnr.commit(self.opts['image'][0], **self.opts) - print(ident) + 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( 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/history_action.py b/contrib/python/pypodman/pypodman/lib/actions/history_action.py index f9aaa54f6..76c3ad756 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/history_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/history_action.py @@ -5,8 +5,7 @@ from collections import OrderedDict import humanize import podman -from pypodman.lib import (AbstractActionBase, BooleanAction, Report, - ReportColumn) +from pypodman.lib import AbstractActionBase, Report, ReportColumn class History(AbstractActionBase): @@ -17,13 +16,10 @@ class History(AbstractActionBase): """Add History command to parent parser.""" parser = parent.add_parser('history', help='report image history') super().subparser(parser) - parser.add_argument( + parser.add_flag( '--human', '-H', - action=BooleanAction, - default='True', - help='Display sizes and dates in human readable format.' - ' (default: %(default)s)') + help='Display sizes and dates in human readable format.') parser.add_argument( '--format', choices=('json', 'table'), diff --git a/contrib/python/pypodman/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/pypodman/lib/actions/images_action.py index 29bf90dd2..21376eeeb 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/images_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/images_action.py @@ -24,11 +24,9 @@ class Images(AbstractActionBase): help=('Change sort ordered of displayed images.' ' (default: %(default)s)')) - group = parser.add_mutually_exclusive_group() - group.add_argument( + parser.add_flag( '--digests', - action='store_true', - help='Include digests with images. (default: %(default)s)') + help='Include digests with images.') parser.set_defaults(class_=cls, method='list') def __init__(self, args): diff --git a/contrib/python/pypodman/pypodman/lib/actions/info_action.py b/contrib/python/pypodman/pypodman/lib/actions/info_action.py index 988284541..3c854a358 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/info_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/info_action.py @@ -22,10 +22,6 @@ class Info(AbstractActionBase): " (default: yaml)") parser.set_defaults(class_=cls, method='info') - def __init__(self, args): - """Construct Info class.""" - super().__init__(args) - def info(self): """Report on Podman Service.""" try: diff --git a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py index 514b4702a..ca5ad2215 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/inspect_action.py @@ -22,12 +22,9 @@ class Inspect(AbstractActionBase): type=str.lower, help='Type of object to inspect', ) - parser.add_argument( - 'size', - action='store_true', - default=True, - help='Display the total file size if the type is a container.' - ' Always True.') + parser.add_flag( + '--size', + help='Display the total file size if the type is a container.') parser.add_argument( 'objects', nargs='+', @@ -35,10 +32,6 @@ class Inspect(AbstractActionBase): ) parser.set_defaults(class_=cls, method='inspect') - def __init__(self, args): - """Construct Inspect class.""" - super().__init__(args) - def _get_container(self, ident): try: logging.debug("Getting container %s", ident) @@ -59,7 +52,7 @@ class Inspect(AbstractActionBase): def inspect(self): """Inspect provided podman objects.""" - output = {} + output = [] try: for ident in self._args.objects: obj = None @@ -78,7 +71,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/kill_action.py b/contrib/python/pypodman/pypodman/lib/actions/kill_action.py index cb3d3f035..e8fb4e74d 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/kill_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/kill_action.py @@ -1,9 +1,8 @@ """Remote client command for signaling podman containers.""" -import signal import sys import podman -from pypodman.lib import AbstractActionBase +from pypodman.lib import AbstractActionBase, SignalAction class Kill(AbstractActionBase): @@ -16,10 +15,9 @@ class Kill(AbstractActionBase): parser.add_argument( '--signal', '-s', - choices=range(1, signal.NSIG), - metavar='[1,{}]'.format(signal.NSIG), + action=SignalAction, default=9, - help='Signal to send to the container. (default: 9)') + help='Signal to send to the container. (default: %(default)s)') parser.add_argument( 'containers', nargs='+', @@ -27,10 +25,6 @@ class Kill(AbstractActionBase): ) parser.set_defaults(class_=cls, method='kill') - def __init__(self, args): - """Construct Kill class.""" - super().__init__(args) - def kill(self): """Signal provided containers.""" try: diff --git a/contrib/python/pypodman/pypodman/lib/actions/pause_action.py b/contrib/python/pypodman/pypodman/lib/actions/pause_action.py index ab64d8b81..7dc02f7fe 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pause_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pause_action.py @@ -19,10 +19,6 @@ class Pause(AbstractActionBase): ) parser.set_defaults(class_=cls, method='pause') - def __init__(self, args): - """Construct Pause class.""" - super().__init__(args) - def pause(self): """Pause provided containers.""" try: diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py index 46c1e3e51..4e0bde777 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/create_parser.py @@ -2,7 +2,7 @@ import sys import podman -from pypodman.lib import AbstractActionBase, BooleanAction +from pypodman.lib import AbstractActionBase class CreatePod(AbstractActionBase): @@ -20,12 +20,9 @@ class CreatePod(AbstractActionBase): type=str, help='Path to cgroups under which the' ' cgroup for the pod will be created.') - parser.add_argument( + parser.add_flag( '--infra', - action=BooleanAction, - default=True, - help='Create an infra container and associate it with the pod' - '(default: %(default)s)') + help='Create an infra container and associate it with the pod.') parser.add_argument( '-l', '--label', diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py index 430ec34e0..9b6229939 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/kill_parser.py @@ -3,7 +3,7 @@ import signal import sys import podman -from pypodman.lib import AbstractActionBase +from pypodman.lib import AbstractActionBase, SignalAction from pypodman.lib import query_model as query_pods @@ -15,18 +15,16 @@ class KillPod(AbstractActionBase): """Add Pod Kill command to parent parser.""" parser = parent.add_parser('kill', help='signal containers in pod') - parser.add_argument( - '-a', + parser.add_flag( '--all', - action='store_true', - help='Sends signal to all pods') + '-a', + help='Sends signal to all pods.') parser.add_argument( '-s', '--signal', - choices=range(1, signal.NSIG), - metavar='[1,{}]'.format(signal.NSIG), + action=SignalAction, default=9, - help='Signal to send to the pod. (default: 9)') + help='Signal to send to the pod. (default: %(default)s)') parser.add_argument('pod', nargs='*', help='pod(s) to signal') parser.set_defaults(class_=cls, method='kill') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py index daae028d4..c751314ca 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/pause_parser.py @@ -13,8 +13,10 @@ class PausePod(AbstractActionBase): def subparser(cls, parent): """Add Pod Pause command to parent parser.""" parser = parent.add_parser('pause', help='pause containers in pod') - parser.add_argument( - '-a', '--all', action='store_true', help='Pause all pods') + parser.add_flag( + '--all', + '-a', + help='Pause all pods.') parser.add_argument('pod', nargs='*', help='pod(s) to pause.') parser.set_defaults(class_=cls, method='pause') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py index ecfcb883a..855e313c7 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/processes_parser.py @@ -14,18 +14,15 @@ class ProcessesPod(AbstractActionBase): parser = parent.add_parser('ps', help='list processes of pod') super().subparser(parser) - parser.add_argument( + parser.add_flag( '--ctr-names', - action='store_true', - help='Include container name in the info field') - parser.add_argument( + help='Include container name in the info field.') + parser.add_flag( '--ctr-ids', - action='store_true', - help='Include container ID in the info field') - parser.add_argument( + help='Include container ID in the info field.') + parser.add_flag( '--ctr-status', - action='store_true', - help='Include container status in the info field') + help='Include container status in the info field.') parser.add_argument( '--format', choices=('json'), diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py index 40eeb7203..289325d14 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/remove_parser.py @@ -13,13 +13,14 @@ class RemovePod(AbstractActionBase): def subparser(cls, parent): """Add Pod Rm command to parent parser.""" parser = parent.add_parser('rm', help='Delete pod and container(s)') - parser.add_argument( - '-a', '--all', action='store_true', help='Remove all pods') - parser.add_argument( - '-f', + parser.add_flag( + '--all', + '-a', + help='Remove all pods.') + parser.add_flag( '--force', - action='store_true', - help='Stop and remove container(s) then delete pod') + '-f', + help='Stop and remove container(s) then delete pod.') parser.add_argument( 'pod', nargs='*', help='Pod to remove. Or, use --all') parser.set_defaults(class_=cls, method='remove') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py index af489ad28..53f45b6de 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/restart_parser.py @@ -13,8 +13,10 @@ class RestartPod(AbstractActionBase): def subparser(cls, parent): """Add Pod Restart command to parent parser.""" parser = parent.add_parser('restart', help='restart containers in pod') - parser.add_argument( - '-a', '--all', action='store_true', help='Restart all pods') + parser.add_flag( + '--all', + '-a', + help='Restart all pods.') parser.add_argument( 'pod', nargs='*', help='Pod to restart. Or, use --all') parser.set_defaults(class_=cls, method='restart') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py index 0ddc336bf..ff62b839e 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/start_parser.py @@ -14,8 +14,10 @@ class StartPod(AbstractActionBase): def subparser(cls, parent): """Add Pod Start command to parent parser.""" parser = parent.add_parser('start', help='start pod') - parser.add_argument( - '-a', '--all', action='store_true', help='Start all pods') + parser.add_flag( + '--all', + '-a', + help='Start all pods.') parser.add_argument( 'pod', nargs='*', help='Pod to start. Or, use --all') parser.set_defaults(class_=cls, method='start') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py index 7054fd38a..cbf2bf1e7 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/stop_parser.py @@ -13,8 +13,10 @@ class StopPod(AbstractActionBase): def subparser(cls, parent): """Add Pod Stop command to parent parser.""" parser = parent.add_parser('stop', help='stop pod') - parser.add_argument( - '-a', '--all', action='store_true', help='Stop all pods') + parser.add_flag( + '--all', + '-a', + help='Stop all pods.') parser.add_argument( 'pod', nargs='*', help='Pod to stop. Or, use --all') parser.set_defaults(class_=cls, method='stop') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py b/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py index 90e1ddbe2..5186cf9cc 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod/unpause_parser.py @@ -13,8 +13,10 @@ class UnpausePod(AbstractActionBase): def subparser(cls, parent): """Add Pod Unpause command to parent parser.""" parser = parent.add_parser('unpause', help='unpause pod') - parser.add_argument( - '-a', '--all', action='store_true', help='Unpause all pods') + parser.add_flag( + '--all', + '-a', + help='Unpause all pods.') parser.add_argument( 'pod', nargs='*', help='Pod to unpause. Or, use --all') parser.set_defaults(class_=cls, method='unpause') diff --git a/contrib/python/pypodman/pypodman/lib/actions/pod_action.py b/contrib/python/pypodman/pypodman/lib/actions/pod_action.py index 046af34bb..4b8997a05 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/pod_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/pod_action.py @@ -5,6 +5,8 @@ import sys from pypodman.lib import AbstractActionBase +# pylint: disable=wildcard-import +# pylint: disable=unused-wildcard-import from .pod import * diff --git a/contrib/python/pypodman/pypodman/lib/actions/port_action.py b/contrib/python/pypodman/pypodman/lib/actions/port_action.py index d2a8ded46..6913f3813 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/port_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/port_action.py @@ -13,16 +13,13 @@ class Port(AbstractActionBase): """Add Port command to parent parser.""" parser = parent.add_parser( 'port', help='retrieve ports from containers') - parser.add_argument( + parser.add_flag( '--all', '-a', - action='store_true', - default=False, help='List all known port mappings for running containers') parser.add_argument( 'containers', nargs='*', - default=None, help='containers to list ports', ) parser.set_defaults(class_=cls, method='port') @@ -61,3 +58,4 @@ class Port(AbstractActionBase): file=sys.stderr, flush=True) return 1 + return 0 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/push_action.py b/contrib/python/pypodman/pypodman/lib/actions/push_action.py index 0030cb5b9..8e86ca335 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/push_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/push_action.py @@ -15,12 +15,10 @@ class Push(AbstractActionBase): 'push', help='push image elsewhere', ) - parser.add_argument( + parser.add_flag( '--tlsverify', - action='store_true', - default=True, help='Require HTTPS and verify certificates when' - ' contacting registries (default: %(default)s)') + ' contacting registries.') parser.add_argument( 'image', nargs=1, help='name or id of image to push') parser.add_argument( @@ -30,10 +28,6 @@ class Push(AbstractActionBase): ) parser.set_defaults(class_=cls, method='push') - def __init__(self, args): - """Construct Push class.""" - super().__init__(args) - def pull(self): """Store image elsewhere.""" try: diff --git a/contrib/python/pypodman/pypodman/lib/actions/restart_action.py b/contrib/python/pypodman/pypodman/lib/actions/restart_action.py index d99d1ad65..415594920 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/restart_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/restart_action.py @@ -23,10 +23,6 @@ class Restart(AbstractActionBase): 'targets', nargs='+', help='container id(s) to restart') parser.set_defaults(class_=cls, method='restart') - def __init__(self, args): - """Construct Restart class.""" - super().__init__(args) - def restart(self): """Restart container(s).""" try: diff --git a/contrib/python/pypodman/pypodman/lib/actions/rm_action.py b/contrib/python/pypodman/pypodman/lib/actions/rm_action.py index e8074ef4e..99ff6c460 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/rm_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/rm_action.py @@ -12,20 +12,14 @@ class Rm(AbstractActionBase): def subparser(cls, parent): """Add Rm command to parent parser.""" parser = parent.add_parser('rm', help='delete container(s)') - parser.add_argument( - '-f', + parser.add_flag( '--force', - action='store_true', - help=('force delete of running container(s).' - ' (default: %(default)s)')) + '-f', + help='force delete of running container(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) - def remove(self): """Remove container(s).""" for ident in self._args.targets: diff --git a/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py b/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py index c6ba835cb..7c3d0bd79 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py @@ -12,19 +12,13 @@ class Rmi(AbstractActionBase): def subparser(cls, parent): """Add Rmi command to parent parser.""" parser = parent.add_parser('rmi', help='delete image(s)') - parser.add_argument( - '-f', + parser.add_flag( '--force', - action='store_true', - help=('force delete of image(s) and associated containers.' - ' (default: %(default)s)')) + '-f', + help='force delete of image(s) and associated containers.') 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) - def remove(self): """Remove image(s).""" for ident in self._args.targets: 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/search_action.py b/contrib/python/pypodman/pypodman/lib/actions/search_action.py index d2a585d92..b7b8b465d 100644 --- a/contrib/python/pypodman/pypodman/lib/actions/search_action.py +++ b/contrib/python/pypodman/pypodman/lib/actions/search_action.py @@ -4,8 +4,8 @@ import sys from collections import OrderedDict import podman -from pypodman.lib import (AbstractActionBase, BooleanValidate, - PositiveIntAction, Report, ReportColumn) +from pypodman.lib import (AbstractActionBase, PositiveIntAction, Report, + ReportColumn) class FilterAction(argparse.Action): @@ -58,16 +58,16 @@ class FilterAction(argparse.Action): if val < 0: parser.error(msg) elif opt == 'is-automated': - try: - val = BooleanValidate()(val) - except ValueError: + if val.capitalize() in ('True', 'False'): + val = bool(val) + else: msg = ('{} option "is-automated"' ' must be True or False.'.format(self.dest)) parser.error(msg) elif opt == 'is-official': - try: - val = BooleanValidate()(val) - except ValueError: + if val.capitalize() in ('True', 'False'): + val = bool(val) + else: msg = ('{} option "is-official"' ' must be True or False.'.format(self.dest)) parser.error(msg) 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..5f88731dc --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/start_action.py @@ -0,0 +1,71 @@ +"""Remote client command for starting containers.""" +import sys + +import podman +from pypodman.lib import AbstractActionBase + + +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_flag( + '--attach', + '-a', + help="Attach container's STDOUT and STDERR.") + 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_flag( + '--interactive', + '-i', + help="Attach container's STDIN.") + # TODO: Implement sig-proxy + parser.add_flag( + '--sig-proxy', + help="Proxy received signals to the process." + ) + 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 + return 0 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..29a0cabe4 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/version_action.py @@ -0,0 +1,35 @@ +"""Remote client command for reporting on Podman service.""" +import sys + +import podman +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 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 c10b85495..3ff12cab8 100644 --- a/contrib/python/pypodman/pypodman/lib/parser_actions.py +++ b/contrib/python/pypodman/pypodman/lib/parser_actions.py @@ -6,6 +6,7 @@ The constructors are very verbose but remain for IDE support. import argparse import copy import os +import signal # API defined by argparse.Action therefore shut up pylint # pragma pylint: disable=redefined-builtin @@ -13,22 +14,8 @@ import os # pragma pylint: disable=too-many-arguments -class BooleanValidate(): - """Validate value is boolean string.""" - - def __call__(self, value): - """Return True, False or raise ValueError.""" - val = value.capitalize() - if val == 'False': - return False - elif val == 'True': - return True - else: - raise ValueError('"{}" is not True or False'.format(value)) - - -class BooleanAction(argparse.Action): - """Convert and validate bool argument.""" +class ChangeAction(argparse.Action): + """Convert and validate change argument.""" def __init__(self, option_strings, @@ -37,11 +24,16 @@ class BooleanAction(argparse.Action): const=None, default=None, type=None, - choices=('True', 'False'), + choices=None, required=False, help=None, - metavar='{True,False}'): - """Create BooleanAction object.""" + metavar='OPT=VALUE'): + """Create ChangeAction object.""" + help = (help or '') + ('Apply change(s) to the new image.' + ' May be given multiple times.') + if default is None: + default = [] + super().__init__( option_strings=option_strings, dest=dest, @@ -56,32 +48,37 @@ class BooleanAction(argparse.Action): def __call__(self, parser, namespace, values, option_string=None): """Convert and Validate input.""" - try: - val = BooleanValidate()(values) - except ValueError: - parser.error('{} must be True or False.'.format(self.dest)) - else: - setattr(namespace, self.dest, val) + items = getattr(namespace, self.dest, None) or [] + items = copy.copy(items) + choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', + 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR') -class ChangeAction(argparse.Action): - """Convert and validate change argument.""" + opt, _ = 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 SignalAction(argparse.Action): + """Validate input as a signal.""" def __init__(self, option_strings, dest, nargs=None, const=None, - default=[], - type=None, + default=None, + type=str, 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.') - + help='The signal to send.' + ' It may be given as a name or a number.', + metavar='SIGNAL'): + """Create SignalAction object.""" super().__init__( option_strings=option_strings, dest=dest, @@ -94,22 +91,40 @@ class ChangeAction(argparse.Action): help=help, metavar=metavar) - def __call__(self, parser, namespace, values, option_string=None): - """Convert and Validate input.""" - print(self.dest) - items = getattr(namespace, self.dest, None) or [] - items = copy.copy(items) + if hasattr(signal, "Signals"): - choices = ('CMD', 'ENTRYPOINT', 'ENV', 'EXPOSE', 'LABEL', 'ONBUILD', - 'STOPSIGNAL', 'USER', 'VOLUME', 'WORKDIR') + def _signal_number(signame): + cooked = 'SIG{}'.format(signame) + try: + return signal.Signals[cooked].value + except ValueError: + pass + else: - opt, val = values.split('=', 1) - if opt not in choices: - parser.error('{} is not a supported "--change" option,' - ' valid options are: {}'.format( - opt, ', '.join(choices))) - items.append(values) - setattr(namespace, self.dest, items) + def _signal_number(signame): + cooked = 'SIG{}'.format(signame) + for n, v in sorted(signal.__dict__.items()): + if n != cooked: + continue + if n.startswith("SIG") and not n.startswith("SIG_"): + return v + + self._signal_number = _signal_number + + def __call__(self, parser, namespace, values, option_string=None): + """Validate input is a signal for platform.""" + if values.isdigit(): + signum = int(values) + if signal.SIGRTMIN <= signum >= signal.SIGRTMAX: + raise ValueError('"{}" is not a valid signal. {}-{}'.format( + values, signal.SIGRTMIN, signal.SIGRTMAX)) + else: + signum = self._signal_number(values) + if signum is None: + parser.error( + '"{}" is not a valid signal,' + ' see your platform documentation.'.format(values)) + setattr(namespace, self.dest, signum) class UnitAction(argparse.Action): @@ -127,8 +142,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, @@ -148,15 +163,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) @@ -174,19 +189,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, @@ -198,7 +210,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..913546a91 100644 --- a/contrib/python/pypodman/pypodman/lib/podman_parser.py +++ b/contrib/python/pypodman/pypodman/lib/podman_parser.py @@ -48,6 +48,18 @@ class PodmanArgumentParser(argparse.ArgumentParser): super().__init__(**kwargs) + def add_flag(self, *args, **kwargs): + """Add flag to parser.""" + flags = [a for a in args if a[0] in self.prefix_chars] + dest = flags[0].lstrip(self.prefix_chars) + no_flag = '{0}{0}no-{1}'.format(self.prefix_chars, dest) + + group = self.add_mutually_exclusive_group(required=False) + group.add_argument(*flags, action='store_true', dest=dest, **kwargs) + group.add_argument(no_flag, action='store_false', dest=dest, **kwargs) + default = kwargs.get('default', False) + self.set_defaults(**{dest: default}) + def initialize_parser(self): """Initialize parser without causing recursion meltdown.""" self.add_argument( @@ -97,6 +109,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 +166,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 +175,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 +200,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 +208,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 +217,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 |