mirror of
https://github.com/containers/podman.git
synced 2025-06-20 00:51:16 +08:00
Implement new subcommands
* Refactor create subparser to share arguments with run subparser * Add argparse.*Action subclasses to reduce duplicate code in parsers * Using BooleanAction now accept True/False value as expected * .pylintrc added to loosen variable name policing * Update AbstractBaseAction to remove unset arguments before transmitting to podman service * Align logging messages to podman output * Renamed global argument from --user to --username, to avoid conflict with create/run podman commands * Add new subcommands: run, create, history, import, info, push, restart and search Signed-off-by: Jhon Honce <jhonce@redhat.com> Closes: #1519 Approved by: rhatdan
This commit is contained in:
@ -1,5 +1,6 @@
|
|||||||
PYTHON ?= /usr/bin/python3
|
PYTHON ?= /usr/bin/python3
|
||||||
DESTDIR ?= /
|
DESTDIR ?= /
|
||||||
|
PODMAN_VERSION ?= '0.0.4'
|
||||||
|
|
||||||
.PHONY: python-podman
|
.PHONY: python-podman
|
||||||
python-podman:
|
python-podman:
|
||||||
@ -17,6 +18,11 @@ integration:
|
|||||||
install:
|
install:
|
||||||
$(PYTHON) setup.py install --root ${DESTDIR}
|
$(PYTHON) setup.py install --root ${DESTDIR}
|
||||||
|
|
||||||
|
.PHONY: upload
|
||||||
|
upload:
|
||||||
|
$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel
|
||||||
|
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
||||||
|
|
||||||
.PHONY: clobber
|
.PHONY: clobber
|
||||||
clobber: uninstall clean
|
clobber: uninstall clean
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ class Mixin:
|
|||||||
Will block if container has been detached.
|
Will block if container has been detached.
|
||||||
"""
|
"""
|
||||||
with self._client() as podman:
|
with self._client() as podman:
|
||||||
|
logging.debug('Starting Container "%s"', self._id)
|
||||||
results = podman.StartContainer(self._id)
|
results = podman.StartContainer(self._id)
|
||||||
logging.debug('Started Container "%s"', results['container'])
|
logging.debug('Started Container "%s"', results['container'])
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import collections
|
|||||||
import functools
|
import functools
|
||||||
import getpass
|
import getpass
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -32,8 +33,17 @@ class Container(AttachMixin, StartMixin, collections.UserDict):
|
|||||||
"""Get items from parent dict."""
|
"""Get items from parent dict."""
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
|
|
||||||
def _refresh(self, podman):
|
def _refresh(self, podman, tries=1):
|
||||||
|
try:
|
||||||
ctnr = podman.GetContainer(self._id)
|
ctnr = podman.GetContainer(self._id)
|
||||||
|
except BrokenPipeError:
|
||||||
|
logging.debug('Failed GetContainer(%s) try %d/3', self._id, tries)
|
||||||
|
if tries > 3:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
with self._client() as pman:
|
||||||
|
self._refresh(pman, tries + 1)
|
||||||
|
else:
|
||||||
super().update(ctnr['container'])
|
super().update(ctnr['container'])
|
||||||
|
|
||||||
for k, v in self.data.items():
|
for k, v in self.data.items():
|
||||||
|
@ -97,11 +97,12 @@ EOT
|
|||||||
|
|
||||||
cat >$TMPDIR/ctnr/hello.sh <<-EOT
|
cat >$TMPDIR/ctnr/hello.sh <<-EOT
|
||||||
echo 'Hello, World'
|
echo 'Hello, World'
|
||||||
|
exit 0
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
cat >$TMPDIR/ctnr/Dockerfile <<-EOT
|
cat >$TMPDIR/ctnr/Dockerfile <<-EOT
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
COPY ./hello.sh /tmp/hello.sh
|
COPY ./hello.sh /tmp/
|
||||||
RUN chmod 755 /tmp/hello.sh
|
RUN chmod 755 /tmp/hello.sh
|
||||||
ENTRYPOINT ["/tmp/hello.sh"]
|
ENTRYPOINT ["/tmp/hello.sh"]
|
||||||
EOT
|
EOT
|
||||||
|
4
contrib/python/pypodman/.pylintrc
Normal file
4
contrib/python/pypodman/.pylintrc
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[VARIABLES]
|
||||||
|
|
||||||
|
# Enforce only pep8 variable names
|
||||||
|
variable-rgx=[a-z0-9_]{1,30}$
|
@ -1,5 +1,6 @@
|
|||||||
PYTHON ?= /usr/bin/python3
|
PYTHON ?= /usr/bin/python3
|
||||||
DESTDIR := /
|
DESTDIR := /
|
||||||
|
PODMAN_VERSION ?= '0.0.4'
|
||||||
|
|
||||||
.PHONY: python-pypodman
|
.PHONY: python-pypodman
|
||||||
python-pypodman:
|
python-pypodman:
|
||||||
@ -17,6 +18,11 @@ integration:
|
|||||||
install:
|
install:
|
||||||
$(PYTHON) setup.py install --root ${DESTDIR}
|
$(PYTHON) setup.py install --root ${DESTDIR}
|
||||||
|
|
||||||
|
.PHONY: upload
|
||||||
|
upload:
|
||||||
|
$(PODMAN_VERSION) $(PYTHON) setup.py sdist bdist_wheel
|
||||||
|
twine upload --repository-url https://test.pypi.org/legacy/ dist/*
|
||||||
|
|
||||||
.PHONY: clobber
|
.PHONY: clobber
|
||||||
clobber: uninstall clean
|
clobber: uninstall clean
|
||||||
|
|
||||||
|
@ -1,8 +1,18 @@
|
|||||||
"""Remote podman client support library."""
|
"""Remote podman client support library."""
|
||||||
from pypodman.lib.action_base import AbstractActionBase
|
from pypodman.lib.action_base import AbstractActionBase
|
||||||
from pypodman.lib.config import PodmanArgumentParser
|
from pypodman.lib.parser_actions import (BooleanAction, BooleanValidate,
|
||||||
|
PathAction, PositiveIntAction,
|
||||||
|
UnitAction)
|
||||||
|
from pypodman.lib.podman_parser import PodmanArgumentParser
|
||||||
from pypodman.lib.report import Report, ReportColumn
|
from pypodman.lib.report import Report, ReportColumn
|
||||||
|
|
||||||
|
# Silence pylint overlording...
|
||||||
|
assert BooleanAction
|
||||||
|
assert BooleanValidate
|
||||||
|
assert PathAction
|
||||||
|
assert PositiveIntAction
|
||||||
|
assert UnitAction
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'AbstractActionBase',
|
'AbstractActionBase',
|
||||||
'PodmanArgumentParser',
|
'PodmanArgumentParser',
|
||||||
|
@ -43,7 +43,12 @@ class AbstractActionBase(abc.ABC):
|
|||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct class."""
|
"""Construct class."""
|
||||||
|
# Dump all unset arguments before transmitting to service
|
||||||
self._args = args
|
self._args = args
|
||||||
|
self.opts = {
|
||||||
|
k: v
|
||||||
|
for k, v in vars(self._args).items() if v is not None
|
||||||
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def remote_uri(self):
|
def remote_uri(self):
|
||||||
|
@ -3,7 +3,10 @@ from pypodman.lib.actions.attach_action import Attach
|
|||||||
from pypodman.lib.actions.commit_action import Commit
|
from pypodman.lib.actions.commit_action import Commit
|
||||||
from pypodman.lib.actions.create_action import Create
|
from pypodman.lib.actions.create_action import Create
|
||||||
from pypodman.lib.actions.export_action import Export
|
from pypodman.lib.actions.export_action import Export
|
||||||
|
from pypodman.lib.actions.history_action import History
|
||||||
from pypodman.lib.actions.images_action import Images
|
from pypodman.lib.actions.images_action import Images
|
||||||
|
from pypodman.lib.actions.import_action import Import
|
||||||
|
from pypodman.lib.actions.info_action import Info
|
||||||
from pypodman.lib.actions.inspect_action import Inspect
|
from pypodman.lib.actions.inspect_action import Inspect
|
||||||
from pypodman.lib.actions.kill_action import Kill
|
from pypodman.lib.actions.kill_action import Kill
|
||||||
from pypodman.lib.actions.logs_action import Logs
|
from pypodman.lib.actions.logs_action import Logs
|
||||||
@ -12,15 +15,22 @@ from pypodman.lib.actions.pause_action import Pause
|
|||||||
from pypodman.lib.actions.port_action import Port
|
from pypodman.lib.actions.port_action import Port
|
||||||
from pypodman.lib.actions.ps_action import Ps
|
from pypodman.lib.actions.ps_action import Ps
|
||||||
from pypodman.lib.actions.pull_action import Pull
|
from pypodman.lib.actions.pull_action import Pull
|
||||||
|
from pypodman.lib.actions.push_action import Push
|
||||||
|
from pypodman.lib.actions.restart_action import Restart
|
||||||
from pypodman.lib.actions.rm_action import Rm
|
from pypodman.lib.actions.rm_action import Rm
|
||||||
from pypodman.lib.actions.rmi_action import Rmi
|
from pypodman.lib.actions.rmi_action import Rmi
|
||||||
|
from pypodman.lib.actions.run_action import Run
|
||||||
|
from pypodman.lib.actions.search_action import Search
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
'Attach',
|
'Attach',
|
||||||
'Commit',
|
'Commit',
|
||||||
'Create',
|
'Create',
|
||||||
'Export',
|
'Export',
|
||||||
|
'History',
|
||||||
'Images',
|
'Images',
|
||||||
|
'Import',
|
||||||
|
'Info',
|
||||||
'Inspect',
|
'Inspect',
|
||||||
'Kill',
|
'Kill',
|
||||||
'Logs',
|
'Logs',
|
||||||
@ -29,6 +39,10 @@ __all__ = [
|
|||||||
'Port',
|
'Port',
|
||||||
'Ps',
|
'Ps',
|
||||||
'Pull',
|
'Pull',
|
||||||
|
'Push',
|
||||||
|
'Restart',
|
||||||
'Rm',
|
'Rm',
|
||||||
'Rmi',
|
'Rmi',
|
||||||
|
'Run',
|
||||||
|
'Search',
|
||||||
]
|
]
|
||||||
|
394
contrib/python/pypodman/pypodman/lib/actions/_create_args.py
Normal file
394
contrib/python/pypodman/pypodman/lib/actions/_create_args.py
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
"""Implement common create container arguments together."""
|
||||||
|
from pypodman.lib import BooleanAction, UnitAction
|
||||||
|
|
||||||
|
|
||||||
|
class CreateArguments():
|
||||||
|
"""Helper to add all the create flags to a command."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_arguments(cls, parser):
|
||||||
|
"""Add CreateArguments to parser."""
|
||||||
|
parser.add_argument(
|
||||||
|
'--add-host',
|
||||||
|
action='append',
|
||||||
|
metavar='HOST',
|
||||||
|
help='Add a line to /etc/hosts.'
|
||||||
|
' The option can be set multiple times.'
|
||||||
|
' (Format: hostname:ip)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--annotation',
|
||||||
|
action='append',
|
||||||
|
help='Add an annotation to the container.'
|
||||||
|
'The option can be set multiple times.'
|
||||||
|
'(Format: key=value)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--attach',
|
||||||
|
'-a',
|
||||||
|
action='append',
|
||||||
|
metavar='FD',
|
||||||
|
help=('Attach to STDIN, STDOUT or STDERR. The option can be set'
|
||||||
|
' for each of stdin, stdout, and stderr.'))
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--blkio-weight',
|
||||||
|
choices=range(10, 1000),
|
||||||
|
metavar='[10-1000]',
|
||||||
|
help=('Block IO weight (relative weight) accepts a'
|
||||||
|
' weight value between 10 and 1000.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--blkio-weight-device',
|
||||||
|
action='append',
|
||||||
|
metavar='WEIGHT',
|
||||||
|
help='Block IO weight, relative device weight.'
|
||||||
|
' (Format: DEVICE_NAME:WEIGHT)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--cap-add',
|
||||||
|
action='append',
|
||||||
|
metavar='CAP',
|
||||||
|
help=('Add Linux capabilities'
|
||||||
|
'The option can be set multiple times.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cap-drop',
|
||||||
|
action='append',
|
||||||
|
metavar='CAP',
|
||||||
|
help=('Drop Linux capabilities'
|
||||||
|
'The option can be set multiple times.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cgroup-parent',
|
||||||
|
metavar='PATH',
|
||||||
|
help='Path to cgroups under which the cgroup for the'
|
||||||
|
' container will be created. If the path is not'
|
||||||
|
' absolute, the path is considered to be relative'
|
||||||
|
' to the cgroups path of the init process. Cgroups'
|
||||||
|
' will be created if they do not already exist.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--cidfile',
|
||||||
|
metavar='PATH',
|
||||||
|
help='Write the container ID to the file, on the remote host.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--conmon-pidfile',
|
||||||
|
metavar='PATH',
|
||||||
|
help=('Write the pid of the conmon process to a file,'
|
||||||
|
' on the remote host.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpu-period',
|
||||||
|
type=int,
|
||||||
|
metavar='PERIOD',
|
||||||
|
help=('Limit the CPU CFS (Completely Fair Scheduler) period.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpu-quota',
|
||||||
|
type=int,
|
||||||
|
metavar='QUOTA',
|
||||||
|
help=('Limit the CPU CFS (Completely Fair Scheduler) quota.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpu-rt-period',
|
||||||
|
type=int,
|
||||||
|
metavar='PERIOD',
|
||||||
|
help=('Limit the CPU real-time period in microseconds.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpu-rt-runtime',
|
||||||
|
type=int,
|
||||||
|
metavar='LIMIT',
|
||||||
|
help=('Limit the CPU real-time runtime in microseconds.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpu-shares',
|
||||||
|
type=int,
|
||||||
|
metavar='SHARES',
|
||||||
|
help=('CPU shares (relative weight)'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpus',
|
||||||
|
type=float,
|
||||||
|
help=('Number of CPUs. The default is 0.0 which means no limit'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpuset-cpus',
|
||||||
|
metavar='LIST',
|
||||||
|
help=('CPUs in which to allow execution (0-3, 0,1)'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--cpuset-mems',
|
||||||
|
metavar='NODES',
|
||||||
|
help=('Memory nodes (MEMs) in which to allow execution (0-3, 0,1).'
|
||||||
|
' Only effective on NUMA systems'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--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(
|
||||||
|
'--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 _)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--device',
|
||||||
|
action='append',
|
||||||
|
help=('Add a host device to the container'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--device-read-bps',
|
||||||
|
action='append',
|
||||||
|
metavar='LIMIT',
|
||||||
|
help=('Limit read rate (bytes per second) from a device'
|
||||||
|
' (e.g. --device-read-bps=/dev/sda:1mb)'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--device-read-iops',
|
||||||
|
action='append',
|
||||||
|
metavar='LIMIT',
|
||||||
|
help=('Limit read rate (IO per second) from a device'
|
||||||
|
' (e.g. --device-read-iops=/dev/sda:1000)'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--device-write-bps',
|
||||||
|
action='append',
|
||||||
|
metavar='LIMIT',
|
||||||
|
help=('Limit write rate (bytes per second) to a device'
|
||||||
|
' (e.g. --device-write-bps=/dev/sda:1mb)'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--device-write-iops',
|
||||||
|
action='append',
|
||||||
|
metavar='LIMIT',
|
||||||
|
help=('Limit write rate (IO per second) to a device'
|
||||||
|
' (e.g. --device-write-iops=/dev/sda:1000)'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dns',
|
||||||
|
action='append',
|
||||||
|
metavar='SERVER',
|
||||||
|
help=('Set custom DNS servers.'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dns-option',
|
||||||
|
action='append',
|
||||||
|
metavar='OPT',
|
||||||
|
help=('Set custom DNS options.'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dns-search',
|
||||||
|
action='append',
|
||||||
|
metavar='DOMAIN',
|
||||||
|
help=('Set custom DNS search domains.'
|
||||||
|
'The option can be set multiple times.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--entrypoint',
|
||||||
|
help=('Overwrite the default ENTRYPOINT of the image.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--env',
|
||||||
|
'-e',
|
||||||
|
action='append',
|
||||||
|
help=('Set environment variables.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--env-file',
|
||||||
|
help=('Read in a line delimited file of environment variables,'
|
||||||
|
' on the remote host.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--expose',
|
||||||
|
metavar='RANGE',
|
||||||
|
help=('Expose a port, or a range of ports'
|
||||||
|
' (e.g. --expose=3300-3310) to set up port redirection.'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--gidmap',
|
||||||
|
metavar='MAP',
|
||||||
|
help=('GID map for the user namespace'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--group-add',
|
||||||
|
action='append',
|
||||||
|
metavar='GROUP',
|
||||||
|
help=('Add additional groups to run as'))
|
||||||
|
parser.add_argument('--hostname', help='Container host name')
|
||||||
|
|
||||||
|
volume_group = parser.add_mutually_exclusive_group()
|
||||||
|
volume_group.add_argument(
|
||||||
|
'--image-volume',
|
||||||
|
choices=['bind', 'tmpfs', 'ignore'],
|
||||||
|
metavar='MODE',
|
||||||
|
help='Tells podman how to handle the builtin image volumes')
|
||||||
|
volume_group.add_argument(
|
||||||
|
'--builtin-volume',
|
||||||
|
choices=['bind', 'tmpfs', 'ignore'],
|
||||||
|
metavar='MODE',
|
||||||
|
help='Tells podman how to handle the builtin image volumes')
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--interactive',
|
||||||
|
'-i',
|
||||||
|
action=BooleanAction,
|
||||||
|
default=False,
|
||||||
|
help='Keep STDIN open even if not attached. (Default: False)')
|
||||||
|
parser.add_argument('--ipc', help='Create namespace')
|
||||||
|
parser.add_argument(
|
||||||
|
'--kernel-memory', action=UnitAction, help='Kernel memory limit')
|
||||||
|
parser.add_argument(
|
||||||
|
'--label',
|
||||||
|
'-l',
|
||||||
|
help=('Add metadata to a container'
|
||||||
|
' (e.g., --label com.example.key=value)'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--label-file', help='Read in a line delimited file of labels')
|
||||||
|
parser.add_argument(
|
||||||
|
'--log-driver',
|
||||||
|
choices=['json-file', 'journald'],
|
||||||
|
help='Logging driver for the container.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--log-opt',
|
||||||
|
action='append',
|
||||||
|
help='Logging driver specific options')
|
||||||
|
parser.add_argument(
|
||||||
|
'--memory', '-m', action=UnitAction, help='Memory limit')
|
||||||
|
parser.add_argument(
|
||||||
|
'--memory-reservation',
|
||||||
|
action=UnitAction,
|
||||||
|
help='Memory soft limit')
|
||||||
|
parser.add_argument(
|
||||||
|
'--memory-swap',
|
||||||
|
action=UnitAction,
|
||||||
|
help=('A limit value equal to memory plus swap.'
|
||||||
|
'Must be used with the --memory flag'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--memory-swappiness',
|
||||||
|
choices=range(0, 100),
|
||||||
|
metavar='[0-100]',
|
||||||
|
help="Tune a container's memory swappiness behavior")
|
||||||
|
parser.add_argument('--name', help='Assign a name to the container')
|
||||||
|
parser.add_argument(
|
||||||
|
'--network',
|
||||||
|
metavar='BRIDGE',
|
||||||
|
help=('Set the Network mode for the container.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--oom-kill-disable',
|
||||||
|
action=BooleanAction,
|
||||||
|
help='Whether to disable OOM Killer for the container or not')
|
||||||
|
parser.add_argument(
|
||||||
|
'--oom-score-adj',
|
||||||
|
choices=range(-1000, 1000),
|
||||||
|
metavar='[-1000-1000]',
|
||||||
|
help="Tune the host's OOM preferences for containers")
|
||||||
|
parser.add_argument('--pid', help='Set the PID mode for the container')
|
||||||
|
parser.add_argument(
|
||||||
|
'--pids-limit',
|
||||||
|
type=int,
|
||||||
|
metavar='LIMIT',
|
||||||
|
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(
|
||||||
|
'--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(
|
||||||
|
'--publish-all',
|
||||||
|
'-P',
|
||||||
|
action=BooleanAction,
|
||||||
|
help='Publish all exposed ports to random'
|
||||||
|
' ports on the host interfaces'
|
||||||
|
'(Default: False)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--quiet',
|
||||||
|
'-q',
|
||||||
|
action='store_true',
|
||||||
|
help='Suppress output information when pulling images')
|
||||||
|
parser.add_argument(
|
||||||
|
'--read-only',
|
||||||
|
action=BooleanAction,
|
||||||
|
help="Mount the container's root filesystem as read only.")
|
||||||
|
parser.add_argument(
|
||||||
|
'--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.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--security-opt',
|
||||||
|
action='append',
|
||||||
|
metavar='OPT',
|
||||||
|
help='Set security options.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--shm-size', action=UnitAction, help='Size of /dev/shm')
|
||||||
|
parser.add_argument(
|
||||||
|
'--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',
|
||||||
|
help='Signal to stop a container')
|
||||||
|
parser.add_argument(
|
||||||
|
'--stop-timeout',
|
||||||
|
metavar='TIMEOUT',
|
||||||
|
type=int,
|
||||||
|
default=10,
|
||||||
|
help='Seconds to wait on stopping container.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--subgidname',
|
||||||
|
metavar='MAP',
|
||||||
|
help='Name for GID map from the /etc/subgid file')
|
||||||
|
parser.add_argument(
|
||||||
|
'--subuidname',
|
||||||
|
metavar='MAP',
|
||||||
|
help='Name for UID map from the /etc/subuid file')
|
||||||
|
parser.add_argument(
|
||||||
|
'--sysctl',
|
||||||
|
action='append',
|
||||||
|
help='Configure namespaced kernel parameters at runtime')
|
||||||
|
parser.add_argument(
|
||||||
|
'--tmpfs', metavar='MOUNT', help='Create a tmpfs mount')
|
||||||
|
parser.add_argument(
|
||||||
|
'--tty',
|
||||||
|
'-t',
|
||||||
|
action=BooleanAction,
|
||||||
|
default=False,
|
||||||
|
help='Allocate a pseudo-TTY for standard input of container.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--uidmap', metavar='MAP', help='UID map for the user namespace')
|
||||||
|
parser.add_argument('--ulimit', metavar='OPT', help='Ulimit options')
|
||||||
|
parser.add_argument(
|
||||||
|
'--user',
|
||||||
|
'-u',
|
||||||
|
help=('Sets the username or UID used and optionally'
|
||||||
|
' the groupname or GID for the specified command.'))
|
||||||
|
parser.add_argument(
|
||||||
|
'--userns',
|
||||||
|
choices=['host', 'ns'],
|
||||||
|
help='Set the usernamespace mode for the container')
|
||||||
|
parser.add_argument(
|
||||||
|
'--uts',
|
||||||
|
choices=['host', 'ns'],
|
||||||
|
help='Set the UTS mode for the container')
|
||||||
|
parser.add_argument('--volume', '-v', help='Create a bind mount.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--volumes-from',
|
||||||
|
action='append',
|
||||||
|
help='Mount volumes from the specified container(s).')
|
||||||
|
parser.add_argument(
|
||||||
|
'--workdir',
|
||||||
|
'-w',
|
||||||
|
metavar='PATH',
|
||||||
|
help='Working directory inside the container')
|
@ -2,7 +2,7 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import podman
|
import podman
|
||||||
from pypodman.lib import AbstractActionBase
|
from pypodman.lib import AbstractActionBase, BooleanAction
|
||||||
|
|
||||||
|
|
||||||
class Commit(AbstractActionBase):
|
class Commit(AbstractActionBase):
|
||||||
@ -47,14 +47,14 @@ class Commit(AbstractActionBase):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--pause',
|
'--pause',
|
||||||
'-p',
|
'-p',
|
||||||
choices=('True', 'False'),
|
action=BooleanAction,
|
||||||
default=True,
|
default=True,
|
||||||
type=bool,
|
|
||||||
help='Pause the container when creating an image',
|
help='Pause the container when creating an image',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--quiet',
|
'--quiet',
|
||||||
'-q',
|
'-q',
|
||||||
|
action='store_true',
|
||||||
help='Suppress output',
|
help='Suppress output',
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@ -71,20 +71,24 @@ class Commit(AbstractActionBase):
|
|||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Commit class."""
|
"""Construct Commit class."""
|
||||||
super().__init__(args)
|
|
||||||
if not args.container:
|
if not args.container:
|
||||||
raise ValueError('You must supply one container id'
|
raise ValueError('You must supply one container id'
|
||||||
' or name to be used as source.')
|
' or name to be used as source.')
|
||||||
if not args.image:
|
if not args.image:
|
||||||
raise ValueError('You must supply one image id'
|
raise ValueError('You must supply one image id'
|
||||||
' or name to be created.')
|
' or name to be created.')
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
|
# used only on client
|
||||||
|
del self.opts['image']
|
||||||
|
del self.opts['container']
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
"""Create image from container."""
|
"""Create image from container."""
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
ctnr = self.client.containers.get(self._args.container[0])
|
ctnr = self.client.containers.get(self._args.container[0])
|
||||||
ident = ctnr.commit(**self._args)
|
ident = ctnr.commit(**self.opts)
|
||||||
print(ident)
|
print(ident)
|
||||||
except podman.ContainerNotFound as e:
|
except podman.ContainerNotFound as e:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
@ -1,413 +1,11 @@
|
|||||||
"""Remote client command for creating container from image."""
|
"""Remote client command for creating container from image."""
|
||||||
import argparse
|
|
||||||
import sys
|
import sys
|
||||||
from builtins import vars
|
from builtins import vars
|
||||||
|
|
||||||
import podman
|
import podman
|
||||||
from pypodman.lib import AbstractActionBase
|
from pypodman.lib import AbstractActionBase
|
||||||
|
|
||||||
|
from ._create_args import CreateArguments
|
||||||
class UnitAction(argparse.Action):
|
|
||||||
"""Validate number given is positive integer, with optional suffix."""
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
"""Validate input."""
|
|
||||||
if isinstance(values, str):
|
|
||||||
if not values[:-1].isdigit():
|
|
||||||
msg = 'unit must be a positive integer, with optional suffix'
|
|
||||||
raise argparse.ArgumentError(self, msg)
|
|
||||||
if not values[-1] in ('b', 'k', 'm', 'g'):
|
|
||||||
msg = 'unit only supports suffices of: b, k, m, g'
|
|
||||||
raise argparse.ArgumentError(self, msg)
|
|
||||||
elif values <= 0:
|
|
||||||
msg = 'number must be a positive integer.'
|
|
||||||
raise argparse.ArgumentError(self, msg)
|
|
||||||
|
|
||||||
setattr(namespace, self.dest, values)
|
|
||||||
|
|
||||||
|
|
||||||
def add_options(parser):
|
|
||||||
"""Add options for Create command."""
|
|
||||||
parser.add_argument(
|
|
||||||
'--add-host',
|
|
||||||
action='append',
|
|
||||||
metavar='HOST',
|
|
||||||
help=('Add a line to /etc/hosts. The format is hostname:ip.'
|
|
||||||
' The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--attach',
|
|
||||||
'-a',
|
|
||||||
action='append',
|
|
||||||
metavar='FD',
|
|
||||||
help=('Attach to STDIN, STDOUT or STDERR. The option can be set'
|
|
||||||
' for each of stdin, stdout, and stderr.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--annotation',
|
|
||||||
action='append',
|
|
||||||
help=('Add an annotation to the container. The format is'
|
|
||||||
' key=value. The option can be set multiple times.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--blkio-weight',
|
|
||||||
choices=range(10, 1000),
|
|
||||||
metavar='[10-1000]',
|
|
||||||
help=('Block IO weight (relative weight) accepts a'
|
|
||||||
' weight value between 10 and 1000.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--blkio-weight-device',
|
|
||||||
action='append',
|
|
||||||
metavar='WEIGHT',
|
|
||||||
help=('Block IO weight (relative device weight,'
|
|
||||||
' format: DEVICE_NAME:WEIGHT).'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cap-add',
|
|
||||||
action='append',
|
|
||||||
metavar='CAP',
|
|
||||||
help=('Add Linux capabilities'
|
|
||||||
'The option can be set multiple times.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cap-drop',
|
|
||||||
action='append',
|
|
||||||
metavar='CAP',
|
|
||||||
help=('Drop Linux capabilities'
|
|
||||||
'The option can be set multiple times.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cgroup-parent',
|
|
||||||
metavar='PATH',
|
|
||||||
help=('Path to cgroups under which the cgroup for the'
|
|
||||||
' container will be created. If the path is not'
|
|
||||||
' absolute, the path is considered to be relative'
|
|
||||||
' to the cgroups path of the init process. Cgroups'
|
|
||||||
' will be created if they do not already exist.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cidfile',
|
|
||||||
metavar='PATH',
|
|
||||||
help='Write the container ID to the file, on the remote host.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--conmon-pidfile',
|
|
||||||
metavar='PATH',
|
|
||||||
help=('Write the pid of the conmon process to a file,'
|
|
||||||
' on the remote host.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpu-count',
|
|
||||||
type=int,
|
|
||||||
metavar='COUNT',
|
|
||||||
help=('Limit the number of CPUs available'
|
|
||||||
' for execution by the container.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpu-period',
|
|
||||||
type=int,
|
|
||||||
metavar='PERIOD',
|
|
||||||
help=('Limit the CPU CFS (Completely Fair Scheduler) period.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpu-quota',
|
|
||||||
type=int,
|
|
||||||
metavar='QUOTA',
|
|
||||||
help=('Limit the CPU CFS (Completely Fair Scheduler) quota.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpu-rt-period',
|
|
||||||
type=int,
|
|
||||||
metavar='PERIOD',
|
|
||||||
help=('Limit the CPU real-time period in microseconds.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpu-rt-runtime',
|
|
||||||
type=int,
|
|
||||||
metavar='LIMIT',
|
|
||||||
help=('Limit the CPU real-time runtime in microseconds.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpu-shares',
|
|
||||||
type=int,
|
|
||||||
metavar='SHARES',
|
|
||||||
help=('CPU shares (relative weight)'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpus',
|
|
||||||
type=int,
|
|
||||||
help=('Number of CPUs. The default is 0 which means no limit'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpuset-cpus',
|
|
||||||
metavar='LIST',
|
|
||||||
help=('CPUs in which to allow execution (0-3, 0,1)'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--cpuset-mems',
|
|
||||||
metavar='NODES',
|
|
||||||
help=('Memory nodes (MEMs) in which to allow execution (0-3, 0,1).'
|
|
||||||
' Only effective on NUMA systems'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--detach',
|
|
||||||
'-d',
|
|
||||||
choices=['True', 'False'],
|
|
||||||
help=('Detached mode: run the container in the background and'
|
|
||||||
' print the new container ID. The default is false.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--detach-keys',
|
|
||||||
metavar='KEY(s)',
|
|
||||||
help=('Override the key sequence for detaching a container.'
|
|
||||||
' Format is a single character [a-Z] or ctrl-<value> where'
|
|
||||||
' <value> is one of: a-z, @, ^, [, , or _.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--device',
|
|
||||||
action='append',
|
|
||||||
help=('Add a host device to the container'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--device-read-bps',
|
|
||||||
action='append',
|
|
||||||
metavar='LIMIT',
|
|
||||||
help=('Limit read rate (bytes per second) from a device'
|
|
||||||
' (e.g. --device-read-bps=/dev/sda:1mb)'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--device-read-iops',
|
|
||||||
action='append',
|
|
||||||
metavar='LIMIT',
|
|
||||||
help=('Limit read rate (IO per second) from a device'
|
|
||||||
' (e.g. --device-read-iops=/dev/sda:1000)'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--device-write-bps',
|
|
||||||
action='append',
|
|
||||||
metavar='LIMIT',
|
|
||||||
help=('Limit write rate (bytes per second) to a device'
|
|
||||||
' (e.g. --device-write-bps=/dev/sda:1mb)'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--device-write-iops',
|
|
||||||
action='append',
|
|
||||||
metavar='LIMIT',
|
|
||||||
help=('Limit write rate (IO per second) to a device'
|
|
||||||
' (e.g. --device-write-iops=/dev/sda:1000)'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--dns',
|
|
||||||
action='append',
|
|
||||||
metavar='SERVER',
|
|
||||||
help=('Set custom DNS servers.'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--dns-option',
|
|
||||||
action='append',
|
|
||||||
metavar='OPT',
|
|
||||||
help=('Set custom DNS options.'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--dns-search',
|
|
||||||
action='append',
|
|
||||||
metavar='DOMAIN',
|
|
||||||
help=('Set custom DNS search domains.'
|
|
||||||
'The option can be set multiple times.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--entrypoint',
|
|
||||||
help=('Overwrite the default ENTRYPOINT of the image.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--env',
|
|
||||||
'-e',
|
|
||||||
action='append',
|
|
||||||
help=('Set environment variables.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--env-file',
|
|
||||||
help=('Read in a line delimited file of environment variables,'
|
|
||||||
' on the remote host.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--expose',
|
|
||||||
metavar='PORT(s)',
|
|
||||||
help=('Expose a port, or a range of ports'
|
|
||||||
' (e.g. --expose=3300-3310) to set up port redirection.'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--gidmap',
|
|
||||||
metavar='MAP',
|
|
||||||
help=('GID map for the user namespace'),
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
'--group-add',
|
|
||||||
action='append',
|
|
||||||
metavar='GROUP',
|
|
||||||
help=('Add additional groups to run as'))
|
|
||||||
parser.add_argument('--hostname', help='Container host name')
|
|
||||||
|
|
||||||
volume_group = parser.add_mutually_exclusive_group()
|
|
||||||
volume_group.add_argument(
|
|
||||||
'--image-volume',
|
|
||||||
choices=['bind', 'tmpfs', 'ignore'],
|
|
||||||
metavar='MODE',
|
|
||||||
help='Tells podman how to handle the builtin image volumes')
|
|
||||||
volume_group.add_argument(
|
|
||||||
'--builtin-volume',
|
|
||||||
choices=['bind', 'tmpfs', 'ignore'],
|
|
||||||
metavar='MODE',
|
|
||||||
help='Tells podman how to handle the builtin image volumes')
|
|
||||||
parser.add_argument(
|
|
||||||
'--interactive',
|
|
||||||
'-i',
|
|
||||||
choices=['True', 'False'],
|
|
||||||
help='Keep STDIN open even if not attached. The default is false')
|
|
||||||
parser.add_argument('--ipc', help='Create namespace')
|
|
||||||
parser.add_argument(
|
|
||||||
'--kernel-memory',
|
|
||||||
action=UnitAction,
|
|
||||||
metavar='UNIT',
|
|
||||||
help=('Kernel memory limit (format: <number>[<unit>],'
|
|
||||||
' where unit = b, k, m or g)'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--label',
|
|
||||||
'-l',
|
|
||||||
help=('Add metadata to a container'
|
|
||||||
' (e.g., --label com.example.key=value)'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--label-file', help='Read in a line delimited file of labels')
|
|
||||||
parser.add_argument(
|
|
||||||
'--log-driver',
|
|
||||||
choices=['json-file', 'journald'],
|
|
||||||
help='Logging driver for the container.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--log-opt', action='append', help='Logging driver specific options')
|
|
||||||
parser.add_argument(
|
|
||||||
'--mac-address', help='Container MAC address (e.g. 92:d0:c6:0a:29:33)')
|
|
||||||
parser.add_argument(
|
|
||||||
'--memory',
|
|
||||||
'-m',
|
|
||||||
action=UnitAction,
|
|
||||||
metavar='UNIT',
|
|
||||||
help='Memory limit (format: [], where unit = b, k, m or g)')
|
|
||||||
parser.add_argument(
|
|
||||||
'--memory-reservation',
|
|
||||||
action=UnitAction,
|
|
||||||
metavar='UNIT',
|
|
||||||
help='Memory soft limit (format: [], where unit = b, k, m or g)')
|
|
||||||
parser.add_argument(
|
|
||||||
'--memory-swap',
|
|
||||||
action=UnitAction,
|
|
||||||
metavar='UNIT',
|
|
||||||
help=('A limit value equal to memory plus swap.'
|
|
||||||
'Must be used with the --memory flag'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--memory-swappiness',
|
|
||||||
choices=range(0, 100),
|
|
||||||
metavar='[0-100]',
|
|
||||||
help="Tune a container's memory swappiness behavior")
|
|
||||||
parser.add_argument('--name', help='Assign a name to the container')
|
|
||||||
parser.add_argument(
|
|
||||||
'--network',
|
|
||||||
metavar='BRIDGE',
|
|
||||||
help=('Set the Network mode for the container.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--oom-kill-disable',
|
|
||||||
choices=['True', 'False'],
|
|
||||||
help='Whether to disable OOM Killer for the container or not')
|
|
||||||
parser.add_argument(
|
|
||||||
'--oom-score-adj',
|
|
||||||
choices=range(-1000, 1000),
|
|
||||||
metavar='[-1000-1000]',
|
|
||||||
help="Tune the host's OOM preferences for containers")
|
|
||||||
parser.add_argument('--pid', help='Set the PID mode for the container')
|
|
||||||
parser.add_argument(
|
|
||||||
'--pids-limit',
|
|
||||||
type=int,
|
|
||||||
metavar='LIMIT',
|
|
||||||
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(
|
|
||||||
'--privileged',
|
|
||||||
choices=['True', 'False'],
|
|
||||||
help='Give extended privileges to this container.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--publish',
|
|
||||||
'-p',
|
|
||||||
metavar='PORT(s)',
|
|
||||||
help="Publish a container's port, or range of ports, to the host")
|
|
||||||
parser.add_argument(
|
|
||||||
'--publish-all',
|
|
||||||
'-P',
|
|
||||||
action='store_true',
|
|
||||||
help=("Publish all exposed ports to random"
|
|
||||||
" ports on the host interfaces"))
|
|
||||||
parser.add_argument(
|
|
||||||
'--quiet',
|
|
||||||
'-q',
|
|
||||||
action='store_true',
|
|
||||||
help='Suppress output information when pulling images')
|
|
||||||
parser.add_argument(
|
|
||||||
'--read-only',
|
|
||||||
choices=['True', 'False'],
|
|
||||||
help="Mount the container's root filesystem as read only.")
|
|
||||||
parser.add_argument(
|
|
||||||
'--rm',
|
|
||||||
choices=['True', '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.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--security-opt',
|
|
||||||
action='append',
|
|
||||||
metavar='OPT',
|
|
||||||
help='Set security options.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--shm-size',
|
|
||||||
action=UnitAction,
|
|
||||||
metavar='UNIT',
|
|
||||||
help='Size of /dev/shm')
|
|
||||||
parser.add_argument(
|
|
||||||
'--stop-signal', metavar='SIGTERM', help='Signal to stop a container')
|
|
||||||
parser.add_argument(
|
|
||||||
'--stop-timeout',
|
|
||||||
metavar='TIMEOUT',
|
|
||||||
help='Seconds to wait on stopping container.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--subgidname',
|
|
||||||
metavar='MAP',
|
|
||||||
help='Name for GID map from the /etc/subgid file')
|
|
||||||
parser.add_argument(
|
|
||||||
'--subuidname',
|
|
||||||
metavar='MAP',
|
|
||||||
help='Name for UID map from the /etc/subuid file')
|
|
||||||
parser.add_argument(
|
|
||||||
'--sysctl',
|
|
||||||
action='append',
|
|
||||||
help='Configure namespaced kernel parameters at runtime')
|
|
||||||
parser.add_argument('--tmpfs', help='Create a tmpfs mount')
|
|
||||||
parser.add_argument(
|
|
||||||
'--tty',
|
|
||||||
'-t',
|
|
||||||
choices=['True', 'False'],
|
|
||||||
help='Allocate a pseudo-TTY for standard input of container.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--uidmap', metavar='MAP', help='UID map for the user namespace')
|
|
||||||
parser.add_argument('--ulimit', metavar='OPT', help='Ulimit options')
|
|
||||||
parser.add_argument(
|
|
||||||
'--user',
|
|
||||||
'-u',
|
|
||||||
help=('Sets the username or UID used and optionally'
|
|
||||||
' the groupname or GID for the specified command.'))
|
|
||||||
parser.add_argument(
|
|
||||||
'--userns',
|
|
||||||
choices=['host', 'ns'],
|
|
||||||
help='Set the usernamespace mode for the container')
|
|
||||||
parser.add_argument(
|
|
||||||
'--uts',
|
|
||||||
choices=['host', 'ns'],
|
|
||||||
help='Set the UTS mode for the container')
|
|
||||||
parser.add_argument('--volume', '-v', help='Create a bind mount.')
|
|
||||||
parser.add_argument(
|
|
||||||
'--volumes-from',
|
|
||||||
action='append',
|
|
||||||
help='Mount volumes from the specified container(s).')
|
|
||||||
parser.add_argument(
|
|
||||||
'--workdir', '-w', help='Working directory inside the container')
|
|
||||||
|
|
||||||
|
|
||||||
class Create(AbstractActionBase):
|
class Create(AbstractActionBase):
|
||||||
@ -419,30 +17,30 @@ class Create(AbstractActionBase):
|
|||||||
parser = parent.add_parser(
|
parser = parent.add_parser(
|
||||||
'create', help='create container from image')
|
'create', help='create container from image')
|
||||||
|
|
||||||
add_options(parser)
|
CreateArguments.add_arguments(parser)
|
||||||
|
|
||||||
parser.add_argument('image', nargs='*', help='source image id.')
|
parser.add_argument('image', nargs=1, help='source image id')
|
||||||
|
parser.add_argument(
|
||||||
|
'command',
|
||||||
|
nargs='*',
|
||||||
|
help='command and args to run.',
|
||||||
|
)
|
||||||
parser.set_defaults(class_=cls, method='create')
|
parser.set_defaults(class_=cls, method='create')
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Create class."""
|
"""Construct Create class."""
|
||||||
super().__init__(args)
|
super().__init__(args)
|
||||||
if not args.image:
|
|
||||||
raise ValueError('You must supply at least one image id'
|
# image id used only on client
|
||||||
' or name to be retrieved.')
|
del self.opts['image']
|
||||||
|
|
||||||
def create(self):
|
def create(self):
|
||||||
"""Create container."""
|
"""Create container."""
|
||||||
# Dump all unset arguments before transmitting to service
|
try:
|
||||||
opts = {k: v for k, v in vars(self._args).items() if v is not None}
|
|
||||||
|
|
||||||
# image id(s) used only on client
|
|
||||||
del opts['image']
|
|
||||||
|
|
||||||
for ident in self._args.image:
|
for ident in self._args.image:
|
||||||
try:
|
try:
|
||||||
img = self.client.images.get(ident)
|
img = self.client.images.get(ident)
|
||||||
img.container(**opts)
|
img.container(**self.opts)
|
||||||
print(ident)
|
print(ident)
|
||||||
except podman.ImageNotFound as e:
|
except podman.ImageNotFound as e:
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
|
@ -29,7 +29,6 @@ class Export(AbstractActionBase):
|
|||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Export class."""
|
"""Construct Export class."""
|
||||||
super().__init__(args)
|
|
||||||
if not args.container:
|
if not args.container:
|
||||||
raise ValueError('You must supply one container id'
|
raise ValueError('You must supply one container id'
|
||||||
' or name to be used as source.')
|
' or name to be used as source.')
|
||||||
@ -37,6 +36,7 @@ class Export(AbstractActionBase):
|
|||||||
if not args.output:
|
if not args.output:
|
||||||
raise ValueError('You must supply one filename'
|
raise ValueError('You must supply one filename'
|
||||||
' to be created as tarball using --output.')
|
' to be created as tarball using --output.')
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
def export(self):
|
def export(self):
|
||||||
"""Create tarball from container filesystem."""
|
"""Create tarball from container filesystem."""
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
"""Remote client for reporting image history."""
|
||||||
|
import json
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import humanize
|
||||||
|
|
||||||
|
import podman
|
||||||
|
from pypodman.lib import (AbstractActionBase, BooleanAction, Report,
|
||||||
|
ReportColumn)
|
||||||
|
|
||||||
|
|
||||||
|
class History(AbstractActionBase):
|
||||||
|
"""Class for reporting Image History."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add History command to parent parser."""
|
||||||
|
parser = parent.add_parser('history', help='report image history')
|
||||||
|
super().subparser(parser)
|
||||||
|
parser.add_argument(
|
||||||
|
'--human',
|
||||||
|
'-H',
|
||||||
|
action=BooleanAction,
|
||||||
|
default='True',
|
||||||
|
help='Display sizes and dates in human readable format.'
|
||||||
|
' (default: %(default)s)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--format',
|
||||||
|
choices=('json', 'table'),
|
||||||
|
help="Alter the output for a format like 'json' or 'table'."
|
||||||
|
" (default: table)")
|
||||||
|
parser.add_argument(
|
||||||
|
'image', nargs='+', help='image for history report')
|
||||||
|
parser.set_defaults(class_=cls, method='history')
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
"""Construct History class."""
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
|
self.columns = OrderedDict({
|
||||||
|
'id':
|
||||||
|
ReportColumn('id', 'ID', 12),
|
||||||
|
'created':
|
||||||
|
ReportColumn('created', 'CREATED', 11),
|
||||||
|
'createdBy':
|
||||||
|
ReportColumn('createdBy', 'CREATED BY', 45),
|
||||||
|
'size':
|
||||||
|
ReportColumn('size', 'SIZE', 8),
|
||||||
|
'comment':
|
||||||
|
ReportColumn('comment', 'COMMENT', 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
def history(self):
|
||||||
|
"""Report image history."""
|
||||||
|
rows = list()
|
||||||
|
for ident in self._args.image:
|
||||||
|
for details in self.client.images.get(ident).history():
|
||||||
|
fields = dict(details._asdict())
|
||||||
|
|
||||||
|
if self._args.human:
|
||||||
|
fields.update({
|
||||||
|
'size':
|
||||||
|
humanize.naturalsize(details.size, binary=True),
|
||||||
|
'created':
|
||||||
|
humanize.naturaldate(
|
||||||
|
podman.datetime_parse(details.created)),
|
||||||
|
})
|
||||||
|
del fields['tags']
|
||||||
|
|
||||||
|
rows.append(fields)
|
||||||
|
|
||||||
|
if self._args.quiet:
|
||||||
|
for row in rows:
|
||||||
|
ident = row['id'][:12] if self._args.truncate else row['id']
|
||||||
|
print(ident)
|
||||||
|
elif self._args.format == 'json':
|
||||||
|
print(json.dumps(rows, indent=2), flush=True)
|
||||||
|
else:
|
||||||
|
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)
|
@ -65,7 +65,7 @@ class Images(AbstractActionBase):
|
|||||||
'created':
|
'created':
|
||||||
humanize.naturaldate(podman.datetime_parse(image.created)),
|
humanize.naturaldate(podman.datetime_parse(image.created)),
|
||||||
'size':
|
'size':
|
||||||
humanize.naturalsize(int(image.size)),
|
humanize.naturalsize(int(image.size), binary=True),
|
||||||
'repoDigests':
|
'repoDigests':
|
||||||
' '.join(image.repoDigests),
|
' '.join(image.repoDigests),
|
||||||
})
|
})
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
"""Remote client command to import tarball as image filesystem."""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import podman
|
||||||
|
from pypodman.lib import AbstractActionBase
|
||||||
|
|
||||||
|
|
||||||
|
class Import(AbstractActionBase):
|
||||||
|
"""Class for importing tarball as image filesystem."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add Import command to parent parser."""
|
||||||
|
parser = parent.add_parser(
|
||||||
|
'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',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--message', '-m', help='Set commit message for imported image.')
|
||||||
|
parser.add_argument(
|
||||||
|
'source',
|
||||||
|
metavar='PATH',
|
||||||
|
nargs=1,
|
||||||
|
help='tarball to use as source on remote system',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'reference',
|
||||||
|
metavar='TAG',
|
||||||
|
nargs='*',
|
||||||
|
help='Optional tag for image. (default: None)',
|
||||||
|
)
|
||||||
|
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."""
|
||||||
|
try:
|
||||||
|
ident = self.client.images.import_image(
|
||||||
|
self.opts.source,
|
||||||
|
self.opts.reference,
|
||||||
|
message=self.opts.message,
|
||||||
|
changes=self.opts.change)
|
||||||
|
print(ident)
|
||||||
|
except podman.ErrorOccurred as e:
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(
|
||||||
|
'{}'.format(e.reason).capitalize(),
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True)
|
||||||
|
return 1
|
49
contrib/python/pypodman/pypodman/lib/actions/info_action.py
Normal file
49
contrib/python/pypodman/pypodman/lib/actions/info_action.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
"""Remote client command for reporting on Podman service."""
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import podman
|
||||||
|
import yaml
|
||||||
|
from pypodman.lib import AbstractActionBase
|
||||||
|
|
||||||
|
|
||||||
|
class Info(AbstractActionBase):
|
||||||
|
"""Class for reporting on Podman Service."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add Info command to parent parser."""
|
||||||
|
parser = parent.add_parser(
|
||||||
|
'info', help='report info on podman service')
|
||||||
|
parser.add_argument(
|
||||||
|
'--format',
|
||||||
|
choices=('json', 'yaml'),
|
||||||
|
help="Alter the output for a format like 'json' or 'yaml'."
|
||||||
|
" (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:
|
||||||
|
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:
|
||||||
|
if self._args.format == 'json':
|
||||||
|
print(json.dumps(info._asdict(), indent=2), flush=True)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
yaml.dump(
|
||||||
|
dict(info._asdict()),
|
||||||
|
canonical=False,
|
||||||
|
default_flow_style=False),
|
||||||
|
flush=True)
|
@ -41,7 +41,7 @@ class Inspect(AbstractActionBase):
|
|||||||
|
|
||||||
def _get_container(self, ident):
|
def _get_container(self, ident):
|
||||||
try:
|
try:
|
||||||
logging.debug("Get container %s", ident)
|
logging.debug("Getting container %s", ident)
|
||||||
ctnr = self.client.containers.get(ident)
|
ctnr = self.client.containers.get(ident)
|
||||||
except podman.ContainerNotFound:
|
except podman.ContainerNotFound:
|
||||||
pass
|
pass
|
||||||
@ -50,7 +50,7 @@ class Inspect(AbstractActionBase):
|
|||||||
|
|
||||||
def _get_image(self, ident):
|
def _get_image(self, ident):
|
||||||
try:
|
try:
|
||||||
logging.debug("Get image %s", ident)
|
logging.debug("Getting image %s", ident)
|
||||||
img = self.client.images.get(ident)
|
img = self.client.images.get(ident)
|
||||||
except podman.ImageNotFound:
|
except podman.ImageNotFound:
|
||||||
pass
|
pass
|
||||||
|
@ -19,7 +19,7 @@ class Kill(AbstractActionBase):
|
|||||||
choices=range(1, signal.NSIG),
|
choices=range(1, signal.NSIG),
|
||||||
metavar='[1,{}]'.format(signal.NSIG),
|
metavar='[1,{}]'.format(signal.NSIG),
|
||||||
default=9,
|
default=9,
|
||||||
help='Signal to send to the container. (Default: 9)')
|
help='Signal to send to the container. (default: 9)')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'containers',
|
'containers',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
|
@ -5,20 +5,7 @@ import sys
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
import podman
|
import podman
|
||||||
from pypodman.lib import AbstractActionBase
|
from pypodman.lib import AbstractActionBase, PositiveIntAction
|
||||||
|
|
||||||
|
|
||||||
class PositiveIntAction(argparse.Action):
|
|
||||||
"""Validate number given is positive integer."""
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
"""Validate input."""
|
|
||||||
if values > 0:
|
|
||||||
setattr(namespace, self.dest, values)
|
|
||||||
return
|
|
||||||
|
|
||||||
msg = 'Must be a positive integer.'
|
|
||||||
raise argparse.ArgumentError(self, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class Logs(AbstractActionBase):
|
class Logs(AbstractActionBase):
|
||||||
@ -32,7 +19,6 @@ class Logs(AbstractActionBase):
|
|||||||
'--tail',
|
'--tail',
|
||||||
metavar='LINES',
|
metavar='LINES',
|
||||||
action=PositiveIntAction,
|
action=PositiveIntAction,
|
||||||
type=int,
|
|
||||||
help='Output the specified number of LINES at the end of the logs')
|
help='Output the specified number of LINES at the end of the logs')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'container',
|
'container',
|
||||||
|
@ -29,10 +29,10 @@ class Port(AbstractActionBase):
|
|||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Port class."""
|
"""Construct Port class."""
|
||||||
super().__init__(args)
|
|
||||||
if not args.all and not args.containers:
|
if not args.all and not args.containers:
|
||||||
ValueError('You must supply at least one'
|
ValueError('You must supply at least one'
|
||||||
' container id or name, or --all.')
|
' container id or name, or --all.')
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
def port(self):
|
def port(self):
|
||||||
"""Retrieve ports from containers."""
|
"""Retrieve ports from containers."""
|
||||||
|
@ -18,10 +18,8 @@ class Ps(AbstractActionBase):
|
|||||||
super().subparser(parser)
|
super().subparser(parser)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--sort',
|
'--sort',
|
||||||
choices=[
|
choices=('createdat', 'id', 'image', 'names', 'runningfor', 'size',
|
||||||
'createdat', 'id', 'image', 'names', 'runningfor', 'size',
|
'status'),
|
||||||
'status'
|
|
||||||
],
|
|
||||||
default='createdat',
|
default='createdat',
|
||||||
type=str.lower,
|
type=str.lower,
|
||||||
help=('Change sort ordered of displayed containers.'
|
help=('Change sort ordered of displayed containers.'
|
||||||
|
@ -17,7 +17,7 @@ class Pull(AbstractActionBase):
|
|||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'targets',
|
'targets',
|
||||||
nargs='*',
|
nargs='+',
|
||||||
help='image id(s) to retrieve.',
|
help='image id(s) to retrieve.',
|
||||||
)
|
)
|
||||||
parser.set_defaults(class_=cls, method='pull')
|
parser.set_defaults(class_=cls, method='pull')
|
||||||
@ -25,9 +25,6 @@ class Pull(AbstractActionBase):
|
|||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Pull class."""
|
"""Construct Pull class."""
|
||||||
super().__init__(args)
|
super().__init__(args)
|
||||||
if not args.targets:
|
|
||||||
raise ValueError('You must supply at least one container id'
|
|
||||||
' or name to be retrieved.')
|
|
||||||
|
|
||||||
def pull(self):
|
def pull(self):
|
||||||
"""Retrieve image."""
|
"""Retrieve image."""
|
||||||
|
56
contrib/python/pypodman/pypodman/lib/actions/push_action.py
Normal file
56
contrib/python/pypodman/pypodman/lib/actions/push_action.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"""Remote client command for pushing image elsewhere."""
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import podman
|
||||||
|
from pypodman.lib import AbstractActionBase
|
||||||
|
|
||||||
|
|
||||||
|
class Push(AbstractActionBase):
|
||||||
|
"""Class for pushing images to repository."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add Push command to parent parser."""
|
||||||
|
parser = parent.add_parser(
|
||||||
|
'push',
|
||||||
|
help='push image elsewhere',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--tlsverify',
|
||||||
|
action='store_true',
|
||||||
|
default=True,
|
||||||
|
help='Require HTTPS and verify certificates when'
|
||||||
|
' contacting registries (default: %(default)s)')
|
||||||
|
parser.add_argument(
|
||||||
|
'image', nargs=1, help='name or id of image to push')
|
||||||
|
parser.add_argument(
|
||||||
|
'tag',
|
||||||
|
nargs=1,
|
||||||
|
help='destination image id',
|
||||||
|
)
|
||||||
|
parser.set_defaults(class_=cls, method='push')
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
"""Construct Push class."""
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
|
def pull(self):
|
||||||
|
"""Store image elsewhere."""
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
img = self.client.images.get(self._args.image[0])
|
||||||
|
except podman.ImageNotFound as e:
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(
|
||||||
|
'Image {} not found.'.format(e.name),
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True)
|
||||||
|
else:
|
||||||
|
img.push(self._args.tag[0], tlsverify=self._args.tlsverify)
|
||||||
|
print(self._args.image[0])
|
||||||
|
except podman.ErrorOccurred as e:
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(
|
||||||
|
'{}'.format(e.reason).capitalize(),
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True)
|
@ -0,0 +1,50 @@
|
|||||||
|
"""Remote client command for restarting containers."""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import podman
|
||||||
|
from pypodman.lib import AbstractActionBase, PositiveIntAction
|
||||||
|
|
||||||
|
|
||||||
|
class Restart(AbstractActionBase):
|
||||||
|
"""Class for Restarting containers."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add Restart command to parent parser."""
|
||||||
|
parser = parent.add_parser('restart', help='restart container(s)')
|
||||||
|
parser.add_argument(
|
||||||
|
'--timeout',
|
||||||
|
action=PositiveIntAction,
|
||||||
|
default=10,
|
||||||
|
help='Timeout to wait before forcibly stopping the container'
|
||||||
|
' (default: %(default)s seconds)')
|
||||||
|
parser.add_argument(
|
||||||
|
'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:
|
||||||
|
for ident in self._args.targets:
|
||||||
|
try:
|
||||||
|
ctnr = self.client.containers.get(ident)
|
||||||
|
logging.debug('Restarting Container %s', ctnr.id)
|
||||||
|
ctnr.restart(timeout=self._args.timeout)
|
||||||
|
print(ident)
|
||||||
|
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)
|
@ -19,15 +19,12 @@ class Rm(AbstractActionBase):
|
|||||||
help=('force delete of running container(s).'
|
help=('force delete of running container(s).'
|
||||||
' (default: %(default)s)'))
|
' (default: %(default)s)'))
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'targets', nargs='*', help='container id(s) to delete')
|
'targets', nargs='+', help='container id(s) to delete')
|
||||||
parser.set_defaults(class_=cls, method='remove')
|
parser.set_defaults(class_=cls, method='remove')
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Rm class."""
|
"""Construct Rm class."""
|
||||||
super().__init__(args)
|
super().__init__(args)
|
||||||
if not args.targets:
|
|
||||||
raise ValueError('You must supply at least one container id'
|
|
||||||
' or name to be deleted.')
|
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Remove container(s)."""
|
"""Remove container(s)."""
|
||||||
|
@ -18,15 +18,12 @@ class Rmi(AbstractActionBase):
|
|||||||
action='store_true',
|
action='store_true',
|
||||||
help=('force delete of image(s) and associated containers.'
|
help=('force delete of image(s) and associated containers.'
|
||||||
' (default: %(default)s)'))
|
' (default: %(default)s)'))
|
||||||
parser.add_argument('targets', nargs='*', help='image id(s) to delete')
|
parser.add_argument('targets', nargs='+', help='image id(s) to delete')
|
||||||
parser.set_defaults(class_=cls, method='remove')
|
parser.set_defaults(class_=cls, method='remove')
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args):
|
||||||
"""Construct Rmi class."""
|
"""Construct Rmi class."""
|
||||||
super().__init__(args)
|
super().__init__(args)
|
||||||
if not args.targets:
|
|
||||||
raise ValueError('You must supply at least one image id'
|
|
||||||
' or name to be deleted.')
|
|
||||||
|
|
||||||
def remove(self):
|
def remove(self):
|
||||||
"""Remove image(s)."""
|
"""Remove image(s)."""
|
||||||
|
73
contrib/python/pypodman/pypodman/lib/actions/run_action.py
Normal file
73
contrib/python/pypodman/pypodman/lib/actions/run_action.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
"""Remote client command for run a command in a new container."""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import podman
|
||||||
|
from pypodman.lib import AbstractActionBase
|
||||||
|
|
||||||
|
from ._create_args import CreateArguments
|
||||||
|
|
||||||
|
|
||||||
|
class Run(AbstractActionBase):
|
||||||
|
"""Class for running a command in a container."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add Run command to parent parser."""
|
||||||
|
parser = parent.add_parser('run', help='Run container from image')
|
||||||
|
|
||||||
|
CreateArguments.add_arguments(parser)
|
||||||
|
|
||||||
|
parser.add_argument('image', nargs=1, help='source image id.')
|
||||||
|
parser.add_argument(
|
||||||
|
'command',
|
||||||
|
nargs='*',
|
||||||
|
help='command and args to run.',
|
||||||
|
)
|
||||||
|
parser.set_defaults(class_=cls, method='run')
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
"""Construct Run class."""
|
||||||
|
super().__init__(args)
|
||||||
|
if args.detach and args.rm:
|
||||||
|
raise ValueError('Incompatible options: --detach and --rm')
|
||||||
|
|
||||||
|
# image id used only on client
|
||||||
|
del self.opts['image']
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""Run container."""
|
||||||
|
for ident in self._args.image:
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
img = self.client.images.get(ident)
|
||||||
|
ctnr = img.container(**self.opts)
|
||||||
|
except podman.ImageNotFound as e:
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(
|
||||||
|
'Image {} not found.'.format(e.name),
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logging.debug('New container created "{}"'.format(ctnr.id))
|
||||||
|
|
||||||
|
if self._args.detach:
|
||||||
|
ctnr.start()
|
||||||
|
print(ctnr.id)
|
||||||
|
else:
|
||||||
|
ctnr.attach(eot=4)
|
||||||
|
ctnr.start()
|
||||||
|
print(ctnr.id)
|
||||||
|
|
||||||
|
if self._args.rm:
|
||||||
|
ctnr.remove(force=True)
|
||||||
|
except (BrokenPipeError, KeyboardInterrupt):
|
||||||
|
print('\nContainer "{}" disconnected.'.format(ctnr.id))
|
||||||
|
except podman.ErrorOccurred as e:
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(
|
||||||
|
'Run for container "{}" failed: {} {}'.format(
|
||||||
|
ctnr.id, repr(e), e.reason.capitalize()),
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True)
|
160
contrib/python/pypodman/pypodman/lib/actions/search_action.py
Normal file
160
contrib/python/pypodman/pypodman/lib/actions/search_action.py
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
"""Remote client command for searching registries for an image."""
|
||||||
|
import argparse
|
||||||
|
import sys
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
import podman
|
||||||
|
from pypodman.lib import (AbstractActionBase, BooleanValidate,
|
||||||
|
PositiveIntAction, Report, ReportColumn)
|
||||||
|
|
||||||
|
|
||||||
|
class FilterAction(argparse.Action):
|
||||||
|
"""Parse filter argument components."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
option_strings,
|
||||||
|
dest,
|
||||||
|
nargs=None,
|
||||||
|
const=None,
|
||||||
|
default=None,
|
||||||
|
type=None,
|
||||||
|
choices=None,
|
||||||
|
required=False,
|
||||||
|
help=None,
|
||||||
|
metavar='FILTER'):
|
||||||
|
"""Create FilterAction object."""
|
||||||
|
help = (help or '') + (' (format: stars=##'
|
||||||
|
' or is-automated=[True|False]'
|
||||||
|
' or is-official=[True|False])')
|
||||||
|
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.
|
||||||
|
|
||||||
|
Note: side effects
|
||||||
|
1) self.dest value is set to subargument dest
|
||||||
|
2) new attribute self.dest + '_value' is created with 2nd value.
|
||||||
|
"""
|
||||||
|
opt, val = values.split('=', 1)
|
||||||
|
if opt == 'stars':
|
||||||
|
msg = ('{} option "stars" requires'
|
||||||
|
' a positive integer').format(self.dest)
|
||||||
|
try:
|
||||||
|
val = int(val)
|
||||||
|
except ValueError:
|
||||||
|
parser.error(msg)
|
||||||
|
|
||||||
|
if val < 0:
|
||||||
|
parser.error(msg)
|
||||||
|
elif opt == 'is-automated':
|
||||||
|
try:
|
||||||
|
val = BooleanValidate()(val)
|
||||||
|
except ValueError:
|
||||||
|
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:
|
||||||
|
msg = ('{} option "is-official"'
|
||||||
|
' must be True or False.'.format(self.dest))
|
||||||
|
parser.error(msg)
|
||||||
|
else:
|
||||||
|
msg = ('{} only supports one of the following options:\n'
|
||||||
|
' stars, is-automated, or is-official').format(self.dest)
|
||||||
|
parser.error(msg)
|
||||||
|
setattr(namespace, self.dest, opt)
|
||||||
|
setattr(namespace, self.dest + '_value', val)
|
||||||
|
|
||||||
|
|
||||||
|
class Search(AbstractActionBase):
|
||||||
|
"""Class for searching registries for an image."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def subparser(cls, parent):
|
||||||
|
"""Add Search command to parent parser."""
|
||||||
|
parser = parent.add_parser('search', help='search for images')
|
||||||
|
super().subparser(parser)
|
||||||
|
parser.add_argument(
|
||||||
|
'--filter',
|
||||||
|
'-f',
|
||||||
|
action=FilterAction,
|
||||||
|
help='Filter output based on conditions provided.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--limit',
|
||||||
|
action=PositiveIntAction,
|
||||||
|
default=25,
|
||||||
|
help='Limit the number of results.'
|
||||||
|
' (default: %(default)s)')
|
||||||
|
parser.add_argument('term', nargs=1, help='search term for image')
|
||||||
|
parser.set_defaults(class_=cls, method='search')
|
||||||
|
|
||||||
|
def __init__(self, args):
|
||||||
|
"""Construct Search class."""
|
||||||
|
super().__init__(args)
|
||||||
|
|
||||||
|
self.columns = OrderedDict({
|
||||||
|
'name':
|
||||||
|
ReportColumn('name', 'NAME', 44),
|
||||||
|
'description':
|
||||||
|
ReportColumn('description', 'DESCRIPTION', 44),
|
||||||
|
'star_count':
|
||||||
|
ReportColumn('star_count', 'STARS', 5),
|
||||||
|
'is_official':
|
||||||
|
ReportColumn('is_official', 'OFFICIAL', 8),
|
||||||
|
'is_automated':
|
||||||
|
ReportColumn('is_automated', 'AUTOMATED', 9),
|
||||||
|
})
|
||||||
|
|
||||||
|
def search(self):
|
||||||
|
"""Search registries for image."""
|
||||||
|
try:
|
||||||
|
rows = list()
|
||||||
|
for entry in self.client.images.search(
|
||||||
|
self._args.term[0], limit=self._args.limit):
|
||||||
|
|
||||||
|
if self._args.filter == 'is-official':
|
||||||
|
if self._args.filter_value != entry.is_official:
|
||||||
|
continue
|
||||||
|
elif self._args.filter == 'is-automated':
|
||||||
|
if self._args.filter_value != entry.is_automated:
|
||||||
|
continue
|
||||||
|
elif self._args.filter == 'stars':
|
||||||
|
if self._args.filter_value > entry.star_count:
|
||||||
|
continue
|
||||||
|
|
||||||
|
fields = dict(entry._asdict())
|
||||||
|
|
||||||
|
status = '[OK]' if entry.is_official else ''
|
||||||
|
fields['is_official'] = status
|
||||||
|
|
||||||
|
status = '[OK]' if entry.is_automated else ''
|
||||||
|
fields['is_automated'] = status
|
||||||
|
|
||||||
|
if self._args.truncate:
|
||||||
|
fields.update({'name': entry.name[-44:]})
|
||||||
|
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)
|
||||||
|
except podman.ErrorOccurred as e:
|
||||||
|
sys.stdout.flush()
|
||||||
|
print(
|
||||||
|
'{}'.format(e.reason).capitalize(),
|
||||||
|
file=sys.stderr,
|
||||||
|
flush=True)
|
185
contrib/python/pypodman/pypodman/lib/parser_actions.py
Normal file
185
contrib/python/pypodman/pypodman/lib/parser_actions.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
"""
|
||||||
|
Supplimental argparse.Action converters and validaters.
|
||||||
|
|
||||||
|
The constructors are very verbose but remain for IDE support.
|
||||||
|
"""
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
# API defined by argparse.Action shut up pylint
|
||||||
|
# pragma pylint: disable=redefined-builtin
|
||||||
|
# pragma pylint: disable=too-few-public-methods
|
||||||
|
# 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."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
option_strings,
|
||||||
|
dest,
|
||||||
|
nargs=None,
|
||||||
|
const=None,
|
||||||
|
default=None,
|
||||||
|
type=None,
|
||||||
|
choices=('True', 'False'),
|
||||||
|
required=False,
|
||||||
|
help=None,
|
||||||
|
metavar='{True,False}'):
|
||||||
|
"""Create BooleanAction object."""
|
||||||
|
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."""
|
||||||
|
try:
|
||||||
|
val = BooleanValidate()(values)
|
||||||
|
except ValueError:
|
||||||
|
parser.error('{} must be True or False.'.format(self.dest))
|
||||||
|
else:
|
||||||
|
setattr(namespace, self.dest, val)
|
||||||
|
|
||||||
|
|
||||||
|
class UnitAction(argparse.Action):
|
||||||
|
"""Validate number given is positive integer, with optional suffix."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
option_strings,
|
||||||
|
dest,
|
||||||
|
nargs=None,
|
||||||
|
const=None,
|
||||||
|
default=None,
|
||||||
|
type=None,
|
||||||
|
choices=None,
|
||||||
|
required=False,
|
||||||
|
help=None,
|
||||||
|
metavar='UNIT'):
|
||||||
|
"""Create UnitAction object."""
|
||||||
|
help = (help or metavar or dest
|
||||||
|
) + ' (format: <number>[<unit>], where unit = b, k, m or g)'
|
||||||
|
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):
|
||||||
|
"""Validate input as a UNIT."""
|
||||||
|
try:
|
||||||
|
val = int(values)
|
||||||
|
except ValueError:
|
||||||
|
if not values[:-1].isdigit():
|
||||||
|
msg = ('{} must be a positive integer,'
|
||||||
|
' with optional suffix').format(self.dest)
|
||||||
|
parser.error(msg)
|
||||||
|
if not values[-1] in ('b', 'k', 'm', 'g'):
|
||||||
|
msg = '{} only supports suffices of: b, k, m, g'.format(
|
||||||
|
self.dest)
|
||||||
|
parser.error(msg)
|
||||||
|
else:
|
||||||
|
if val <= 0:
|
||||||
|
msg = '{} must be a positive integer'.format(self.dest)
|
||||||
|
parser.error(msg)
|
||||||
|
|
||||||
|
setattr(namespace, self.dest, values)
|
||||||
|
|
||||||
|
|
||||||
|
class PositiveIntAction(argparse.Action):
|
||||||
|
"""Validate number given is positive integer."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
option_strings,
|
||||||
|
dest,
|
||||||
|
nargs=None,
|
||||||
|
const=None,
|
||||||
|
default=None,
|
||||||
|
type=int,
|
||||||
|
choices=None,
|
||||||
|
required=False,
|
||||||
|
help=None,
|
||||||
|
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,
|
||||||
|
choices=choices,
|
||||||
|
required=required,
|
||||||
|
help=help,
|
||||||
|
metavar=metavar)
|
||||||
|
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
"""Validate input."""
|
||||||
|
if values > 0:
|
||||||
|
setattr(namespace, self.dest, values)
|
||||||
|
return
|
||||||
|
|
||||||
|
parser.error(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
class PathAction(argparse.Action):
|
||||||
|
"""Expand user- and relative-paths."""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
option_strings,
|
||||||
|
dest,
|
||||||
|
nargs=None,
|
||||||
|
const=None,
|
||||||
|
default=None,
|
||||||
|
type=None,
|
||||||
|
choices=None,
|
||||||
|
required=False,
|
||||||
|
help=None,
|
||||||
|
metavar='PATH'):
|
||||||
|
"""Create PathAction object."""
|
||||||
|
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):
|
||||||
|
"""Resolve full path value on local filesystem."""
|
||||||
|
setattr(namespace, self.dest,
|
||||||
|
os.path.abspath(os.path.expanduser(values)))
|
@ -7,10 +7,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import pytoml
|
import pytoml
|
||||||
|
|
||||||
|
from .parser_actions import PathAction, PositiveIntAction
|
||||||
|
|
||||||
# TODO: setup.py and obtain __version__ from rpm.spec
|
# TODO: setup.py and obtain __version__ from rpm.spec
|
||||||
try:
|
try:
|
||||||
__version__ = pkg_resources.get_distribution('pypodman').version
|
__version__ = pkg_resources.get_distribution('pypodman').version
|
||||||
@ -33,35 +36,14 @@ class HelpFormatter(argparse.RawDescriptionHelpFormatter):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class PortAction(argparse.Action):
|
|
||||||
"""Validate port number given is positive integer."""
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
"""Validate input."""
|
|
||||||
if values > 0:
|
|
||||||
setattr(namespace, self.dest, values)
|
|
||||||
return
|
|
||||||
|
|
||||||
msg = 'port numbers must be a positive integer.'
|
|
||||||
raise argparse.ArgumentError(self, msg)
|
|
||||||
|
|
||||||
|
|
||||||
class PathAction(argparse.Action):
|
|
||||||
"""Expand user- and relative-paths."""
|
|
||||||
|
|
||||||
def __call__(self, parser, namespace, values, option_string=None):
|
|
||||||
"""Resolve full path value."""
|
|
||||||
setattr(namespace, self.dest,
|
|
||||||
os.path.abspath(os.path.expanduser(values)))
|
|
||||||
|
|
||||||
|
|
||||||
class PodmanArgumentParser(argparse.ArgumentParser):
|
class PodmanArgumentParser(argparse.ArgumentParser):
|
||||||
"""Default remote podman configuration."""
|
"""Default remote podman configuration."""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
"""Construct the parser."""
|
"""Construct the parser."""
|
||||||
kwargs['add_help'] = True
|
kwargs['add_help'] = True
|
||||||
kwargs['description'] = __doc__
|
kwargs['description'] = ('Portable and simple management'
|
||||||
|
' tool for containers and images')
|
||||||
kwargs['formatter_class'] = HelpFormatter
|
kwargs['formatter_class'] = HelpFormatter
|
||||||
|
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
@ -83,9 +65,9 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
'--run-dir',
|
'--run-dir',
|
||||||
metavar='DIRECTORY',
|
metavar='DIRECTORY',
|
||||||
help=('directory to place local socket bindings.'
|
help=('directory to place local socket bindings.'
|
||||||
' (default: XDG_RUNTIME_DIR/pypodman'))
|
' (default: XDG_RUNTIME_DIR/pypodman)'))
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--user',
|
'--username',
|
||||||
'-l',
|
'-l',
|
||||||
default=getpass.getuser(),
|
default=getpass.getuser(),
|
||||||
help='Authenicating user on remote host. (default: %(default)s)')
|
help='Authenicating user on remote host. (default: %(default)s)')
|
||||||
@ -94,8 +76,7 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--port',
|
'--port',
|
||||||
'-p',
|
'-p',
|
||||||
type=int,
|
action=PositiveIntAction,
|
||||||
action=PortAction,
|
|
||||||
help='port for ssh tunnel to remote host. (default: 22)')
|
help='port for ssh tunnel to remote host. (default: 22)')
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--remote-socket-path',
|
'--remote-socket-path',
|
||||||
@ -105,18 +86,17 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--identity-file',
|
'--identity-file',
|
||||||
'-i',
|
'-i',
|
||||||
metavar='PATH',
|
|
||||||
action=PathAction,
|
action=PathAction,
|
||||||
help=('path to ssh identity file. (default: ~user/.ssh/id_dsa)'))
|
help='path to ssh identity file. (default: ~user/.ssh/id_dsa)')
|
||||||
self.add_argument(
|
self.add_argument(
|
||||||
'--config-home',
|
'--config-home',
|
||||||
metavar='DIRECTORY',
|
metavar='DIRECTORY',
|
||||||
action=PathAction,
|
action=PathAction,
|
||||||
help=('home of configuration "pypodman.conf".'
|
help=('home of configuration "pypodman.conf".'
|
||||||
' (default: XDG_CONFIG_HOME/pypodman'))
|
' (default: XDG_CONFIG_HOME/pypodman)'))
|
||||||
|
|
||||||
actions_parser = self.add_subparsers(
|
actions_parser = self.add_subparsers(
|
||||||
dest='subparser_name', help='actions')
|
dest='subparser_name', help='commands')
|
||||||
|
|
||||||
# import buried here to prevent import loops
|
# import buried here to prevent import loops
|
||||||
import pypodman.lib.actions # pylint: disable=cyclic-import
|
import pypodman.lib.actions # pylint: disable=cyclic-import
|
||||||
@ -157,11 +137,12 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
if dir_ is None:
|
if dir_ is None:
|
||||||
continue
|
continue
|
||||||
with suppress(OSError):
|
with suppress(OSError):
|
||||||
with open(os.path.join(dir_,
|
cnf = Path(dir_, 'pypodman', 'pypodman.conf')
|
||||||
'pypodman/pypodman.conf')) as stream:
|
with cnf.open() as stream:
|
||||||
config.update(pytoml.load(stream))
|
config.update(pytoml.load(stream))
|
||||||
|
|
||||||
def reqattr(name, value):
|
def reqattr(name, value):
|
||||||
|
"""Raise an error if value is unset."""
|
||||||
if value:
|
if value:
|
||||||
setattr(args, name, value)
|
setattr(args, name, value)
|
||||||
return value
|
return value
|
||||||
@ -173,7 +154,7 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
getattr(args, 'run_dir')
|
getattr(args, 'run_dir')
|
||||||
or os.environ.get('RUN_DIR')
|
or os.environ.get('RUN_DIR')
|
||||||
or config['default'].get('run_dir')
|
or config['default'].get('run_dir')
|
||||||
or os.path.join(args.xdg_runtime_dir, 'pypodman')
|
or Path(args.xdg_runtime_dir, 'pypodman')
|
||||||
) # yapf: disable
|
) # yapf: disable
|
||||||
|
|
||||||
setattr(
|
setattr(
|
||||||
@ -185,11 +166,11 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
) # yapf:disable
|
) # yapf:disable
|
||||||
|
|
||||||
reqattr(
|
reqattr(
|
||||||
'user',
|
'username',
|
||||||
getattr(args, 'user')
|
getattr(args, 'username')
|
||||||
or os.environ.get('USER')
|
or os.environ.get('USER')
|
||||||
or os.environ.get('LOGNAME')
|
or os.environ.get('LOGNAME')
|
||||||
or config['default'].get('user')
|
or config['default'].get('username')
|
||||||
or getpass.getuser()
|
or getpass.getuser()
|
||||||
) # yapf:disable
|
) # yapf:disable
|
||||||
|
|
||||||
@ -215,22 +196,21 @@ class PodmanArgumentParser(argparse.ArgumentParser):
|
|||||||
getattr(args, 'identity_file')
|
getattr(args, 'identity_file')
|
||||||
or os.environ.get('IDENTITY_FILE')
|
or os.environ.get('IDENTITY_FILE')
|
||||||
or config['default'].get('identity_file')
|
or config['default'].get('identity_file')
|
||||||
or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.user))
|
or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.username))
|
||||||
) # yapf:disable
|
) # yapf:disable
|
||||||
|
|
||||||
if not os.path.isfile(args.identity_file):
|
if not os.path.isfile(args.identity_file):
|
||||||
args.identity_file = None
|
args.identity_file = None
|
||||||
|
|
||||||
if args.host:
|
if args.host:
|
||||||
args.local_socket_path = os.path.join(args.run_dir,
|
args.local_socket_path = Path(args.run_dir, 'podman.socket')
|
||||||
"podman.socket")
|
|
||||||
else:
|
else:
|
||||||
args.local_socket_path = args.remote_socket_path
|
args.local_socket_path = args.remote_socket_path
|
||||||
|
|
||||||
args.local_uri = "unix:{}".format(args.local_socket_path)
|
args.local_uri = 'unix:{}'.format(args.local_socket_path)
|
||||||
|
|
||||||
if args.host:
|
if args.host:
|
||||||
components = ['ssh://', args.user, '@', args.host]
|
components = ['ssh://', args.username, '@', args.host]
|
||||||
if args.port:
|
if args.port:
|
||||||
components.extend((':', str(args.port)))
|
components.extend((':', str(args.port)))
|
||||||
components.append(args.remote_socket_path)
|
components.append(args.remote_socket_path)
|
@ -53,7 +53,7 @@ class Report():
|
|||||||
fmt = []
|
fmt = []
|
||||||
|
|
||||||
for key in keys:
|
for key in keys:
|
||||||
slice_ = [i.get(key, '') for i in iterable]
|
slice_ = [str(i.get(key, '')) for i in iterable]
|
||||||
data_len = len(max(slice_, key=len))
|
data_len = len(max(slice_, key=len))
|
||||||
|
|
||||||
info = self._columns.get(key,
|
info = self._columns.get(key,
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
humanize
|
humanize
|
||||||
podman
|
podman
|
||||||
pytoml
|
pytoml
|
||||||
|
PyYAML
|
||||||
setuptools>=39
|
setuptools>=39
|
||||||
|
Reference in New Issue
Block a user