Skip to content

Commit

Permalink
Update Container and ContainersManager
Browse files Browse the repository at this point in the history
* Add unit and integration tests
* Refactor api.prepare_body to recusively cleanup dictionary
* Remove logging setup from PodmanClient() caller will be responsibl.
  Python logging best practice. Loggers used: podman, podman.images,
  podman.containers, etc
* Cleaned up code using dict.get() with None default
* Integration tests now create logging events for Podman service output
  rather than writing to a file

Signed-off-by: Jhon Honce <[email protected]>
  • Loading branch information
jwhonce committed Mar 27, 2021
1 parent 75870f5 commit f367a57
Show file tree
Hide file tree
Showing 24 changed files with 1,114 additions and 478 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ lint:
.PHONY: tests
tests:
DEBUG=1 coverage run -m unittest discover -s podman/tests
coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* --omit=/usr/lib/*
coverage report -m --skip-covered --fail-under=80 --omit=./podman/tests/* --omit=.tox/* \
--omit=/usr/lib/* --omit=*/lib/python*

.PHONY: unittest
unittest:
Expand Down
10 changes: 2 additions & 8 deletions podman/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
"""Tools for connecting to a Podman service."""
from podman.api.client import APIClient
from podman.api.parse_utils import (
decode_header,
parse_repository,
prepare_body,
prepare_cidr,
prepare_timestamp,
)
from podman.api.http_utils import prepare_body, prepare_filters
from podman.api.parse_utils import decode_header, parse_repository, prepare_cidr, prepare_timestamp
from podman.api.tar_utils import create_tar, prepare_containerfile, prepare_dockerignore
from podman.api.url_utils import prepare_filters

from . import version

Expand Down
91 changes: 91 additions & 0 deletions podman/api/http_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Utility functions for working with URL's."""
import collections.abc
import json
from typing import Dict, List, Mapping, Optional, Union, MutableMapping, Any


def prepare_filters(filters: Union[str, List[str], Mapping[str, str]]) -> Optional[str]:
"""Returns filters as an URL quoted JSON Dict[str, List[Any]]."""

if filters is None or len(filters) == 0:
return None

criteria: Dict[str, List[str]] = dict()
if isinstance(filters, str):
_format_string(filters, criteria)
elif isinstance(filters, collections.abc.Mapping):
_format_dict(filters, criteria)
else:
_format_list(filters, criteria)

if len(criteria) == 0:
return None

return json.dumps(criteria, sort_keys=True)


def _format_list(filters, criteria):
for item in filters:
if item is None:
continue

key, value = item.split("=", 1)
if key in criteria:
criteria[key].append(value)
else:
criteria[key] = [value]


def _format_dict(filters, criteria):
for key, value in filters.items():
if value is None:
continue
value = str(value)

if key in criteria:
criteria[key].append(value)
else:
criteria[key] = [value]


def _format_string(filters, criteria):
key, value = filters.split("=", 1)
criteria[key] = [value]


def prepare_body(body: MutableMapping[str, Any]) -> str:
"""Returns JSON payload to be uploaded to server.
Notes:
Values of None and empty Iterables are removed, False and zero-values are retained.
"""
if body is None:
return ""

body = _filter_values(body)
return json.dumps(body, sort_keys=True)


def _filter_values(mapping: Mapping[str, Any]) -> Dict[str, Any]:
"""Returns a canonical dictionary with values == None or empty Iterables removed."""
canonical = dict()
for key, value in mapping.items():
# quick filter if possible...
if value is None or (isinstance(value, collections.abc.Sized) and len(value) <= 0):
continue

# depending on type we need details...
if isinstance(value, collections.abc.MutableMapping):
proposal = _filter_values(value)
elif isinstance(value, collections.abc.Set):
# Convert set() to list() to satisfy json encoder
proposal = [i for i in list(value) if i is not None]
elif isinstance(value, collections.abc.Sequence) and not isinstance(value, str):
proposal = [i for i in value if i is not None]
else:
proposal = value

if proposal not in (None, str(), list(), dict()):
canonical[key] = proposal

return canonical
13 changes: 1 addition & 12 deletions podman/api/parse_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import ipaddress
import json
from datetime import datetime
from typing import Any, Dict, MutableMapping, Optional, Tuple, Union
from typing import Any, Dict, Optional, Tuple, Union


def parse_repository(name: str) -> Tuple[str, Optional[str]]:
Expand Down Expand Up @@ -36,17 +36,6 @@ def decode_header(value: Optional[str]) -> Dict[str, Any]:
return json.loads(text)


def prepare_body(body: MutableMapping[str, Any]) -> str:
"""Strip out any items without a value."""
if body is None:
return ""

targets = {k: v for (k, v) in body.items() if v is None}
for key in targets:
del body[key]
return json.dumps(body)


def prepare_timestamp(value: Union[datetime, int, None]) -> Optional[int]:
"""Returns a UTC UNIX timestamp from given input."""
if value is None:
Expand Down
52 changes: 0 additions & 52 deletions podman/api/url_utils.py

This file was deleted.

11 changes: 2 additions & 9 deletions podman/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from podman.domain.volumes import VolumesManager
from podman.tlsconfig import TLSConfig

logger = logging.getLogger("podman")


class PodmanClient:
"""Create client connection to Podman service"""
Expand All @@ -28,7 +30,6 @@ def __init__(
credstore_env: Optional[Mapping[str, str]] = None,
use_ssh_client: bool = False,
max_pool_size: int = 5,
**kwargs,
) -> None:
"""Instantiate PodmanClient object
Expand All @@ -42,15 +43,7 @@ def __init__(
credstore_env: Dict containing environment for credential store
use_ssh_client: Use system ssh agent rather than ssh module. Default:False
max_pool_size: Number of connections to save in pool
log_level (int): Threshold for logging events. Valid values: logging.CRITICAL,
logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG.
Default: logging.WARNING
"""
log_level = kwargs.get("log_level", logging.WARNING)
if logging.DEBUG < log_level > logging.CRITICAL:
raise ValueError(f"Invalid Logging {logging.getLevelName(log_level)}")
logging.basicConfig(level=log_level)

if not base_url:
uid = os.geteuid()
if uid == 0:
Expand Down
Loading

0 comments on commit f367a57

Please sign in to comment.