Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-podman for openSUSE:Factory checked in at 2024-11-26 20:57:07 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-podman (Old) and /work/SRC/openSUSE:Factory/.python-podman.new.28523 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-podman" Tue Nov 26 20:57:07 2024 rev:19 rq:1226538 version:5.3.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-podman/python-podman.changes 2024-08-03 20:04:50.675809032 +0200 +++ /work/SRC/openSUSE:Factory/.python-podman.new.28523/python-podman.changes 2024-11-26 20:58:55.461975198 +0100 @@ -1,0 +2,52 @@ +Tue Nov 26 13:55:44 UTC 2024 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- update to 5.3.0: + * [skip-ci] Packit: downstream_package_name for each package key + by @lsm5 in #416 + * Make "images.push" method support "format" parameter by + @milanbalazs in #415 + * Add test of container create with DNS option by @Honny1 in #417 + * Fix podman search flake + update CI VM images by @cevich in + #418 + * Update dependency containers/automation_images to v20240529 by + @renovate in #389 + * Remove wait condition in run() by @inknos in #428 + * Update dependency containers/automation_images to v20240821 by + @renovate in #423 + * domain/networks_manager.py: use specified driver in IPAMConfig + by @jtluka in #429 + * Fix/Disable Pylint R0917 by @inknos in #432 + * Extend the parameters of 'images.load' and 'login' methods by + @milanbalazs in #434 + * Don't use root as default user for exec_run by @aparcar in #431 + * Renovate: Update default assignment by @cevich in #437 + * Packit: enable c9s downstream update by @lsm5 in #440 + * Audit and Update OWNERS file by @baude in #441 + * Update dependency ubuntu to v24 by @renovate in #436 + * Remove the container in case of detach mode by @milanbalazs in + #435 + * Update CI VM images by @inknos in #430 + * Fix cyclic-import by @Honny1 in #450 + * Packit: constrain koji and bodhi jobs to fedora package by + @lsm5 in #442 + * Fix default stderr value of container.logs() to match + documentation. by @MattBelle in #452 + * [skip-ci] RPM: remove conditionals from changelog by @lsm5 in + #453 + * Added stream support to Container.exec_run(). by @MattBelle in + #454 + * Accept integer ports in containers_create.create by @krrhodes + in #447 + * Container.labels now returns an empty dict instead of None. by + @MattBelle in #462 + * Fix typos by @kianmeng in #464 + * fix[docs]: Unindented example code on the index page by + @Mr-Sunglasses in #467 + * Added support for mounting directories through the volume + keyword. by @MattBelle in #460 + * docs: Add Installation and docs in README.md by @Mr-Sunglasses + in #471 + * fix: name filter in images.list() by @Mr-Sunglasses in #468 + * Bump release to 5.3.0 and drop python<3.8 by @inknos in #469 + +------------------------------------------------------------------- Old: ---- podman-5.2.0.tar.gz New: ---- podman-5.3.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-podman.spec ++++++ --- /var/tmp/diff_new_pack.He0usu/_old 2024-11-26 20:58:55.997997544 +0100 +++ /var/tmp/diff_new_pack.He0usu/_new 2024-11-26 20:58:55.997997544 +0100 @@ -26,7 +26,7 @@ %bcond_with test %endif Name: python-podman%{psuffix} -Version: 5.2.0 +Version: 5.3.0 Release: 0 Summary: A library to interact with a Podman server License: Apache-2.0 ++++++ podman-5.2.0.tar.gz -> podman-5.3.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/.cirrus.yml new/podman-py-5.3.0/.cirrus.yml --- old/podman-py-5.2.0/.cirrus.yml 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/.cirrus.yml 2024-11-25 16:27:04.000000000 +0100 @@ -15,7 +15,7 @@ #### Cache-image names to test with (double-quotes around names are critical) #### # Google-cloud VM Images - IMAGE_SUFFIX: "c20240320t153921z-f39f38d13" + IMAGE_SUFFIX: "c20241015t085508z-f40f39d13" FEDORA_CACHE_IMAGE_NAME: "fedora-podman-py-${IMAGE_SUFFIX}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/.github/renovate.json5 new/podman-py-5.3.0/.github/renovate.json5 --- old/podman-py-5.2.0/.github/renovate.json5 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/.github/renovate.json5 2024-11-25 16:27:04.000000000 +0100 @@ -51,5 +51,5 @@ *************************************************/ // Don't leave dep. update. PRs "hanging", assign them to people. - "assignees": ["umohnani8", "cevich"], + "assignees": ["inknos"], } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/.github/workflows/pr.yml new/podman-py-5.3.0/.github/workflows/pr.yml --- old/podman-py-5.2.0/.github/workflows/pr.yml 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/.github/workflows/pr.yml 2024-11-25 16:27:04.000000000 +0100 @@ -4,7 +4,7 @@ jobs: commit: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 # Only check commits on pull requests. if: github.event_name == 'pull_request' steps: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/.packit.yaml new/podman-py-5.3.0/.packit.yaml --- old/podman-py-5.2.0/.packit.yaml 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/.packit.yaml 2024-11-25 16:27:04.000000000 +0100 @@ -2,15 +2,16 @@ # See the documentation for more information: # https://packit.dev/docs/configuration/ -downstream_package_name: python-podman upstream_tag_template: v{version} packages: python-podman-fedora: pkg_tool: fedpkg + downstream_package_name: python-podman specfile_path: rpm/python-podman.spec python-podman-centos: pkg_tool: centpkg + downstream_package_name: python-podman specfile_path: rpm/python-podman.spec python-podman-rhel: specfile_path: rpm/python-podman.spec @@ -63,13 +64,16 @@ packages: [python-podman-centos] dist_git_branches: - c10s + - c9s - job: koji_build trigger: commit + packages: [python-podman-fedora] dist_git_branches: - fedora-all - job: bodhi_update trigger: commit + packages: [python-podman-fedora] dist_git_branches: - fedora-branched # rawhide updates are created automatically diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/Makefile new/podman-py-5.3.0/Makefile --- old/podman-py-5.2.0/Makefile 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/Makefile 2024-11-25 16:27:04.000000000 +0100 @@ -8,7 +8,7 @@ EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD) HEAD ?= HEAD -export PODMAN_VERSION ?= "5.2.0" +export PODMAN_VERSION ?= "5.3.0" .PHONY: podman podman: @@ -24,7 +24,7 @@ .PHONY: tests tests: tox # see tox.ini for environment variable settings - $(PYTHON) -m tox -e pylint,coverage,py36,py38,py39,py310,py311 + $(PYTHON) -m tox -e coverage,py39,py310,py311,py312,py313 .PHONY: unittest unittest: @@ -39,9 +39,9 @@ .PHONY: tox tox: ifeq (, $(shell which dnf)) - brew install python@3.8 python@3.9 python@3.10 python@3.11 + brew install python@3.9 python@3.10 python@3.11 python@3.12 python@3.13 else - -dnf install -y python3 python3.6 python3.8 python3.9 + -dnf install -y python3 python3.9 python3.10 python3.11 python3.12 python3.13 endif # ensure tox is available. It will take care of other testing requirements $(PYTHON) -m pip install --user tox diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/OWNERS new/podman-py-5.3.0/OWNERS --- old/podman-py-5.2.0/OWNERS 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/OWNERS 2024-11-25 16:27:04.000000000 +0100 @@ -1,5 +1,4 @@ approvers: - - baude - edsantiago - giuseppe - jwhonce @@ -7,23 +6,11 @@ - Luap99 - mheon - mwhahaha - - rhatdan - - TomSweeneyRedHat - umohnani8 - vrothberg + - inknos reviewers: - ashley-cui - baude - - cdoern - - edsantiago - - giuseppe - - jwhonce - - lsm5 - - Luap99 - - mheon - - mwhahaha - rhatdan - TomSweeneyRedHat - - umohnani8 - - vrothberg - - inknos diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/README.md new/podman-py-5.3.0/README.md --- old/podman-py-5.2.0/README.md 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/README.md 2024-11-25 16:27:04.000000000 +0100 @@ -4,6 +4,23 @@ This python package is a library of bindings to use the RESTful API of [Podman](https://github.com/containers/podman). It is currently under development and contributors are welcome! +## Installation + +<div class="termy"> + +```console +pip install podman +``` + +</div> + +--- + +**Documentation**: <a href="https://podman-py.readthedocs.io/en/latest/" target="_blank">https://podman-py.readthedocs.io/en/latest/</a> + +**Source Code**: <a href="https://github.com/containers/podman-py" target="_blank">https://github.com/containers/podman-py</a> + +--- ## Dependencies diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/docs/source/index.rst new/podman-py-5.3.0/docs/source/index.rst --- old/podman-py-5.2.0/docs/source/index.rst 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/docs/source/index.rst 2024-11-25 16:27:04.000000000 +0100 @@ -34,13 +34,14 @@ .. code-block:: python :linenos: - import podman + import podman + + with podman.PodmanClient() as client: + if client.ping(): + images = client.images.list() + for image in images: + print(image.id) - with podman.PodmanClient() as client: - if client.ping(): - images = client.images.list() - for image in images: - print(image.id) .. toctree:: :caption: Podman Client @@ -73,4 +74,4 @@ * :ref:`genindex` * :ref:`modindex` -* :ref:`search` +* :ref:`search` \ No newline at end of file diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/__init__.py new/podman-py-5.3.0/podman/api/__init__.py --- old/podman-py-5.2.0/podman/api/__init__.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/__init__.py 2024-11-25 16:27:04.000000000 +0100 @@ -1,9 +1,8 @@ """Tools for connecting to a Podman service.""" -import re - from podman.api.cached_property import cached_property from podman.api.client import APIClient +from podman.api.api_versions import VERSION, COMPATIBLE_VERSION from podman.api.http_utils import prepare_body, prepare_filters from podman.api.parse_utils import ( decode_header, @@ -15,24 +14,9 @@ stream_helper, ) from podman.api.tar_utils import create_tar, prepare_containerfile, prepare_containerignore -from .. import version DEFAULT_CHUNK_SIZE = 2 * 1024 * 1024 - -def _api_version(release: str, significant: int = 3) -> str: - """Return API version removing any additional identifiers from the release version. - - This is a simple lexicographical parsing, no semantics are applied, e.g. semver checking. - """ - items = re.split(r"\.|-|\+", release) - parts = items[0:significant] - return ".".join(parts) - - -VERSION: str = _api_version(version.__version__) -COMPATIBLE_VERSION: str = _api_version(version.__compatible_version__, 2) - try: from typing import Literal except (ImportError, ModuleNotFoundError): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/api_versions.py new/podman-py-5.3.0/podman/api/api_versions.py --- old/podman-py-5.2.0/podman/api/api_versions.py 1970-01-01 01:00:00.000000000 +0100 +++ new/podman-py-5.3.0/podman/api/api_versions.py 2024-11-25 16:27:04.000000000 +0100 @@ -0,0 +1,18 @@ +"""Constants API versions""" + +import re +from .. import version + + +def _api_version(release: str, significant: int = 3) -> str: + """Return API version removing any additional identifiers from the release version. + + This is a simple lexicographical parsing, no semantics are applied, e.g. semver checking. + """ + items = re.split(r"\.|-|\+", release) + parts = items[0:significant] + return ".".join(parts) + + +VERSION: str = _api_version(version.__version__) +COMPATIBLE_VERSION: str = _api_version(version.__compatible_version__, 2) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/client.py new/podman-py-5.3.0/podman/api/client.py --- old/podman-py-5.2.0/podman/api/client.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/client.py 2024-11-25 16:27:04.000000000 +0100 @@ -8,7 +8,7 @@ import requests from requests.adapters import HTTPAdapter -from podman import api # pylint: disable=cyclic-import +from podman.api.api_versions import VERSION, COMPATIBLE_VERSION from podman.api.ssh import SSHAdapter from podman.api.uds import UDSAdapter from podman.errors import APIError, NotFound @@ -102,7 +102,7 @@ use_ssh_client=True, max_pool_size=None, **kwargs, - ): # pylint: disable=unused-argument + ): # pylint: disable=unused-argument,too-many-positional-arguments """Instantiate APIClient object. Args: @@ -158,9 +158,9 @@ else: assert False, "APIClient.supported_schemes changed without adding a branch here." - self.version = version or api.VERSION + self.version = version or VERSION self.path_prefix = f"/v{self.version}/libpod/" - self.compatible_version = kwargs.get("compatible_version", api.COMPATIBLE_VERSION) + self.compatible_version = kwargs.get("compatible_version", COMPATIBLE_VERSION) self.compatible_prefix = f"/v{self.compatible_version}/" self.timeout = timeout @@ -199,6 +199,7 @@ def delete( self, path: Union[str, bytes], + *, params: Union[None, bytes, Mapping[str, str]] = None, headers: Optional[Mapping[str, str]] = None, timeout: _Timeout = None, @@ -233,6 +234,7 @@ def get( self, path: Union[str, bytes], + *, params: Union[None, bytes, Mapping[str, List[str]]] = None, headers: Optional[Mapping[str, str]] = None, timeout: _Timeout = None, @@ -267,6 +269,7 @@ def head( self, path: Union[str, bytes], + *, params: Union[None, bytes, Mapping[str, str]] = None, headers: Optional[Mapping[str, str]] = None, timeout: _Timeout = None, @@ -301,6 +304,7 @@ def post( self, path: Union[str, bytes], + *, params: Union[None, bytes, Mapping[str, str]] = None, data: _Data = None, headers: Optional[Mapping[str, str]] = None, @@ -320,6 +324,7 @@ Keyword Args: compatible: Will override the default path prefix with compatible prefix + verify: Whether to verify TLS certificates. Raises: APIError: when service returns an error @@ -338,6 +343,7 @@ def put( self, path: Union[str, bytes], + *, params: Union[None, bytes, Mapping[str, str]] = None, data: _Data = None, headers: Optional[Mapping[str, str]] = None, @@ -376,6 +382,7 @@ self, method: str, path: Union[str, bytes], + *, data: _Data = None, params: Union[None, bytes, Mapping[str, str]] = None, headers: Optional[Mapping[str, str]] = None, @@ -394,6 +401,7 @@ Keyword Args: compatible: Will override the default path prefix with compatible prefix + verify: Whether to verify TLS certificates. Raises: APIError: when service returns an error @@ -409,10 +417,10 @@ path = path.lstrip("/") # leading / makes urljoin crazy... - # TODO should we have an option for HTTPS support? + scheme = "https" if kwargs.get("verify", None) else "http" # Build URL for operation from base_url uri = urllib.parse.ParseResult( - "http", + scheme, self.base_url.netloc, urllib.parse.urljoin(path_prefix, path), self.base_url.params, @@ -429,6 +437,7 @@ data=data, headers=(headers or {}), stream=stream, + verify=kwargs.get("verify", None), **timeout_kw, ) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/output_utils.py new/podman-py-5.3.0/podman/api/output_utils.py --- old/podman-py-5.2.0/podman/api/output_utils.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/output_utils.py 2024-11-25 16:27:04.000000000 +0100 @@ -46,4 +46,4 @@ # Update data for next frame data_bytes = data_bytes[payload_size:] - return stdout, stderr + return stdout or None, stderr or None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/parse_utils.py new/podman-py-5.3.0/podman/api/parse_utils.py --- old/podman-py-5.2.0/podman/api/parse_utils.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/parse_utils.py 2024-11-25 16:27:04.000000000 +0100 @@ -8,6 +8,7 @@ from typing import Any, Dict, Iterator, Optional, Tuple, Union from requests import Response +from .output_utils import demux_output def parse_repository(name: str) -> Tuple[str, Optional[str]]: @@ -79,11 +80,13 @@ yield response.content[frame_begin:frame_end] -def stream_frames(response: Response) -> Iterator[bytes]: +def stream_frames( + response: Response, demux: bool = False +) -> Iterator[Union[bytes, Tuple[bytes, bytes]]]: """Returns each frame from multiplexed streamed payload. - Notes: - The stdout and stderr frames are undifferentiated as they are returned. + If ``demux`` then output will be tuples where the first position is ``STDOUT`` and the second + is ``STDERR``. """ while True: header = response.raw.read(8) @@ -95,6 +98,10 @@ continue data = response.raw.read(frame_length) + + if demux: + data = demux_output(header + data) + if not data: return yield data diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/ssh.py new/podman-py-5.3.0/podman/api/ssh.py --- old/podman-py-5.2.0/podman/api/ssh.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/ssh.py 2024-11-25 16:27:04.000000000 +0100 @@ -250,7 +250,7 @@ max_retries: int = DEFAULT_RETRIES, pool_block: int = DEFAULT_POOLBLOCK, **kwargs, - ): + ): # pylint: disable=too-many-positional-arguments """Initialize SSHAdapter. Args: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/typing_extensions.py new/podman-py-5.3.0/podman/api/typing_extensions.py --- old/podman-py-5.2.0/podman/api/typing_extensions.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/typing_extensions.py 2024-11-25 16:27:04.000000000 +0100 @@ -22,7 +22,7 @@ # After PEP 560, internal typing API was substantially reworked. # This is especially important for Protocol class which uses internal APIs -# quite extensivelly. +# quite extensively. PEP_560 = sys.version_info[:3] >= (3, 7, 0) if PEP_560: @@ -2097,7 +2097,7 @@ return len(name) > 4 and name.startswith('__') and name.endswith('__') # Prior to Python 3.7 types did not have `copy_with`. A lot of the equality - # checks, argument expansion etc. are done on the _subs_tre. As a result we + # checks, argument expansion etc. are done on the _subs_tree. As a result we # can't provide a get_type_hints function that strips out annotations. class AnnotatedMeta(typing.GenericMeta): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/api/uds.py new/podman-py-5.3.0/podman/api/uds.py --- old/podman-py-5.2.0/podman/api/uds.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/api/uds.py 2024-11-25 16:27:04.000000000 +0100 @@ -137,7 +137,7 @@ max_retries=DEFAULT_RETRIES, pool_block=DEFAULT_POOLBLOCK, **kwargs, - ): + ): # pylint: disable=too-many-positional-arguments """Initialize UDSAdapter. Args: @@ -153,7 +153,7 @@ Examples: requests.Session.mount( - "http://", UDSAdapater("http+unix:///run/user/1000/podman/podman.sock")) + "http://", UDSAdapter("http+unix:///run/user/1000/podman/podman.sock")) """ self.poolmanager: Optional[UDSPoolManager] = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/client.py new/podman-py-5.3.0/podman/client.py --- old/podman-py-5.2.0/podman/client.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/client.py 2024-11-25 16:27:04.000000000 +0100 @@ -82,6 +82,7 @@ @classmethod def from_env( cls, + *, version: str = "auto", timeout: Optional[int] = None, max_pool_size: Optional[int] = None, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/containers.py new/podman-py-5.3.0/podman/domain/containers.py --- old/podman-py-5.2.0/podman/domain/containers.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/containers.py 2024-11-25 16:27:04.000000000 +0100 @@ -42,11 +42,15 @@ @property def labels(self): """dict[str, str]: Returns labels associated with container.""" + labels = None with suppress(KeyError): + # Container created from ``list()`` operation if "Labels" in self.attrs: - return self.attrs["Labels"] - return self.attrs["Config"]["Labels"] - return {} + labels = self.attrs["Labels"] + # Container created from ``get()`` operation + else: + labels = self.attrs["Config"].get("Labels", {}) + return labels or {} @property def status(self): @@ -126,10 +130,11 @@ response.raise_for_status() return response.json() - # pylint: disable=too-many-arguments,unused-argument + # pylint: disable=too-many-arguments def exec_run( self, cmd: Union[str, List[str]], + *, stdout: bool = True, stderr: bool = True, stdin: bool = False, @@ -138,11 +143,13 @@ user=None, detach: bool = False, stream: bool = False, - socket: bool = False, + socket: bool = False, # pylint: disable=unused-argument environment: Union[Mapping[str, str], List[str]] = None, workdir: str = None, demux: bool = False, - ) -> Tuple[Optional[int], Union[Iterator[bytes], Any, Tuple[bytes, bytes]]]: + ) -> Tuple[ + Optional[int], Union[Iterator[Union[bytes, Tuple[bytes, bytes]]], Any, Tuple[bytes, bytes]] + ]: """Run given command inside container and return results. Args: @@ -152,10 +159,10 @@ stdin: Attach to stdin. Default: False tty: Allocate a pseudo-TTY. Default: False privileged: Run as privileged. - user: User to execute command as. Default: root + user: User to execute command as. detach: If true, detach from the exec command. Default: False - stream: Stream response data. Default: False + stream: Stream response data. Ignored if ``detach`` is ``True``. Default: False socket: Return the connection socket to allow custom read/write operations. Default: False environment: A dictionary or a List[str] in @@ -165,17 +172,19 @@ demux: Return stdout and stderr separately Returns: - First item is the command response code. - Second item is the requests response content. - If demux is True, the second item is a tuple of - (stdout, stderr). + A tuple of (``response_code``, ``output``). + ``response_code``: + The exit code of the provided command. ``None`` if ``stream``. + ``output``: + If ``stream``, then a generator yielding response chunks. + If ``demux``, then a tuple of (``stdout``, ``stderr``). + Else the response content. Raises: NotImplementedError: method not implemented. APIError: when service reports error """ # pylint: disable-msg=too-many-locals - user = user or "root" if isinstance(environment, dict): environment = [f"{k}={v}" for k, v in environment.items()] data = { @@ -187,18 +196,26 @@ "Env": environment, "Privileged": privileged, "Tty": tty, - "User": user, "WorkingDir": workdir, } + if user: + data["User"] = user + + stream = stream and not detach + # create the exec instance response = self.client.post(f"/containers/{self.name}/exec", data=json.dumps(data)) response.raise_for_status() exec_id = response.json()['Id'] # start the exec instance, this will store command output start_resp = self.client.post( - f"/exec/{exec_id}/start", data=json.dumps({"Detach": detach, "Tty": tty}) + f"/exec/{exec_id}/start", data=json.dumps({"Detach": detach, "Tty": tty}), stream=stream ) start_resp.raise_for_status() + + if stream: + return None, api.stream_frames(start_resp, demux=demux) + # get and return exec information response = self.client.get(f"/exec/{exec_id}/json") response.raise_for_status() @@ -285,7 +302,7 @@ params = { "follow": kwargs.get("follow", kwargs.get("stream", None)), "since": api.prepare_timestamp(kwargs.get("since")), - "stderr": kwargs.get("stderr", None), + "stderr": kwargs.get("stderr", True), "stdout": kwargs.get("stdout", True), "tail": kwargs.get("tail"), "timestamps": kwargs.get("timestamps"), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/containers_create.py new/podman-py-5.3.0/podman/domain/containers_create.py --- old/podman-py-5.2.0/podman/domain/containers_create.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/containers_create.py 2024-11-25 16:27:04.000000000 +0100 @@ -16,6 +16,8 @@ logger = logging.getLogger("podman.containers") +NAMED_VOLUME_PATTERN = re.compile(r'[a-zA-Z0-9][a-zA-Z0-9_.-]*') + class CreateMixin: # pylint: disable=too-few-public-methods """Class providing create method for ContainersManager.""" @@ -174,7 +176,7 @@ pids_limit (int): Tune a container's pids limit. Set -1 for unlimited. platform (str): Platform in the format os[/arch[/variant]]. Only used if the method needs to pull the requested image. - ports (Dict[str, Union[int, Tuple[str, int], List[int], Dict[str, Union[int, Tuple[str, int], List[int]]]]]): Ports to bind inside the container. + ports (Dict[Union[int, str], Union[int, Tuple[str, int], List[int], Dict[str, Union[int, Tuple[str, int], List[int]]]]]): Ports to bind inside the container. The keys of the dictionary are the ports to bind inside the container, either as an integer or a string in the form port/protocol, where the protocol is either @@ -622,6 +624,9 @@ return result for container, host in args.pop("ports", {}).items(): + if isinstance(container, int): + container = str(container) + if "/" in container: container_port, protocol = container.split("/") else: @@ -680,8 +685,21 @@ raise ValueError("'mode' value should be a str") options.append(mode) - volume = {"Name": key, "Dest": value["bind"], "Options": options} - params["volumes"].append(volume) + # The Podman API only supports named volumes through the ``volume`` parameter. Directory + # mounting needs to happen through the ``mounts`` parameter. Luckily the translation + # isn't too complicated so we can just do it for the user if we suspect that the key + # isn't a named volume. + if NAMED_VOLUME_PATTERN.match(key): + volume = {"Name": key, "Dest": value["bind"], "Options": options} + params["volumes"].append(volume) + else: + mount_point = { + "destination": value['bind'], + "options": options, + "source": key, + "type": 'bind', + } + params["mounts"].append(mount_point) for item in args.pop("secrets", []): if isinstance(item, Secret): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/containers_run.py new/podman-py-5.3.0/podman/domain/containers_run.py --- old/podman-py-5.2.0/podman/domain/containers_run.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/containers_run.py 2024-11-25 16:27:04.000000000 +0100 @@ -1,6 +1,7 @@ """Mixin to provide Container run() method.""" import logging +import threading from contextlib import suppress from typing import Generator, Iterator, List, Union @@ -18,6 +19,7 @@ self, image: Union[str, Image], command: Union[str, List[str], None] = None, + *, stdout=True, stderr=False, remove: bool = False, @@ -64,10 +66,21 @@ container = self.create(image=image, command=command, **kwargs) container.start() - container.wait(condition=["running", "exited"]) container.reload() + def remove_container(container_object: Container) -> None: + """ + Wait the container to finish and remove it. + Args: + container_object: Container object + """ + container_object.wait() # Wait for the container to finish + container_object.remove() # Remove the container + if kwargs.get("detach", False): + if remove: + # Start a background thread to remove the container after finishing + threading.Thread(target=remove_container, args=(container,)).start() return container with suppress(KeyError): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/images_manager.py new/podman-py-5.3.0/podman/domain/images_manager.py --- old/podman-py-5.2.0/podman/domain/images_manager.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/images_manager.py 2024-11-25 16:27:04.000000000 +0100 @@ -3,8 +3,10 @@ import io import json import logging +import os import urllib.parse -from typing import Any, Dict, Generator, Iterator, List, Mapping, Optional, Union +from typing import Any, Dict, Iterator, List, Mapping, Optional, Union, Generator +from pathlib import Path import requests from podman import api @@ -14,7 +16,7 @@ from podman.domain.images_build import BuildMixin from podman.domain.manager import Manager from podman.domain.registry_data import RegistryData -from podman.errors import APIError, ImageNotFound +from podman.errors import APIError, ImageNotFound, PodmanError try: from rich.progress import ( @@ -59,10 +61,13 @@ Raises: APIError: when service returns an error """ + filters = kwargs.get("filters", {}).copy() + if name := kwargs.get("name"): + filters["reference"] = name + params = { "all": kwargs.get("all"), - "name": kwargs.get("name"), - "filters": api.prepare_filters(kwargs.get("filters")), + "filters": api.prepare_filters(filters=filters), } response = self.client.get("/images/json", params=params) if response.status_code == requests.codes.not_found: @@ -113,26 +118,51 @@ collection=self, ) - def load(self, data: bytes) -> Generator[Image, None, None]: + def load( + self, data: Optional[bytes] = None, file_path: Optional[os.PathLike] = None + ) -> Generator[bytes, None, None]: """Restore an image previously saved. Args: data: Image to be loaded in tarball format. + file_path: Path of the Tarball. + It works with both str and Path-like objects Raises: - APIError: when service returns an error + APIError: When service returns an error. + PodmanError: When the arguments are not set correctly. """ # TODO fix podman swagger cannot use this header! # headers = {"Content-type": "application/x-www-form-urlencoded"} + # Check that exactly one of the data or file_path is provided + if not data and not file_path: + raise PodmanError("The 'data' or 'file_path' parameter should be set.") + + if data and file_path: + raise PodmanError( + "Only one parameter should be set from 'data' and 'file_path' parameters." + ) + + post_data = data + if file_path: + # Convert to Path if file_path is a string + file_path_object = Path(file_path) + post_data = file_path_object.read_bytes() # Read the tarball file as bytes + + # Make the client request before entering the generator response = self.client.post( - "/images/load", data=data, headers={"Content-type": "application/x-tar"} + "/images/load", data=post_data, headers={"Content-type": "application/x-tar"} ) - response.raise_for_status() + response.raise_for_status() # Catch any errors before proceeding + + def _generator(body: dict) -> Generator[bytes, None, None]: + # Iterate and yield images from response body + for item in body["Names"]: + yield self.get(item) - body = response.json() - for item in body["Names"]: - yield self.get(item) + # Pass the response body to the generator + return _generator(response.json()) def prune( self, filters: Optional[Mapping[str, Any]] = None @@ -206,6 +236,8 @@ destination (str): alternate destination for image. (Podman only) stream (bool): return output as blocking generator. Default: False. tlsVerify (bool): Require TLS verification. + format (str): Manifest type (oci, v2s1, or v2s2) to use when pushing an image. + Default is manifest type of source, with fallbacks. Raises: APIError: when service returns an error @@ -220,6 +252,7 @@ params = { "destination": kwargs.get("destination"), "tlsVerify": kwargs.get("tlsVerify"), + "format": kwargs.get("format"), } name = f'{repository}:{tag}' if tag else repository diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/ipam.py new/podman-py-5.3.0/podman/domain/ipam.py --- old/podman-py-5.2.0/podman/domain/ipam.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/ipam.py 2024-11-25 16:27:04.000000000 +0100 @@ -40,7 +40,7 @@ def __init__( self, - driver: Optional[str] = "default", + driver: Optional[str] = "host-local", pool_configs: Optional[List[IPAMPool]] = None, options: Optional[Mapping[str, Any]] = None, ): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/networks_manager.py new/podman-py-5.3.0/podman/domain/networks_manager.py --- old/podman-py-5.2.0/podman/domain/networks_manager.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/networks_manager.py 2024-11-25 16:27:04.000000000 +0100 @@ -76,6 +76,9 @@ return self.prepare_model(attrs=response.json()) def _prepare_ipam(self, data: Dict[str, Any], ipam: Dict[str, Any]): + if "Driver" in ipam: + data["ipam_options"] = {"driver": ipam["Driver"]} + if "Config" not in ipam: return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/domain/system.py new/podman-py-5.3.0/podman/domain/system.py --- old/podman-py-5.2.0/podman/domain/system.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/domain/system.py 2024-11-25 16:27:04.000000000 +0100 @@ -1,7 +1,7 @@ """SystemManager to provide system level information from Podman service.""" import logging -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Union from podman.api.client import APIClient from podman import api @@ -36,14 +36,18 @@ response.raise_for_status() return response.json() - def login( + def login( # pylint: disable=too-many-arguments,too-many-positional-arguments,unused-argument self, username: str, password: Optional[str] = None, email: Optional[str] = None, registry: Optional[str] = None, - reauth: Optional[bool] = False, # pylint: disable=unused-argument - dockercfg_path: Optional[str] = None, # pylint: disable=unused-argument + reauth: Optional[bool] = False, + dockercfg_path: Optional[str] = None, + auth: Optional[str] = None, + identitytoken: Optional[str] = None, + registrytoken: Optional[str] = None, + tls_verify: Optional[Union[bool, str]] = None, ) -> Dict[str, Any]: """Log into Podman service. @@ -55,6 +59,11 @@ reauth: Ignored: If True, refresh existing authentication. Default: False dockercfg_path: Ignored: Path to custom configuration file. https://quay.io/v2 + auth: TODO: Add description based on the source code of Podman. + identitytoken: IdentityToken is used to authenticate the user and + get an access token for the registry. + registrytoken: RegistryToken is a bearer token to be sent to a registry + tls_verify: Whether to verify TLS certificates. """ payload = { @@ -62,6 +71,9 @@ "password": password, "email": email, "serveraddress": registry, + "auth": auth, + "identitytoken": identitytoken, + "registrytoken": registrytoken, } payload = api.prepare_body(payload) response = self.client.post( @@ -69,6 +81,7 @@ headers={"Content-type": "application/json"}, data=payload, compatible=True, + verify=tls_verify, # Pass tls_verify to the client ) response.raise_for_status() return response.json() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/errors/exceptions.py new/podman-py-5.3.0/podman/errors/exceptions.py --- old/podman-py-5.2.0/podman/errors/exceptions.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/errors/exceptions.py 2024-11-25 16:27:04.000000000 +0100 @@ -115,7 +115,7 @@ command: Union[str, List[str]], image: str, stderr: Optional[Iterable[str]] = None, - ): + ): # pylint: disable=too-many-positional-arguments """Initialize ContainerError. Args: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/__init__.py new/podman-py-5.3.0/podman/tests/__init__.py --- old/podman-py-5.2.0/podman/tests/__init__.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/__init__.py 2024-11-25 16:27:04.000000000 +0100 @@ -3,5 +3,5 @@ # Do not auto-update these from version.py, # as test code should be changed to reflect changes in Podman API versions BASE_SOCK = "unix:///run/api.sock" -LIBPOD_URL = "http://%2Frun%2Fapi.sock/v5.2.0/libpod" +LIBPOD_URL = "http://%2Frun%2Fapi.sock/v5.3.0/libpod" COMPATIBLE_URL = "http://%2Frun%2Fapi.sock/v1.40" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/integration/test_container_create.py new/podman-py-5.3.0/podman/tests/integration/test_container_create.py --- old/podman-py-5.2.0/podman/tests/integration/test_container_create.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/integration/test_container_create.py 2024-11-25 16:27:04.000000000 +0100 @@ -24,7 +24,7 @@ for container in self.containers: container.remove(force=True) - def test_container_volume_mount(self): + def test_container_named_volume_mount(self): with self.subTest("Check volume mount"): volumes = { 'test_bind_1': {'bind': '/mnt/vol1', 'mode': 'rw'}, @@ -52,6 +52,33 @@ for o in other_options: self.assertIn(o, mount.get('Options')) + def test_container_directory_volume_mount(self): + """Test that directories can be mounted with the ``volume`` parameter.""" + with self.subTest("Check bind mount"): + volumes = { + "/etc/hosts": dict(bind="/test_ro", mode='ro'), + "/etc/hosts": dict(bind="/test_rw", mode='rw'), + } + container = self.client.containers.create( + self.alpine_image, command=["cat", "/test_ro", "/test_rw"], volumes=volumes + ) + container_mounts = container.attrs.get('Mounts', {}) + self.assertEqual(len(container_mounts), len(volumes)) + + self.containers.append(container) + + for directory, mount_spec in volumes.items(): + self.assertIn( + f"{directory}:{mount_spec['bind']}:{mount_spec['mode']},rprivate,rbind", + container.attrs.get('HostConfig', {}).get('Binds', list()), + ) + + # check if container can be started and exits with EC == 0 + container.start() + container.wait() + + self.assertEqual(container.attrs.get('State', dict()).get('ExitCode', 256), 0) + def test_container_extra_hosts(self): """Test Container Extra hosts""" extra_hosts = {"host1 host3": "127.0.0.2", "host2": "127.0.0.3"} @@ -142,6 +169,16 @@ '1223/tcp': [{'HostIp': '', 'HostPort': '1235'}], }, }, + { + 'input': { + 2244: 3344, + }, + 'expected_output': { + '2244/tcp': [ + {'HostIp': '', 'HostPort': '3344'}, + ], + }, + }, ] for port_test in port_tests: @@ -157,6 +194,26 @@ ) ) + def test_container_dns_option(self): + expected_dns_opt = ['edns0'] + + container = self.client.containers.create( + self.alpine_image, command=["cat", "/etc/resolv.conf"], dns_opt=expected_dns_opt + ) + self.containers.append(container) + + with self.subTest("Check HostConfig"): + self.assertEqual( + container.attrs.get('HostConfig', {}).get('DnsOptions'), expected_dns_opt + ) + + with self.subTest("Check content of /etc/resolv.conf"): + container.start() + container.wait() + self.assertTrue( + all([opt in b"\n".join(container.logs()).decode() for opt in expected_dns_opt]) + ) + def test_container_healthchecks(self): """Test passing various healthcheck options""" parameters = { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/integration/test_container_exec.py new/podman-py-5.3.0/podman/tests/integration/test_container_exec.py --- old/podman-py-5.2.0/podman/tests/integration/test_container_exec.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/integration/test_container_exec.py 2024-11-25 16:27:04.000000000 +0100 @@ -49,5 +49,75 @@ error_code, output = container.exec_run("ls nonexistent", demux=True) self.assertEqual(error_code, 1) - self.assertEqual(output[0], b'') + self.assertEqual(output[0], None) self.assertEqual(output[1], b"ls: nonexistent: No such file or directory\n") + + def test_container_exec_run_stream(self): + """Test streaming the output from a long running command.""" + container = self.client.containers.create(self.alpine_image, command=["top"], detach=True) + container.start() + + command = [ + '/bin/sh', + '-c', + 'echo 0 ; sleep .1 ; echo 1 ; sleep .1 ; echo 2 ; sleep .1 ;', + ] + error_code, output = container.exec_run(command, stream=True) + + self.assertEqual(error_code, None) + self.assertEqual( + list(output), + [ + b'0\n', + b'1\n', + b'2\n', + ], + ) + + def test_container_exec_run_stream_demux(self): + """Test streaming the output from a long running command with demux enabled.""" + container = self.client.containers.create(self.alpine_image, command=["top"], detach=True) + container.start() + + command = [ + '/bin/sh', + '-c', + 'echo 0 ; >&2 echo 1 ; sleep .1 ; ' + + 'echo 2 ; >&2 echo 3 ; sleep .1 ; ' + + 'echo 4 ; >&2 echo 5 ; sleep .1 ;', + ] + error_code, output = container.exec_run(command, stream=True, demux=True) + + self.assertEqual(error_code, None) + self.assertEqual( + list(output), + [ + (b'0\n', None), + (None, b'1\n'), + (b'2\n', None), + (None, b'3\n'), + (b'4\n', None), + (None, b'5\n'), + ], + ) + + def test_container_exec_run_stream_detach(self): + """Test streaming the output from a long running command with detach enabled.""" + container = self.client.containers.create(self.alpine_image, command=["top"], detach=True) + container.start() + + command = [ + '/bin/sh', + '-c', + 'echo 0 ; sleep .1 ; echo 1 ; sleep .1 ; echo 2 ; sleep .1 ;', + ] + error_code, output = container.exec_run(command, stream=True, detach=True) + + # Detach should make the ``exec_run`` ignore the ``stream`` flag so we will assert against the standard, + # non-streaming behavior. + self.assertEqual(error_code, 0) + # The endpoint should return immediately, before we are able to actually get any of the output. + self.assertEqual( + output, + b'\n', + ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/integration/test_containers.py new/podman-py-5.3.0/podman/tests/integration/test_containers.py --- old/podman-py-5.2.0/podman/tests/integration/test_containers.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/integration/test_containers.py 2024-11-25 16:27:04.000000000 +0100 @@ -43,7 +43,9 @@ with self.subTest("Create from Alpine Image"): container = self.client.containers.create( - self.alpine_image, command=["echo", random_string], ports={'2222/tcp': 3333} + self.alpine_image, + command=["echo", random_string], + ports={'2222/tcp': 3333, 2244: 3344}, ) self.assertIsInstance(container, Container) self.assertGreater(len(container.attrs), 0) @@ -63,6 +65,10 @@ self.assertEqual( "3333", container.attrs["NetworkSettings"]["Ports"]["2222/tcp"][0]["HostPort"] ) + self.assertIn("2244/tcp", container.attrs["NetworkSettings"]["Ports"]) + self.assertEqual( + "3344", container.attrs["NetworkSettings"]["Ports"]["2244/tcp"][0]["HostPort"] + ) file_contents = b"This is an integration test for archive." file_buffer = io.BytesIO(file_contents) @@ -189,6 +195,31 @@ volume_list = self.client.volumes.list() self.assertEqual(len(volume_list), len(existing_volumes)) + def test_container_labels(self): + labels = {'label1': 'value1', 'label2': 'value2'} + labeled_container = self.client.containers.create(self.alpine_image, labels=labels) + unlabeled_container = self.client.containers.create( + self.alpine_image, + ) + + # inspect and list have 2 different schemas so we need to verify that we can + # successfully retrieve the labels on both + try: + # inspect schema + self.assertEqual(labeled_container.labels, labels) + self.assertEqual(unlabeled_container.labels, {}) + + # list schema + for container in self.client.containers.list(all=True): + if container.id == labeled_container.id: + self.assertEqual(container.labels, labels) + elif container.id == unlabeled_container.id: + self.assertEqual(container.labels, {}) + + finally: + labeled_container.remove(v=True) + unlabeled_container.remove(v=True) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/integration/test_images.py new/podman-py-5.3.0/podman/tests/integration/test_images.py --- old/podman-py-5.2.0/podman/tests/integration/test_images.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/integration/test_images.py 2024-11-25 16:27:04.000000000 +0100 @@ -44,7 +44,7 @@ """Test Image CRUD. Notes: - Written to maximize re-use of pulled image. + Written to maximize reuse of pulled image. """ with self.subTest("Pull Alpine Image"): @@ -129,12 +129,9 @@ self.assertTrue("alpine" in str(repositories_content)) def test_search(self): - actual = self.client.images.search("alpine", filters={"is-official": True}) - self.assertEqual(len(actual), 1) - self.assertEqual(actual[0]["Official"], "[OK]") - - actual = self.client.images.search("alpine", listTags=True) - self.assertIsNotNone(actual[0]["Tag"]) + # N/B: This is an infrequently used feature, that tends to flake a lot. + # Just check that it doesn't throw an exception and move on. + self.client.images.search("alpine") @unittest.skip("Needs Podman 3.1.0") def test_corrupt_load(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/unit/test_containersmanager.py new/podman-py-5.3.0/podman/tests/unit/test_containersmanager.py --- old/podman-py-5.2.0/podman/tests/unit/test_containersmanager.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/unit/test_containersmanager.py 2024-11-25 16:27:04.000000000 +0100 @@ -1,3 +1,4 @@ +import json import unittest try: @@ -7,7 +8,7 @@ # Python < 3.10 from collections import Iterator -from unittest.mock import DEFAULT, patch +from unittest.mock import ANY, DEFAULT, patch, MagicMock import requests_mock @@ -213,6 +214,49 @@ with self.assertRaises(ImageNotFound): self.client.containers.create("fedora", "/usr/bin/ls", cpu_count=9999) + @requests_mock.Mocker() + def test_create_parse_host_port(self, mock): + mock_response = MagicMock() + mock_response.json = lambda: { + "Id": "87e1325c82424e49a00abdd4de08009eb76c7de8d228426a9b8af9318ced5ecd", + "Size": 1024, + } + self.client.containers.client.post = MagicMock(return_value=mock_response) + mock.get( + tests.LIBPOD_URL + + "/containers/87e1325c82424e49a00abdd4de08009eb76c7de8d228426a9b8af9318ced5ecd/json", + json=FIRST_CONTAINER, + ) + + port_str = {'2233': 3333} + port_str_protocol = {'2244/tcp': 3344} + port_int = {2255: 3355} + ports = {**port_str, **port_str_protocol, **port_int} + self.client.containers.create("fedora", "/usr/bin/ls", ports=ports) + + self.client.containers.client.post.assert_called() + expected_ports = [ + { + 'container_port': 2233, + 'host_port': 3333, + 'protocol': 'tcp', + }, + { + 'container_port': 2244, + 'host_port': 3344, + 'protocol': 'tcp', + }, + { + 'container_port': 2255, + 'host_port': 3355, + 'protocol': 'tcp', + }, + ] + actual_ports = json.loads(self.client.containers.client.post.call_args[1]['data'])[ + 'portmappings' + ] + self.assertEqual(expected_ports, actual_ports) + def test_create_unsupported_key(self): with self.assertRaises(TypeError) as e: self.client.containers.create("fedora", "/usr/bin/ls", blkio_weight=100.0) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/tests/unit/test_imagesmanager.py new/podman-py-5.3.0/podman/tests/unit/test_imagesmanager.py --- old/podman-py-5.2.0/podman/tests/unit/test_imagesmanager.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/tests/unit/test_imagesmanager.py 2024-11-25 16:27:04.000000000 +0100 @@ -1,5 +1,6 @@ import types import unittest +from unittest.mock import mock_open, patch try: # Python >= 3.10 @@ -13,7 +14,7 @@ from podman import PodmanClient, tests from podman.domain.images import Image from podman.domain.images_manager import ImagesManager -from podman.errors import APIError, ImageNotFound +from podman.errors import APIError, ImageNotFound, PodmanError FIRST_IMAGE = { "Id": "sha256:326dd9d7add24646a325e8eaa82125294027db2332e49c5828d96312c5d773ab", @@ -320,6 +321,37 @@ @requests_mock.Mocker() def test_load(self, mock): + with self.assertRaises(PodmanError): + self.client.images.load() + + with self.assertRaises(PodmanError): + self.client.images.load(b'data', b'file_path') + + with self.assertRaises(PodmanError): + self.client.images.load(data=b'data', file_path=b'file_path') + + # Patch Path.read_bytes to mock the file reading behavior + with patch("pathlib.Path.read_bytes", return_value=b"mock tarball data"): + mock.post( + tests.LIBPOD_URL + "/images/load", + json={"Names": ["quay.io/fedora:latest"]}, + ) + mock.get( + tests.LIBPOD_URL + "/images/quay.io%2ffedora%3Alatest/json", + json=FIRST_IMAGE, + ) + + # 3a. Test the case where only 'file_path' is provided + gntr = self.client.images.load(file_path="mock_file.tar") + self.assertIsInstance(gntr, types.GeneratorType) + + report = list(gntr) + self.assertEqual(len(report), 1) + self.assertEqual( + report[0].id, + "sha256:326dd9d7add24646a325e8eaa82125294027db2332e49c5828d96312c5d773ab", + ) + mock.post( tests.LIBPOD_URL + "/images/load", json={"Names": ["quay.io/fedora:latest"]}, @@ -557,6 +589,84 @@ images[1].id, "c4b16966ecd94ffa910eab4e630e24f259bf34a87e924cd4b1434f267b0e354e" ) + @requests_mock.Mocker() + def test_list_with_name_parameter(self, mock): + """Test that name parameter is correctly converted to a reference filter""" + mock.get( + tests.LIBPOD_URL + "/images/json?filters=%7B%22reference%22%3A+%5B%22fedora%22%5D%7D", + json=[FIRST_IMAGE], + ) + + images = self.client.images.list(name="fedora") + + self.assertEqual(len(images), 1) + self.assertIsInstance(images[0], Image) + self.assertEqual(images[0].tags, ["fedora:latest", "fedora:33"]) + + @requests_mock.Mocker() + def test_list_with_name_and_existing_filters(self, mock): + """Test that name parameter works alongside other filters""" + mock.get( + tests.LIBPOD_URL + + "/images/json?filters=%7B%22dangling%22%3A+%5B%22True%22%5D%2C+%22reference%22%3A+%5B%22fedora%22%5D%7D", + json=[FIRST_IMAGE], + ) + + images = self.client.images.list(name="fedora", filters={"dangling": True}) + + self.assertEqual(len(images), 1) + self.assertIsInstance(images[0], Image) + + @requests_mock.Mocker() + def test_list_with_name_overrides_reference_filter(self, mock): + """Test that name parameter takes precedence over existing reference filter""" + mock.get( + tests.LIBPOD_URL + "/images/json?filters=%7B%22reference%22%3A+%5B%22fedora%22%5D%7D", + json=[FIRST_IMAGE], + ) + + # The name parameter should override the reference filter + images = self.client.images.list( + name="fedora", filters={"reference": "ubuntu"} # This should be overridden + ) + + self.assertEqual(len(images), 1) + self.assertIsInstance(images[0], Image) + + @requests_mock.Mocker() + def test_list_with_all_and_name(self, mock): + """Test that all parameter works alongside name filter""" + mock.get( + tests.LIBPOD_URL + + "/images/json?all=true&filters=%7B%22reference%22%3A+%5B%22fedora%22%5D%7D", + json=[FIRST_IMAGE], + ) + + images = self.client.images.list(all=True, name="fedora") + + self.assertEqual(len(images), 1) + self.assertIsInstance(images[0], Image) + + @requests_mock.Mocker() + def test_list_with_empty_name(self, mock): + """Test that empty name parameter doesn't add a reference filter""" + mock.get(tests.LIBPOD_URL + "/images/json", json=[FIRST_IMAGE]) + + images = self.client.images.list(name="") + + self.assertEqual(len(images), 1) + self.assertIsInstance(images[0], Image) + + @requests_mock.Mocker() + def test_list_with_none_name(self, mock): + """Test that None name parameter doesn't add a reference filter""" + mock.get(tests.LIBPOD_URL + "/images/json", json=[FIRST_IMAGE]) + + images = self.client.images.list(name=None) + + self.assertEqual(len(images), 1) + self.assertIsInstance(images[0], Image) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/podman/version.py new/podman-py-5.3.0/podman/version.py --- old/podman-py-5.2.0/podman/version.py 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/podman/version.py 2024-11-25 16:27:04.000000000 +0100 @@ -1,4 +1,4 @@ """Version of PodmanPy.""" -__version__ = "5.2.0" +__version__ = "5.3.0" __compatible_version__ = "1.40" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/pyproject.toml new/podman-py-5.3.0/pyproject.toml --- old/podman-py-5.2.0/pyproject.toml 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/pyproject.toml 2024-11-25 16:27:04.000000000 +0100 @@ -2,7 +2,7 @@ line-length = 100 skip-string-normalization = true preview = true -target-version = ["py36"] +target-version = ["py39"] include = '\.pyi?$' exclude = ''' /( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/rpm/python-podman.spec new/podman-py-5.3.0/rpm/python-podman.spec --- old/podman-py-5.2.0/rpm/python-podman.spec 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/rpm/python-podman.spec 2024-11-25 16:27:04.000000000 +0100 @@ -100,9 +100,4 @@ %doc README.md %changelog -%if %{defined autochangelog} %autochangelog -%else -* Mon May 01 2023 RH Container Bot <rhcontainer...@fedoraproject.org> -- Placeholder changelog for envs that are not autochangelog-ready -%endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/setup.cfg new/podman-py-5.3.0/setup.cfg --- old/podman-py-5.2.0/setup.cfg 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/setup.cfg 2024-11-25 16:27:04.000000000 +0100 @@ -1,6 +1,6 @@ [metadata] name = podman -version = 5.2.0 +version = 5.3.0 author = Brent Baude, Jhon Honce, Urvashi Mohnani, Nicola Sella author_email = jho...@redhat.com description = Bindings for Podman RESTful API @@ -19,19 +19,17 @@ License :: OSI Approved :: Apache Software License Operating System :: OS Independent Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Topic :: Software Development :: Libraries :: Python Modules keywords = podman, libpod [options] include_package_data = True -python_requires = >=3.6 +python_requires = >=3.9 test_suite = # Any changes should be copied into pyproject.toml install_requires = diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.2.0/tox.ini new/podman-py-5.3.0/tox.ini --- old/podman-py-5.2.0/tox.ini 2024-08-02 15:18:25.000000000 +0200 +++ new/podman-py-5.3.0/tox.ini 2024-11-25 16:27:04.000000000 +0100 @@ -1,6 +1,6 @@ [tox] minversion = 3.2.0 -envlist = pylint,coverage,py36,py38,py39,py310,py311,py312 +envlist = pylint,coverage,py39,py310,py311,py312,py313 ignore_basepython_conflict = true [testenv]