Skip to content

Commit cdf00e0

Browse files
committed
Add macOS Docker and Podman fallback paths to CxWrapper
1 parent d03675d commit cdf00e0

File tree

2 files changed

+111
-17
lines changed

2 files changed

+111
-17
lines changed

src/main/java/com/checkmarx/ast/wrapper/CxConstants.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,14 @@ public final class CxConstants {
9797
static final String PODMAN = "podman";
9898
static final String PODMAN_FALLBACK_PATH = "/usr/local/bin/podman";
9999
static final String DOCKER_FALLBACK_PATH = "/usr/local/bin/docker";
100+
101+
// Additional Docker fallback paths for macOS
102+
// These paths cover various Docker installation methods on macOS:
103+
// - Homebrew on Apple Silicon: /opt/homebrew/bin/docker
104+
// - Docker Desktop CLI tools: ~/.docker/bin/docker (resolved at runtime)
105+
// - Docker.app bundle: /Applications/Docker.app/Contents/Resources/bin/docker
106+
// - Rancher Desktop: ~/.rd/bin/docker (resolved at runtime)
107+
static final String DOCKER_HOMEBREW_PATH = "/opt/homebrew/bin/docker";
108+
static final String DOCKER_APP_PATH = "/Applications/Docker.app/Contents/Resources/bin/docker";
109+
static final String PODMAN_HOMEBREW_PATH = "/opt/homebrew/bin/podman";
100110
}

src/main/java/com/checkmarx/ast/wrapper/CxWrapper.java

Lines changed: 101 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -421,33 +421,117 @@ public String checkEngineExist(@NonNull String engineName) throws CxException, I
421421
}
422422

