Add Environment Variable DynamicConfigProvider#11377
Add Environment Variable DynamicConfigProvider#11377jihoonson merged 14 commits intoapache:masterfrom
Conversation
jihoonson
left a comment
There was a problem hiding this comment.
@bananaaggle thanks for the PR. I left some comments.
| @JsonProperty("variables") Map<String, String> config | ||
| ) | ||
| { | ||
| this.variables = ImmutableMap.copyOf(config); |
There was a problem hiding this comment.
config can be null if there is something wrong in the configuration. This will throw NPE in that case. It would be better to tell what is wrong than NPE with no message.
| import java.util.Map; | ||
| import java.util.Objects; | ||
|
|
||
| public class EnvironmentVariableDynamicConfigProvider implements DynamicConfigProvider |
There was a problem hiding this comment.
| public class EnvironmentVariableDynamicConfigProvider implements DynamicConfigProvider | |
| public class EnvironmentVariableDynamicConfigProvider implements DynamicConfigProvider<String> |
| } | ||
|
|
||
| @Override | ||
| public Map getConfig() |
There was a problem hiding this comment.
| public Map getConfig() | |
| public Map<String> getConfig() |
| @Override | ||
| public int hashCode() | ||
| { | ||
| return variables != null ? variables.hashCode() : 0; |
There was a problem hiding this comment.
variables doesn't seem to be able to null.
| try { | ||
| this.variables = ImmutableMap.copyOf(config); | ||
| } | ||
| catch (NullPointerException e) { | ||
| log.error(e, "Can not parse config by EnvironmentVariableDynamicConfigProvider! Please check your config file."); | ||
| throw e; | ||
| } |
There was a problem hiding this comment.
It would be nice to propagate the error to users so that they don't have to look up logs when this happens.
| try { | |
| this.variables = ImmutableMap.copyOf(config); | |
| } | |
| catch (NullPointerException e) { | |
| log.error(e, "Can not parse config by EnvironmentVariableDynamicConfigProvider! Please check your config file."); | |
| throw e; | |
| } | |
| this.variables = ImmutableMap.copyOf(Preconditions.checkNotNull(config, "config")); |
|
|
||
| private static final Logger log = new Logger(EnvironmentVariableDynamicConfigProvider.class); | ||
|
|
||
| private ImmutableMap<String, String> variables; |
|
@bananaaggle thanks for the quick response. The code change LGTM, but there are 2 things missing here, one is documentation and another is tests. For documentation, I think you can add some in |
Document added. I'll add more tests in week. |
Hi, @jihoonson. I add unit test for getConfig(). To set environment variable, I find a method named |
jihoonson
left a comment
There was a problem hiding this comment.
@bananaaggle thank you for adding a documentation! I left some comments on it.
For unit tests, I was thinking of some tests that should run with a set of certain environment variables. So, for Travis, those variables should be set in .travis.yaml. To run those tests locally, those variables should be set and passed properly to the process that runs the tests. I thought this would be the easiest way to add tests, but if you think there is a better way, I'm OK with it as long as those tests are not flaky.
|
|
||
| `EnvironmentVariableDynamicConfigProvider` can be used to replace `EnvironmentVariablePasswordProvider`. This class allow users to avoid exposing passwords or other secret information in the runtime.properties file. You can set environment variables in the following example: | ||
| ```json | ||
| druid.metadata.storage.connector.dynamicConfigProvider={"type": "environment","variables":{"user": "MY_USER_NAME_VAR","password": "MY_PASSWORD_VAR"} |
There was a problem hiding this comment.
We don't support dynamicConfig for metadata yet.
| druid.metadata.storage.connector.dynamicConfigProvider={"type": "environment","variables":{"user": "MY_USER_NAME_VAR","password": "MY_PASSWORD_VAR"} | |
| druid.some.config.dynamicConfigProvider={"type": "environment","variables":{"secret1": "SECRET1_VAR","secret2": "SECRET2_VAR"} |
There was a problem hiding this comment.
Fixed. In #11389, I make a design to replace PasswordProvider. I think it can work with metadata and other classes which use PasswordProvider.
There was a problem hiding this comment.
Cool, I will take a look at that PR too. Thanks!
|
|
||
| ## EnvironmentVariableDynamicConfigProvider | ||
|
|
||
| `EnvironmentVariableDynamicConfigProvider` can be used to replace `EnvironmentVariablePasswordProvider`. This class allow users to avoid exposing passwords or other secret information in the runtime.properties file. You can set environment variables in the following example: |
There was a problem hiding this comment.
| `EnvironmentVariableDynamicConfigProvider` can be used to replace `EnvironmentVariablePasswordProvider`. This class allow users to avoid exposing passwords or other secret information in the runtime.properties file. You can set environment variables in the following example: | |
| `EnvironmentVariableDynamicConfigProvider` can be used to avoid exposing credentials or other secret information in the configuration files using environment variables. An example to use this configProvider is: |
jihoonson
left a comment
There was a problem hiding this comment.
Hey @bananaaggle, thank you for adding unit tests. I have some concerns about the new code added. Please see my comments.
| theEnvironmentField.setAccessible(true); | ||
| Map<String, String> env = (Map<String, String>) theEnvironmentField.get(null); | ||
| env.putAll(newenv); | ||
| Field theCaseInsensitiveEnvironmentField = processEnvironmentClass.getDeclaredField("theCaseInsensitiveEnvironment"); |
There was a problem hiding this comment.
What is this theCaseInsensitiveEnvironment variable? I don't see it in java.lang.ProcessEnvironment.
| catch (NoSuchFieldException e) { | ||
| Class[] classes = Collections.class.getDeclaredClasses(); | ||
| Map<String, String> env = System.getenv(); | ||
| for (Class cl : classes) { | ||
| if ("java.util.Collections$UnmodifiableMap".equals(cl.getName())) { | ||
| Field field = cl.getDeclaredField("m"); | ||
| field.setAccessible(true); | ||
| Object obj = field.get(env); | ||
| Map<String, String> map = (Map<String, String>) obj; | ||
| map.clear(); | ||
| map.putAll(newenv); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Can you elaborate more on what this catch clause is doing?
| Assert.assertEquals("123", ((EnvironmentVariableDynamicConfigProvider) provider).getConfig().get("password")); | ||
| } | ||
|
|
||
| protected static void setEnv(Map<String, String> newenv) throws Exception |
There was a problem hiding this comment.
This method seems to have a couple of issues.
- This method seems to be copied from the code snippet in https://stackoverflow.com/a/7201825/4127682. If this is true, we cannot use it directly because it violates the ASF license policy. You can get some hint from the stack overflow, but should not copy from it.
- As noted in the stack overflow link, the environment variables should be reset because they are shared by all tests run by the same JVM process.
- It would be nice to add some comments to help others understand the code. I left some comments for this.
There was a problem hiding this comment.
I've changed this unit test. Separated setting and getting environment variables. I use a map to record which environment variables are changed and add a method to recover those system environment variables after test. Add comment for getENVMap().
|
Hi @jihoonson! I've changed this unit test but CI tests failed. It seems caused by travis error, can you help me rerun it? And is there something in code needed to change? Thanks! |
|
@bananaaggle sorry, I forgot about this PR. I restarted the timed-out tests. Will finish my review today. |
jihoonson
left a comment
There was a problem hiding this comment.
LGTM. Thanks @bananaaggle!
Thank you very much for review! In 11389, I make a design to replace PasswordProvider, but I'm not sure this design is proper or not. Can you help review it? If design is proper, I will involve more classes which use PasswordProvider in that PR. I also comment about it in 9351. |
As #9351 described, DynamicConfigProvider should replace PasswordProvider. PasswordProvider has an implementation named EnvironmentVariablePasswordProvider, which allow users to read config from environment variable. This PR provides a same implementation for DynamicConfigProvider. There is an example for its usage:
This PR has: