-
Notifications
You must be signed in to change notification settings - Fork 967
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 a config.strategy to enable overwrite of properties from env vars #620
Conversation
Useful context from gitter chat is that we're wanting to allow setting a JVM property flag one time, and then a DevOps or Ops person could add new overrides via environment variables at any time, without editing the config file itself. This is useful in cases like Docker or Heroku or Kubernetes which are often configured via env var. Possible requirements:
I feel like the "strategy" notion is a little non-orthogonal (it potentially bundles together multiple behaviors), thinking it could be easier to think about this as a feature which is on or off. One way it could look is:
Possible questions:
The escaping situation is a little bit tough; config (and JSON) allow keys to be about anything, but many characters are awkward in an env variable. The PR so far invents a novel escaping mechanism to allow An option we've discussed in the past is an env var whose value would be parsed as HOCON and made into overrides, e.g. on #427 we mentioned it; an advantage to this is that values can also be unset and that any arbitrary key is possible. See #582 The downside to It's possible we should do a combined approach? Like all (minor detail if we did go that road maybe we should choose between FORCE and OVERRIDES for both, like with the prefixes in the CONFIG_ namespace it kinda seems OK to me to enable this by default without a JVM flag... |
I like your proposal and I will re-work this PR next week to follow. On the I personally don't love the
this is an assumption actually, debugging an issue caused by this will be incredibly hard and counter-intuitive, I will start with a flag and in case we remove it if there is enough community endorsement. |
@havocp please have a look at the updated implementation. |
👨🎨 Could the prefix be (I still believe we should rebrand this library as "hocon" - #514) |
I am not sure about the implementation details but the idea of ops person being able to override things via environment variables seems consistent with Twelve-Factor App (https://12factor.net/config), Kubernetes, etc ethos. |
Thinking twice at it, having a prefix like |
(Still in 👨🎨-mode) I'd rather avoid the extra FORCE/OVERRIDE. For example, I'd love if I could specify with an environment variable akka discovery's method with just: export HOCON_AKKA_DISCOVERY_METHOD=akka-dns |
If there’s nothing after HOCON then we can’t use that namespace for any other purpose (env vars that affect the library itself for example). Maybe that’s OK. It feels a bit odd to me to use this namespace here and |
also your points are valid @havocp , to not stall the PR on this can we make it configurable via java properties and have a longish default like |
(Sure, but I identify this library as the hocon library. 😄) |
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.
Might be helpful to add a note to the README or other docs where config.*
props are already mentioned, about this new -Dconfig.override_with_env_vars
Overall I feel like this is good. I like the CONFIG_FORCE_ naming I guess, though I don't like the overly-generic name of the entire library, I don't know that we should address that piecemeal in this context. :-)
I'm fine with leaving this as opt-in behavior, at least to start. We could always make it the default down the road, I suppose.
@@ -383,7 +384,11 @@ public static Config defaultReference(ClassLoader loader) { | |||
* @return the default override configuration | |||
*/ | |||
public static Config defaultOverrides() { | |||
return systemProperties(); | |||
if (!getOverrideWithEnv()) { |
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.
Think it's easier to read if you reverse the if/else to avoid the !
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.
done
private static Boolean getOverrideWithEnv() { | ||
String overrideWithEnv = System.getProperties().getProperty(OVERRIDE_WITH_ENV_PROPERTY_NAME); | ||
|
||
return Boolean.parseBoolean(overrideWithEnv); |
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.
if the value of the property is not a boolean this will throw an ugly and likely-mysterious exception right? might want to catch it and say something like "the value of OVERRIDE_WITH_ENV_PROPERTY_NAME must be a boolean not '%s'"
Not sure if we already have this bug when looking at any other env var values
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 deliberately choose to use Boolean.parseBoolean because it doesn't throw exceptions.
Basically it always return false unless the string is (case-insensitive) true
.
* This method can return a global immutable singleton, so it's preferred | ||
* over parsing system properties yourself. | ||
* <p> | ||
* {@link #defaultOverrides} will include the system system environment variables as |
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.
* {@link #defaultOverrides} will include the system system environment variables as | |
* {@link #defaultOverrides} will include the system environment variables as |
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.
tnx!
Oh and thank you for working on this! |
@havocp I followed your review and added a little section to the |
@havocp ping? |
For anyone else interested on the topic, I published Env4Config: as a side note (maybe for @dwijnand ) I found really misleading the fact that Play doesn't always load configuration in a standard way: |
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 noticed one more thing that invalidateCaches should cover the new cached thing, but otherwise 💯
lgtm
README.md
Outdated
i.e. The environment variable `CONFIG_FORCE_a_b__c___d` set the | ||
configuration key `a.b-c_d` | ||
|
||
Rationale the name mangling: |
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'd probably leave this section out, if people really care they can look in the code or commit messages (maybe it's worth moving it to one of those places)
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.
moved to a comment in the code
} | ||
|
||
private static class EnvVariablesOverridesHolder { | ||
static volatile AbstractConfigObject envVariables = loadEnvVariablesOverrides(); |
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 guess we need to make ConfigFactory.invalidateCaches clear this
public static void invalidateCaches() { |
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.
done
ready for next round :-) |
Need to re-trigger the CI |
Not sure why, but Travis was failing to download sbt-launcher 1.2.7, bumping it to 1.2.8 seems to solve the issue. |
config/checkstyle-config.xml
Outdated
@@ -1,7 +1,7 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE module PUBLIC | |||
"-//Puppy Crawl//DTD Check Configuration 1.3//EN" |
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.
Shell we update/fix the 'Puppy Crawl' comment as well?
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.
done
config/checkstyle-suppressions.xml
Outdated
@@ -1,6 +1,6 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE suppressions PUBLIC "-//Puppy Crawl//DTD Suppressions 1.1//EN" |
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.
And here still revers to Puppy Crawl as well
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.
done
@@ -360,6 +361,71 @@ public static void reloadEnvVariablesConfig() { | |||
EnvVariablesHolder.envVariables = loadEnvVariables(); | |||
} | |||
|
|||
private static AbstractConfigObject loadEnvVariablesOverrides() { | |||
Map<String, String> env = new HashMap(System.getenv()); |
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 don't mean to bike shed -- especially as I've seen you've written tests against this. I just though I'd add a suggestion which could (1) perhaps make the different override cases more easily scannable/readable and (2) make the test cases easier:
Instead of conflating the 'System.getenv()' call within the same method which does the override logic, you might consider having something like:
static String envVariableAsProperty(envVariable : String, envVarOverridePrefix : String) : String = {
... your code from line 377 here
}
which you can then just test via:
assertEquals(envVariableAsProperty("PREFIX_A_B__C", "PREFIX_"), "a.b-c")
assertEquals(envVariableAsProperty("PREFIX_A_B___C", "PREFIX_"), "a.b_c")
etc
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 makes totally sense, thanks for pushing me out from my laziness :-)
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.
Great work - thanks for (1) fixing the puppy crawl issue and (2) adding support for this feature, which obviates the need for my similar, but not as good implementation of the same thing in 'args4c'!
thanks for the review @aaronp ! ready for next round! :-) |
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.
Great work!
Thanks! Sorry I'm so slow on the reviews. |
@havocp I just wanted to say thank you for continuing to review and merge pull requests at all! |
Thanks @havocp ! any ETA on the next release? |
@andreaTP Seems like there is an issue with this feature, please see this bug report: playframework/playframework#10206 |
This PR propose to introduce an alternative
config.strategy
to enable the overwrite of configurations entries with environment variables even if a substitution is not explicitly provided in the configuration files.Rationale:
We want to be able to dynamically override potentially any configuration option with environment variables.
The current env variable substitution mechanism is verbose and have to be explicitly defined for every configuration ahead of time.
more on the topic is detailed here: https://github.com/codacy/env2props#rationale
Unfortunately the approach taken in env2props has a big drawback, getting the bash string substitution correct is not trivial and is error-prone.
Here I'm proposing to have an alternative implementation of the
config.strategy
that allow to obtain the very same behavior.Notes:
the mangling of env vars is needed because in a default bash environment: