diff --git a/podman/__init__.py b/podman/__init__.py index b6e41d70..6326601b 100644 --- a/podman/__init__.py +++ b/podman/__init__.py @@ -1,4 +1,4 @@ """Podman service module.""" from podman.api_connection import ApiConnection -__ALL__ = ["ApiConnection"] +__all__ = ["ApiConnection"] diff --git a/podman/api_connection.py b/podman/api_connection.py index ac411c0a..16e20971 100644 --- a/podman/api_connection.py +++ b/podman/api_connection.py @@ -7,17 +7,19 @@ from http import HTTPStatus from http.client import HTTPConnection +import podman.containers as containers import podman.errors as errors import podman.images as images import podman.system as system class ApiConnection(HTTPConnection, AbstractContextManager): - """ ApiConnection provides a specialized HTTPConnection - to a Podman service.""" + """ApiConnection provides a specialized HTTPConnection + to a Podman service.""" - def __init__(self, url, base="/v1.24/libpod", *args, - **kwargs): # pylint: disable-msg=W1113 + def __init__( + self, url, base="/v1.24/libpod", *args, **kwargs + ): # pylint: disable-msg=W1113 if url is None or not url: raise ValueError("url is required for service connection.") @@ -25,8 +27,11 @@ def __init__(self, url, base="/v1.24/libpod", *args, supported_schemes = ("unix", "ssh") uri = urllib.parse.urlparse(url) if uri.scheme not in ("unix", "ssh"): - raise ValueError("The scheme '{}' is not supported, only {}".format( - uri.scheme, supported_schemes)) + raise ValueError( + "The scheme '{}' is not supported, only {}".format( + uri.scheme, supported_schemes + ) + ) self.uri = uri self.base = base @@ -37,8 +42,9 @@ def connect(self): sock.connect(self.uri.path) self.sock = sock else: - raise NotImplementedError("Scheme {} not yet implemented".format( - self.uri.scheme)) + raise NotImplementedError( + "Scheme {} not yet implemented".format(self.uri.scheme) + ) def delete(self, path, params=None): """Basic DELETE wrapper for requests @@ -49,7 +55,7 @@ def delete(self, path, params=None): :param params: optional dictionary of query params added to the request :return: http response object """ - return self.request('DELETE', self.join(path, params)) + return self.request("DELETE", self.join(path, params)) def get(self, path, params=None): """Basic GET wrapper for requests @@ -60,7 +66,7 @@ def get(self, path, params=None): :param params: optional dictionary of query params added to the request :return: http response object """ - return self.request('GET', self.join(path, params)) + return self.request("GET", self.join(path, params)) def post(self, path, params=None, headers=None, encode=False): """Basic POST wrapper for requests @@ -82,32 +88,27 @@ def post(self, path, params=None, headers=None, encode=False): headers = {} if encode: - if ('content-type' not in set(key.lower() for key in headers) - and params): - headers['content-type'] = 'application/x-www-form-urlencoded' + if ( + "content-type" not in set(key.lower() for key in headers) + and params + ): + headers["content-type"] = "application/x-www-form-urlencoded" data = urllib.parse.urlencode(params) - return self.request('POST', - self.join(path), - body=data, - headers=headers) - - def request(self, - method, - url, - body=None, - headers=None, - *, - encode_chunked=False): + return self.request( + "POST", self.join(path), body=data, headers=headers + ) + + def request( + self, method, url, body=None, headers=None, *, encode_chunked=False + ): """Make request to Podman service.""" if headers is None: headers = {} - super().request(method, - url, - body, - headers, - encode_chunked=encode_chunked) + super().request( + method, url, body, headers, encode_chunked=encode_chunked + ) response = super().getresponse() # Errors are mapped to exceptions @@ -123,14 +124,16 @@ def request(self, ), response, ) - elif (response.status >= HTTPStatus.BAD_REQUEST - and response.status < HTTPStatus.INTERNAL_SERVER_ERROR): + elif ( + response.status >= HTTPStatus.BAD_REQUEST + and response.status < HTTPStatus.INTERNAL_SERVER_ERROR + ): raise errors.RequestError( "Request {}:{} failed: {}".format( method, url, response.reason - or "Response Status Code {}".format(response.status) + or "Response Status Code {}".format(response.status), ), response, ) @@ -163,8 +166,8 @@ def quote(value): def raise_not_found(exc, response, exception_type=errors.ImageNotFound): """helper function to raise a not found exception of exception_type""" body = json.loads(response.read()) - logging.info(body['cause']) - raise exception_type(body['message']) from exc + logging.info(body["cause"]) + raise exception_type(body["message"]) from exc def __exit__(self, exc_type, exc_value, traceback): self.close() @@ -174,6 +177,7 @@ def __exit__(self, exc_type, exc_value, traceback): with ApiConnection("unix:///run/podman/podman.sock") as api: print(system.version(api)) print(images.list_images(api)) + print(containers.list_containers(api)) try: images.inspect(api, "bozo the clown") diff --git a/podman/containers/__init__.py b/podman/containers/__init__.py new file mode 100644 index 00000000..e8a9e751 --- /dev/null +++ b/podman/containers/__init__.py @@ -0,0 +1,18 @@ +"""containers provides the operations against containers for a Podman service. +""" + +import json + + +def list_containers(api, all_=None): + """List all images for a Podman service.""" + query = {} + if all_: + query["all"] = True + response = api.get("/containers/json", query) + return json.loads(str(response.read(), "utf-8")) + + +__all__ = [ + "list_containers", +] diff --git a/podman/errors/__init__.py b/podman/errors/__init__.py index 48fe4cd4..11125b7e 100644 --- a/podman/errors/__init__.py +++ b/podman/errors/__init__.py @@ -34,6 +34,7 @@ class PodNotFound(NotFoundError): class RequestError(HTTPException): """Podman service reported issue with the request""" + def __init__(self, message, response=None): super().__init__(message) self.response = response diff --git a/podman/images/__init__.py b/podman/images/__init__.py index aab3a902..fd4451ab 100644 --- a/podman/images/__init__.py +++ b/podman/images/__init__.py @@ -1,23 +1,23 @@ """images provides the operations against images for a Podman service.""" import json - from http import HTTPStatus + import podman.errors as errors def list_images(api): """List all images for a Podman service.""" response = api.get("/images/json") - return json.loads(str(response.read(), 'utf-8')) + return json.loads(str(response.read(), "utf-8")) def inspect(api, name): """Report on named image for a Podman service. - Name may also be a image ID. + Name may also be an image ID. """ try: response = api.get("/images/{}/json".format(api.quote(name))) - return json.loads(str(response.read(), 'utf-8')) + return json.loads(str(response.read(), "utf-8")) except errors.NotFoundError as e: api.raise_not_found(e, e.response) @@ -34,12 +34,12 @@ def image_exists(api, name): def remove(api, name, force=None): """Remove named/identified image from Podman storage.""" params = {} - path = '/images/{}'.format(api.quote(name)) + path = "/images/{}".format(api.quote(name)) if force is not None: - params = {'force': force} + params = {"force": force} try: response = api.delete(path, params) - return json.loads(str(response.read(), 'utf-8')) + return json.loads(str(response.read(), "utf-8")) except errors.NotFoundError as e: api.raise_not_found(e, e.response) @@ -51,12 +51,9 @@ def tag_image(api, name, repo, tag): :param tag: string for the image tag :return boolean """ - data = { - 'repo': repo, - 'tag': tag - } + data = {"repo": repo, "tag": tag} try: - response = api.post('/images/{}/tag'.format(api.quote(name)), data) + response = api.post("/images/{}/tag".format(api.quote(name)), data) return response.status == HTTPStatus.CREATED except errors.NotFoundError as e: api.raise_image_not_found(e, e.response) @@ -65,13 +62,13 @@ def tag_image(api, name, repo, tag): def history(api, name): """get image history""" try: - response = api.get('/images/{}/history'.format(api.quote(name))) - return json.loads(str(response.read(), 'utf-8')) + response = api.get("/images/{}/history".format(api.quote(name))) + return json.loads(str(response.read(), "utf-8")) except errors.NotFoundError as e: api.raise_image_not_found(e, e.response) -__ALL__ = [ +__all__ = [ "list_images", "inspect", "image_exists", diff --git a/podman/networks/__init__.py b/podman/networks/__init__.py index 23309ec8..9bf0bfce 100644 --- a/podman/networks/__init__.py +++ b/podman/networks/__init__.py @@ -62,7 +62,7 @@ def remove(api, name, force=None): api.raise_not_found(e, e.response, errors.NetworkNotFound) -__ALL__ = [ +__all__ = [ "create", "inspect", "list_networks", diff --git a/podman/system/__init__.py b/podman/system/__init__.py index f42632fc..6fc4de52 100644 --- a/podman/system/__init__.py +++ b/podman/system/__init__.py @@ -1,7 +1,7 @@ """Provide system level information for the Podman service.""" -from http import HTTPStatus import json import logging +from http import HTTPStatus import podman.errors as errors diff --git a/podman/tests/unit/containers/__init__.py b/podman/tests/unit/containers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/podman/tests/unit/containers/test_containers.py b/podman/tests/unit/containers/test_containers.py new file mode 100644 index 00000000..b5b5a81c --- /dev/null +++ b/podman/tests/unit/containers/test_containers.py @@ -0,0 +1,46 @@ +"""podman.containers unit tests""" + +import unittest +import urllib.parse +from unittest import mock + +import podman.containers +import podman.errors +import podman.system + + +class TestContainers(unittest.TestCase): + """Test the containers calls.""" + + def setUp(self): + super().setUp() + self.request = mock.MagicMock() + self.response = mock.MagicMock() + self.request.return_value = self.response + self.api = mock.MagicMock() + self.api.get = self.request + self.api.post = self.request + self.api.delete = self.request + self.api.quote = urllib.parse.quote + + def test_list_containers(self): + """test list call""" + mock_read = mock.MagicMock() + mock_read.return_value = b'[{"Id": "foo"}]' + self.response.status = 200 + self.response.read = mock_read + expected = [{"Id": "foo"}] + ret = podman.containers.list_containers(self.api) + self.assertEqual(ret, expected) + self.request.assert_called_once_with("/containers/json", {}) + + def test_list_containers_all(self): + """test list call""" + mock_read = mock.MagicMock() + mock_read.return_value = b'[{"Id": "foo"}]' + self.response.status = 200 + self.response.read = mock_read + expected = [{"Id": "foo"}] + ret = podman.containers.list_containers(self.api, True) + self.assertEqual(ret, expected) + self.request.assert_called_once_with("/containers/json", {"all": True}) diff --git a/podman/tests/unit/images/test_images.py b/podman/tests/unit/images/test_images.py index b718f116..1587d12a 100644 --- a/podman/tests/unit/images/test_images.py +++ b/podman/tests/unit/images/test_images.py @@ -15,9 +15,9 @@ """podman.images unit tests""" import unittest - -from unittest import mock import urllib.parse +from unittest import mock + import podman.errors import podman.system @@ -37,7 +37,7 @@ def setUp(self): self.api.quote = urllib.parse.quote def test_list_images(self): - """test version call""" + """test list call""" mock_read = mock.MagicMock() mock_read.return_value = b'[{"Id": "foo"}]' self.response.status = 200 @@ -45,7 +45,7 @@ def test_list_images(self): expected = [{"Id": "foo"}] ret = podman.images.list_images(self.api) self.assertEqual(ret, expected) - self.request.assert_called_once_with('/images/json') + self.request.assert_called_once_with("/images/json") def test_inspect(self): """test inspect call""" @@ -54,20 +54,20 @@ def test_inspect(self): self.response.status = 200 self.response.read = mock_read expected = {"Id": "foo"} - ret = podman.images.inspect(self.api, 'foo') + ret = podman.images.inspect(self.api, "foo") self.assertEqual(ret, expected) - self.request.assert_called_once_with('/images/foo/json') + self.request.assert_called_once_with("/images/foo/json") def test_image_exists(self): """test exists call""" self.response.status = 204 - self.assertTrue(podman.images.image_exists(self.api, 'foo')) - self.request.assert_called_once_with('/images/foo/exists') + self.assertTrue(podman.images.image_exists(self.api, "foo")) + self.request.assert_called_once_with("/images/foo/exists") def test_image_exists_missing(self): """test exists call with missing image""" - self.request.side_effect = podman.errors.NotFoundError('nope') - self.assertFalse(podman.images.image_exists(self.api, 'foo')) + self.request.side_effect = podman.errors.NotFoundError("nope") + self.assertFalse(podman.images.image_exists(self.api, "foo")) def test_remove(self): """test remove call""" @@ -75,10 +75,10 @@ def test_remove(self): mock_read.return_value = b'[{"deleted": "str","untagged": ["str"]}]' self.response.status = 200 self.response.read = mock_read - expected = [{'deleted': 'str', 'untagged': ['str']}] - ret = podman.images.remove(self.api, 'foo') + expected = [{"deleted": "str", "untagged": ["str"]}] + ret = podman.images.remove(self.api, "foo") self.assertEqual(ret, expected) - self.api.delete.assert_called_once_with('/images/foo', {}) + self.api.delete.assert_called_once_with("/images/foo", {}) def test_remove_force(self): """test remove call with force""" @@ -86,37 +86,40 @@ def test_remove_force(self): mock_read.return_value = b'[{"deleted": "str","untagged": ["str"]}]' self.response.status = 200 self.response.read = mock_read - expected = [{'deleted': 'str', 'untagged': ['str']}] - ret = podman.images.remove(self.api, 'foo', True) + expected = [{"deleted": "str", "untagged": ["str"]}] + ret = podman.images.remove(self.api, "foo", True) self.assertEqual(ret, expected) - self.api.delete.assert_called_once_with('/images/foo', {'force': True}) + self.api.delete.assert_called_once_with("/images/foo", {"force": True}) def test_remove_missing(self): """test remove call with missing image""" mock_raise = mock.MagicMock() - mock_raise.side_effect = podman.errors.ImageNotFound('yikes') + mock_raise.side_effect = podman.errors.ImageNotFound("yikes") self.api.raise_not_found = mock_raise - self.request.side_effect = podman.errors.NotFoundError('nope') - self.assertRaises(podman.errors.ImageNotFound, - podman.images.remove, - self.api, - 'foo') + self.request.side_effect = podman.errors.NotFoundError("nope") + self.assertRaises( + podman.errors.ImageNotFound, podman.images.remove, self.api, "foo" + ) def test_tag_image(self): """test tag image""" self.response.status = 201 - self.assertTrue(podman.images.tag_image(self.api, 'foo', 'bar', 'baz')) + self.assertTrue(podman.images.tag_image(self.api, "foo", "bar", "baz")) def test_tag_image_fail(self): """test remove call with missing image""" mock_raise = mock.MagicMock() - mock_raise.side_effect = podman.errors.ImageNotFound('yikes') + mock_raise.side_effect = podman.errors.ImageNotFound("yikes") self.api.raise_image_not_found = mock_raise - self.request.side_effect = podman.errors.NotFoundError('nope') - self.assertRaises(podman.errors.ImageNotFound, - podman.images.tag_image, - self.api, - 'foo', 'bar', 'baz') + self.request.side_effect = podman.errors.NotFoundError("nope") + self.assertRaises( + podman.errors.ImageNotFound, + podman.images.tag_image, + self.api, + "foo", + "bar", + "baz", + ) def test_history(self): """test image history""" @@ -124,18 +127,17 @@ def test_history(self): mock_read.return_value = b'{"Id": "a"}' self.response.status = 200 self.response.read = mock_read - expected = {'Id': 'a'} - ret = podman.images.history(self.api, 'foo') + expected = {"Id": "a"} + ret = podman.images.history(self.api, "foo") self.assertEqual(ret, expected) - self.api.delete.assert_called_once_with('/images/foo/history') + self.api.delete.assert_called_once_with("/images/foo/history") def test_history_missing_image(self): """test history with missing image""" mock_raise = mock.MagicMock() - mock_raise.side_effect = podman.errors.ImageNotFound('yikes') + mock_raise.side_effect = podman.errors.ImageNotFound("yikes") self.api.raise_image_not_found = mock_raise - self.request.side_effect = podman.errors.NotFoundError('nope') - self.assertRaises(podman.errors.ImageNotFound, - podman.images.history, - self.api, - 'foo') + self.request.side_effect = podman.errors.NotFoundError("nope") + self.assertRaises( + podman.errors.ImageNotFound, podman.images.history, self.api, "foo" + ) diff --git a/podman/tests/unit/networks/test_networks.py b/podman/tests/unit/networks/test_networks.py index 80c17f54..3c8e97cf 100644 --- a/podman/tests/unit/networks/test_networks.py +++ b/podman/tests/unit/networks/test_networks.py @@ -14,11 +14,11 @@ # """podman.networks unit tests""" -import unittest - -from unittest import mock import json +import unittest import urllib.parse +from unittest import mock + import podman.errors import podman.networks diff --git a/podman/tests/unit/system/test_system.py b/podman/tests/unit/system/test_system.py index 04836460..30f476ec 100644 --- a/podman/tests/unit/system/test_system.py +++ b/podman/tests/unit/system/test_system.py @@ -15,8 +15,8 @@ """podman.system unit tests""" import unittest - from unittest import mock + import podman.errors import podman.system diff --git a/podman/tests/unit/test_api_connection.py b/podman/tests/unit/test_api_connection.py index 26d6248f..91376fb5 100644 --- a/podman/tests/unit/test_api_connection.py +++ b/podman/tests/unit/test_api_connection.py @@ -14,12 +14,12 @@ # """podman.ApiConnection unit tests""" -import unittest import socket - +import unittest from unittest import mock -from podman import ApiConnection + import podman.errors +from podman import ApiConnection class TestApiConnection(unittest.TestCase): @@ -114,7 +114,7 @@ def test_post(self): headers={}), mock.call('POST', '/test/bar', body='a=b&c=d', headers={ - 'x': 'y', + 'x': 'y', 'content-type': 'application/x-www-form-urlencoded' }),