Update python directories to better support setup.py

Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
Jhon Honce
2018-07-12 19:26:14 -07:00
parent 44b523c946
commit 74ccd9ce5f
54 changed files with 445 additions and 154 deletions

View File

@ -114,7 +114,12 @@ bin/podman.cross.%: .gopathok
python-podman: python-podman:
ifdef HAS_PYTHON3 ifdef HAS_PYTHON3
$(MAKE) -C contrib/python python-podman $(MAKE) -C contrib/python/podman python-podman
endif
python-pypodman:
ifdef HAS_PYTHON3
$(MAKE) -C contrib/python/pypodman python-pypodman
endif endif
clean: clean:
@ -128,9 +133,10 @@ clean:
test/copyimg/copyimg \ test/copyimg/copyimg \
test/testdata/redis-image \ test/testdata/redis-image \
cmd/podman/varlink/ioprojectatomicpodman.go \ cmd/podman/varlink/ioprojectatomicpodman.go \
$(MANPAGES) $(MANPAGES) ||:
ifdef HAS_PYTHON3 ifdef HAS_PYTHON3
$(MAKE) -C contrib/python clean $(MAKE) -C contrib/python/podman clean
$(MAKE) -C contrib/python/pypodman clean
endif endif
find . -name \*~ -delete find . -name \*~ -delete
find . -name \#\* -delete find . -name \#\* -delete
@ -169,12 +175,13 @@ localintegration: varlink_generate test-binaries clientintegration
ginkgo -v -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/. ginkgo -v -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/.
clientintegration: clientintegration:
$(MAKE) -C contrib/python integration $(MAKE) -C contrib/python/podman integration
$(MAKE) -C contrib/python/pypodman integration
vagrant-check: vagrant-check:
BOX=$(BOX) sh ./vagrant.sh BOX=$(BOX) sh ./vagrant.sh
binaries: varlink_generate podman python-podman binaries: varlink_generate podman python-podman python-pypodman
test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp test-binaries: test/bin2img/bin2img test/copyimg/copyimg test/checkseccomp/checkseccomp
@ -313,4 +320,5 @@ validate: gofmt .gitvalidation
validate \ validate \
install.libseccomp.sudo \ install.libseccomp.sudo \
python-podman \ python-podman \
python-pypodman \
clientintegration clientintegration

View File

@ -1,5 +0,0 @@
"""Remote podman client support library."""
from .action_base import AbstractActionBase
from .report import Report, ReportColumn
__all__ = ['AbstractActionBase', 'Report', 'ReportColumn']

View File

@ -8,9 +8,14 @@ python-podman:
integration: integration:
test/test_runner.sh test/test_runner.sh
.PHONY: install
install:
$(PYTHON) setup.py install --user
.PHONY: clean .PHONY: clean
clean: clean:
$(PYTHON) setup.py clean --all $(PYTHON) setup.py clean --all
pip3 uninstall podman ||:
rm -rf podman.egg-info dist rm -rf podman.egg-info dist
find . -depth -name __pycache__ -exec rm -rf {} \; find . -depth -name __pycache__ -exec rm -rf {} \;
find . -depth -name \*.pyc -exec rm -f {} \; find . -depth -name \*.pyc -exec rm -f {} \;

View File

@ -9,7 +9,7 @@ See [libpod](https://github.com/projectatomic/libpod)
To build the podman egg: To build the podman egg:
```sh ```sh
cd ~/libpod/contrib/pypodman cd ~/libpod/contrib/python
python3 setup.py clean -a && python3 setup.py bdist python3 setup.py clean -a && python3 setup.py bdist
``` ```

View File

@ -44,11 +44,11 @@ class BaseClient(object):
raise ValueError('path is required for uri,' raise ValueError('path is required for uri,'
' expected format "unix://path_to_socket"') ' expected format "unix://path_to_socket"')
if kwargs.get('remote_uri') or kwargs.get('identity_file'): if kwargs.get('remote_uri'):
# Remote access requires the full tuple of information # Remote access requires the full tuple of information
if kwargs.get('remote_uri') is None: if kwargs.get('remote_uri') is None:
raise ValueError( raise ValueError(
'remote is required,' 'remote_uri is required,'
' expected format "ssh://user@hostname/path_to_socket".') ' expected format "ssh://user@hostname/path_to_socket".')
remote = urlparse(kwargs['remote_uri']) remote = urlparse(kwargs['remote_uri'])
if remote.username is None: if remote.username is None:
@ -64,20 +64,16 @@ class BaseClient(object):
'hostname is required for remote_uri,' 'hostname is required for remote_uri,'
' expected format "ssh://user@hostname/path_to_socket".') ' expected format "ssh://user@hostname/path_to_socket".')
if kwargs.get('identity_file') is None:
raise ValueError('identity_file is required.')
if not os.path.isfile(kwargs['identity_file']):
raise FileNotFoundError(
errno.ENOENT,
os.strerror(errno.ENOENT),
kwargs['identity_file'],
)
return RemoteClient( return RemoteClient(
Context(uri, interface, local_path, remote.path, Context(
remote.username, remote.hostname, uri,
kwargs['identity_file'])) interface,
local_path,
remote.path,
remote.username,
remote.hostname,
kwargs.get('identity_file'),
))
else: else:
return LocalClient( return LocalClient(
Context(uri, interface, None, None, None, None, None)) Context(uri, interface, None, None, None, None, None))

