mirror of
https://github.com/containers/podman.git
synced 2025-06-17 06:57:43 +08:00
Update python directories to better support setup.py
Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
18
Makefile
18
Makefile
@ -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
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
"""Remote podman client support library."""
|
|
||||||
from .action_base import AbstractActionBase
|
|
||||||
from .report import Report, ReportColumn
|
|
||||||
|
|
||||||
__all__ = ['AbstractActionBase', 'Report', 'ReportColumn']
|
|
@ -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 {} \;
|
@ -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
|
||||||
```
|
```
|
||||||
|
|
@ -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))
|
@ -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
|
@ -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()
|
|
@ -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]
|
@ -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',
|
1
contrib/python/pypodman/MANIFEST.in
Normal file
1
contrib/python/pypodman/MANIFEST.in
Normal file
@ -0,0 +1 @@
|
|||||||
|
include README.md
|
21
contrib/python/pypodman/Makefile
Normal file
21
contrib/python/pypodman/Makefile
Normal 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 {} \;
|
32
contrib/python/pypodman/README.md
Normal file
32
contrib/python/pypodman/README.md
Normal 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
|
||||||
|
```
|
82
contrib/python/pypodman/docs/pypodman.1.md
Normal file
82
contrib/python/pypodman/docs/pypodman.1.md
Normal 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)`
|
11
contrib/python/pypodman/lib/__init__.py
Normal file
11
contrib/python/pypodman/lib/__init__.py
Normal 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',
|
||||||
|
]
|
@ -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,
|
@ -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."""
|
@ -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."""
|
@ -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."""
|
@ -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."""
|
156
contrib/python/cmd/pydman.py → contrib/python/pypodman/lib/config.py
Executable file → Normal file
156
contrib/python/cmd/pydman.py → contrib/python/pypodman/lib/config.py
Executable file → Normal 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)
|
|
76
contrib/python/pypodman/lib/pypodman.py
Executable file
76
contrib/python/pypodman/lib/pypodman.py
Executable 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())
|
4
contrib/python/pypodman/requirements.txt
Normal file
4
contrib/python/pypodman/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
humanize
|
||||||
|
podman
|
||||||
|
pytoml
|
||||||
|
setuptools>=39.2.0
|
44
contrib/python/pypodman/setup.py
Normal file
44
contrib/python/pypodman/setup.py
Normal 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',
|
||||||
|
])
|
23
contrib/python/pypodman/test/test_report.py
Normal file
23
contrib/python/pypodman/test/test_report.py
Normal 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')
|
@ -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
|
||||||
|
Reference in New Issue
Block a user