Skip to content

Commit

Permalink
Merge pull request #2909 from entur/otp2_ds5_resolve_environment_vari…
Browse files Browse the repository at this point in the history
…ables

Otp2 ds5 resolve environment variables
  • Loading branch information
abyrd committed Jan 10, 2020
2 parents cc1a9d3 + 761c719 commit 181a4eb
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import static org.opentripplanner.util.EnvironmentVariableReplacer.insertEnvironmentVariables;

/**
* Generic config file loader. This is used to load all configuration files.
* <p>
Expand Down Expand Up @@ -60,11 +62,11 @@ public static ConfigLoader fromString(String json) {
}

/**
* Generic method to parse the given json and return a JsonNode tree. The name is used
* to generate a proper error message in case the string is not a proper JSON document.
* Generic method to parse the given json and return a JsonNode tree. The {@code source} is
* used to generate a proper error message in case the string is not a proper JSON document.
*/
public static JsonNode fromString(String json, String name) {
return new ConfigLoader(null, json).stringToJsonNode(json, name);
public static JsonNode fromString(String json, String source) {
return new ConfigLoader(null, json).stringToJsonNode(json, source);
}

/**
Expand Down Expand Up @@ -169,14 +171,19 @@ private JsonNode loadJsonFile(File file) {
}

/**
* Convert a String into JsonNode. Comments and unquoted fields are allowed
* int he given {@code jsonAsString} input.
* Convert a String into JsonNode. Comments and unquoted fields are allowed in the given {@code
* jsonAsString} input.
*
* @param source is used only to generate a human friendly error message in case of an error
* parsing the JSON or inserting environment variables.
*/
private JsonNode stringToJsonNode(String jsonAsString, String source) {
try {
if(jsonAsString == null) {
return MissingNode.getInstance();
}
jsonAsString = insertEnvironmentVariables(jsonAsString, source);

return mapper.readTree(jsonAsString);
}
catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.opentripplanner.util;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Replaces environment variable placeholders specified on the format ${variable} in a text
* with the current system environment variable values.
*/
public class EnvironmentVariableReplacer {
/**
* A pattern matching a placeholder like '${VAR_NAME}'. The placeholder must start with
* '${' and end with '}'. The environment variable name must consist of only alphanumerical
* characters(a-z, A-Z, 0-9) and underscore '_'.
*/
private static Pattern PATTERN = Pattern.compile("\\$\\{(\\w+)}");


/**
* Search for {@link #PATTERN}s and replace each placeholder with the value of the
* corresponding environment variable.
*
* @param source is used only to generate human friendly error message in case the text
* contain a placeholder whitch can not be found.
* @throws IllegalArgumentException if a placeholder exist in the {@code text}, but the
* environment variable do not exist.
*/
public static String insertEnvironmentVariables(String text, String source) {
Map<String, String> environmentVariables = new HashMap<>();
Matcher matcher = PATTERN.matcher(text);

while (matcher.find()) {
String envVar = matcher.group(0);
String nameOnly = matcher.group(1);
if (!environmentVariables.containsKey(nameOnly)) {
String value = System.getenv(nameOnly);
if (value != null) {
environmentVariables.put(envVar, value);
}
else {
throw new IllegalArgumentException(
"Environment variable name '" + nameOnly + "' in config '"
+ source + "' not found in the system environment variables."
);
}
}
}
for (Map.Entry<String, String> entry : environmentVariables.entrySet()) {
text = text.replace(entry.getKey(), entry.getValue());
}
return text;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
Expand Down Expand Up @@ -88,6 +89,58 @@ public void parseJsonString() {
assertEquals("value", node.path("key").asText());
}

/**
* Test replacing environment variables in JSON config. The {@link ConfigLoader} should replace
* placeholders like '${ENV_NAME}' when converting a JSON string to a node tree.
* <p>
* This test pick a random system environment variable and insert it into the JSON string to
* be able to test the replace functionality. This is necessary to avoid changing the system
* environment variables and to apply this test on the {@link ConfigLoader} level.
*/
@Test
public void testReplacementOfEnvironmentVariables() {
// Given: Search for a environment variable name containing only alphanumeric characters
// and a value with less than 30 characters (avoid long values like paths for
// readability). We will use this to insert it in the JSON and later see if the
// ConfigLoader is able to replace the placeholder with the expected value.
Map.Entry<String, String> envVar = System.getenv().entrySet()
.stream()
.filter(e ->
e.getKey().matches("\\w+") && e.getValue().length() < 30
)
.findFirst()
.orElse(null);

if(envVar == null) {
fail("No environment variable matching '\\w+' found.");
}

String eName = envVar.getKey();
String expectedValue = envVar.getValue();

// Create JSON with the environment variable inserted
String json = json("{ 'key': '${" + eName + "}', 'key2':'${" + eName + "}' }");

// When: parse JSON
JsonNode node = ConfigLoader.fromString(json, "test");

// Then: verify that the JSON node have the expected value
String actualValue = node.path("key").asText(null);

assertEquals(expectedValue, actualValue);
}

/**
* Test replacing environment variables in config fails on a unknown environment variable.
*/
@Test(expected = IllegalArgumentException.class)
public void testMissingEnvironmentVariable() {
ConfigLoader.fromString(
"{ key: '${none_existing_env_variable}' }",
"test"
);
}

@Test
public void configFailsIfBaseDirectoryDoesNotExist() {
File cfgDir = new File(tempDir, "cfg");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.opentripplanner.util;

import org.junit.Before;
import org.junit.Test;
import org.opentripplanner.standalone.config.ConfigLoader;

import java.util.Map;

import static org.junit.Assert.assertEquals;

public class EnvironmentVariableReplacerTest {


private String envName = "<not set>";
private String envValue = "<not set>";


/**
* To test the system environment variable substitution we must have a name/value pair that
* exist in the system environment variables. So to prepare for the test, we look up a random
* environment variable and use it later to construct the test samples, and the expected
* results.
*
* We search for a environment variable name containing only alphanumeric characters and a
* value with less than 30 characters. We do this to make it easier for humans to see what is
* going on, if a test fails. This constraint is just to make the text involved more
* readable.
*/
@Before
public void setup() {
Map.Entry<String, String> envVar = System.getenv().entrySet()
.stream()
.filter(e ->
e.getKey().matches("\\w+") && e.getValue().length() < 30
)
.findFirst()
.orElse(null);

if(envVar == null) {
throw new IllegalStateException("No environment variables for testing found.");
}
envName = envVar.getKey();
envValue = envVar.getValue();
}


/**
* Test replacing environment variables in a text. The {@link EnvironmentVariableReplacer}
* should replace placeholders like '${ENV_NAME}' with the value of the system environment
* variable.
*/
@Test
public void insertEnvironmentVariables() {
// Given: a text and a expected result
String text = "Env.var: ${" + envName + "}.";
String expectedResult = "Env.var: " + envValue + ".";

// When:
String result = EnvironmentVariableReplacer.insertEnvironmentVariables(text, "test");

// Then:
assertEquals(expectedResult, result);
}

@Test
public void verifyThatAEnvVariableMayExistMoreThanOnce() {
// Given: a text and a expected result
String text = "Env. var1: ${" + envName + "} and var2: ${" + envName + "}.";
String expectedResult = "Env. var1: " + envValue + " and var2: " + envValue + ".";

// When:
String result = EnvironmentVariableReplacer.insertEnvironmentVariables(text, "test");

// Then:
assertEquals(expectedResult, result);
}


/**
* Test replacing environment variable fails for unknown environment variable.
*/
@Test(expected = IllegalArgumentException.class)
public void testMissingEnvironmentVariable() {
ConfigLoader.fromString(
"None existing env.var: '${none_existing_env_variable}'.", "test"
);
}
}

0 comments on commit 181a4eb

Please sign in to comment.