View File

@ -97,26 +97,27 @@ class Tunnel(object):
def bore(self, id): def bore(self, id):
"""Create SSH tunnel from given context.""" """Create SSH tunnel from given context."""
ssh_opts = '-nNT' cmd = ['ssh']
ssh_opts = '-fNT'
if logging.getLogger().getEffectiveLevel() == logging.DEBUG: if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
ssh_opts += 'v' ssh_opts += 'v'
else: else:
ssh_opts += 'q' ssh_opts += 'q'
cmd.append(ssh_opts)
cmd.extend(('-L', '{}:{}'.format(self.context.local_socket,
self.context.remote_socket)))
if self.context.identity_file:
cmd.extend(('-i', self.context.identity_file))
cmd.append('ssh://{}@{}'.format(self.context.username,
self.context.hostname))
cmd = [
'ssh',
ssh_opts,
'-L',
'{}:{}'.format(self.context.local_socket,
self.context.remote_socket),
'-i',
self.context.identity_file,
'ssh://{}@{}'.format(self.context.username, self.context.hostname),
]
logging.debug('Tunnel cmd "{}"'.format(' '.join(cmd))) logging.debug('Tunnel cmd "{}"'.format(' '.join(cmd)))
self._tunnel = subprocess.Popen(cmd, close_fds=True) self._tunnel = subprocess.Popen(cmd, close_fds=True)
for i in range(10): for i in range(300):
# TODO: Make timeout configurable # TODO: Make timeout configurable
if os.path.exists(self.context.local_socket): if os.path.exists(self.context.local_socket):
break break

View File

@ -21,11 +21,10 @@ class TestClient(unittest.TestCase):
self.assertIsInstance(p._client, LocalClient) self.assertIsInstance(p._client, LocalClient)
self.assertIsInstance(p._client, BaseClient) self.assertIsInstance(p._client, BaseClient)
mock_ping.assert_called_once() mock_ping.assert_called_once_with()
@patch('os.path.isfile', return_value=True)
@patch('podman.libs.system.System.ping', return_value=True) @patch('podman.libs.system.System.ping', return_value=True)
def test_remote(self, mock_ping, mock_isfile): def test_remote(self, mock_ping):
p = Client( p = Client(
uri='unix:/run/podman', uri='unix:/run/podman',
interface='io.projectatomic.podman', interface='io.projectatomic.podman',
@ -33,5 +32,4 @@ class TestClient(unittest.TestCase):
identity_file='~/.ssh/id_rsa') identity_file='~/.ssh/id_rsa')
self.assertIsInstance(p._client, BaseClient) self.assertIsInstance(p._client, BaseClient)
mock_ping.assert_called_once() mock_ping.assert_called_once_with()
mock_isfile.assert_called_once()

View File

