From 9bea1c7af9f850d2071ad161921bd1ea77eb0602 Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Thu, 25 Jun 2026 01:24:56 +0200 Subject: [PATCH 1/2] fix(image): gracefully handle missing Docker/Podman engine ImageUtils.hostInfo() now returns an empty string instead of throwing RuntimeException when the container engine binary is missing (IOException) or returns error output. This allows callers to fall back gracefully rather than crashing when neither Docker nor Podman is installed. Co-Authored-By: Claude Opus 4.6 --- .../github/guacsec/trustifyda/image/ImageUtils.java | 12 ++++++++++-- .../guacsec/trustifyda/image/ImageUtilsTest.java | 6 ++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java b/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java index 30da1baa..5c5ff4c4 100644 --- a/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java +++ b/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java @@ -281,10 +281,18 @@ static String hostInfo(String engine, String info) { var exec = Operations.getCustomPathOrElse(engine); var cmd = new String[] {exec, "info"}; - var output = Operations.runProcessGetFullOutput(null, cmd, null); + Operations.ProcessExecOutput output; + try { + output = Operations.runProcessGetFullOutput(null, cmd, null); + } catch (RuntimeException e) { + if (e.getCause() instanceof IOException) { + return ""; + } + throw e; + } if (output.getOutput().isEmpty() && (!output.getError().isEmpty() || output.getExitCode() != 0)) { - throw new RuntimeException(output.getError()); + return ""; } return output diff --git a/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java b/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java index 12838309..211ae8b9 100644 --- a/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java +++ b/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java @@ -27,7 +27,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; @@ -549,9 +548,8 @@ void test_host_info_no_docker_path() { isNull(), aryEq(new String[] {"docker", "info"}), isNull())) .thenReturn(output); - var exception = - assertThrows(RuntimeException.class, () -> ImageUtils.hostInfo("docker", "info")); - assertEquals("test-error", exception.getMessage()); + var result = ImageUtils.hostInfo("docker", "info"); + assertEquals("", result); } } From f493385ab74489cdceb45c581e683e747eb153eb Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Thu, 25 Jun 2026 02:15:20 +0200 Subject: [PATCH 2/2] fix: add logging for IOException catch and test for exception propagation Address Sourcery review feedback: log a warning when a container engine is not available instead of silently returning empty, and add a test verifying that non-IO RuntimeExceptions still propagate. Co-Authored-By: Claude Opus 4.6 --- .../guacsec/trustifyda/image/ImageUtils.java | 7 ++++++ .../trustifyda/image/ImageUtilsTest.java | 23 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java b/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java index 5c5ff4c4..0eb173e2 100644 --- a/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java +++ b/src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import com.github.packageurl.MalformedPackageURLException; +import io.github.guacsec.trustifyda.logging.LoggersFactory; import io.github.guacsec.trustifyda.tools.Operations; import io.github.guacsec.trustifyda.utils.Environment; import java.io.File; @@ -35,11 +36,14 @@ import java.util.Map; import java.util.Objects; import java.util.function.Supplier; +import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.StreamSupport; public class ImageUtils { + private static final Logger LOG = LoggersFactory.getLogger(ImageUtils.class.getName()); + static final String TRUSTIFY_DA_SYFT_CONFIG_PATH = "TRUSTIFY_DA_SYFT_CONFIG_PATH"; static final String TRUSTIFY_DA_SYFT_IMAGE_SOURCE = "TRUSTIFY_DA_SYFT_IMAGE_SOURCE"; static final String TRUSTIFY_DA_IMAGE_PLATFORM = "TRUSTIFY_DA_IMAGE_PLATFORM"; @@ -286,6 +290,9 @@ static String hostInfo(String engine, String info) { output = Operations.runProcessGetFullOutput(null, cmd, null); } catch (RuntimeException e) { if (e.getCause() instanceof IOException) { + LOG.warning( + String.format( + "Container engine '%s' not available: %s", exec, e.getCause().getMessage())); return ""; } throw e; diff --git a/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java b/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java index 211ae8b9..115331dc 100644 --- a/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java +++ b/src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java @@ -27,6 +27,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; @@ -553,6 +555,27 @@ void test_host_info_no_docker_path() { } } + @Test + void test_host_info_other_runtime_exceptions_propagate() { + try (MockedStatic mock = Mockito.mockStatic(Operations.class)) { + RuntimeException expected = new RuntimeException("non-io-error"); + + mock.when(() -> Operations.getCustomPathOrElse(eq("docker"))).thenReturn("docker"); + + mock.when(() -> Operations.getExecutable(eq("docker"), any())).thenReturn("docker"); + + mock.when( + () -> + Operations.runProcessGetFullOutput( + isNull(), aryEq(new String[] {"docker", "info"}), isNull())) + .thenThrow(expected); + + RuntimeException thrown = + assertThrows(RuntimeException.class, () -> ImageUtils.hostInfo("docker", "info")); + assertSame(expected, thrown); + } + } + @Test @SetSystemProperty(key = "TRUSTIFY_DA_DOCKER_PATH", value = mockDockerPath) @SetSystemProperty(key = "PATH", value = mockPath)