From 859c29abcd15fe6fdfb73b79b5aec3b314eb1b47 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Mon, 3 Nov 2025 10:58:49 +0000 Subject: [PATCH 1/3] feat: support flag '--secrets' when building container images Signed-off-by: Guilherme Silva --- podman/domain/images_build.py | 4 +++ podman/tests/integration/test_images.py | 38 +++++++++++++++++++++++-- podman/tests/unit/test_build.py | 4 ++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/podman/domain/images_build.py b/podman/domain/images_build.py index 8713d1b6..7d8f2983 100644 --- a/podman/domain/images_build.py +++ b/podman/domain/images_build.py @@ -68,6 +68,7 @@ def build(self, **kwargs) -> tuple[Image, Iterator[bytes]]: outputformat (str) - The format of the output image's manifest and configuration data. manifest (str) - add the image to the specified manifest list. Creates manifest list if it does not exist. + secrets (list[str]) - Secret files/envs to expose to the build Returns: first item is the podman.domain.images.Image built @@ -209,6 +210,9 @@ def _render_params(kwargs) -> dict[str, list[Any]]: if "labels" in kwargs: params["labels"] = json.dumps(kwargs.get("labels")) + if "secrets" in kwargs: + params["secrets"] = json.dumps(kwargs.get("secrets")) + if params["dockerfile"] is None: params["dockerfile"] = f".containerfile.{random.getrandbits(160):x}" diff --git a/podman/tests/integration/test_images.py b/podman/tests/integration/test_images.py index e910a651..fa862826 100644 --- a/podman/tests/integration/test_images.py +++ b/podman/tests/integration/test_images.py @@ -15,16 +15,17 @@ """Images integration tests.""" import io +import os import platform import tarfile +import tempfile import types import unittest import podman.tests.integration.base as base from podman import PodmanClient from podman.domain.images import Image -from podman.errors import APIError, ImageNotFound, PodmanError - +from podman.errors import APIError, ContainerError, ImageNotFound, PodmanError # @unittest.skipIf(os.geteuid() != 0, 'Skipping, not running as root') @@ -197,6 +198,39 @@ def add_file(name: str, content: str): self.assertIsNotNone(image) self.assertIsNotNone(image.id) + def test_build_with_secret(self): + with tempfile.TemporaryDirectory() as context_dir: + dockerfile_path = os.path.join(context_dir, "Dockerfile") + with open(dockerfile_path, "w") as f: + f.write(""" + FROM quay.io/libpod/alpine_labels:latest + RUN --mount=type=secret,id=example cat /run/secrets/example > /output.txt + """) + + secret_path = os.path.join(context_dir, "build-secret.txt") + with open(secret_path, "w") as f: + f.write("secret123") + + image, stream = self.client.images.build( + path=context_dir, + secrets=["id=example,src=build-secret.txt"], + dockerfile="Dockerfile", + ) + + self.assertIsNotNone(image) + self.assertIsNotNone(image.id) + + # Verify secret was passed and stored in file (NOT RECOMMENDED for real use cases) + container_out = self.client.containers.run( + image.id, command=["cat", "/output.txt"], remove=True, log_config={"Type": "json-file"} + ) + assert "secret123" in container_out.decode("utf-8") + + # Verify mounted secret file is not present in image + with self.assertRaises(ContainerError) as exc: + self.client.containers.run(image.id, command=["ls", "/run/secrets"], remove=True) + self.assertIn("No such file or directory", b"".join(exc.exception.stderr).decode("utf-8")) + @unittest.skipIf(platform.architecture()[0] == "32bit", "no 32-bit image available") def test_pull_stream(self): generator = self.client.images.pull("ubi8", tag="latest", stream=True) diff --git a/podman/tests/unit/test_build.py b/podman/tests/unit/test_build.py index 5015e269..e83238ce 100644 --- a/podman/tests/unit/test_build.py +++ b/podman/tests/unit/test_build.py @@ -67,7 +67,8 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar): "&cpuperiod=10" "&extrahosts=%7B%22database%22%3A+%22127.0.0.1%22%7D" "&labels=%7B%22Unittest%22%3A+%22true%22%7D" - "&manifest=example%3Av1.2.3", + "&manifest=example%3Av1.2.3" + "&secrets=%5B%22id%3Dexample%2Csrc%3Dpodman-build-secret123%22%5D", text=buffer.getvalue(), ) mock.get( @@ -100,6 +101,7 @@ def test_build(self, mock_prepare_containerfile, mock_create_tar): extra_hosts={"database": "127.0.0.1"}, labels={"Unittest": "true"}, manifest="example:v1.2.3", + secrets=["id=example,src=podman-build-secret123"], ) self.assertIsInstance(image, Image) self.assertEqual(image.id, "032b8b2855fc") From 9f120377a2eb4fe1ef029fa64d707836bc30b777 Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Wed, 5 Nov 2025 15:40:48 +0000 Subject: [PATCH 2/3] Bump pre-commit tmt version to 1.60.0 (fix CI check) Signed-off-by: Guilherme Silva --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3d6a78a9..b7dbbdbb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: # Run the formatter. - id: ruff-format - repo: https://github.com/teemtee/tmt.git - rev: 1.39.0 + rev: 1.60.0 hooks: - id: tmt-lint - repo: https://github.com/pre-commit/mirrors-mypy From 0a07b7d7596bb2603b18e625e98f8e45bed74a4f Mon Sep 17 00:00:00 2001 From: Guilherme Silva Date: Wed, 5 Nov 2025 16:05:01 +0000 Subject: [PATCH 3/3] fix: check existence of secret file directly Signed-off-by: Guilherme Silva --- podman/tests/integration/test_images.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/podman/tests/integration/test_images.py b/podman/tests/integration/test_images.py index fa862826..5711e2b2 100644 --- a/podman/tests/integration/test_images.py +++ b/podman/tests/integration/test_images.py @@ -211,7 +211,7 @@ def test_build_with_secret(self): with open(secret_path, "w") as f: f.write("secret123") - image, stream = self.client.images.build( + image, _ = self.client.images.build( path=context_dir, secrets=["id=example,src=build-secret.txt"], dockerfile="Dockerfile", @@ -224,11 +224,13 @@ def test_build_with_secret(self): container_out = self.client.containers.run( image.id, command=["cat", "/output.txt"], remove=True, log_config={"Type": "json-file"} ) - assert "secret123" in container_out.decode("utf-8") + self.assertIn(b"secret123", container_out) # Verify mounted secret file is not present in image with self.assertRaises(ContainerError) as exc: - self.client.containers.run(image.id, command=["ls", "/run/secrets"], remove=True) + self.client.containers.run( + image.id, command=["cat", "/run/secrets/example"], remove=True + ) self.assertIn("No such file or directory", b"".join(exc.exception.stderr).decode("utf-8")) @unittest.skipIf(platform.architecture()[0] == "32bit", "no 32-bit image available")