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 2022-08-16 17:07:51 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-podman (Old) and /work/SRC/openSUSE:Factory/.python-podman.new.1521 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-podman" Tue Aug 16 17:07:51 2022 rev:6 rq:995862 version:4.2.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-podman/python-podman.changes 2022-07-04 11:32:45.992012650 +0200 +++ /work/SRC/openSUSE:Factory/.python-podman.new.1521/python-podman.changes 2022-08-16 17:07:52.755902422 +0200 @@ -1,0 +2,10 @@ +Mon Aug 15 11:45:24 UTC 2022 - Michael Str??der <mich...@stroeder.com> + +- update to 4.2.0 + * Added support for devices in container creation + * Implemented the login endpoint + * Added relabel option for mounts and other mount option support + * Implemented exec_run + * Bug Fixes + +------------------------------------------------------------------- Old: ---- podman-4.0.0.tar.gz New: ---- podman-4.2.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-podman.spec ++++++ --- /var/tmp/diff_new_pack.Y8mWtR/_old 2022-08-16 17:07:53.271903965 +0200 +++ /var/tmp/diff_new_pack.Y8mWtR/_new 2022-08-16 17:07:53.279903989 +0200 @@ -27,7 +27,7 @@ %bcond_with test %endif Name: python-podman%{psuffix} -Version: 4.0.0 +Version: 4.2.0 Release: 0 Summary: A library to interact with a Podman server License: Apache-2.0 ++++++ podman-4.0.0.tar.gz -> podman-4.2.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/.cirrus.yml new/podman-py-4.2.0/.cirrus.yml --- old/podman-py-4.0.0/.cirrus.yml 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/.cirrus.yml 2022-08-10 15:03:52.000000000 +0200 @@ -14,12 +14,12 @@ #### #### Cache-image names to test with (double-quotes around names are critical) #### - FEDORA_NAME: "fedora-35" - PRIOR_FEDORA_NAME: "fedora-34" - UBUNTU_NAME: "ubuntu-2110" + FEDORA_NAME: "fedora-36" + PRIOR_FEDORA_NAME: "fedora-35" + UBUNTU_NAME: "ubuntu-2204" # Google-cloud VM Images - IMAGE_SUFFIX: "c4764556961513472" + IMAGE_SUFFIX: "c6193881921355776" FEDORA_CACHE_IMAGE_NAME: "fedora-podman-py-${IMAGE_SUFFIX}" @@ -90,7 +90,7 @@ name: "VM img. keepalive" container: &smallcontainer - image: "quay.io/libpod/imgts:$IMAGE_SUFFIX" + image: "quay.io/libpod/imgts:latest" cpu: 1 memory: 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/CONTRIBUTING.md new/podman-py-4.2.0/CONTRIBUTING.md --- old/podman-py-4.0.0/CONTRIBUTING.md 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/CONTRIBUTING.md 2022-08-10 15:03:52.000000000 +0200 @@ -70,11 +70,15 @@ `tox -e black-format` to update the code formatting prior to committing. - Pass pylint - exceptions are possible, but you will need to make a good argument -- use spaces not tabs for indentation +- Use spaces not tabs for indentation - This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. +- Use Google style python [docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) + - A general exception is made for kwargs where we use the Sphinx extension of adding a section + "Keyword Arguments" and documenting the accepted keyword arguments, their type and usage. + Example: kwarg1 (int): Description of kwarg1 Again thank you for your interest and participation. Jhon Honce `<jhonce at redhat dot com>` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/Makefile new/podman-py-4.2.0/Makefile --- old/podman-py-4.0.0/Makefile 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/Makefile 2022-08-10 15:03:52.000000000 +0200 @@ -8,7 +8,7 @@ EPOCH_TEST_COMMIT ?= $(shell git merge-base $${DEST_BRANCH:-main} HEAD) HEAD ?= HEAD -export PODMAN_VERSION ?= "4.0.0" +export PODMAN_VERSION ?= "4.2.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 + $(PYTHON) -m tox -e pylint,coverage,py36,py38,py39,py310,py311 .PHONY: unittest unittest: @@ -63,6 +63,10 @@ podman podman/tests sphinx-build build/docs/source build/html +.PHONY: rpm +rpm: ## Build rpm packages + rpkg local + # .PHONY: install HEAD ?= HEAD # install: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/OWNERS new/podman-py-4.2.0/OWNERS --- old/podman-py-4.0.0/OWNERS 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/OWNERS 2022-08-10 15:03:52.000000000 +0200 @@ -1,8 +1,10 @@ approvers: - baude + - cdoern - edsantiago - giuseppe - jwhonce + - lsm5 - Luap99 - mheon - mwhahaha @@ -13,9 +15,11 @@ reviewers: - ashley-cui - baude + - cdoern - edsantiago - giuseppe - jwhonce + - lsm5 - Luap99 - mheon - mwhahaha diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/api/client.py new/podman-py-4.2.0/podman/api/client.py --- old/podman-py-4.0.0/podman/api/client.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/api/client.py 2022-08-10 15:03:52.000000000 +0200 @@ -1,7 +1,7 @@ """APIClient for connecting to Podman service.""" import json import urllib.parse -from typing import IO, Any, ClassVar, Iterable, List, Mapping, Optional, Tuple, Union, Type +from typing import Any, ClassVar, IO, Iterable, List, Mapping, Optional, Tuple, Type, Union import requests from requests.adapters import HTTPAdapter @@ -46,9 +46,7 @@ """Forward any query for an attribute not defined in this proxy class to wrapped class.""" return getattr(self._response, item) - def raise_for_status( - self, not_found: Type[APIError] = NotFound - ) -> None: # pylint: disable=arguments-differ + def raise_for_status(self, not_found: Type[APIError] = NotFound) -> None: """Raises exception when Podman service reports one.""" if self.status_code < 400: return @@ -69,8 +67,7 @@ """Client for Podman service API.""" # Abstract methods (delete,get,head,post) are specialized and pylint cannot walk hierarchy. - # pylint: disable=arguments-differ - # pylint: disable=too-many-instance-attributes + # pylint: disable=too-many-instance-attributes,arguments-differ,arguments-renamed supported_schemes: ClassVar[List[str]] = ( "unix", diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/api/http_utils.py new/podman-py-4.2.0/podman/api/http_utils.py --- old/podman-py-4.0.0/podman/api/http_utils.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/api/http_utils.py 2022-08-10 15:03:52.000000000 +0200 @@ -1,4 +1,5 @@ """Utility functions for working with URLs.""" +import base64 import collections.abc import json from typing import Dict, List, Mapping, Optional, Union, Any @@ -88,3 +89,7 @@ canonical[key] = proposal return canonical + + +def encode_auth_header(auth_config: Dict[str, str]) -> str: + return base64.b64encode(json.dumps(auth_config).encode('utf-8')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/api/typing_extensions.py new/podman-py-4.2.0/podman/api/typing_extensions.py --- old/podman-py-4.0.0/podman/api/typing_extensions.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/api/typing_extensions.py 2022-08-10 15:03:52.000000000 +0200 @@ -984,7 +984,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.defaultdict, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1000,7 +999,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.defaultdict, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1021,7 +1019,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.OrderedDict, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1037,7 +1034,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.OrderedDict, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1061,7 +1057,6 @@ class Counter( collections.Counter, typing.Dict[T, int], metaclass=_CounterMeta, extra=collections.Counter ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1077,7 +1072,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.Counter, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1093,7 +1087,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.Counter, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1115,7 +1108,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.ChainMap, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1131,7 +1123,6 @@ metaclass=_ExtensionsGenericMeta, extra=collections.ChainMap, ): - __slots__ = () def __new__(cls, *args, **kwds): @@ -1550,7 +1541,6 @@ bases="Protocol, Generic[T]" if OLD_GENERICS else "Protocol[T]" ) - elif PEP_560: from typing import _type_check, _GenericAlias, _collect_type_vars # noqa @@ -2473,7 +2463,6 @@ if hasattr(typing, 'ParamSpec'): ParamSpec = typing.ParamSpec else: - # Inherits from list as a workaround for Callable checks in Python < 3.9.2. class ParamSpec(list): """Parameter specification variable. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/domain/containers.py new/podman-py-4.2.0/podman/domain/containers.py --- old/podman-py-4.0.0/podman/domain/containers.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/domain/containers.py 2022-08-10 15:03:52.000000000 +0200 @@ -2,6 +2,7 @@ import io import json import logging +import shlex from contextlib import suppress from typing import Any, Dict, Iterable, Iterator, List, Mapping, Optional, Sequence, Tuple, Union @@ -43,6 +44,8 @@ def labels(self): """dict[str, str]: Returns labels associated with container.""" with suppress(KeyError): + if "Labels" in self.attrs: + return self.attrs["Labels"] return self.attrs["Config"]["Labels"] return {} @@ -92,7 +95,7 @@ Keyword Args: author (str): Name of commit author changes (List[str]): Instructions to apply during commit - comment (List[str]): Instructions to apply while committing in Dockerfile format + comment (str): Commit message to include with Image, overrides keyword message conf (dict[str, Any]): Ignored. format (str): Format of the image manifest and metadata message (str): Commit message to include with Image @@ -101,7 +104,7 @@ params = { "author": kwargs.get("author"), "changes": kwargs.get("changes"), - "comment": kwargs.get("comment"), + "comment": kwargs.get("comment", kwargs.get("message")), "container": self.id, "format": kwargs.get("format"), "pause": kwargs.get("pause"), @@ -112,7 +115,7 @@ response.raise_for_status() body = response.json() - return ImagesManager(client=self.client).get(body["ID"]) + return ImagesManager(client=self.client).get(body["Id"]) def diff(self) -> List[Dict[str, int]]: """Report changes of a container's filesystem. @@ -124,13 +127,14 @@ response.raise_for_status() return response.json() + # pylint: disable=too-many-arguments,unused-argument def exec_run( self, cmd: Union[str, List[str]], stdout: bool = True, stderr: bool = True, stdin: bool = False, - tty: bool = False, + tty: bool = True, privileged: bool = False, user=None, detach: bool = False, @@ -139,9 +143,7 @@ environment: Union[Mapping[str, str], List[str]] = None, workdir: str = None, demux: bool = False, - ) -> Tuple[ - Optional[int], Union[Iterator[bytes], Any, Tuple[bytes, bytes]] - ]: # pylint: disable=too-many-arguments,unused-argument + ) -> Tuple[Optional[int], Union[Iterator[bytes], Any, Tuple[bytes, bytes]]]: """Run given command inside container and return results. Args: @@ -164,16 +166,42 @@ demux: Return stdout and stderr separately Returns: - TBD + First item is the command response code + Second item is the requests response content Raises: NotImplementedError: method not implemented. APIError: when service reports error """ - if user is None: - user = "root" - - raise NotImplementedError() + # 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 = { + "AttachStderr": stderr, + "AttachStdin": stdin, + "AttachStdout": stdout, + "Cmd": cmd if isinstance(cmd, list) else shlex.split(cmd), + # "DetachKeys": detach, # This is something else + "Env": environment, + "Privileged": privileged, + "Tty": tty, + "User": user, + "WorkingDir": workdir, + } + # 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}) + ) + start_resp.raise_for_status() + # get and return exec information + response = self.client.get(f"/exec/{exec_id}/json") + response.raise_for_status() + return response.json().get('ExitCode'), start_resp.content def export(self, chunk_size: int = api.DEFAULT_CHUNK_SIZE) -> Iterator[bytes]: """Download container's filesystem contents as a tar archive. @@ -250,6 +278,7 @@ until (Union[datetime, int]): Show logs that occurred before the given datetime or integer epoch (in seconds) """ + stream = bool(kwargs.get("stream", False)) params = { "follow": kwargs.get("follow", kwargs.get("stream", None)), "since": api.prepare_timestamp(kwargs.get("since")), @@ -260,10 +289,10 @@ "until": api.prepare_timestamp(kwargs.get("until")), } - response = self.client.get(f"/containers/{self.id}/logs", params=params) + response = self.client.get(f"/containers/{self.id}/logs", stream=stream, params=params) response.raise_for_status() - if bool(kwargs.get("stream", False)): + if stream: return api.stream_frames(response) return api.frames(response) @@ -390,7 +419,7 @@ with io.StringIO() as buffer: for entry in response.text: - buffer.writer(json.dumps(entry) + "\n") + buffer.write(json.dumps(entry) + "\n") return buffer.getvalue() @staticmethod @@ -476,8 +505,9 @@ """Block until the container enters given state. Keyword Args: - condition (str): Container state on which to release, values: - not-running (default), next-exit or removed. + condition (Union[str, List[str]]): Container state on which to release. + One or more of: "configured", "created", "running", "stopped", + "paused", "exited", "removing", "stopping". timeout (int): Ignored. Returns: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/domain/containers_create.py new/podman-py-4.2.0/podman/domain/containers_create.py --- old/podman-py-4.0.0/podman/domain/containers_create.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/domain/containers_create.py 2022-08-10 15:03:52.000000000 +0200 @@ -53,7 +53,7 @@ device_read_iops: Limit read rate (IO per second) from a device. device_write_bps: Limit write rate (bytes per second) from a device. device_write_iops: Limit write rate (IO per second) from a device. - devices (List[str): Expose host devices to the container, as a List[str] in the form + devices (List[str]): Expose host devices to the container, as a List[str] in the form <path_on_host>:<path_in_container>:<cgroup_permissions>. For example: @@ -101,11 +101,34 @@ mounts (List[Mount]): Specification for mounts to be added to the container. More powerful alternative to volumes. Each item in the list is expected to be a Mount object. + For example : + [ + { + "type": "bind", + "source": "/a/b/c1", + "target" "/d1", + "read_only": True, + "relabel": "Z" + }, + { + "type": "tmpfs", + "source": "tmpfs", # If this was not passed, the regular directory + # would be created rather than tmpfs mount !!! + # as this will cause to have invalid entry + # in /proc/self/mountinfo + "target" "/d2", + "size": "100k", + "chown": True + } + ] + name (str): The name for this container. nano_cpus (int): CPU quota in units of 1e-9 CPUs. - network (str): Name of the network this container will be connected to at creation time. - You can connect to additional networks using Network.connect. - Incompatible with network_mode. + networks (Dict[str, Dict[str, Union[str, List[str]]): + Networks which will be connected to container during container creation + Values of the network configuration can be : + - string + - list of strings (e.g. Aliases) network_disabled (bool): Disable networking. network_mode (str): One of: @@ -142,9 +165,9 @@ For example: {'2222/tcp': None}. - A tuple of (address, port) if you want to specify the host interface. For example: {'1111/tcp': ('127.0.0.1', 1111)}. - - A list of integers, if you want to bind multiple host ports to a single container - port. - For example: {'1111/tcp': [1234, 4567]}. + - A list of integers or tuples of (address, port), if you want to bind + multiple host ports to a single container port. + For example: {'1111/tcp': [1234, ("127.0.0.1", 4567)]}. For example: {'9090': 7878, '10932/tcp': '8781', "8989/tcp": ("127.0.0.1", 9091)} @@ -192,17 +215,26 @@ version (str): The version of the API to use. Set to auto to automatically detect the server's version. Default: 3.0.0 volume_driver (str): The name of a volume driver/plugin. - volumes (Dict[str, Dict[str, str]]): A dictionary to configure volumes mounted inside - the container. The key is either the host path or a volume name, and the value is + volumes (Dict[str, Dict[str, Union[str, list]]]): A dictionary to configure + volumes mounted inside the container. + The key is either the host path or a volume name, and the value is a dictionary with the keys: - bind: The path to mount the volume inside the container - mode: Either rw to mount the volume read/write, or ro to mount it read-only. + Kept for docker-py compatibility + - extended_mode: List of options passed to volume mount. For example: - {'/home/user1/': {'bind': '/mnt/vol2', 'mode': 'rw'}, - '/var/www': {'bind': '/mnt/vol1', 'mode': 'ro'}} + { + 'test_bind_1': + {'bind': '/mnt/vol1', 'mode': 'rw'}, + 'test_bind_2': + {'bind': '/mnt/vol2', 'extended_mode': ['ro', 'noexec']}, + 'test_bind_3': + {'bind': '/mnt/vol3', 'extended_mode': ['noexec'], 'mode': 'rw'} + } volumes_from (List[str]): List of container names or IDs to get volumes from. working_dir (str): Path to the working directory. @@ -265,7 +297,6 @@ "device_requests", # FIXME In addition to device Major/Minor include path "device_write_bps", # FIXME In addition to device Major/Minor include path "device_write_iops", # FIXME In addition to device Major/Minor include path - "devices", # FIXME In addition to device Major/Minor include path "domainname", "network_disabled", # FIXME Where to map for Podman API? "storage_opt", # FIXME Where to map for Podman API? @@ -320,7 +351,6 @@ # Transform keywords into parameters params = { - "aliases": pop("aliases"), # TODO document, podman only "annotations": pop("annotations"), # TODO document, podman only "apparmor_profile": pop("apparmor_profile"), # TODO document, podman only "cap_add": pop("cap_add"), @@ -331,6 +361,7 @@ "command": args.pop("command", args.pop("cmd", None)), "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_search": pop("dns_search"), "dns_server": pop("dns"), @@ -358,6 +389,7 @@ "name": pop("name"), "namespace": pop("namespace"), # TODO What is this for? "network_options": pop("network_options"), # TODO document, podman only + "networks": pop("networks"), "no_new_privileges": pop("no_new_privileges"), # TODO document, podman only "oci_runtime": pop("runtime"), "oom_score_adj": pop("oom_score_adj"), @@ -379,8 +411,6 @@ "secrets": pop("secrets"), # TODO document, podman only "selinux_opts": pop("security_opt"), "shm_size": to_bytes(pop("shm_size")), - "static_ip": pop("static_ip"), # TODO document, podman only - "static_ipv6": pop("static_ipv6"), # TODO document, podman only "static_mac": pop("mac_address"), "stdin": pop("stdin_open"), "stop_signal": pop("stop_signal"), @@ -401,6 +431,9 @@ "work_dir": pop("working_dir"), } + for device in args.pop("devices", []): + params["devices"].append({"path": device}) + for item in args.pop("exposed_ports", []): port, protocol = item.split("/") params["expose"][int(port)] = protocol @@ -425,17 +458,24 @@ "type": item.get("type"), } + # some names are different for podman-py vs REST API due to compatibility with docker + # some (e.g. chown) despite listed in podman-run documentation fails with error + names_dict = {"read_only": "ro", "chown": "U"} + options = [] - if "read_only" in item: - options.append("ro") - if "consistency" in item: - options.append(f"consistency={item['consistency']}") - if "mode" in item: - options.append(f"mode={item['mode']}") - if "propagation" in item: - options.append(item["propagation"]) - if "size" in item: - options.append(f"size={item['size']}") + simple_options = ["propagation", "relabel"] + bool_options = ["read_only", "U", "chown"] + regular_options = ["consistency", "mode", "size"] + + for k, v in item.items(): + option_name = names_dict.get(k, k) + if k in bool_options and v is True: + options.append(option_name) + elif k in regular_options: + options.append(f'{option_name}={v}') + elif k in simple_options: + options.append(v) + mount_point["options"] = options params["mounts"].append(mount_point) @@ -461,9 +501,21 @@ port_map["host_ip"] = host[0] port_map["host_port"] = int(host[1]) elif isinstance(host, list): - raise ValueError( - "Podman API does not support multiple port bound to a single host port." - ) + for host_list in host: + port_map = {"container_port": int(container_port), "protocol": protocol} + if ( + isinstance(host_list, int) + or isinstance(host_list, str) + and host_list.isdigit() + ): + port_map["host_port"] = int(host_list) + elif isinstance(host_list, tuple): + port_map["host_ip"] = host_list[0] + port_map["host_port"] = int(host_list[1]) + else: + raise ValueError(f"'ports' value of '{host_list}' is not supported.") + params["portmappings"].append(port_map) + continue else: raise ValueError(f"'ports' value of '{host}' is not supported.") @@ -492,7 +544,7 @@ "kernelTCP": args.pop("kernel_memory_tcp", None), "limit": to_bytes(args.pop("mem_limit", None)), "reservation": to_bytes(args.pop("mem_reservation", None)), - "swap": args.pop("memswap_limit", None), + "swap": to_bytes(args.pop("memswap_limit", None)), "swappiness": args.pop("mem_swappiness", None), "useHierarchy": args.pop("mem_use_hierarchy", None), } @@ -508,11 +560,18 @@ for item in args.pop("volumes", {}).items(): key, value = item - volume = { - "Name": key, - "Dest": value["bind"], - "Options": [value["mode"]] if "mode" in value else [], - } + extended_mode = value.get('extended_mode', []) + if not isinstance(extended_mode, list): + raise ValueError("'extended_mode' value should be a list") + + options = extended_mode + mode = value.get('mode') + if mode is not None: + if not isinstance(mode, str): + raise ValueError("'mode' value should be a str") + options.append(mode) + + volume = {"Name": key, "Dest": value["bind"], "Options": options} params["volumes"].append(volume) if "cgroupns" in args: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/domain/containers_run.py new/podman-py-4.2.0/podman/domain/containers_run.py --- old/podman-py-4.0.0/podman/domain/containers_run.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/domain/containers_run.py 2022-08-10 15:03:52.000000000 +0200 @@ -64,7 +64,7 @@ container = self.create(image=image, command=command, **kwargs) container.start() - container.wait(condition="running") + container.wait(condition=["running", "exited"]) container.reload() if kwargs.get("detach", False): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/domain/images_manager.py new/podman-py-4.2.0/podman/domain/images_manager.py --- old/podman-py-4.0.0/podman/domain/images_manager.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/domain/images_manager.py 2022-08-10 15:03:52.000000000 +0200 @@ -9,6 +9,7 @@ from podman import api from podman.api import Literal +from podman.api.http_utils import encode_auth_header from podman.domain.images import Image from podman.domain.images_build import BuildMixin from podman.domain.manager import Manager @@ -193,10 +194,13 @@ Raises: APIError: when service returns an error """ - # TODO set X-Registry-Auth + auth_config: Optional[Dict[str, str]] = kwargs.get("auth_config") + headers = { # A base64url-encoded auth configuration - "X-Registry-Auth": "" + "X-Registry-Auth": encode_auth_header(auth_config) + if auth_config + else "" } params = { @@ -204,7 +208,8 @@ "tlsVerify": kwargs.get("tlsVerify"), } - name = urllib.parse.quote_plus(repository) + name = f'{repository}:{tag}' if tag else repository + name = urllib.parse.quote_plus(name) response = self.client.post(f"/images/{name}/push", params=params, headers=headers) response.raise_for_status(not_found=ImageNotFound) @@ -244,7 +249,7 @@ # pylint: disable=too-many-locals,too-many-branches def pull( self, repository: str, tag: Optional[str] = None, all_tags: bool = False, **kwargs - ) -> Union[Image, List[Image]]: + ) -> Union[Image, List[Image], Iterator[str]]: """Request Podman service to pull image(s) from repository. Args: @@ -258,8 +263,11 @@ keys to be valid. platform (str) ??? Platform in the format os[/arch[/variant]] tls_verify (bool) - Require TLS verification. Default: True. + stream (bool) - When True, the pull progress will be published as received. + Default: False. Returns: + When stream is True, return a generator publishing the service pull progress. If all_tags is True, return list of Image's rather than Image pulled. Raises: @@ -273,6 +281,15 @@ else: tag = "latest" + auth_config: Optional[Dict[str, str]] = kwargs.get("auth_config") + + headers = { + # A base64url-encoded auth configuration + "X-Registry-Auth": encode_auth_header(auth_config) + if auth_config + else "" + } + params = { "reference": repository, "tlsVerify": kwargs.get("tls_verify"), @@ -294,26 +311,23 @@ if len(tokens) > 2: params["Variant"] = tokens[2] - if "auth_config" in kwargs: - username = kwargs["auth_config"].get("username") - password = kwargs["auth_config"].get("password") - if username is None or password is None: - raise ValueError("'auth_config' requires keys 'username' and 'password'") - params["credentials"] = f"{username}:{password}" - - response = self.client.post("/images/pull", params=params) + stream = kwargs.get("stream", False) + response = self.client.post("/images/pull", params=params, stream=stream, headers=headers) response.raise_for_status(not_found=ImageNotFound) + if stream: + return response.iter_lines() + for item in response.iter_lines(): - body = json.loads(item) - if all_tags and "images" in body: + obj = json.loads(item) + if all_tags and "images" in obj: images: List[Image] = [] - for name in body["images"]: + for name in obj["images"]: images.append(self.get(name)) return images - if "id" in body: - return self.get(body["id"]) + if "id" in obj: + return self.get(obj["id"]) return self.resource() def remove( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/domain/manager.py new/podman-py-4.2.0/podman/domain/manager.py --- old/podman-py-4.0.0/podman/domain/manager.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/domain/manager.py 2022-08-10 15:03:52.000000000 +0200 @@ -5,8 +5,8 @@ from podman.api.client import APIClient -# Methods use this Type when a sub-class of PodmanResource is expected. -PodmanResourceType = TypeVar("PodmanResourceType", bound="PodmanResource") +# Methods use this Type when a subclass of PodmanResource is expected. +PodmanResourceType: TypeVar = TypeVar("PodmanResourceType", bound="PodmanResource") class PodmanResource(ABC): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/domain/system.py new/podman-py-4.2.0/podman/domain/system.py --- old/podman-py-4.0.0/podman/domain/system.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/domain/system.py 2022-08-10 15:03:52.000000000 +0200 @@ -3,6 +3,7 @@ from typing import Any, Dict, Optional from podman.api.client import APIClient +from podman import api logger = logging.getLogger("podman.system") @@ -40,8 +41,8 @@ password: Optional[str] = None, email: Optional[str] = None, registry: Optional[str] = None, - reauth: bool = False, - dockercfg_path: Optional[str] = None, + reauth: Optional[bool] = False, # pylint: disable=unused-argument + dockercfg_path: Optional[str] = None, # pylint: disable=unused-argument ) -> Dict[str, Any]: """Log into Podman service. @@ -50,12 +51,27 @@ password: Registry plaintext password email: Registry account email address registry: URL for registry access. For example, + reauth: Ignored: If True, refresh existing authentication. Default: False + dockercfg_path: Ignored: Path to custom configuration file. https://quay.io/v2 - reauth: If True, refresh existing authentication. Default: False - dockercfg_path: Path to custom configuration file. - Default: $HOME/.config/containers/config.json """ + payload = { + "username": username, + "password": password, + "email": email, + "serveraddress": registry, + } + payload = api.prepare_body(payload) + response = self.client.post( + path="/auth", + headers={"Content-type": "application/json"}, + data=payload, + compatible=True, + ) + response.raise_for_status() + return response.json() + def ping(self) -> bool: """Returns True if service responded with OK.""" response = self.client.head("/_ping") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/__init__.py new/podman-py-4.2.0/podman/tests/__init__.py --- old/podman-py-4.0.0/podman/tests/__init__.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/__init__.py 2022-08-10 15:03:52.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/v4.0.0/libpod" +LIBPOD_URL = "http://%2Frun%2Fapi.sock/v4.2.0/libpod" COMPATIBLE_URL = "http://%2Frun%2Fapi.sock/v1.40" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/integration/test_container_create.py new/podman-py-4.2.0/podman/tests/integration/test_container_create.py --- old/podman-py-4.0.0/podman/tests/integration/test_container_create.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/integration/test_container_create.py 2022-08-10 15:03:52.000000000 +0200 @@ -1,9 +1,10 @@ import unittest +import re + import podman.tests.integration.base as base from podman import PodmanClient - # @unittest.skipIf(os.geteuid() != 0, 'Skipping, not running as root') @@ -23,6 +24,34 @@ for container in self.containers: container.remove(force=True) + def test_container_volume_mount(self): + with self.subTest("Check volume mount"): + volumes = { + 'test_bind_1': {'bind': '/mnt/vol1', 'mode': 'rw'}, + 'test_bind_2': {'bind': '/mnt/vol2', 'extended_mode': ['ro', 'noexec']}, + 'test_bind_3': {'bind': '/mnt/vol3', 'extended_mode': ['noexec'], 'mode': 'rw'}, + } + container = self.client.containers.create(self.alpine_image, volumes=volumes) + container_mounts = container.attrs.get('Mounts', {}) + self.assertEqual(len(container_mounts), len(volumes)) + + for mount in container_mounts: + name = mount.get('Name') + self.assertIn(name, volumes) + test_mount = volumes.get(name) + test_mode = test_mount.get('mode', '') + test_extended_mode = test_mount.get('extended_mode', []) + # check RO/RW + if 'ro' in test_mode or 'ro' in test_extended_mode: + self.assertEqual(mount.get('RW'), False) + + if 'rw' in test_mode or 'rw' in test_extended_mode: + self.assertEqual(mount.get('RW'), True) + + other_options = [o for o in test_extended_mode if o not in ['ro', 'rw']] + for o in other_options: + self.assertIn(o, mount.get('Options')) + def test_container_extra_hosts(self): """Test Container Extra hosts""" extra_hosts = {"host1 host3": "127.0.0.2", "host2": "127.0.0.3"} @@ -42,11 +71,11 @@ proper_container.start() proper_container.wait() logs = b"\n".join(proper_container.logs()).decode() - formatted_hosts = [f"{ip} {hosts}" for hosts, ip in extra_hosts.items()] + formatted_hosts = [f"{ip}\t{hosts}" for hosts, ip in extra_hosts.items()] for hosts_entry in formatted_hosts: self.assertIn(hosts_entry, logs) - def _test_memory_limit(self, parameter_name, host_config_name): + def _test_memory_limit(self, parameter_name, host_config_name, set_mem_limit=False): """Base for tests which checks memory limits""" memory_limit_tests = [ {'value': 1000, 'expected_value': 1000}, @@ -58,19 +87,72 @@ ] for test in memory_limit_tests: - container = self.client.containers.create( - self.alpine_image, **{parameter_name: test['value']} - ) + parameters = {parameter_name: test['value']} + if set_mem_limit: + parameters['mem_limit'] = test['expected_value'] - 100 + + container = self.client.containers.create(self.alpine_image, **parameters) self.containers.append(container) self.assertEqual( container.attrs.get('HostConfig', dict()).get(host_config_name), test['expected_value'], ) + def test_container_ports(self): + """Test ports binding""" + port_tests = { + '97/tcp': '43', + '2/udp': ('127.0.0.1', '939'), + '11123/tcp': [[('127.0.0.1', '11123'), ('127.0.0.1', '112')], ['1123', '159']], + } + for container_port, host_port in port_tests.items(): + if isinstance(host_port, str) or isinstance(host_port, tuple): + self._test_container_ports(container_port, host_port) + else: + for port_option in host_port: + self._test_container_ports(container_port, port_option) + + def _test_container_ports(self, container_port, host_port): + """ "Base for tests to check port binding is configured correctly""" + + def __get_expected_value(container_p, host_p): + """Generate the expected value based on the input""" + if isinstance(host_p, str): + return {container_p: [{'HostIp': '', 'HostPort': host_p}]} + elif isinstance(host_p, tuple): + return {container_p: [{'HostIp': host_p[0], 'HostPort': host_p[1]}]} + else: + host_ports = [] + for host_port in host_p: + if isinstance(host_port, tuple): + host_ports.append({'HostIp': host_port[0], 'HostPort': host_port[1]}) + elif isinstance(host_port, str): + host_ports.append({'HostIp': '', 'HostPort': host_port}) + return {container_p: host_ports} + + expected_value = __get_expected_value(container_port, host_port) + container = self.client.containers.create( + self.alpine_image, ports={container_port: host_port} + ) + self.containers.append(container) + + self.assertTrue( + all( + [ + x in expected_value + for x in container.attrs.get('HostConfig', dict()).get('PortBindings') + ] + ) + ) + def test_container_mem_limit(self): """Test passing memory limit""" self._test_memory_limit('mem_limit', 'Memory') + def test_container_memswap_limit(self): + """Test passing memory swap limit""" + self._test_memory_limit('memswap_limit', 'MemorySwap', set_mem_limit=True) + def test_container_mem_reservation(self): """Test passing memory reservation""" self._test_memory_limit('mem_reservation', 'MemoryReservation') @@ -79,6 +161,100 @@ """Test passing shared memory size""" self._test_memory_limit('shm_size', 'ShmSize') + def test_container_mounts(self): + """Test passing mounts""" + with self.subTest("Check bind mount"): + mount = { + "type": "bind", + "source": "/etc/hosts", + "target": "/test", + "read_only": True, + "relabel": "Z", + } + container = self.client.containers.create( + self.alpine_image, command=["cat", "/test"], mounts=[mount] + ) + self.containers.append(container) + self.assertIn( + f"{mount['source']}:{mount['target']}:ro,Z,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) + + with self.subTest("Check tmpfs mount"): + mount = {"type": "tmpfs", "source": "tmpfs", "target": "/test", "size": "456k"} + container = self.client.containers.create( + self.alpine_image, command=["df", "-h"], mounts=[mount] + ) + self.containers.append(container) + self.assertEqual( + container.attrs.get('HostConfig', {}).get('Tmpfs', {}).get(mount['target']), + f"size={mount['size']},rw,rprivate,nosuid,nodev,tmpcopyup", + ) + + container.start() + container.wait() + + logs = b"\n".join(container.logs()).decode() + + self.assertTrue( + re.search( + rf"{mount['size'].replace('k', '.0K')}.*?{mount['target']}", + logs, + flags=re.MULTILINE, + ) + ) + + def test_container_devices(self): + devices = ["/dev/null:/dev/foo", "/dev/zero:/dev/bar"] + container = self.client.containers.create( + self.alpine_image, devices=devices, command=["ls", "-l", "/dev/"] + ) + self.containers.append(container) + + container_devices = container.attrs.get('HostConfig', {}).get('Devices', []) + with self.subTest("Check devices in container object"): + for device in devices: + path_on_host, path_in_container = device.split(':', 1) + self.assertTrue( + any( + [ + c.get('PathOnHost') == path_on_host + and c.get('PathInContainer') == path_in_container + for c in container_devices + ] + ) + ) + + with self.subTest("Check devices in running container object"): + container.start() + container.wait() + + logs = b"\n".join(container.logs()).decode() + + device_regex = r'(\d+, *?\d+).*?{}\n' + + for device in devices: + # check whether device exists + source_device, destination_device = device.split(':', 1) + source_match = re.search( + device_regex.format(source_device.rsplit("/", 1)[-1]), logs + ) + destination_match = re.search( + device_regex.format(destination_device.rsplit("/", 1)[-1]), logs + ) + + self.assertIsNotNone(source_match) + self.assertIsNotNone(destination_match) + + # validate if proper device was added (by major/minor numbers) + self.assertEqual(source_match.group(1), destination_match.group(1)) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/integration/test_containers.py new/podman-py-4.2.0/podman/tests/integration/test_containers.py --- old/podman-py-4.0.0/podman/tests/integration/test_containers.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/integration/test_containers.py 2022-08-10 15:03:52.000000000 +0200 @@ -146,6 +146,17 @@ with self.assertRaises(NotFound): self.client.containers.get(top_ctnr.id) + def test_container_commit(self): + """Commit new image.""" + busybox = self.client.images.pull("quay.io/libpod/busybox", tag="latest") + container = self.client.containers.create( + busybox, command=["echo", f"{random.getrandbits(160):x}"] + ) + + image = container.commit(repository="busybox.local", tag="unittest") + self.assertIn("localhost/busybox.local:unittest", image.attrs["RepoTags"]) + busybox.remove(force=True) + if __name__ == '__main__': unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/integration/test_images.py new/podman-py-4.2.0/podman/tests/integration/test_images.py --- old/podman-py-4.0.0/podman/tests/integration/test_images.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/integration/test_images.py 2022-08-10 15:03:52.000000000 +0200 @@ -14,13 +14,18 @@ # """Images integration tests.""" import io +import queue import tarfile +import threading +import types import unittest +from contextlib import suppress +from datetime import datetime, timedelta import podman.tests.integration.base as base from podman import PodmanClient from podman.domain.images import Image -from podman.errors import ImageNotFound, APIError +from podman.errors import APIError, ImageNotFound # @unittest.skipIf(os.geteuid() != 0, 'Skipping, not running as root') @@ -124,3 +129,7 @@ image, stream = self.client.images.build(fileobj=buffer) self.assertIsNotNone(image) self.assertIsNotNone(image.id) + + def test_pull_stream(self): + generator = self.client.images.pull("ubi8", tag="latest", stream=True) + self.assertIsInstance(generator, types.GeneratorType) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/integration/test_manifests.py new/podman-py-4.2.0/podman/tests/integration/test_manifests.py --- old/podman-py-4.0.0/podman/tests/integration/test_manifests.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/integration/test_manifests.py 2022-08-10 15:03:52.000000000 +0200 @@ -66,7 +66,7 @@ with self.subTest("Remove digest"): manifest.remove(self.alpine_image.attrs["RepoDigests"][0]) - self.assertEqual(len(manifest.attrs["manifests"]), 0) + self.assertIsNone(manifest.attrs["manifests"]) def test_create_409(self): """Test that invalid Image names are caught and not corrupt storage.""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/integration/test_system.py new/podman-py-4.2.0/podman/tests/integration/test_system.py --- old/podman-py-4.0.0/podman/tests/integration/test_system.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/integration/test_system.py 2022-08-10 15:03:52.000000000 +0200 @@ -16,6 +16,7 @@ import podman.tests.integration.base as base from podman import PodmanClient +from podman.errors import APIError class SystemIntegrationTest(base.IntegrationTest): @@ -44,3 +45,15 @@ self.assertTrue('Images' in output) self.assertTrue('Containers' in output) self.assertTrue('Volumes' in output) + + def test_login(self): + """integration: system login call""" + # here, we just test the sanity of the endpoint + # confirming that we get through to podman, and get tcp rejected. + with self.assertRaises(APIError) as e: + next( + self.client.login( + "fake_user", "fake_password", "fake_email@fake_domain.test", "fake_registry" + ) + ) + self.assertIn("lookup fake_registry: no such host", e.exception.explanation) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/unit/test_container.py new/podman-py-4.2.0/podman/tests/unit/test_container.py --- old/podman-py-4.0.0/podman/tests/unit/test_container.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/unit/test_container.py 2022-08-10 15:03:52.000000000 +0200 @@ -306,7 +306,7 @@ "&container=87e1325c82424e49a00abdd4de08009eb76c7de8d228426a9b8af9318ced5ecd&format=docker" "&pause=True&repo=quay.local&tag=unittest", status_code=201, - json={"ID": "d2459aad75354ddc9b5b23f863786e279637125af6ba4d4a83f881866b3c903f"}, + json={"Id": "d2459aad75354ddc9b5b23f863786e279637125af6ba4d4a83f881866b3c903f"}, ) get_adapter = mock.get( tests.LIBPOD_URL diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/tests/unit/test_imagesmanager.py new/podman-py-4.2.0/podman/tests/unit/test_imagesmanager.py --- old/podman-py-4.0.0/podman/tests/unit/test_imagesmanager.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/tests/unit/test_imagesmanager.py 2022-08-10 15:03:52.000000000 +0200 @@ -417,7 +417,7 @@ @requests_mock.Mocker() def test_push(self, mock): - mock.post(tests.LIBPOD_URL + "/images/quay.io%2ffedora/push") + mock.post(tests.LIBPOD_URL + "/images/quay.io%2Ffedora%3Alatest/push") report = self.client.images.push("quay.io/fedora", "latest") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/podman/version.py new/podman-py-4.2.0/podman/version.py --- old/podman-py-4.0.0/podman/version.py 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/podman/version.py 2022-08-10 15:03:52.000000000 +0200 @@ -1,4 +1,4 @@ """Version of PodmanPy.""" -__version__ = "4.0.0" +__version__ = "4.2.0" __compatible_version__ = "1.40" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/pyproject.toml new/podman-py-4.2.0/pyproject.toml --- old/podman-py-4.0.0/pyproject.toml 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/pyproject.toml 2022-08-10 15:03:52.000000000 +0200 @@ -1,7 +1,7 @@ [tool.black] line-length = 100 skip-string-normalization = true -experimental-string-processing = true +preview = true target-version = ["py36"] include = '\.pyi?$' exclude = ''' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/python-podman.spec.rpkg new/podman-py-4.2.0/python-podman.spec.rpkg --- old/podman-py-4.0.0/python-podman.spec.rpkg 1970-01-01 01:00:00.000000000 +0100 +++ new/podman-py-4.2.0/python-podman.spec.rpkg 2022-08-10 15:03:52.000000000 +0200 @@ -0,0 +1,109 @@ +# For automatic rebuilds in COPR + +# The following tag is to get correct syntax highlighting for this file in vim text editor +# vim: syntax=spec + +%global debug_package %{nil} + +%if ! 0%{?fedora} && 0%{?rhel} <= 8 +%global old_rhel 1 +%else +%global old_rhel 0 +%endif + +%global pypi_name podman +%global desc %{pypi_name} is a library of bindings to use the RESTful API for Podman. + +# git_dir_name returns repository name derived from remote Git repository URL +Name: python-podman + +Epoch: 101 + +# git_dir_version returns version based on commit and tag history of the Git project +Version: {{{ git_dir_version }}} + +# This can be useful later for adding downstream patches +Release: 1%{?dist} + +# Basic description of the package +Summary: Manage Pods, Containers and Container Images + +# License. We assume GPLv2+ here. +License: ASL 2.0 + +# Home page of the project. Can also point to the public Git repository page. +URL: https://github.com/containers/podman-py + +# Detailed information about the source Git repository and the source commit +# for the created rpm package +VCS: {{{ git_dir_vcs }}} + +# git_dir_pack macro places the repository content (the source files) into a tarball +# and returns its filename. The tarball will be used to build the rpm. +Source: {{{ git_dir_pack }}} + +%description +%desc + +%package -n python%{python3_pkgversion}-%{pypi_name} +BuildRequires: git-core +BuildRequires: python%{python3_pkgversion}-devel +%if %{?old_rhel} +BuildRequires: python%{python3_pkgversion}-pytoml +BuildRequires: python%{python3_pkgversion}-pyxdg +BuildRequires: python%{python3_pkgversion}-requests +BuildRequires: python%{python3_pkgversion}-setuptools +Requires: python%{python3_pkgversion}-pytoml +Requires: python%{python3_pkgversion}-pyxdg +Requires: python%{python3_pkgversion}-requests +%else +BuildRequires: pyproject-rpm-macros +%endif +%if 0%{?fedora} <= 35 && ! 0%{?rhel} +BuildRequires: python%{python3_pkgversion}-toml +Requires: python%{python3_pkgversion}-toml +%endif +Provides: %{pypi_name}-py = %{version}-%{release} +Summary: %{summary} +%{?python_provide:%python_provide python%{python3_pkgversion}-%{pypi_name}} + +%description -n python%{python3_pkgversion}-%{pypi_name} +%desc + +# The following four sections already describe the rpm build process itself. +# prep will extract the tarball defined as Source above and descend into it. +%prep +{{{ git_dir_setup_macro }}} + +%if ! %{?old_rhel} +%generate_buildrequires +%pyproject_buildrequires %{?with_tests:-t} +%endif + +# This will invoke `make` command in the directory with the extracted sources. +%build +%if %{?old_rhel} +%py3_build +%else +%pyproject_wheel +%endif + +%install +%if %{?old_rhel} +%py3_install +%else +%pyproject_install +%endif + +# This lists all the files that are included in the rpm package and that +# are going to be installed into target system where the rpm is installed. +%files -n python3-podman +%license LICENSE +%doc README.md +%{python3_sitelib}/podman/* +%{python3_sitelib}/podman-*/* + +# Finally, changes from the latest release of your application are generated from +# your project's Git history. It will be empty until you make first annotated Git tag. +%changelog +{{{ git_dir_changelog }}} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/podman-py-4.0.0/tox.ini new/podman-py-4.2.0/tox.ini --- old/podman-py-4.0.0/tox.ini 2022-02-28 17:35:01.000000000 +0100 +++ new/podman-py-4.2.0/tox.ini 2022-08-10 15:03:52.000000000 +0200 @@ -1,6 +1,6 @@ [tox] minversion = 3.2.0 -envlist = pylint,coverage,py36,py38,py39,py310 +envlist = pylint,coverage,py36,py38,py39,py310,py311 ignore_basepython_conflict = true [testenv]