Skip to content

Commit

Permalink
Issue #116: Develop OS Command Extension
Browse files Browse the repository at this point in the history
* Implemented a new class `SudoInformation` to wrap information such as
whether to use sudo, the sudo commands that require sudo and the sudo
command key.

* Prepare OsCommandHelper to be defined in the engine to define a set of
utilities that are required in various extensions.
  • Loading branch information
NassimBtk committed Apr 24, 2024
1 parent eee0abe commit 7e72c97
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import lombok.Data;
import lombok.NoArgsConstructor;
import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException;
import org.sentrysoftware.metricshub.engine.strategy.utils.SudoInformation;

/**
* The OsCommandConfiguration class represents the configuration for executing OS commands in the MetricsHub engine.
Expand All @@ -35,14 +36,13 @@
@NoArgsConstructor
public class OsCommandConfiguration implements IConfiguration {

private static final String SUDO = "sudo";
/**
* Default Timeout
*/
public static final Long DEFAULT_TIMEOUT = 30L;
private boolean useSudo;
private Set<String> useSudoCommands = new HashSet<>();
private String sudoCommand = SUDO;
private String sudoCommand = SudoInformation.SUDO;
private Long timeout = DEFAULT_TIMEOUT;

/**
Expand All @@ -62,7 +62,7 @@ public OsCommandConfiguration(
) {
this.useSudo = useSudo;
this.useSudoCommands = useSudoCommands == null ? new HashSet<>() : useSudoCommands;
this.sudoCommand = sudoCommand == null ? SUDO : sudoCommand;
this.sudoCommand = sudoCommand == null ? SudoInformation.SUDO : sudoCommand;
this.timeout = timeout == null ? DEFAULT_TIMEOUT : timeout;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ public class OsCommandHelper {
* Create the temporary embedded files in the given command line.
*
* @param commandLine The command line to process.
* @param osCommandConfiguration The OS Command Configuration.
* @param sudoInformation The Sudo Information of the Os Command configuration.
* @param commandLineEmbeddedFiles A map of embedded files referenced in the command line.
* @param tempFileCreator The function that creates a temporary file.
* @return A map with EmbeddedFile tags as keys and corresponding temporary File objects.
* @throws IOException If an error occurs during temp file creation.
*/
public static Map<String, File> createOsCommandEmbeddedFiles(
@NonNull final String commandLine,
final OsCommandConfiguration osCommandConfiguration,
final SudoInformation sudoInformation,
@NonNull final Map<String, EmbeddedFile> commandLineEmbeddedFiles,
final Function<String, File> tempFileCreator
) throws IOException {
Expand Down Expand Up @@ -131,7 +131,7 @@ public static Map<String, File> createOsCommandEmbeddedFiles(
state(content != null, () -> "EmbeddedFile content is null. File name: " + fileName);

try {
return createTempFileWithEmbeddedFileContent(embeddedFile, osCommandConfiguration, tempFileCreator);
return createTempFileWithEmbeddedFileContent(embeddedFile, sudoInformation, tempFileCreator);
} catch (final IOException e) {
throw new TempFileCreationException(e);
}
Expand All @@ -152,15 +152,15 @@ public static Map<String, File> createOsCommandEmbeddedFiles(
/**
* Create a temporary file with the content of the embeddedFile.
*
* @param embeddedFile {@link EmbeddedFile} instance used to write the file content (mandatory)
* @param osCommandConfiguration The OS Command Configuration.
* @param tempFileCreator The function that creates a temporary file.
* @param embeddedFile {@link EmbeddedFile} instance used to write the file content (mandatory)
* @param sudoInformation The Sudo Information of the Os Command configuration.
* @param tempFileCreator The function that creates a temporary file.
* @return The File.
* @throws IOException
*/
static File createTempFileWithEmbeddedFileContent(
final EmbeddedFile embeddedFile,
final OsCommandConfiguration osCommandConfiguration,
final SudoInformation sudoInformation,
Function<String, File> tempFileCreator
) throws IOException {
final String extension = embeddedFile.getType() != null ? "." + embeddedFile.getType() : EMPTY;
Expand All @@ -173,7 +173,7 @@ static File createTempFileWithEmbeddedFileContent(
StandardCharsets.UTF_8
)
) {
bufferedWriter.write(replaceSudo(embeddedFile.getContent(), osCommandConfiguration));
bufferedWriter.write(replaceSudo(embeddedFile.getContent(), sudoInformation));
}
return tempFile;
}
Expand All @@ -199,22 +199,22 @@ static File createEmbeddedTempFile(final String extension) {
* If the useSudo configuration is enabled and the sudo command is associated with the specified file,
* it replaces the tag with the sudo command; otherwise, it replaces it with an empty string.
*
* @param text The text containing %{SUDO:xxx}% tags to be replaced.
* @param osCommandConfiguration The configuration for OS commands.
* @param text The text containing %{SUDO:xxx}% tags to be replaced.
* @param sudoInformation The Sudo Information of the Os Command configuration.
* @return The text with %{SUDO:xxx}% tags replaced with the sudo command or empty string.
*/
static String replaceSudo(final String text, final OsCommandConfiguration osCommandConfiguration) {
static String replaceSudo(final String text, final SudoInformation sudoInformation) {
if (text == null || text.isBlank()) {
return text;
}

final Optional<String> maybeSudoFile = getFileNameFromSudoCommand(text);

final String sudoReplace = maybeSudoFile.isPresent() &&
osCommandConfiguration != null &&
osCommandConfiguration.isUseSudo() &&
osCommandConfiguration.getUseSudoCommands().contains(maybeSudoFile.get())
? osCommandConfiguration.getSudoCommand()
sudoInformation != null &&
sudoInformation.isUseSudo() &&
sudoInformation.useSudoCommands().contains(maybeSudoFile.get())
? sudoInformation.sudoCommand()
: EMPTY;

return maybeSudoFile
Expand Down Expand Up @@ -524,9 +524,19 @@ public static OsCommandResult runOsCommand(
.getConfigurations()
.get(OsCommandConfiguration.class);

SudoInformation sudoInformation = null;
if (osCommandConfiguration != null) {
sudoInformation =
new SudoInformation(
osCommandConfiguration.isUseSudo(),
osCommandConfiguration.getUseSudoCommands(),
osCommandConfiguration.getSudoCommand()
);
}

final Map<String, File> embeddedTempFiles = createOsCommandEmbeddedFiles(
commandLine,
osCommandConfiguration,
sudoInformation,
EmbeddedFileHelper.findEmbeddedFiles(commandLine),
TEMP_FILE_CREATOR
);
Expand All @@ -540,7 +550,7 @@ public static OsCommandResult runOsCommand(
hostname
);

final String updatedSudoCommand = replaceSudo(updatedHostnameCommand, osCommandConfiguration);
final String updatedSudoCommand = replaceSudo(updatedHostnameCommand, sudoInformation);

final String updatedEmbeddedFilesCommand = embeddedTempFiles
.entrySet()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.sentrysoftware.metricshub.engine.strategy.utils;

/*-
* ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
* MetricsHub Engine
* ჻჻჻჻჻჻
* Copyright 2023 - 2024 Sentry Software
* ჻჻჻჻჻჻
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
*/

import java.util.Set;
import lombok.Builder;

/**
* Represents the necessary information to manage the sudo commands.
* This record contains settings that determine whether sudo is used, which commands require sudo,
* and the specific sudo command to be used.
*
* @param isUseSudo Indicates if sudo is to be used.
* @param useSudoCommands A set of commands that specifically require sudo.
* @param sudoCommand The actual sudo command string to be used.
*/
@Builder
public record SudoInformation(boolean isUseSudo, Set<String> useSudoCommands, String sudoCommand) {
/**
* Default SUDO command
*/
public static final String SUDO = "sudo";
}
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ void testCreateOsCommandEmbeddedFiles() throws Exception {

final Map<String, File> embeddedTempFiles = OsCommandHelper.createOsCommandEmbeddedFiles(
EMBEDDED_FILE_1_COPY_COMMAND_LINE,
OsCommandConfiguration.builder().useSudo(true).useSudoCommands(Set.of(ARCCONF_PATH)).build(),
new SudoInformation(true, Set.of(ARCCONF_PATH), SudoInformation.SUDO),
commandLineEmbeddedFiles,
jUnitTempFileCreator
);
Expand Down Expand Up @@ -300,45 +300,33 @@ void testCreateOsCommandEmbeddedFiles() throws Exception {
@Test
void testReplaceSudo() {
assertNull(OsCommandHelper.replaceSudo(null, null));
assertNull(OsCommandHelper.replaceSudo(null, OsCommandConfiguration.builder().build()));
final SudoInformation defaultSudoInformation = new SudoInformation(false, Set.of(), SudoInformation.SUDO);
assertNull(OsCommandHelper.replaceSudo(null, defaultSudoInformation));

assertEquals(EMPTY, OsCommandHelper.replaceSudo(EMPTY, null));
assertEquals(EMPTY, OsCommandHelper.replaceSudo(EMPTY, OsCommandConfiguration.builder().build()));
assertEquals(EMPTY, OsCommandHelper.replaceSudo(EMPTY, defaultSudoInformation));
assertEquals(SINGLE_SPACE, OsCommandHelper.replaceSudo(SINGLE_SPACE, null));
assertEquals(SINGLE_SPACE, OsCommandHelper.replaceSudo(SINGLE_SPACE, OsCommandConfiguration.builder().build()));
assertEquals(SINGLE_SPACE, OsCommandHelper.replaceSudo(SINGLE_SPACE, defaultSudoInformation));

assertEquals(TEXT, OsCommandHelper.replaceSudo(TEXT, null));
assertEquals(TEXT, OsCommandHelper.replaceSudo(TEXT, OsCommandConfiguration.builder().build()));
assertEquals(TEXT, OsCommandHelper.replaceSudo(TEXT, defaultSudoInformation));

// Check replace sudo tag with empty string.
assertEquals(SPACE_KEY, OsCommandHelper.replaceSudo(SUDO_KEY, null));
assertEquals(SPACE_KEY, OsCommandHelper.replaceSudo(SUDO_KEY, OsCommandConfiguration.builder().build()));
assertEquals(
SPACE_KEY,
OsCommandHelper.replaceSudo(SUDO_KEY, OsCommandConfiguration.builder().useSudo(true).build())
);
assertEquals(SPACE_KEY, OsCommandHelper.replaceSudo(SUDO_KEY, defaultSudoInformation));
final SudoInformation useSudoInformation = new SudoInformation(true, Set.of(), SudoInformation.SUDO);
assertEquals(SPACE_KEY, OsCommandHelper.replaceSudo(SUDO_KEY, useSudoInformation));
assertEquals(
SPACE_KEY + END_OF_LINE + SPACE_KEY,
OsCommandHelper.replaceSudo(
SUDO_KEY + END_OF_LINE + SUDO_KEY,
OsCommandConfiguration.builder().useSudo(true).build()
)
OsCommandHelper.replaceSudo(SUDO_KEY + END_OF_LINE + SUDO_KEY, useSudoInformation)
);

assertEquals(
SUDO_KEYWORD + SPACE_KEY,
OsCommandHelper.replaceSudo(
SUDO_KEY,
OsCommandConfiguration.builder().useSudo(true).useSudoCommands(Set.of(KEY)).build()
)
);
final SudoInformation useSudoKeyInformation = new SudoInformation(true, Set.of(KEY), SudoInformation.SUDO);
assertEquals(SUDO_KEYWORD + SPACE_KEY, OsCommandHelper.replaceSudo(SUDO_KEY, useSudoKeyInformation));

assertEquals(
SUDO_KEY_RESULT,
OsCommandHelper.replaceSudo(
SUDO_KEY + END_OF_LINE + SUDO_KEY,
OsCommandConfiguration.builder().useSudo(true).useSudoCommands(Set.of(KEY)).build()
)
OsCommandHelper.replaceSudo(SUDO_KEY + END_OF_LINE + SUDO_KEY, useSudoKeyInformation)
);
}

Expand Down Expand Up @@ -899,6 +887,11 @@ void testRunOsCommandRemoteLinuxNoSudo() throws Exception {
)
.build();

final SudoInformation sudoInformation = new SudoInformation(
osCommandConfiguration.isUseSudo(),
osCommandConfiguration.getUseSudoCommands(),
osCommandConfiguration.getSudoCommand()
);
final TelemetryManager telemetryManager = TelemetryManager.builder().hostConfiguration(hostConfiguration).build();

try (final MockedStatic<OsCommandHelper> mockedOsCommandHelper = mockStatic(OsCommandHelper.class)) {
Expand All @@ -908,15 +901,15 @@ void testRunOsCommandRemoteLinuxNoSudo() throws Exception {
.thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getPassword(sshConfiguration)).thenCallRealMethod();
mockedOsCommandHelper
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(osCommandConfiguration)))
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(sudoInformation)))
.thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getFileNameFromSudoCommand(anyString())).thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.toCaseInsensitiveRegex(anyString())).thenCallRealMethod();
mockedOsCommandHelper
.when(() ->
OsCommandHelper.createOsCommandEmbeddedFiles(
NAVISECCLI_COMMAND,
osCommandConfiguration,
sudoInformation,
commandLineEmbeddedFiles,
TEMP_FILE_CREATOR
)
Expand Down Expand Up @@ -968,6 +961,11 @@ void testRunOsCommandRemoteLinuxNotInUseSudoCommands() throws Exception {
)
.build();

final SudoInformation sudoInformation = new SudoInformation(
osCommandConfiguration.isUseSudo(),
osCommandConfiguration.getUseSudoCommands(),
osCommandConfiguration.getSudoCommand()
);
final TelemetryManager telemetryManager = TelemetryManager.builder().hostConfiguration(hostConfiguration).build();

try (final MockedStatic<OsCommandHelper> mockedOsCommandHelper = mockStatic(OsCommandHelper.class)) {
Expand All @@ -977,15 +975,15 @@ void testRunOsCommandRemoteLinuxNotInUseSudoCommands() throws Exception {
.thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getPassword(sshConfiguration)).thenCallRealMethod();
mockedOsCommandHelper
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(osCommandConfiguration)))
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(sudoInformation)))
.thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getFileNameFromSudoCommand(anyString())).thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.toCaseInsensitiveRegex(anyString())).thenCallRealMethod();
mockedOsCommandHelper
.when(() ->
OsCommandHelper.createOsCommandEmbeddedFiles(
NAVISECCLI_COMMAND,
osCommandConfiguration,
sudoInformation,
commandLineEmbeddedFiles,
TEMP_FILE_CREATOR
)
Expand Down Expand Up @@ -1037,6 +1035,11 @@ void testRunOsCommandRemoteLinuxWithSudoReplaced() throws Exception {
)
.build();

final SudoInformation sudoInformation = new SudoInformation(
osCommandConfiguration.isUseSudo(),
osCommandConfiguration.getUseSudoCommands(),
osCommandConfiguration.getSudoCommand()
);
final TelemetryManager telemetryManager = TelemetryManager.builder().hostConfiguration(hostConfiguration).build();

try (final MockedStatic<OsCommandHelper> mockedOsCommandHelper = mockStatic(OsCommandHelper.class)) {
Expand All @@ -1046,15 +1049,15 @@ void testRunOsCommandRemoteLinuxWithSudoReplaced() throws Exception {
.thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getPassword(sshConfiguration)).thenCallRealMethod();
mockedOsCommandHelper
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(osCommandConfiguration)))
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(sudoInformation)))
.thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getFileNameFromSudoCommand(anyString())).thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.toCaseInsensitiveRegex(anyString())).thenCallRealMethod();
mockedOsCommandHelper
.when(() ->
OsCommandHelper.createOsCommandEmbeddedFiles(
NAVISECCLI_COMMAND,
osCommandConfiguration,
sudoInformation,
commandLineEmbeddedFiles,
TEMP_FILE_CREATOR
)
Expand Down Expand Up @@ -1115,6 +1118,11 @@ void testRunOsCommandRemoteLinuxWithEmbeddedFilesReplaced() throws Exception {
)
.build();

final SudoInformation sudoInformation = new SudoInformation(
osCommandConfiguration.isUseSudo(),
osCommandConfiguration.getUseSudoCommands(),
osCommandConfiguration.getSudoCommand()
);
final TelemetryManager telemetryManager = TelemetryManager.builder().hostConfiguration(hostConfiguration).build();

try (
Expand All @@ -1129,14 +1137,14 @@ void testRunOsCommandRemoteLinuxWithEmbeddedFilesReplaced() throws Exception {
mockedOsCommandHelper.when(() -> OsCommandHelper.toCaseInsensitiveRegex(anyString())).thenCallRealMethod();
mockedOsCommandHelper.when(() -> OsCommandHelper.getFileNameFromSudoCommand(anyString())).thenCallRealMethod();
mockedOsCommandHelper
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(osCommandConfiguration)))
.when(() -> OsCommandHelper.replaceSudo(anyString(), eq(sudoInformation)))
.thenCallRealMethod();

mockedOsCommandHelper
.when(() ->
OsCommandHelper.createOsCommandEmbeddedFiles(
SH_EMBEDDED_FILE_1,
osCommandConfiguration,
sudoInformation,
commandLineEmbeddedFiles,
TEMP_FILE_CREATOR
)
Expand Down

0 comments on commit 7e72c97

Please sign in to comment.