From 2043b05bbe8cda91037fbaf464f5c47c8855ef07 Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Fri, 29 Jan 2016 10:04:47 +0000 Subject: [PATCH] Add placeholder substitution to search paths within SCM repository E.g. ...searchPaths={application}/ to search the directory with the same name as the application (in addition to the root). Fixes gh-328 --- .../main/asciidoc/spring-cloud-config.adoc | 22 +++++ .../JGitEnvironmentRepository.java | 2 +- .../NativeEnvironmentRepository.java | 7 +- .../SvnKitEnvironmentRepository.java | 14 +-- .../server/support/AbstractScmAccessor.java | 86 +++++++++++++++---- .../JGitEnvironmentRepositoryTests.java | 13 +++ 6 files changed, 119 insertions(+), 25 deletions(-) diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index e9e9b1e5..464b0885 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -315,6 +315,28 @@ that should be applicable. HTTPS proxy settings can be set in `~/.git/config` or in the same way as for any other JVM process via system properties (`-Dhttps.proxyHost` and `-Dhttps.proxyPort`). +===== Placeholders in Git Search Paths + +Spring Cloud Config Server also supports a search path with +placeholders for the `{application}` and `{profile}` (and `{label}` if +you need it). Example: + +[source,yaml] +---- +spring: + cloud: + config: + server: + git: + uri: https://github.com/spring-cloud-samples/config-repo + searchPaths: {application} +---- + +searches the repository for files in the same name as the directory +(as well as the top level). Wildcards are also valid in a search +path with placeholders (any matching directory is included in the +search). + ==== File System Backend There is also a "native" profile in the Config Server that doesn't use diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java index a7556436..29ec937d 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepository.java @@ -124,7 +124,7 @@ public class JGitEnvironmentRepository extends AbstractScmEnvironmentRepository version = ref.getObjectId().getName(); } return new Locations(application, profile, label, version, - getSearchLocations(getWorkingDirectory())); + getSearchLocations(getWorkingDirectory(), application, profile, label)); } @Override diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java index 2dff7fba..cbffd64d 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/NativeEnvironmentRepository.java @@ -146,8 +146,11 @@ public class NativeEnvironmentRepository } } for (String location : locations) { - if (isDirectory(location) && StringUtils.hasText(label)) { - output.add(location + label.trim() + "/"); + if (StringUtils.hasText(label)) { + String labelled = location + label.trim() + "/"; + if (isDirectory(labelled)) { + output.add(labelled); + } } } return new Locations(application, profile, label, this.version, diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java index ceee7f2e..237b1031 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/environment/SvnKitEnvironmentRepository.java @@ -54,7 +54,7 @@ public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepositor private String defaultLabel = DEFAULT_LABEL; public String getDefaultLabel() { - return this.defaultLabel ; + return this.defaultLabel; } public void setDefaultLabel(String defaultLabel) { @@ -62,8 +62,9 @@ public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepositor } @Override - public synchronized Locations getLocations(String application, String profile, String label) { - if (label==null) { + public synchronized Locations getLocations(String application, String profile, + String label) { + if (label == null) { label = this.defaultLabel; } SvnOperationFactory svnOperationFactory = new SvnOperationFactory(); @@ -80,7 +81,8 @@ public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepositor else { version = checkout(svnOperationFactory); } - return new Locations(application, profile, label, version, getLocations(label)); + return new Locations(application, profile, label, version, + getPaths(application, profile, label)); } catch (SVNException e) { throw new IllegalStateException("Cannot checkout repository", e); @@ -90,8 +92,8 @@ public class SvnKitEnvironmentRepository extends AbstractScmEnvironmentRepositor } } - private String[] getLocations(String label) { - String[] locations = getSearchLocations(getSvnPath(getWorkingDirectory(), label)); + private String[] getPaths(String application, String profile, String label) { + String[] locations = getSearchLocations(getSvnPath(getWorkingDirectory(), label), application, profile, label); boolean exists = false; for (String location : locations) { location = location.startsWith("file:") ? location.substring("file:".length()) diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AbstractScmAccessor.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AbstractScmAccessor.java index 341de8ee..43b600c7 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AbstractScmAccessor.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/support/AbstractScmAccessor.java @@ -20,14 +20,20 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.eclipse.jgit.util.FileUtils; +import org.springframework.context.ResourceLoaderAware; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.UrlResource; -import org.springframework.util.PatternMatchUtils; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.util.StringUtils; /** @@ -36,7 +42,9 @@ import org.springframework.util.StringUtils; * @author Dave Syer * */ -public class AbstractScmAccessor { +public class AbstractScmAccessor implements ResourceLoaderAware { + + private static final String[] DEFAULT_LOCATIONS = new String[] {"/"}; protected Log logger = LogFactory.getLog(getClass()); /** @@ -59,13 +67,20 @@ public class AbstractScmAccessor { /** * Search paths to use within local working copy. By default searches only the root. */ - private String[] searchPaths = new String[0]; + private String[] searchPaths = DEFAULT_LOCATIONS; + + private ResourceLoader resourceLoader = new DefaultResourceLoader(); public AbstractScmAccessor(ConfigurableEnvironment environment) { this.environment = environment; this.basedir = createBaseDir(); } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + protected File createBaseDir() { try { final File basedir = Files.createTempDirectory("config-repo-").toFile(); @@ -76,7 +91,8 @@ public class AbstractScmAccessor { FileUtils.delete(basedir, FileUtils.RECURSIVE); } catch (IOException e) { - AbstractScmAccessor.this.logger.warn("Failed to delete temporary directory on exit: " + e); + AbstractScmAccessor.this.logger.warn( + "Failed to delete temporary directory on exit: " + e); } } }); @@ -100,7 +116,7 @@ public class AbstractScmAccessor { uri = uri.substring(0, uri.length() - 1); } int index = uri.indexOf("://"); - if (index>0 && !uri.substring(index+"://".length()).contains("/")) { + if (index > 0 && !uri.substring(index + "://".length()).contains("/")) { // If there's no context path add one uri = uri + "/"; } @@ -149,25 +165,63 @@ public class AbstractScmAccessor { return new UrlResource(StringUtils.cleanPath(this.uri)).getFile(); } catch (Exception e) { - throw new IllegalStateException("Cannot convert uri to file: " + this.uri); + throw new IllegalStateException( + "Cannot convert uri to file: " + this.uri); } } return this.basedir; } - protected String[] getSearchLocations(File dir) { - List locations = new ArrayList(); - locations.add(dir.toURI().toString()); - String[] list = dir.list(); - if (list!=null) { - for (String path : list) { - File file = new File(dir, path); - if (file.isDirectory() && PatternMatchUtils.simpleMatch(this.searchPaths, path)) { - locations.add(file.toURI().toString()); + protected String[] getSearchLocations(File dir, String application, String profile, + String label) { + String[] locations = this.searchPaths; + if (locations == null || locations.length == 0) { + locations = DEFAULT_LOCATIONS; + } else { + locations = StringUtils.concatenateStringArrays(DEFAULT_LOCATIONS, locations); + } + Collection output = new LinkedHashSet(); + for (String location : locations) { + String[] profiles = new String[] { profile }; + if (profile != null) { + profiles = StringUtils.commaDelimitedListToStringArray(profile); + } + for (String prof : profiles) { + String value = location; + if (application != null) { + value = value.replace("{application}", application); + } + if (prof != null) { + value = value.replace("{profile}", prof); + } + if (label != null) { + value = value.replace("{label}", label); + } + if (!value.endsWith("/")) { + value = value + "/"; + } + output.addAll(matchingDirectories(dir, value)); + } + } + return output.toArray(new String[0]); + } + + private List matchingDirectories(File dir, String value) { + List output = new ArrayList(); + try { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver( + this.resourceLoader); + String path = new File(dir, value).toURI().toString(); + for (Resource resource : resolver.getResources(path)) { + if (resource.getFile().isDirectory()) { + output.add(resource.getURI().toString()); } } } - return locations.toArray(new String[0]); + catch (IOException e) { + } + return output; } + } \ No newline at end of file diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java index d1689918..2bfa8b3e 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/environment/JGitEnvironmentRepositoryTests.java @@ -83,6 +83,19 @@ public class JGitEnvironmentRepositoryTests { assertVersion(environment); } + @Test + public void placeholderInSearchPath() throws IOException { + String uri = ConfigServerTestUtils.prepareLocalRepo("another-config-repo"); + this.repository.setUri(uri); + this.repository.setSearchPaths(new String[] {"{application}"}); + this.repository.findOne("sub", "staging", "master"); + Environment environment = this.repository.findOne("sub", "staging", "master"); + assertEquals(1, environment.getPropertySources().size()); + assertEquals(this.repository.getUri() + "/sub/application.yml", + environment.getPropertySources().get(0).getName()); + assertVersion(environment); + } + private void assertVersion(Environment environment) { String version = environment.getVersion(); assertNotNull("version was null", version);