diff options
Diffstat (limited to 'contrib/python')
-rw-r--r-- | contrib/python/cmd/lib/__init__.py | 5 | ||||
-rw-r--r-- | contrib/python/podman/CHANGES.txt (renamed from contrib/python/CHANGES.txt) | 0 | ||||
-rw-r--r-- | contrib/python/podman/LICENSE.txt (renamed from contrib/python/LICENSE.txt) | 0 | ||||
-rw-r--r-- | contrib/python/podman/MANIFEST.in (renamed from contrib/python/MANIFEST.in) | 0 | ||||
-rw-r--r-- | contrib/python/podman/Makefile (renamed from contrib/python/Makefile) | 5 | ||||
-rw-r--r-- | contrib/python/podman/README.md (renamed from contrib/python/README.md) | 2 | ||||
-rw-r--r-- | contrib/python/podman/examples/eg_attach.py (renamed from contrib/python/examples/eg_attach.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/examples/eg_containers_by_image.py (renamed from contrib/python/examples/eg_containers_by_image.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/examples/eg_image_list.py (renamed from contrib/python/examples/eg_image_list.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/examples/eg_inspect_fedora.py (renamed from contrib/python/examples/eg_inspect_fedora.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/examples/eg_latest_containers.py (renamed from contrib/python/examples/eg_latest_containers.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/examples/eg_new_image.py (renamed from contrib/python/examples/eg_new_image.py) | 0 | ||||
-rwxr-xr-x | contrib/python/podman/examples/run_example.sh (renamed from contrib/python/examples/run_example.sh) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/__init__.py (renamed from contrib/python/podman/__init__.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/client.py (renamed from contrib/python/podman/client.py) | 26 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/__init__.py (renamed from contrib/python/podman/libs/__init__.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/_containers_attach.py (renamed from contrib/python/podman/libs/_containers_attach.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/_containers_start.py (renamed from contrib/python/podman/libs/_containers_start.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/containers.py (renamed from contrib/python/podman/libs/containers.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/errors.py (renamed from contrib/python/podman/libs/errors.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/images.py (renamed from contrib/python/podman/libs/images.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/system.py (renamed from contrib/python/podman/libs/system.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/podman/libs/tunnel.py (renamed from contrib/python/podman/libs/tunnel.py) | 25 | ||||
-rw-r--r-- | contrib/python/podman/requirements.txt (renamed from contrib/python/requirements.txt) | 0 | ||||
-rw-r--r-- | contrib/python/podman/setup.py (renamed from contrib/python/setup.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/test/__init__.py (renamed from contrib/python/test/__init__.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/test/podman_testcase.py (renamed from contrib/python/test/podman_testcase.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/test/test_client.py (renamed from contrib/python/test/test_client.py) | 8 | ||||
-rw-r--r-- | contrib/python/podman/test/test_containers.py (renamed from contrib/python/test/test_containers.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/test/test_images.py (renamed from contrib/python/test/test_images.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/test/test_libs.py (renamed from contrib/python/test/test_libs.py) | 0 | ||||
-rwxr-xr-x | contrib/python/podman/test/test_runner.sh (renamed from contrib/python/test/test_runner.sh) | 4 | ||||
-rw-r--r-- | contrib/python/podman/test/test_system.py (renamed from contrib/python/test/test_system.py) | 0 | ||||
-rw-r--r-- | contrib/python/podman/test/test_tunnel.py (renamed from contrib/python/test/test_tunnel.py) | 2 | ||||
-rw-r--r-- | contrib/python/pypodman/MANIFEST.in | 1 | ||||
-rw-r--r-- | contrib/python/pypodman/Makefile | 21 | ||||
-rw-r--r-- | contrib/python/pypodman/README.md | 32 | ||||
-rw-r--r-- | contrib/python/pypodman/docs/pypodman.1.md | 82 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/__init__.py | 11 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/action_base.py (renamed from contrib/python/cmd/lib/action_base.py) | 16 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/__init__.py (renamed from contrib/python/cmd/lib/actions/__init__.py) | 0 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/images_action.py (renamed from contrib/python/cmd/lib/actions/images_action.py) | 2 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/ps_action.py (renamed from contrib/python/cmd/lib/actions/ps_action.py) | 2 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/rm_action.py (renamed from contrib/python/cmd/lib/actions/rm_action.py) | 2 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/actions/rmi_action.py (renamed from contrib/python/cmd/lib/actions/rmi_action.py) | 2 | ||||
-rw-r--r--[-rwxr-xr-x] | contrib/python/pypodman/lib/config.py (renamed from contrib/python/cmd/pydman.py) | 156 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/future_abstract.py (renamed from contrib/python/cmd/lib/future_abstract.py) | 0 | ||||
-rwxr-xr-x | contrib/python/pypodman/lib/pypodman.py | 76 | ||||
-rw-r--r-- | contrib/python/pypodman/lib/report.py (renamed from contrib/python/cmd/lib/report.py) | 0 | ||||
-rw-r--r-- | contrib/python/pypodman/requirements.txt | 4 | ||||
-rw-r--r-- | contrib/python/pypodman/setup.py | 44 | ||||
-rw-r--r-- | contrib/python/pypodman/test/test_report.py | 23 |
52 files changed, 404 insertions, 147 deletions
diff --git a/contrib/python/cmd/lib/__init__.py b/contrib/python/cmd/lib/__init__.py deleted file mode 100644 index db0f640b1..000000000 --- a/contrib/python/cmd/lib/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Remote podman client support library.""" -from .action_base import AbstractActionBase -from .report import Report, ReportColumn - -__all__ = ['AbstractActionBase', 'Report', 'ReportColumn'] diff --git a/contrib/python/CHANGES.txt b/contrib/python/podman/CHANGES.txt index 2bac1c867..2bac1c867 100644 --- a/contrib/python/CHANGES.txt +++ b/contrib/python/podman/CHANGES.txt diff --git a/contrib/python/LICENSE.txt b/contrib/python/podman/LICENSE.txt index decfce56d..decfce56d 100644 --- a/contrib/python/LICENSE.txt +++ b/contrib/python/podman/LICENSE.txt diff --git a/contrib/python/MANIFEST.in b/contrib/python/podman/MANIFEST.in index 72e638cb9..72e638cb9 100644 --- a/contrib/python/MANIFEST.in +++ b/contrib/python/podman/MANIFEST.in diff --git a/contrib/python/Makefile b/contrib/python/podman/Makefile index 6cb63c403..ea40cccac 100644 --- a/contrib/python/Makefile +++ b/contrib/python/podman/Makefile @@ -8,9 +8,14 @@ python-podman: integration: test/test_runner.sh +.PHONY: install +install: + $(PYTHON) setup.py install --user + .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/README.md b/contrib/python/podman/README.md index dcf40a1a9..fad03fd27 100644 --- a/contrib/python/README.md +++ b/contrib/python/podman/README.md @@ -9,7 +9,7 @@ See [libpod](https://github.com/projectatomic/libpod) To build the podman egg: ```sh -cd ~/libpod/contrib/pypodman +cd ~/libpod/contrib/python python3 setup.py clean -a && python3 setup.py bdist ``` diff --git a/contrib/python/examples/eg_attach.py b/contrib/python/podman/examples/eg_attach.py index f5070dc53..f5070dc53 100644 --- a/contrib/python/examples/eg_attach.py +++ b/contrib/python/podman/examples/eg_attach.py diff --git a/contrib/python/examples/eg_containers_by_image.py b/contrib/python/podman/examples/eg_containers_by_image.py index bf4fdebf1..bf4fdebf1 100644 --- a/contrib/python/examples/eg_containers_by_image.py +++ b/contrib/python/podman/examples/eg_containers_by_image.py diff --git a/contrib/python/examples/eg_image_list.py b/contrib/python/podman/examples/eg_image_list.py index ef31fd708..ef31fd708 100644 --- a/contrib/python/examples/eg_image_list.py +++ b/contrib/python/podman/examples/eg_image_list.py diff --git a/contrib/python/examples/eg_inspect_fedora.py b/contrib/python/podman/examples/eg_inspect_fedora.py index b5bbba46d..b5bbba46d 100644 --- a/contrib/python/examples/eg_inspect_fedora.py +++ b/contrib/python/podman/examples/eg_inspect_fedora.py diff --git a/contrib/python/examples/eg_latest_containers.py b/contrib/python/podman/examples/eg_latest_containers.py index 446f670dd..446f670dd 100644 --- a/contrib/python/examples/eg_latest_containers.py +++ b/contrib/python/podman/examples/eg_latest_containers.py diff --git a/contrib/python/examples/eg_new_image.py b/contrib/python/podman/examples/eg_new_image.py index 21e076dcb..21e076dcb 100644 --- a/contrib/python/examples/eg_new_image.py +++ b/contrib/python/podman/examples/eg_new_image.py diff --git a/contrib/python/examples/run_example.sh b/contrib/python/podman/examples/run_example.sh index 0f6575073..0f6575073 100755 --- a/contrib/python/examples/run_example.sh +++ b/contrib/python/podman/examples/run_example.sh diff --git a/contrib/python/podman/__init__.py b/contrib/python/podman/podman/__init__.py index 5a0356311..5a0356311 100644 --- a/contrib/python/podman/__init__.py +++ b/contrib/python/podman/podman/__init__.py diff --git a/contrib/python/podman/client.py b/contrib/python/podman/podman/client.py index ad166eb06..404b7d117 100644 --- a/contrib/python/podman/client.py +++ b/contrib/python/podman/podman/client.py @@ -44,11 +44,11 @@ class BaseClient(object): raise ValueError('path is required for uri,' ' 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 if kwargs.get('remote_uri') is None: raise ValueError( - 'remote is required,' + 'remote_uri is required,' ' expected format "ssh://user@hostname/path_to_socket".') remote = urlparse(kwargs['remote_uri']) if remote.username is None: @@ -64,20 +64,16 @@ class BaseClient(object): 'hostname is required for remote_uri,' ' 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( - Context(uri, interface, local_path, remote.path, - remote.username, remote.hostname, - kwargs['identity_file'])) + Context( + uri, + interface, + local_path, + remote.path, + remote.username, + remote.hostname, + kwargs.get('identity_file'), + )) else: return LocalClient( Context(uri, interface, None, None, None, None, None)) diff --git a/contrib/python/podman/libs/__init__.py b/contrib/python/podman/podman/libs/__init__.py index 3a8a35021..3a8a35021 100644 --- a/contrib/python/podman/libs/__init__.py +++ b/contrib/python/podman/podman/libs/__init__.py diff --git a/contrib/python/podman/libs/_containers_attach.py b/contrib/python/podman/podman/libs/_containers_attach.py index df12fa998..df12fa998 100644 --- a/contrib/python/podman/libs/_containers_attach.py +++ b/contrib/python/podman/podman/libs/_containers_attach.py diff --git a/contrib/python/podman/libs/_containers_start.py b/contrib/python/podman/podman/libs/_containers_start.py index ad9f32eab..ad9f32eab 100644 --- a/contrib/python/podman/libs/_containers_start.py +++ b/contrib/python/podman/podman/libs/_containers_start.py diff --git a/contrib/python/podman/libs/containers.py b/contrib/python/podman/podman/libs/containers.py index 6dc2c141e..6dc2c141e 100644 --- a/contrib/python/podman/libs/containers.py +++ b/contrib/python/podman/podman/libs/containers.py diff --git a/contrib/python/podman/libs/errors.py b/contrib/python/podman/podman/libs/errors.py index b98210481..b98210481 100644 --- a/contrib/python/podman/libs/errors.py +++ b/contrib/python/podman/podman/libs/errors.py diff --git a/contrib/python/podman/libs/images.py b/contrib/python/podman/podman/libs/images.py index 334ff873c..334ff873c 100644 --- a/contrib/python/podman/libs/images.py +++ b/contrib/python/podman/podman/libs/images.py diff --git a/contrib/python/podman/libs/system.py b/contrib/python/podman/podman/libs/system.py index c59867760..c59867760 100644 --- a/contrib/python/podman/libs/system.py +++ b/contrib/python/podman/podman/libs/system.py diff --git a/contrib/python/podman/libs/tunnel.py b/contrib/python/podman/podman/libs/tunnel.py index 9effdff6c..440eb3951 100644 --- a/contrib/python/podman/libs/tunnel.py +++ b/contrib/python/podman/podman/libs/tunnel.py @@ -97,26 +97,27 @@ class Tunnel(object): def bore(self, id): """Create SSH tunnel from given context.""" - ssh_opts = '-nNT' + cmd = ['ssh'] + + ssh_opts = '-fNT' if logging.getLogger().getEffectiveLevel() == logging.DEBUG: ssh_opts += 'v' else: 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))) self._tunnel = subprocess.Popen(cmd, close_fds=True) - for i in range(10): + for i in range(300): # TODO: Make timeout configurable if os.path.exists(self.context.local_socket): break diff --git a/contrib/python/requirements.txt b/contrib/python/podman/requirements.txt index d294af3c7..d294af3c7 100644 --- a/contrib/python/requirements.txt +++ b/contrib/python/podman/requirements.txt diff --git a/contrib/python/setup.py b/contrib/python/podman/setup.py index c9db30199..c9db30199 100644 --- a/contrib/python/setup.py +++ b/contrib/python/podman/setup.py diff --git a/contrib/python/test/__init__.py b/contrib/python/podman/test/__init__.py index e69de29bb..e69de29bb 100644 --- a/contrib/python/test/__init__.py +++ b/contrib/python/podman/test/__init__.py diff --git a/contrib/python/test/podman_testcase.py b/contrib/python/podman/test/podman_testcase.py index f96a3a013..f96a3a013 100644 --- a/contrib/python/test/podman_testcase.py +++ b/contrib/python/podman/test/podman_testcase.py diff --git a/contrib/python/test/test_client.py b/contrib/python/podman/test/test_client.py index e642c8add..2abc60a24 100644 --- a/contrib/python/test/test_client.py +++ b/contrib/python/podman/test/test_client.py @@ -21,11 +21,10 @@ class TestClient(unittest.TestCase): self.assertIsInstance(p._client, LocalClient) 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) - def test_remote(self, mock_ping, mock_isfile): + def test_remote(self, mock_ping): p = Client( uri='unix:/run/podman', interface='io.projectatomic.podman', @@ -33,5 +32,4 @@ class TestClient(unittest.TestCase): identity_file='~/.ssh/id_rsa') self.assertIsInstance(p._client, BaseClient) - mock_ping.assert_called_once() - mock_isfile.assert_called_once() + mock_ping.assert_called_once_with() diff --git a/contrib/python/test/test_containers.py b/contrib/python/podman/test/test_containers.py index ec2dcde03..ec2dcde03 100644 --- a/contrib/python/test/test_containers.py +++ b/contrib/python/podman/test/test_containers.py diff --git a/contrib/python/test/test_images.py b/contrib/python/podman/test/test_images.py index 14bf90992..14bf90992 100644 --- a/contrib/python/test/test_images.py +++ b/contrib/python/podman/test/test_images.py diff --git a/contrib/python/test/test_libs.py b/contrib/python/podman/test/test_libs.py index 202bed1d8..202bed1d8 100644 --- a/contrib/python/test/test_libs.py +++ b/contrib/python/podman/test/test_libs.py diff --git a/contrib/python/test/test_runner.sh b/contrib/python/podman/test/test_runner.sh index 602e0d6fd..b3d2ba15b 100755 --- a/contrib/python/test/test_runner.sh +++ b/contrib/python/podman/test/test_runner.sh @@ -7,11 +7,11 @@ if [[ $(id -u) != 0 ]]; then fi # 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\" exit 1 fi -export PATH=../../bin:$PATH +export PATH=../../../bin:$PATH function usage { echo 1>&2 $0 [-v] [-h] [test.TestCase|test.TestCase.step] diff --git a/contrib/python/test/test_system.py b/contrib/python/podman/test/test_system.py index 3f6ca57a2..3f6ca57a2 100644 --- a/contrib/python/test/test_system.py +++ b/contrib/python/podman/test/test_system.py diff --git a/contrib/python/test/test_tunnel.py b/contrib/python/podman/test/test_tunnel.py index 2522df0ab..719a2f9a4 100644 --- a/contrib/python/test/test_tunnel.py +++ b/contrib/python/podman/test/test_tunnel.py @@ -66,7 +66,7 @@ class TestTunnel(unittest.TestCase): cmd = [ 'ssh', - '-nNTq', + '-fNTq', '-L', '{}:{}'.format(context.local_socket, context.remote_socket), '-i', diff --git a/contrib/python/pypodman/MANIFEST.in b/contrib/python/pypodman/MANIFEST.in new file mode 100644 index 000000000..bb3ec5f0d --- /dev/null +++ b/contrib/python/pypodman/MANIFEST.in @@ -0,0 +1 @@ +include README.md diff --git a/contrib/python/pypodman/Makefile b/contrib/python/pypodman/Makefile new file mode 100644 index 000000000..4d76b1a1e --- /dev/null +++ b/contrib/python/pypodman/Makefile @@ -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 {} \; diff --git a/contrib/python/pypodman/README.md b/contrib/python/pypodman/README.md new file mode 100644 index 000000000..8a1c293f1 --- /dev/null +++ b/contrib/python/pypodman/README.md @@ -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 +``` diff --git a/contrib/python/pypodman/docs/pypodman.1.md b/contrib/python/pypodman/docs/pypodman.1.md new file mode 100644 index 000000000..1a6be994d --- /dev/null +++ b/contrib/python/pypodman/docs/pypodman.1.md @@ -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)` diff --git a/contrib/python/pypodman/lib/__init__.py b/contrib/python/pypodman/lib/__init__.py new file mode 100644 index 000000000..5a8303668 --- /dev/null +++ b/contrib/python/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/cmd/lib/action_base.py b/contrib/python/pypodman/lib/action_base.py index bafddea03..ff2922262 100644 --- a/contrib/python/cmd/lib/action_base.py +++ b/contrib/python/pypodman/lib/action_base.py @@ -14,8 +14,8 @@ class AbstractActionBase(abc.ABC): """Define parser for this action. Subclasses must implement. API: - Use set_defaults() to set attributes "klass" and "method". These will - be invoked as klass(parsed_args).method() + Use set_defaults() to set attributes "class_" and "method". These will + be invoked as class_(parsed_args).method() """ parser.add_argument( '--all', @@ -64,10 +64,14 @@ class AbstractActionBase(abc.ABC): @lru_cache(maxsize=1) def client(self): """Podman remote client for communicating.""" - return podman.Client( - uri=self.local_uri, - remote_uri=self.remote_uri, - identity_file=self.identity_file) + 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.""" diff --git a/contrib/python/cmd/lib/actions/__init__.py b/contrib/python/pypodman/lib/actions/__init__.py index cdc58b6ab..cdc58b6ab 100644 --- a/contrib/python/cmd/lib/actions/__init__.py +++ b/contrib/python/pypodman/lib/actions/__init__.py diff --git a/contrib/python/cmd/lib/actions/images_action.py b/contrib/python/pypodman/lib/actions/images_action.py index 74c77edbb..f6a7497e5 100644 --- a/contrib/python/cmd/lib/actions/images_action.py +++ b/contrib/python/pypodman/lib/actions/images_action.py @@ -29,7 +29,7 @@ class Images(AbstractActionBase): '--digests', action='store_true', 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): """Construct Images class.""" diff --git a/contrib/python/cmd/lib/actions/ps_action.py b/contrib/python/pypodman/lib/actions/ps_action.py index 9fc3a155b..4bbec5578 100644 --- a/contrib/python/cmd/lib/actions/ps_action.py +++ b/contrib/python/pypodman/lib/actions/ps_action.py @@ -26,7 +26,7 @@ class Ps(AbstractActionBase): type=str.lower, help=('Change sort ordered of displayed containers.' ' (default: %(default)s)')) - parser.set_defaults(klass=cls, method='list') + parser.set_defaults(class_=cls, method='list') def __init__(self, args): """Construct Ps class.""" diff --git a/contrib/python/cmd/lib/actions/rm_action.py b/contrib/python/pypodman/lib/actions/rm_action.py index 7595fee6a..bd8950bd6 100644 --- a/contrib/python/cmd/lib/actions/rm_action.py +++ b/contrib/python/pypodman/lib/actions/rm_action.py @@ -21,7 +21,7 @@ class Rm(AbstractActionBase): ' (default: %(default)s)')) parser.add_argument( '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): """Construct Rm class.""" diff --git a/contrib/python/cmd/lib/actions/rmi_action.py b/contrib/python/pypodman/lib/actions/rmi_action.py index db59fe030..91f0deeaf 100644 --- a/contrib/python/cmd/lib/actions/rmi_action.py +++ b/contrib/python/pypodman/lib/actions/rmi_action.py @@ -20,7 +20,7 @@ class Rmi(AbstractActionBase): 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(klass=cls, method='remove') + parser.set_defaults(class_=cls, method='remove') def __init__(self, args): """Construct Rmi class.""" diff --git a/contrib/python/cmd/pydman.py b/contrib/python/pypodman/lib/config.py index 5008c706d..e687697ef 100755..100644 --- a/contrib/python/cmd/pydman.py +++ b/contrib/python/pypodman/lib/config.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python3 -"""Remote podman client.""" - import argparse import curses import getpass @@ -11,14 +8,11 @@ import sys import pkg_resources -import lib.actions import pytoml -assert lib.actions # silence pyflakes - # TODO: setup.py and obtain __version__ from rpm.spec try: - __version__ = pkg_resources.get_distribution('pydman').version + __version__ = pkg_resources.get_distribution('pypodman').version except Exception: __version__ = '0.0.0' @@ -59,36 +53,40 @@ class PodmanArgumentParser(argparse.ArgumentParser): self.add_argument( '--log-level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'], - default='INFO', + 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)')) + ' (default: XDG_RUNTIME_DIR/pypodman')) self.add_argument( '--user', - help=('Authenicating user on remote host.' - ' (default: {})').format(getpass.getuser())) + 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', - 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( - '--config', - default='/etc/containers/podman_client.conf', - dest='config_file', - help='path of configuration file. (default: %(default)s)') + '--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)): @@ -110,47 +108,49 @@ class PodmanArgumentParser(argparse.ArgumentParser): def resolve_configuration(self, args): """Find and fill in any arguments not passed on command line.""" - try: - # Configuration file optionall, arguments may be provided elsewhere - with open(args.config_file, 'r') as stream: - config = pytoml.load(stream) - 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'] = {} + 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 resolve(name, value): + def reqattr(name, value): if value: setattr(args, name, value) return value self.error('Required argument "%s" is not configured.' % name) - xdg = os.path.join(os.environ['XDG_RUNTIME_DIR'], 'podman') \ - if os.environ.get('XDG_RUNTIME_DIR') else None - - resolve( + reqattr( 'run_dir', getattr(args, 'run_dir') or os.environ.get('RUN_DIR') or config['default'].get('run_dir') - or xdg - or '/tmp/podman' if os.path.isdir('/tmp') else None + or os.path.join(args.xdg_runtime_dir, 'pypodman') ) # yapf: disable - args.local_socket_path = os.path.join(args.run_dir, "podman.socket") - - resolve( + setattr( + args, 'host', getattr(args, 'host') or os.environ.get('HOST') or config['default'].get('host') ) # yapf:disable - resolve( + reqattr( 'user', getattr(args, 'user') or os.environ.get('USER') @@ -158,7 +158,7 @@ class PodmanArgumentParser(argparse.ArgumentParser): or getpass.getuser() ) # yapf:disable - resolve( + reqattr( 'remote_socket_path', getattr(args, 'remote_socket_path') or os.environ.get('REMOTE_SOCKET_PATH') @@ -166,14 +166,32 @@ class PodmanArgumentParser(argparse.ArgumentParser): or '/run/podman/io.projectatomic.podman' ) # 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', getattr(args, 'identity_file') or os.environ.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 + 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) @@ -192,57 +210,3 @@ class PodmanArgumentParser(argparse.ArgumentParser): logging.error("Try '{} --help' for more information.".format( self.prog)) 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) diff --git a/contrib/python/cmd/lib/future_abstract.py b/contrib/python/pypodman/lib/future_abstract.py index 75a1d42db..75a1d42db 100644 --- a/contrib/python/cmd/lib/future_abstract.py +++ b/contrib/python/pypodman/lib/future_abstract.py diff --git a/contrib/python/pypodman/lib/pypodman.py b/contrib/python/pypodman/lib/pypodman.py new file mode 100755 index 000000000..4bc71a9cc --- /dev/null +++ b/contrib/python/pypodman/lib/pypodman.py @@ -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()) diff --git a/contrib/python/cmd/lib/report.py b/contrib/python/pypodman/lib/report.py index 25fe2ae0d..25fe2ae0d 100644 --- a/contrib/python/cmd/lib/report.py +++ b/contrib/python/pypodman/lib/report.py diff --git a/contrib/python/pypodman/requirements.txt b/contrib/python/pypodman/requirements.txt new file mode 100644 index 000000000..f9cd4f904 --- /dev/null +++ b/contrib/python/pypodman/requirements.txt @@ -0,0 +1,4 @@ +humanize +podman +pytoml +setuptools>=39.2.0 diff --git a/contrib/python/pypodman/setup.py b/contrib/python/pypodman/setup.py new file mode 100644 index 000000000..0483eb71c --- /dev/null +++ b/contrib/python/pypodman/setup.py @@ -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', + ]) diff --git a/contrib/python/pypodman/test/test_report.py b/contrib/python/pypodman/test/test_report.py new file mode 100644 index 000000000..280a9a954 --- /dev/null +++ b/contrib/python/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') |