From 04dd546af030367c31aa277fd677d3273dc59ca4 Mon Sep 17 00:00:00 2001 From: Milan Balazs Date: Sat, 14 Sep 2024 19:09:05 +0200 Subject: [PATCH] Fix the 'remove' and 'detach' usage. Signed-off-by: Milan Balazs --- podman/api/client.py | 4 +++ podman/domain/containers_run.py | 14 ++++++++++ podman/domain/images_manager.py | 20 +++++++++++++-- podman/domain/system.py | 15 ++++++++++- podman/tests/unit/test_imagesmanager.py | 34 ++++++++++++++++++++++++- 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/podman/api/client.py b/podman/api/client.py index 7f5e3f37..dd40eb54 100644 --- a/podman/api/client.py +++ b/podman/api/client.py @@ -320,6 +320,7 @@ def post( 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 @@ -394,6 +395,7 @@ def _request( 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 @@ -412,6 +414,7 @@ def _request( # TODO should we have an option for HTTPS support? # Build URL for operation from base_url uri = urllib.parse.ParseResult( + # TODO: Does it make sense: "https" if kwargs.get("verify", None) else "http" ? "http", self.base_url.netloc, urllib.parse.urljoin(path_prefix, path), @@ -429,6 +432,7 @@ def _request( data=data, headers=(headers or {}), stream=stream, + verify=kwargs.get("verify", None), **timeout_kw, ) ) diff --git a/podman/domain/containers_run.py b/podman/domain/containers_run.py index 945034e4..97d3a1df 100644 --- a/podman/domain/containers_run.py +++ b/podman/domain/containers_run.py @@ -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 @@ -67,7 +68,20 @@ def run( 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 --git a/podman/domain/images_manager.py b/podman/domain/images_manager.py index 51cbc14f..d0c912ba 100644 --- a/podman/domain/images_manager.py +++ b/podman/domain/images_manager.py @@ -14,7 +14,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 ( @@ -113,11 +113,14 @@ def get_registry_data( collection=self, ) - def load(self, data: bytes) -> Generator[Image, None, None]: + def load( + self, data: Optional[bytes] = None, file_path: Optional[str] = None + ) -> Generator[Image, None, None]: """Restore an image previously saved. Args: data: Image to be loaded in tarball format. + file_path: Path of the Tarball. Raises: APIError: when service returns an error @@ -125,6 +128,19 @@ def load(self, data: bytes) -> Generator[Image, None, None]: # TODO fix podman swagger cannot use this header! # headers = {"Content-type": "application/x-www-form-urlencoded"} + 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." + ) + + if file_path: + # Load a tarball containing the image + with open(file_path, "rb") as tarball_file: + data = tarball_file.read() # Read the tarball file as bytes + response = self.client.post( "/images/load", data=data, headers={"Content-type": "application/x-tar"} ) diff --git a/podman/domain/system.py b/podman/domain/system.py index dd965717..8642e8f6 100644 --- a/podman/domain/system.py +++ b/podman/domain/system.py @@ -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 @@ -44,6 +44,10 @@ def login( registry: Optional[str] = None, reauth: Optional[bool] = False, # pylint: disable=unused-argument dockercfg_path: Optional[str] = None, # pylint: disable=unused-argument + 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 @@ def login( reauth: Ignored: If True, refresh existing authentication. Default: False dockercfg_path: Ignored: Path to custom configuration file. https://quay.io/v2 + auth = None, + 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 @@ def login( "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 @@ def login( 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 --git a/podman/tests/unit/test_imagesmanager.py b/podman/tests/unit/test_imagesmanager.py index c51cef8b..0db5079a 100644 --- a/podman/tests/unit/test_imagesmanager.py +++ b/podman/tests/unit/test_imagesmanager.py @@ -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 @@ def test_remove(self, mock): @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') + + with patch("builtins.open", mock_open(read_data=b"mock tarball data")) as mock_file: + 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_file.assert_called_once_with("mock_file.tar", "rb") + mock.post( tests.LIBPOD_URL + "/images/load", json={"Names": ["quay.io/fedora:latest"]},