Skip to content

Commit 2db003f

Browse files
ruromeroclaude
andauthored
fix(image): gracefully handle missing Docker/Podman engine (#539)
## Summary - `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 - Updated test to assert empty string return instead of exception ## Test plan - [ ] Verify image analysis works normally when Docker is available - [ ] Verify image analysis degrades gracefully when neither Docker nor Podman is installed - [ ] Run existing `ImageUtilsTest` suite 🤖 Generated with [Claude Code](https://claude.com/claude-code) ## Summary by Sourcery Handle missing container engine binaries gracefully when retrieving image host info. Bug Fixes: - Return an empty string from ImageUtils.hostInfo when the Docker/Podman binary is missing or returns only error output instead of throwing a RuntimeException. Tests: - Update ImageUtilsTest to assert an empty string result when no Docker path is available instead of expecting an exception. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7d20efe commit 2db003f

2 files changed

Lines changed: 40 additions & 4 deletions

File tree

src/main/java/io/github/guacsec/trustifyda/image/ImageUtils.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import com.fasterxml.jackson.databind.node.ObjectNode;
2525
import com.fasterxml.jackson.databind.node.TextNode;
2626
import com.github.packageurl.MalformedPackageURLException;
27+
import io.github.guacsec.trustifyda.logging.LoggersFactory;
2728
import io.github.guacsec.trustifyda.tools.Operations;
2829
import io.github.guacsec.trustifyda.utils.Environment;
2930
import java.io.File;
@@ -35,11 +36,14 @@
3536
import java.util.Map;
3637
import java.util.Objects;
3738
import java.util.function.Supplier;
39+
import java.util.logging.Logger;
3840
import java.util.stream.Collectors;
3941
import java.util.stream.StreamSupport;
4042

4143
public class ImageUtils {
4244

45+
private static final Logger LOG = LoggersFactory.getLogger(ImageUtils.class.getName());
46+
4347
static final String TRUSTIFY_DA_SYFT_CONFIG_PATH = "TRUSTIFY_DA_SYFT_CONFIG_PATH";
4448
static final String TRUSTIFY_DA_SYFT_IMAGE_SOURCE = "TRUSTIFY_DA_SYFT_IMAGE_SOURCE";
4549
static final String TRUSTIFY_DA_IMAGE_PLATFORM = "TRUSTIFY_DA_IMAGE_PLATFORM";
@@ -281,10 +285,21 @@ static String hostInfo(String engine, String info) {
281285
var exec = Operations.getCustomPathOrElse(engine);
282286
var cmd = new String[] {exec, "info"};
283287

284-
var output = Operations.runProcessGetFullOutput(null, cmd, null);
288+
Operations.ProcessExecOutput output;
289+
try {
290+
output = Operations.runProcessGetFullOutput(null, cmd, null);
291+
} catch (RuntimeException e) {
292+
if (e.getCause() instanceof IOException) {
293+
LOG.warning(
294+
String.format(
295+
"Container engine '%s' not available: %s", exec, e.getCause().getMessage()));
296+
return "";
297+
}
298+
throw e;
299+
}
285300
if (output.getOutput().isEmpty()
286301
&& (!output.getError().isEmpty() || output.getExitCode() != 0)) {
287-
throw new RuntimeException(output.getError());
302+
return "";
288303
}
289304

290305
return output

src/test/java/io/github/guacsec/trustifyda/image/ImageUtilsTest.java

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static org.assertj.core.api.Assertions.assertThat;
2828
import static org.junit.jupiter.api.Assertions.assertEquals;
2929
import static org.junit.jupiter.api.Assertions.assertNull;
30+
import static org.junit.jupiter.api.Assertions.assertSame;
3031
import static org.junit.jupiter.api.Assertions.assertThrows;
3132
import static org.junit.jupiter.api.Assertions.assertTrue;
3233
import static org.mockito.AdditionalMatchers.aryEq;
@@ -549,9 +550,29 @@ void test_host_info_no_docker_path() {
549550
isNull(), aryEq(new String[] {"docker", "info"}), isNull()))
550551
.thenReturn(output);
551552

552-
var exception =
553+
var result = ImageUtils.hostInfo("docker", "info");
554+
assertEquals("", result);
555+
}
556+
}
557+
558+
@Test
559+
void test_host_info_other_runtime_exceptions_propagate() {
560+
try (MockedStatic<Operations> mock = Mockito.mockStatic(Operations.class)) {
561+
RuntimeException expected = new RuntimeException("non-io-error");
562+
563+
mock.when(() -> Operations.getCustomPathOrElse(eq("docker"))).thenReturn("docker");
564+
565+
mock.when(() -> Operations.getExecutable(eq("docker"), any())).thenReturn("docker");
566+
567+
mock.when(
568+
() ->
569+
Operations.runProcessGetFullOutput(
570+
isNull(), aryEq(new String[] {"docker", "info"}), isNull()))
571+
.thenThrow(expected);
572+
573+
RuntimeException thrown =
553574
assertThrows(RuntimeException.class, () -> ImageUtils.hostInfo("docker", "info"));
554-
assertEquals("test-error", exception.getMessage());
575+
assertSame(expected, thrown);
555576
}
556577
}
557578

0 commit comments

Comments
 (0)