Skip to content

Commit

Permalink
SE Config Profiles (helidon-io#3113)
Browse files Browse the repository at this point in the history
* Support for inlined config source in meta configuration

Signed-off-by: Tomas Langer <[email protected]>

* Support for config profiles in SE Config

Signed-off-by: Tomas Langer <[email protected]>

* Config profiles example.

Signed-off-by: Tomas Langer <[email protected]>

* Config profile documentation

Signed-off-by: Tomas Langer <[email protected]>
  • Loading branch information
tomas-langer authored Jul 8, 2021
1 parent 9776d26 commit 76b7d56
Show file tree
Hide file tree
Showing 29 changed files with 900 additions and 303 deletions.
28 changes: 0 additions & 28 deletions config/config/etc/checkstyle-suppressions.xml

This file was deleted.

5 changes: 5 additions & 0 deletions config/config/etc/spotbugs/exclude.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@
<Class name="io.helidon.config.OverrideSources"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>
<Match>
<!-- Path from config/code/system property, not openly from user -->
<Class name="io.helidon.config.MetaConfigFinder"/>
<Bug pattern="PATH_TRAVERSAL_IN"/>
</Match>
<Match>
<!-- Path from config/code, not openly from user -->
<Class name="io.helidon.config.ClasspathConfigSource"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.config;

import java.util.Map;

import io.helidon.config.spi.ConfigSource;

final class InlinedConfigSource {
private InlinedConfigSource() {
}

static ConfigSource create(Config config) {
return MapConfigSource.create(config.detach().asMap().orElse(Map.of()));
}
}
157 changes: 151 additions & 6 deletions config/config/src/main/java/io/helidon/config/MetaConfigFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import java.util.logging.Logger;

import io.helidon.common.media.type.MediaTypes;
import io.helidon.config.spi.ConfigNode.ListNode;
import io.helidon.config.spi.ConfigNode.ObjectNode;
import io.helidon.config.spi.ConfigSource;

/**
Expand All @@ -36,8 +38,36 @@
final class MetaConfigFinder {
/**
* System property used to set a file with meta configuration.
* This can also be used in combination with {@link #CONFIG_PROFILE_SYSTEM_PROPERTY}
* to define a custom location of profile specific files.
*/
public static final String META_CONFIG_SYSTEM_PROPERTY = "io.helidon.config.meta-config";
/**
* System property used to set a configuration profile. This profile is then used to discover
* meta configuration named {@code config-profile-${config.profile}.xxx"}.
*
* @see #CONFIG_PROFILE_ENVIRONMENT_VARIABLE
*/
public static final String CONFIG_PROFILE_SYSTEM_PROPERTY = "config.profile";

/**
* System property used to set a configuration profile. This profile is then used to discover
* meta configuration named {@code config-profile-${config.profile}.xxx"}.
* This property is Helidon specific (in case the {@link #CONFIG_PROFILE_SYSTEM_PROPERTY} is
* in use by another component).
*
* @see #CONFIG_PROFILE_ENVIRONMENT_VARIABLE
* @see #CONFIG_PROFILE_SYSTEM_PROPERTY
*/
public static final String HELIDON_CONFIG_PROFILE_SYSTEM_PROPERTY = "helidon.config.profile";

/**
* Environment variable used to set a configuration profile.
* Environment variable is the most significant.
*
* @see #CONFIG_PROFILE_SYSTEM_PROPERTY
*/
public static final String CONFIG_PROFILE_ENVIRONMENT_VARIABLE = "HELIDON_CONFIG_PROFILE";

private static final Logger LOGGER = Logger.getLogger(MetaConfigFinder.class.getName());
private static final List<String> CONFIG_SUFFIXES = List.of("yaml", "conf", "json", "properties");
Expand Down Expand Up @@ -67,23 +97,138 @@ private static Optional<ConfigSource> findMetaConfigSource(Function<String, Bool
Optional<ConfigSource> source;

// check if meta configuration is configured using system property
String property = System.getProperty(META_CONFIG_SYSTEM_PROPERTY);
if (null != property) {
String metaConfigFile = System.getProperty(META_CONFIG_SYSTEM_PROPERTY);
// check name of the profile
String profileName = System.getenv(CONFIG_PROFILE_ENVIRONMENT_VARIABLE);
if (profileName == null) {
profileName = System.getProperty(HELIDON_CONFIG_PROFILE_SYSTEM_PROPERTY);
}
if (profileName == null) {
profileName = System.getProperty(CONFIG_PROFILE_SYSTEM_PROPERTY);
}

if (metaConfigFile != null && profileName != null) {
// we have both profile name and meta configuration file defined
// this means we want to have a custom profile file (maybe a custom location) combined with a profile
int lastDot = metaConfigFile.lastIndexOf('.');
String metaWithProfile;
if (lastDot == 0) {
// .configuration -> dev.configuration
metaWithProfile = profileName + metaConfigFile.substring(lastDot);
} else if (lastDot > 0) {
// config/profile/profile.yaml -> config/profile/profile-dev.yaml
metaWithProfile = metaConfigFile.substring(0, lastDot) + "-" + profileName + metaConfigFile.substring(lastDot);
} else {
// config/configuration -> config/configuration-dev
metaWithProfile = metaConfigFile + "-" + profileName;
}
source = findFile(metaWithProfile, "config profile");
if (source.isPresent()) {
return source;
}
source = findClasspath(cl, metaWithProfile, "config profile");
if (source.isPresent()) {
return source;
}
LOGGER.info("Custom profile file not found: " + metaWithProfile);
}
if (metaConfigFile == null) {
if (profileName != null) {
return Optional.of(profileSource(supportedMediaType, cl, profileName, supportedSuffixes));
}
} else {
// is it a file
source = findFile(property, "meta configuration");
source = findFile(metaConfigFile, "meta configuration");
if (source.isPresent()) {
return source;
}
// so it is a classpath resource?
source = findClasspath(cl, property, "meta configuration");
source = findClasspath(cl, metaConfigFile, "meta configuration");
if (source.isPresent()) {
return source;
}

LOGGER.info("Meta configuration file not found: " + property);
LOGGER.info("Meta configuration file not found: " + metaConfigFile);
}

return findSource(supportedMediaType, cl, META_CONFIG_PREFIX, "meta configuration", supportedSuffixes);
return findSource(supportedMediaType, cl, META_CONFIG_PREFIX, "meta configuration", supportedSuffixes)
.or(() -> findSource(supportedMediaType, cl, "config-profile.", "config profile", supportedSuffixes));
}

private static ConfigSource profileSource(Function<String, Boolean> supportedMediaType,
ClassLoader cl,
String profileName,
List<String> supportedSuffixes) {
// first try to find the profile itself
// default name is `config-profile.xxx`, we start with `config-profile-${profile}.xxx`
String profileFileName = "config-profile-" + profileName + ".";
// first find files for each supported suffix (first one wins)
for (String supportedSuffix : supportedSuffixes) {
Optional<ConfigSource> profile = findFile(profileFileName + supportedSuffix, "config profile");
if (profile.isPresent()) {
return profile.get();
}
}
// now let's do the same thing with classpath
for (String supportedSuffix : supportedSuffixes) {
Optional<ConfigSource> profile = findClasspath(cl, profileFileName + supportedSuffix, "config profile");
if (profile.isPresent()) {
return profile.get();
}
}
// we did not find a config profile, let's create one with the usual suspects
// to make things easier, let's try both application.xxx and META-INF/microprofile-config.properties
ListNode.Builder sourceListBuilder = ListNode.builder();

sourceListBuilder.addObject(ObjectNode.builder().addValue("type", "environment-variables").build())
.addObject(ObjectNode.builder().addValue("type", "system-properties").build());

// all profile files
for (String supportedSuffix : supportedSuffixes) {
addFile(sourceListBuilder, "application-" + profileName, supportedSuffix);
}

// all profile classpath
for (String supportedSuffix : supportedSuffixes) {
addClasspath(sourceListBuilder, "application-" + profileName, supportedSuffix);
}

// all main files
for (String supportedSuffix : supportedSuffixes) {
addFile(sourceListBuilder, "application", supportedSuffix);
}

// all main classpath
for (String supportedSuffix : supportedSuffixes) {
addClasspath(sourceListBuilder, "application", supportedSuffix);
}

addClasspath(sourceListBuilder, "META-INF/microprofile-config-" + profileName, "properties");
addClasspath(sourceListBuilder, "META-INF/microprofile-config", "properties");

return ConfigSources.create(ObjectNode.builder()
.addList("sources", sourceListBuilder.build())
.build());
}

private static void addClasspath(ListNode.Builder sourceListBuilder, String fileName, String supportedSuffix) {
sourceListBuilder.addObject(ObjectNode.builder()
.addValue("type", "classpath")
.addObject("properties", ObjectNode.builder()
.addValue("resource", fileName + "." + supportedSuffix)
.addValue("optional", "true")
.build())
.build());
}

private static void addFile(ListNode.Builder sourceListBuilder, String fileName, String supportedSuffix) {
sourceListBuilder.addObject(ObjectNode.builder()
.addValue("type", "file")
.addObject("properties", ObjectNode.builder()
.addValue("path", fileName + "." + supportedSuffix)
.addValue("optional", "true")
.build())
.build());
}

private static Optional<ConfigSource> findSource(Function<String, Boolean> supportedMediaType,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019, 2020 Oracle and/or its affiliates.
* Copyright (c) 2019, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -265,6 +265,7 @@ private static final class BuiltInConfigSourcesProvider implements ConfigSourceP
private static final String DIRECTORY_TYPE = "directory";
private static final String URL_TYPE = "url";
private static final String PREFIXED_TYPE = "prefixed";
private static final String INLINED_TYPE = "inlined";

private static final Map<String, Function<Config, ConfigSource>> BUILT_INS = new HashMap<>();

Expand All @@ -277,6 +278,7 @@ private static final class BuiltInConfigSourcesProvider implements ConfigSourceP
BUILT_INS.put(DIRECTORY_TYPE, DirectoryConfigSource::create);
BUILT_INS.put(URL_TYPE, UrlConfigSource::create);
BUILT_INS.put(PREFIXED_TYPE, PrefixedConfigSource::create);
BUILT_INS.put(INLINED_TYPE, InlinedConfigSource::create);
}

@Override
Expand Down
4 changes: 3 additions & 1 deletion config/config/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2020 Oracle and/or its affiliates.
* Copyright (c) 2017, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,6 +18,8 @@

/**
* Helidon SE Config module.
*
* @see io.helidon.config
*/
module io.helidon.config {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
* Copyright (c) 2020, 2021 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -211,6 +211,30 @@ public void testPrefixed() {
assertThat(config.get("this.is.prefix.key.app.page-size").asInt().get(), is(10));
}

@Test
public void testInlined() {
Config metaConfig = builderFrom(ConfigSources.create(
ObjectNode.builder()
.addValue("type", "inlined")
.addObject("properties", ObjectNode.builder()
.addValue("key", "inlined-value")
.addObject("server", ObjectNode.builder()
.addValue("port", "8014")
.addValue("host", "localhost")
.build())
.build())
.build()))
.build();

ConfigSource source = singleSource(metaConfig);

Config config = justFrom(source);

assertThat(config.get("key").asString().get(), is("inlined-value"));
assertThat(config.get("server.port").asInt().get(), is(8014));
assertThat(config.get("server.host").asString().get(), is("localhost"));
}

private ConfigSource singleSource(Config metaConfig) {
List<ConfigSource> sources = metaConfig.as(MetaConfig::configSource).get();

Expand Down
24 changes: 12 additions & 12 deletions docs/se/config/01_introduction.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,8 @@ filters, overrides, mappers, whether or not environment variables and Java
system properties serve as config sources. The JavaDoc explains how to use the
link:{javadoc-base-url-api}/Config.Builder.html[`Config.Builder`].
+
or
* creating a <<config/06_advanced-configuration.adoc#Config-Advanced-metaconfig,meta-configuration>>
* using a <<se/config/09_config-profiles.adoc,config profile>> to choose the sources to be used
* creating a <<se/config/06_advanced-configuration.adoc#Config-Advanced-metaconfig, meta-configuration>>
file on the runtime classpath or file system to control how the config system prepares the
default configuration.
Expand All @@ -325,26 +325,26 @@ other config topics.
|===
| Topic |Documentation
| Where config comes from |<<config/02_config-sources.adoc,Config sources>>,
<<config/06_advanced-configuration.adoc#metaconfig,meta-configuration>>
| What format config data is expressed in |<<config/02_config-sources.adoc#parsers,Config parsers>>,
<<config/08_supported-formats.adoc,supported formats>>
| How to filter, override, and dereference values |<<config/06_advanced-configuration.adoc#filters-and-overrides,Filters and overrides>>
| What happens when config data changes |<<config/05_mutability-support.adoc#polling,Config polling>>
| How to deal with loading errors |<<config/02_config-sources.adoc#retry,Config retry policies>>
| Where config comes from |<<se/config/02_config-sources.adoc,Config sources>>,<<se/config/09_config-profiles.adoc,Config Profiles>>,
<<se/config/06_advanced-configuration.adoc#Config-Advanced-metaconfig,meta-configuration>>
| What format config data is expressed in |<<se/config/02_config-sources.adoc#parsers,Config parsers>>,
<<se/config/08_supported-formats.adoc,supported formats>>
| How to filter, override, and dereference values |<<se/config/06_advanced-configuration.adoc#filters-and-overrides,Filters and overrides>>
| What happens when config data changes |<<se/config/05_mutability-support.adoc#polling,Config polling>>
| How to deal with loading errors |<<se/config/02_config-sources.adoc#retry,Config retry policies>>
|===
.Accessing Configuration Data
|===
| Topic |Documentation
| How config data is translated into Java types |<<config/04_property-mapping.adoc,Config mappers>>
| How to navigate config trees |<<config/03_hierarchical-features.adoc,Navigation>>
| How config data is translated into Java types |<<se/config/04_property-mapping.adoc,Config mappers>>
| How to navigate config trees |<<se/config/03_hierarchical-features.adoc,Navigation>>
|===
.Extending and Fine-tuning the Config System
|===
| Topic |Documentation
| Writing extensions |<<config/07_extensions.adoc,Extensions>>
| Writing extensions |<<se/config/07_extensions.adoc,Extensions>>
|===
Loading

0 comments on commit 76b7d56

Please sign in to comment.