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

Split out a helper method for parsing only application overrides #708

Merged
merged 9 commits into from
Sep 23, 2020

Conversation

bbaldino
Copy link
Contributor

This came out of a discussion in gitter with @havocp. I had a need to be able to write a custom Config load which would allow an override (via -Dconfig.*) but also fallback to application.conf, even if an override were present. Previously the logic to parse overrides existed only in DefaultConfigLoadingStrategy#parseApplicationConfig, which would replace any application resource with the -Dconfig.* file. This PR moves the logic for parsing the overrides into a separate helper method, which is now used by DefaultConfigLoadingStrategy#parseApplicationConfig but can be used elsewhere as well.

A few questions on things that may need changing:

  1. Is the argument (ConfigParseOptions) to parseApplicationOverride acceptable? There should probably be another version which takes no arguments and passes ConfigParseOptions.defaults().
  2. Is there any issue with returning ConfigImpl.emptyConfig in the case where no override was specified? This requires an origin description and I wasn't sure what would be best to put there, maybe there's a better object to return.
  3. Is throwing from ConfigFactory#parseApplicationOverride acceptable (in the case where no override files were given). Maybe it's better to return empty here as well?

@lightbend-cla-validator
Copy link
Collaborator

Hi @bbaldino,

Thank you for your contribution! We really value the time you've taken to put this together.

Before we proceed with reviewing this pull request, please sign the Lightbend Contributors License Agreement:

https://www.lightbend.com/contribute/cla

@bbaldino
Copy link
Contributor Author

For a specific use case, this PR allows me to do:

ConfigFactory.parseApplicationOverride(ConfigParseOptions.defaults())
    .withFallback(ConfigFactory.parseResourcesAnySyntax("application"))
    .withFallback(ConfigFactory.defaultReference())

Copy link
Collaborator

@havocp havocp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Re: your questions,

  1. Yeah there are various overloads for everything on ConfigFactory and it's a bit annoying (and also hard to get right). The precedent from defaultApplication would be to have one that takes no options and one that takes just a class loader; following that pattern seems reasonable to me.
  2. notes inline, I think use the ensureClassLoader method
  3. I think throwing is right, it's better than silently falling back to application.conf (would be pretty hard to debug why my -Dconfig.whatever was being ignored in that case). Also, it keeps the current behavior, which is nice if in doubt.

@@ -8,7 +8,7 @@

import java.io.File;
import java.io.Reader;
import java.net.URL;
import java.net.*;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My memory isn't 100% but I think the codebase may mostly avoid wildcard imports

@@ -1090,6 +1090,63 @@ public static Config parseResourcesAnySyntax(String resourceBasename) {
return parseResourcesAnySyntax(resourceBasename, ConfigParseOptions.defaults());
}

/**
* Parse only any application overrides (those specified by config.{resource,file,url}), returning
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "override" already has a different meaning for ConfigFactory.defaultOverrides so I wonder what else we could call this ... the README uses the word "replacement" perhaps? parseApplicationReplacement?

if (loader == null)
throw new ConfigException.BugOrBroken(
"ClassLoader should have been set here; bug in ConfigFactory. "
+ "(You can probably work around this bug by passing in a class loader or calling currentThread().setContextClassLoader() though.)");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably follow the pattern in the rest of ConfigFactory and call ensureClassLoader rather than expecting it to be already set. The assertion here was because ConfigFactory is supposed to be the only caller of the strategy and it always does the ensureClassLoader. So here you can use ensureClassLoader which already throws if it can't find a loader.

specified += 1;

if (specified == 0) {
return ConfigImpl.emptyConfig("TODO: what to put here? Should something else be returned?");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use Java 8 Optional ... ? I don't know what current best practice is around that, I haven't Java'd in a few years. I think there is a difference here between "something was specified and it was empty" and "nothing was specified."

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds good. Other option would be to return null I guess, but the way Optional allows orElseGet in DefaultConfigLoadingStrategy is nice, so will use Optional.

specified += 1;

if (specified == 0) {
Config overrideConfig = ConfigFactory.parseApplicationOverride(parseOptions);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we rename to replacement or something I'd carry it through variable names and terminology in comments


if (specified == 0) {
Config overrideConfig = ConfigFactory.parseApplicationOverride(parseOptions);
if (overrideConfig.isEmpty()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

with optional it could be orElseGet(() -> ConfigFactory.parseResourcesAnySyntax("application", parseOptions)) maybe

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should .gitignore these probably and remove from the PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 Will remove this.

@bbaldino
Copy link
Contributor Author

bbaldino commented Sep 23, 2020

Thanks for the comments, @havocp ! I think I addressed everything. One unfortunate thing I found about the Optional is when the code is used outside, e.g. my use case now becomes:

ConfigFactory.parseApplicationOverride().orElseGet(() -> ConfigFactory.parseString(""))
    .withFallback(ConfigFactory.parseResourcesAnySyntax("application"))
    .withFallback(ConfigFactory.defaultReference())

But it's not terrible. Is there some kind of static EmptyConfig instance that could be used in places like this? Or maybe there's a better way to write this logic.

@havocp
Copy link
Collaborator

havocp commented Sep 23, 2020

Yep there's a ConfigFactory.empty() I think.

One other thought here is to add @since in javadocs; I believe the next release will be 1.4.1 #702

@havocp
Copy link
Collaborator

havocp commented Sep 23, 2020

(for since tags, there are some existing examples I believe to copy)

@bbaldino
Copy link
Contributor Author

Yep there's a ConfigFactory.empty() I think.

Ah, great.

One other thought here is to add @since in javadocs; I believe the next release will be 1.4.1 #702

Done. 👍

@havocp havocp merged commit 2d43727 into lightbend:master Sep 23, 2020
@havocp
Copy link
Collaborator

havocp commented Sep 23, 2020

Thanks!

@havocp havocp mentioned this pull request Sep 23, 2020
@bbaldino bbaldino deleted the parse_app_overrides branch September 23, 2020 19:36
*/
public static java.util.Optional<Config> parseApplicationReplacement(ConfigParseOptions parseOptions) {
ensureClassLoader(parseOptions, "parseApplicationReplacement");
ClassLoader loader = parseOptions.getClassLoader();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, oops - I didn't catch this before merging, but ensureClassLoader returns the new options, doesn't modify the original options.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. I can open a follow up fix PR today.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lukaszlenart
Copy link
Contributor

I think this PR broke backward compatibility. We have been using version 1.4.0 and it was possible to override any config options by using -Dconfig.override_with_env_vars=true and then setting up an env variable, eg.: cms.dgraph.hosts.2=... when having such application.conf

  cms {
    dgraph {
      hosts = ["dgraph-host"]
      port = 9080
    }
...

After switching to version 1.4.1 this stopped working and now we must use CONFIG_FORCE_cms_dgraph_hosts_2=...

I know that this is against the docs but it worked, so in such case you broke backward comaptibility.

@lukaszlenart
Copy link
Contributor

My bad, this isn't this PR but this commit - this should be pointed out in Version Notes.

@lukaszlenart
Copy link
Contributor

I found a workaround but I need to test it

val config = ConfigFactory.parseMap(System.getenv()).withFallback(ConfigFactory.load())

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants