diff --git a/podman/domain/images_build.py b/podman/domain/images_build.py index 66eb90f2..00f35678 100644 --- a/podman/domain/images_build.py +++ b/podman/domain/images_build.py @@ -63,9 +63,10 @@ def build(self, **kwargs) -> tuple[Image, Iterator[bytes]]: isolation (str) – Isolation technology used during build. (ignored) use_config_proxy (bool) – (ignored) http_proxy (bool) - Inject http proxy environment variables into container (Podman only) - layers (bool) - Cache intermediate layers during build. + layers (bool) - Cache intermediate layers during build. Default True. output (str) - specifies if any custom build output is selected for following build. outputformat (str) - The format of the output image's manifest and configuration data. + Default to "application/vnd.oci.image.manifest.v1+json" (OCI format). Returns: first item is the podman.domain.images.Image built @@ -167,7 +168,7 @@ def _render_params(kwargs) -> dict[str, list[Any]]: raise PodmanError("Custom encoding not supported when gzip enabled.") params = { - "dockerfile": kwargs.get("dockerfile"), + "dockerfile": kwargs.get("dockerfile", f".containerfile.{random.getrandbits(160):x}"), "forcerm": kwargs.get("forcerm"), "httpproxy": kwargs.get("http_proxy"), "networkmode": kwargs.get("network_mode"), @@ -181,9 +182,11 @@ def _render_params(kwargs) -> dict[str, list[Any]]: "squash": kwargs.get("squash"), "t": kwargs.get("tag"), "target": kwargs.get("target"), - "layers": kwargs.get("layers"), + "layers": kwargs.get("layers", True), "output": kwargs.get("output"), - "outputformat": kwargs.get("outputformat"), + "outputformat": kwargs.get( + "outputformat", "application/vnd.oci.image.manifest.v1+json" + ), } if "buildargs" in kwargs: @@ -204,8 +207,5 @@ def _render_params(kwargs) -> dict[str, list[Any]]: if "labels" in kwargs: params["labels"] = json.dumps(kwargs.get("labels")) - if params["dockerfile"] is None: - params["dockerfile"] = f".containerfile.{random.getrandbits(160):x}" - # Remove any unset parameters return dict(filter(lambda i: i[1] is not None, params.items())) diff --git a/podman/tests/integration/test_images.py b/podman/tests/integration/test_images.py index a8636411..4f5b88dc 100644 --- a/podman/tests/integration/test_images.py +++ b/podman/tests/integration/test_images.py @@ -15,6 +15,7 @@ """Images integration tests.""" import io +import json import platform import tarfile import types @@ -144,6 +145,19 @@ def test_build(self): self.assertIsNotNone(image) self.assertIsNotNone(image.id) + def test_build_cache(self): + """Check that building twice the same image uses caching""" + buffer = io.StringIO("""FROM quay.io/libpod/alpine_labels:latest\nLABEL test=value""") + image, _ = self.client.images.build(fileobj=buffer) + buffer.seek(0) + _, stream = self.client.images.build(fileobj=buffer) + for line in stream: + # Search for a line with contents "-> Using cache " + parsed = json.loads(line)['stream'] + if "Using cache" in parsed: + break + self.assertEqual(parsed.split()[3], image.id) + def test_build_with_context(self): context = io.BytesIO() with tarfile.open(fileobj=context, mode="w") as tar: diff --git a/podman/tests/unit/test_build.py b/podman/tests/unit/test_build.py index 6adf0fe8..207d2864 100644 --- a/podman/tests/unit/test_build.py +++ b/podman/tests/unit/test_build.py @@ -1,4 +1,5 @@ import io +import requests import json import unittest @@ -16,6 +17,20 @@ from podman.domain.images import Image from podman.errors import BuildError, DockerException +good_image_id = "032b8b2855fc" +good_stream = [ + {"stream": " ---\u003e a9eb17255234"}, + {"stream": "Step 1 : VOLUME /data"}, + {"stream": " ---\u003e Running in abdc1e6896c6"}, + {"stream": " ---\u003e 713bca62012e"}, + {"stream": "Removing intermediate container abdc1e6896c6"}, + {"stream": "Step 2 : CMD [\"/bin/sh\"]"}, + {"stream": " ---\u003e Running in dba30f2a1a7e"}, + {"stream": " ---\u003e 032b8b2855fc"}, + {"stream": "Removing intermediate container dba30f2a1a7e"}, + {"stream": f"{good_image_id}\n"}, +] + class TestBuildCase(unittest.TestCase): """Test ImagesManager build(). @@ -41,19 +56,7 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar): mock_prepare_containerfile.return_value = "Containerfile" mock_create_tar.return_value = b"This is a mocked tarball." - stream = [ - {"stream": " ---\u003e a9eb17255234"}, - {"stream": "Step 1 : VOLUME /data"}, - {"stream": " ---\u003e Running in abdc1e6896c6"}, - {"stream": " ---\u003e 713bca62012e"}, - {"stream": "Removing intermediate container abdc1e6896c6"}, - {"stream": "Step 2 : CMD [\"/bin/sh\"]"}, - {"stream": " ---\u003e Running in dba30f2a1a7e"}, - {"stream": " ---\u003e 032b8b2855fc"}, - {"stream": "Removing intermediate container dba30f2a1a7e"}, - {"stream": "032b8b2855fc\n"}, - ] - + stream = good_stream buffer = io.StringIO() for entry in stream: buffer.write(json.JSONEncoder().encode(entry)) @@ -70,9 +73,9 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar): text=buffer.getvalue(), ) mock.get( - tests.LIBPOD_URL + "/images/032b8b2855fc/json", + tests.LIBPOD_URL + f"/images/{good_image_id}/json", json={ - "Id": "032b8b2855fc", + "Id": good_image_id, "ParentId": "", "RepoTags": ["fedora:latest", "fedora:33", ":"], "RepoDigests": [ @@ -100,7 +103,7 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar): labels={"Unittest": "true"}, ) self.assertIsInstance(image, Image) - self.assertEqual(image.id, "032b8b2855fc") + self.assertEqual(image.id, good_image_id) self.assertIsInstance(logs, Iterable) @patch.object(api, "create_tar") @@ -130,16 +133,47 @@ def test_build_logged_error(self, mock_prepare_containerfile, mock_create_tar): @requests_mock.Mocker() def test_build_no_context(self, mock): - mock.post(tests.LIBPOD_URL + "/images/build") + mock.post(tests.LIBPOD_URL + "/build") with self.assertRaises(TypeError): self.client.images.build() @requests_mock.Mocker() def test_build_encoding(self, mock): - mock.post(tests.LIBPOD_URL + "/images/build") + mock.post(tests.LIBPOD_URL + "/build") with self.assertRaises(DockerException): self.client.images.build(path="/root", gzip=True, encoding="utf-8") + @patch.object(api, "create_tar") + @patch.object(api, "prepare_containerfile") + def test_build_defaults(self, mock_prepare_containerfile, mock_create_tar): + """Check the defaults used by images.build""" + mock_prepare_containerfile.return_value = "Containerfile" + mock_create_tar.return_value = b"This is a mocked tarball." + + stream = good_stream + buffer = io.StringIO() + for entry in stream: + buffer.write(json.dumps(entry)) + buffer.write("\n") + + with requests_mock.Mocker() as mock: + query = "?outputformat=" + ( + requests.utils.quote("application/vnd.oci.image.manifest.v1+json", safe='') + + "&layers=True" + ) + mock.post( + tests.LIBPOD_URL + "/build" + query, + text=buffer.getvalue(), + ) + mock.get( + tests.LIBPOD_URL + f"/images/{good_image_id}/json", + json={ + "Id": "unittest", + }, + ) + img, _ = self.client.images.build(path="/tmp/context_dir") + assert img.id == "unittest" + if __name__ == '__main__': unittest.main()