From 677977432a191d56310707f91d37616ba81d6ae9 Mon Sep 17 00:00:00 2001 From: Nicola Sella Date: Wed, 31 Jul 2024 15:53:11 +0200 Subject: [PATCH] Add all, external, and label to Image.prune() Param all is now supported by prune. Param external is now supported by prune. Filter by label, which was already supported, is now documented and tested. Resolves: https://github.com/containers/podman-py/issues/312 Signed-off-by: Nicola Sella --- podman/domain/images_manager.py | 23 ++++++++-- podman/tests/unit/test_imagesmanager.py | 58 +++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/podman/domain/images_manager.py b/podman/domain/images_manager.py index 0ebf57c5..416b21dd 100644 --- a/podman/domain/images_manager.py +++ b/podman/domain/images_manager.py @@ -135,24 +135,39 @@ def load(self, data: bytes) -> Generator[Image, None, None]: yield self.get(item) def prune( - self, filters: Optional[Mapping[str, Any]] = None + self, + all: Optional[bool] = False, # pylint: disable=redefined-builtin + external: Optional[bool] = False, + filters: Optional[Mapping[str, Any]] = None, ) -> Dict[Literal["ImagesDeleted", "SpaceReclaimed"], Any]: """Delete unused images. The Untagged keys will always be "". Args: + all: Remove all images not in use by containers, not just dangling ones. + external: Remove images even when they are used by external containers + (e.g, by build containers). filters: Qualify Images to prune. Available filters: - dangling (bool): when true, only delete unused and untagged images. + - label: (dict): filter by label. + Examples: + filters={"label": {"key": "value"}} + filters={"label!": {"key": "value"}} - until (str): Delete images older than this timestamp. Raises: APIError: when service returns an error """ - response = self.client.post( - "/images/prune", params={"filters": api.prepare_filters(filters)} - ) + + params = { + "all": all, + "external": external, + "filters": api.prepare_filters(filters), + } + + response = self.client.post("/images/prune", params=params) response.raise_for_status() deleted: List[Dict[str, str]] = [] diff --git a/podman/tests/unit/test_imagesmanager.py b/podman/tests/unit/test_imagesmanager.py index 6de8910f..dbe648ae 100644 --- a/podman/tests/unit/test_imagesmanager.py +++ b/podman/tests/unit/test_imagesmanager.py @@ -207,6 +207,64 @@ def test_prune_filters(self, mock): self.assertEqual(len(untagged), 2) self.assertEqual(len("".join(untagged)), 0) + @requests_mock.Mocker() + def test_prune_filters_label(self, mock): + """Unit test filters param label for Images prune().""" + mock.post( + tests.LIBPOD_URL + + "/images/prune?filters=%7B%22label%22%3A+%5B%22%7B%27license%27%3A+%27Apache-2.0%27%7D%22%5D%7D", + json=[ + { + "Id": "326dd9d7add24646a325e8eaa82125294027db2332e49c5828d96312c5d773ab", + "Size": 1024, + }, + ], + ) + + report = self.client.images.prune(filters={"label": {"license": "Apache-2.0"}}) + self.assertIn("ImagesDeleted", report) + self.assertIn("SpaceReclaimed", report) + + self.assertEqual(report["SpaceReclaimed"], 1024) + + deleted = [r["Deleted"] for r in report["ImagesDeleted"] if "Deleted" in r] + self.assertEqual(len(deleted), 1) + self.assertIn("326dd9d7add24646a325e8eaa82125294027db2332e49c5828d96312c5d773ab", deleted) + self.assertGreater(len("".join(deleted)), 0) + + untagged = [r["Untagged"] for r in report["ImagesDeleted"] if "Untagged" in r] + self.assertEqual(len(untagged), 1) + self.assertEqual(len("".join(untagged)), 0) + + @requests_mock.Mocker() + def test_prune_filters_not_label(self, mock): + """Unit test filters param NOT-label for Images prune().""" + mock.post( + tests.LIBPOD_URL + + "/images/prune?filters=%7B%22label%21%22%3A+%5B%22%7B%27license%27%3A+%27Apache-2.0%27%7D%22%5D%7D", + json=[ + { + "Id": "c4b16966ecd94ffa910eab4e630e24f259bf34a87e924cd4b1434f267b0e354e", + "Size": 1024, + }, + ], + ) + + report = self.client.images.prune(filters={"label!": {"license": "Apache-2.0"}}) + self.assertIn("ImagesDeleted", report) + self.assertIn("SpaceReclaimed", report) + + self.assertEqual(report["SpaceReclaimed"], 1024) + + deleted = [r["Deleted"] for r in report["ImagesDeleted"] if "Deleted" in r] + self.assertEqual(len(deleted), 1) + self.assertIn("c4b16966ecd94ffa910eab4e630e24f259bf34a87e924cd4b1434f267b0e354e", deleted) + self.assertGreater(len("".join(deleted)), 0) + + untagged = [r["Untagged"] for r in report["ImagesDeleted"] if "Untagged" in r] + self.assertEqual(len(untagged), 1) + self.assertEqual(len("".join(untagged)), 0) + @requests_mock.Mocker() def test_prune_failure(self, mock): """Unit test to report error carried in response body."""