diff --git a/docs/src/main/asciidoc/spring-cloud-config.adoc b/docs/src/main/asciidoc/spring-cloud-config.adoc index 4bf1e230..2c30d903 100644 --- a/docs/src/main/asciidoc/spring-cloud-config.adoc +++ b/docs/src/main/asciidoc/spring-cloud-config.adoc @@ -143,7 +143,10 @@ the URL if you are using a command line client like curl (e.g. escape them from the shell with quotes ''). Spring Cloud Config Server supports a single or multiple git -repositories: +repositories with pattern matching on the application and profile +name. The pattern format is a comma-separated list of +`{application}/{profile}` names with wildcards (where a pattern +beginning with a wildcard may need to be quoted). Example: ---- spring: @@ -155,25 +158,59 @@ spring: repos: simple: https://github.com/simple/config-repo special: - pattern: pattern*,*pattern1* + pattern: special*/dev*,*special*/dev* uri: https://github.com/special/config-repo local: pattern: local* uri: file:/home/configsvc/config-repo ---- -In the above example, if `{application}` does not match any of the -patterns, it will use the default uri defined under -"spring.cloud.config.server.git.uri". For the "simple" repository, the -pattern is "simple" (i.e. it only matches one application named "simple"). -The pattern format is a comma-separated list of application names with -wildcards (a pattern beginning with a wildcard may need to be quoted). +If `{application}/{profile}` does not match any of the patterns, it +will use the default uri defined under +"spring.cloud.config.server.git.uri". In the above example, for the +"simple" repository, the pattern is `simple/\*` (i.e. it only matches +one application named "simple" in all profiles). The "local" +repository matches all application names beginning with "local" in all +profiles (the `/*` suffix is added automatically to any pattern that +doesn't have a profile matcher). NOTE: the "one-liner" short cut used in the "simple" example above can only be used if the only property to be set is the URI. If you need to set anything else (credentials, pattern, etc.) you need to use the full form. +The `pattern` property in the repo is actually an array, so you can +use a YAML array (or `[0]`, `[1]`, etc. suffixes in properties files) +to bind to multiple patterns. You may need to do this if you are going +to run apps with multiple profiles. Example: + +---- +spring: + cloud: + config: + server: + git: + uri: https://github.com/spring-cloud-samples/config-repo + repos: + development: + pattern: + - */development + - */staging + uri: https://github.com/development/config-repo + staging: + pattern: + - */qa + - */production + uri: https://github.com/staging/config-repo +---- + +NOTE: Spring Cloud will guess that a pattern containing a profile that +doesn't end in `\*` implies that you actually want to match a list of +profiles starting with this pattern (so `*/staging` is a shortcut for +`["\*/staging", "*/staging,*"]`). This is common where you need to run +apps in the "development" profile locally but also the "cloud" profile +remotely, for instance. + Every repository can also optionally store config files in sub-directories, and patterns to search for those directories can be specified as `searchPaths`. For example at the top level: diff --git a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java index e185603c..f262f097 100644 --- a/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java +++ b/spring-cloud-config-server/src/main/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepository.java @@ -16,7 +16,11 @@ package org.springframework.cloud.config.server; +import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import org.springframework.boot.context.properties.ConfigurationProperties; @@ -87,10 +91,10 @@ public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository return super.findOne(application, profile, label); } - public static class PatternMatchingJGitEnvironmentRepository extends - JGitEnvironmentRepository { + public static class PatternMatchingJGitEnvironmentRepository + extends JGitEnvironmentRepository { - private String[] pattern; + private String[] pattern = new String[0]; private String name; public PatternMatchingJGitEnvironmentRepository() { @@ -109,7 +113,8 @@ public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository return null; } - if (PatternMatchUtils.simpleMatch(this.pattern, application)) { + if (PatternMatchUtils.simpleMatch(this.pattern, + application + "/" + profile)) { return super.findOne(application, profile, label); } @@ -130,7 +135,27 @@ public class MultipleJGitEnvironmentRepository extends JGitEnvironmentRepository } public void setPattern(String[] pattern) { - this.pattern = pattern; + Collection patterns = new ArrayList<>(); + List otherProfiles = new ArrayList<>(); + for (String p : pattern) { + if (p != null) { + if (!p.contains("/")) { + // Match any profile + patterns.add(p + "/*"); + } + if (!p.endsWith("*")) { + // If user supplies only one profile, allow others + otherProfiles.add(p + ",*"); + } + } + patterns.add(p); + } + patterns.addAll(otherProfiles); + if (!patterns.contains(null)) { + // Make sure they are unique + patterns = new LinkedHashSet<>(patterns); + } + this.pattern = patterns.toArray(new String[0]); } } diff --git a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java index d4b6a279..20d3c312 100644 --- a/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java +++ b/spring-cloud-config-server/src/test/java/org/springframework/cloud/config/server/MultipleJGitEnvironmentRepositoryIntegrationTests.java @@ -30,8 +30,6 @@ import org.junit.Test; import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.cloud.config.environment.Environment; -import org.springframework.cloud.config.server.ConfigServerTestUtils; -import org.springframework.cloud.config.server.EnvironmentRepository; import org.springframework.cloud.config.server.config.EnvironmentRepositoryConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; @@ -39,6 +37,7 @@ import org.springframework.context.annotation.Import; /** * @author Andy Chan (iceycake) + * @author Dave Syer * */ public class MultipleJGitEnvironmentRepositoryIntegrationTests { @@ -49,25 +48,25 @@ public class MultipleJGitEnvironmentRepositoryIntegrationTests { @Before public void init() throws Exception { - if (basedir.exists()) { - FileUtils.delete(basedir, FileUtils.RECURSIVE); + if (this.basedir.exists()) { + FileUtils.delete(this.basedir, FileUtils.RECURSIVE); } ConfigServerTestUtils.deleteLocalRepo("config-copy"); } @After public void close() { - if (context != null) { - context.close(); + if (this.context != null) { + this.context.close(); } } @Test public void defaultRepo() throws IOException { String defaultRepoUri = ConfigServerTestUtils.prepareLocalRepo("config-repo"); - context = new SpringApplicationBuilder(TestConfiguration.class).web(false) + this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false) .properties("spring.cloud.config.server.git.uri:" + defaultRepoUri).run(); - EnvironmentRepository repository = context.getBean(EnvironmentRepository.class); + EnvironmentRepository repository = this.context.getBean(EnvironmentRepository.class); repository.findOne("bar", "staging", "master"); Environment environment = repository.findOne("bar", "staging", "master"); assertEquals(2, environment.getPropertySources().size()); @@ -80,27 +79,82 @@ public class MultipleJGitEnvironmentRepositoryIntegrationTests { Map repoMapping = new LinkedHashMap(); repoMapping.put("spring.cloud.config.server.git.repos[test1].pattern", "*test1*"); - repoMapping.put("spring.cloud.config.server.git.repos[test1].uri", test1RepoUri); - context = new SpringApplicationBuilder(TestConfiguration.class).web(false) + repoMapping.put("spring.cloud.config.server.git.repos[test1].uri", test1RepoUri); + this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false) .properties("spring.cloud.config.server.git.uri:" + defaultRepoUri) .properties(repoMapping).run(); - EnvironmentRepository repository = context.getBean(EnvironmentRepository.class); + EnvironmentRepository repository = this.context.getBean(EnvironmentRepository.class); repository.findOne("test1-svc", "staging", "master"); Environment environment = repository.findOne("test1-svc", "staging", "master"); assertEquals(2, environment.getPropertySources().size()); } + @Test + public void mappingRepoWithProfile() throws IOException { + String defaultRepoUri = ConfigServerTestUtils.prepareLocalRepo("config-repo"); + String test1RepoUri = ConfigServerTestUtils.prepareLocalRepo("test1-config-repo"); + + Map repoMapping = new LinkedHashMap(); + repoMapping.put("spring.cloud.config.server.git.repos[test1].pattern", "*/staging"); + repoMapping.put("spring.cloud.config.server.git.repos[test1].uri", test1RepoUri); + this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false) + .properties("spring.cloud.config.server.git.uri:" + defaultRepoUri) + .properties(repoMapping).run(); + EnvironmentRepository repository = this.context.getBean(EnvironmentRepository.class); + repository.findOne("test1-svc", "staging", "master"); + Environment environment = repository.findOne("test1-svc", "staging", "master"); + assertEquals(2, environment.getPropertySources().size()); + } + + @Test + public void mappingRepoWithProfileDefaultPatterns() throws IOException { + String defaultRepoUri = ConfigServerTestUtils.prepareLocalRepo("config-repo"); + String test1RepoUri = ConfigServerTestUtils.prepareLocalRepo("test1-config-repo"); + + Map repoMapping = new LinkedHashMap(); + repoMapping.put("spring.cloud.config.server.git.repos[test1].pattern", "*/staging"); + repoMapping.put("spring.cloud.config.server.git.repos[test1].uri", test1RepoUri); + this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false) + .properties("spring.cloud.config.server.git.uri:" + defaultRepoUri) + .properties(repoMapping).run(); + EnvironmentRepository repository = this.context.getBean(EnvironmentRepository.class); + repository.findOne("test1-svc", "staging", "master"); + Environment environment = repository.findOne("test1-svc", "staging,cloud", "master"); + assertEquals(2, environment.getPropertySources().size()); + } + + @Test + public void mappingRepoWithProfiles() throws IOException { + String defaultRepoUri = ConfigServerTestUtils.prepareLocalRepo("config-repo"); + String test1RepoUri = ConfigServerTestUtils.prepareLocalRepo("test1-config-repo"); + + Map repoMapping = new LinkedHashMap(); + repoMapping.put("spring.cloud.config.server.git.repos[test1].pattern[0]", "*/staging,*"); + repoMapping.put("spring.cloud.config.server.git.repos[test1].pattern[1]", "*/*,staging"); + repoMapping.put("spring.cloud.config.server.git.repos[test1].pattern[2]", "*/staging"); + repoMapping.put("spring.cloud.config.server.git.repos[test1].uri", test1RepoUri); + this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false) + .properties("spring.cloud.config.server.git.uri:" + defaultRepoUri) + .properties(repoMapping).run(); + EnvironmentRepository repository = this.context.getBean(EnvironmentRepository.class); + repository.findOne("test1-svc", "staging", "master"); + Environment environment = repository.findOne("test1-svc", "cloud,staging", "master"); + assertEquals(2, environment.getPropertySources().size()); + environment = repository.findOne("test1-svc", "staging,cloud", "master"); + assertEquals(2, environment.getPropertySources().size()); + } + @Test public void mappingRepoWithJustUri() throws IOException { String defaultRepoUri = ConfigServerTestUtils.prepareLocalRepo("config-repo"); String test1RepoUri = ConfigServerTestUtils.prepareLocalRepo("test1-config-repo"); Map repoMapping = new LinkedHashMap(); - repoMapping.put("spring.cloud.config.server.git.repos.test1-svc", test1RepoUri); - context = new SpringApplicationBuilder(TestConfiguration.class).web(false) + repoMapping.put("spring.cloud.config.server.git.repos.test1-svc", test1RepoUri); + this.context = new SpringApplicationBuilder(TestConfiguration.class).web(false) .properties("spring.cloud.config.server.git.uri:" + defaultRepoUri) .properties(repoMapping).run(); - EnvironmentRepository repository = context.getBean(EnvironmentRepository.class); + EnvironmentRepository repository = this.context.getBean(EnvironmentRepository.class); repository.findOne("test1-svc", "staging", "master"); Environment environment = repository.findOne("test1-svc", "staging", "master"); assertEquals(2, environment.getPropertySources().size());