From 9a18681ba62d1a297809c243607a7b3763131c36 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 16 Jul 2018 17:29:50 -0700 Subject: [WIP] Refactor and simplify python builds * pypodman namespaced in site-packages * version numbers pulled from requirements.txt * add python-podman spec file to install eggs Signed-off-by: Jhon Honce Closes: #1106 Approved by: rhatdan --- Makefile | 22 ++- contrib/python/podman/Makefile | 16 +- contrib/python/podman/README.md | 7 +- contrib/python/podman/podman/__init__.py | 4 +- contrib/python/podman/podman/libs/errors.py | 4 +- contrib/python/podman/requirements.txt | 6 +- contrib/python/podman/setup.py | 14 +- contrib/python/pypodman/MANIFEST.in | 1 + contrib/python/pypodman/Makefile | 15 +- contrib/python/pypodman/README.md | 12 +- contrib/python/pypodman/docs/man1/pypodman.1 | 96 ++++++++++ contrib/python/pypodman/docs/pypodman.1.md | 82 -------- contrib/python/pypodman/lib/__init__.py | 11 -- contrib/python/pypodman/lib/action_base.py | 84 -------- contrib/python/pypodman/lib/actions/__init__.py | 7 - .../python/pypodman/lib/actions/images_action.py | 88 --------- contrib/python/pypodman/lib/actions/ps_action.py | 76 -------- contrib/python/pypodman/lib/actions/rm_action.py | 51 ----- contrib/python/pypodman/lib/actions/rmi_action.py | 50 ----- contrib/python/pypodman/lib/config.py | 212 --------------------- contrib/python/pypodman/lib/future_abstract.py | 29 --- contrib/python/pypodman/lib/pypodman.py | 76 -------- contrib/python/pypodman/lib/report.py | 67 ------- contrib/python/pypodman/pypodman/__init__.py | 0 contrib/python/pypodman/pypodman/lib/__init__.py | 11 ++ .../python/pypodman/pypodman/lib/action_base.py | 84 ++++++++ .../pypodman/pypodman/lib/actions/__init__.py | 7 + .../pypodman/pypodman/lib/actions/images_action.py | 88 +++++++++ .../pypodman/pypodman/lib/actions/ps_action.py | 76 ++++++++ .../pypodman/pypodman/lib/actions/rm_action.py | 51 +++++ .../pypodman/pypodman/lib/actions/rmi_action.py | 50 +++++ contrib/python/pypodman/pypodman/lib/config.py | 211 ++++++++++++++++++++ .../pypodman/pypodman/lib/future_abstract.py | 20 ++ contrib/python/pypodman/pypodman/lib/report.py | 67 +++++++ contrib/python/pypodman/pypodman/main.py | 73 +++++++ .../python/pypodman/pypodman/test/test_report.py | 23 +++ contrib/python/pypodman/requirements.txt | 2 +- contrib/python/pypodman/setup.py | 7 +- contrib/python/pypodman/test/test_report.py | 23 --- contrib/spec/podman.spec.in | 33 +--- contrib/spec/python-podman.spec.in | 104 ++++++++++ 41 files changed, 1032 insertions(+), 928 deletions(-) create mode 100644 contrib/python/pypodman/docs/man1/pypodman.1 delete mode 100644 contrib/python/pypodman/docs/pypodman.1.md delete mode 100644 contrib/python/pypodman/lib/__init__.py delete mode 100644 contrib/python/pypodman/lib/action_base.py delete mode 100644 contrib/python/pypodman/lib/actions/__init__.py delete mode 100644 contrib/python/pypodman/lib/actions/images_action.py delete mode 100644 contrib/python/pypodman/lib/actions/ps_action.py delete mode 100644 contrib/python/pypodman/lib/actions/rm_action.py delete mode 100644 contrib/python/pypodman/lib/actions/rmi_action.py delete mode 100644 contrib/python/pypodman/lib/config.py delete mode 100644 contrib/python/pypodman/lib/future_abstract.py delete mode 100755 contrib/python/pypodman/lib/pypodman.py delete mode 100644 contrib/python/pypodman/lib/report.py create mode 100644 contrib/python/pypodman/pypodman/__init__.py create mode 100644 contrib/python/pypodman/pypodman/lib/__init__.py create mode 100644 contrib/python/pypodman/pypodman/lib/action_base.py create mode 100644 contrib/python/pypodman/pypodman/lib/actions/__init__.py create mode 100644 contrib/python/pypodman/pypodman/lib/actions/images_action.py create mode 100644 contrib/python/pypodman/pypodman/lib/actions/ps_action.py create mode 100644 contrib/python/pypodman/pypodman/lib/actions/rm_action.py create mode 100644 contrib/python/pypodman/pypodman/lib/actions/rmi_action.py create mode 100644 contrib/python/pypodman/pypodman/lib/config.py create mode 100644 contrib/python/pypodman/pypodman/lib/future_abstract.py create mode 100644 contrib/python/pypodman/pypodman/lib/report.py create mode 100755 contrib/python/pypodman/pypodman/main.py create mode 100644 contrib/python/pypodman/pypodman/test/test_report.py delete mode 100644 contrib/python/pypodman/test/test_report.py create mode 100644 contrib/spec/python-podman.spec.in diff --git a/Makefile b/Makefile index ba679a303..cfe923e06 100644 --- a/Makefile +++ b/Makefile @@ -87,6 +87,9 @@ endif lint: .gopathok varlink_generate @echo "checking lint" @./.tool/lint + # Not ready + # @$(MAKE) -C contrib/python/podman lint + # @$(MAKE) -C contrib/python/pypodman lint gofmt: find . -name '*.go' ! -path './vendor/*' -exec gofmt -s -w {} \+ @@ -112,13 +115,9 @@ bin/podman.cross.%: .gopathok GOARCH="$${TARGET##*.}" \ $(GO) build -i -ldflags '$(LDFLAGS_PODMAN)' -tags '$(BUILDTAGS_CROSS)' -o "$@" $(PROJECT)/cmd/podman -python-podman: +python: ifdef HAS_PYTHON3 $(MAKE) -C contrib/python/podman python-podman -endif - -python-pypodman: -ifdef HAS_PYTHON3 $(MAKE) -C contrib/python/pypodman python-pypodman endif @@ -181,7 +180,7 @@ clientintegration: vagrant-check: BOX=$(BOX) sh ./vagrant.sh -binaries: varlink_generate podman python-podman python-pypodman +binaries: varlink_generate podman python test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp @@ -206,7 +205,7 @@ changelog: $(shell cat $(TMPFILE) >> changelog.txt) $(shell rm $(TMPFILE)) -install: .gopathok install.bin install.man install.cni install.systemd +install: .gopathok install.bin install.man install.cni install.systemd install.python install.bin: install ${SELINUXOPT} -D -m 755 bin/podman $(BINDIR)/podman @@ -242,6 +241,10 @@ install.systemd: install ${SELINUXOPT} -m 644 -D contrib/varlink/io.projectatomic.podman.service ${SYSTEMDDIR}/io.projectatomic.podman.service install ${SELINUXOPT} -m 644 -D contrib/varlink/podman.conf ${TMPFILESDIR}/podman.conf +install.python: + $(MAKE) -C contrib/python/podman install + $(MAKE) -C contrib/python/pypodman install + uninstall: for i in $(filter %.1,$(MANPAGES)); do \ rm -f $(MANDIR)/man1/$$(basename $${i}); \ @@ -249,6 +252,8 @@ uninstall: for i in $(filter %.5,$(MANPAGES)); do \ rm -f $(MANDIR)/man5/$$(basename $${i}); \ done + $(MAKE) -C contrib/python/pypodman uninstall + $(MAKE) -C contrib/python/podman uninstall .PHONY: .gitvalidation .gitvalidation: .gopathok @@ -319,6 +324,5 @@ validate: gofmt .gitvalidation changelog \ validate \ install.libseccomp.sudo \ - python-podman \ - python-pypodman \ + python \ clientintegration diff --git a/contrib/python/podman/Makefile b/contrib/python/podman/Makefile index ea40cccac..0a0804566 100644 --- a/contrib/python/podman/Makefile +++ b/contrib/python/podman/Makefile @@ -2,7 +2,11 @@ PYTHON ?= /usr/bin/python3 .PHONY: python-podman python-podman: - $(PYTHON) setup.py bdist + $(PYTHON) setup.py sdist bdist + +.PHONY: lint +lint: + $(PYTHON) -m pylint podman .PHONY: integration integration: @@ -10,12 +14,18 @@ integration: .PHONY: install install: - $(PYTHON) setup.py install --user + $(PYTHON) setup.py install + +.PHONY: clobber +clobber: uninstall clean + +.PHONY: uninstall +uninstall: + $(PYTHON) -m pip uninstall --yes podman ||: .PHONY: clean clean: $(PYTHON) setup.py clean --all - pip3 uninstall podman ||: rm -rf podman.egg-info dist find . -depth -name __pycache__ -exec rm -rf {} \; find . -depth -name \*.pyc -exec rm -f {} \; diff --git a/contrib/python/podman/README.md b/contrib/python/podman/README.md index fad03fd27..ec4a0480b 100644 --- a/contrib/python/podman/README.md +++ b/contrib/python/podman/README.md @@ -6,11 +6,12 @@ See [libpod](https://github.com/projectatomic/libpod) ## Releases -To build the podman egg: +To build the podman egg and install as user: ```sh -cd ~/libpod/contrib/python -python3 setup.py clean -a && python3 setup.py bdist +cd ~/libpod/contrib/python/podman +python3 setup.py clean -a && python3 setup.py sdist bdist +python3 setup.py install --user ``` ## Code snippets/examples: diff --git a/contrib/python/podman/podman/__init__.py b/contrib/python/podman/podman/__init__.py index 5a0356311..ec4775178 100644 --- a/contrib/python/podman/podman/__init__.py +++ b/contrib/python/podman/podman/__init__.py @@ -4,7 +4,7 @@ import pkg_resources from .client import Client from .libs import datetime_format, datetime_parse from .libs.errors import (ContainerNotFound, ErrorOccurred, ImageNotFound, - RuntimeError) + PodmanError) try: __version__ = pkg_resources.get_distribution('podman').version @@ -18,5 +18,5 @@ __all__ = [ 'datetime_parse', 'ErrorOccurred', 'ImageNotFound', - 'RuntimeError', + 'PodmanError', ] diff --git a/contrib/python/podman/podman/libs/errors.py b/contrib/python/podman/podman/libs/errors.py index b98210481..3d2300e39 100644 --- a/contrib/python/podman/podman/libs/errors.py +++ b/contrib/python/podman/podman/libs/errors.py @@ -43,7 +43,7 @@ class ErrorOccurred(VarlinkErrorProxy): pass -class RuntimeError(VarlinkErrorProxy): +class PodmanError(VarlinkErrorProxy): """Raised when Client fails to connect to runtime.""" pass @@ -53,7 +53,7 @@ error_map = { 'io.projectatomic.podman.ContainerNotFound': ContainerNotFound, 'io.projectatomic.podman.ErrorOccurred': ErrorOccurred, 'io.projectatomic.podman.ImageNotFound': ImageNotFound, - 'io.projectatomic.podman.RuntimeError': RuntimeError, + 'io.projectatomic.podman.RuntimeError': PodmanError, } diff --git a/contrib/python/podman/requirements.txt b/contrib/python/podman/requirements.txt index d294af3c7..5a936e7d5 100644 --- a/contrib/python/podman/requirements.txt +++ b/contrib/python/podman/requirements.txt @@ -1,3 +1,3 @@ -varlink>=26.1.0 -setuptools>=39.2.0 -python-dateutil>=2.7.3 +python-dateutil +setuptools>=39 +varlink diff --git a/contrib/python/podman/setup.py b/contrib/python/podman/setup.py index c9db30199..a342c05fd 100644 --- a/contrib/python/podman/setup.py +++ b/contrib/python/podman/setup.py @@ -15,24 +15,22 @@ with open(os.path.join(root, 'requirements.txt')) as r: setup( name='podman', version=os.environ.get('PODMAN_VERSION', '0.0.0'), - description='A client for communicating with a Podman server', - long_description=readme, + description='A library for communicating with a Podman server', author='Jhon Honce', author_email='jhonce@redhat.com', - url='http://github.com/projectatomic/libpod', license='Apache Software License', - python_requires='>=3', + long_description=readme, include_package_data=True, install_requires=requirements, packages=find_packages(exclude=['test']), + python_requires='>=3', zip_safe=True, + url='http://github.com/projectatomic/libpod', keywords='varlink libpod podman', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', - 'Topic :: Software Development', 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.4', + 'Topic :: Software Development', ]) -# Not supported -# long_description_content_type='text/markdown', diff --git a/contrib/python/pypodman/MANIFEST.in b/contrib/python/pypodman/MANIFEST.in index bb3ec5f0d..72e638cb9 100644 --- a/contrib/python/pypodman/MANIFEST.in +++ b/contrib/python/pypodman/MANIFEST.in @@ -1 +1,2 @@ +prune test/ include README.md diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile index 4d76b1a1e..6bc5b968b 100644 --- a/contrib/python/pypodman/Makefile +++ b/contrib/python/pypodman/Makefile @@ -2,7 +2,11 @@ PYTHON ?= /usr/bin/python3 .PHONY: python-pypodman python-pypodman: - $(PYTHON) setup.py bdist + $(PYTHON) setup.py sdist bdist + +.PHONY: lint +lint: + $(PYTHON) -m pylint pypodman .PHONY: integration integration: @@ -10,12 +14,17 @@ integration: .PHONY: install install: - $(PYTHON) setup.py install --user + $(PYTHON) setup.py install + +.PHONY: clobber +clobber: uninstall clean + +.PHONY: uninstall + $(PYTHON) -m pip uninstall --yes pypodman ||: .PHONY: clean clean: $(PYTHON) setup.py clean --all - pip3 uninstall pypodman ||: rm -rf pypodman.egg-info dist find . -depth -name __pycache__ -exec rm -rf {} \; find . -depth -name \*.pyc -exec rm -f {} \; diff --git a/contrib/python/pypodman/README.md b/contrib/python/pypodman/README.md index 8a1c293f1..935f6a631 100644 --- a/contrib/python/pypodman/README.md +++ b/contrib/python/pypodman/README.md @@ -1,17 +1,19 @@ -# pypodman - CLI interface for podman written in python +# pypodman - CLI for podman written in python ## Status: Active Development -See [libpod](https://github.com/projectatomic/libpod/contrib/python/cmd) +See [libpod](https://github.com/projectatomic/libpod/contrib/python/pypodman) ## Releases -To build the pypodman egg: +To build the pypodman egg and install as user: ```sh -cd ~/libpod/contrib/python/cmd -python3 setup.py clean -a && python3 setup.py bdist +cd ~/libpod/contrib/python/pypodman +python3 setup.py clean -a && python3 setup.py sdist bdist +python3 setup.py install --user ``` +Add `~/.local/bin` to your `PATH` to run pypodman command. ## Running command: diff --git a/contrib/python/pypodman/docs/man1/pypodman.1 b/contrib/python/pypodman/docs/man1/pypodman.1 new file mode 100644 index 000000000..50d88f84d --- /dev/null +++ b/contrib/python/pypodman/docs/man1/pypodman.1 @@ -0,0 +1,96 @@ +.TH pypodman 1 2018-07-20 0.7.3 +.SH NAME +pypodman \- CLI management tool for containers and images +.SH SYNOPSIS +\f[B]pypodman\f[] [\f[I]global options\f[]] \f[I]command\f[] [\f[I]options\f[]] +.SH DESCRIPTION +pypodman is a simple client only tool to help with debugging issues when daemons +such as CRI runtime and the kubelet are not responding or failing. +.P +pypodman uses a VarLink API to commicate with a podman service running on either +the local or remote machine. pypodman uses ssh to create secure tunnels when +communicating with a remote service. +.SH GLOBAL OPTIONS +.PP +\f[B]\[en]help, \-h\f[] +.PP +Print usage statement. +.PP +\f[B]\[en]version\f[] +.PP +Print program version number and exit. +.PP +\f[B]\[en]config\-home\f[] +.PP +Directory that will be namespaced with \f[C]pypodman\f[] to hold +\f[C]pypodman.conf\f[]. +See FILES below for more details. +.PP +\f[B]\[en]log\-level\f[] +.PP +Log events above specified level: DEBUG, INFO, WARNING (default), ERROR, +or CRITICAL. +.PP +\f[B]\[en]run\-dir\f[] +.PP +Directory that will be namespaced with \f[C]pypodman\f[] to hold local socket +bindings. The default is `\f[C]$XDG_RUNTIME_DIR\\\f[]. +.PP +\f[B]\[en]user\f[] +.PP +Authenicating user on remote host. \f[C]pypodman\f[] defaults to the logged in +user. +.PP +\f[B]\[en]host\f[] +.PP +Name of remote host. There is no default, if not given \f[C]pypodman\f[] +attempts to connect to \f[C]\-\-remote\-socket\-path\f[] on local host. +.PP +\f[B]\[en]remote\-socket\-path\f[] +.PP +Path on remote host for podman service's \f[C]AF_UNIX\f[] socket. The default is +\f[C]/run/podman/io.projectatomic.podman\f[]. +.PP +\f[B]\[en]identity\-file\f[] +.PP +The optional \f[C]ssh\f[] identity file to authenicate when tunnelling to remote +host. Default is None and will allow \f[C]ssh\f[] to follow it's default methods +for resolving the identity and private key using the logged in user. +.SH COMMANDS +.PP +See podman(1) (podman.1.md) +.SH FILES +.PP +\f[B]pypodman/pypodman.conf\f[] +(\f[C]Any\ element\ of\ XDG_CONFIG_DIRS\f[] and/or +\f[C]XDG_CONFIG_HOME\f[] and/or \f[B]\[en]config\-home\f[]) +.PP +pypodman.conf is one or more configuration files for running the pypodman +command. pypodman.conf is a TOML file with the stanza \f[C][default]\f[], with a +map of \f[C]option: value\f[]. +.PP +pypodman follows the XDG (freedesktop.org) conventions for resolving it's +configuration. The list below are read from top to bottom with later items +overwriting earlier. Any missing items are ignored. +.IP \[bu] 2 +\f[C]pypodman/pypodman.conf\f[] from any path element in +\f[C]XDG_CONFIG_DIRS\f[] or \f[C]\\etc\\xdg\f[] +.IP \[bu] 2 +\f[C]XDG_CONFIG_HOME\f[] or $HOME/.config + \f[C]pypodman/pypodman.conf\f[] +.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 +.IP \[bu] 2 +From command line option, for example: \[en]run\-dir +.PP +This should provide Operators the ability to setup basic configurations +and allow users to customize them. +.PP +\f[B]XDG_RUNTIME_DIR\f[] (\f[C]XDG_RUNTIME_DIR/io.projectatomic.podman\f[]) +.PP +Directory where pypodman stores non\-essential runtime files and other file +objects (such as sockets, named pipes, \&...). +.SH SEE ALSO +.PP +\f[C]podman(1)\f[], \f[C]libpod(8)\f[] diff --git a/contrib/python/pypodman/docs/pypodman.1.md b/contrib/python/pypodman/docs/pypodman.1.md deleted file mode 100644 index 1a6be994d..000000000 --- a/contrib/python/pypodman/docs/pypodman.1.md +++ /dev/null @@ -1,82 +0,0 @@ -% pypodman "1" - -## NAME - -pypodman - Simple management tool for containers and images - -## SYNOPSIS - -**pypodman** [*global options*] _command_ [*options*] - -## DESCRIPTION - -pypodman is a simple client only tool to help with debugging issues when daemons -such as CRI runtime and the kubelet are not responding or failing. pypodman uses -a VarLink API to commicate with a podman service running on either the local or -remote machine. pypodman uses ssh to create secure tunnels when communicating -with a remote service. - -## GLOBAL OPTIONS - -**--help, -h** - -Print usage statement. - -**--version** - -Print program version number and exit. - -**--config-home** - -Directory that will be namespaced with `pypodman` to hold `pypodman.conf`. See FILES below for more details. - -**--log-level** - -Log events above specified level: DEBUG, INFO, WARNING (default), ERROR, or CRITICAL. - -**--run-dir** - -Directory that will be namespaced with `pypodman` to hold local socket bindings. The default is ``$XDG_RUNTIME_DIR\`. - -**--user** - -Authenicating user on remote host. `pypodman` defaults to the logged in user. - -**--host** - -Name of remote host. There is no default, if not given `pypodman` attempts to connect to `--remote-socket-path` on local host. - -**--remote-socket-path** - -Path on remote host for podman service's `AF_UNIX` socket. The default is `/run/podman/io.projectatomic.podman`. - -**--identity-file** - -The optional `ssh` identity file to authenicate when tunnelling to remote host. Default is None and will allow `ssh` to follow it's default methods for resolving the identity and private key using the logged in user. - -## COMMANDS - -See [podman(1)](podman.1.md) - -## FILES - -**pypodman/pypodman.conf** (`Any element of XDG_CONFIG_DIRS` and/or `XDG_CONFIG_HOME` and/or **--config-home**) - -pypodman.conf is one or more configuration files for running the pypodman command. pypodman.conf is a TOML file with the stanza `[default]`, with a map of option: value. - -pypodman follows the XDG (freedesktop.org) conventions for resolving it's configuration. The list below are read from top to bottom with later items overwriting earlier. Any missing items are ignored. - -- `pypodman/pypodman.conf` from any path element in `XDG_CONFIG_DIRS` or `\etc\xdg` -- `XDG_CONFIG_HOME` or $HOME/.config + `pypodman/pypodman.conf` -- From `--config-home` command line option + `pypodman/pypodman.conf` -- From environment variable, for example: RUN_DIR -- From command line option, for example: --run-dir - -This should provide Operators the ability to setup basic configurations and allow users to customize them. - -**XDG_RUNTIME_DIR** (`XDG_RUNTIME_DIR/io.projectatomic.podman`) - -Directory where pypodman stores non-essential runtime files and other file objects (such as sockets, named pipes, ...). - -## SEE ALSO -`podman(1)`, `libpod(8)` diff --git a/contrib/python/pypodman/lib/__init__.py b/contrib/python/pypodman/lib/__init__.py deleted file mode 100644 index 5a8303668..000000000 --- a/contrib/python/pypodman/lib/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Remote podman client support library.""" -from .action_base import AbstractActionBase -from .config import PodmanArgumentParser -from .report import Report, ReportColumn - -__all__ = [ - 'AbstractActionBase', - 'PodmanArgumentParser', - 'Report', - 'ReportColumn', -] diff --git a/contrib/python/pypodman/lib/action_base.py b/contrib/python/pypodman/lib/action_base.py deleted file mode 100644 index ff2922262..000000000 --- a/contrib/python/pypodman/lib/action_base.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Base class for all actions of remote client.""" -import abc -from functools import lru_cache - -import podman - - -class AbstractActionBase(abc.ABC): - """Base class for all actions of remote client.""" - - @classmethod - @abc.abstractmethod - def subparser(cls, parser): - """Define parser for this action. Subclasses must implement. - - API: - Use set_defaults() to set attributes "class_" and "method". These will - be invoked as class_(parsed_args).method() - """ - parser.add_argument( - '--all', - action='store_true', - help=('list all items.' - ' (default: no-op, included for compatibility.)')) - parser.add_argument( - '--no-trunc', - '--notruncate', - action='store_false', - dest='truncate', - default=True, - help='Display extended information. (default: False)') - parser.add_argument( - '--noheading', - action='store_false', - dest='heading', - default=True, - help=('Omit the table headings from the output.' - ' (default: False)')) - parser.add_argument( - '--quiet', - action='store_true', - help='List only the IDs. (default: %(default)s)') - - def __init__(self, args): - """Construct class.""" - self._args = args - - @property - def remote_uri(self): - """URI for remote side of connection.""" - return self._args.remote_uri - - @property - def local_uri(self): - """URI for local side of connection.""" - return self._args.local_uri - - @property - def identity_file(self): - """Key for authenication.""" - return self._args.identity_file - - @property - @lru_cache(maxsize=1) - def client(self): - """Podman remote client for communicating.""" - if self._args.host is None: - return podman.Client( - uri=self.local_uri) - else: - return podman.Client( - uri=self.local_uri, - remote_uri=self.remote_uri, - identity_file=self.identity_file) - - def __repr__(self): - """Compute the “official” string representation of object.""" - return ("{}(local_uri='{}', remote_uri='{}'," - " identity_file='{}')").format( - self.__class__, - self.local_uri, - self.remote_uri, - self.identity_file, - ) diff --git a/contrib/python/pypodman/lib/actions/__init__.py b/contrib/python/pypodman/lib/actions/__init__.py deleted file mode 100644 index cdc58b6ab..000000000 --- a/contrib/python/pypodman/lib/actions/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Module to export all the podman subcommands.""" -from .images_action import Images -from .ps_action import Ps -from .rm_action import Rm -from .rmi_action import Rmi - -__all__ = ['Images', 'Ps', 'Rm', 'Rmi'] diff --git a/contrib/python/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/lib/actions/images_action.py deleted file mode 100644 index f6a7497e5..000000000 --- a/contrib/python/pypodman/lib/actions/images_action.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Remote client commands dealing with images.""" -import operator -from collections import OrderedDict - -import humanize -import podman - -from .. import AbstractActionBase, Report, ReportColumn - - -class Images(AbstractActionBase): - """Class for Image manipulation.""" - - @classmethod - def subparser(cls, parent): - """Add Images commands to parent parser.""" - parser = parent.add_parser('images', help='list images') - super().subparser(parser) - parser.add_argument( - '--sort', - choices=['created', 'id', 'repository', 'size', 'tag'], - default='created', - type=str.lower, - help=('Change sort ordered of displayed images.' - ' (default: %(default)s)')) - - group = parser.add_mutually_exclusive_group() - group.add_argument( - '--digests', - action='store_true', - help='Include digests with images. (default: %(default)s)') - parser.set_defaults(class_=cls, method='list') - - def __init__(self, args): - """Construct Images class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'name': - ReportColumn('name', 'REPOSITORY', 40), - 'tag': - ReportColumn('tag', 'TAG', 10), - 'id': - ReportColumn('id', 'IMAGE ID', 12), - 'created': - ReportColumn('created', 'CREATED', 12), - 'size': - ReportColumn('size', 'SIZE', 8), - 'repoDigests': - ReportColumn('repoDigests', 'DIGESTS', 35), - }) - - def list(self): - """List images.""" - images = sorted( - self.client.images.list(), - key=operator.attrgetter(self._args.sort)) - if len(images) == 0: - return 0 - - rows = list() - for image in images: - fields = dict(image) - fields.update({ - 'created': - humanize.naturaldate(podman.datetime_parse(image.created)), - 'size': - humanize.naturalsize(int(image.size)), - 'repoDigests': - ' '.join(image.repoDigests), - }) - - for r in image.repoTags: - name, tag = r.split(':', 1) - fields.update({ - 'name': name, - 'tag': tag, - }) - rows.append(fields) - - if not self._args.digests: - del self.columns['repoDigests'] - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/lib/actions/ps_action.py b/contrib/python/pypodman/lib/actions/ps_action.py deleted file mode 100644 index 4bbec5578..000000000 --- a/contrib/python/pypodman/lib/actions/ps_action.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Remote client commands dealing with containers.""" -import operator -from collections import OrderedDict - -import humanize -import podman - -from .. import AbstractActionBase, Report, ReportColumn - - -class Ps(AbstractActionBase): - """Class for Container manipulation.""" - - @classmethod - def subparser(cls, parent): - """Add Images command to parent parser.""" - parser = parent.add_parser('ps', help='list containers') - super().subparser(parser) - parser.add_argument( - '--sort', - choices=[ - 'createdat', 'id', 'image', 'names', 'runningfor', 'size', - 'status' - ], - default='createdat', - type=str.lower, - help=('Change sort ordered of displayed containers.' - ' (default: %(default)s)')) - parser.set_defaults(class_=cls, method='list') - - def __init__(self, args): - """Construct Ps class.""" - super().__init__(args) - - self.columns = OrderedDict({ - 'id': - ReportColumn('id', 'CONTAINER ID', 14), - 'image': - ReportColumn('image', 'IMAGE', 30), - 'command': - ReportColumn('column', 'COMMAND', 20), - 'createdat': - ReportColumn('createdat', 'CREATED', 12), - 'status': - ReportColumn('status', 'STATUS', 10), - 'ports': - ReportColumn('ports', 'PORTS', 28), - 'names': - ReportColumn('names', 'NAMES', 18) - }) - - def list(self): - """List containers.""" - # TODO: Verify sorting on dates and size - ctnrs = sorted( - self.client.containers.list(), - key=operator.attrgetter(self._args.sort)) - if len(ctnrs) == 0: - return 0 - - rows = list() - for ctnr in ctnrs: - fields = dict(ctnr) - fields.update({ - 'command': - ' '.join(ctnr.command), - 'createdat': - humanize.naturaldate(podman.datetime_parse(ctnr.createdat)), - }) - rows.append(fields) - - with Report(self.columns, heading=self._args.heading) as report: - report.layout( - rows, self.columns.keys(), truncate=self._args.truncate) - for row in rows: - report.row(**row) diff --git a/contrib/python/pypodman/lib/actions/rm_action.py b/contrib/python/pypodman/lib/actions/rm_action.py deleted file mode 100644 index bd8950bd6..000000000 --- a/contrib/python/pypodman/lib/actions/rm_action.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Remote client command for deleting containers.""" -import sys - -import podman - -from .. import AbstractActionBase - - -class Rm(AbstractActionBase): - """Class for removing containers from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Rm command to parent parser.""" - parser = parent.add_parser('rm', help='delete container(s)') - parser.add_argument( - '-f', - '--force', - action='store_true', - help=('force delete of running container(s).' - ' (default: %(default)s)')) - parser.add_argument( - 'targets', nargs='*', help='container id(s) to delete') - parser.set_defaults(class_=cls, method='remove') - - def __init__(self, args): - """Construct Rm class.""" - super().__init__(args) - if len(args.targets) < 1: - raise ValueError('You must supply at least one container id' - ' or name to be deleted.') - - def remove(self): - """Remove container(s).""" - for id in self._args.targets: - try: - ctnr = self.client.containers.get(id) - ctnr.remove(self._args.force) - print(id) - except podman.ContainerNotFound as e: - sys.stdout.flush() - print( - 'Container {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/lib/actions/rmi_action.py b/contrib/python/pypodman/lib/actions/rmi_action.py deleted file mode 100644 index 91f0deeaf..000000000 --- a/contrib/python/pypodman/lib/actions/rmi_action.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Remote client command for deleting images.""" -import sys - -import podman - -from .. import AbstractActionBase - - -class Rmi(AbstractActionBase): - """Clas for removing images from storage.""" - - @classmethod - def subparser(cls, parent): - """Add Rmi command to parent parser.""" - parser = parent.add_parser('rmi', help='delete image(s)') - parser.add_argument( - '-f', - '--force', - action='store_true', - help=('force delete of image(s) and associated containers.' - ' (default: %(default)s)')) - parser.add_argument('targets', nargs='*', help='image id(s) to delete') - parser.set_defaults(class_=cls, method='remove') - - def __init__(self, args): - """Construct Rmi class.""" - super().__init__(args) - if len(args.targets) < 1: - raise ValueError('You must supply at least one image id' - ' or name to be deleted.') - - def remove(self): - """Remove image(s).""" - for id in self._args.targets: - try: - img = self.client.images.get(id) - img.remove(self._args.force) - print(id) - except podman.ImageNotFound as e: - sys.stdout.flush() - print( - 'Image {} not found.'.format(e.name), - file=sys.stderr, - flush=True) - except podman.ErrorOccurred as e: - sys.stdout.flush() - print( - '{}'.format(e.reason).capitalize(), - file=sys.stderr, - flush=True) diff --git a/contrib/python/pypodman/lib/config.py b/contrib/python/pypodman/lib/config.py deleted file mode 100644 index e687697ef..000000000 --- a/contrib/python/pypodman/lib/config.py +++ /dev/null @@ -1,212 +0,0 @@ -import argparse -import curses -import getpass -import inspect -import logging -import os -import sys - -import pkg_resources - -import pytoml - -# TODO: setup.py and obtain __version__ from rpm.spec -try: - __version__ = pkg_resources.get_distribution('pypodman').version -except Exception: - __version__ = '0.0.0' - - -class HelpFormatter(argparse.RawDescriptionHelpFormatter): - """Set help width to screen size.""" - - def __init__(self, *args, **kwargs): - """Construct HelpFormatter using screen width.""" - if 'width' not in kwargs: - kwargs['width'] = 80 - try: - height, width = curses.initscr().getmaxyx() - kwargs['width'] = width - finally: - curses.endwin() - super().__init__(*args, **kwargs) - - -class PodmanArgumentParser(argparse.ArgumentParser): - """Default remote podman configuration.""" - - def __init__(self, **kwargs): - """Construct the parser.""" - kwargs['add_help'] = True - kwargs['allow_abbrev'] = True - kwargs['description'] = __doc__ - kwargs['formatter_class'] = HelpFormatter - - super().__init__(**kwargs) - - def initialize_parser(self): - """Initialize parser without causing recursion meltdown.""" - self.add_argument( - '--version', - action='version', - version='%(prog)s v. ' + __version__) - self.add_argument( - '--log-level', - choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - default='WARNING', - type=str.upper, - help='set logging level for events. (default: %(default)s)', - ) - self.add_argument( - '--run-dir', - metavar='DIRECTORY', - help=('directory to place local socket bindings.' - ' (default: XDG_RUNTIME_DIR/pypodman')) - self.add_argument( - '--user', - default=getpass.getuser(), - help='Authenicating user on remote host. (default: %(default)s)') - self.add_argument( - '--host', help='name of remote host. (default: None)') - self.add_argument( - '--remote-socket-path', - metavar='PATH', - help=('path of podman socket on remote host' - ' (default: /run/podman/io.projectatomic.podman)')) - self.add_argument( - '--identity-file', - metavar='PATH', - help=('path to ssh identity file. (default: ~user/.ssh/id_dsa)')) - self.add_argument( - '--config-home', - metavar='DIRECTORY', - help=('home of configuration "pypodman.conf".' - ' (default: XDG_CONFIG_HOME/pypodman')) - - actions_parser = self.add_subparsers( - dest='subparser_name', help='actions') - - # pull in plugin(s) code for each subcommand - for name, obj in inspect.getmembers( - sys.modules['lib.actions'], - lambda member: inspect.isclass(member)): - if hasattr(obj, 'subparser'): - try: - obj.subparser(actions_parser) - except NameError as e: - logging.critical(e) - logging.warning( - 'See subparser configuration for Class "{}"'.format( - name)) - sys.exit(3) - - def parse_args(self, args=None, namespace=None): - """Parse command line arguments, backed by env var and config_file.""" - self.initialize_parser() - cooked = super().parse_args(args, namespace) - return self.resolve_configuration(cooked) - - def resolve_configuration(self, args): - """Find and fill in any arguments not passed on command line.""" - args.xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp') - args.xdg_config_home = os.environ.get('XDG_CONFIG_HOME', - os.path.expanduser('~/.config')) - args.xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg') - - # Configuration file(s) are optional, - # required arguments may be provided elsewhere - config = {'default': {}} - dirs = args.xdg_config_dirs.split(':') - dirs.extend((args.xdg_config_home, args.config_home)) - for dir_ in dirs: - if dir_ is None: - continue - try: - with open(os.path.join(dir_, 'pypodman/pypodman.conf'), - 'r') as stream: - config.update(pytoml.load(stream)) - except OSError: - pass - - def reqattr(name, value): - if value: - setattr(args, name, value) - return value - self.error('Required argument "%s" is not configured.' % name) - - reqattr( - 'run_dir', - getattr(args, 'run_dir') - or os.environ.get('RUN_DIR') - or config['default'].get('run_dir') - or os.path.join(args.xdg_runtime_dir, 'pypodman') - ) # yapf: disable - - setattr( - args, - 'host', - getattr(args, 'host') - or os.environ.get('HOST') - or config['default'].get('host') - ) # yapf:disable - - reqattr( - 'user', - getattr(args, 'user') - or os.environ.get('USER') - or config['default'].get('user') - or getpass.getuser() - ) # yapf:disable - - reqattr( - 'remote_socket_path', - getattr(args, 'remote_socket_path') - or os.environ.get('REMOTE_SOCKET_PATH') - or config['default'].get('remote_socket_path') - or '/run/podman/io.projectatomic.podman' - ) # yapf:disable - - reqattr( - 'log_level', - getattr(args, 'log_level') - or os.environ.get('LOG_LEVEL') - or config['default'].get('log_level') - or logging.WARNING - ) # yapf:disable - - setattr( - args, - 'identity_file', - getattr(args, 'identity_file') - or os.environ.get('IDENTITY_FILE') - or config['default'].get('identity_file') - or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.user)) - ) # yapf:disable - - if not os.path.isfile(args.identity_file): - args.identity_file = None - - if args.host: - args.local_socket_path = os.path.join(args.run_dir, - "podman.socket") - else: - args.local_socket_path = args.remote_socket_path - - args.local_uri = "unix:{}".format(args.local_socket_path) - args.remote_uri = "ssh://{}@{}{}".format(args.user, args.host, - args.remote_socket_path) - return args - - def exit(self, status=0, message=None): - """Capture message and route to logger.""" - if message: - log = logging.info if status == 0 else logging.error - log(message) - super().exit(status) - - def error(self, message): - """Capture message and route to logger.""" - logging.error('{}: {}'.format(self.prog, message)) - logging.error("Try '{} --help' for more information.".format( - self.prog)) - super().exit(2) diff --git a/contrib/python/pypodman/lib/future_abstract.py b/contrib/python/pypodman/lib/future_abstract.py deleted file mode 100644 index 75a1d42db..000000000 --- a/contrib/python/pypodman/lib/future_abstract.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Utilities for with-statement contexts. See PEP 343.""" - -import abc - -import _collections_abc - -try: - from contextlib import AbstractContextManager -except ImportError: - # Copied from python3.7 library as "backport" - class AbstractContextManager(abc.ABC): - """An abstract base class for context managers.""" - - def __enter__(self): - """Return `self` upon entering the runtime context.""" - return self - - @abc.abstractmethod - def __exit__(self, exc_type, exc_value, traceback): - """Raise any exception triggered within the runtime context.""" - return None - - @classmethod - def __subclasshook__(cls, C): - """Check whether subclass is considered a subclass of this ABC.""" - if cls is AbstractContextManager: - return _collections_abc._check_methods(C, "__enter__", - "__exit__") - return NotImplemented diff --git a/contrib/python/pypodman/lib/pypodman.py b/contrib/python/pypodman/lib/pypodman.py deleted file mode 100755 index 4bc71a9cc..000000000 --- a/contrib/python/pypodman/lib/pypodman.py +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env python3 -"""Remote podman client.""" - -import logging -import os -import sys - -import lib.actions -from lib import PodmanArgumentParser - -assert lib.actions # silence pyflakes - - -def main(): - """Entry point.""" - # Setup logging so we use stderr and can change logging level later - # Do it now before there is any chance of a default setup hardcoding crap. - log = logging.getLogger() - fmt = logging.Formatter('%(asctime)s | %(levelname)-8s | %(message)s', - '%Y-%m-%d %H:%M:%S %Z') - stderr = logging.StreamHandler(stream=sys.stderr) - stderr.setFormatter(fmt) - log.addHandler(stderr) - log.setLevel(logging.WARNING) - - parser = PodmanArgumentParser() - args = parser.parse_args() - - log.setLevel(args.log_level) - logging.debug('Logging initialized at level {}'.format( - logging.getLevelName(logging.getLogger().getEffectiveLevel()))) - - def want_tb(): - """Add traceback when logging events.""" - return log.getEffectiveLevel() == logging.DEBUG - - try: - if not os.path.exists(args.run_dir): - os.makedirs(args.run_dir) - except PermissionError as e: - logging.critical(e, exc_info=want_tb()) - sys.exit(6) - - # class_(args).method() are set by the sub-command's parser - returncode = None - try: - obj = args.class_(args) - except Exception as e: - logging.critical(repr(e), exc_info=want_tb()) - logging.warning('See subparser "{}" configuration.'.format( - args.subparser_name)) - sys.exit(5) - - try: - returncode = getattr(obj, args.method)() - except AttributeError as e: - logging.critical(e, exc_info=want_tb()) - logging.warning('See subparser "{}" configuration.'.format( - args.subparser_name)) - returncode = 3 - except KeyboardInterrupt: - pass - except ( - ConnectionRefusedError, - ConnectionResetError, - TimeoutError, - ) as e: - logging.critical(e, exc_info=want_tb()) - logging.info('Review connection arguments for correctness.') - returncode = 4 - - return 0 if returncode is None else returncode - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/contrib/python/pypodman/lib/report.py b/contrib/python/pypodman/lib/report.py deleted file mode 100644 index 25fe2ae0d..000000000 --- a/contrib/python/pypodman/lib/report.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Report Manager.""" -import sys -from collections import namedtuple - -from .future_abstract import AbstractContextManager - - -class ReportColumn(namedtuple('ReportColumn', 'key display width default')): - """Hold attributes of output column.""" - - __slots__ = () - - def __new__(cls, key, display, width, default=None): - """Add defaults for attributes.""" - return super(ReportColumn, cls).__new__(cls, key, display, width, - default) - - -class Report(AbstractContextManager): - """Report Manager.""" - - def __init__(self, columns, heading=True, epilog=None, file=sys.stdout): - """Construct Report. - - columns is a mapping for named fields to column headings. - headers True prints headers on table. - epilog will be printed when the report context is closed. - """ - self._columns = columns - self._file = file - self._heading = heading - self.epilog = epilog - self._format = None - - def row(self, **fields): - """Print row for report.""" - if self._heading: - hdrs = {k: v.display for (k, v) in self._columns.items()} - print(self._format.format(**hdrs), flush=True, file=self._file) - self._heading = False - fields = {k: str(v) for k, v in fields.items()} - print(self._format.format(**fields)) - - def __exit__(self, exc_type, exc_value, traceback): - """Leave Report context and print epilog if provided.""" - if self.epilog: - print(self.epilog, flush=True, file=self._file) - - def layout(self, iterable, keys, truncate=True): - """Use data and headings build format for table to fit.""" - format = [] - - for key in keys: - value = max(map(lambda x: len(str(x.get(key, ''))), iterable)) - # print('key', key, 'value', value) - - if truncate: - row = self._columns.get( - key, ReportColumn(key, key.upper(), len(key))) - if value < row.width: - step = row.width if value == 0 else value - value = max(len(key), step) - elif value > row.width: - value = row.width if row.width != 0 else value - - format.append('{{{0}:{1}.{1}}}'.format(key, value)) - self._format = ' '.join(format) diff --git a/contrib/python/pypodman/pypodman/__init__.py b/contrib/python/pypodman/pypodman/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/python/pypodman/pypodman/lib/__init__.py b/contrib/python/pypodman/pypodman/lib/__init__.py new file mode 100644 index 000000000..5a8303668 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/__init__.py @@ -0,0 +1,11 @@ +"""Remote podman client support library.""" +from .action_base import AbstractActionBase +from .config import PodmanArgumentParser +from .report import Report, ReportColumn + +__all__ = [ + 'AbstractActionBase', + 'PodmanArgumentParser', + 'Report', + 'ReportColumn', +] diff --git a/contrib/python/pypodman/pypodman/lib/action_base.py b/contrib/python/pypodman/pypodman/lib/action_base.py new file mode 100644 index 000000000..ff2922262 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/action_base.py @@ -0,0 +1,84 @@ +"""Base class for all actions of remote client.""" +import abc +from functools import lru_cache + +import podman + + +class AbstractActionBase(abc.ABC): + """Base class for all actions of remote client.""" + + @classmethod + @abc.abstractmethod + def subparser(cls, parser): + """Define parser for this action. Subclasses must implement. + + API: + Use set_defaults() to set attributes "class_" and "method". These will + be invoked as class_(parsed_args).method() + """ + parser.add_argument( + '--all', + action='store_true', + help=('list all items.' + ' (default: no-op, included for compatibility.)')) + parser.add_argument( + '--no-trunc', + '--notruncate', + action='store_false', + dest='truncate', + default=True, + help='Display extended information. (default: False)') + parser.add_argument( + '--noheading', + action='store_false', + dest='heading', + default=True, + help=('Omit the table headings from the output.' + ' (default: False)')) + parser.add_argument( + '--quiet', + action='store_true', + help='List only the IDs. (default: %(default)s)') + + def __init__(self, args): + """Construct class.""" + self._args = args + + @property + def remote_uri(self): + """URI for remote side of connection.""" + return self._args.remote_uri + + @property + def local_uri(self): + """URI for local side of connection.""" + return self._args.local_uri + + @property + def identity_file(self): + """Key for authenication.""" + return self._args.identity_file + + @property + @lru_cache(maxsize=1) + def client(self): + """Podman remote client for communicating.""" + if self._args.host is None: + return podman.Client( + uri=self.local_uri) + else: + return podman.Client( + uri=self.local_uri, + remote_uri=self.remote_uri, + identity_file=self.identity_file) + + def __repr__(self): + """Compute the “official” string representation of object.""" + return ("{}(local_uri='{}', remote_uri='{}'," + " identity_file='{}')").format( + self.__class__, + self.local_uri, + self.remote_uri, + self.identity_file, + ) diff --git a/contrib/python/pypodman/pypodman/lib/actions/__init__.py b/contrib/python/pypodman/pypodman/lib/actions/__init__.py new file mode 100644 index 000000000..cdc58b6ab --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/__init__.py @@ -0,0 +1,7 @@ +"""Module to export all the podman subcommands.""" +from .images_action import Images +from .ps_action import Ps +from .rm_action import Rm +from .rmi_action import Rmi + +__all__ = ['Images', 'Ps', 'Rm', 'Rmi'] diff --git a/contrib/python/pypodman/pypodman/lib/actions/images_action.py b/contrib/python/pypodman/pypodman/lib/actions/images_action.py new file mode 100644 index 000000000..f6a7497e5 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/images_action.py @@ -0,0 +1,88 @@ +"""Remote client commands dealing with images.""" +import operator +from collections import OrderedDict + +import humanize +import podman + +from .. import AbstractActionBase, Report, ReportColumn + + +class Images(AbstractActionBase): + """Class for Image manipulation.""" + + @classmethod + def subparser(cls, parent): + """Add Images commands to parent parser.""" + parser = parent.add_parser('images', help='list images') + super().subparser(parser) + parser.add_argument( + '--sort', + choices=['created', 'id', 'repository', 'size', 'tag'], + default='created', + type=str.lower, + help=('Change sort ordered of displayed images.' + ' (default: %(default)s)')) + + group = parser.add_mutually_exclusive_group() + group.add_argument( + '--digests', + action='store_true', + help='Include digests with images. (default: %(default)s)') + parser.set_defaults(class_=cls, method='list') + + def __init__(self, args): + """Construct Images class.""" + super().__init__(args) + + self.columns = OrderedDict({ + 'name': + ReportColumn('name', 'REPOSITORY', 40), + 'tag': + ReportColumn('tag', 'TAG', 10), + 'id': + ReportColumn('id', 'IMAGE ID', 12), + 'created': + ReportColumn('created', 'CREATED', 12), + 'size': + ReportColumn('size', 'SIZE', 8), + 'repoDigests': + ReportColumn('repoDigests', 'DIGESTS', 35), + }) + + def list(self): + """List images.""" + images = sorted( + self.client.images.list(), + key=operator.attrgetter(self._args.sort)) + if len(images) == 0: + return 0 + + rows = list() + for image in images: + fields = dict(image) + fields.update({ + 'created': + humanize.naturaldate(podman.datetime_parse(image.created)), + 'size': + humanize.naturalsize(int(image.size)), + 'repoDigests': + ' '.join(image.repoDigests), + }) + + for r in image.repoTags: + name, tag = r.split(':', 1) + fields.update({ + 'name': name, + 'tag': tag, + }) + rows.append(fields) + + if not self._args.digests: + del self.columns['repoDigests'] + + with Report(self.columns, heading=self._args.heading) as report: + report.layout( + rows, self.columns.keys(), truncate=self._args.truncate) + for row in rows: + report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/ps_action.py b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py new file mode 100644 index 000000000..4bbec5578 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/ps_action.py @@ -0,0 +1,76 @@ +"""Remote client commands dealing with containers.""" +import operator +from collections import OrderedDict + +import humanize +import podman + +from .. import AbstractActionBase, Report, ReportColumn + + +class Ps(AbstractActionBase): + """Class for Container manipulation.""" + + @classmethod + def subparser(cls, parent): + """Add Images command to parent parser.""" + parser = parent.add_parser('ps', help='list containers') + super().subparser(parser) + parser.add_argument( + '--sort', + choices=[ + 'createdat', 'id', 'image', 'names', 'runningfor', 'size', + 'status' + ], + default='createdat', + type=str.lower, + help=('Change sort ordered of displayed containers.' + ' (default: %(default)s)')) + parser.set_defaults(class_=cls, method='list') + + def __init__(self, args): + """Construct Ps class.""" + super().__init__(args) + + self.columns = OrderedDict({ + 'id': + ReportColumn('id', 'CONTAINER ID', 14), + 'image': + ReportColumn('image', 'IMAGE', 30), + 'command': + ReportColumn('column', 'COMMAND', 20), + 'createdat': + ReportColumn('createdat', 'CREATED', 12), + 'status': + ReportColumn('status', 'STATUS', 10), + 'ports': + ReportColumn('ports', 'PORTS', 28), + 'names': + ReportColumn('names', 'NAMES', 18) + }) + + def list(self): + """List containers.""" + # TODO: Verify sorting on dates and size + ctnrs = sorted( + self.client.containers.list(), + key=operator.attrgetter(self._args.sort)) + if len(ctnrs) == 0: + return 0 + + rows = list() + for ctnr in ctnrs: + fields = dict(ctnr) + fields.update({ + 'command': + ' '.join(ctnr.command), + 'createdat': + humanize.naturaldate(podman.datetime_parse(ctnr.createdat)), + }) + rows.append(fields) + + with Report(self.columns, heading=self._args.heading) as report: + report.layout( + rows, self.columns.keys(), truncate=self._args.truncate) + for row in rows: + report.row(**row) diff --git a/contrib/python/pypodman/pypodman/lib/actions/rm_action.py b/contrib/python/pypodman/pypodman/lib/actions/rm_action.py new file mode 100644 index 000000000..bd8950bd6 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/rm_action.py @@ -0,0 +1,51 @@ +"""Remote client command for deleting containers.""" +import sys + +import podman + +from .. import AbstractActionBase + + +class Rm(AbstractActionBase): + """Class for removing containers from storage.""" + + @classmethod + def subparser(cls, parent): + """Add Rm command to parent parser.""" + parser = parent.add_parser('rm', help='delete container(s)') + parser.add_argument( + '-f', + '--force', + action='store_true', + help=('force delete of running container(s).' + ' (default: %(default)s)')) + parser.add_argument( + 'targets', nargs='*', help='container id(s) to delete') + parser.set_defaults(class_=cls, method='remove') + + def __init__(self, args): + """Construct Rm class.""" + super().__init__(args) + if len(args.targets) < 1: + raise ValueError('You must supply at least one container id' + ' or name to be deleted.') + + def remove(self): + """Remove container(s).""" + for id in self._args.targets: + try: + ctnr = self.client.containers.get(id) + ctnr.remove(self._args.force) + print(id) + except podman.ContainerNotFound as e: + sys.stdout.flush() + print( + 'Container {} not found.'.format(e.name), + file=sys.stderr, + flush=True) + except podman.ErrorOccurred as e: + sys.stdout.flush() + print( + '{}'.format(e.reason).capitalize(), + file=sys.stderr, + flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py b/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py new file mode 100644 index 000000000..91f0deeaf --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/actions/rmi_action.py @@ -0,0 +1,50 @@ +"""Remote client command for deleting images.""" +import sys + +import podman + +from .. import AbstractActionBase + + +class Rmi(AbstractActionBase): + """Clas for removing images from storage.""" + + @classmethod + def subparser(cls, parent): + """Add Rmi command to parent parser.""" + parser = parent.add_parser('rmi', help='delete image(s)') + parser.add_argument( + '-f', + '--force', + action='store_true', + help=('force delete of image(s) and associated containers.' + ' (default: %(default)s)')) + parser.add_argument('targets', nargs='*', help='image id(s) to delete') + parser.set_defaults(class_=cls, method='remove') + + def __init__(self, args): + """Construct Rmi class.""" + super().__init__(args) + if len(args.targets) < 1: + raise ValueError('You must supply at least one image id' + ' or name to be deleted.') + + def remove(self): + """Remove image(s).""" + for id in self._args.targets: + try: + img = self.client.images.get(id) + img.remove(self._args.force) + print(id) + except podman.ImageNotFound as e: + sys.stdout.flush() + print( + 'Image {} not found.'.format(e.name), + file=sys.stderr, + flush=True) + except podman.ErrorOccurred as e: + sys.stdout.flush() + print( + '{}'.format(e.reason).capitalize(), + file=sys.stderr, + flush=True) diff --git a/contrib/python/pypodman/pypodman/lib/config.py b/contrib/python/pypodman/pypodman/lib/config.py new file mode 100644 index 000000000..90848b567 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/config.py @@ -0,0 +1,211 @@ +"""Parse configuration while building subcommands.""" +import argparse +import curses +import getpass +import inspect +import logging +import os +import sys + +import pkg_resources +import pytoml + +# TODO: setup.py and obtain __version__ from rpm.spec +try: + __version__ = pkg_resources.get_distribution('pypodman').version +except Exception: + __version__ = '0.0.0' + + +class HelpFormatter(argparse.RawDescriptionHelpFormatter): + """Set help width to screen size.""" + + def __init__(self, *args, **kwargs): + """Construct HelpFormatter using screen width.""" + if 'width' not in kwargs: + kwargs['width'] = 80 + try: + height, width = curses.initscr().getmaxyx() + kwargs['width'] = width + finally: + curses.endwin() + super().__init__(*args, **kwargs) + + +class PodmanArgumentParser(argparse.ArgumentParser): + """Default remote podman configuration.""" + + def __init__(self, **kwargs): + """Construct the parser.""" + kwargs['add_help'] = True + kwargs['description'] = __doc__ + kwargs['formatter_class'] = HelpFormatter + + super().__init__(**kwargs) + + def initialize_parser(self): + """Initialize parser without causing recursion meltdown.""" + self.add_argument( + '--version', + action='version', + version='%(prog)s v. ' + __version__) + self.add_argument( + '--log-level', + choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], + default='WARNING', + type=str.upper, + help='set logging level for events. (default: %(default)s)', + ) + self.add_argument( + '--run-dir', + metavar='DIRECTORY', + help=('directory to place local socket bindings.' + ' (default: XDG_RUNTIME_DIR/pypodman')) + self.add_argument( + '--user', + default=getpass.getuser(), + help='Authenicating user on remote host. (default: %(default)s)') + self.add_argument( + '--host', help='name of remote host. (default: None)') + self.add_argument( + '--remote-socket-path', + metavar='PATH', + help=('path of podman socket on remote host' + ' (default: /run/podman/io.projectatomic.podman)')) + self.add_argument( + '--identity-file', + metavar='PATH', + help=('path to ssh identity file. (default: ~user/.ssh/id_dsa)')) + self.add_argument( + '--config-home', + metavar='DIRECTORY', + help=('home of configuration "pypodman.conf".' + ' (default: XDG_CONFIG_HOME/pypodman')) + + actions_parser = self.add_subparsers( + dest='subparser_name', help='actions') + + # pull in plugin(s) code for each subcommand + for name, obj in inspect.getmembers( + sys.modules['pypodman.lib.actions'], + lambda member: inspect.isclass(member)): + if hasattr(obj, 'subparser'): + try: + obj.subparser(actions_parser) + except NameError as e: + logging.critical(e) + logging.warning( + 'See subparser configuration for Class "{}"'.format( + name)) + sys.exit(3) + + def parse_args(self, args=None, namespace=None): + """Parse command line arguments, backed by env var and config_file.""" + self.initialize_parser() + cooked = super().parse_args(args, namespace) + return self.resolve_configuration(cooked) + + def resolve_configuration(self, args): + """Find and fill in any arguments not passed on command line.""" + args.xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp') + args.xdg_config_home = os.environ.get('XDG_CONFIG_HOME', + os.path.expanduser('~/.config')) + args.xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg') + + # Configuration file(s) are optional, + # required arguments may be provided elsewhere + config = {'default': {}} + dirs = args.xdg_config_dirs.split(':') + dirs.extend((args.xdg_config_home, args.config_home)) + for dir_ in dirs: + if dir_ is None: + continue + try: + with open(os.path.join(dir_, 'pypodman/pypodman.conf'), + 'r') as stream: + config.update(pytoml.load(stream)) + except OSError: + pass + + def reqattr(name, value): + if value: + setattr(args, name, value) + return value + self.error('Required argument "%s" is not configured.' % name) + + reqattr( + 'run_dir', + getattr(args, 'run_dir') + or os.environ.get('RUN_DIR') + or config['default'].get('run_dir') + or os.path.join(args.xdg_runtime_dir, 'pypodman') + ) # yapf: disable + + setattr( + args, + 'host', + getattr(args, 'host') + or os.environ.get('HOST') + or config['default'].get('host') + ) # yapf:disable + + reqattr( + 'user', + getattr(args, 'user') + or os.environ.get('USER') + or config['default'].get('user') + or getpass.getuser() + ) # yapf:disable + + reqattr( + 'remote_socket_path', + getattr(args, 'remote_socket_path') + or os.environ.get('REMOTE_SOCKET_PATH') + or config['default'].get('remote_socket_path') + or '/run/podman/io.projectatomic.podman' + ) # yapf:disable + + reqattr( + 'log_level', + getattr(args, 'log_level') + or os.environ.get('LOG_LEVEL') + or config['default'].get('log_level') + or logging.WARNING + ) # yapf:disable + + setattr( + args, + 'identity_file', + getattr(args, 'identity_file') + or os.environ.get('IDENTITY_FILE') + or config['default'].get('identity_file') + or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.user)) + ) # yapf:disable + + if not os.path.isfile(args.identity_file): + args.identity_file = None + + if args.host: + args.local_socket_path = os.path.join(args.run_dir, + "podman.socket") + else: + args.local_socket_path = args.remote_socket_path + + args.local_uri = "unix:{}".format(args.local_socket_path) + args.remote_uri = "ssh://{}@{}{}".format(args.user, args.host, + args.remote_socket_path) + return args + + def exit(self, status=0, message=None): + """Capture message and route to logger.""" + if message: + log = logging.info if status == 0 else logging.error + log(message) + super().exit(status) + + def error(self, message): + """Capture message and route to logger.""" + logging.error('{}: {}'.format(self.prog, message)) + logging.error("Try '{} --help' for more information.".format( + self.prog)) + super().exit(2) diff --git a/contrib/python/pypodman/pypodman/lib/future_abstract.py b/contrib/python/pypodman/pypodman/lib/future_abstract.py new file mode 100644 index 000000000..79256d987 --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/future_abstract.py @@ -0,0 +1,20 @@ +"""Utilities for with-statement contexts. See PEP 343.""" + +import abc + +try: + from contextlib import AbstractContextManager + assert AbstractContextManager +except ImportError: + class AbstractContextManager(abc.ABC): + """An abstract base class for context managers.""" + + @abc.abstractmethod + def __enter__(self): + """Return `self` upon entering the runtime context.""" + return self + + @abc.abstractmethod + def __exit__(self, exc_type, exc_value, traceback): + """Raise any exception triggered within the runtime context.""" + return None diff --git a/contrib/python/pypodman/pypodman/lib/report.py b/contrib/python/pypodman/pypodman/lib/report.py new file mode 100644 index 000000000..25fe2ae0d --- /dev/null +++ b/contrib/python/pypodman/pypodman/lib/report.py @@ -0,0 +1,67 @@ +"""Report Manager.""" +import sys +from collections import namedtuple + +from .future_abstract import AbstractContextManager + + +class ReportColumn(namedtuple('ReportColumn', 'key display width default')): + """Hold attributes of output column.""" + + __slots__ = () + + def __new__(cls, key, display, width, default=None): + """Add defaults for attributes.""" + return super(ReportColumn, cls).__new__(cls, key, display, width, + default) + + +class Report(AbstractContextManager): + """Report Manager.""" + + def __init__(self, columns, heading=True, epilog=None, file=sys.stdout): + """Construct Report. + + columns is a mapping for named fields to column headings. + headers True prints headers on table. + epilog will be printed when the report context is closed. + """ + self._columns = columns + self._file = file + self._heading = heading + self.epilog = epilog + self._format = None + + def row(self, **fields): + """Print row for report.""" + if self._heading: + hdrs = {k: v.display for (k, v) in self._columns.items()} + print(self._format.format(**hdrs), flush=True, file=self._file) + self._heading = False + fields = {k: str(v) for k, v in fields.items()} + print(self._format.format(**fields)) + + def __exit__(self, exc_type, exc_value, traceback): + """Leave Report context and print epilog if provided.""" + if self.epilog: + print(self.epilog, flush=True, file=self._file) + + def layout(self, iterable, keys, truncate=True): + """Use data and headings build format for table to fit.""" + format = [] + + for key in keys: + value = max(map(lambda x: len(str(x.get(key, ''))), iterable)) + # print('key', key, 'value', value) + + if truncate: + row = self._columns.get( + key, ReportColumn(key, key.upper(), len(key))) + if value < row.width: + step = row.width if value == 0 else value + value = max(len(key), step) + elif value > row.width: + value = row.width if row.width != 0 else value + + format.append('{{{0}:{1}.{1}}}'.format(key, value)) + self._format = ' '.join(format) diff --git a/contrib/python/pypodman/pypodman/main.py b/contrib/python/pypodman/pypodman/main.py new file mode 100755 index 000000000..9d747f0ef --- /dev/null +++ b/contrib/python/pypodman/pypodman/main.py @@ -0,0 +1,73 @@ +"""Remote podman client.""" +from __future__ import absolute_import + +import logging +import os +import sys + +from .lib import PodmanArgumentParser + + +def main(): + """Entry point.""" + # Setup logging so we use stderr and can change logging level later + # Do it now before there is any chance of a default setup hardcoding crap. + log = logging.getLogger() + fmt = logging.Formatter('%(asctime)s | %(levelname)-8s | %(message)s', + '%Y-%m-%d %H:%M:%S %Z') + stderr = logging.StreamHandler(stream=sys.stderr) + stderr.setFormatter(fmt) + log.addHandler(stderr) + log.setLevel(logging.WARNING) + + parser = PodmanArgumentParser() + args = parser.parse_args() + + log.setLevel(args.log_level) + logging.debug('Logging initialized at level {}'.format( + logging.getLevelName(logging.getLogger().getEffectiveLevel()))) + + def want_tb(): + """Add traceback when logging events.""" + return log.getEffectiveLevel() == logging.DEBUG + + try: + if not os.path.exists(args.run_dir): + os.makedirs(args.run_dir) + except PermissionError as e: + logging.critical(e, exc_info=want_tb()) + sys.exit(6) + + # class_(args).method() are set by the sub-command's parser + returncode = None + try: + obj = args.class_(args) + except Exception as e: + logging.critical(repr(e), exc_info=want_tb()) + logging.warning('See subparser "{}" configuration.'.format( + args.subparser_name)) + sys.exit(5) + + try: + returncode = getattr(obj, args.method)() + except AttributeError as e: + logging.critical(e, exc_info=want_tb()) + logging.warning('See subparser "{}" configuration.'.format( + args.subparser_name)) + returncode = 3 + except KeyboardInterrupt: + pass + except ( + ConnectionRefusedError, + ConnectionResetError, + TimeoutError, + ) as e: + logging.critical(e, exc_info=want_tb()) + logging.info('Review connection arguments for correctness.') + returncode = 4 + + return 0 if returncode is None else returncode + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/contrib/python/pypodman/pypodman/test/test_report.py b/contrib/python/pypodman/pypodman/test/test_report.py new file mode 100644 index 000000000..280a9a954 --- /dev/null +++ b/contrib/python/pypodman/pypodman/test/test_report.py @@ -0,0 +1,23 @@ +from __future__ import absolute_import + +import unittest + +from report import Report, ReportColumn + + +class TestReport(unittest.TestCase): + def setUp(self): + pass + + def test_report_column(self): + rc = ReportColumn('k', 'v', 3) + self.assertEqual(rc.key, 'k') + self.assertEqual(rc.display, 'v') + self.assertEqual(rc.width, 3) + self.assertIsNone(rc.default) + + rc = ReportColumn('k', 'v', 3, 'd') + self.assertEqual(rc.key, 'k') + self.assertEqual(rc.display, 'v') + self.assertEqual(rc.width, 3) + self.assertEqual(rc.default, 'd') diff --git a/contrib/python/pypodman/requirements.txt b/contrib/python/pypodman/requirements.txt index f9cd4f904..69cf41761 100644 --- a/contrib/python/pypodman/requirements.txt +++ b/contrib/python/pypodman/requirements.txt @@ -1,4 +1,4 @@ humanize podman pytoml -setuptools>=39.2.0 +setuptools>=39 diff --git a/contrib/python/pypodman/setup.py b/contrib/python/pypodman/setup.py index 04f8fb5f5..0509bf942 100644 --- a/contrib/python/pypodman/setup.py +++ b/contrib/python/pypodman/setup.py @@ -10,6 +10,7 @@ with open(os.path.join(root, 'README.md')) as me: with open(os.path.join(root, 'requirements.txt')) as r: requirements = r.read().splitlines() + setup( name='pypodman', version=os.environ.get('PODMAN_VERSION', '0.0.0'), @@ -19,15 +20,15 @@ setup( license='Apache Software License', long_description=readme, entry_points={'console_scripts': [ - 'pypodman = lib.pypodman:main', + 'pypodman = pypodman.main:main', ]}, include_package_data=True, install_requires=requirements, - keywords='varlink libpod podman pypodman', packages=find_packages(exclude=['test']), python_requires='>=3', zip_safe=True, url='http://github.com/projectatomic/libpod', + keywords='varlink libpod podman pypodman', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', @@ -36,7 +37,7 @@ setup( 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', - 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.4', 'Topic :: System :: Systems Administration', 'Topic :: Utilities', ]) diff --git a/contrib/python/pypodman/test/test_report.py b/contrib/python/pypodman/test/test_report.py deleted file mode 100644 index 280a9a954..000000000 --- a/contrib/python/pypodman/test/test_report.py +++ /dev/null @@ -1,23 +0,0 @@ -from __future__ import absolute_import - -import unittest - -from report import Report, ReportColumn - - -class TestReport(unittest.TestCase): - def setUp(self): - pass - - def test_report_column(self): - rc = ReportColumn('k', 'v', 3) - self.assertEqual(rc.key, 'k') - self.assertEqual(rc.display, 'v') - self.assertEqual(rc.width, 3) - self.assertIsNone(rc.default) - - rc = ReportColumn('k', 'v', 3, 'd') - self.assertEqual(rc.key, 'k') - self.assertEqual(rc.display, 'v') - self.assertEqual(rc.width, 3) - self.assertEqual(rc.default, 'd') diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index e40cf47a7..1f1274a4d 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -205,26 +205,10 @@ Requires: python3-varlink Requires: python3-dateutil Provides: python3-%{name} = %{version}-%{release} -Summary: Python 3 bindings for %{name} +Summary: Python 3 bindings and client for %{name} %description -n python3-%{name} -This package contains Python 3 bindings for %{name}. - -%package -n python3-py%{name} -BuildArch: noarch -BuildRequires: python3-devel -BuildRequires: python3-setuptools -BuildRequires: python3-varlink - -Requires: python3-setuptools -Requires: python3-varlink -Requires: python3-dateutil - -Provides: python3-py%{name} = %{version}-%{release} -Summary: Python 3 tool for %{name} - -%description -n python3-py%{name} -This package contains Python 3 tool for %{name}. +This package contains Python 3 bindings and client for %{name}. %endif # varlink %if 0%{?with_devel} @@ -403,18 +387,6 @@ GOPATH=$GOPATH go generate ./cmd/podman/varlink/... GOPATH=$GOPATH BUILDTAGS=$BUILDTAGS %gobuild -o bin/%{name} %{import_path}/cmd/%{name} BUILDTAGS=$BUILDTAGS make binaries docs -%if %{with varlink} -#untar contents for python-podman -pushd contrib/python/podman/dist -tar zxf %{name}*.tar.gz -popd - -#untar contents for python-pypodman -pushd contrib/python/pypodman/dist -tar zxf %{name}*.tar.gz -popd -%endif #varlink - %install install -dp %{buildroot}%{_unitdir} %make_install PREFIX=%{buildroot}%{_prefix} install install.completions @@ -502,6 +474,7 @@ export GOPATH=%{buildroot}/%{gopath}:$(pwd)/vendor:%{gopath} %license LICENSE %doc README.md CONTRIBUTING.md install.md code-of-conduct.md transfer.md %{_bindir}/%{name} +%{_bindir}/py%{name} %{_mandir}/man1/*.1* %{_mandir}/man5/*.5* %{_datadir}/bash-completion/completions/* diff --git a/contrib/spec/python-podman.spec.in b/contrib/spec/python-podman.spec.in new file mode 100644 index 000000000..d7956d110 --- /dev/null +++ b/contrib/spec/python-podman.spec.in @@ -0,0 +1,104 @@ +# If any of the following macros should be set otherwise, +# you can wrap any of them with the following conditions: +# - %%if 0%%{?centos} == 7 +# - %%if 0%%{?rhel} == 7 +# - %%if 0%%{?fedora} == 23 +# Or just test for particular distribution: +# - %%if 0%%{?centos} +# - %%if 0%%{?rhel} +# - %%if 0%%{?fedora} +# +# Be aware, on centos, both %%rhel and %%centos are set. If you want to test +# rhel specific macros, you can use %%if 0%%{?rhel} && 0%%{?centos} == 0 condition. +# (Don't forget to replace double percentage symbol with single one in order to apply a condition) + +%undefine _enable_debug_packages + +%global provider github +%global provider_tld com +%global project projectatomic +%global repo libpod +# https://github.com/projectatomic/libpod +%global provider_prefix %{provider}.%{provider_tld}/%{project}/%{repo} +%global import_path %{provider_prefix} +%global commit #COMMIT# +%global shortcommit %(c=%{commit}; echo ${c:0:7}) + +Name: python3-podman +Version: 0.7.3 +Release: #COMMITDATE#.git%{shortcommit}%{?dist} +Summary: Python 3 bindings and client for podman +License: ASL 2.0 +URL: https://%{provider_prefix} +Source0: https://api.%{provider}.%{provider_tld}/repos/%{project}/%{repo}/tarball/%{commit} + +BuildArch: noarch +BuildRequires: git +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-varlink + +Requires: python3-humanize +Requires: python3-pytoml +Requires: python3-setuptools +Requires: python3-varlink +Requires: podman + +%if 0%{?fedora} +# 2018-07-20 RHEL8 doesn't have varlink RPM yet +Requires: python3-varlink +%endif + +Provides: %{name} = %{version}-%{release} + +%description +%{summary} +python3-podman provides python bindings and client for communicating +with podman as a service. + +%prep +%autosetup -Sgit -n %{project}-%{repo}-%{shortcommit} + +%build +export PODMAN_VERSION=%{version} + +pushd contrib/python/podman +%{__python3} setup.py build +popd + +pushd contrib/python/pypodman +%{__python3} setup.py build +popd + +%install +export PODMAN_VERSION=%{version} + +install -d -m 755 %{buildroot}%{_mandir}/man1 + +pushd contrib/python/pypodman +install -m 644 -t %{buildroot}%{_mandir}/man1 docs/man1/*.1 +%{__python3} setup.py install --skip-build --root %{buildroot} +popd + +pushd contrib/python/podman +%{__python3} setup.py install --skip-build --root %{buildroot} +popd + + +%check +#define license tag if not already defined +%{!?_licensedir:%global license %doc} + +%files +%license LICENSE +%doc README.md CONTRIBUTING.md install.md code-of-conduct.md transfer.md +%{_bindir}/pypodman +%{_mandir}/man1/pypodman.1* +%dir %{python3_sitelib}/podman +%dir %{python3_sitelib}/pypodman +%{python3_sitelib}/podman/* +%{python3_sitelib}/pypodman/* +%{python3_sitelib}/podman-%{version}*.egg-info +%{python3_sitelib}/pypodman-%{version}*.egg-info + +%changelog -- cgit v1.2.3-54-g00ecf