Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 37 additions & 7 deletions src/main/java/emissary/command/BaseCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,20 @@
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Command(description = "Base Command")
public abstract class BaseCommand implements EmissaryCommand {
static final Logger LOG = LoggerFactory.getLogger(BaseCommand.class);

public static final String COMMAND_NAME = "BaseCommand";

@Option(names = {"-c", "--config"}, description = "config dir, comma separated if multiple, defaults to <projectBase>/config",
@Option(names = {"-c", "--config"}, split = ",", description = "config dir, comma separated if multiple, defaults to <projectBase>/config",
converter = PathExistsConverter.class)
@Nullable
private Path config;
private List<Path> config;

@Option(names = {"-b", "--projectBase"}, description = "defaults to PROJECT_BASE, errors if different\nDefault: ${DEFAULT-VALUE}",
converter = ProjectBaseConverter.class)
Expand Down Expand Up @@ -62,17 +64,45 @@ public abstract class BaseCommand implements EmissaryCommand {
@Option(names = {"-q", "--quiet"}, description = "hide banner and non essential messages\nDefault: ${DEFAULT-VALUE}")
private boolean quiet = false;

public Path getConfig() {
/**
* Get all configured config directories, in order. Defaults to a single {@code <projectBase>/config} dir when none were
* supplied on the command line.
*
* @return the ordered list of config directories
*/
public List<Path> getConfigDirs() {
if (config == null) {
config = getProjectBase().toAbsolutePath().resolve("config");
if (!Files.exists(config)) {
throw new IllegalArgumentException("Config dir not configured and " + config.toAbsolutePath() + " does not exist");
Path defaultConfig = getProjectBase().toAbsolutePath().resolve("config");
if (!Files.exists(defaultConfig)) {
throw new IllegalArgumentException("Config dir not configured and " + defaultConfig.toAbsolutePath() + " does not exist");
}
config = new ArrayList<>(List.of(defaultConfig));
}

return config;
}
Comment thread
cfkoehler marked this conversation as resolved.

/**
* Get the first/primary config directory.
*
* @return the first configured config directory
*/
public Path getConfig() {
return getConfigDirs().get(0);
}

/**
* Get the configured config directories as the comma-separated, absolute-path string used for the
* {@value ConfigUtil#CONFIG_DIR_PROPERTY} system property.
*
* @return the config dirs joined by commas
*/
public String getConfigDirsProperty() {
return getConfigDirs().stream()
.map(p -> p.toAbsolutePath().toString())
.collect(Collectors.joining(","));
}

public Path getProjectBase() {
return projectBase;
}
Expand Down Expand Up @@ -130,7 +160,7 @@ public void setupCommand() {

public void setupConfig() {
logInfo("{} is set to {} ", ConfigUtil.PROJECT_BASE_ENV, getProjectBase().toAbsolutePath().toString());
setSystemProperty(ConfigUtil.CONFIG_DIR_PROPERTY, getConfig().toAbsolutePath().toString());
setSystemProperty(ConfigUtil.CONFIG_DIR_PROPERTY, getConfigDirsProperty());
setSystemProperty(ConfigUtil.CONFIG_BIN_PROPERTY, getBinDir().toAbsolutePath().toString());
setSystemProperty(ConfigUtil.CONFIG_OUTPUT_ROOT_PROPERTY, getOutputDir().toAbsolutePath().toString());
logInfo("Emissary error dir set to {} ", getErrorDir().toAbsolutePath().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ public Path convert(String value) {
}

public Path convert(String option, String value) {
Path p = Path.of(Strings.CS.removeEnd(value, "/"));
String trimmed = value == null ? "" : value.trim();
// reject blank entries (e.g. an empty token from a comma-separated list), which would otherwise resolve to the
// current working directory via Path.of("")
if (trimmed.isEmpty()) {
String msg = String.format("The option '%s' was configured with a blank path", option);
LOG.error(msg);
throw new IllegalArgumentException(msg);
}
Path p = Path.of(Strings.CS.removeEnd(trimmed, "/"));
// ensure the value exists
if (!Files.exists(p)) {
String msg = String.format("The option '%s' was configured with path '%s' which does not exist", option, p);
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/emissary/server/EmissaryServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -725,7 +726,9 @@ protected void csrfFilter(ResourceConfig application) {
private ContextHandler buildEmissaryHandler() throws EmissaryException {
// must set these set or you are not an EmissaryNode
String configDir = System.getProperty(ConfigUtil.CONFIG_DIR_PROPERTY, null);
if (configDir == null || !Files.exists(Path.of(configDir))) {
// split with -1 limit so trailing empty tokens are kept and rejected; trim each entry to tolerate whitespace
if (configDir == null
|| Arrays.stream(configDir.split(",", -1)).map(String::trim).anyMatch(d -> d.isEmpty() || !Files.exists(Path.of(d)))) {
throw new EmissaryException("Config dir error. " + ConfigUtil.CONFIG_DIR_PROPERTY + " is " + configDir);
Comment thread
cfkoehler marked this conversation as resolved.
}
// set number of agents if it has been set
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/emissary/server/api/Env.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private MapResponseEntity getEnv() {
try {
EmissaryServer server = (EmissaryServer) Namespace.lookup(EMISSARY_SERVER);
ServerCommand command = server.getServerCommand();
entity.addKeyValue("CONFIG_DIR", command.getConfig().toAbsolutePath().toString());
entity.addKeyValue("CONFIG_DIR", command.getConfigDirsProperty());
entity.addKeyValue("PROJECT_BASE", command.getProjectBase().toAbsolutePath().toString());
entity.addKeyValue("OUTPUT_ROOT", command.getOutputDir().toAbsolutePath().toString());
entity.addKeyValue("BIN_DIR", command.getBinDir().toAbsolutePath().toString());
Expand Down
41 changes: 41 additions & 0 deletions src/test/java/emissary/command/ServerCommandIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
import emissary.test.core.junit5.UnitTest;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Path;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
Expand All @@ -26,6 +28,45 @@ void testGetConfigWithTrailingSlash() throws Exception {
assertEquals(Path.of(PROJECT_BASE_SLASH + "/config"), cmd.getConfig());
}

@Test
void testGetMultipleConfigDirs(@TempDir Path extraConfig) throws Exception {
String firstConfig = PROJECT_BASE + "/config";
ServerCommand cmd = ServerCommand.parse(ServerCommand.class, "-b", PROJECT_BASE, "-m", "standalone",
"-c", firstConfig + "," + extraConfig);
Path pathFirstConfig = Path.of(firstConfig);
assertEquals(List.of(pathFirstConfig, extraConfig), cmd.getConfigDirs());
// first/primary dir is still returned by getConfig()
assertEquals(pathFirstConfig, cmd.getConfig());
// property is propagated as the comma-joined absolute paths
assertEquals(pathFirstConfig.toAbsolutePath() + "," + extraConfig.toAbsolutePath(),
System.getProperty(ConfigUtil.CONFIG_DIR_PROPERTY));
}

@Test
void testNonExistentConfigDirInListRejected() {
String firstConfig = PROJECT_BASE + "/config";
assertThrows(Exception.class,
() -> ServerCommand.parse(ServerCommand.class, "-b", PROJECT_BASE, "-m", "standalone",
"-c", firstConfig + ",/path/does/not/exist"));
}

@Test
void testBlankConfigDirInListRejected() {
String firstConfig = PROJECT_BASE + "/config";
// an empty token (a,,b) must not silently resolve to the current working directory
assertThrows(Exception.class,
() -> ServerCommand.parse(ServerCommand.class, "-b", PROJECT_BASE, "-m", "standalone",
"-c", firstConfig + ",," + firstConfig));
}

@Test
void testConfigDirWhitespaceTrimmed(@TempDir Path extraConfig) throws Exception {
String firstConfig = PROJECT_BASE + "/config";
ServerCommand cmd = ServerCommand.parse(ServerCommand.class, "-b", PROJECT_BASE, "-m", "standalone",
"-c", firstConfig + " , " + extraConfig);
assertEquals(List.of(Path.of(firstConfig), extraConfig), cmd.getConfigDirs());
}

@Test
void testModeAddedToFlavors() throws Exception {
ServerCommand cmd = ServerCommand.parse(ServerCommand.class, "-b ", PROJECT_BASE_SLASH + "/", "-m", "standalone");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.nio.file.Files;
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;

Expand Down Expand Up @@ -54,4 +55,18 @@ void convertFailed() {
assertThrows(IllegalArgumentException.class, () -> converter.convert("hello"));
}

@Test
void blankRejected() {
assertThrows(IllegalArgumentException.class, () -> converter.convert(""));
assertThrows(IllegalArgumentException.class, () -> converter.convert(" "));
}

@Test
void trimsWhitespace() {
// leading/trailing whitespace is trimmed before resolving the path
Path result = converter.convert(" " + path + " ");

assertEquals(path, result);
}

}
1 change: 1 addition & 0 deletions src/test/java/emissary/server/api/EmissaryApiTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public void setup() {
when(mockServer.getNode()).thenReturn(node);
when(mockServer.getServerCommand()).thenReturn(srvCmd);
when(srvCmd.getConfig()).thenReturn(Path.of("/path/to/project/config"));
when(srvCmd.getConfigDirsProperty()).thenReturn("/path/to/project/config");
when(srvCmd.getProjectBase()).thenReturn(Path.of("/path/to/project"));
when(srvCmd.getOutputDir()).thenReturn(Path.of("/path/to/project/output"));
when(srvCmd.getBinDir()).thenReturn(Path.of("/path/to/project/bin"));
Expand Down
Loading