From d99076a160446beaede4701a67e4af4de396411f 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 8cc3cdbe..0a14853c 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 c51cef8b..bcadd2b7 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."""