@ -7,11 +7,11 @@ if [[ $(id -u) != 0 ]]; then
fi fi
# setup path to find new binaries _NOT_ system binaries # setup path to find new binaries _NOT_ system binaries
if [[ ! -x ../../bin/podman ]]; then if [[ ! -x ../../../bin/podman ]]; then
echo 1>&2 Cannot find podman binary from libpod root directory. Run \"make binaries\" echo 1>&2 Cannot find podman binary from libpod root directory. Run \"make binaries\"
exit 1 exit 1
fi fi
export PATH=../../bin:$PATH export PATH=../../../bin:$PATH
function usage { function usage {
echo 1>&2 $0 [-v] [-h] [test.TestCase|test.TestCase.step] echo 1>&2 $0 [-v] [-h] [test.TestCase|test.TestCase.step]

View File

@ -66,7 +66,7 @@ class TestTunnel(unittest.TestCase):
cmd = [ cmd = [
'ssh', 'ssh',
'-nNTq', '-fNTq',
'-L', '-L',
'{}:{}'.format(context.local_socket, context.remote_socket), '{}:{}'.format(context.local_socket, context.remote_socket),
'-i', '-i',

View File

@ -0,0 +1 @@
include README.md

View File

@ -0,0 +1,21 @@
PYTHON ?= /usr/bin/python3
.PHONY: python-pypodman
python-pypodman:
$(PYTHON) setup.py bdist
.PHONY: integration
integration:
true
.PHONY: install
install:
$(PYTHON) setup.py install --user
.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 {} \;

View File

@ -0,0 +1,32 @@
# pypodman - CLI interface for podman written in python
## Status: Active Development
See [libpod](https://github.com/projectatomic/libpod/contrib/python/cmd)
## Releases
To build the pypodman egg:
```sh
cd ~/libpod/contrib/python/cmd
python3 setup.py clean -a && python3 setup.py bdist
```
## Running command:
### Against local podman service
```sh
$ pypodman images
```
### Against remote podman service
```sh
$ pypodman --host node001.example.org images
```
### Full help system available
```sh
$ pypodman -h
```
```sh
$ pypodman images -h
```

View File

@ -0,0 +1,82 @@
% 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)`

View File

@ -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',
]

View File

@ -14,8 +14,8 @@ class AbstractActionBase(abc.ABC):
"""Define parser for this action. Subclasses must implement. """Define parser for this action. Subclasses must implement.
API: API:
Use set_defaults() to set attributes "klass" and "method". These will Use set_defaults() to set attributes "class_" and "method". These will
be invoked as klass(parsed_args).method() be invoked as class_(parsed_args).method()
""" """
parser.add_argument( parser.add_argument(
'--all', '--all',
@ -64,6 +64,10 @@ class AbstractActionBase(abc.ABC):
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def client(self): def client(self):
"""Podman remote client for communicating.""" """Podman remote client for communicating."""
if self._args.host is None:
return podman.Client(
uri=self.local_uri)
else:
return podman.Client( return podman.Client(
uri=self.local_uri, uri=self.local_uri,
remote_uri=self.remote_uri, remote_uri=self.remote_uri,

View File

@ -29,7 +29,7 @@ class Images(AbstractActionBase):
'--digests', '--digests',
action='store_true', action='store_true',
help='Include digests with images. (default: %(default)s)') help='Include digests with images. (default: %(default)s)')
parser.set_defaults(klass=cls, method='list') parser.set_defaults(class_=cls, method='list')
def __init__(self, args): def __init__(self, args):
"""Construct Images class.""" """Construct Images class."""

View File

@ -26,7 +26,7 @@ class Ps(AbstractActionBase):
type=str.lower, type=str.lower,
help=('Change sort ordered of displayed containers.' help=('Change sort ordered of displayed containers.'
' (default: %(default)s)')) ' (default: %(default)s)'))
parser.set_defaults(klass=cls, method='list') parser.set_defaults(class_=cls, method='list')
def __init__(self, args): def __init__(self, args):
"""Construct Ps class.""" """Construct Ps class."""

View File

@ -21,7 +21,7 @@ class Rm(AbstractActionBase):
' (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(klass=cls, method='remove') parser.set_defaults(class_=cls, method='remove')
def __init__(self, args): def __init__(self, args):
"""Construct Rm class.""" """Construct Rm class."""

View File

@ -20,7 +20,7 @@ class Rmi(AbstractActionBase):
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(klass=cls, method='remove') parser.set_defaults(class_=cls, method='remove')
def __init__(self, args): def __init__(self, args):
"""Construct Rmi class.""" """Construct Rmi class."""

View File

@ -1,6 +1,3 @@
#!/usr/bin/env python3
"""Remote podman client."""
import argparse import argparse
import curses import curses
import getpass import getpass
@ -11,14 +8,11 @@ import sys
import pkg_resources import pkg_resources
import lib.actions
import pytoml import pytoml
assert lib.actions # silence pyflakes
# 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('pydman').version __version__ = pkg_resources.get_distribution('pypodman').version
except Exception: except Exception:
__version__ = '0.0.0' __version__ = '0.0.0'
@ -59,36 +53,40 @@ class PodmanArgumentParser(argparse.ArgumentParser):
self.add_argument( self.add_argument(
'--log-level', '--log-level',
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
default='INFO', default='WARNING',
type=str.upper, type=str.upper,
help='set logging level for events. (default: %(default)s)', help='set logging level for events. (default: %(default)s)',
) )
self.add_argument( self.add_argument(
'--run-dir', '--run-dir',
metavar='DIRECTORY',
help=('directory to place local socket bindings.' help=('directory to place local socket bindings.'
' (default: XDG_RUNTIME_DIR)')) ' (default: XDG_RUNTIME_DIR/pypodman'))
self.add_argument( self.add_argument(
'--user', '--user',
help=('Authenicating user on remote host.' default=getpass.getuser(),
' (default: {})').format(getpass.getuser())) help='Authenicating user on remote host. (default: %(default)s)')
self.add_argument( self.add_argument(
'--host', help='name of remote host. (default: None)') '--host', help='name of remote host. (default: None)')
self.add_argument( self.add_argument(
'--remote-socket-path', '--remote-socket-path',
metavar='PATH',
help=('path of podman socket on remote host' help=('path of podman socket on remote host'
' (default: /run/podman/io.projectatomic.podman)')) ' (default: /run/podman/io.projectatomic.podman)'))
self.add_argument( self.add_argument(
'--identity-file', '--identity-file',
help=('path to ssh identity file. (default: ~/.ssh/id_rsa)')) metavar='PATH',
help=('path to ssh identity file. (default: ~user/.ssh/id_dsa)'))
self.add_argument( self.add_argument(
'--config', '--config-home',
default='/etc/containers/podman_client.conf', metavar='DIRECTORY',
dest='config_file', help=('home of configuration "pypodman.conf".'
help='path of configuration file. (default: %(default)s)') ' (default: XDG_CONFIG_HOME/pypodman'))
actions_parser = self.add_subparsers( actions_parser = self.add_subparsers(
dest='subparser_name', help='actions') dest='subparser_name', help='actions')
# pull in plugin(s) code for each subcommand
for name, obj in inspect.getmembers( for name, obj in inspect.getmembers(
sys.modules['lib.actions'], sys.modules['lib.actions'],
lambda member: inspect.isclass(member)): lambda member: inspect.isclass(member)):
@ -110,47 +108,49 @@ class PodmanArgumentParser(argparse.ArgumentParser):
def resolve_configuration(self, args): def resolve_configuration(self, args):
"""Find and fill in any arguments not passed on command line.""" """Find and fill in any arguments not passed on command line."""
try: args.xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/tmp')
# Configuration file optionall, arguments may be provided elsewhere args.xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
with open(args.config_file, 'r') as stream: os.path.expanduser('~/.config'))
config = pytoml.load(stream) args.xdg_config_dirs = os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg')
except OSError:
logging.info(
'Failed to read: {}'.format(args.config_file),
exc_info=args.log_level == logging.DEBUG)
config = {'default': {}}
else:
if 'default' not in config:
config['default'] = {}
def resolve(name, value): # 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: if value:
setattr(args, name, value) setattr(args, name, value)
return value return value
self.error('Required argument "%s" is not configured.' % name) self.error('Required argument "%s" is not configured.' % name)
xdg = os.path.join(os.environ['XDG_RUNTIME_DIR'], 'podman') \ reqattr(
if os.environ.get('XDG_RUNTIME_DIR') else None
resolve(
'run_dir', 'run_dir',
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 xdg or os.path.join(args.xdg_runtime_dir, 'pypodman')
or '/tmp/podman' if os.path.isdir('/tmp') else None
) # yapf: disable ) # yapf: disable
args.local_socket_path = os.path.join(args.run_dir, "podman.socket") setattr(
args,
resolve(
'host', 'host',
getattr(args, 'host') getattr(args, 'host')
or os.environ.get('HOST') or os.environ.get('HOST')
or config['default'].get('host') or config['default'].get('host')
) # yapf:disable ) # yapf:disable
resolve( reqattr(
'user', 'user',
getattr(args, 'user') getattr(args, 'user')
or os.environ.get('USER') or os.environ.get('USER')
@ -158,7 +158,7 @@ class PodmanArgumentParser(argparse.ArgumentParser):
or getpass.getuser() or getpass.getuser()
) # yapf:disable ) # yapf:disable
resolve( reqattr(
'remote_socket_path', 'remote_socket_path',
getattr(args, 'remote_socket_path') getattr(args, 'remote_socket_path')
or os.environ.get('REMOTE_SOCKET_PATH') or os.environ.get('REMOTE_SOCKET_PATH')
@ -166,14 +166,32 @@ class PodmanArgumentParser(argparse.ArgumentParser):
or '/run/podman/io.projectatomic.podman' or '/run/podman/io.projectatomic.podman'
) # yapf:disable ) # yapf:disable
resolve( 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', 'identity_file',
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_rsa'.format(args.user)) or os.path.expanduser('~{}/.ssh/id_dsa'.format(args.user))
) # yapf:disable ) # 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.local_uri = "unix:{}".format(args.local_socket_path)
args.remote_uri = "ssh://{}@{}{}".format(args.user, args.host, args.remote_uri = "ssh://{}@{}{}".format(args.user, args.host,
args.remote_socket_path) args.remote_socket_path)
@ -192,57 +210,3 @@ class PodmanArgumentParser(argparse.ArgumentParser):
logging.error("Try '{} --help' for more information.".format( logging.error("Try '{} --help' for more information.".format(
self.prog)) self.prog))
super().exit(2) super().exit(2)
if __name__ == '__main__':
# Setup logging so we use stderr and can change logging level later
# Do it now before there is any chance of a default setup.
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.INFO)
parser = PodmanArgumentParser()
args = parser.parse_args()
log.setLevel(args.log_level)
logging.debug('Logging initialized at level {}'.format(
logging.getLevelName(logging.getLogger().getEffectiveLevel())))
def istraceback():
"""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=istraceback())
sys.exit(6)
# Klass(args).method() are setup by the sub-command's parser
returncode = None
try:
obj = args.klass(args)
except Exception as e:
logging.critical(repr(e), exc_info=istraceback())
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=istraceback())
logging.warning('See subparser "{}" configuration.'.format(
args.subparser_name))
returncode = 3
except (ConnectionResetError, TimeoutError) as e:
logging.critical(e, exc_info=istraceback())
logging.info('Review connection arguments for correctness.')
returncode = 4
sys.exit(0 if returncode is None else returncode)

View File

@ -0,0 +1,76 @@
#!/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())

View File

@ -0,0 +1,4 @@
humanize
podman
pytoml
setuptools>=39.2.0

View File

@ -0,0 +1,44 @@
import os
from setuptools import find_packages, setup
root = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(root, 'README.md')) as me:
readme = me.read()
with open(os.path.join(root, 'requirements.txt')) as r:
requirements = r.read().splitlines()
print(find_packages(where='pypodman', exclude=['test']))
setup(
name='pypodman',
version=os.environ.get('PODMAN_VERSION', '0.0.0'),
description='A client for communicating with a Podman server',
author_email='jhonce@redhat.com',
author='Jhon Honce',
license='Apache Software License',
long_description=readme,
entry_points={'console_scripts': [
'pypodman = lib.pypodman: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',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
'Operating System :: MacOS :: MacOS X',
'Operating System :: Microsoft :: Windows',
'Operating System :: POSIX',
'Programming Language :: Python :: 3.6',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
])

View File

@ -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')

View File

@ -209,6 +209,22 @@ Summary: Python 3 bindings for %{name}
%description -n python3-%{name} %description -n python3-%{name}
This package contains Python 3 bindings for %{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}.
%endif # varlink %endif # varlink
%if 0%{?with_devel} %if 0%{?with_devel}
@ -389,7 +405,12 @@ BUILDTAGS=$BUILDTAGS make binaries docs
%if %{with varlink} %if %{with varlink}
#untar contents for python-podman #untar contents for python-podman
pushd contrib/python/dist 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 tar zxf %{name}*.tar.gz
popd popd
%endif #varlink %endif #varlink
@ -400,7 +421,12 @@ install -dp %{buildroot}%{_unitdir}
%if %{with varlink} %if %{with varlink}
#install python-podman #install python-podman
pushd contrib/python pushd contrib/python/podman
%{__python3} setup.py install --root %{buildroot}
popd
#install python-pypodman
pushd contrib/python/pypodman
%{__python3} setup.py install --root %{buildroot} %{__python3} setup.py install --root %{buildroot}
popd popd
%endif #varlink %endif #varlink