Support extensionless file imports

Update `ResourceConfigDataLocationResolver` so that it can import files
that have no file extension.

Closes gh-22280
This commit is contained in:
Phillip Webb 2020-08-25 13:57:10 -07:00
parent 081a7ee28c
commit d0fce0553f
4 changed files with 39 additions and 2 deletions

View File

@ -698,6 +698,21 @@ If you want to support your own locations, see the `ConfigDataLocationResolver`
==== Importing Extensionless Files
Some cloud platforms cannot add a file extension to volume mounted files.
To import these extensionless files, you need to give Spring Boot a hint so that it knows how to load them.
You can do this by putting an extension hint in square brackets.
For example, suppose you have a `/etc/config/myconfig` file that you wish to import as yaml.
You can import it from your `application.properties` using the following:
[source,properties,indent=0]
----
spring.config.import=file:/etc/config/myconfig[.yaml]
----
[[boot-features-external-config-files-configtree]]
==== Using Configuration Trees
When running applications on a cloud platform (such as Kubernetes) you often need to read config values that the platform supplies.

View File

@ -25,6 +25,7 @@ import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
@ -63,6 +64,8 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
private static final Pattern URL_PREFIX = Pattern.compile("^([a-zA-Z][a-zA-Z0-9*]*?:)(.*$)");
private static final Pattern EXTENSION_HINT_PATTERN = Pattern.compile("^(.*)\\[(\\.\\w+)\\](?!\\[)$");
private static final String NO_PROFILE = null;
private final Log logger;
@ -183,11 +186,17 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
}
private Set<Resolvable> getResolvablesForFile(String fileLocation, boolean optional, String profile) {
Matcher extensionHintMatcher = EXTENSION_HINT_PATTERN.matcher(fileLocation);
boolean extensionHintLocation = extensionHintMatcher.matches();
if (extensionHintLocation) {
fileLocation = extensionHintMatcher.group(1) + extensionHintMatcher.group(2);
}
for (PropertySourceLoader loader : this.propertySourceLoaders) {
String extension = getLoadableFileExtension(loader, fileLocation);
if (extension != null) {
String root = fileLocation.substring(0, fileLocation.length() - extension.length() - 1);
return Collections.singleton(new Resolvable(null, root, optional, profile, extension, loader));
return Collections.singleton(new Resolvable(null, root, optional, profile,
(!extensionHintLocation) ? extension : null, loader));
}
}
throw new IllegalStateException("File extension is not known to any PropertySourceLoader. "
@ -343,7 +352,7 @@ class ResourceConfigDataLocationResolver implements ConfigDataLocationResolver<R
PropertySourceLoader loader) {
String profileSuffix = (StringUtils.hasText(profile)) ? "-" + profile : "";
this.directory = directory;
this.resourceLocation = rootLocation + profileSuffix + "." + extension;
this.resourceLocation = rootLocation + profileSuffix + ((extension != null) ? "." + extension : "");
this.optional = optional;
this.profile = profile;
this.loader = loader;

View File

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.env.PropertiesPropertySourceLoader;
import org.springframework.boot.logging.DeferredLog;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.ResourceLoader;
@ -206,6 +207,17 @@ public class ResourceConfigDataLocationResolverTests {
.satisfies((ex) -> assertThat(ex.getCause()).hasMessageStartingWith("File extension is not known"));
}
@Test
void resolveWhenLocationUsesOptionalExtensionSyntaxResolves() throws Exception {
String location = "classpath:/application-props-no-extension[.properties]";
List<ResourceConfigDataLocation> locations = this.resolver.resolve(this.context, location, true);
assertThat(locations.size()).isEqualTo(1);
ResourceConfigDataLocation resolved = locations.get(0);
assertThat(resolved.getResource().getFilename()).endsWith("application-props-no-extension");
PropertySource<?> propertySource = resolved.load().get(0);
assertThat(propertySource.getProperty("withnotext")).isEqualTo("test");
}
@Test
void resolveProfileSpecificReturnsProfileSpecificFiles() {
String location = "classpath:/configdata/properties/";

View File

@ -0,0 +1 @@
withnotext=test