Browse Source

Support extensionless file imports

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

Closes gh-22280
pull/23084/head
Phillip Webb 5 years ago
parent
commit
d0fce0553f
  1. 15
      spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc
  2. 13
      spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolver.java
  3. 12
      spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolverTests.java
  4. 1
      spring-boot-project/spring-boot/src/test/resources/application-props-no-extension

15
spring-boot-project/spring-boot-docs/src/docs/asciidoc/spring-boot-features.adoc

@ -698,6 +698,21 @@ If you want to support your own locations, see the `ConfigDataLocationResolver` @@ -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.

13
spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolver.java

@ -25,6 +25,7 @@ import java.util.Comparator; @@ -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 @@ -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 @@ -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 @@ -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;

12
spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/config/ResourceConfigDataLocationResolverTests.java

@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test; @@ -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 { @@ -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/";

1
spring-boot-project/spring-boot/src/test/resources/application-props-no-extension

@ -0,0 +1 @@ @@ -0,0 +1 @@
withnotext=test
Loading…
Cancel
Save