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-08-03 20:04:43 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-podman (Old) and /work/SRC/openSUSE:Factory/.python-podman.new.7232 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-podman" Sat Aug 3 20:04:43 2024 rev:18 rq:1191231 version:5.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-podman/python-podman.changes 2024-03-25 21:19:23.790095387 +0100 +++ /work/SRC/openSUSE:Factory/.python-podman.new.7232/python-podman.changes 2024-08-03 20:04:50.675809032 +0200 @@ -1,0 +2,37 @@ +Fri Aug 2 14:04:13 UTC 2024 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- update to 5.2.0: + * Fix the TypeError exception in the images.prune method by + @milanbalazs in #412 + * Bump version to 5.2.0 by @inknos in #414 + +------------------------------------------------------------------- +Fri Aug 2 13:53:22 UTC 2024 - Johannes Kastl <opensuse_buildserv...@ojkastl.de> + +- update to 5.1.0: + * Fix dns_option typo by @robbmanes in #386 + * Fixes encoding of X-Registry-auth HTTP Header value from Base64 + to url_safe Base64 by @apozsuse in #385 + * Include py.typed marker file. by @jonded94 in #381 + * [skip-ci] Packit: enable c10s downstream sync by @lsm5 in #388 + * ignore proxies from the env vars when using UNIX Domain Sockets + by @eighthave in #391 + * Fix Pylint E0606 for undefined variable after else by @inknos + in #392 + * [skip-ci] Packit: use default update_release behavior by @lsm5 + in #393 + * Update OWNERS by @jwhonce in #394 + * Fix README TypeError when one container is running by @inknos + in #395 + * Remove Fedora release number from task names by @cevich in #396 + * Add python 3.12 support and remove python xdg by @inknos in + #401 + * Update index.rst Client -> PodmanClient by @jwoehr in #405 + * Implementing the functionality of the 'named' argument of the + 'Image.save' method by @milanbalazs in #406 + * Fix the locally non-existent image fails with AttributeError by + @milanbalazs in #408 + * Enable demux option in exec_run by @inknos in #410 + * Bump version to 5.1.0 by @inknos in #409 + +------------------------------------------------------------------- Old: ---- podman-5.0.0.tar.gz New: ---- podman-5.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-podman.spec ++++++ --- /var/tmp/diff_new_pack.BxTKP8/_old 2024-08-03 20:04:51.407839106 +0200 +++ /var/tmp/diff_new_pack.BxTKP8/_new 2024-08-03 20:04:51.407839106 +0200 @@ -26,7 +26,7 @@ %bcond_with test %endif Name: python-podman%{psuffix} -Version: 5.0.0 +Version: 5.2.0 Release: 0 Summary: A library to interact with a Podman server License: Apache-2.0 @@ -35,7 +35,6 @@ Source: https://github.com/containers/podman-py/archive/refs/tags/v%{version}.tar.gz#./podman-%{version}.tar.gz BuildRequires: %{python_module pbr} BuildRequires: %{python_module tomli >= 1.2.3 if python-base < 3.11} -BuildRequires: %{python_module pyxdg >= 0.26} BuildRequires: %{python_module requests >= 2.24} BuildRequires: %{python_module setuptools} BuildRequires: %{python_module pip} @@ -43,7 +42,6 @@ BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: (python-tomli >= 1.2.3 if python-base < 3.11) -Requires: python-pyxdg Requires: python-requests Requires: python-urllib3 BuildArch: noarch ++++++ podman-5.0.0.tar.gz -> podman-5.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/.cirrus.yml new/podman-py-5.2.0/.cirrus.yml --- old/podman-py-5.0.0/.cirrus.yml 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/.cirrus.yml 2024-08-02 15:18:25.000000000 +0200 @@ -14,8 +14,6 @@ #### #### Cache-image names to test with (double-quotes around names are critical) #### - FEDORA_NAME: "fedora-39" - # Google-cloud VM Images IMAGE_SUFFIX: "c20240320t153921z-f39f38d13" FEDORA_CACHE_IMAGE_NAME: "fedora-podman-py-${IMAGE_SUFFIX}" @@ -51,7 +49,7 @@ - make lint test_task: - name: "Test on $FEDORA_NAME" + name: "Test on Fedora" alias: test depends_on: @@ -64,7 +62,7 @@ - ${SCRIPT_BASE}/test.sh latest_task: - name: "Test Podman main on $FEDORA_NAME" + name: "Test Podman main on Fedora" alias: latest allow_failures: true diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/.packit.yaml new/podman-py-5.2.0/.packit.yaml --- old/podman-py-5.0.0/.packit.yaml 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/.packit.yaml 2024-08-02 15:18:25.000000000 +0200 @@ -3,33 +3,67 @@ # https://packit.dev/docs/configuration/ downstream_package_name: python-podman -specfile_path: rpm/python-podman.spec upstream_tag_template: v{version} +packages: + python-podman-fedora: + pkg_tool: fedpkg + specfile_path: rpm/python-podman.spec + python-podman-centos: + pkg_tool: centpkg + specfile_path: rpm/python-podman.spec + python-podman-rhel: + specfile_path: rpm/python-podman.spec + srpm_build_deps: - make jobs: + # Copr builds for Fedora - job: copr_build trigger: pull_request + packages: [python-podman-fedora] targets: - fedora-all - - centos-stream-8 + + # Copr builds for CentOS Stream + - job: copr_build + trigger: pull_request + packages: [python-podman-centos] + targets: + - centos-stream-10 - centos-stream-9 + # Copr builds for RHEL + - job: copr_build + trigger: pull_request + packages: [python-podman-rhel] + targets: + - epel-9 + # Run on commit to main branch - job: copr_build trigger: commit + packages: [python-podman-fedora] branch: main owner: rhcontainerbot project: podman-next + # Downstream sync for Fedora - job: propose_downstream trigger: release - update_release: false + packages: [python-podman-fedora] dist_git_branches: - fedora-all + # Downstream sync for CentOS Stream + # TODO: c9s enablement being tracked in https://issues.redhat.com/browse/RUN-2123 + - job: propose_downstream + trigger: release + packages: [python-podman-centos] + dist_git_branches: + - c10s + - job: koji_build trigger: commit dist_git_branches: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/Makefile new/podman-py-5.2.0/Makefile --- old/podman-py-5.0.0/Makefile 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/Makefile 2024-08-02 15:18:25.000000000 +0200 @@ -8,7 +8,7 @@ EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD) HEAD ?= HEAD -export PODMAN_VERSION ?= "5.0.0" +export PODMAN_VERSION ?= "5.2.0" .PHONY: podman podman: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/OWNERS new/podman-py-5.2.0/OWNERS --- old/podman-py-5.0.0/OWNERS 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/OWNERS 2024-08-02 15:18:25.000000000 +0200 @@ -1,6 +1,5 @@ approvers: - baude - - cdoern - edsantiago - giuseppe - jwhonce @@ -27,3 +26,4 @@ - TomSweeneyRedHat - umohnani8 - vrothberg + - inknos diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/README.md new/podman-py-5.2.0/README.md --- old/podman-py-5.0.0/README.md 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/README.md 2024-08-02 15:18:25.000000000 +0200 @@ -35,9 +35,12 @@ # find all containers for container in client.containers.list(): - first_name = container['Names'][0] - container = client.containers.get(first_name) + # After a list call you would probably want to reload the container + # to get the information about the variables such as status. + # Note that list() ignores the sparse option and assumes True by default. + container.reload() print(container, container.id, "\n") + print(container, container.status, "\n") # available fields print(sorted(container.attrs.keys())) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/docs/source/index.rst new/podman-py-5.2.0/docs/source/index.rst --- old/podman-py-5.0.0/docs/source/index.rst 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/docs/source/index.rst 2024-08-02 15:18:25.000000000 +0200 @@ -36,7 +36,7 @@ import podman - with podman.Client() as client: + with podman.PodmanClient() as client: if client.ping(): images = client.images.list() for image in images: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/api/client.py new/podman-py-5.2.0/podman/api/client.py --- old/podman-py-5.0.0/podman/api/client.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/api/client.py 2024-08-02 15:18:25.000000000 +0200 @@ -145,6 +145,8 @@ if self.base_url.scheme == "http+unix": self.mount("http://", UDSAdapter(self.base_url.geturl(), **adapter_kwargs)) self.mount("https://", UDSAdapter(self.base_url.geturl(), **adapter_kwargs)) + # ignore proxies from the env vars + self.trust_env = False elif self.base_url.scheme == "http+ssh": self.mount("http://", SSHAdapter(self.base_url.geturl(), **adapter_kwargs)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/api/http_utils.py new/podman-py-5.2.0/podman/api/http_utils.py --- old/podman-py-5.0.0/podman/api/http_utils.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/api/http_utils.py 2024-08-02 15:18:25.000000000 +0200 @@ -100,4 +100,4 @@ def encode_auth_header(auth_config: Dict[str, str]) -> str: - return base64.b64encode(json.dumps(auth_config).encode('utf-8')) + return base64.urlsafe_b64encode(json.dumps(auth_config).encode('utf-8')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/api/output_utils.py new/podman-py-5.2.0/podman/api/output_utils.py --- old/podman-py-5.0.0/podman/api/output_utils.py 1970-01-01 01:00:00.000000000 +0100 +++ new/podman-py-5.2.0/podman/api/output_utils.py 2024-08-02 15:18:25.000000000 +0200 @@ -0,0 +1,49 @@ +"""Utility functions for dealing with stdout and stderr.""" + +HEADER_SIZE = 8 +STDOUT = 1 +STDERR = 2 + + +# pylint: disable=line-too-long +def demux_output(data_bytes): + """Demuxes the output of a container stream into stdout and stderr streams. + + Stream data is expected to be in the following format: + - 1 byte: stream type (1=stdout, 2=stderr) + - 3 bytes: padding + - 4 bytes: payload size (big-endian) + - N bytes: payload data + ref: https://docs.podman.io/en/latest/_static/api.html?version=v5.0#tag/containers/operation/ContainerAttachLibpod + + Args: + data_bytes: Bytes object containing the combined stream data. + + Returns: + A tuple containing two bytes objects: (stdout, stderr). + """ + stdout = b"" + stderr = b"" + while len(data_bytes) >= HEADER_SIZE: + # Extract header information + header, data_bytes = data_bytes[:HEADER_SIZE], data_bytes[HEADER_SIZE:] + stream_type = header[0] + payload_size = int.from_bytes(header[4:HEADER_SIZE], "big") + # Check if data is sufficient for payload + if len(data_bytes) < payload_size: + break # Incomplete frame, wait for more data + + # Extract and process payload + payload = data_bytes[:payload_size] + if stream_type == STDOUT: + stdout += payload + elif stream_type == STDERR: + stderr += payload + else: + # todo: Handle unexpected stream types + pass + + # Update data for next frame + data_bytes = data_bytes[payload_size:] + + return stdout, stderr diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/api/path_utils.py new/podman-py-5.2.0/podman/api/path_utils.py --- old/podman-py-5.0.0/podman/api/path_utils.py 1970-01-01 01:00:00.000000000 +0100 +++ new/podman-py-5.2.0/podman/api/path_utils.py 2024-08-02 15:18:25.000000000 +0200 @@ -0,0 +1,46 @@ +"""Helper functions for managing paths""" + +import errno +import getpass +import os +import stat + + +def get_runtime_dir() -> str: + """Returns the runtime directory for the current user""" + try: + return os.environ['XDG_RUNTIME_DIR'] + except KeyError: + user = getpass.getuser() + fallback = f'/tmp/podmanpy-runtime-dir-fallback-{user}' + + try: + # This must be a real directory, not a symlink, so attackers can't + # point it elsewhere. So we use lstat to check it. + fallback_st = os.lstat(fallback) + except OSError as e: + if e.errno == errno.ENOENT: + os.mkdir(fallback, 0o700) + else: + raise + else: + # The fallback must be a directory + if not stat.S_ISDIR(fallback_st.st_mode): + os.unlink(fallback) + os.mkdir(fallback, 0o700) + # Must be owned by the user and not accessible by anyone else + elif (fallback_st.st_uid != os.getuid()) or ( + fallback_st.st_mode & (stat.S_IRWXG | stat.S_IRWXO) + ): + os.rmdir(fallback) + os.mkdir(fallback, 0o700) + + return fallback + + +def get_xdg_config_home() -> str: + """Returns the XDG_CONFIG_HOME directory for the current user""" + try: + return os.environ["XDG_CONFIG_HOME"] + except KeyError: + return os.path.join(os.path.expanduser("~"), ".config") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/api/ssh.py new/podman-py-5.2.0/podman/api/ssh.py --- old/podman-py-5.0.0/podman/api/ssh.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/api/ssh.py 2024-08-02 15:18:25.000000000 +0200 @@ -15,12 +15,12 @@ from typing import Optional, Union import time -import xdg.BaseDirectory import urllib3 import urllib3.connection from requests.adapters import DEFAULT_POOLBLOCK, DEFAULT_RETRIES, HTTPAdapter +from podman.api.path_utils import get_runtime_dir from .adapter_utils import _key_normalizer @@ -46,7 +46,7 @@ self.identity = identity self._proc: Optional[subprocess.Popen] = None - runtime_dir = pathlib.Path(xdg.BaseDirectory.get_runtime_dir(strict=False)) / "podman" + runtime_dir = pathlib.Path(get_runtime_dir()) / "podman" runtime_dir.mkdir(mode=0o700, parents=True, exist_ok=True) self.local_sock = runtime_dir / f"podman-forward-{random.getrandbits(80):x}.sock" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/client.py new/podman-py-5.2.0/podman/client.py --- old/podman-py-5.0.0/podman/client.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/client.py 2024-08-02 15:18:25.000000000 +0200 @@ -6,10 +6,9 @@ from pathlib import Path from typing import Any, Dict, Optional -import xdg.BaseDirectory - from podman.api import cached_property from podman.api.client import APIClient +from podman.api.path_utils import get_runtime_dir from podman.domain.config import PodmanConfig from podman.domain.containers_manager import ContainersManager from podman.domain.events import EventsManager @@ -70,9 +69,7 @@ # Override configured identity, if provided in arguments api_kwargs["identity"] = kwargs.get("identity", str(connection.identity)) elif "base_url" not in api_kwargs: - path = str( - Path(xdg.BaseDirectory.get_runtime_dir(strict=False)) / "podman" / "podman.sock" - ) + path = str(Path(get_runtime_dir()) / "podman" / "podman.sock") api_kwargs["base_url"] = "http+unix://" + path self.api = APIClient(**api_kwargs) @@ -141,7 +138,7 @@ @cached_property def containers(self) -> ContainersManager: """Returns Manager for operations on containers stored by a Podman service.""" - return ContainersManager(client=self.api) + return ContainersManager(client=self.api, podman_client=self) @cached_property def images(self) -> ImagesManager: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/config.py new/podman-py-5.2.0/podman/domain/config.py --- old/podman-py-5.0.0/podman/domain/config.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/config.py 2024-08-02 15:18:25.000000000 +0200 @@ -6,9 +6,8 @@ from typing import Dict, Optional import json -import xdg.BaseDirectory - from podman.api import cached_property +from podman.api.path_utils import get_xdg_config_home if sys.version_info >= (3, 11): from tomllib import loads as toml_loads @@ -69,7 +68,7 @@ self.is_default = False if path is None: - home = Path(xdg.BaseDirectory.xdg_config_home) + home = Path(get_xdg_config_home()) self.path = home / "containers" / "podman-connections.json" old_toml_file = home / "containers" / "containers.conf" self.is_default = True @@ -81,6 +80,7 @@ self.is_default = True else: self.path = Path(path) + old_toml_file = None self.attrs = {} if self.path.exists(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/containers.py new/podman-py-5.2.0/podman/domain/containers.py --- old/podman-py-5.0.0/podman/domain/containers.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/containers.py 2024-08-02 15:18:25.000000000 +0200 @@ -9,6 +9,7 @@ import requests from podman import api +from podman.api.output_utils import demux_output from podman.domain.images import Image from podman.domain.images_manager import ImagesManager from podman.domain.manager import PodmanResource @@ -164,8 +165,10 @@ demux: Return stdout and stderr separately Returns: - First item is the command response code - Second item is the requests response content + 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). Raises: NotImplementedError: method not implemented. @@ -199,6 +202,9 @@ # get and return exec information response = self.client.get(f"/exec/{exec_id}/json") response.raise_for_status() + if demux: + stdout_data, stderr_data = demux_output(start_resp.content) + return response.json().get('ExitCode'), (stdout_data, stderr_data) return response.json().get('ExitCode'), start_resp.content def export(self, chunk_size: int = api.DEFAULT_CHUNK_SIZE) -> Iterator[bytes]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/containers_create.py new/podman-py-5.2.0/podman/domain/containers_create.py --- old/podman-py-5.0.0/podman/domain/containers_create.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/containers_create.py 2024-08-02 15:18:25.000000000 +0200 @@ -460,7 +460,7 @@ "conmon_pid_file": pop("conmon_pid_file"), # TODO document, podman only "containerCreateCommand": pop("containerCreateCommand"), # TODO document, podman only "devices": [], - "dns_options": pop("dns_opt"), + "dns_option": pop("dns_opt"), "dns_search": pop("dns_search"), "dns_server": pop("dns"), "entrypoint": pop("entrypoint"), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/containers_manager.py new/podman-py-5.2.0/podman/domain/containers_manager.py --- old/podman-py-5.0.0/podman/domain/containers_manager.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/containers_manager.py 2024-08-02 15:18:25.000000000 +0200 @@ -30,7 +30,7 @@ """Get container by name or id. Args: - container_id: Container name or id. + key: Container name or id. Returns: A `Container` object corresponding to `key`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/containers_run.py new/podman-py-5.2.0/podman/domain/containers_run.py --- old/podman-py-5.0.0/podman/domain/containers_run.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/containers_run.py 2024-08-02 15:18:25.000000000 +0200 @@ -60,7 +60,7 @@ try: container = self.create(image=image, command=command, **kwargs) except ImageNotFound: - self.client.images.pull(image, platform=kwargs.get("platform")) + self.podman_client.images.pull(image, platform=kwargs.get("platform")) container = self.create(image=image, command=command, **kwargs) container.start() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/images.py new/podman-py-5.2.0/podman/domain/images.py --- old/podman-py-5.0.0/podman/domain/images.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/images.py 2024-08-02 15:18:25.000000000 +0200 @@ -3,9 +3,11 @@ import logging from typing import Any, Dict, Iterator, List, Optional, Union +import urllib.parse + from podman import api from podman.domain.manager import PodmanResource -from podman.errors import ImageNotFound +from podman.errors import ImageNotFound, InvalidArgument logger = logging.getLogger("podman.images") @@ -68,7 +70,7 @@ def save( self, chunk_size: Optional[int] = api.DEFAULT_CHUNK_SIZE, - named: Union[str, bool] = False, # pylint: disable=unused-argument + named: Union[str, bool] = False, ) -> Iterator[bytes]: """Returns Image as tarball. @@ -77,13 +79,28 @@ Args: chunk_size: If None, data will be streamed in received buffer size. If not None, data will be returned in sized buffers. Default: 2MB - named: Ignored. + named (str or bool): If ``False`` (default), the tarball will not + retain repository and tag information for this image. If set + to ``True``, the first tag in the :py:attr:`~tags` list will + be used to identify the image. Alternatively, any element of + the :py:attr:`~tags` list can be used as an argument to use + that specific tag as the saved identifier. Raises: - APIError: when service returns an error + APIError: When service returns an error + InvalidArgument: When the provided Tag name is not valid for the image. """ + + img = self.id + if named: + img = urllib.parse.quote(self.tags[0] if self.tags else img) + if isinstance(named, str): + if named not in self.tags: + raise InvalidArgument(f"'{named}' is not a valid tag for this image") + img = urllib.parse.quote(named) + response = self.client.get( - f"/images/{self.id}/get", params={"format": ["docker-archive"]}, stream=True + f"/images/{img}/get", params={"format": ["docker-archive"]}, stream=True ) response.raise_for_status(not_found=ImageNotFound) return response.iter_content(chunk_size=chunk_size) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/images_manager.py new/podman-py-5.2.0/podman/domain/images_manager.py --- old/podman-py-5.0.0/podman/domain/images_manager.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/images_manager.py 2024-08-02 15:18:25.000000000 +0200 @@ -158,17 +158,22 @@ deleted: List[Dict[str, str]] = [] error: List[str] = [] reclaimed: int = 0 - for element in response.json(): - if "Err" in element and element["Err"] is not None: - error.append(element["Err"]) - else: - reclaimed += element["Size"] - deleted.append( - { - "Deleted": element["Id"], - "Untagged": "", - } - ) + # If the prune doesn't remove images, the API returns "null" + # and it's interpreted as None (NoneType) + # so the for loop throws "TypeError: 'NoneType' object is not iterable". + # The below if condition fixes this issue. + if response.json() is not None: + for element in response.json(): + if "Err" in element and element["Err"] is not None: + error.append(element["Err"]) + else: + reclaimed += element["Size"] + deleted.append( + { + "Deleted": element["Id"], + "Untagged": "", + } + ) if len(error) > 0: raise APIError(response.url, response=response, explanation="; ".join(error)) @@ -309,7 +314,8 @@ else: params["reference"] = f"{repository}:{tag}" - if "platform" in kwargs: + # Check if "platform" in kwargs AND it has value. + if "platform" in kwargs and kwargs["platform"]: tokens = kwargs.get("platform").split("/") if 1 < len(tokens) > 3: raise ValueError(f'\'{kwargs.get("platform")}\' is not a legal platform.') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/domain/manager.py new/podman-py-5.2.0/podman/domain/manager.py --- old/podman-py-5.0.0/podman/domain/manager.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/domain/manager.py 2024-08-02 15:18:25.000000000 +0200 @@ -22,6 +22,7 @@ attrs: Optional[Mapping[str, Any]] = None, client: Optional[APIClient] = None, collection: Optional["Manager"] = None, + podman_client: Optional["PodmanClient"] = None, ): """Initialize base class for PodmanResource's. @@ -29,10 +30,12 @@ attrs: Mapping of attributes for resource from Podman service. client: Configured connection to a Podman service. collection: Manager of this category of resource, named `collection` for compatibility + podman_client: PodmanClient() configured to connect to Podman object. """ super().__init__() self.client = client self.manager = collection + self.podman_client = podman_client self.attrs = {} if attrs is not None: @@ -77,14 +80,18 @@ def resource(self): """Type[PodmanResource]: Class which the factory method prepare_model() will use.""" - def __init__(self, client: APIClient = None) -> None: + def __init__( + self, client: Optional[APIClient] = None, podman_client: Optional["PodmanClient"] = None + ) -> None: """Initialize Manager() object. Args: client: APIClient() configured to connect to Podman service. + podman_client: PodmanClient() configured to connect to Podman object. """ super().__init__() self.client = client + self.podman_client = podman_client @abstractmethod def exists(self, key: str) -> bool: @@ -110,6 +117,7 @@ # Refresh existing PodmanResource. if isinstance(attrs, PodmanResource): attrs.client = self.client + attrs.podman_client = self.podman_client attrs.collection = self return attrs @@ -117,7 +125,9 @@ if isinstance(attrs, abc.Mapping): # TODO Determine why pylint is reporting typing.Type not callable # pylint: disable=not-callable - return self.resource(attrs=attrs, client=self.client, collection=self) + return self.resource( + attrs=attrs, client=self.client, podman_client=self.podman_client, collection=self + ) # pylint: disable=broad-exception-raised raise Exception(f"Can't create {self.resource.__name__} from {attrs}") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/tests/__init__.py new/podman-py-5.2.0/podman/tests/__init__.py --- old/podman-py-5.0.0/podman/tests/__init__.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/tests/__init__.py 2024-08-02 15:18:25.000000000 +0200 @@ -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.0.0/libpod" +LIBPOD_URL = "http://%2Frun%2Fapi.sock/v5.2.0/libpod" COMPATIBLE_URL = "http://%2Frun%2Fapi.sock/v1.40" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/tests/integration/test_container_exec.py new/podman-py-5.2.0/podman/tests/integration/test_container_exec.py --- old/podman-py-5.0.0/podman/tests/integration/test_container_exec.py 1970-01-01 01:00:00.000000000 +0100 +++ new/podman-py-5.2.0/podman/tests/integration/test_container_exec.py 2024-08-02 15:18:25.000000000 +0200 @@ -0,0 +1,53 @@ +import unittest + +import podman.tests.integration.base as base +from podman import PodmanClient + +# @unittest.skipIf(os.geteuid() != 0, 'Skipping, not running as root') + + +class ContainersExecIntegrationTests(base.IntegrationTest): + """Containers integration tests for exec""" + + def setUp(self): + super().setUp() + + self.client = PodmanClient(base_url=self.socket_uri) + self.addCleanup(self.client.close) + + self.alpine_image = self.client.images.pull("quay.io/libpod/alpine", tag="latest") + self.containers = [] + + def tearDown(self): + for container in self.containers: + container.remove(force=True) + + def test_container_exec_run(self): + """Test any command that will return code 0 and no output""" + container = self.client.containers.create(self.alpine_image, command=["top"], detach=True) + container.start() + error_code, stdout = container.exec_run("echo hello") + + self.assertEqual(error_code, 0) + self.assertEqual(stdout, b'\x01\x00\x00\x00\x00\x00\x00\x06hello\n') + + def test_container_exec_run_errorcode(self): + """Test a failing command with stdout and stderr in a single bytestring""" + container = self.client.containers.create(self.alpine_image, command=["top"], detach=True) + container.start() + error_code, output = container.exec_run("ls nonexistent") + + self.assertEqual(error_code, 1) + self.assertEqual( + output, b"\x02\x00\x00\x00\x00\x00\x00+ls: nonexistent: No such file or directory\n" + ) + + def test_container_exec_run_demux(self): + """Test a failing command with stdout and stderr in a bytestring tuple""" + container = self.client.containers.create(self.alpine_image, command=["top"], detach=True) + container.start() + error_code, output = container.exec_run("ls nonexistent", demux=True) + + self.assertEqual(error_code, 1) + self.assertEqual(output[0], b'') + self.assertEqual(output[1], b"ls: nonexistent: No such file or directory\n") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/tests/integration/test_images.py new/podman-py-5.2.0/podman/tests/integration/test_images.py --- old/podman-py-5.0.0/podman/tests/integration/test_images.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/tests/integration/test_images.py 2024-08-02 15:18:25.000000000 +0200 @@ -109,6 +109,25 @@ self.assertIn(image.id, deleted) self.assertGreater(actual["SpaceReclaimed"], 0) + with self.subTest("Export Image to tarball (in memory) with named mode"): + alpine_image = self.client.images.pull("quay.io/libpod/alpine", tag="latest") + image_buffer = io.BytesIO() + for chunk in alpine_image.save(named=True): + image_buffer.write(chunk) + image_buffer.seek(0, 0) + + with tarfile.open(fileobj=image_buffer, mode="r") as tar: + items_in_tar = tar.getnames() + # Check if repositories file is available in the tarball + self.assertIn("repositories", items_in_tar) + # Extract the 'repositories' file + repositories_file = tar.extractfile("repositories") + if repositories_file is not None: + # Check the content of the "repositories" file. + repositories_content = repositories_file.read().decode("utf-8") + # Check if "repositories" file contains the name of the Image (named). + 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) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/tests/unit/test_imagesmanager.py new/podman-py-5.2.0/podman/tests/unit/test_imagesmanager.py --- old/podman-py-5.0.0/podman/tests/unit/test_imagesmanager.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/tests/unit/test_imagesmanager.py 2024-08-02 15:18:25.000000000 +0200 @@ -224,6 +224,15 @@ self.assertEqual(e.exception.explanation, "Test prune failure in response body.") @requests_mock.Mocker() + def test_prune_empty(self, mock): + """Unit test if prune API responses null (None).""" + mock.post(tests.LIBPOD_URL + "/images/prune", text="null") + + report = self.client.images.prune() + self.assertEqual(report["ImagesDeleted"], []) + self.assertEqual(report["SpaceReclaimed"], 0) + + @requests_mock.Mocker() def test_get(self, mock): mock.get( tests.LIBPOD_URL + "/images/fedora%3Alatest/json", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/tests/unit/test_podmanclient.py new/podman-py-5.2.0/podman/tests/unit/test_podmanclient.py --- old/podman-py-5.0.0/podman/tests/unit/test_podmanclient.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/tests/unit/test_podmanclient.py 2024-08-02 15:18:25.000000000 +0200 @@ -5,9 +5,9 @@ from unittest.mock import MagicMock import requests_mock -import xdg from podman import PodmanClient, tests +from podman.api.path_utils import get_runtime_dir, get_xdg_config_home class PodmanClientTestCase(unittest.TestCase): @@ -88,7 +88,7 @@ ) # Build path to support tests running as root or a user - expected = Path(xdg.BaseDirectory.xdg_config_home) / "containers" / "containers.conf" + expected = Path(get_xdg_config_home()) / "containers" / "containers.conf" PodmanClientTestCase.opener.assert_called_with(expected, encoding="utf-8") def test_connect_404(self): @@ -100,16 +100,12 @@ with mock.patch.multiple(Path, open=self.mocked_open, exists=MagicMock(return_value=True)): with PodmanClient() as client: expected = "http+unix://" + urllib.parse.quote_plus( - str( - Path(xdg.BaseDirectory.get_runtime_dir(strict=False)) - / "podman" - / "podman.sock" - ) + str(Path(get_runtime_dir()) / "podman" / "podman.sock") ) self.assertEqual(client.api.base_url.geturl(), expected) # Build path to support tests running as root or a user - expected = Path(xdg.BaseDirectory.xdg_config_home) / "containers" / "containers.conf" + expected = Path(get_xdg_config_home()) / "containers" / "containers.conf" PodmanClientTestCase.opener.assert_called_with(expected, encoding="utf-8") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/podman/version.py new/podman-py-5.2.0/podman/version.py --- old/podman-py-5.0.0/podman/version.py 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/podman/version.py 2024-08-02 15:18:25.000000000 +0200 @@ -1,4 +1,4 @@ """Version of PodmanPy.""" -__version__ = "5.0.0" +__version__ = "5.2.0" __compatible_version__ = "1.40" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/requirements.txt new/podman-py-5.2.0/requirements.txt --- old/podman-py-5.0.0/requirements.txt 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/requirements.txt 2024-08-02 15:18:25.000000000 +0200 @@ -1,5 +1,4 @@ # Any changes should be copied into pyproject.toml -pyxdg>=0.26 requests>=2.24 setuptools sphinx diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/setup.cfg new/podman-py-5.2.0/setup.cfg --- old/podman-py-5.0.0/setup.cfg 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/setup.cfg 2024-08-02 15:18:25.000000000 +0200 @@ -1,7 +1,7 @@ [metadata] name = podman -version = 5.0.0 -author = Brent Baude, Jhon Honce, Urvashi Mohnani +version = 5.2.0 +author = Brent Baude, Jhon Honce, Urvashi Mohnani, Nicola Sella author_email = jho...@redhat.com description = Bindings for Podman RESTful API long_description = file: README.md @@ -25,6 +25,7 @@ Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Topic :: Software Development :: Libraries :: Python Modules keywords = podman, libpod @@ -34,7 +35,6 @@ test_suite = # Any changes should be copied into pyproject.toml install_requires = - pyxdg >=0.26 requests >=2.24 tomli>=1.2.3; python_version<'3.11' urllib3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-5.0.0/tox.ini new/podman-py-5.2.0/tox.ini --- old/podman-py-5.0.0/tox.ini 2024-03-22 22:46:27.000000000 +0100 +++ new/podman-py-5.2.0/tox.ini 2024-08-02 15:18:25.000000000 +0200 @@ -1,6 +1,6 @@ [tox] minversion = 3.2.0 -envlist = pylint,coverage,py36,py38,py39,py310,py311 +envlist = pylint,coverage,py36,py38,py39,py310,py311,py312 ignore_basepython_conflict = true [testenv]