423423
private String verifyEngineOnMAC(String engineName,List<String>arguments) throws CxException, IOException, InterruptedException {
424-
Exception lastException = null;
425-
String enginePath;
424+
this.logger.debug("Verifying container engine '{}' on macOS", engineName);
425+
426+
// First, try to find the engine via shell command (works when launched from terminal)
426427
try{
427-
enginePath= Execution.executeCommand((arguments), logger, line->line);
428-
return enginePath;
428+
String enginePath = Execution.executeCommand((arguments), logger, line->line);
429+
if (enginePath != null && !enginePath.isEmpty()) {
430+
this.logger.debug("Found engine '{}' via shell command: {}", engineName, enginePath);
431+
return enginePath;
432+
}
429433
} catch (CxException | IOException e) {
430-
lastException = e;
434+
this.logger.debug("Shell command lookup failed for '{}': {}", engineName, e.getMessage());
431435
}
432-
Path dockerPath = Paths.get(CxConstants.DOCKER_FALLBACK_PATH);
433-
Path podmanPath = Paths.get(CxConstants.PODMAN_FALLBACK_PATH);
436+
437+
// Build list of fallback paths based on engine type
438+
// This handles the case when IntelliJ is launched via GUI (double-click) and doesn't inherit shell PATH
439+
List<String> fallbackPaths = new ArrayList<>();
434440
if (CxConstants.DOCKER.equalsIgnoreCase(engineName)) {
435-
if (Files.isSymbolicLink(dockerPath)) {
436-
return Files.readSymbolicLink(dockerPath).toAbsolutePath().toString();
441+
fallbackPaths.add(CxConstants.DOCKER_FALLBACK_PATH); // /usr/local/bin/docker
442+
fallbackPaths.add(CxConstants.DOCKER_HOMEBREW_PATH); // /opt/homebrew/bin/docker (Apple Silicon)
443+
fallbackPaths.add(CxConstants.DOCKER_APP_PATH); // /Applications/Docker.app/Contents/Resources/bin/docker
444+
// Add user home-based paths
445+
String userHome = System.getProperty("user.home");
446+
if (userHome != null) {
447+
fallbackPaths.add(userHome + "/.docker/bin/docker"); // Docker Desktop CLI
448+
fallbackPaths.add(userHome + "/.rd/bin/docker"); // Rancher Desktop
449+
}
450+
} else if (CxConstants.PODMAN.equalsIgnoreCase(engineName)) {
451+
fallbackPaths.add(CxConstants.PODMAN_FALLBACK_PATH); // /usr/local/bin/podman
452+
fallbackPaths.add(CxConstants.PODMAN_HOMEBREW_PATH); // /opt/homebrew/bin/podman (Apple Silicon)
453+
// Add user home-based paths
454+
String userHome = System.getProperty("user.home");
455+
if (userHome != null) {
456+
fallbackPaths.add(userHome + "/.local/bin/podman");
437457
}
438-
else { return dockerPath.toAbsolutePath().toString(); }
439458
}
440-
else if (CxConstants.PODMAN.equalsIgnoreCase(engineName)) {
441-
if (Files.exists(podmanPath)) {
442-
if (Files.isSymbolicLink(podmanPath)) {
443-
return Files.readSymbolicLink(podmanPath).toAbsolutePath().toString();
459+
460+
this.logger.debug("Checking {} fallback paths for engine '{}'", fallbackPaths.size(), engineName);
461+
462+
// Try each fallback path
463+
for (String pathStr : fallbackPaths) {
464+
Path path = Paths.get(pathStr);
465+
this.logger.debug("Checking fallback path: {}", pathStr);
466+
467+
if (Files.exists(path)) {
468+
String resolvedPath;
469+
try {
470+
if (Files.isSymbolicLink(path)) {
471+
resolvedPath = Files.readSymbolicLink(path).toAbsolutePath().toString();
472+
this.logger.debug("Resolved symlink {} -> {}", pathStr, resolvedPath);
473+
} else {
474+
resolvedPath = path.toAbsolutePath().toString();
475+
}
476+
477+
// Verify the engine is executable and works
478+
if (verifyEngineExecutable(resolvedPath)) {
479+
this.logger.info("Found working container engine '{}' at: {}", engineName, resolvedPath);
480+
return resolvedPath;
481+
}
482+
} catch (IOException e) {
483+
this.logger.debug("Failed to resolve path {}: {}", pathStr, e.getMessage());
444484
}
445-
else{
446-
return podmanPath.toAbsolutePath().toString();
485+
} else {
486+
this.logger.debug("Path does not exist: {}", pathStr);
487+
}
488+
}
489+
490+
String errorMsg = String.format(
491+
"Container engine '%s' not found. Checked paths: %s. " +
492+
"Please ensure Docker or Podman is installed and accessible. " +
493+
"If installed via Docker Desktop, try launching IntelliJ from terminal: open -a 'IntelliJ IDEA'",
494+
engineName, String.join(", ", fallbackPaths)
495+
);
496+
this.logger.error(errorMsg);
497+
throw new CxException(1, errorMsg);
498+
}
499+
500+
/**
501+
* Verifies that the engine at the given path is executable and responds to --version.
502+
*
503+
* @param enginePath the absolute path to the container engine executable
504+
* @return true if the engine is working, false otherwise
505+
*/
506+
private boolean verifyEngineExecutable(String enginePath) {
507+
try {
508+
Path path = Paths.get(enginePath);
509+
if (!Files.exists(path) || !Files.isExecutable(path)) {
510+
this.logger.debug("Engine path '{}' is not executable", enginePath);
511+
return false;
512+
}
513+
514+
// Run a quick version check to verify the engine works
515+
ProcessBuilder pb = new ProcessBuilder(enginePath, "--version");
516+
pb.redirectErrorStream(true);
517+
Process process = pb.start();
518+
boolean completed = process.waitFor(5, java.util.concurrent.TimeUnit.SECONDS);
519+
520+
if (completed && process.exitValue() == 0) {
521+
this.logger.debug("Engine at '{}' verified successfully", enginePath);
522+
return true;
523+
} else {
524+
this.logger.debug("Engine at '{}' failed verification (exit code: {}, completed: {})",
525+
enginePath, process.exitValue(), completed);
526+
if (!completed) {
527+
process.destroyForcibly();
447528
}
529+
return false;
448530
}
531+
} catch (Exception e) {
532+
this.logger.debug("Engine verification failed for '{}': {}", enginePath, e.getMessage());
533+
return false;
449534
}
450-
throw new CxException( 1, "Engine '" + engineName + "' is not installed or not symlinked to /usr/local/bin." );
451535
}
452536

453537
private String checkEngine(String engineName, String osType ) throws CxException, IOException, InterruptedException {

0 commit comments

Comments
 (0)