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

Add interpolation to JsonConfigurator #13023

Merged
merged 6 commits into from
Sep 7, 2022
Merged
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
9 changes: 9 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
</dependency>
<dependency>
<groupId>org.skife.config</groupId>
<artifactId>config-magic</artifactId>
Expand Down Expand Up @@ -368,6 +372,11 @@
<version>${postgresql.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.stefanbirkner</groupId>
<artifactId>system-rules</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
14 changes: 13 additions & 1 deletion core/src/main/java/org/apache/druid/guice/JsonConfigurator.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.inject.Inject;
import com.google.inject.ProvisionException;
import com.google.inject.spi.Message;
import org.apache.commons.text.StringSubstitutor;
import org.apache.commons.text.lookup.StringLookupFactory;
import org.apache.druid.java.util.common.StringUtils;
import org.apache.druid.java.util.common.logger.Logger;

Expand Down Expand Up @@ -58,6 +61,15 @@ public class JsonConfigurator

private final ObjectMapper jsonMapper;
private final Validator validator;
private final StringSubstitutor stringSubstitutor = new StringSubstitutor(StringLookupFactory.INSTANCE.interpolatorStringLookup(
ImmutableMap.of(
StringLookupFactory.KEY_SYS, StringLookupFactory.INSTANCE.systemPropertyStringLookup(),
StringLookupFactory.KEY_ENV, StringLookupFactory.INSTANCE.environmentVariableStringLookup(),
StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup()
),
null,
false
)).setEnableSubstitutionInVariables(true).setEnableUndefinedVariableException(true);

@Inject
public JsonConfigurator(
Expand Down Expand Up @@ -89,7 +101,7 @@ public <T> T configurate(
Map<String, Object> jsonMap = new HashMap<>();
for (String prop : props.stringPropertyNames()) {
if (prop.startsWith(propertyBase)) {
final String propValue = props.getProperty(prop);
final String propValue = stringSubstitutor.replace(props.getProperty(prop));
Object value;
try {
// If it's a String Jackson wants it to be quoted, so check if it's not an object or array and quote.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
import org.apache.druid.TestObjectMapper;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;
Expand All @@ -43,6 +46,12 @@ public class JsonConfiguratorTest
private final ObjectMapper mapper = new TestObjectMapper();
private final Properties properties = new Properties();

@Rule
public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();

@Rule
public final EnvironmentVariables environmentVariables = new EnvironmentVariables();

@Before
public void setUp()
{
Expand Down Expand Up @@ -159,6 +168,71 @@ public void testPropertyWithDot()
Assert.assertEquals("prop1", obj.prop1);

}

@Test
public void testPropertyInterpolation()
{
System.setProperty("my.property", "value1");
List<String> list = ImmutableList.of("list", "of", "strings");
environmentVariables.set("MY_VAR", "value2");

final JsonConfigurator configurator = new JsonConfigurator(mapper, validator);
properties.setProperty(PROP_PREFIX + "prop1", "${sys:my.property}");
properties.setProperty(PROP_PREFIX + "prop1List", "${file:UTF-8:src/test/resources/list.json}");
properties.setProperty(PROP_PREFIX + "prop2.prop.2", "${env:MY_VAR}");
final MappableObject obj = configurator.configurate(properties, PROP_PREFIX, MappableObject.class);
Assert.assertEquals(System.getProperty("my.property"), obj.prop1);
Assert.assertEquals(list, obj.prop1List);
Assert.assertEquals("value2", obj.prop2);
}

@Test
public void testPropertyInterpolationInName()
{
System.setProperty("my.property", "value1");
List<String> list = ImmutableList.of("list", "of", "strings");
environmentVariables.set("MY_VAR", "value2");

environmentVariables.set("SYS_PROP", "my.property");
System.setProperty("json.path", "src/test/resources/list.json");
environmentVariables.set("PROP2_NAME", "MY_VAR");

final JsonConfigurator configurator = new JsonConfigurator(mapper, validator);
properties.setProperty(PROP_PREFIX + "prop1", "${sys:${env:SYS_PROP}}");
properties.setProperty(PROP_PREFIX + "prop1List", "${file:UTF-8:${sys:json.path}}");
properties.setProperty(PROP_PREFIX + "prop2.prop.2", "${env:${env:PROP2_NAME}}");
final MappableObject obj = configurator.configurate(properties, PROP_PREFIX, MappableObject.class);
Assert.assertEquals(System.getProperty("my.property"), obj.prop1);
Assert.assertEquals(list, obj.prop1List);
Assert.assertEquals("value2", obj.prop2);
}

@Test
public void testPropertyInterpolationFallback()
{
List<String> list = ImmutableList.of("list", "of", "strings");

final JsonConfigurator configurator = new JsonConfigurator(mapper, validator);
properties.setProperty(PROP_PREFIX + "prop1", "${sys:my.property:-value1}");
properties.setProperty(PROP_PREFIX + "prop1List", "${unknown:-[\"list\", \"of\", \"strings\"]}");
properties.setProperty(PROP_PREFIX + "prop2.prop.2", "${MY_VAR:-value2}");
final MappableObject obj = configurator.configurate(properties, PROP_PREFIX, MappableObject.class);
Assert.assertEquals("value1", obj.prop1);
Assert.assertEquals(list, obj.prop1List);
Assert.assertEquals("value2", obj.prop2);
}

@Test
public void testPropertyInterpolationUndefinedException()
{
final JsonConfigurator configurator = new JsonConfigurator(mapper, validator);
properties.setProperty(PROP_PREFIX + "prop1", "${sys:my.property}");

Assert.assertThrows(
IllegalArgumentException.class,
() -> configurator.configurate(properties, PROP_PREFIX, MappableObject.class)
);
}
}

class MappableObject
Expand Down
5 changes: 5 additions & 0 deletions core/src/test/resources/list.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"list",
"of",
"strings"
]
29 changes: 29 additions & 0 deletions docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,35 @@ The `jvm.config` files contain JVM flags such as heap sizing properties for each

Common properties shared by all services are placed in `_common/common.runtime.properties`.

## Configuration Interpolation

Configuration values can be interpolated from System Properties, Environment Variables, or local files. Below is an example of how this can be used:

```
druid.metadata.storage.type=${env:METADATA_STORAGE_TYPE}
druid.processing.tmpDir=${sys:java.io.tmpdir}
druid.segmentCache.locations=${file:UTF-8:/config/segment-cache-def.json}
```

Interpolation is also recursive so you can do:

```
druid.segmentCache.locations=${file:UTF-8:${env:SEGMENT_DEF_LOCATION}}
```

If the property is not set an exception will be thrown on startup, but a default can be provided if desired. Setting a default value will not work with file interpolation as an exception will be thrown if the file does not exist.

```
druid.metadata.storage.type=${env:METADATA_STORAGE_TYPE:-mysql}
druid.processing.tmpDir=${sys:java.io.tmpdir:-/tmp}
```

If you need to set a variable that is wrapped by `${...}` but do not want it to be interpolated you can escape it by adding another `$`. For example:

```
config.name=$${value}
```

## Common Configurations

The properties under this section are common configurations that should be shared across all Druid services in a cluster.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ public Builder(String clusterName)
// previously set in Maven.
propertyEnvVarBinding("druid.test.config.dockerIp", "DOCKER_IP");
propertyEnvVarBinding("druid.zk.service.host", "DOCKER_IP");
propertyEnvVarBinding("druid.test.config.hadoopDir", "HADOOP_DIR");
property("druid.client.https.trustStorePath", "client_tls/truststore.jks");
property("druid.client.https.trustStorePassword", "druid123");
property("druid.client.https.keyStorePath", "client_tls/client.jks");
Expand Down
1 change: 0 additions & 1 deletion integration-tests/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,6 @@
-Duser.timezone=UTC
-Dfile.encoding=UTF-8
-Ddruid.test.config.dockerIp=${env.DOCKER_IP}
-Ddruid.test.config.hadoopDir=${env.HADOOP_DIR}
-Ddruid.test.config.extraDatasourceNameSuffix=${extra.datasource.name.suffix}
-Ddruid.zk.service.host=${env.DOCKER_IP}
-Ddruid.client.https.trustStorePath=client_tls/truststore.jks
Expand Down
21 changes: 9 additions & 12 deletions licenses.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -668,13 +668,16 @@ name: Apache Commons Lang
license_category: binary
module: java-core
license_name: Apache License version 2.0
version: 3.8.1
version: 3.12.0
libraries:
- org.apache.commons: commons-lang3
notices:
- commons-lang3: |
Apache Commons Lang
Copyright 2001-2018 The Apache Software Foundation
Copyright 2001-2021 The Apache Software Foundation

This product includes software developed at
The Apache Software Foundation (https://www.apache.org/).

---

Expand Down Expand Up @@ -719,23 +722,17 @@ name: Apache Commons Text
license_category: binary
module: java-core
license_name: Apache License version 2.0
version: 1.3
version: 1.9
libraries:
- org.apache.commons: commons-text
notices:
- commons-text: |
Apache Commons Text
Copyright 2001-2018 The Apache Software Foundation
Copyright 2014-2020 The Apache Software Foundation

---
This product includes software developed at
The Apache Software Foundation (https://www.apache.org/).

name: Apache Commons Text
license_category: binary
module: java-core
license_name: Apache License version 2.0
version: 1.4
libraries:
- org.apache.commons: commons-text
---

name: Airline
Expand Down
7 changes: 6 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,12 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
<version>3.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
Expand Down
1 change: 0 additions & 1 deletion server/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,6 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down