Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #232: Linux Connector CLI Not Functioning Locally #247

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public class OsCommandService {

private static final String NEGATIVE_TIMEOUT = "timeout mustn't be negative nor zero.";

private static final String[] LOCAL_SHELL_COMMAND = buildShellCommand();

/**
* Run the given command on the localhost machine.
*
Expand All @@ -96,9 +98,10 @@ public static String runLocalCommand(
) throws InterruptedException, IOException, TimeoutException {
isTrue(timeout > 0, NEGATIVE_TIMEOUT);

final String cmd = LocalOsHandler.isWindows() ? "CMD.EXE /C " + command : command;
final ProcessBuilder builder = createProcessBuilder(command);

final Process process = builder.start();

final Process process = Runtime.getRuntime().exec(cmd);
if (process == null) {
throw new IllegalStateException("Local command Process is null.");
}
Expand Down Expand Up @@ -144,6 +147,77 @@ public static String runLocalCommand(
}
}

/**
* Create a process builder for the given command. The start method of the
* builder should be called to execute the command.
*
* @param command The command to be executed.
* @return The process builder for the given command.
*/
static ProcessBuilder createProcessBuilder(final String command) {
return new ProcessBuilder().command(LOCAL_SHELL_COMMAND[0], LOCAL_SHELL_COMMAND[1], command);
}

/**
* Build the shell to be used for the local command execution based on the operating system.
*
* @return The shell command to be used.
*/
private static String[] buildShellCommand() {
if (LocalOsHandler.isWindows()) {
return new String[] { getComSpecEnvVar(), "/C" };
} else {
return new String[] { getShellEnvVar(), "-c" };
}
}

/**
* Get the shell environment variable for Linux/Unix systems.
*
* @return The shell environment variable or /bin/sh if not found.
*/
private static String getShellEnvVar() {
var shell = System.getenv("SHELL");
if (shell == null || shell.isBlank()) {
// List of common shells to check
final String[] commonShells = {
"/bin/bash",
"/usr/bin/bash",
"/bin/sh",
"/usr/bin/sh",
"/bin/zsh",
"/usr/bin/zsh",
"/bin/ksh",
"/usr/bin/ksh"
};

// Find the first common shell that exists
for (String s : commonShells) {
if (new File(s).exists()) {
shell = s;
break;
}
}
// Fallback if no common shell is found
if (shell == null || shell.isBlank()) {
shell = "/bin/sh"; // Minimal fallback
}
}
return shell;
}

/**
* Get the ComSpec environment variable for Windows systems.
* @return The ComSpec environment variable or cmd.exe if not found.
*/
private static String getComSpecEnvVar() {
var comSpec = System.getenv("ComSpec");
if (comSpec == null || comSpec.isBlank()) {
comSpec = "cmd.exe";
}
return comSpec;
}

/**
* Run an SSH command, checking if it can be executed on localhost or remotely.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@
import org.mockito.junit.jupiter.MockitoExtension;
import org.sentrysoftware.metricshub.engine.common.exception.ControlledSshException;
import org.sentrysoftware.metricshub.engine.common.exception.NoCredentialProvidedException;
import org.sentrysoftware.metricshub.engine.common.helpers.LocalOsHandler;
import org.sentrysoftware.metricshub.engine.configuration.HostConfiguration;
import org.sentrysoftware.metricshub.engine.connector.model.common.DeviceKind;
import org.sentrysoftware.metricshub.engine.connector.model.common.EmbeddedFile;
Expand Down Expand Up @@ -413,30 +412,37 @@ void testRunLocalCommandKO() throws Exception {
assertThrows(IllegalArgumentException.class, () -> OsCommandService.runLocalCommand(CMD, -1, null));
assertThrows(IllegalArgumentException.class, () -> OsCommandService.runLocalCommand(CMD, 0, null));

// case Process null Linux
final Runtime runtime = mock(Runtime.class);
try (
final MockedStatic<LocalOsHandler> mockedLocalOSHandler = mockStatic(LocalOsHandler.class);
final MockedStatic<Runtime> mockedRuntime = mockStatic(Runtime.class)
) {
mockedLocalOSHandler.when(LocalOsHandler::isWindows).thenReturn(false);
mockedRuntime.when(Runtime::getRuntime).thenReturn(runtime);
when(runtime.exec(CMD)).thenReturn(null);
// Case IllegalStateException when the process cannot be created
final ProcessBuilder processBuilderMock = mock(ProcessBuilder.class);
try (final MockedStatic<OsCommandService> mockedOsCommandService = mockStatic(OsCommandService.class);) {
mockedOsCommandService.when(() -> OsCommandService.runLocalCommand(CMD, 1, null)).thenCallRealMethod();
mockedOsCommandService.when(() -> OsCommandService.createProcessBuilder(CMD)).thenReturn(processBuilderMock);
when(processBuilderMock.start()).thenReturn(null);

assertThrows(IllegalStateException.class, () -> OsCommandService.runLocalCommand(CMD, 1, null));
}
}

// case Process null Windows
try (
final MockedStatic<LocalOsHandler> mockedLocalOSHandler = mockStatic(LocalOsHandler.class);
final MockedStatic<Runtime> mockedRuntime = mockStatic(Runtime.class)
) {
mockedLocalOSHandler.when(LocalOsHandler::isWindows).thenReturn(true);
mockedRuntime.when(Runtime::getRuntime).thenReturn(runtime);
when(runtime.exec(CMD_COMMAND)).thenReturn(null);
@Test
@EnabledOnOs(OS.WINDOWS)
void testCreateProcessBuilderWindows() {
final ProcessBuilder processBuilder = OsCommandService.createProcessBuilder(CMD);
assertNotNull(processBuilder);
final List<String> command = processBuilder.command();
assertNotNull(command.get(0));
assertEquals("/C", command.get(1));
assertEquals(CMD, command.get(2));
}

assertThrows(IllegalStateException.class, () -> OsCommandService.runLocalCommand(CMD, 1, null));
}
@Test
@EnabledOnOs(OS.LINUX)
void testCreateProcessBuilderLinux() {
final ProcessBuilder processBuilder = OsCommandService.createProcessBuilder(CMD);
assertNotNull(processBuilder);
final List<String> command = processBuilder.command();
assertNotNull(command.get(0));
assertEquals("-c", command.get(1));
assertEquals(CMD, command.get(2));
}

@Test
Expand Down