-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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 Environment Variable DynamicConfigProvider #11377
Add Environment Variable DynamicConfigProvider #11377
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
import java.util.Map; | ||
import java.util.Objects; | ||
|
||
public class EnvironmentVariableDynamicConfigProvider implements DynamicConfigProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public class EnvironmentVariableDynamicConfigProvider implements DynamicConfigProvider | |
public class EnvironmentVariableDynamicConfigProvider implements DynamicConfigProvider<String> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
} | ||
|
||
@Override | ||
public Map getConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public Map getConfig() | |
public Map<String> getConfig() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
@Override | ||
public int hashCode() | ||
{ | ||
return variables != null ? variables.hashCode() : 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
variables
doesn't seem to be able to null.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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")); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
private static final Logger log = new Logger(EnvironmentVariableDynamicConfigProvider.class); | ||
|
||
private ImmutableMap<String, String> variables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this can be final
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
@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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, I will take a look at that PR too. Thanks!
@@ -31,3 +31,16 @@ Users can create custom extension of the `DynamicConfigProvider` interface that | |||
|
|||
For more information, see [Adding a new DynamicConfigProvider implementation](../development/modules.md#adding-a-new-dynamicconfigprovider-implementation). | |||
|
|||
## 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
`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: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this theCaseInsensitiveEnvironment
variable? I don't see it in java.lang.ProcessEnvironment
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate more on what this catch clause is doing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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: