Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion podman/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Podman service module."""
from podman.api_connection import ApiConnection

__ALL__ = ["ApiConnection"]
__all__ = ["ApiConnection"]
74 changes: 39 additions & 35 deletions podman/api_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,31 @@
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pylint seems unhappy with this for whatever reason, but we can resolve that later in the black usage patch.

): # pylint: disable-msg=W1113
if url is None or not url:
raise ValueError("url is required for service connection.")

super().__init__("localhost", *args, **kwargs)
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

Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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,
)
Expand Down Expand Up @@ -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()
Expand All @@ -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")
Expand Down
18 changes: 18 additions & 0 deletions podman/containers/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
]
1 change: 1 addition & 0 deletions podman/errors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 12 additions & 15 deletions podman/images/__init__.py
Original file line number Diff line number Diff line change
@@ -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)

Expand All @@ -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)

Expand All @@ -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)
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion podman/networks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion podman/system/__init__.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Empty file.
46 changes: 46 additions & 0 deletions podman/tests/unit/containers/test_containers.py
Original file line number Diff line number Diff line change
@@ -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})
Loading